簡體   English   中英

如何在C ++中調用多線程中的非線程安全DLL?

[英]How to call not-thread safe DLL in multi-thread in C++?

我想與線程(而不是多進程)並行化本機c ++代碼,該代碼使用不是線程安全的DLL(Compute.dll)。

實際上,我有一種可以並行化的循環:

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

我找到了一種並行化我的本機代碼的方法:克隆並重命名Compute1.dll中的Compute.dll,Compute2.dll,...,ComputeN.dll並通過線程使用一個dll。 所以對於鏈接,以同樣的方式,我必須復制Compute1.lib中的Compute.lib,Compute2.lib,...,ComputeN.lib

有了這個解決方案,我必須在我的應用程序中復制代碼以定義多個ComputeDLL類:ComputeDLL1,ComputeDLL2,...帶有顯式靜態鏈接的ComputeDLLN:

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

你能告訴我這個解決方案是否有效嗎?

在此解決方案中:

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

還有另一種更清潔的方法可以解決我的問題嗎? 我可以使用LoadLibrary()嗎?

謝謝

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

使用許多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單獨副本加載的send只會指向相同的發送函數,因為wsock32.dll只是ws2_32.dll的蹦床。

但是,當您加載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/

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

這將導致鏈接階段出現重復符號。

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

過程方法是最簡單的,但是你說你有很多要轉換的數據。 由於您可以控制調用者,讓我嘗試一個涉及IPC(進程間通信)的答案。 多種方法可以做到這一點

以下是如何使用命名管道的示例。

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

序列化,用它自己的問題,但你可以發揮快速和松散的,如果你可以假定這兩個過程都在同一台主機上,並同位數 (32位或兩個64位)。 如果你不能做出這個假設,試試協議緩沖區

使用這種方法,調用者將創建一個進程,將該進程傳遞給rendez-vous

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

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

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM