C++ dll動態程式庫建置架構筆記(CMake build) -5 如何使用LoadLibraryA()讀取class架構dll

上一篇介紹dll裡建立class,但他是使用header.h+.lib+.dll方式讀取dll程式,這篇要講解我如何使用LoadLibraryA()讀取,先說程式碼會很多,因為要把整個class重新改寫成function型式,這方法應該能讓C++ dll程式在C語言上執行。這其實是我一開始學習建置dll的方式,順便紀錄一下,可能還有更好的方式我還沒研究到。

一、void pointer說明

這個功能很強大,他可以指向所有資料型態的指標,我這邊把他拿來指向整個class。在外部使用的人只會看到這是一個void pointer,不知道他其實是一個class,所以可以將整個class隱藏在dll內部。

二、建立class指標

假如有一個 class 名稱是 foo。
class foo
{
    void Add(int b);
    ...
};
使用 void pointer 指標指向 class。
//create void pointer
typedef void* foo_handle;

//void pointer指標指向新建立的class
foo_handle foo_h = (foo_handle)new foo();

三、刪除空指標裡的class

當 class 不再使用時,或程式結束時,要將佔用的資源釋放,pointer 要指向 NULL,因為通常會檢查這個指標如果非 NULL 表示指向的 class 還存在著。
//刪除class
delete (foo*)foo_h;

//將void pointer 指向空
foo_h = NULL;

四、在dll裡建立\刪除class

我這邊使用 extern "C" __declspec(dllexport) 建立dll function。
typedef void* foo_handle;
extern "C" __declspec(dllexport) foo_handle createClass()
{
    return (foo_handle)new foo();
}
extern "C" __declspec(dllexport) int destroyClass(foo_handle &handle)
{
    if(!handle) return -1;
    delete (foo*)handle;    //刪除class
    handle = NULL;          //pointer 指向空
    return 0;
}
使用 createClass() 取得新建立 class 的位置,資料型態為 foo_handle。
釋放的時候將 foo_handle 輸入至 destroyClass(),他會刪除 class 並將 foo_handle 指定成 NULL。

五、class裡的函數

要將 class 的指標傳入"foo_handle handle",檢查 handle 是不是 NULL,如果是 NULL 表示 class 沒建立或是已經遭刪除,如果不是 NULL 轉換回原本的class樣子,在進行 class 內部函數的操作。
extern "C" __declspec(dllexport) int Add_C(foo_handle handle, int b)
{
    if(!handle) return -1;
    foo* p_foo = (foo*)handle;  //換回原本class樣子
    p_foo ->Add(b);             //操作foo裡的Add()
    return 0;
}

六、實作Dll部分

這邊使用上一篇文章的範例做延伸。
之前說明過 SV_EXTERN_C → extern "C",SV_EXPORTS → __declspec(dllexport)。

Dll_class_c\math_class\include\my_math_class_c.h
#ifndef my_math_H_
#define my_math_H_
#include "SVdef.h"

struct newNumber
{
    int i;
    int j;
};

class math_class
{
public:    
    newNumber a;
    math_class(int value);
    void input_a(int value);
    void get_a(newNumber &a_);
    void Add(int b);
};

// ============  use LoadLibraryA()  =============================================

#define checkHandle(_handle) { if (!_handle) return -2; }
typedef void* type_math_handle;
SV_EXTERN_C SV_EXPORTS type_math_handle createMathClass_C(int a);
SV_EXTERN_C SV_EXPORTS int destroyMathClass_C(type_math_handle &handle);
SV_EXTERN_C SV_EXPORTS int get_a_C(type_math_handle handle, int &i, int &j);
SV_EXTERN_C SV_EXPORTS int Add_C(type_math_handle handle, int b);

#endif //my_math_H_

Dll_class_c\math_class\src\my_math_class_c.cpp
#include "my_math_class_c.h"

math_class::math_class(int value)
{
    input_a(value);
}
void math_class::input_a(int value)
{
    a.i = value;
    a.j = value*10;
}
void math_class::get_a(newNumber &a_)
{
    a_ = a;
}
void math_class::Add(int b)
{
    a.i+=b;
    a.j+=b;
}

// ============  use LoadLibraryA()  =============================================

