简体   繁体   English

如何在C ++中调用多线程中的非线程安全DLL?

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

I want to parallelize with threads (not with multi-process) a native c++ code which use a DLL (Compute.dll) which is not thread-safe . 我想与线程(而不是多进程)并行化本机c ++代码,该代码使用不是线程安全的DLL(Compute.dll)。

Actually, I have a kind of loop that I can parallelize : 实际上,我有一种可以并行化的循环:

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

I identify a way to parallelize my native code : Clone and rename Compute.dll in Compute1.dll, Compute2.dll, ..., ComputeN.dll and use one dll by thread. 我找到了一种并行化我的本机代码的方法:克隆并重命名Compute1.dll中的Compute.dll,Compute2.dll,...,ComputeN.dll并通过线程使用一个dll。 So for the link, in the same way, i have to duplicate Compute.lib in Compute1.lib, Compute2.lib, ..., ComputeN.lib 所以对于链接,以同样的方式,我必须复制Compute1.lib中的Compute.lib,Compute2.lib,...,ComputeN.lib

With this solution, I have to duplicate code in my app to define multiple ComputeDLL class : ComputeDLL1, ComputeDLL2, ... ComputeDLLN with an explicit static link : 有了这个解决方案,我必须在我的应用程序中复制代码以定义多个ComputeDLL类:ComputeDLL1,ComputeDLL2,...带有显式静态链接的ComputeDLLN:

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

Can you tell me if this solution will work ? 你能告诉我这个解决方案是否有效吗?

In this solution : 在此解决方案中:

  • the number of thread must be known before compilation 编译前必须知道线程数
  • i have too much duplicated code 我有太多重复的代码

Is there an another cleaner good way to solve my problem ? 还有另一种更清洁的方法可以解决我的问题吗? May I use LoadLibrary() ? 我可以使用LoadLibrary()吗?

Thanks 谢谢

Nb : I don't want to use muli-processing because in my real case, I have to send big data in parameter to my DLL (so i don't want to use files for communication because I need performance) and a DoWork is very fast (10 ms). Nb:我不想使用muli处理,因为在我的实际情况中,我必须将参数中的大数据发送到我的DLL(所以我不想使用文件进行通信,因为我需要性能)而且DoWork是非常快(10毫秒)。 I want to easily work in memory without sockets, windows messages queues, etc... and in the future, i will perhaps need custom synchronization between threads => multi-threading pattern is the best for me 我希望在没有套接字,Windows消息队列等的情况下轻松地在内存中工作......并且在将来,我可能需要线程之间的自定义同步=>多线程模式对我来说是最好的

Using many DLLs has several drawbacks: 使用许多DLL有几个缺点:

  • You will have problems with duplicate symbols. 您将遇到重复符号的问题。 That is the linker will have a difficult time knowing to which dll a function call refers to 也就是说,链接器将很难知道函数调用所指的是哪个dll
  • Threading problems can still occur when the DLL uses some shared global data. 当DLL使用一些共享的全局数据时,仍然可能发生线程问题。
  • You need at least one DLL per thread which means you will have to copy the DLL every time if you want it to work with arbitrary number of threads. 每个线程至少需要一个DLL,这意味着如果您希望它可以使用任意数量的线程,则每次都必须复制DLL。 Very clunky. 非常笨重。

A solution to the duplicate symbols problem is using LoadLibrary / GetProcAddress for every thread you create: 重复符号问题的解决方案是为您创建的每个线程使用LoadLibrary / GetProcAddress:

  • when a thread starts you would have to call LoadLibrary to a DLL not loaded before 当线程启动时,您必须将LoadLibrary调用到之前未加载的DLL
  • then use GetProcAddress to set the thread-local function pointers 然后使用GetProcAddress设置线程局部函数指针
  • from now on, you will have to use these thread-local function pointers to call the functions in the DLLs 从现在开始,您将不得不使用这些线程局部函数指针来调用DLL中的函数

