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

时间:2012-06-01 14:56:22

标签: c# .net pinvoke 32bit-64bit dllimport

以下是我在dot.net应用程序中使用基于C的dll的情况。有2个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);

到目前为止很简单。

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

9 个答案:

答案 0 :(得分:54)

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

当然,如果您有很多导入,手动维护可能会非常麻烦。

答案 1 :(得分:51)

这是另一种替代方案,要求两个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即使Shadow Copying已启用也能正常运行。

答案 2 :(得分:14)

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

答案 3 :(得分:7)

  

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

它不是静态变量。在编译时它是一个常量。您无法在运行时更改编译时常量。

  

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

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

如果需要x64,您可以:

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

  • 共有两个单独的版本,一个用于x86,另一个用于x64。

答案 4 :(得分:2)

你所描述的被称为“并排组装”(同一组件的两个版本,一个32和另一个64位)......我认为你会发现这些有用:

Here您可以找到完全符合您的方案的演练(.NET DLL包装引用本机DLL的C ++ / CLI DLL)。

建议:

只需将其构建为x86并完成它...或者有2个版本(一个x86和一个x64)......因为上述技术相当复杂......

答案 5 :(得分:1)

另一种方法可能是

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

答案 6 :(得分:0)

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

  

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

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

答案 7 :(得分:0)

我为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,然后将项目添加到解决方案中。在Visual Studio中打开每个项目的项目设置,并确保Assembly Name的名称中包含x64或x86。此时,您有3个项目:第一个“占位符”项目和2个特定于体系结构的项目。对于2个新项目:

    a)打开x64界面项目设置,转到Build标签,在顶部的All Platforms中选择Platform,然后在{{1}中输入x64 }。

    b)打开x86界面项目设置,转到Conditional compilation symbols标签,在顶部的Build中选择All Platforms,然后在{{1}中输入Platform }。

  2. 打开x86并确保Conditional compilation symbols和x86项目均选择Build->Configuration Manager...作为x64项目的平台,而x64则同时选择x86作为x86项目的平台。 AND Debug配置。

  3. 确保将2个新的接口项目(对于x64和x86)输出到宿主项目的相同位置(请参阅项目设置Release)。

  4. 最后的魔力:在我的引擎的静态构造函数中,我迅速附加到程序集解析器:

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)); ,选择在步骤1中创建的第一个虚拟项目,右键单击它以打开属性,然后将References设置为Copy Local。这样,您就可以为每个P / Invoke函数使用一个名称进行开发,同时使用解析器确定要实际加载的功能。

请注意,程序集加载器仅在需要时运行。它仅在首次访问引擎类时由CLR系统自动触发(就我而言)。转化为您的方式取决于您的宿主项目的设计方式。

答案 8 :(得分:0)

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