基于DllImport调用供应商DLL时无需复制DLL的多连接方案问询
Great question! The behavior you're seeing tells me the supplier's DLL is using global internal state—it tracks a single connection context per loaded DLL instance. Duplicating the DLL works because each copy gets loaded as a separate, isolated instance with its own state. But there's a cleaner way to do this without copying files or recompiling every time.
1. Dynamic DLL Loading with LoadLibrary + GetProcAddress
This is the most straightforward fix. Instead of using DllImport (which loads the DLL once at startup), you'll load the DLL dynamically each time you need a new connection. Every call to LoadLibrary on the same DLL file creates a separate instance with its own global state.
Here's how to implement this in C#:
First, define delegates that match the signature of the DLL's exported functions:
using System; using System.Runtime.InteropServices; // Match the exact signature of AllocateHandle private delegate bool AllocateHandleDelegate(out uint handle, string connectionDetails); // Match the exact signature of DeallocateHandle private delegate bool DeallocateHandleDelegate(uint handle);
Then, use Windows API functions to load the DLL, get function pointers, and clean up:
[DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr LoadLibrary(string dllPath); [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr GetProcAddress(IntPtr hModule, string procName); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool FreeLibrary(IntPtr hModule); // Helper class to manage a single DLL instance and its connection public class SupplierConnection : IDisposable { private readonly IntPtr _dllHandle; private readonly AllocateHandleDelegate _allocateHandle; private readonly DeallocateHandleDelegate _deallocateHandle; public uint ConnectionHandle { get; private set; } public SupplierConnection(string dllPath, string connectionDetails) { // Load a new instance of the DLL _dllHandle = LoadLibrary(dllPath); if (_dllHandle == IntPtr.Zero) { throw new System.ComponentModel.Win32Exception(); } // Get pointers to the exported functions IntPtr allocatePtr = GetProcAddress(_dllHandle, "AllocateHandle"); IntPtr deallocatePtr = GetProcAddress(_dllHandle, "DeallocateHandle"); if (allocatePtr == IntPtr.Zero || deallocatePtr == IntPtr.Zero) { FreeLibrary(_dllHandle); throw new InvalidOperationException("Could not find exported functions in DLL."); } // Convert pointers to delegates _allocateHandle = Marshal.GetDelegateForFunctionPointer<AllocateHandleDelegate>(allocatePtr); _deallocateHandle = Marshal.GetDelegateForFunctionPointer<DeallocateHandleDelegate>(deallocatePtr); // Initialize the connection if (!_allocateHandle(out ConnectionHandle, connectionDetails)) { FreeLibrary(_dllHandle); throw new InvalidOperationException("Failed to allocate handle for connection."); } } public void Dispose() { // First release the handle using this DLL instance's DeallocateHandle if (ConnectionHandle != 0) { _deallocateHandle(ConnectionHandle); ConnectionHandle = 0; } // Then unload the DLL instance if (_dllHandle != IntPtr.Zero) { FreeLibrary(_dllHandle); } } }
To use this:
// Create two separate connections to different addresses, no DLL duplication needed! using var conn1 = new SupplierConnection(@"C:\Path\To\Supplier.dll", "10.1.1.1"); using var conn2 = new SupplierConnection(@"C:\Path\To\Supplier.dll", "10.1.1.2"); // Use conn1.ConnectionHandle and conn2.ConnectionHandle with other DLL functions (you'd extend the SupplierConnection class to wrap those too)
Key notes:
- Each
SupplierConnectioninstance uses its own loaded DLL copy, so their states don't interfere. - Always call
Dispose()(or useusingstatements) to clean up the handle and unload the DLL. - You'll need to wrap any other DLL functions you need in the same way, using delegates and
GetProcAddress.
2. Check for Supplier API Alternatives
Before diving into dynamic loading, double-check the supplier's documentation or reach out to their support. Some DLLs offer a way to create multiple connections explicitly—for example:
- Functions that accept a custom context object (instead of relying on global state)
- An extended
AllocateHandlemethod that takes an instance ID or additional parameters to isolate connections
If such an API exists, this will be a much cleaner solution than dynamic loading.
3. AppDomain Isolation (Advanced)
For more complex scenarios, you could create a separate AppDomain for each connection. Each AppDomain loads its own copy of the DLL, so they're fully isolated. However, this approach adds significant complexity:
- Data passed between AppDomains must be serializable
- Managing AppDomains and cleanup is more involved
Dynamic loading is almost always preferable unless you have specific reasons to use AppDomains.
内容的提问来源于stack exchange,提问作者Tvde1