This can be a bad idea when the DLL you load will load another DLL. 当您加载的DLL将加载另一个DLL时,这可能是一个坏主意。 In this case wsock32 loads ws2_32.dll. 在这种情况下,wsock32加载ws2_32.dll。 Problematic: 问题:

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

Here the send loaded from separate copies of wsock32.dll will just point to the same send function, because wsock32.dll is just a trampoline for ws2_32.dll. 这里从wsock32.dll单独副本加载的send只会指向相同的发送函数,因为wsock32.dll只是ws2_32.dll的蹦床。

However, you get different entry points for send, when you load ws2_32. 但是,当您加载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

Additional info: LoadLibrary loads a dll to the address space of the calling process. 附加信息:LoadLibrary将dll加载到调用进程的地址空间。 LoadLibrary remembers when you have already loaded a dll, so by using different dll names you can force loadlibrary to load the same dll to different places in the process address space. LoadLibrary会记住您已加载dll的时间,因此通过使用不同的dll名称,您可以强制loadlibrary将相同的dll加载到进程地址空间中的不同位置。

A better solution would be to manually load the DLL code from memory, it can spare you the trouble of maintaining different copies of the same dll on disk. 更好的解决方案是从内存中手动加载DLL代码,它可以省去在磁盘上维护相同dll的不同副本的麻烦。

http://www.joachim-bauch.de/tutorials/loading-a-dll-from-memory/ 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

This will lead to duplicate symbols on linking stage. 这将导致链接阶段出现重复符号。

But yes, your idea can be implemented using dynamic loading of dlls via LoadLibrary() for each thread separately and dynamic resolving of library functions via GetProcAddress() . 但是,您可以通过LoadLibrary()为每个线程单独动态加载dll并通过GetProcAddress()动态解析库函数来实现您的想法。 You have to have different dll file for each thread (with different names), as you suggested above. 如上所述,您必须为每个线程(具有不同的名称)使用不同的dll文件。

The process approach is the simplest, but you said you had a lot of data to transert. 过程方法是最简单的,但是你说你有很多要转换的数据。 Since you have control over the caller, let me try an answer that involves IPC (inter process communication). 由于您可以控制调用者,让我尝试一个涉及IPC(进程间通信)的答案。 There are multiple ways to do that . 多种方法可以做到这一点

Here is an example of how you would work with named pipes. 以下是如何使用命名管道的示例。

  1. Create a single thread wrapper application that loads the single threaded DLL. 创建一个加载单线程DLL的线程包装器应用程序。
  2. Have the wrapper read a connection name from the command line. 让包装器从命令行读取连接名称。
  3. Listen to that connection. 听听那个联系。
  4. Deserialize what comes in, call the DLL, and if needed serialize the results back. 反序列化的内容,调用DLL,如果需要,将结果序列化。
  5. Add a special message for tearing the wrapper process down. 添加一条特殊消息,用于撕下包装器进程。

Serialization comes with it own set of problems, but you can play it fast and loose if you can assume that both process are on the same host and are the same bitness (both 32 bits or both 64 bits). 序列化,用它自己的问题,但你可以发挥快速和松散的,如果你可以假定这两个过程都在同一台主机上,并同位数 (32位或两个64位)。 If you cannot make that assumption, try protocol buffers . 如果你不能做出这个假设,试试协议缓冲区

With that approach, the caller will create a process, passing that process rendez-vous : 使用这种方法,调用者将创建一个进程,将该进程传递给rendez-vous

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

To call instance 1 of the DLL, you would simply serialize your data and write it to \\\\.\\wrapper_1 . 要调用DLL的实例1,您只需序列化数据并将其写入\\\\.\\wrapper_1

When I had to do this, I recreated the DLL's interface in code so I could replace the wrapper by the real DLL with a #define statement and a rebuild. 当我不得不这样做时,我在代码中重新创建了DLL的接口,因此我可以使用#define语句和重建来替换真实DLL的包装器。 It will help debugging the caller, and isolate IPC problems. 它将帮助调试调用者,并隔离IPC问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM