简体   繁体   English

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

[英]Using a 32bit or 64bit dll in C# DllImport

Here is the situation, I'm using a C based dll in my dot.net application.情况是这样的,我在我的 dot.net 应用程序中使用基于 dll 的 C。 There are 2 dlls, one is 32bit called MyDll32.dll and the other is a 64bit version called MyDll64.dll.有 2 个 dll,一个是 32 位版本,名为 MyDll32.dll,另一个是 64 位版本,名为 MyDll64.dll。

There is a static variable holding the DLL file name: string DLL_FILE_NAME.有一个 static 变量保存 DLL 文件名:string DLL_FILE_NAME。

and it is used in the following way:它以下列方式使用:

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

Simple so far.到目前为止很简单。

As you can imagine, the software is compiled with "Any CPU" turned on.可以想象,该软件是在“任何 CPU”打开的情况下编译的。

I also have the following code to determine if the system should use the 64bit file or the 32bit file.我还有以下代码来确定系统应该使用 64 位文件还是 32 位文件。

#if WIN64
        public const string DLL_FILE_NAME = "MyDll64.dll";
#else
        public const string DLL_FILE_NAME = "MyDll32.dll";        
#endif

By now you should see the problem.. DLL_FILE_NAME is defined in compilation time and not in execution time so the right dll isn't loaded according to the execution context.现在你应该看到问题了.. DLL_FILE_NAME 是在编译时定义的,而不是在执行时定义的,所以正确的 dll 没有根据执行上下文加载。

What would be the correct way to deal with this issue?处理这个问题的正确方法是什么? I do not want two execution files (one for 32bit and the other for 64bit)?我不想要两个执行文件(一个用于 32 位,另一个用于 64 位)? How can I set DLL_FILE_NAME before it is used in the DllImport statement?在 DllImport 语句中使用之前如何设置 DLL_FILE_NAME?

I've found the simplest way to do this is to import the two methods with different names, and calling the right one.我发现最简单的方法是导入具有不同名称的两种方法,并调用正确的方法。 The DLL won't be loaded until the call is made so it's fine:在调用之前不会加载 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);
}

Of course, if you have many imports, this can be become quite cumbersome to maintain manually.当然,如果您有很多导入,手动维护会变得非常麻烦。

Here is another alternative that requires that the two DLLs have the same name and are placed in different folders.这是另一种替代方法,它要求两个 DLL 具有相同的名称并放置在不同的文件夹中。 For instance:例如:

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

The trick is to manually load the DLL with LoadLibrary before the CLR does it.诀窍是在 CLR 执行之前使用LoadLibrary手动加载 DLL。 It will then see that a MyDll.dll is already loaded and use it.然后它会看到一个MyDll.dll已经被加载并使用它。

This can be done easily in the static constructor of the parent class.这可以在父类的静态构造函数中轻松完成。

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 : Use Assembly.CodeBase so that it works even if Shadow Copying is enabled.编辑 2017/02/01 :使用Assembly.CodeBase以便即使启用了阴影复制它也能工作。

In this case, i should do like this (make 2 folders, x64 and x86 + put the corresponding dll, WITH THE SAME NAME, in both folders):在这种情况下,我应该这样做(创建 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);
}

There is a static variable holding the DLL file name有一个静态变量保存 DLL 文件名

It is not a static variable.它不是静态变量。 It's a constant, at compile time.它是一个常量,在编译时。 You can't change a compile time constant at runtime.您不能在运行时更改编译时常量。

What would be the correct way to deal with this issue?处理这个问题的正确方法是什么?

Honestly I would recommend just targeting x86 and forgetting the 64-bit version all together, and letting your application run on WOW64, unless your application has a compelling need to run as x64.老实说,我建议只针对 x86 而忘记 64 位版本,并让您的应用程序在 WOW64 上运行,除非您的应用程序迫切需要以 x64 运行。

If there is a need for x64, you could:如果需要 x64,您可以:

  • Change the DLLs to have the same name, such as MyDll.dll , and at install / deploy time, put the right one in place.将 DLL 更改为具有相同名称,例如MyDll.dll ,并在安装/部署时放置正确的 DLL。 (If the OS is x64, deploy the 64-bit version of the DLL, otherwise the x86 version). (如果操作系统是 x64,则部署 64 位版本的 DLL,否则部署 x86 版本)。

  • Have two separate builds altogether, one for x86 and one for x64.总共有两个单独的版本,一个用于 x86,一个用于 x64。

What you describe is known as "side-by-side assembly" (two versions of the same assembly, one 32 and the other 64 bit)... I think you will find these helpful:您所描述的称为“并行程序集”(同一程序集的两个版本,一个是 32 位,另一个是 64 位)……我想您会发现这些很有帮助:

Here you can find a walkthrough for exactly your scenario (.NET DLL wrapping C++/CLI DLL referencing a native DLL). 在这里,您可以找到针对您的场景的演练(.NET DLL 包装 C++/CLI DLL 引用本机 DLL)。

RECOMMENDATION:推荐:

Just build it as x86 and be done with it... or have 2 builds (one x86 and one x64)... as the above techniques are rather complicated...只需将其构建为 x86 并完成它...或者有 2 个构建(一个 x86 和一个 x64)...因为上述技术相当复杂...

an alternative approach may be另一种方法可能是

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

