2013-12-13 59 views
4

我想與線程(不是多進程)並行使用DLL(Compute.dll),這是非線程安全的本地C++代碼如何在C++的多線程中調用非線程安全的DLL?

其實,我有一種循環的,我可以並行:

for(int i = 0; i < x; i++) { 
    ComputeDLL.DoWork(i); // DLL API Call not thread-safe 
} 

我找出一種方法來並行我的本地代碼:克隆和Compute1.dll,Compute2.dll重命名Compute.dll。 ..,ComputeN.dll並通過線程使用一個DLL。 因此,對於鏈接,以同樣的方式,我必須在Compute1.lib,Compute2.lib,...,ComputeN.lib中複製Compute.lib

使用此解決方案,我必須在我的應用程序中複製代碼定義多個ComputeDLL類:ComputeDLL1,ComputeDLL2,... ComputeDLLN有一個明確的靜態鏈接:

#pragma comment(lib,'Compute1.lib'); // for ComputeDLL1.h 
#pragma comment(lib,'Compute2.lib'); // for ComputeDLL2.h 
etc. 

你能告訴我,如果這個解決方案是否行得通呢?

在這個解決方案:

  • 線程的數量必須編譯
  • 之前知道我有太多的重複代碼

有沒有解決我的問題的另一種清潔的好辦法嗎? 我可以使用LoadLibrary()嗎?

感謝

注:我不想因爲我的實際情況使用木裏處理,我必須在參數大數據發送到我的DLL(所以我不希望使用文件通信因爲我需要性能)並且DoWork非常快(10毫秒)。我想輕鬆地在記憶工作,沒有插座,Windows消息隊列,等等......而在將來,我也許會需要線程之間的自定義同步=>多線程模式是最適合我

+1

許多過程將是最安全的(也是最簡單的) - 爲什麼這麼難? – doctorlove

+0

因爲我必須將參數中的大數據發送給我的DLL(所以我不想使用文件進行通信,因爲我需要性能)並且DoWork非常快(10毫秒)。我想在沒有套接字,windows消息隊列等的情況下輕鬆地在內存中工作......將來,我可能需要在線程之間進行自定義同步=>多線程模式對我來說是最好的 –

+2

很多進程,而不是對話彼此,是最安全和最簡單的。 – doctorlove

回答

2

使用許多DLL文件有幾個缺點:

  • 你將不得不重複的符號問題。這就是鏈接器將很難知道函數調用引用哪個dll
  • 當DLL使用某些共享的全局數據時,仍然會出現線程問題。
  • 每個線程至少需要一個DLL,這意味着如果您希望它可以使用任意數量的線程,則每次都必須複製DLL。非常笨重。

一個解決重複的符號問題是使用調用LoadLibrary/GetProcAddress的爲每個線程創建:

  • 當一個線程開始,你就必須調用LoadLibrary來電
  • 那麼之前未加載的DLL使用GetProcAddress從現在開始設置線程本地函數指針
  • ,您將不得不使用這些線程局部函數指針調用DLL中的函數

當你加載的DLL加載另一個DLL時,這可能是一個壞主意。 在這種情況下,wsock32加載ws2_32.dll。問題:

In [1]: import ctypes 

In [2]: k=ctypes.windll.kernel32 

In [3]: a=k.LoadLibraryA('wsock32_1.dll') 

In [4]: b=k.LoadLibraryA('wsock32_2.dll') 

In [5]: a 
Out[5]: 1885405184 

In [6]: b 
Out[6]: 1885339648 

In [7]: k.GetProcAddress(a, "send") 
Out[7]: 1980460801 

In [8]: k.GetProcAddress(b, "send") 
Out[8]: 1980460801 

這裏距離只會指向相同的發送功能的wsock32.dll單獨的副本加載,因爲的wsock32.dll僅僅是ws2_32.dll的蹦牀的send

但是,當您加載ws2_32時,會得到不同的發送入口點。

In [1]: import ctypes 

In [2]: k=ctypes.windll.kernel32 

In [3]: a=k.LoadLibraryA('ws2_32_1.dll') 

In [4]: b=k.LoadLibraryA('ws2_32_2.dll') 

In [5]: a 
Out[5]: 1874853888 

In [6]: b 
Out[6]: 1874591744 

In [7]: k.GetProcAddress(a, "send") 
Out[7]: 1874882305 

In [8]: k.GetProcAddress(b, "send") 
Out[8]: 1874620161 

附加信息:調用LoadLibrary加載DLL的調用進程的地址空間。 LoadLibrary 當您已經加載dll時會記住,因此通過使用不同的dll名稱,您可以強制loadlibrary將相同的dll加載到進程地址空間中的不同位置。

更好的解決方案是從內存中手動加載DLL代碼,它可以免除維護磁盤上相同DLL的不同副本的麻煩。

http://www.joachim-bauch.de/tutorials/loading-a-dll-from-memory/

+0

非常感謝您提供這些警告! –

+0

最後,我將嘗試使用Boost.Interprocess與多進程和共享內存通信的另一個解決方案 –

1
#pragma comment(lib,'Compute1.lib'); // for ComputeDLL1.h 
#pragma comment(lib,'Compute2.lib'); // for ComputeDLL2.h 

這將導致鏈接階段重複的符號。

但是,您的想法可以通過LoadLibrary()爲每個線程單獨動態加載dll來實現,並通過GetProcAddress()動態解析庫函數。您必須爲每個線程(具有不同的名稱)使用不同的dll文件,如上所述。

0

過程方法是最簡單的,但你說你有很多數據來transert。既然你可以控制呼叫者,讓我試試一個涉及IPC(進程間通信)的答案。有multiple ways to do that

下面是一個如何使用命名管道的例子。

  1. 創建加載單線程DLL的單線程包裝器應用程序。
  2. 讓封裝器從命令行讀取連接名稱。
  3. 收聽那個連接。
  4. 反序列化進來的內容,調用DLL,並在需要時序列化結果。
  5. 添加一個特殊消息,用於撕開包裝過程。

序列化,用它自己的問題,但你可以發揮快速和鬆散的,如果你可以假定這兩個過程都在同一臺主機上,並同位數(32位或兩個64位)。如果您無法做出這樣的假設,請嘗試protocol buffers

有了這種方法,調用者將創建一個進程,通過該進程相約

wrapper.exe "\\.\wrapper_1" 
wrapper.exe "\\.\wrapper_2" 
wrapper.exe "\\.\wrapper_3" 

調用DLL的實例1中,您只需將序列化數據並將它寫入\\.\wrapper_1

當我不得不這樣做時,我在代碼中重新創建了DLL的接口,所以我可以用一個#define語句和一個重建來替換真正的DLL的包裝。它將幫助調試調用者,並隔離IPC問題。