SV_EXTERN_C SV_EXPORTS type_math_handle createMathClass_C(int a)
{
    return (type_math_handle)new math_class(a);
}
SV_EXTERN_C SV_EXPORTS int destroyMathClass_C(type_math_handle &handle)
{
    checkHandle(handle);
    delete (math_class *)handle;
    handle = NULL;
    return 0;
}
SV_EXTERN_C SV_EXPORTS int get_a_C(type_math_handle handle, int &i, int &j)
{
    i = j = 0;
    checkHandle(handle);
    math_class *p_math_class = (math_class *)handle;
    newNumber a;
    p_math_class->get_a(a);
    i = a.i;
    j = a.j;
    return 0;
}
SV_EXTERN_C SV_EXPORTS int Add_C(type_math_handle handle, int b)
{
    checkHandle(handle);
    math_class *p_math_class = (math_class *)handle;
    p_math_class->Add(b);
    return 0;
}

七、實作執行dll的部分

這裡需要定義每個 function 原始的樣子,在來宣告 void pointer 去 handle class "typedef void* type_math_handle;",名稱要與 dll 內設定的一樣,之後將每個 function 的 address 讀入,記得要先創建 class "createMathClass()" 後才能使用,一個簡單的 class 被搞得非常複雜,所以我覺得非必要真的不要用,如果這個 class 很大的話真的會寫得很累人。

Dll_class_c\main\test_withoutLib.cpp
#include <stdio.h>
#include <windows.h>

struct My_math_dll
{
private:
    HMODULE hLib;
    bool enable = false;

public:
    // handle class 的 void pointer
    typedef void* type_math_handle;
    //定義每個 function 原始的樣子
    typedef type_math_handle(*createMathClass_C)(int a);
    typedef int(*destroyMathClass_C)(type_math_handle &handle);
    typedef int(*get_a_C)(type_math_handle handle, int &i, int &j);
    typedef int(*Add_C)(type_math_handle handle, int b);

    //宣告 function 在這邊使用時的名稱
    type_math_handle math_h = NULL;
    createMathClass_C createMathClass;
    destroyMathClass_C destroyMathClass;
    get_a_C get_a;
    Add_C Add;

    ~My_math_dll()
    {
        release();
    }

    int loadAPI()
    {
        // 讀入 dll
        hLib = LoadLibraryA("Dll_class_math_c.dll");
        
        // If the dll file fails, the memory address will be 0.
        printf_s("Dll_class_math_c.dll address:%p\n", hLib);
        if (!hLib)
        { 
            printf_s("load Dll_class_math_c.dll fail!\n");
            return 0;
        }

        // 將每個 function 的 address 讀入
        createMathClass = (createMathClass_C)GetProcAddress(hLib, "createMathClass_C");
        destroyMathClass = (destroyMathClass_C)GetProcAddress(hLib, "destroyMathClass_C");
        get_a = (get_a_C)GetProcAddress(hLib, "get_a_C");
        Add = (Add_C)GetProcAddress(hLib, "Add_C");

        // Check the memory address obtained by the function API.
        // If the function API fails, the memory address will be 0.
        printf_s("API address: %p %p\n", createMathClass, Add);
        enable = true;
        return 0;
    }

    void release()
    {
        if (enable)
        {
            destroyMathClass(math_h);  //刪除 class
            FreeLibrary(hLib);
            enable = false;
        }
    }
};

int main()
{
    int a = 10;
    int b = 20;
    int i,j;

    My_math_dll dll = My_math_dll();
    dll.loadAPI();
    dll.math_h = dll.createMathClass(a);  //先創建class

    dll.get_a(dll.math_h, i, j);
    printf("get_a_C %d %d\n", i, j);

    dll.Add(dll.math_h, b);
    dll.get_a(dll.math_h, i, j);
    printf("Add_C %d %d\n", i, j);

    dll.release();
    return 0;
}

其他篇文章:

留言

這個網誌中的熱門文章

C# 模擬鍵盤滑鼠控制電腦

python pyautogui 簡介

android 定時通知(永久長期的) 本篇只講AlarmManager使用

raspberrypi 開機自動執行程式 與 在terminal開啟第二個terminal執行python

python nn 聲音辨識 -1 傅立葉轉換