[英]Using a 32bit or 64bit dll in C# DllImport
情况是这样的,我在我的 dot.net 应用程序中使用基于 dll 的 C。 有 2 个 dll,一个是 32 位版本,名为 MyDll32.dll,另一个是 64 位版本,名为 MyDll64.dll。
有一个 static 变量保存 DLL 文件名:string DLL_FILE_NAME。
它以下列方式使用:
[DllImport(DLL_FILE_NAME, CallingConvention=CallingConvention.Cdecl, EntryPoint=Func1")]
private static extern int is_Func1(int var1, int var2);
到目前为止很简单。
可以想象,该软件是在“任何 CPU”打开的情况下编译的。
我还有以下代码来确定系统应该使用 64 位文件还是 32 位文件。
#if WIN64
public const string DLL_FILE_NAME = "MyDll64.dll";
#else
public const string DLL_FILE_NAME = "MyDll32.dll";
#endif
现在你应该看到问题了.. DLL_FILE_NAME 是在编译时定义的,而不是在执行时定义的,所以正确的 dll 没有根据执行上下文加载。
处理这个问题的正确方法是什么? 我不想要两个执行文件(一个用于 32 位,另一个用于 64 位)? 在 DllImport 语句中使用之前如何设置 DLL_FILE_NAME?
我发现最简单的方法是导入具有不同名称的两种方法,并调用正确的方法。 在调用之前不会加载 DLL,所以它很好:
[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);
}
当然,如果您有很多导入,手动维护会变得非常麻烦。
这是另一种替代方法,它要求两个 DLL 具有相同的名称并放置在不同的文件夹中。 例如:
win32/MyDll.dll
win64/MyDll.dll
诀窍是在 CLR 执行之前使用LoadLibrary
手动加载 DLL。 然后它会看到一个MyDll.dll
已经被加载并使用它。
这可以在父类的静态构造函数中轻松完成。
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);
}
编辑 2017/02/01 :使用Assembly.CodeBase
以便即使启用了阴影复制它也能工作。
在这种情况下,我应该这样做(创建 2 个文件夹,x64 和 x86 + 将相应的 dll,同名,放在两个文件夹中):
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);
}
有一个静态变量保存 DLL 文件名
它不是静态变量。 它是一个常量,在编译时。 您不能在运行时更改编译时常量。
处理这个问题的正确方法是什么?
老实说,我建议只针对 x86 而忘记 64 位版本,并让您的应用程序在 WOW64 上运行,除非您的应用程序迫切需要以 x64 运行。
如果需要 x64,您可以:
将 DLL 更改为具有相同名称,例如MyDll.dll
,并在安装/部署时放置正确的 DLL。 (如果操作系统是 x64,则部署 64 位版本的 DLL,否则部署 x86 版本)。
总共有两个单独的版本,一个用于 x86,一个用于 x64。
您所描述的称为“并行程序集”(同一程序集的两个版本,一个是 32 位,另一个是 64 位)……我想您会发现这些很有帮助:
在这里,您可以找到针对您的场景的演练(.NET DLL 包装 C++/CLI DLL 引用本机 DLL)。
推荐:
只需将其构建为 x86 并完成它...或者有 2 个构建(一个 x86 和一个 x64)...因为上述技术相当复杂...
另一种方法可能是
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);
}
我使用了 vcsjones 所指的方法之一:
“将 DLL 更改为具有相同名称,例如 MyDll.dll,并在安装/部署时将正确的放在适当的位置。”
这种方法需要维护两个构建平台,但请参阅此链接了解更多详细信息: https : //stackoverflow.com/a/6446638/38368
我用于V8.Net的技巧是:
V8.Net-ProxyInterface
; 例子: public unsafe static class V8NetProxy
{
#if x86
[DllImport("V8_Net_Proxy_x86")]
#elif x64
[DllImport("V8_Net_Proxy_x64")]
#else
[DllImport("V8_Net_Proxy")] // (dummy - NOT USED!)
#endif
public static extern NativeV8EngineProxy* CreateV8EngineProxy(bool enableDebugging, void* debugMessageDispatcher, int debugPort);
这是您将参考的项目。 不要引用接下来的两个:
再创建两个项目以生成库的 x64 和 x86 版本。 这非常简单:只需复制粘贴即可在同一文件夹中复制.csproj
文件.csproj
命名它们。 在我的例子中,项目文件被重命名为V8.Net-ProxyInterface-x64
和V8.Net-ProxyInterface-x86
,然后我将项目添加到我的解决方案中。 在 Visual Studio 中打开每个项目的项目设置,并确保Assembly Name
中包含 x64 或 x86。 此时您有 3 个项目:第一个“占位符”项目和 2 个特定于体系结构的项目。 对于 2 个新项目:
a) 打开x64接口项目设置,进入Build
选项卡,在最上方选择All Platforms
for Platform
,然后在Conditional compilation symbols
输入x64
。
b) 打开 x86 接口项目设置,进入Build
选项卡,选择最上方的All Platforms
for Platform
,然后在Conditional compilation symbols
输入x86
。
打开Build->Configuration Manager...
,并确保x64
被选中作为平台,x64的项目, x86
选择为x86项目,对于这两种Debug
和Release
配置。
确保 2 个新接口项目(用于 x64 和 x86)输出到宿主项目的相同位置(请参阅项目设置Build->Output path
)。
最后的魔法:在我引擎的静态构造函数中,我快速附加到程序集解析器:
static V8Engine()
{
AppDomain.CurrentDomain.AssemblyResolve += Resolver;
}
在Resolver
方法中,我只是根据当前进程指示的当前平台加载文件(注意:此代码为精简版,未测试):
var currentExecPath = Assembly.GetExecutingAssembly().Location;
var platform = Environment.Is64BitProcess ? "x64" : "x86";
var filename = "V8.Net.Proxy.Interface." + platform + ".dll"
return Assembly.LoadFrom(Path.Combine(currentExecPath , fileName));
最后,转到解决方案资源管理器中的宿主项目,展开References
,选择您在步骤 1 中创建的第一个虚拟项目,右键单击它以打开属性,并将Copy Local
设置为false
。 这允许您为每个 P/Invoke 函数开发一个名称,同时使用解析器确定实际加载哪个名称。
请注意,程序集加载器仅在需要时运行。 它仅在第一次访问引擎类时由 CLR 系统自动触发(在我的情况下)。 这如何转化为您取决于您的宿主项目的设计方式。
基于Julien Lebosquain 的出色回答,这就是我在类似案例中最终所做的事情:
private static class Api32
{
private const string DllPath = "MyDll32.dll";
[DllImport(DllPath, CallingConvention = CallingConvention.Cdecl)]
private static extern int Func1(int var1, int var2);
[DllImport(DllPath, CallingConvention = CallingConvention.Cdecl)]
private static extern int Func2();
...
}
private static class Api64
{
private const string DllPath = "MyDll64.dll";
[DllImport(DllPath, CallingConvention = CallingConvention.Cdecl)]
private static extern int Func1(int var1, int var2);
[DllImport(DllPath, CallingConvention = CallingConvention.Cdecl)]
private static extern int Func2();
...
}
public static int Func1(int var1, int var2) {
return Environment.Is64BitProcess
? Api64.Func1(var1, var2)
: Api32.Func1(var1, var2);
}
我认为如果您在同一个 DLL 中有多个入口点,则此选项的扩展性更好,原因如下:
我认为这有助于动态加载 DLL:
#if X64
[DllImport("MyDll64.dll", CallingConvention=CallingConvention.Cdecl, EntryPoint=Func1")]
#else
[DllImport("MyDll32.dll", CallingConvention=CallingConvention.Cdecl, EntryPoint=Func1")]
#endif
private static extern int is_Func1(int var1, int var2);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.