2016-05-17 112 views
0

考虑应用程序的微内核软件体系结构。 我有一个内核和一个组件。反向动态链接函数调用

该组件是在运行时使用LoadLibrary API在Windows中由内核加载的DLL;当然,可以使用GetProcAddress来调用导出的函数。

现在组件需要将消息发送到内核。换句话说,现在是一个加载的DLL的组件需要从内核调用函数。什么是正确的机制?

+0

当你说“内核”你是什么意思?在计算机编程中,“内核”这个词有多种用法,这里不清楚究竟哪一种含义是相关的。你还应该[阅读如何提出好问题](http://stackoverflow.com/help/how-to-ask),并学习如何创建[最小,完整和可验证示例](http:// stackoverflow.com/help/mcve)。 –

+0

@JoachimPileborg它改变了答案吗?考虑微内核体系结构。内核构成了应用程序的核心。 –

+0

所以你正在编写自己的*操作系统*内核?没有例如一个CUDA内核?请更新您的标签以反映这一点,或者至少在问题主体中进行拼写。 –

回答

3

它应该工作,在这里看到: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, "[email protected]"); 
    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与建立EXE时创建的“MyExe.lib”链接。 main()从DLL中调用callSum()函数,DLL又调用由EXE提供的getSum()。这就是说,我仍然更愿意使用“依赖倒置”并将接口传递给DLL--对我来说,它似乎更干净,也更灵活(例如通过接口继承进行版本控制等)。

EDIT#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, "[email protected]"); 
    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不需要链接到任何“内核“库(纯粹的虚拟接口不需要导出)。

即使在编译器(即由不同于DLL的编译器编译的可执行文件,例如MSVC和GCC)中,这通常也可以工作 - 假设虚拟表的实现是相同的。这不是强制性的,但它实际上是COM工作的先决条件(提供不同实现多态性的编译器不能使用Microsoft COM调用)。

但特别是在这种情况下,您绝对必须确保在DLL中分配的对象不会在EXE中释放,反之亦然(它们可能会使用不同的堆)。如果这是必要的,那么接口应该提供一个纯粹的虚拟destroy()方法,它可以确保在正确的内存环境中“删除这个”的多态调用。但是,即使直接调用函数,这也可能是个问题(仍然一般不应该在另一端释放()memory malloc())。另外,不得允许C++异常通过API边界。

+0

非常感谢。我正在探索答案。 –

+0

一个问题,这个技术(使用.lib文件)每次修改内核时都需要重新编译组件吗?顺便说一下,有没有任何依赖反转技术的例子? –

+0

我想我得到了问题第一部分的答案。没有必要重新编译使用.lib文件的程序。 –

0

考虑让你的设计相反:也就是说,'内核'被制作成一个DLL并由组件应用程序加载。由于它是为组件提供服务的内核,而不是其他方式,所以这样做更有意义。

+0

内核应该为组件提供通过消息传递相互通信的基础结构。我不能那样做。 –