简体   繁体   English

如何在不考虑平台的情况下在C#应用程序中使用C ++类?

[英]How do I use a C++ class in a C# application without regard to platform?

I have a native/unmanaged C++ library with a number of classes that I would like to use from C#. 我有一个本机/非托管C ++库,其中包含许多我想在C#中使用的类。 Most of the solutions I've read (like this one and this one ) suggest that I should create a C++/CLI wrapper, and use the wrapper in my C# project. 我读过的大多数解决方案(比如这个这个 )建议我应该创建一个C ++ / CLI包装器,并在我的C#项目中使用包装器。 Most of these suggestions, however, ignore platform. 然而,大多数这些建议都忽略了平台。 As far as I am aware, if the unmanaged DLL is 32-bit, my wrapper DLL will have to be 32-bit, and that will force my C# project to use the x86 platform, even if I have both 32- and 64-bit versions of the unmanaged DLL available. 据我所知,如果非托管DLL是32位,我的包装DLL将必须是32位,这将迫使我的C#项目使用x86平台,即使我同时拥有32-和64-可用的非托管DLL的位版本。

I've solved this problem before with C APIs by using P/Invoke with LoadLibrary() and Marshal.GetDelegateForFunctionPointer() , but I think wrapping every method call of the C++ objects would be error-prone and difficult to maintain. 我在使用P / Invoke和LoadLibrary()以及Marshal.GetDelegateForFunctionPointer()之前使用C API解决了这个问题,但我认为包装C ++对象的每个方法调用都容易出错且难以维护。 I also don't think I should attempt to rely on discovering the mangled name of the exports in the C++ DLL either. 我也不认为我应该尝试依赖于在C ++ DLL中发现导出的错误名称。

Incidentally, the C++ library I'm attempting to use is the Google V8 JavaScript VM ( http://code.google.com/p/v8/ ) which can be compiled for x86 or x64, so porting the C++ source code to straight C# is out of the question. 顺便提一下,我试图使用的C ++库是可以为x86或x64编译的Google V8 JavaScript VM( http://code.google.com/p/v8/ ),因此将C ++源代码移植到笔直C#是不可能的。 And yes, I'm aware of several existing projects that wrap V8 for use with managed code, such as v8sharp ( http://v8sharp.codeplex.com/ ) and Javascript .NET ( http://javascriptdotnet.codeplex.com/ ). 是的,我知道有几个现有项目将V8包装成托管代码,例如v8sharp( http://v8sharp.codeplex.com/ )和Javascript .NET( http://javascriptdotnet.codeplex.com/ )。 However, to my knowledge, all of them use a C++/CLI wrapper that is platform-specific. 但是,据我所知,所有这些都使用特定于平台的C ++ / CLI包装器。 For interop with other managed code libraries, I need my managed code component to use AnyCPU. 为了与其他托管代码库互操作,我需要我的托管代码组件来使用AnyCPU。

Is there a good way to accomplish this? 有没有一个很好的方法来实现这一目标?

Well there is a cunningish way to do it, but it does add extra code burden (although you could just do it at the start of your app). 好吧,有一种狡猾的方式来做,但它确实增加了额外的代码负担(虽然你可以在应用程序的开始时这样做)。

It relies on creating a new app domain with platform specific private bin paths from where to load assemblies. 它依赖于创建一个新的应用程序域,其中包含从哪里加载程序集的特定于平台的专用bin路径。 You then hide your native code in either the 32 or 64 bit dirs and it will load which ever is most appropriate. 然后,您可以在32位或64位目录中隐藏本机代码,它将加载最合适的代码。

So for sake of argument you have a C++ CLR project with: 所以为了论证,你有一个C ++ CLR项目:

#pragma once

using namespace System;

namespace NativeLib {

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

Build that as both 32 and 64 bits. 将其构建为32位和64位。 Reference your C# app to use the library. 引用您的C#应用​​程序以使用该库。

Now you need to change you code so that it creates a new app domain, and run all your code in that (you could also create types in your default, but it makes it slightly more complex and potentially slow). 现在,您需要更改代码,以便创建一个新的应用程序域,并在其中运行所有代码(您也可以在默认情况下创建类型,但这会使其稍微复杂一些并且可能很慢)。

So define a bootstrap class to start your application up: 因此,定义一个引导类来启动您的应用程序:

using NativeLib;

namespace BitnessTest
{
    class StartClass
    {
        public static void Start()
        {
            NativeClass.DoSomething();
        }
    }
}

Finally change your Main function to something like: 最后将主要功能更改为:

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

Now ensure you delete NativeLib.dll from the current application directory, create an x86 and an x64 directory and put the respective versions of the native lib in each one. 现在确保从当前应用程序目录中删除NativeLib.dll,创建x86和x64目录,并在每个目录中放置相应版本的本机lib。 Run it and it should now work on 32 and 64 bit. 运行它现在应该在32位和64位上运行。

If you don't want another appdomain and you are willing to live with deprecated code (which may go away, but is still in .net 4) you can do: 如果您不想要其他应用程序域并且您愿意使用已弃用的代码(可能会消失,但仍在.net 4中),您可以执行以下操作:

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

StartClass.Start();

Of course there are caveats, it relies on the fact that assemblies are generally late bound, so if before you create your app domain you use the native types it will probably break. 当然有一些警告,它依赖于程序集通常是后期绑定的事实,因此如果在创建应用程序域之前使用它可能会破坏的本机类型。 There is also ways of making this more generic, you could for example write a wrapper exe which bootstraps a delay loaded assembly containing your real code, which means it would work more generically. 还有一些方法可以使它更通用,你可以编写一个包装器exe,它引导一个包含你真实代码的延迟加载程序集,这意味着它可以更通用地工作。

Of course as you want this to be a library you might have to go with a boot strapping assembly a mess with the appdomain's private path in say a static constructor, might not be a very polite thing to do ;) 当然,因为你希望这是一个库,你可能不得不使用引导捆绑组件与appdomain的私有路径混淆,比如一个静态构造函数,可能不是很有礼貌的事情;)

Compile x64 and x86 versions, create PInvoke sigs for them seperately, and just create a method for each sig you want to use that checks IntPtr.Size and calls the correct pinvoke for the current bitness. 编译x64和x86版本,单独为它们创建PInvoke sigs,然后为要使用的每个sig创建一个方法,检查IntPtr.Size并为当前位数调用正确的pinvoke。

Unless I'm wrong, but I believe that's how it can be done, I can't remember if you need an extra layer of indirection in that you make interop that are 32 and 64 bit with the respective pinvoke sigs and reflection load the correct one depending on IntPtr.Size instead of putting the pinvoke sigs together in the same binary. 除非我错了,但我相信它是如何完成的,我不记得你是否需要额外的间接层,因为你使用相应的pinvoke sig进行32和64位的互操作并且反射加载正确一个取决于IntPtr.Size而不是将pinvoke sigs放在同一个二进制文件中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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