2016-05-17 113 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

內核應該爲組件提供通過消息傳遞相互通信的基礎結構。我不能那樣做。 –