有没有更好的方法在C ++中加载DLL?

时间:2010-01-13 21:27:30

标签: c++ windows dll loadlibrary

现在我做了类似这样的事情,如果我想在我的DLL中引用很多函数,它似乎很麻烦。是否有更好,更清晰的方式来访问函数,而无需为每个函数定义创建一个typedef,以便它可以正确地编译和加载函数。我的意思是函数定义已经在.h文件中,我不应该在加载函数后重新声明它们(或者我?)是否有比使用LoadLibary更好的解决方案?如果有一种方法可以在Visual Studio 2005项目设置中执行相同的操作,我不一定需要该函数。


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);
    }

}

7 个答案:

答案 0 :(得分:22)

在Windows世界中,有(至少)4种方式使用DLL:

  1. Run-Time Dynamic Linking(你现在在做什么)
  2. Load-Time Dynamic Linking(使用DLL的“典型”方式)
  3. Delay-Load Dynamic Linking
  4. DLL Forwarding
  5. 我不必解释运行时动态链接,因为您已经在做了。我现在选择不解释延迟加载动态链接,而不仅仅是描述它的一般含义。延迟加载与加载时动态链接基本相同,除非它是实时的而不是在应用程序加载时完成的。这并不像你想象的那样有用或有益,它很难用来编写代码。所以,至少现在,我们不要去那里。 DLL Forwarding 甚至比延迟加载更具异国情调 - 如此异国情调,我甚至从未听说过它,直到@mox在评论中提及它。我会让你阅读上面的链接来了解它,但足以说DLL转发就是你在一个DLL中调用导出的函数但该请求实际上是转发到另一个函数中不同的DLL。

    加载时动态链接

    我认为这是 Vanilla DLL Linking

    这是大多数人在他们的应用程序中引用DLL时所指的内容。您只需#include DLL的头文件并链接到LIB文件。无需GetProcAddress()或创建函数指针typedef。以下是它的工作原理:

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

    2)您编写应用程序,#include来自DLL的头文件并调用这些函数,就像在任何头文件中使用任何函数一样。编译器知道您使用的函数和对象的名称,因为它们位于DLL的头文件中。但它还不知道它们在记忆中的位置。这就是LIB文件的来源......

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

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

答案 1 :(得分:9)

构建.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并导出函数,在客户端应用程序中,无需定义任何内容。

答案 2 :(得分:3)

Import libraries(。lib)简化了用户代码中的DLL使用,请参阅例如here获得基本教程 它们使用GetProcAddress()和函数指针本身使用户无需加载DLL - 它们静态链接到导入库,而不是为它们工作。

答案 3 :(得分:1)

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

alt text

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

// 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,你可以像使用静态链接那样使用所有函数。

答案 4 :(得分:0)

构建dll时,还应该获得可以链接的lib文件。这将为你做背景的繁重工作。包括您创建的头文件,但更改为dllimport而不是dllexport。你可以使用一个define,这样你的dll项目就可以使用dllexport,所有其他不使用这个定义的人都会使用dllimport。

如果要自己动态加载dll,只需要手动LoadLibrary和typedef。如果您执行上述方法,则如果dll不存在,则exe将失败。

您还可以将dll项目构建到静态库中并加载它,这也将解决该问题但会增加exe的大小。如果你真的希望它是一个dll,这当然不是一个选择。

答案 5 :(得分:0)

您可以直接链接到DLL的符号,而不是使用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所生成的import library。这使得DLL中的符号可用于链接到它的代码。

答案 6 :(得分:-2)

当然你不需要 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的函数指针类型有点难以实现。 (如果你经常使用它们,那么你的软件可能有点不正统)。

Microsoft dllimport扩展和编译器创建了一个静态库,可以为您加载并提供蹦床或thunks,正如其他人发布的那样。除非您创建的插件系统不知道它将加载哪个DLL,否则请使用它。