如何在通过 LoadLibrary() 加载的 DLL 中初始化 C++ 类?

How to initialize a C++ class inside a DLL which was loaded via LoadLibrary()?

提问人:hzh 提问时间:1/19/2023 最后编辑:hzh 更新时间:1/21/2023 访问量:343

问:

我从客户那里得到了一个问题。

他们从他们的产品中删除了一些 VC++ 项目,只有 dll。

现在,他们希望使用这些dll中的一些函数处理一些数据。

假设这是 dll 中的一个类。

//MyClass.h
class __declspec(dllexport) MyClass{
public:
    int a;
    int b;
public:
    MyClass();
    int Sum(int, int);
}
//MyClass.cpp
MyClass::MyClass(){
    a=0;
    b=0;
}

int MyClass::Sum(int c, int d){
    return c+d;
}

而且,这是exe。

//Test.exe
typedef void(__stdcall *TCon)();    // for constructor
typedef int(__stdcall *TSum)(int, int);    // for function Sum()

int main(){
    HMODULE myDll = LoadLibrary(TEXT("MyClass.dll"));
    FARPROC con = GetProcAddress(myDll, "??0MyClass@@QAE@XZ");  // I got this from DUMPBIN.exe
    FARPROC sum = GetProcAddress(myDll, "?Sum@MyClass@@QAEHHH@Z");  // I got this from DUMPBIN.exe, too

    TCon f_con = (TCon)con;
    TSum f_sum = (TSum)sum;

    f_con();    //Here I got access violation exception
    printf("Sum is:%d¥n", f_sum(1,2));

    return 0;
}

如果 MyClass 只有 Sum() 函数,没有构造函数,没有像 a 和 b 这样的成员,那么可以毫无问题地调用 Sum() 函数,我用 VS2017 对此进行了测试。

但是,当类具有构造函数和成员时,就会发生内存访问冲突。

我想这也许是应该首先初始化 MyClass,所以我尝试调用构造函数,这就是我添加f_con的原因,但f_con遇到了同样的访问冲突问题。

客户问的是,我们只有导入类的dll文件。

据我所知,有几种方法可以通过包含它们的标头从其他dll导入类。因为至少我们需要我们想要导入的类的声明。

我能做些什么来让 Sum() 工作吗?

C 可视化 C++ 加载库

评论

1赞 273K 1/19/2023
将代码构建为可执行文件,对其进行反汇编并了解构造函数的调用方式。
0赞 hzh 1/19/2023
@273K 我可以使用 dumpbin.exe 来获取一些有用的信息,所以我不需要拆卸它吗?
1赞 Richard Critten 1/19/2023
为类和它编写一个新的头文件。__declspec(dllimport)
0赞 Pepijn Kramer 1/19/2023
@RichardCritten OP 在没有头文件的情况下进行尝试。但是要获取实例,您首先需要调用构造函数,据我所知,无法获取该地址(更不用说使用它来获取有效的构造对象了)。eel.is/c++draft/class.ctor.general#6
0赞 Richard Critten 1/19/2023
@PepijnKramer我知道(请阅读下面的回答和评论),OP 试图做的是对类进行逆向工程。如果 OP 可以做到这一点,那么新的头文件将是最佳的。

答:

0赞 Pepijn Kramer 1/19/2023 #1

我感觉你错过了什么。所有那些在入口点上乱搞的东西都不应该是必要的。除非你想把它作为一种练习来做。

当您的客户端代码也是 C++ 时,您应该能够包含类的头文件并像往常一样使用它。

但是(若要获取运行时链接),必须能够在类声明的 dllexport 和 dllimport 之间切换。编译 DLL 时必须使用 DLLexport,使用 DLL 时必须使用 DLLIMPORT。

因此,请使用 COMPILING_DLL(命令行预处理器定义)编译您的 dll 然后用它添加一个头文件,并在你的类头文件中使用它。

#if COMPILING_DLL
    #define DLLEXPORT __declspec(dllexport)
#else
    #define DLLEXPORT __declspec(dllimport)
#endif

并将类声明更改为:

class DLLEXPORT  MyClass{...};

评论

0赞 hzh 1/19/2023
很抱歉在我的示例代码中遗漏了一些东西。是的,我知道如何使用 dll 导出和导入内容。是的,我知道 dllexport 和 dllimport 是如何工作的。实际上,我做了很多dll。我的问题是,如何在不包含此类头文件的情况下加载类并初始化它。LoadLibrary 可以在没有任何头文件的情况下加载 dll,您只需要 dll 的名称和函数的名称。但是 LoadLibrary 不能给我一个新类,对吧?
1赞 Pepijn Kramer 1/19/2023
无法获取构造函数的地址。请参阅C++标准 eel.is/c++draft/class.ctor.general#6(不得采用构造函数的地址。换句话说:调用构造函数只是获取对象的有效实例的一个步骤。
0赞 hzh 1/19/2023
使用 LoadLibrary 导入 dll 后,当我运行 Class 的 Sum() 函数时,我遇到了访问冲突。我想访问冲突的原因可能是我没有获得有效的实例,所以我试图找到一种方法来运行构造函数,但失败了。
0赞 hzh 1/21/2023 #2

在这里找到了我需要的东西。

正如我测试的那样,使用内联汇编器语法将解决我的问题:

__asm {MOV ECX, p};  //p points to a malloc area for instance.

现在,我可以将该类的 INSTANCE 关联到构造函数。

因为在EXE端,我必须自己做所有事情,所以每次我想调用该类的函数时,我都需要使用该汇编器语法。喜欢这个:

...
    __asm {MOV ECX, p};  //move instance's address into the ECX register
    f_con();  //call constructor
    __asm {MOV ECX, p};  //move instance's address into the ECX register again
    int r = f_Sum(1,2);  //call member function
...

现在我可以调用没有任何标头的类成员函数了!