10

现在我做这样的事情,如果我最终有很多我想在我的 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);
    }

}
4

7 回答 7

25

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

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

我不必解释运行时动态链接,因为您已经在这样做了。我现在选择不解释延迟负载动态链接,而不仅仅是笼统地描述它是什么。延迟加载本质上与加载时动态链接相同,只是它是即时完成的,而不是在应用程序加载时完成的。这并不像您想象的那么有用或有益,它很难使用并且难以编写代码。所以我们不要去那里,至少现在是这样。 DLL 转发比延迟加载更奇特——如此奇特,直到@mox 在评论中提到它,我才听说过它。我会让您阅读上面的链接以了解它,但只要说 DLL 转发就是当您在一个 DLL 中调用导出的函数但该请求实际上被转发到不同 DLL 中的另一个函数时就足够了。

加载时动态链接

这就是我认为的Vanilla DLL Linking

这就是大多数人在提到在他们的应用程序中使用 DLL 时所指的内容。您只需#includeDLL 的头文件并链接到 LIB 文件。无需GetProcAddress()或创建函数指针类型定义。简而言之,它的工作原理如下:

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

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

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

4) 编译你的应用程序。如果您已正确设置所有内容,它应该可以编译、链接和运行。当您收到“未解决的外部参考”链接器错误时,通常是由于设置不正确。您可能没有指定 LIB 文件的正确路径,或者您需要包含更多 LIB 文件。

于 2010-01-13T21:44:59.673 回答
10

构建您的 .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
}

这样,在构建您EXPORTS_API在项目设置和函数中定义的 dll 时,将在客户端应用程序中导出,无需定义任何内容。

于 2010-01-13T21:34:15.910 回答
4

导入库(.lib) 简化了用户代码中的 DLL 使用,请参阅此处了解基本教程。
它们使用户免于加载 DLL、使用GetProcAddress()和函数指针本身——它们静态链接到导入库,而不是为他们完成工作。

于 2010-01-13T21:37:35.123 回答
2

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

替代文字

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

// 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 的项目中只需包含头文件和.lib该 DLL 项目生成的文件。这样,它会自动加载 DLL,并且您可以像静态链接一样使用所有函数。

于 2010-01-13T21:45:20.843 回答
0

构建 dll 时,您还应该获得一个可以链接的 lib 文件。这将为您在后台完成繁重的工作。包括您创建的头文件,但更改为 dllimport 而不是 dllexport。您可以为此使用定义,以便为您的 dll 项目使用 dllexport,而所有其他不使用此定义的项目将使用 dllimport。

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

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

于 2010-01-13T21:36:58.743 回答
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 生成的导入库。这使得 DLL 中的符号可用于链接到它的代码。

于 2010-01-13T21:39:35.490 回答
-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 的函数指针类型很难正确处理,除非您经常使用它们。(如果您经常使用它们,那么您的软件可能有点不正统)。

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

于 2010-01-13T21:56:54.193 回答