如何在 C# 应用程序中使用 C++ 类而不考虑平台?

Jim*_*ans 5 c# c++ interop 32bit-64bit

我有一个本机/非托管 C++ 库,其中包含许多我想从 C# 使用的类。我读过的大多数解决方案(比如这个这个)都建议我应该创建一个 C++/CLI 包装器,并在我的 C# 项目中使用这个包装器。然而,这些建议中的大多数都忽略了平台。据我所知,如果非托管 DLL 是 32 位,我的包装 DLL 必须是 32 位,这将强制我的 C# 项目使用 x86 平台,即使我同时拥有 32 位和 64 位可用的非托管 DLL 的位版本。

我之前通过使用带有LoadLibrary()和的P/Invoke 使用 C API 解决了这个问题Marshal.GetDelegateForFunctionPointer(),但我认为包装 C++ 对象的每个方法调用将容易出错且难以维护。我也不认为我应该尝试依赖于发现 C++ DLL 中导出的损坏名称。

顺便说一句,我尝试使用的 C++ 库是 Google V8 JavaScript VM ( http://code.google.com/p/v8/ ),它可以为 x86 或 x64 编译,因此将 C++ 源代码直接移植到C# 是不可能的。是的,我知道有几个现有项目包装了 V8 以与托管代码一起使用,例如 v8sharp ( http://v8sharp.codeplex.com/ ) 和 Javascript .NET ( http://javascriptdotnet.codeplex.com/)。但是,据我所知,它们都使用特定于平台的 C++/CLI 包装器。为了与其他托管代码库互操作,我需要我的托管代码组件才能使用 AnyCPU。

有没有好的方法来实现这一点?

tyr*_*nid 4

好吧,有一种狡猾的方法可以做到这一点,但它确实增加了额外的代码负担(尽管您可以在应用程序启动时执行此操作)。

它依赖于创建一个新的应用程序域,其中包含加载程序集的平台特定的私有 bin 路径。然后,您将本机代码隐藏在 32 位或 64 位目录中,它将加载最合适的一个。

因此,为了便于论证,您有一个 C++ CLR 项目:

#pragma once

using namespace System;

namespace NativeLib {

    public ref class NativeClass
    {
    public:
        static void DoSomething()
        {
            Console::WriteLine("IntPtr.Size = {0}", IntPtr::Size);
        }
    };
}
Run Code Online (Sandbox Code Playgroud)

将其构建为 32 位和 64 位。引用您的 C# 应用程序来使用该库。

现在您需要更改代码,以便创建一个新的应用程序域,并在其中运行所有代码(您也可以在默认值中创建类型,但这会使它稍微复杂一点并且可能会很慢)。

因此,定义一个引导类来启动您的应用程序:

using NativeLib;

namespace BitnessTest
{
    class StartClass
    {
        public static void Start()
        {
            NativeClass.DoSomething();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

最后将您的 Main 函数更改为:

using System;
using System.Reflection;

namespace BitnessTest
{
    class Program
    {
        static void Main(string[] args)
        {
            AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;

            if (IntPtr.Size > 4)
            {
                setup.PrivateBinPath = "x64";
            }
            else
            {
                setup.PrivateBinPath = "x86";
            }            

            AppDomain appDomain = AppDomain.CreateDomain("Real Domain", null, setup);
            appDomain.DoCallBack(StartClass.Start);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在确保从当前应用程序目录中删除 NativeLib.dll,创建一个 x86 和一个 x64 目录,并将各自版本的本机 lib 放入每个目录中。运行它,它现在应该可以在 32 位和 64 位上运行。

如果您不想要另一个应用程序域并且愿意接受已弃用的代码(这些代码可能会消失,但仍在 .net 4 中),您可以执行以下操作:

if (IntPtr.Size > 4)
{
    AppDomain.CurrentDomain.AppendPrivatePath("x64");
}
else
{
    AppDomain.CurrentDomain.AppendPrivatePath("x86");                
}

StartClass.Start();
Run Code Online (Sandbox Code Playgroud)

当然有一些警告,它依赖于程序集通常是后期绑定的事实,因此如果在创建应用程序域之前使用本机类型,它可能会崩溃。还有一些方法可以使其更通用,例如,您可以编写一个包装器 exe,它引导包含真实代码的延迟加载程序集,这意味着它可以更通用地工作。

当然,当您希望这是一个库时,您可能必须使用启动捆绑程序集,例如静态构造函数中的应用程序域的私有路径,这可能不是一个非常有礼貌的事情;)