有没有更好的方法在 C++ 中加载 dll?
现在我做了这样的事情,如果我最终在 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);
}
}
Right now I do something like this and it seems messy if I end having a lot of functions I want to reference in my DLL. Is there a better and cleaner way of accessing the functions without having to create a typedef for each function definition so that it will compile and load the function properly. I mean the function definitions are already in the .h file and I shouldn't have to redeclare them after I load the function (or do I?) Is there a better solution than using LoadLibary? I don't necessarily need that function if there is a way I can do the same thing within Visual Studio 2005 project settings.
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);
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
在 Windows 世界中,(至少)有 4 种使用 DLL 的方法:
我不必解释运行时动态链接,因为您已经这样做了。我现在选择不解释延迟加载动态链接,而只是笼统地描述它是什么。延迟加载本质上与加载时动态链接相同,只是它是即时完成的,而不是在应用程序加载时完成的。这并不像您想象的那么有用或有益,它很难使用并且很难编写代码。所以我们不要去那里,至少现在是这样。 DLL 转发 比延迟加载更加奇特——如此奇特,我什至从未听说过它,直到 @mox 在评论中提到它。我会让您阅读上面的链接来了解它,但我只想说,DLL 转发是指您在一个 DLL 中调用导出函数,但该请求实际上转发到一个 DLL 中的另一个函数。不同的DLL。
加载时动态链接
这是我认为的Vanilla DLL 链接。
这就是大多数人在应用程序中提到使用 DLL 时所指的内容。您只需
#include
DLL 的头文件并链接到LIB 文件。无需GetProcAddress()
或创建函数指针类型定义。简而言之,它的工作原理如下:您通常会获得 3 个文件:带有运行时代码的 DLL、LIB 文件和头文件。头文件只是一个头文件——它描述了 DLL 中您可以使用的所有功能。
您编写应用程序,
#include
从 DLL 中获取头文件并调用这些函数,就像在任何头文件中使用任何函数一样。编译器知道您使用的函数和对象的名称,因为它们位于 DLL 的头文件中。但它还不知道它们在内存中的位置。这就是 LIB 文件的用武之地......您转到项目的链接器设置并添加“附加库依赖项”,指定 LIB 文件。 LIB 文件告诉链接器您使用的 H 文件中的函数和对象驻留在内存中的位置(显然是相对术语,而不是绝对术语)。
编译您的应用程序。如果一切设置正确,它应该可以编译、链接并运行。当您收到“未解析的外部引用”链接器错误时,通常是由于设置不正确造成的。您可能没有指定 LIB 文件的正确路径,或者您需要包含更多 LIB 文件。
In the Windows world, there are (at least) 4 ways to use DLLs:
I don't have to explain Run-Time Dynamic Linking since you're already doing it. I choose not to explain Delay-Load Dynamic Linking now beyond just describing what it is in general terms. Delay Load is essentially the same as Load-Time Dynamic Linking except it's done Just-In-Time instead of at application load. This is not as useful or as beneficial as you might think, it is difficult to work with and tricky to code for. So let's not go there, at least for now. DLL Forwarding is even more exotic than Delay-Loading -- so exotic, I'd never even heard of it until @mox mentioned it in the comments. I'll let you read the link above to learn about it, but suffice it to say that DLL Forwarding is when you call an exported function in one DLL but that request is actually forwarded to another function in a different DLL.
Load-Time Dynamic Linking
This is what I would consider to be Vanilla DLL Linking.
This is what most people are referring to when they refer to using DLLs in their applications. You just
#include
the DLL's header file and link to the LIB file. No need toGetProcAddress()
or create function pointer typedefs. Here's how it works in a nutshell:You typically get 3 files: a DLL with the runtime code, a LIB file and a header file. The header file is just a header file -- it describes all the facilities in the DLL you can use.
You write your application,
#include
'ing the header file from the DLL and making calls to those functions just like you would use any function in any header file. The compiler knows the names of functions and objects you use because they are in the DLL's header file. But it doesn't know where they are in memory yet. That is where the LIB file comes in...You go to the linker settings for your project and add an "additional library dependency," specifying the LIB file. The LIB file tells the linker where the functions and objects you use from the H file reside in memory (in relative terms, not absolute terms, obviously).
Compile your app. If you have set everything up correctly it should compile, link and run. When you get "unresolved external reference" linker errors commonly this is due to things not being set up right. You may either have not specified the correct path to the LIB file or you need to include more LIB files.
构建 .dll 后,获取附近的 .lib 文件并将您的测试应用程序与其链接。使用在 .h 中声明的函数
您需要在头文件中做一个小的更改:
这样,在构建 dll 时,您可以在项目设置中定义
EXPORTS_API
,并且函数将在客户端应用程序,无需定义任何内容。After building your .dll get the .lib file nearby and link your test application with it. Use functions as they are declared in .h
There's a minor change you need to do in your header file:
This way, when building your dll you define
EXPORTS_API
in your project settings and functions get exported, in the client application, no need to define anything.导入库 (.lib) 简化用户代码中的 DLL 使用,请参阅例如此处了解基本教程。
它们使用户无需使用 GetProcAddress() 和函数指针本身加载 DLL - 它们静态链接到导入库,而不是由导入库为他们完成工作。
Import libraries (.lib) simplify DLL usage in user code, see e.g. here for a basic tutorial.
They spare the users from loading the DLL, using
GetProcAddress()
and function pointers themselves - they statically link to the import library instead which does the work for them.你为什么不让 VS 围绕你的 DLL 生成一个 shim 静态库。这样,您所要做的就是在头文件中添加调用约定并添加几个预处理器指令。最简单的方法是创建一个新的 DLL 项目(Visual C++>Win32 项目,选择 DLL 项目,选中导入符号)
使用主头文件作为示例,了解如何使用导入/导出调用约定来装饰类。这个头很重要,因为它解释了如何使用在那里声明的函数和类:
然后,在使用该 DLL 的项目中,只需包含 DLL 项目生成的头文件和 .lib 即可。这样它会自动加载 DLL,并且您可以像静态链接一样使用所有函数。
Why don't you get VS to generate a shim static library around your DLL. That way all you have to do is add a calling convention in the header file and add a couple of pre-procesor directives. The easiest way to figure out how to do it is to create a new DLL project (Visual C++>Win32 Project, Choose DLL Project, check Import symbols)
Use the main header file as an example on how to decorate your classes with the import/export calling convention. This head is the important bit as it explains how to use the functions and classes declared there:
Then, in the project that uses that DLL simply include the header file and
.lib
that the DLL project generated. That way it automatically loads the DLL and you can use all the functions as though statically linked.当您构建 dll 时,您还应该获得一个可以链接的 lib 文件。这将为您完成后台的繁重工作。包含您创建的头文件,但更改为 dllimport 而不是 dllexport。您可以为此使用定义,以便您的 dll 项目使用 dllexport,而所有其他不使用此定义的项目将使用 dllimport。
如果你想自己动态加载dll,你只需要手动LoadLibrary和typedef。如果您执行上述方法,如果 dll 不存在,您的 exe 将失败。
您还可以将 dll 项目构建到静态库中并加载它,这也可以解决该问题,但会增加 exe 的大小。如果你真的希望它是一个 dll,这当然不是一个选择。
When you build your dll, you should also get a lib file that you can link with. That will do the heavy lifting in the background for you. Include the header file that you've created, but change into dllimport instead of dllexport. You can use a define for that, so that for your dll project it uses dllexport and all others that do not use this define will use dllimport.
You only need to do a manual LoadLibrary and the typedef if you want to dynamically load the dll yourself. If you do the above approach your exe will fail if the dll is not present.
You can also build the dll project into a static library and load that instead, which will also get rid of that problem but will increase the size of your exe. This is of course not an option if you really want it to be a dll.
您可以直接链接到 DLL 的符号,而不是使用 GetProcAddress(),后者在运行时获取函数的地址。
示例头文件片段:
然后在
BHannan_Test_Class.cpp
中,您需要在包含标头之前#define MY_LIB_EXPORTS
。在 dll_test.cpp 中,您将包含标头并像使用普通函数一样使用 Factorial() 。链接时,您需要链接到构建 DLL 时生成的导入库。这使得 DLL 中的符号可供链接到它的代码使用。
You can link to the DLL's symbols directly instead of using
GetProcAddress()
, which gets the address of a function at runtime.Example header file snippet:
Then in
BHannan_Test_Class.cpp
, you would#define MY_LIB_EXPORTS
before including the header.In
dll_test.cpp
you would include the header and just useFactorial()
as you would use a normal function. When linking, you want to link to the import library that building your DLL produced. This makes the symbols from the DLL available to the code that links to it.当然,您不需要 typedef
,或者利用 C++ 初始化和测试习惯
来整理类型的重复,
但不使用 typedef 通常更糟而不是更好,因为 C 的函数指针除非您经常使用类型,否则要正确使用它们有点棘手。 (如果您经常使用它们,那么您的软件可能有点不正统)。
Microsoft
dllimport
扩展和编译器创建一个静态库,它为您进行加载并提供 Trampolines 或 thunk,正如其他人发布的那样。除非您正在创建一个不知道它将加载哪个 dll 的插件系统,否则请使用它。Of course you don't need the typedef
or, exploiting C++ initialise and test idiom
given this to tidy up the repetition of the type
But not using a typedef is generally worse rather than better, as C's function pointer types are a bit tricky to get right unless you're using them regularly. (if you are using them regularly, then your software may be somewhat unorthodox).
The Microsoft
dllimport
extensions and compiler create a static library which does the loading for you and provides trampolines or thunks, as others have posted. Unless you're creating a plug-in system which doesn't know which dll it will load, then use that instead.