Использование предварительно скомпилированной разделяемой библиотеки C с JNI / NDK - PullRequest
0 голосов
/ 11 сентября 2018

В настоящее время я занимаюсь разработкой небольшого демо-приложения для Android с интеграцией C ++. Я использую Android Studio 3.1.4, и мой проект поддерживает NDK (среда разработки Windows 10 ).

Цель приложения - загрузить файл общей библиотеки (.so) из хранилища (sdcard), а затем динамически связать библиотеку.

Вот шаги, которые я прошел, чтобы создать это приложение:

1. Использование GCC в Cygwin для создания общей библиотеки

Это файлы, которые я использовал для компиляции библиотеки

"sumLib.h":

#define _MYLIB_H_
#define MAX_INPUT 25
extern double sum(double x, double y);

"sumLib.c":

#include <stdlib.h>
#include "sumLib.h"
double sum(double x, double y) {
    return x + y;
}

Затем я использовал следующие команды для создания файла библиотеки:

gcc -shared -o sumLib.o -c sumLib.c
gcc -shared -o sumLib.so sumLib.o

2. Загрузить файл библиотеки на клиенте Android

Я перенес файл на SD-карту эмулятора Android (используя Device File Explored) (/ storage / emulated / 0 / Download / sumLib.so)

Вот код C ++, который читает библиотеку и связывает ее с символом "sum" :

extern "C" {
JNIEXPORT jdouble JNICALL
Java_com_example_user_demo_MainActivity_sum(JNIEnv *env, jobject obj, jdouble n1, jdouble n2) {
    void *handle;
    double (*sum)(double, double);
    char *error;

    char fname[PATH_MAX];
    strcpy(fname, internalPath); //internal path is a predefined string that points to the Download folder in the SD card
    strcat(fname, "/sumLib.so");

    handle = dlopen(fname, RTLD_LAZY);
    if (!handle) {
        __android_log_write(ANDROID_LOG_ERROR, "Creating handle", "Error creating handle");
        exit(EXIT_FAILURE);
    }

    dlerror();

    *(void **) (&sum) = dlsym(handle, "sum");

    if ((error = dlerror()) != NULL) {
        __android_log_write(ANDROID_LOG_ERROR, "Error linking symbol", error);
        exit(EXIT_FAILURE);
    }

    double result = (*sum)(n1, n2);
    dlclose(handle);
    return result;
}
}

Вот код Java, который вызывает функцию c ++:

public native double sum(double n1, double n2);

// function was called after the click of a button
public void onClickTestBtn(View v) {

    // set up the text view to display the result
    TextView tv = findViewById(R.id.sample_text);
    double result = sum(134.3, 11.3);
    tv.setText(String.format("Result is %f", result));
}

Теоретически это должно вызвать функцию «сумма» , определенную с помощью c ++, и использовать библиотеку для поиска символа «сумма» и выполнения операции, но dlopen не может создать дескриптор из файла, он продолжал выдавать ошибки.

Вот сообщение об ошибке:

"dlopen failed: library \"/storage/emulated/0/Download/sumLib.so\" needed or dlopened by \"/data/app/com.example.user.demo-fzNLN7tBu86LCNun1yLQlg==/lib/x86_64/libnative-lib.so\" is not accessible for the namespace \"classloader-namespace\""

Я заметил в конце адреса файла в сообщении об ошибке, что они добавили прямую косую черту вместо обратной косой черты. Также я не уверен, что означает вторая половина сообщения об ошибке.

Я считал, что GCC на Cygwin может компилироваться на архитектуре, отличной от той, на которой работает эмулятор Android, но после проверки файла я обнаружил, что он был скомпилирован для x64 (согласно на это , я нашел PE d + от чтения файла). Для эмулятора я запускаю образ x86_64 (API 27), и он напечатал x86_64 после того, как я позвонил:

String arch = System.getProperty("os.arch");
Log.d("Debug", String.format("system arch is %s", arch));

Я также проверил, доступен ли файл из среды c ++, поэтому я использовал

FILE *f = fopen(fname, "r");

и в отладчике он не указывал на ноль, он указывал на что-то, 0x00007227dd414018 , если быть точным, безопасно ли делать предположение, что он нашел файл и может правильно его прочитать?

Я не уверен, как исправить эту ошибку, которую dlopen дал мне, что касается целостности и правильности файла библиотеки, я проверил его с помощью следующего кода C:

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
int main(int agrc, char **agrv) {
    void *handle;
    double (*sum)(double, double);
    char *error;

    handle = dlopen("./sumLib.so", RTLD_LAZY);
    if (!handle) {
        printf("error opening file");
        fprintf(stderr, "%s\n", dlerror());
        exit(EXIT_FAILURE);
    }
    dlerror();

    *(void **) (&sum) = dlsym(handle, "sum");

    if ((error = dlerror()) != NULL) {
        fprintf(stderr, "%s\n", error);
        exit(EXIT_FAILURE);
    }
    printf("Sum is %f\n", (*sum)(13.3, 113.4));
    dlclose(handle);
    exit(EXIT_SUCCESS);
    return 0;
}

Он смог правильно открыть библиотеку, связать символ и вернуть правильный результат.

Что я делаю не так, когда dlopen выдает эту ошибку?

1 Ответ

0 голосов
/ 12 сентября 2018

Попробуйте создать приложение для целевого SDK 23 или ниже и запустить его на API эмулятора 24 или ниже.Я считаю, что неудача, которую вы видите, является проявлением недавних мер безопасности.См. Изменения поведения Android 7.0 :

Начиная с Android 7.0, система предотвращает динамическое связывание приложений с библиотеками, отличными от NDK.

Создание компилятора sumLib.so с хостом gcc также не поможет.Несмотря на то, что результирующая библиотека предназначена для одного и того же x86_64 ABI, среда выполнения на Android совершенно другая.Пожалуйста, используйте набор инструментов NDK.При желании вы можете использовать автономный набор инструментов вместо ndk-build или CMake сценариев.

...