Usando una dll de 32 bits o de 64 bits en C#DllImport
.net pinvoke (7)
Aquí está la situación, estoy usando un dll basado en C en mi aplicación dot.net. Hay 2 archivos DLL, uno es de 32 bits llamado MyDll32.dll y el otro es una versión de 64 bits llamada MyDll64.dll.
Hay una variable estática que contiene el nombre del archivo DLL: cadena DLL_FILE_NAME.
y se usa de la siguiente manera:
[DllImport(DLL_FILE_NAME, CallingConvention=CallingConvention.Cdecl, EntryPoint=Func1")]
private static extern int is_Func1(int var1, int var2);
Simple hasta ahora.
Como puede imaginar, el software se compila con "Cualquier CPU" activada.
También tengo el siguiente código para determinar si el sistema debe usar el archivo de 64 bits o el archivo de 32 bits.
#if WIN64
public const string DLL_FILE_NAME = "MyDll64.dll";
#else
public const string DLL_FILE_NAME = "MyDll32.dll";
#endif
Por ahora debería ver el problema ... DLL_FILE_NAME está definido en tiempo de compilación y no en tiempo de ejecución, por lo que la dll correcta no se carga de acuerdo con el contexto de ejecución.
¿Cuál sería la forma correcta de tratar este problema? ¿No quiero dos archivos de ejecución (uno para 32 bits y otro para 64 bits)? ¿Cómo puedo configurar DLL_FILE_NAME antes de que se use en la declaración DllImport?
Hay una variable estática que contiene el nombre del archivo DLL
No es una variable estática. Es una constante, en tiempo de compilación. No se puede cambiar una constante de tiempo de compilación en tiempo de ejecución.
¿Cuál sería la forma correcta de tratar este problema?
Honestamente, recomendaría simplemente apuntar a x86 y olvidarse de la versión de 64 bits, y dejar que su aplicación se ejecute en WOW64, a menos que su aplicación tenga la necesidad de ejecutarse como x64.
Si hay necesidad de x64, podrías:
Cambie los archivos DLL para que tengan el mismo nombre, como
MyDll.dll
, y en el momento de instalar / implementar, coloque el correcto en su lugar. (Si el sistema operativo es x64, implemente la versión de 64 bits de la DLL, de lo contrario, la versión x86).Tenga dos compilaciones separadas en conjunto, una para x86 y otra para x64.
Aquí hay otra alternativa que requiere que las dos DLL tengan el mismo nombre y estén ubicadas en carpetas diferentes. Por ejemplo:
-
win32/MyDll.dll
-
win64/MyDll.dll
El truco es cargar manualmente la DLL con LoadLibrary
antes de que CLR lo haga. A continuación, verá que ya está cargado un MyDll.dll
y lo utiliza.
Esto se puede hacer fácilmente en el constructor estático de la clase padre.
static class MyDll
{
static MyDll()
{
var myPath = new Uri(typeof(MyDll).Assembly.CodeBase).LocalPath;
var myFolder = Path.GetDirectoryName(myPath);
var is64 = IntPtr.Size == 8;
var subfolder = is64 ? "//win64//" : "//win32//";
LoadLibrary(myFolder + subfolder + "MyDll.dll");
}
[DllImport("kernel32.dll")]
private static extern IntPtr LoadLibrary(string dllToLoad);
[DllImport("MyDll.dll")]
public static extern int MyFunction(int var1, int var2);
}
EDITAR 01/02/2017 : Use Assembly.CodeBase
para que funcione incluso si Shadow Copying está habilitado.
En este caso, debería hacer esto (haga 2 carpetas, x64 y x86 + coloque la dll correspondiente, CON EL MISMO NOMBRE, en ambas carpetas):
using System;
using System.Runtime.InteropServices;
using System.Reflection;
using System.IO;
class Program {
static void Main(string[] args) {
var path = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
path = Path.Combine(path, IntPtr.Size == 8 ? "x64" : "x86");
bool ok = SetDllDirectory(path);
if (!ok) throw new System.ComponentModel.Win32Exception();
}
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool SetDllDirectory(string path);
}
He encontrado que la forma más sencilla de hacer esto es importar los dos métodos con nombres diferentes y llamar al correcto. La DLL no se cargará hasta que se realice la llamada, así que está bien:
[DllImport("MyDll32.dll", EntryPoint = "Func1", CallingConvention = CallingConvention.Cdecl)]
private static extern int Func1_32(int var1, int var2);
[DllImport("MyDll64.dll", EntryPoint = "Func1", CallingConvention = CallingConvention.Cdecl)]
private static extern int Func1_64(int var1, int var2);
public static int Func1(int var1, int var2) {
return IntPtr.Size == 8 /* 64bit */ ? Func1_64(var1, var2) : Func1_32(var1, var2);
}
Por supuesto, si tiene muchas importaciones, esto puede ser bastante engorroso de mantener manualmente.
He utilizado uno de los enfoques mencionados por vcsjones:
"Cambie los archivos DLL para que tengan el mismo nombre, como MyDll.dll, y en el momento de la instalación / implementación, coloque el correcto en su lugar".
Este enfoque requiere mantener dos plataformas de compilación, aunque vea este enlace para obtener más detalles: https://.com/a/6446638/38368
Lo que describe se conoce como "ensamblaje en paralelo" (dos versiones del mismo ensamblaje, uno de 32 y otro de 64 bits) ... Creo que esto le resultará útil:
- Usar ensamblajes paralelos para cargar la versión x64 o x32 de una DLL
- http://blogs.msdn.com/b/gauravseth/archive/2006/03/07/545104.aspx
- http://www.thescarms.com/dotnet/Assembly.aspx
Here puede encontrar un tutorial para su situación exacta (.NET DLL que envuelve a C ++ / CLI DLL que hace referencia a una DLL nativa).
RECOMENDACIÓN:
Simplemente compílelo como x86 y termine con él ... o tenga 2 versiones (una x86 y una x64) ... ya que las técnicas anteriores son bastante complicadas ...
un enfoque alternativo puede ser
public static class Sample
{
public Sample()
{
string StartupDirEndingWithSlash = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName) + "//";
string ResolvedDomainTimeFileName = StartupDirEndingWithSlash + "ABCLib_Resolved.dll";
if (!File.Exists(ResolvedDomainTimeFileName))
{
if (Environment.Is64BitProcess)
{
if (File.Exists(StartupDirEndingWithSlash + "ABCLib_64.dll"))
File.Copy(StartupDirEndingWithSlash + "ABCLib_64.dll", ResolvedDomainTimeFileName);
}
else
{
if (File.Exists(StartupDirEndingWithSlash + "ABCLib_32.dll"))
File.Copy(StartupDirEndingWithSlash + "ABCLib_32.dll", ResolvedDomainTimeFileName);
}
}
}
[DllImport("ABCLib__Resolved.dll")]
private static extern bool SomeFunctionName(ref int FT);
}