有没有更好的方式来加载在C + +的DLL?

现在我做这样的事情,如果我有很多我想在我的DLL中引用的函数,它似乎很混乱。 有没有一种更好,更干净的方式来访问函数,而不必为每个函数定义创build一个typedef,以便它将编译和正确加载函数。 我的意思是函数定义已经在.h文件中,我不应该重新声明他们后我加载函数(或我?)有没有比使用LoadLibary更好的解决scheme? 如果在Visual Studio 2005项目设置中可以做同样的事情,我不一定需要这个function。


BHannan_Test_Class.h

#include "stdafx.h" #include <windows.h> #ifndef BHANNAN_TEST_CLASS_H_ #define BHANNAN_TEST_CLASS_H_ extern "C" { // Returns n! (the factorial of n). For negative n, n! is defined to be 1. int __declspec (dllexport) Factorial(int n); // Returns true iff n is a prime number. bool __declspec (dllexport) IsPrime(int n); } #endif // BHANNAN_TEST_CLASS_H_ 

BHannan_Test_Class.cpp

 #include "stdafx.h" #include "BHannan_Test_Class.h" // Returns n! (the factorial of n). For negative n, n! is defined to be 1. int Factorial(int n) { int result = 1; for (int i = 1; i <= n; i++) { result *= i; } return result; } // Returns true iff n is a prime number. bool IsPrime(int n) { // Trivial case 1: small numbers if (n <= 1) return false; // Trivial case 2: even numbers if (n % 2 == 0) return n == 2; // Now, we have that n is odd and n >= 3. // Try to divide n by every odd number i, starting from 3 for (int i = 3; ; i += 2) { // We only have to try i up to the squre root of n if (i > n/i) break; // Now, we have i <= n/i < n. // If n is divisible by i, n is not prime. if (n % i == 0) return false; } // n has no integer factor in the range (1, n), and thus is prime. return true; } 

dll_test.cpp

 #include <BHannan_Test_Class.h> typedef int (*FactorialPtr) (int); FactorialPtr myFactorial=NULL; // Tests factorial of negative numbers. TEST(FactorialTest, Negative) { HMODULE myDLL = LoadLibrary("BHannan_Sample_DLL.dll"); if(myDLL) { myFactorial = (FactorialPtr) GetProcAddress(myDLL,"Factorial"); if(myFactorial) { EXPECT_EQ(1, myFactorial(-5)); EXPECT_EQ(1, myFactorial(-1)); EXPECT_TRUE(myFactorial(-10) > 0); } FreeLibrary(myDLL); } } 

生成.dll文件后,在附近获取.lib文件,并将测试应用程序与其链接。 使用在.h中声明的函数

你需要在你的头文件中做一些小的改动:

 #ifdef EXPORTS_API #define MY_API_EXPORT __declspec (dllexport) #else #define MY_API_EXPORT __declspec (dllimport) #endif extern "C" { int MY_API_EXPORT Factorial(int n); // do the same for other functions } 

这样,在构建你的dll时,你可以在你的项目设置中定义EXPORTS_API ,并且在客户端应用程序中导出函数,不需要定义任何东西。

在Windows世界中,至少有4种使用DLL的方法:

  1. 运行时动态链接 (你现在在做什么)
  2. 加载时间动态链接 (使用DLL的“典型”方式)
  3. 延迟加载动态链接
  4. DLL转发

我不必解释运行时动态链接,因为你已经在做。 我现在选择不解释延迟加载动态链接,而不是简单地描述它是什么。 延迟加载与加载 – 时间动态链接基本相同,不同之处在于它是在应用程序加载时执行的。 这并不像你想象的那样有用或者有益,但是编码起来很困难。 所以我们不要去那里,至少现在。 DLL转发比延迟加载更加奇特 – 非常奇特,直到@mox在评论中提到它时,我才听说过它。 我会让你阅读上面的链接来了解它,但是可以说DLL转发是当你在一个DLL中调用一个导出的函数,但是这个请求实际上被转发到另一个DLL中的另一个函数。

加载 – 时间动态链接

这是我认为是香草DLL链接

这是大多数人在他们的应用程序中使用DLL时提到的。 你只要#include DLL的头文件并链接到LIB文件。 不需要GetProcAddress()或创建函数指针typedefs。 下面是它的工作原理:

1)你通常得到3个文件:一个带有运行时代码的DLL,一个LIB文件和一个头文件。 头文件只是一个头文件 – 它描述了您可以使用的DLL中的所有工具。

2)你编写你的应用程序, #include从DLL中包含头文件,并调用这些函数,就像你在任何头文件中使用任何函数一样。 编译器知道您使用的函数和对象的名称,因为它们位于DLL的头文件中。 但它不知道他们在什么地方。 这是LIB文件进来的地方…

