从DLL动态加载函数
Dynamically load a function from a DLL
我正在看一下。dll文件,我了解它们的用法,我正在努力了解如何使用它们。
我创建了一个.dll文件,其中包含一个返回名为funci()的整数的函数
使用这段代码,我(认为)已经将.dll文件导入到项目中(没有抱怨):
#include <windows.h>
#include <iostream>
int main() {
HINSTANCE hGetProcIDDLL = LoadLibrary("C:\Documents and Settings\User\Desktop \fgfdg\dgdg\test.dll");
if (hGetProcIDDLL == NULL) {
std::cout << "cannot locate the .dll file" << std::endl;
} else {
std::cout << "it has been called" << std::endl;
return -1;
}
int a = funci();
return a;
}
# funci function
int funci() {
return 40;
}
然而,当我尝试编译这个.cpp文件时,我认为已经导入了。dll,我有以下错误:
C:Documents and SettingsUserDesktopfgfdgonemore.cpp||In function 'int main()':|
C:Documents and SettingsUserDesktopfgfdgonemore.cpp|16|error: 'funci' was not declared in this scope|
||=== Build finished: 1 errors, 0 warnings ===|
我知道。dll与头文件不同,所以我知道我不能像这样导入函数,但这是我能想出的最好的方法来表明我已经尝试过了。
我的问题是,我如何使用hGetProcIDDLL
指针访问.dll中的函数。
我希望这个问题是有意义的,我没有再一次错怪别人。
LoadLibrary
并不像你想象的那样。它将DLL加载到当前进程的内存中,但它不会而不是神奇地导入其中定义的函数!这是不可能的,因为函数调用是在编译时由链接器解析的,而LoadLibrary
是在运行时调用的(请记住c++是一种静态类型语言)。
你需要一个单独的WinAPI函数来获取动态加载函数的地址:GetProcAddress
.
#include <windows.h>
#include <iostream>
/* Define a function pointer for our imported
* function.
* This reads as "introduce the new type f_funci as the type:
* pointer to a function returning an int and
* taking no arguments.
*
* Make sure to use matching calling convention (__cdecl, __stdcall, ...)
* with the exported function. __stdcall is the convention used by the WinAPI
*/
typedef int (__stdcall *f_funci)();
int main()
{
HINSTANCE hGetProcIDDLL = LoadLibrary("C:\Documents and Settings\User\Desktop\test.dll");
if (!hGetProcIDDLL) {
std::cout << "could not load the dynamic library" << std::endl;
return EXIT_FAILURE;
}
// resolve function address here
f_funci funci = (f_funci)GetProcAddress(hGetProcIDDLL, "funci");
if (!funci) {
std::cout << "could not locate the function" << std::endl;
return EXIT_FAILURE;
}
std::cout << "funci() returned " << funci() << std::endl;
return EXIT_SUCCESS;
}
另外,您应该正确地从DLL中导出函数。可以这样做:
int __declspec(dllexport) __stdcall funci() {
// ...
}
正如Lundin所指出的,如果不再需要的话,释放库的句柄是一个很好的做法。如果没有其他进程仍然持有同一DLL的句柄,这将导致它被卸载。
除了已经发布的答案之外,我认为我应该分享一个方便的技巧,我使用它通过函数指针将所有DLL函数加载到程序中,而无需为每个函数编写单独的GetProcAddress调用。我也喜欢像在op中尝试的那样直接调用函数。
首先定义一个泛型函数指针类型:
typedef int (__stdcall* func_ptr_t)();
使用什么类型并不重要。现在创建该类型的数组,它对应于DLL中的函数数量:
func_ptr_t func_ptr [DLL_FUNCTIONS_N];
在这个数组中,我们可以存储指向DLL内存空间的实际函数指针。
下一个问题是GetProcAddress
期望函数名为字符串。因此,在DLL中创建一个由函数名组成的类似数组:
const char* DLL_FUNCTION_NAMES [DLL_FUNCTIONS_N] =
{
"dll_add",
"dll_subtract",
"dll_do_stuff",
...
};
现在我们可以很容易地在循环中调用GetProcAddress()并将每个函数存储在该数组中:
for(int i=0; i<DLL_FUNCTIONS_N; i++)
{
func_ptr[i] = GetProcAddress(hinst_mydll, DLL_FUNCTION_NAMES[i]);
if(func_ptr[i] == NULL)
{
// error handling, most likely you have to terminate the program here
}
}
如果循环成功,我们现在唯一的问题是调用函数。前面的函数指针类型定义没有帮助,因为每个函数都有自己的签名。这可以通过创建一个包含所有函数类型的结构体来解决:
typedef struct
{
int (__stdcall* dll_add_ptr)(int, int);
int (__stdcall* dll_subtract_ptr)(int, int);
void (__stdcall* dll_do_stuff_ptr)(something);
...
} functions_struct;
最后,为了连接到前面的数组,创建一个union:
typedef union
{
functions_struct by_type;
func_ptr_t func_ptr [DLL_FUNCTIONS_N];
} functions_union;
现在您可以使用方便的循环从DLL中加载所有函数,但是通过by_type
联合成员调用它们。
当然,输入像
这样的东西有点麻烦 functions.by_type.dll_add_ptr(1, 1);
当你想调用一个函数时。
事实证明,这就是为什么我在名称后添加"ptr"后缀的原因:我想使它们与实际的函数名称保持不同。现在,通过使用一些宏,我们可以简化棘手的结构体语法并获得所需的名称:
#define dll_add (functions.by_type.dll_add_ptr)
#define dll_subtract (functions.by_type.dll_subtract_ptr)
#define dll_do_stuff (functions.by_type.dll_do_stuff_ptr)
现在,您可以使用带有正确类型和参数的函数名,就像它们被静态地链接到您的项目一样:
int result = dll_add(1, 1);
免责声明:严格来说,C标准没有定义不同函数指针之间的转换,因此不安全。形式上,我这里做的是未定义行为。然而,在Windows世界中,函数指针的大小总是相同的,无论它们的类型如何,它们之间的转换在我使用过的任何版本的Windows上都是可以预测的。
此外,理论上可能会在联合/结构体中插入填充,这会导致所有操作失败。然而,指针的大小恰好与Windows中的对齐要求相同。确保struct/union没有填充的static_assert
可能仍然是有序的。
这不是一个热门话题,但我有一个工厂类,允许dll创建实例并将其作为dll返回。这就是我来找的东西,但找不到。
它叫做,
IHTTP_Server *server = SN::SN_Factory<IHTTP_Server>::CreateObject();
IHTTP_Server *server2 =
SN::SN_Factory<IHTTP_Server>::CreateObject(IHTTP_Server_special_entry);
其中IHTTP_Server是在另一个DLL或同一个DLL中创建的类的纯虚拟接口。
DEFINE_INTERFACE用于给类id一个接口。
接口类看起来像,
class IMyInterface
{
DEFINE_INTERFACE(IMyInterface);
public:
virtual ~IMyInterface() {};
virtual void MyMethod1() = 0;
...
};
头文件是这样的
#if !defined(SN_FACTORY_H_INCLUDED)
#define SN_FACTORY_H_INCLUDED
#pragma once
在这个宏定义中列出了这些库。每个库/可执行文件一行。如果我们能调用另一个可执行文件,那就太好了。
#define SN_APPLY_LIBRARIES(L, A)
L(A, sn, "sn.dll")
L(A, http_server_lib, "http_server_lib.dll")
L(A, http_server, "")
然后为每个dll/exe定义一个宏并列出它的实现。Def意味着它是接口的默认实现。如果它不是默认值,则为用于标识它的接口指定一个名称。即,特殊,名称为IHTTP_Server_special_entry。
#define SN_APPLY_ENTRYPOINTS_sn(M)
M(IHTTP_Handler, SNI::SNI_HTTP_Handler, sn, def)
M(IHTTP_Handler, SNI::SNI_HTTP_Handler, sn, special)
#define SN_APPLY_ENTRYPOINTS_http_server_lib(M)
M(IHTTP_Server, HTTP::server::server, http_server_lib, def)
#define SN_APPLY_ENTRYPOINTS_http_server(M)
所有库都设置好后,头文件使用宏定义来定义所需的宏。
#define APPLY_ENTRY(A, N, L)
SN_APPLY_ENTRYPOINTS_##N(A)
#define DEFINE_INTERFACE(I)
public:
static const long Id = SN::I##_def_entry;
private:
namespace SN
{
#define DEFINE_LIBRARY_ENUM(A, N, L)
N##_library,
为库创建一个enum。
enum LibraryValues
{
SN_APPLY_LIBRARIES(DEFINE_LIBRARY_ENUM, "")
LastLibrary
};
#define DEFINE_ENTRY_ENUM(I, C, L, D)
I##_##D##_entry,
为接口实现创建一个enum。
enum EntryValues
{
SN_APPLY_LIBRARIES(APPLY_ENTRY, DEFINE_ENTRY_ENUM)
LastEntry
};
long CallEntryPoint(long id, long interfaceId);
这定义了工厂类。这里没什么可说的。
template <class I>
class SN_Factory
{
public:
SN_Factory()
{
}
static I *CreateObject(long id = I::Id )
{
return (I *)CallEntryPoint(id, I::Id);
}
};
}
#endif //SN_FACTORY_H_INCLUDED
则CPP为,
#include "sn_factory.h"
#include <windows.h>
创建外部入口点您可以使用depends.exe检查它是否存在。
extern "C"
{
__declspec(dllexport) long entrypoint(long id)
{
#define CREATE_OBJECT(I, C, L, D)
case SN::I##_##D##_entry: return (int) new C();
switch (id)
{
SN_APPLY_CURRENT_LIBRARY(APPLY_ENTRY, CREATE_OBJECT)
case -1:
default:
return 0;
}
}
}
宏设置所需的所有数据。
namespace SN
{
bool loaded = false;
char * libraryPathArray[SN::LastLibrary];
#define DEFINE_LIBRARY_PATH(A, N, L)
libraryPathArray[N##_library] = L;
static void LoadLibraryPaths()
{
SN_APPLY_LIBRARIES(DEFINE_LIBRARY_PATH, "")
}
typedef long(*f_entrypoint)(long id);
f_entrypoint libraryFunctionArray[LastLibrary - 1];
void InitlibraryFunctionArray()
{
for (long j = 0; j < LastLibrary; j++)
{
libraryFunctionArray[j] = 0;
}
#define DEFAULT_LIBRARY_ENTRY(A, N, L)
libraryFunctionArray[N##_library] = &entrypoint;
SN_APPLY_CURRENT_LIBRARY(DEFAULT_LIBRARY_ENTRY, "")
}
enum SN::LibraryValues libraryForEntryPointArray[SN::LastEntry];
#define DEFINE_ENTRY_POINT_LIBRARY(I, C, L, D)
libraryForEntryPointArray[I##_##D##_entry] = L##_library;
void LoadLibraryForEntryPointArray()
{
SN_APPLY_LIBRARIES(APPLY_ENTRY, DEFINE_ENTRY_POINT_LIBRARY)
}
enum SN::EntryValues defaultEntryArray[SN::LastEntry];
#define DEFINE_ENTRY_DEFAULT(I, C, L, D)
defaultEntryArray[I##_##D##_entry] = I##_def_entry;
void LoadDefaultEntries()
{
SN_APPLY_LIBRARIES(APPLY_ENTRY, DEFINE_ENTRY_DEFAULT)
}
void Initialize()
{
if (!loaded)
{
loaded = true;
LoadLibraryPaths();
InitlibraryFunctionArray();
LoadLibraryForEntryPointArray();
LoadDefaultEntries();
}
}
long CallEntryPoint(long id, long interfaceId)
{
Initialize();
// assert(defaultEntryArray[id] == interfaceId, "Request to create an object for the wrong interface.")
enum SN::LibraryValues l = libraryForEntryPointArray[id];
f_entrypoint f = libraryFunctionArray[l];
if (!f)
{
HINSTANCE hGetProcIDDLL = LoadLibraryA(libraryPathArray[l]);
if (!hGetProcIDDLL) {
return NULL;
}
// resolve function address here
f = (f_entrypoint)GetProcAddress(hGetProcIDDLL, "entrypoint");
if (!f) {
return NULL;
}
libraryFunctionArray[l] = f;
}
return f(id);
}
}
每个库都包含这个"cpp",每个库/可执行文件都有一个cpp存根。任何特定的编译头文件
#include "sn_pch.h"
设置此库
#define SN_APPLY_CURRENT_LIBRARY(L, A)
L(A, sn, "sn.dll")
主cpp的include。我猜这个cpp可能是。h。但有不同的方法可以做到这一点。这个方法对我很有效。
#include "../inc/sn_factory.cpp"
- 是否有原子加载非原子值的函数?
- 如何修复加载图标()函数不显示图标?
- GLFW DDS 加载函数的 OpenGL 链路错误
- 为什么这个加载函数只抓取文件中的第一件事?
- 使用动态链接加载程序 <dlfcn.h> 而不是直接函数调用的目的是什么?
- 加载由 MATLAB Coder 生成的带有函数的 DLL,该函数调用外部函数
- Linux c++.在预加载的共享库中定义的基类的崩溃调用函数
- 空函数的参数是否加载到缓存中?
- 从动态加载的 dll 内部调用C++函数
- 对外部函数的调用是否强制从内存加载
- 无法使用c++/cli包装从c#dll加载函数
- C/C++函数动态加载器(helper)
- 如何动态加载和调用具有特定于库的类型作为函数参数的符号
- 使用 QOpenGLWidget 加载 OpenGL 函数指针
- 无法从 dll 加载函数
- 使用loadFromFile()函数加载文件
- 如何使用ffi将WinApi函数加载到Node.js中
- 如何使用ASSIMP的ReadFile函数加载多个模型
- 关于 Vulkan 中的函数加载器
- 是否有可能在程序执行过程中通过函数加载精灵、纹理和音乐?