Хотя я получил много ответов, в которых предлагалось перейти к решению 3, и хотя я согласен, что это лучшее решение концептуально , я думаю, что не было никакого способа реализовать это решение практически и надежно при моих ограничениях.
Вместо этого я фактически реализовал вариант решения № 1.Хотя имя DLL в DLLImport
должно быть константой времени компиляции, этот вопрос объясняет, как сделать это динамически.
Если мой код раньше выглядел так:
using System.Runtime.InteropServices;
class DotNetAccess {
[DllImport("mylib.dll", EntryPoint="GetVersion")]
private static extern int _getVersion();
public int GetVersion()
{
return _getVersion();
//May include error handling
}
}
Теперь это выглядит так:
using System.IO;
using System.ComponentModel;
using System.Runtime.InteropServices;
using Assembly = System.Reflection.Assembly;
class DotNetAccess: IDisposable {
[DllImport("kernel32.dll", EntryPoint="LoadLibrary", SetLastError=true)]
private static extern IntPtr _loadLibrary(string name);
[DllImport("kernel32.dll", EntryPoint = "FreeLibrary", SetLastError = true)]
private static extern bool _freeLibrary(IntPtr hModule);
[DllImport("kernel32.dll", EntryPoint="GetProcAddress", CharSet=CharSet.Ansi, ExactSpelling=true, SetLastError=true)]
private static extern IntPtr _getProcAddress(IntPtr hModule, string name);
private static IntPtr LoadLibrary(string name)
{
IntPtr dllHandle = _loadLibrary(name);
if (dllHandle == IntPtr.Zero)
throw new Win32Exception();
return dllHandle;
}
private static void FreeLibrary(IntPtr hModule)
{
if (!_freeLibrary(hModule))
throw new Win32Exception();
}
private static D GetProcEntryDelegate<D>(IntPtr hModule, string name)
where D: class
{
IntPtr addr = _getProcAddress(hModule, name);
if (addr == IntPtr.Zero)
throw new Win32Exception();
return Marshal.GetDelegateForFunctionPointer(addr, typeof(D)) as D;
}
private string dllPath;
private IntPtr dllHandle;
public DotNetAccess()
{
string dllDir = Path.GetDirectoryName(Assembly.GetCallingAssembly().Location);
string origDllPath = Path.Combine(dllDir, "mylib.dll");
if (!File.Exists(origDllPath))
throw new Exception("MyLib DLL not found");
string myDllPath = Path.Combine(dllDir, String.Format("mylib-{0}.dll", GetHashCode()));
File.Copy(origDllPath, myDllPath);
dllPath = myDllPath;
dllHandle = LoadLibrary(dllPath);
_getVersion = GetProcEntryDelegate<_getVersionDelegate>(dllHandle, "GetVersion");
}
public void Dispose()
{
if (dllHandle != IntPtr.Zero)
{
FreeLibrary(dllHandle);
dllHandle = IntPtr.Zero;
}
if (dllPath != null)
{
File.Delete(dllPath);
dllPath = null;
}
}
private delegate int _getVersionDelegate();
private readonly _getVersionDelegate _getVersion;
public int GetVersion()
{
return _getVersion();
//May include error handling
}
}
Фу.
Это может показаться чрезвычайно сложным, если вы видите две версии рядом друг с другом, но после того, как вы установилиинфраструктура, это очень систематическое изменение.И что более важно, он локализует изменения в моем DotNetAccess
слое, что означает, что мне не нужно делать модификации, разбросанные по очень большой кодовой базе, которая не принадлежит мне.