在 C # DllImport 中使用32位或64位 dll

情况是这样的,我在 dot.net 应用程序中使用了一个基于 C 的 dll。有两个 dll,一个是32位的 MyDll32.dll,另一个是64位的 MyDll64.dll。

有一个静态变量保存 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 之前

61100 次浏览

有一个持有 DLL 文件名的静态变量

它不是静态变量。在编译时,它是一个常量。不能在运行时更改编译时常数。

处理这个问题的正确方法是什么?

老实说,我建议只针对 x86,忘记所有64位版本,让您的应用程序在 WOW64上运行,除非您的应用程序迫切需要以 x64运行。

如果需要 x64,你可以:

  • 将 DLL 更改为具有相同的名称,如 MyDll.dll,并在安装/部署时放置正确的名称。(如果操作系统是 x64,则部署64位版本的 DLL,否则部署 x86版本)。

  • 一共有两个独立的构建,一个用于 x86,一个用于 x64。

你所描述的被称为“并行汇编”(同一汇编的两个版本,一个32位,另一个64位) ... 我想你会发现这些有帮助:

这里 您可以找到与您的场景完全相同的演练(. NET DLL 包装 C + +/CLI DLL 引用本机 DLL)。

建议:

只要构建为 x86并使用它... 或者有2个构建(一个 x86和一个 x64) ... 因为上面的技术是相当复杂的..。

我发现最简单的方法是导入两个具有不同名称的方法,并调用正确的方法。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);
}

当然,如果您有许多导入,那么手动维护这些导入会变得非常麻烦。

我使用了 vcsjones 提到的一种方法:

”将 DLL 更改为相同的名称,如 MyDll.dll,并在安装/部署时放置正确的名称

这种方法需要维护两个构建平台,不过有关更多细节,请参见下面的链接: https://stackoverflow.com/a/6446638/38368

下面是另一种选择,它要求这两个 DLL 具有 同一个名字并放置在不同的文件夹中。例如:

  • win32/MyDll.dll
  • win64/MyDll.dll

诀窍是在 CLR 加载 DLL 之前,用 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,WITHTHE 相同的名称,在两个文件夹) :

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);
}

另一种方法可能是

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);
}

我对 V8.Net使用的技巧是这样的:

  1. 创建一个新的 C # “代理接口”项目,其中包含在不同架构之间切换的所有定义。在我的案例中,这个项目被命名为 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);


这是你要参考的项目。不要参考下面两个:

  1. 再创建两个项目来生成库的 x64和 x86版本。这是非常容易的: 只要复制-n-粘贴复制在同一个文件夹中的 .csproj文件,并重命名它们。在我的案例中,项目文件被重命名为 V8.Net-ProxyInterface-x64V8.Net-ProxyInterface-x86,然后我将这些项目添加到我的解决方案中。在 VisualStudio 中为每个项目打开项目设置,并确保 Assembly Name的名称中包含 x64或 x86。此时,您有3个项目: 第一个“占位符”项目和2个特定于体系结构的项目。对于这两个新项目:

    A)打开 x64接口项目设置,转到 Build选项卡,在顶部为 Platform选择 All Platforms,然后在 Conditional compilation symbols中输入 x64

    B)打开 x86接口项目设置,转到 Build选项卡,在顶部为 Platform选择 All Platforms,然后在 Conditional compilation symbols中输入 x86

  2. 打开 Build->Configuration Manager...并确保选择 x64作为 x64项目的平台,选择 x86作为 x86项目的平台,同时进行 DebugRelease配置。

  3. 确保2个新的接口项目(对于 x64和 x86)输出到主项目的相同位置(参见项目设置 Build->Output path)。

  4. 最后的魔法: 在我的引擎的静态构造函数中,我快速地连接到组装解析器:

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 函数使用 ONE 名称进行开发,同时使用解析器来确定实际加载哪个函数。

注意,程序集加载程序只在需要时运行。它仅在第一次访问引擎类时由 CLR 系统自动触发(在我的例子中)。如何转化为您的需求取决于宿主项目的设计方式。

我认为这有助于动态加载 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);