views:

116

answers:

2

I have a native/unmanaged C++ library with a number of classes that I would like to use from 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. 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.

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. I also don't think I should attempt to rely on discovering the mangled name of the exports in the C++ DLL either.

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. 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/). However, to my knowledge, all of them use a C++/CLI wrapper that is platform-specific. For interop with other managed code libraries, I need my managed code component to use AnyCPU.

Is there a good way to accomplish this?

A: 

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.

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.

Jimmy Hoffa
This is functionally identical to using the LoadLibrary(), GetProcAddress() API functions and Marshal.GetDelegateForFunctionPointer(). Please see the second paragraph of the original post.
JimEvans
+2  A: 

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. You then hide your native code in either the 32 or 64 bit dirs and it will load which ever is most appropriate.

So for sake of argument you have a C++ CLR project with:

#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. Reference your C# app to use the library.

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. Run it and it should now work on 32 and 64 bit.

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:

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.

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

tyranid
Great answer. I think I might be able to accomplish something similar by hooking the main AppDomain's AssemblyResolve event, but I haven't had a chance to try it yet.
JimEvans
Indeed that will probably also work :)
tyranid