反向动态链接函数调用
Reverse Dynamic Linking function call
考虑应用程序的微内核软件架构。
我有一个内核和一个组件。
组件是内核在运行时使用LoadLibrary
API in Windows加载的DLL;当然,可以使用 GetProcAddress
.
调用导出的函数
现在组件需要向内核发送消息。换句话说,组件现在是一个加载的 DLL,需要从内核调用函数。什么是正确的机制?
考虑以相反的方式进行设计:也就是说,'kernel' 是一个 DLL 并由您的组件应用程序加载。由于是内核为组件提供服务,而不是相反,这更有意义。
应该可以,请看这里:https://whosebug.com/a/30475042/1274747
对于 MSVC,您基本上会在 .exe 中使用 __declspec(dllexport)
。 compiler/linker 为 .exe 生成导入库,然后可以将其与 DLL 链接,DLL 将使用 .exe 中的符号。
另一种选择是通过 "dependency inversion" 解决这个问题 - .exe 不会导出符号,但会提供一个(纯虚拟)接口,该接口将在 .exe 内部实现并通过(通过引用或指向接口的指针)加载后进入 DLL。然后 DLL 可以调用 .exe 内部提供的接口上的方法。但实际上,正如您所说的微内核,这取决于虚拟调用开销是否为您接受 table (尽管从 .exe 导出函数时,该方法也是通过函数指针调用的 AFAIK,所以我不希望有任何显着差异)。
编辑
我刚刚创建了一个对我有用的示例(只是一个快速代码,没有太多润色,通常会使用 headers 等):
文件"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()
函数,后者又调用 EXE 提供的 getSum()
。
话虽如此,我仍然更喜欢使用 "dependency inversion" 并将接口传递给 DLL - 对我来说,它看起来更干净也更灵活(例如通过接口继承等进行版本控制) .
编辑#2
至于依赖倒置技术,例如可以是这样的:
文件ikernel.hpp(由内核executable提供,不是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;
}
如前所述,这更灵活,恕我直言,更易于维护;内核可以为不同的 DLL 调用提供不同的回调。如果有必要,DLL 还可以提供一些接口的实现作为内核的说明符,首先从 DLL 中检索,内核将调用函数。
另一个方便是DLL不需要链接到任何"kernel"库(不需要导出纯虚拟接口)。
这通常甚至可以跨编译器工作(即 executable 由与 DLL 不同的编译器编译,例如 MSVC 和 GCC)——前提是虚拟 table 实现是相同的。这不是强制性的,但它实际上是 COM 工作的先决条件(提供不同多态性实现的编译器不能使用 Microsoft COM 调用)。
但尤其是在那种情况下,您绝对必须确保在 DLL 中分配的 objects 不会在 EXE 中释放,反之亦然(它们可能使用不同的堆)。如果有必要,该接口应提供纯虚拟 destroy() 方法,以确保在正确的内存上下文中对 "delete this" 进行多态调用。但这可能是一个问题,即使直接调用函数(通常仍然不应该 free() memory malloc()-ed 在另一边)。此外,不得允许 C++ 异常通过 API 边界。
考虑应用程序的微内核软件架构。 我有一个内核和一个组件。
组件是内核在运行时使用LoadLibrary
API in Windows加载的DLL;当然,可以使用 GetProcAddress
.
现在组件需要向内核发送消息。换句话说,组件现在是一个加载的 DLL,需要从内核调用函数。什么是正确的机制?
考虑以相反的方式进行设计:也就是说,'kernel' 是一个 DLL 并由您的组件应用程序加载。由于是内核为组件提供服务,而不是相反,这更有意义。
应该可以,请看这里:https://whosebug.com/a/30475042/1274747
对于 MSVC,您基本上会在 .exe 中使用 __declspec(dllexport)
。 compiler/linker 为 .exe 生成导入库,然后可以将其与 DLL 链接,DLL 将使用 .exe 中的符号。
另一种选择是通过 "dependency inversion" 解决这个问题 - .exe 不会导出符号,但会提供一个(纯虚拟)接口,该接口将在 .exe 内部实现并通过(通过引用或指向接口的指针)加载后进入 DLL。然后 DLL 可以调用 .exe 内部提供的接口上的方法。但实际上,正如您所说的微内核,这取决于虚拟调用开销是否为您接受 table (尽管从 .exe 导出函数时,该方法也是通过函数指针调用的 AFAIK,所以我不希望有任何显着差异)。
编辑
我刚刚创建了一个对我有用的示例(只是一个快速代码,没有太多润色,通常会使用 headers 等):
文件"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()
函数,后者又调用 EXE 提供的 getSum()
。
话虽如此,我仍然更喜欢使用 "dependency inversion" 并将接口传递给 DLL - 对我来说,它看起来更干净也更灵活(例如通过接口继承等进行版本控制) .
编辑#2
至于依赖倒置技术,例如可以是这样的:
文件ikernel.hpp(由内核executable提供,不是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;
}
如前所述,这更灵活,恕我直言,更易于维护;内核可以为不同的 DLL 调用提供不同的回调。如果有必要,DLL 还可以提供一些接口的实现作为内核的说明符,首先从 DLL 中检索,内核将调用函数。
另一个方便是DLL不需要链接到任何"kernel"库(不需要导出纯虚拟接口)。
这通常甚至可以跨编译器工作(即 executable 由与 DLL 不同的编译器编译,例如 MSVC 和 GCC)——前提是虚拟 table 实现是相同的。这不是强制性的,但它实际上是 COM 工作的先决条件(提供不同多态性实现的编译器不能使用 Microsoft COM 调用)。
但尤其是在那种情况下,您绝对必须确保在 DLL 中分配的 objects 不会在 EXE 中释放,反之亦然(它们可能使用不同的堆)。如果有必要,该接口应提供纯虚拟 destroy() 方法,以确保在正确的内存上下文中对 "delete this" 进行多态调用。但这可能是一个问题,即使直接调用函数(通常仍然不应该 free() memory malloc()-ed 在另一边)。此外,不得允许 C++ 异常通过 API 边界。