问题描述
我正在尝试加载 .dll 文件中定义的类。但是,在 dll 中定义类有两种稍微不同的方法。我不确定哪种方法更合法,我不知道为什么第二种方法也有效。这是一个简单的例子:
方法一:
在main.cpp
:
#include <iostream>
#include <windows.h>
#include <memory>
#include "bar.h"
using namespace std;
typedef bar* (* MYDLLPROC)();
int main()
{
unique_ptr<bar> theresult;
auto thelib = LoadLibrary(TEXT("foo.dll"));
if (thelib != NULL) {
MYDLLPROC theprocs = (MYDLLPROC)GetProcAddress(thelib,"Myfoo");
cout << "load successfully" << endl;
theresult.reset(theprocs());
theresult->printmsg();
} else {
cout << "cannot load the dll" << endl;
}
return 1;
}
bar
定义为 bar.h
中的纯虚拟类:
class bar {
public:
virtual ~bar() {};
virtual void printmsg() = 0;
};
在 foo.dll
源文件中:
#include <iostream>
#include <windows.h>
#include "bar.h"
using namespace std;
class foo: public bar {
public:
foo() { cout << "foo is instantiated" << endl; }
~foo() {}
void printmsg() final { cout << "msg from foo print" << endl; }
};
extern "C" __declspec(dllexport) foo* __cdecl Myfoo()
{
return new foo();
}
在第一种方法中,纯虚拟类 bar
用作接口,当加载 dll 时,它的成员函数被 foo
中的成员函数覆盖是有道理的。
然而,我发现 foo
不必从 bar
派生,只要 foo
有一个 Vtable,一切仍然有效:
在第二种方法中,除了foo
的定义外,一切都相同:
#include <iostream>
#include <windows.h>
using namespace std;
class foo {
public:
foo() { cout << "foo is instantiated" << endl; }
virtual ~foo() {}
virtual void printmsg() final { cout << "msg from foo print" << endl; }
};
extern "C" __declspec(dllexport) foo* __cdecl Myfoo()
{
return new foo();
}
谁能告诉我为什么第二种方法有效?我有点困惑,因为 foo
和 bar
不相关,但 bar
中的成员函数仍然可以被覆盖。
解决方法
因此,您将返回 foo*
的函数转换为返回 bar*
的函数,然后调用它。
最终结果是您有一个指向 foo
的指针,它是一个指向无关类型 bar
的指针。以任何方式使用它都会导致undefined behavior。
它似乎在这种特定情况下有效,因为 printmsg
虚函数在两个 vtable 中的位置相同,因此在 bar::printmsg
的实例上调用 foo
只需调用“ vtable 中的第 N 个条目”。如果您在 foo
之前向 printmsg
添加另一个虚拟成员,则它可能会被调用(否则程序可能会崩溃)。
第一个例子非常脆弱,因为它隐含地依赖于 foo
和 bar
是指针可互换的。
第二个示例被破坏,因为函数 Myfoo
返回一个指向类 foo
的指针,该指针与 bar
无关,并且取消引用它会导致未定义的行为。
函数签名必须与 dll 中实现的函数相匹配。您现在拥有的基本上是从 reinterpret_cast
到 foo *
的狂野 bar *
。