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

Posted

技术标签:

【中文标题】在 C# DllImport 中使用 32 位或 64 位 dll【英文标题】:Using a 32bit or 64bit dll in C# DllImport 【发布时间】:2012-06-06 20:00:41 【问题描述】:

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

有一个静态变量保存 DLL 文件名:字符串 DLL_FILE_NAME。

它的使用方式如下:

[DllImport(DLL_FILE_NAME, CallingConvention=CallingConvention.Cdecl, EntryPoint=Func1")]
private static extern int is_Func1(int var1, int var2);

到目前为止很简单。

您可以想象,软件是在“Any 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 位)?如何设置 DLL_FILE_NAME 之前在 DllImport 语句中使用它?

【问题讨论】:

64 位和 32 位 dll 的区别是什么?有什么32位不能在64上做的吗?如果是这样,我只会使用 32。 在 64 位操作系统上,在程序执行时决定是在纯 64 位还是 WOW64(模拟 32 位)中执行代码。如果程序在 32 位模式下执行,它应该使用基于 C 的 dll,相应地在 32 位和 64 位中编译。 如果您真的想这样做,您需要完全绕过DllImport 属性并自己手动加载DLL,使用LoadLibraryGetProcAddessFreeLibrary职能。该技术在here 进行了讨论。不过,这是一项相当多的工作,而且很容易出错。让 P/Invoke 机制为您做这件事要容易得多。正如其他人所指出的那样,如果您可以一直回退到 32 位 DLL 作为最低公分母,这可能不值得。 可能重复 CPU Architecture Independent P/Invoke: Can the DllName or path be "dynamic"? 和 Set DllImport attribute dynamically 【参考方案1】:

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

【讨论】:

【参考方案2】:

我对@9​​87654321@ 使用的技巧是这样的:

    使用所有定义创建一个新的 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);

这是您将参考的项目。不要引用接下来的两个:

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

    a) 打开x64接口项目设置,进入Build选项卡,在顶部选择All PlatformsPlatform,然后在Conditional compilation symbols中输入x64

    b) 打开x86接口项目设置,进入Build选项卡,在顶部选择All PlatformsPlatform,然后在Conditional compilation symbols中输入x86

    打开Build->Configuration Manager...并确保x64被选为x64项目的平台,x86被选为x86项目,DebugRelease配置。

    确保 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 系统自动触发(在我的情况下)。这如何转化为您取决于您​​的宿主项目的设计方式。

【讨论】:

【参考方案3】:

这是另一种替代方法,它要求两个 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);

EDIT 2017/02/01:使用Assembly.CodeBase,这样即使Shadow Copying 已启用,它也能正常工作。

【讨论】:

这是迄今为止最优雅的答案,最适合这个问题。我成功地将它用于 FreeImage 项目 .NET Wrapper!真棒的想法。我改用了Environment.Is64BitProcess,并像 Kisdeds answer 建议的那样解决了程序集的路径。非常感谢! 您还可以将 32 位和 64 位 DLL 作为资源嵌入到 .NET DLL 中,并提取正确的/调用 LoadLibrary,如果您想打包所有内容。 如果您的原生 dll 需要其他一些库,您可能会遇到问题。 并准备好让您的用户不断地向您发送有关愚蠢的反恶意软件程序的报告,这些程序仅仅因为使用了 LoadLibrary 功能而将您的应用程序标记为恶意软件。去过那里,做到了。【参考方案4】:

另一种方法可能是

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

【讨论】:

它应该复制 dll 使应用程序花费更多时间。【参考方案5】:

在这种情况下,我应该这样做(创建 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);

【讨论】:

【参考方案6】:

我发现最简单的方法是导入具有不同名称的两个方法,然后调用正确的方法。在调用之前不会加载 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);

当然,如果你有很多导入,手动维护会变得相当麻烦。

【讨论】:

为了获得最大的可读性,我建议使用Environment.Is64BitProcess property 而不是检查IntPtr 类型的大小。当然,这个属性是在 .NET 4.0 中引入的,所以如果你的目标是旧版本,它将不可用。 这正是我想做的。虽然编码有一些开销,但它可以工作并且可以完成工作。 对您的工作印象深刻。我发现通常我需要在决策和调用之间再增加一层间接性。【参考方案7】:

有一个静态变量保存 DLL 文件名

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

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

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

如果需要 x64,您可以:

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

共有两个独立的版本,一个用于 x86,一个用于 x64。

【讨论】:

同意,这绝对是解决问题的最简单方法。绝大多数应用程序,尤其是业务线应用程序,都无法从 64 位原生中获益。即使可能有一些边际性能改进,64 位增加的开销也往往会抵消这些改进。您提出的关于为 DLL 使用相同名称并根据系统架构部署正确版本的另一个建议正是 Microsoft 对系统 DLL 所做的。这似乎也是一个不错的选择。【参考方案8】:

我使用了 vcsjones 所指的一种方法:

“将 DLL 更改为具有相同名称,例如 MyDll.dll,并在安装/部署时,将正确的名称放在适当的位置。”

此方法需要维护两个构建平台,但请参阅此链接了解更多详细信息:https://***.com/a/6446638/38368

【讨论】:

【参考方案9】:

您所描述的称为“并行程序集”(同一程序集的两个版本,一个 32 位,另一个 64 位)...我认为您会发现这些很有帮助:

Using Side-by-Side assemblies to load the x64 or x32 version of a DLL http://blogs.msdn.com/b/gauravseth/archive/2006/03/07/545104.aspx http://www.thescarms.com/dotnet/Assembly.aspx

Here 您可以找到适合您的场景的演练(.NET DLL 包装 C++/CLI DLL 引用本机 DLL)。

建议:

只需将其构建为 x86 即可完成...或进行 2 个构建(一个 x86 和一个 x64)...因为上述技术相当复杂...

【讨论】:

我不想要两个应用程序。那是 90 年代。 @Gilad 然后点击提供的链接,它们会显示一些可能更像您想要的选项(注意:它变得非常复杂)... 90 年代没有 64 位,所以不可能是 90 年代:p @banging 不完全正确...... APLHA 和 MIPS 架构在 90 年代都有 64 位产品......安腾 64 位于 2001 年问世(在 90 年代后半期开发)...... 这与 Visual Studio 无关,也不是“搞砸了”。你只是做错了。

以上是关于在 C# DllImport 中使用 32 位或 64 位 dll的主要内容,如果未能解决你的问题,请参考以下文章

C# 中dllimport 调用不同文件夹终极方法

C# 中dllimport 调用不同文件夹终极方法

将 MediaInfo DLL 与 C# DLLImport 一起使用

C#的Dllimport能不能调用指定路径的dll文件?

使用 DLLImport 函数时程序崩溃

C#的Dllimport能不能调用指定路径的dll文件?