首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

JNI 多層調用 C/C++:動態庫加載與函數指釈獲取 (LoadLibrary/dlopen + GetProcAddress/dlsym)

     編輯:程式設計綜合問答

架構圖


Java (JNI) → func.c (JNI 入口) → func1.c (C 邏輯層) → func2.so/dll (動態庫)

核心問題

`func1.c` 如何調用 `func2.so` (Linux) / `func2.dll` (Windows) 中的函數?

平台相關加載方式

Windows (DLL)


// func1.c
#include 
#include "func2.h" // 聲明頭文件

// 函數指針類型定義
typedef int (*Func2_Add_t)(int, int);
typedef void (*Func2_Init_t)(void);

static HINSTANCE hFunc2Dll = NULL;
static Func2_Add_t pFunc2_Add = NULL;
static Func2_Init_t pFunc2_Init = NULL;

BOOL LoadFunc2Library() {
    if (hFunc2Dll) return TRUE; // 已加載
    
    // 1. 加載 DLL (路徑: 同目錄、系統目錄、PATH 環境變量)
    hFunc2Dll = LoadLibrary("func2.dll"); // 或完整路徑
    if (!hFunc2Dll) {
        DWORD err = GetLastError();
        // 記錄錯誤
        return FALSE;
    }
    
    // 2. 獲取函數地址
    pFunc2_Add = (Func2_Add_t)GetProcAddress(hFunc2Dll, "Func2_Add");
    pFunc2_Init = (Func2_Init_t)GetProcAddress(hFunc2Dll, "Func2_Init");
    
    if (!pFunc2_Add || !pFunc2_Init) {
        FreeLibrary(hFunc2Dll);
        hFunc2Dll = NULL;
        return FALSE;
    }
    
    // 3. 初始化 (可選)
    pFunc2_Init();
    return TRUE;
}

void UnloadFunc2Library() {
    if (hFunc2Dll) {
        FreeLibrary(hFunc2Dll);
        hFunc2Dll = NULL;
        pFunc2_Add = pFunc2_Init = NULL;
    }
}

// 使用示例
int CallFunc2Add(int a, int b) {
    if (!LoadFunc2Library()) return -1;
    return pFunc2_Add(a, b);
}

Linux/Android (SO)


// func1.c
#include 
#include "func2.h"

typedef int (*Func2_Add_t)(int, int);
typedef void (*Func2_Init_t)(void);

static void* hFunc2So = NULL;
static Func2_Add_t pFunc2_Add = NULL;
static Func2_Init_t pFunc2_Init = NULL;

int LoadFunc2Library() {
    if (hFunc2So) return 1;
    
    // 1. 加載 SO (RTLD_LAZY/RTLD_NOW)
    // 路徑: ./func2.so、/system/lib64/、LD_LIBRARY_PATH、rpath
    hFunc2So = dlopen("func2.so", RTLD_NOW);
    if (!hFunc2So) {
        const char* err = dlerror();
        // 記錄錯誤
        return 0;
    }
    
    // 2. 獲取函數指針 (dlsym 返回 void*)
    pFunc2_Add = (Func2_Add_t)dlsym(hFunc2So, "Func2_Add");
    pFunc2_Init = (Func2_Init_t)dlsym(hFunc2So, "Func2_Init");
    
    const char* symErr = dlerror();
    if (symErr || !pFunc2_Add || !pFunc2_Init) {
        dlclose(hFunc2So);
        hFunc2So = NULL;
        return 0;
    }
    
    pFunc2_Init();
    return 1;
}

void UnloadFunc2Library() {
    if (hFunc2So) {
        dlclose(hFunc2So);
        hFunc2So = NULL;
        pFunc2_Add = pFunc2_Init = NULL;
    }
}

關鍵注意事項

問題解決
庫文件查找路徑Windows: 應用目錄→系統目錄→PATH。Linux: `LD_LIBRARY_PATH`、rpath (`-Wl,-rpath,$ORIGIN`)、`/system/lib64` (Android 系統庫)。建議將 `func2.so` 放在 `jniLibs//` 隨 APK 打包,系統自動提取到 `nativeLibraryDir`。
C++ 名稱修飾若 `func2.cpp` 定義函數,**必須用 `extern "C"`** 防止名稱修飾,否則 `GetProcAddress`/`dlsym` 找不到符號。
`extern "C" int Func2_Add(int a, int b) { ... }`
線程安全加載/卸載函數需加鎖 (互斥量/原子標誌),防止并發重複加載/卸載。
JNI 線程附著若 `func2` 回調 Java,需 `AttachCurrentThread` 獲取 `JNIEnv*`。
錯誤處理記錄 `GetLastError()` / `dlerror()` 具體信息,便於診斷「找不到庫」「符號缺失」「依賴缺失」。

Android 具體實踐

  1. 將 `func2.so` 放入 `src/main/jniLibs/arm64-v8a/` (及其他 abi)。
  2. `func1.c` 中 `dlopen("libfunc2.so", RTLD_NOW)` (系統會在 `nativeLibraryDir` 尋找)。
  3. 在 `JNI_OnLoad` 中調用 `LoadFunc2Library()`,`JNI_OnUnload` 中 `UnloadFunc2Library()`。
  4. 構建腳本 `CMakeLists.txt` 鏈接 `log`、`dl` 庫:`target_link_libraries(func1 ${log-lib} dl)`。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved