反向动态链接函数调用
Reverse Dynamic Linking function call
考虑应用程序的微内核软件体系结构。我有一个内核和一个组件。
该组件是由内核在Windows中使用LoadLibrary
API在运行时加载的DLL;当然,导出的函数可以使用GetProcAddress
调用。
现在组件需要向内核发送消息。换句话说,组件现在是一个加载的DLL,需要从内核调用函数。什么是正确的机制?
它应该可以工作,请参阅此处:https://stackoverflow.com/a/30475042/1274747
对于MSVC,你基本上会在.exe中使用__declspec(dllexport)
。编译器/链接器为.exe生成导入库,然后可以将其与DLL链接,然后DLL将使用.exe中的符号。
另一种选择是通过"依赖反转"来解决这个问题——.exe不会导出符号,但会提供一个(纯虚拟)接口,该接口将在.exe内部实现,并在加载后传递(通过引用或指向接口的指针)到DLL中。DLL可以调用接口上的方法,接口是在.exe中提供的。但事实上,当你谈到微内核时,这取决于虚拟调用开销是否对你来说是可以接受的(尽管从.exe导出函数时,该方法也是通过函数指针调用的AFAIK,所以我预计不会有任何显著差异)。
编辑
我刚刚创建了一个对我有用的例子(只是一个快速的代码,没有太多的修饰,通常会使用头等):
文件"mydll.cpp":
// resolved against the executable
extern "C" __declspec(dllimport)
int __stdcall getSum(int a, int b);
extern "C" __declspec(dllexport)
int __stdcall callSum(int a, int b)
{
return getSum(a, b);
}
文件"myexe.cpp":
#include <iostream>
using namespace std;
#include <windows.h>
// export from the .exe
extern "C" __declspec(dllexport)
int __stdcall getSum(int a, int b)
{
return a + b;
}
typedef int(__stdcall * callSumFn)(int a, int b);
int main()
{
HMODULE hLibrary = LoadLibrary(TEXT("MyDll.dll"));
if (!hLibrary)
{
cerr << "Failed to load library" << endl;
return 1;
}
callSumFn callSum = (callSumFn)GetProcAddress(hLibrary, "_callSum@8");
if (!callSum)
{
cerr << "Failed to get function address" << endl;
FreeLibrary(hLibrary);
return 1;
}
cout << "callSum(3, 4) = " << callSum(3, 4) << endl;
FreeLibrary(hLibrary);
return 0;
}
DLL链接到"MyExe.lib",后者是在构建EXE时创建的。main()
从DLL调用callSum()
函数,而DLL又调用EXE提供的getSum()
。
话虽如此,我仍然更喜欢使用"依赖反转"并将接口传递给DLL——对我来说,这似乎更干净,也更灵活(例如,通过接口继承进行版本控制等)
编辑#2
至于依赖性反转技术,例如可以是这样的:
文件ikernel.hpp(由内核可执行文件提供,而不是由DLL提供):
#ifndef IKERNEL_HPP
#define IKERNEL_HPP
class IKernel
{
protected:
// or public virtual, but then there are differences between different compilers
~IKernel() {}
public:
virtual int someKernelFunc() = 0;
virtual int someOtherKernelFunc(int x) = 0;
};
#endif
文件"mydll.cpp":
#include "ikernel.hpp"
// if passed the class by pointer, can be extern "C", i.e. loadable by LoadLibrary/GetProcAddress
extern "C" __declspec(dllexport)
int __stdcall callOperation(IKernel *kernel, int x)
{
return kernel->someKernelFunc() + kernel->someOtherKernelFunc(x);
}
文件"myexe.cpp":
#include "ikernel.hpp"
#include <iostream>
using namespace std;
#include <windows.h>
// the actual kernel definition
class KernelImpl: public IKernel
{
public:
virtual ~KernelImpl() {}
virtual int someKernelFunc()
{
return 10;
}
virtual int someOtherKernelFunc(int x)
{
return x + 20;
}
};
typedef int(__stdcall * callOperationFn)(IKernel *kernel, int x);
int main()
{
HMODULE hLibrary = LoadLibrary(TEXT("ReverseDll.dll"));
if (!hLibrary)
{
cerr << "Failed to load library" << endl;
return 1;
}
callOperationFn callOperation = (callOperationFn)GetProcAddress(hLibrary, "_callOperation@8");
if (!callOperation)
{
cerr << "Failed to get function address" << endl;
FreeLibrary(hLibrary);
return 1;
}
KernelImpl kernel;
cout << "callOperation(kernel, 5) = " << callOperation(&kernel, 5) << endl;
FreeLibrary(hLibrary);
return 0;
}
如前所述,这更灵活,IMHO更易于维护;内核可以为不同的DLL调用提供不同的回调。如果有必要,DLL还可以提供一些接口的实现,作为内核的说明符,这些接口将首先从DLL中检索,内核将在其上调用函数。
另一个方便之处是DLL不需要链接到任何"内核"库(纯虚拟接口不需要导出)。
这通常甚至适用于编译器(即由与DLL不同的编译器编译的可执行文件,例如MSVC和GCC),前提是虚拟表实现相同。这不是强制性的,但它实际上是COM工作的先决条件(提供不同多态性实现的编译器不能使用Microsoft COM调用)。
但尤其是在这种情况下,您必须确保DLL中分配的对象不会在EXE中释放,反之亦然(它们可能使用不同的堆)。如果有必要,接口应该提供一个纯虚拟destroy()方法,它可以确保在正确的内存上下文中多态调用"delete this"。但是,即使直接调用函数,这也可能是一个问题(通常情况下,不应该在另一侧释放()内存malloc()-ed)。此外,C++异常不能被允许通过API边界。
考虑以另一种方式进行设计:也就是说,"内核"是一个DLL,由组件应用程序加载。由于是内核向组件提供服务,而不是反过来,所以这更有意义。
- 构造函数的链接时间自动注册
- 如何使用模板元编程在自由函数C++链接两个不相关的类
- 是否可以在调用链接器时强制 CMake 重新排序参数?
- 引用构造函数时链接失败
- 如何在Linux中使用GLFW函数正确链接C++对象?
- 带有虚拟函数的链接器错误C
- 使用<实验/文件系统>和调用函数的链接器错误
- C++在使用模板时没有用于调用的匹配函数,链接器错误
- g++/clang 覆盖特定函数的链接器
- C++命名空间中静态函数的链接
- 内联函数的链接器未解析的外部符号
- 方法调用链接;返回指针与引用
- 模板类中与友元函数的链接错误
- 未定义的引用;类公共函数在链接时不可访问
- StrRetToBuf函数的链接器错误
- C++生成器链接器是否支持函数级链接
- 注释掉几个函数后链接器错误
- 从文件调用 .mm 函数时链接器出错.cpp
- 尽管使用了 EXTERN "C",但从C++调用 C 函数时链接器出错
- C++虚函数:链接器是否可以删除虚函数表中未调用的条目?