3)转到项目的链接器设置,并添加一个“额外的库依赖项”,指定LIB文件。 LIB文件告诉链接器你从H文件中使用的函数和对象在内存中的位置(相对而言,显然不是绝对的)。

4)编译你的应用程序。 如果你已经正确地设置了一切,它应该编译,链接和运行。 当你得到“无法解析的外部参考”链接器错误通常这是由于事情没有设置正确。 您可能没有指定正确的路径到LIB文件,或者您需要包含更多的LIB文件。

导入库 (.lib)简化了用户代码中的DLL使用,请参阅这里的基本教程。
它们使用户无需使用GetProcAddress()和函数指针自己加载DLL,而是静态链接到导入库,而不是为他们工作。

为什么不让VS在你的DLL中生成一个shim静态库? 这样你所要做的就是在头文件中添加一个调用约定,并添加一些预处理指令。 最简单的方法是创建一个新的DLL项目(Visual C ++> Win32项目,选择DLL项目,检查导入符号)

替代文字http://img341.imageshack.us/img341/7048/dll.png

使用主头文件作为示例,介绍如何用导入/导出调用约定修饰类。 这个头很重要,因为它解释了如何使用在那里声明的函数和类:

 // The following ifdef block is the standard way of creating macros which make exporting // from a DLL simpler. All files within this DLL are compiled with the DLLTEST2_EXPORTS // symbol defined on the command line. this symbol should not be defined on any project // that uses this DLL. This way any other project whose source files include this file see // DLLTEST2_API functions as being imported from a DLL, whereas this DLL sees symbols // defined with this macro as being exported. #ifdef DLLTEST2_EXPORTS #define DLLTEST2_API __declspec(dllexport) #else #define DLLTEST2_API __declspec(dllimport) #endif // This class is exported from the dlltest2.dll class DLLTEST2_API Cdlltest2 { public: Cdlltest2(void); // TODO: add your methods here. }; extern DLLTEST2_API int ndlltest2; DLLTEST2_API int fndlltest2(void); 

然后,在使用该DLL的项目中,只包含DLL项目生成的头文件和.lib 。 这样它会自动加载DLL,你可以像静态链接一样使用所有的函数。

当你建立你的DLL,你也应该得到一个lib文件,你可以链接。 这将为你做背景的繁重工作。 包括你创建的头文件,但改成dllimport而不是dllexport。 你可以使用一个定义,因此,为您的dll项目使用dllexport和所有其他不使用此定义将使用dllimport。

你只需要做一个手动的LoadLibrary和typedef,如果你想自己动态加载dll。 如果你做了上述的方法你的EXE将失败,如果DLL不存在。

你也可以建立一个静态库的DLL项目,并加载,而这也将摆脱这个问题,但会增加您的EXE的大小。 这当然不是一个选项,如果你真的想要它是一个DLL。

您可以直接链接到DLL的符号,而不是使用GetProcAddress()GetProcAddress()在运行时获取函数的地址。

示例头文件片段:

 #if defined(MY_LIB_STATIC) #define MY_LIB_EXPORT #elif defined(MY_LIB_EXPORTS) #define MY_LIB_EXPORT __declspec(dllexport) #else #define MY_LIB_EXPORT __declspec(dllimport) #endif #ifdef __cplusplus extern "C" { #endif int MY_LIB_EXPORT Factorial(int n); #ifdef __cplusplus } #endif 

然后在BHannan_Test_Class.cpp ,在包含头文件之前,先#define MY_LIB_EXPORTS

dll_test.cpp你将包含头文件,并像使用普通函数一样使用Factorial() 。 链接时,您要链接到生成您的DLL生成的导入库 。 这使得DLL中的符号可用于链接到它的代码。

当然你不需要 typedef

 int (* myFactorial)(int) = 0; HMODULE myDLL = LoadLibrary("BHannan_Sample_DLL.dll"); if(myDLL) { myFactorial = reinterpret_cast<int (*) (int)>( GetProcAddress(myDLL,"Factorial")); ... } 

或者,利用C ++初始化和测试习惯

 if ( HMODULE myDLL = LoadLibrary("BHannan_Sample_DLL.dll") ) { if ( int (* myFactorial)(int) = GetProcAddress ( myFactorial, myDLL, "Factorial" ) ) { ... } } 

给这个整理类型的重复

 template <typename T> T GetProcAddress ( const T&, HMODULE mod, const char* name) { return reinterpret_cast<T> (GetProcAddress(mod,name)); } 

但是不使用typedef通常更糟糕,而不是更好,因为除非定期使用C函数,否则C的函数指针类型有点棘手。 (如果你经常使用它们,那么你的软件可能有点不正规)。

Microsoft dllimport扩展和编译器创建一个静态库,为您加载并提供蹦床或thunk,如其他人所发布的。 除非你创建的插件系统不知道它将加载哪个dll,那就用它来代替。