简体   繁体   中英

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 .

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. So for the link, in the same way, i have to duplicate Compute.lib in Compute1.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 :

#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() ?

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). 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

Using many DLLs has several drawbacks:

  • 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
  • Threading problems can still occur when the DLL uses some shared global data.
  • 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. Very clunky.

A solution to the duplicate symbols problem is using LoadLibrary / GetProcAddress for every thread you create:

  • when a thread starts you would have to call LoadLibrary to a DLL not loaded before
  • then use GetProcAddress to set the thread-local function pointers
  • from now on, you will have to use these thread-local function pointers to call the functions in the DLLs

This can be a bad idea when the DLL you load will load another DLL. In this case wsock32 loads 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.

However, you get different entry points for send, when you load 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 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.

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.

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() . You have to have different dll file for each thread (with different names), as you suggested above.

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). 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.
  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.
  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). If you cannot make that assumption, try protocol buffers .

With that approach, the caller will create a process, passing that process 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 .

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. It will help debugging the caller, and isolate IPC problems.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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