如何导出 DLL 中的复杂类
导出派生类、设计模式等。
引言
由于我花了过去几天的时间来寻找如何从 DLL 导出类的方法,并且找到很多关于 DLL 的资料,但几乎没有帮助我的内容,所以我决定将有用的信息总结在这篇简短的教程中。
背景
对于我们大多数参与面向对象编程的人来说,期望能够从 DLL 导出整个对象是相当自然的。好消息是这是可行的。坏消息是:这并不明显。而且,更糟糕的是:尝试导出继承类或返回类指针的函数。
解决方案
这不是解决此问题的唯一方法,我也不知道它是否是最好的解决方案。这更像是一种提供灵活性和鲁棒性的方法。
从 DLL 导出类的问题是由于 C++ 风格导致函数名称被破坏(mangled names)。对于函数,这很容易 - 只需要将它们定义为 extern "C"
,就完成了。由于 extern "C"
对类没有影响,你必须想出其他方法。一种方法是从一个带有 纯虚成员函数 的 DLL 包装类 派生你的类,每个你想导出类的函数都对应一个纯虚函数。你的类真正需要继承的是包装类函数的 虚表。包装类可能看起来像一个接口类,在某些情况下你可以将其用作一个接口类,但你必须将其视为一个仅用于一个真实类的包装器。这在简单的继承方案中很明显,你有一个真实的基类和一个真实的派生类。
当你想在 DLL 中拥有相同的结构并导出 Derived1
或 Derived2
时,你必须重新设计你的方案为
在 C++ 中,这看起来像
class DllWrapperBase
{
public:
DllWrapperBase();
virtual ~DllWrapperBase();
virtual long __stdcall test1()=0;
virtual float __stdcall test2()=0;
};
class Base : public DllWrapperBase
{
public:
Base();
virtual ~Base();
virtual long __stdcall test1(){return 1;}
virtual float __stdcall test2(){return 1.8f;};
};
class DllWrapperDerived : public Base
{
public:
DllWrapperDerived();
virtual ~DllWrapperDerived();
virtual long __stdcall test3()=0;
virtual float __stdcall test4()=0;
};
class Derived : public DllWrapperDerived
{
public:
Derived();
virtual ~Derived();
virtual long __stdcall test3(){return 3;}
virtual float __stdcall test4(){return 3.9f;}
};
为了使它更复杂,我们引入一个 工厂类,它生成 派生类 的对象。这是一种常见的设计模式,看起来如下
enum eProducts
{
PRODUCT1
};
class Base;
class Derived;
class DllWrapperFactory
{
public:
DllWrapperFactory();
virtual ~DllWrapperFactory();
virtual Base * __stdcall CreateProduct(eProducts iValue)=0;
protected:
virtual Derived * __stdcall CreateDerived()=0;
};
class Factory : public DllWrapperFactory
{
public:
Factory();
virtual ~Factory();
Base * __stdcall CreateProduct(eProducts iValue);
protected:
Derived * __stdcall CreateDerived();
};
然后,我们添加主要的 DLL 文件
// dll_export.h
#include "Factory.h"
extern "C" __declspec(dllexport) DllWrapperFactory * returnFactory()
{
DllWrapperFactory * pObj =
static_cast<dllwrapperfactory*>(&FactorySingleton::Instance());
return pObj;
}
// dll_export.cpp
#include "stdafx.h"
#include "dll_export.h"
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
你像这样使用新创建的 DLL
// dll_tester.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <stdlib.h>
#include <windows.h>
#include "Factory.h"
#include "Derived.h"
typedef Factory * (__stdcall *DLLGETFACTORY)(void);
int main(int argc, char* argv[])
{
printf("Hello World!\n");
HINSTANCE hInstDll = LoadLibrary("dll_test.dll");
if(!hInstDll) printf("Failed to load dll\n");
DLLGETFACTORY pDllGetFactory =
(DLLGETFACTORY) GetProcAddress(hInstDll, "returnFactory");
// Create the object using the factory function
Factory * pMyFactory = (pDllGetFactory)();
if (pMyFactory == NULL)
return 0;
Derived * d = (Derived *)pMyFactory->CreateProduct(PRODUCT1);
int i = d->test1();
float f = d->test2();
int j = d->test3();
float e = d->test4();
system("PAUSE");
return 0;
}
你必须记住,为了通过 DLL 使用任何类,你必须在应用程序中包含它的头文件。