I have used one of the approaches meantioned by vcsjones:我使用了 vcsjones 所指的方法之一:

"Change the DLLs to have the same name, such as MyDll.dll, and at install / deploy time, put the right one in place." “将 DLL 更改为具有相同名称,例如 MyDll.dll,并在安装/部署时将正确的放在适当的位置。”

This approach requires maintaining two build platforms though see this link for more details: https://stackoverflow.com/a/6446638/38368这种方法需要维护两个构建平台,但请参阅此链接了解更多详细信息: https : //stackoverflow.com/a/6446638/38368

The trick I use for V8.Net is this:我用于V8.Net的技巧是:

  1. Create a new C# "proxy interface" project with all the defines to switch between the different architectures.创建一个新的 C#“代理接口”项目,其中包含在不同架构之间切换的所有定义。 In my case the project was named V8.Net-ProxyInterface ;在我的例子中,该项目被命名为V8.Net-ProxyInterface example:例子:
 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);

THIS is the project you will reference.这是您将参考的项目。 DO NOT reference the next two:不要引用接下来的两个:

  1. Create two more projects to generate x64 and x86 versions of the library.再创建两个项目以生成库的 x64 和 x86 版本。 This is VERY EASY: Just copy-n-paste to duplicate the .csproj file in the same folder and renamed them.这非常简单:只需复制粘贴即可在同一文件夹中复制.csproj文件.csproj命名它们。 In my case the project file was renamed to V8.Net-ProxyInterface-x64 and V8.Net-ProxyInterface-x86 , then I added the projects to my solution.在我的例子中,项目文件被重命名为V8.Net-ProxyInterface-x64V8.Net-ProxyInterface-x86 ,然后我将项目添加到我的解决方案中。 Open the project settings for each of them in Visual Studio and make sure the Assembly Name has either x64 or x86 in the name.在 Visual Studio 中打开每个项目的项目设置,并确保Assembly Name中包含 x64 或 x86。 At this point you have 3 projects: the first "placeholder" project, and the 2 architecture-specific ones.此时您有 3 个项目:第一个“占位符”项目和 2 个特定于体系结构的项目。 For the 2 new projects:对于 2 个新项目:

    a) Open the x64 interface project settings, go to the Build tab, select All Platforms for Platform at the top, then enter x64 in Conditional compilation symbols . a) 打开x64接口项目设置,进入Build选项卡,在最上方选择All Platforms for Platform ,然后在Conditional compilation symbols输入x64

    b) Open the x86 interface project settings, go to the Build tab, select All Platforms for Platform at the top, then enter x86 in Conditional compilation symbols . b) 打开 x86 接口项目设置,进入Build选项卡,选择最上方的All Platforms for Platform ,然后在Conditional compilation symbols输入x86

  2. Open Build->Configuration Manager... and make sure that x64 is selected as the platform for x64 projects, and x86 is selected for the x86 projects, for BOTH Debug AND Release configurations.打开Build->Configuration Manager... ,并确保x64被选中作为平台,x64的项目, x86选择为x86项目,对于这两种DebugRelease配置。

  3. Make sure the 2 new interface projects (for x64 and x86) output to the same location of your host project (see project setting Build->Output path ).确保 2 个新接口项目(用于 x64 和 x86)输出到宿主项目的相同位置(请参阅项目设置Build->Output path )。

  4. The final magic: In a static constructor for my engine I quickly attach to the assembly resolver:最后的魔法:在我引擎的静态构造函数中,我快速附加到程序集解析器:

static V8Engine()
{
    AppDomain.CurrentDomain.AssemblyResolve += Resolver;
}

In the Resolver method, I just load the file based on the current platform indicated by the current process (note: this code is a stripped-down version and not tested):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));

Finally, go to your host project in the solution explorer, expand References , select the first dummy project you created in step 1, right-click it to open the properties, and set Copy Local to false .最后,转到解决方案资源管理器中的宿主项目,展开References ,选择您在步骤 1 中创建的第一个虚拟项目,右键单击它以打开属性,并将Copy Local设置为false This allows you to develop with ONE name for each P/Invoke function, while using the resolver to figure out which one to actually load.这允许您为每个 P/Invoke 函数开发一个名称,同时使用解析器确定实际加载哪个名称。

Note that the assembly loader only runs when needed.请注意,程序集加载器仅在需要时运行。 It is only triggered (in my case) automatically by the CLR system upon the first access to the engine class.它仅在第一次访问引擎类时由 CLR 系统自动触发(在我的情况下)。 How that translates to you depends on how your host project is designed.这如何转化为您取决于您​​的宿主项目的设计方式。

Based on Julien Lebosquain's great answer , this is what I ended up doing in a similar case:基于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);
}

I think this option scales better if you have multiple entry points in the same DLL for the following reasons:我认为如果您在同一个 DLL 中有多个入口点,则此选项的扩展性更好,原因如下:

  • The Api32 and Api64 classes are completely the same except for the single constant defining the path to the DLL file. Api32 和 Api64 类完全相同,除了定义 DLL 文件路径的单个常量。 This means that I can just copy & paste the declarations from one class to the other if anything changes.这意味着如果有任何变化,我可以将声明从一个 class 复制并粘贴到另一个。
  • No need to specify the EntryPoint, reducing the possibility of typos.无需指定入口点,减少错别字的可能性。

I think this could help to load the DLL dynamically:我认为这有助于动态加载 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM