2010-09-16 5 views
4

J'ai une bibliothèque C++ native/non managée avec un certain nombre de classes que je voudrais utiliser de C#. La plupart des solutions que j'ai lues (comme this one et this one) suggèrent que je devrais créer un wrapper C++/CLI et utiliser le wrapper dans mon projet C#. La plupart de ces suggestions, cependant, ignorent la plate-forme. Pour autant que je sache, si la DLL non-managée est en 32 bits, ma DLL wrapper devra être en 32 bits, ce qui forcera mon projet C# à utiliser la plate-forme x86, même si j'ai 32- et 64- versions binaires de la DLL non managée disponibles. J'ai déjà résolu ce problème avec les API C en utilisant P/Invoke avec LoadLibrary() et Marshal.GetDelegateForFunctionPointer(), mais je pense que l'encapsulation de chaque appel de méthode des objets C++ serait sujette aux erreurs et difficile à maintenir. Je ne pense pas que je devrais tenter de découvrir le nom mutilé des exportations dans la DLL C++ non plus. Incidemment, la bibliothèque C++ que j'essaie d'utiliser est la VM JavaScript de Google V8 (http://code.google.com/p/v8/) qui peut être compilée pour x86 ou x64, donc le portage du code source C++ vers C# droit est hors de question. Et oui, je suis au courant de plusieurs projets existants qui enveloppent V8 pour une utilisation avec du code managé, tels que v8sharp (http://v8sharp.codeplex.com/) et Javascript .NET (http://javascriptdotnet.codeplex.com/). Cependant, à ma connaissance, tous utilisent un wrapper C++/CLI spécifique à la plate-forme. Pour interopérer avec d'autres bibliothèques de code managé, j'ai besoin de mon composant de code managé pour utiliser AnyCPU.Comment utiliser une classe C++ dans une application C# sans tenir compte de la plateforme?

Existe-t-il un bon moyen d'y parvenir?

+2

La seule chose qui me vient à l'esprit est peut-être un wrapper COM ... – CodingGorilla

+0

@Coding Gorilla - Cependant, le COM back-end aura probablement besoin de cibler x86 ou x64 (je le fais, mais ce n'est toujours pas gratuit "n'importe quel CPU"). –

+0

@ user166390 - Oui, je pensais que je n'y pensais pas trop, c'est pourquoi je lui ai laissé un commentaire plutôt qu'une réponse :) – CodingGorilla

Répondre

3

Eh bien, il y a une façon astucieuse de le faire, mais cela ajoute un fardeau de code supplémentaire (bien que vous pourriez le faire au début de votre application).

Il repose sur la création d'un nouveau domaine d'application avec des chemins d'accès aux conteneurs privés spécifiques à la plate-forme à partir desquels charger les assemblages. Vous cachez alors votre code natif dans les répertoires 32 ou 64 bits et chargera ce qui est le plus approprié.

Donc, pour l'amour de la discussion que vous avez un projet CLR C++ avec:

#pragma once 

using namespace System; 

namespace NativeLib { 

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

Construire que les 32 et 64 bits. Référencez votre application C# pour utiliser la bibliothèque. Maintenant vous devez changer votre code pour qu'il crée un nouveau domaine d'application et y exécuter tout votre code (vous pouvez également créer des types par défaut, mais cela le rend un peu plus complexe et potentiellement lent).

donc définir une classe d'amorçage pour démarrer votre application jusqu'à:

using NativeLib; 

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

enfin changer votre fonction principale à quelque chose comme:

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

Maintenant, vous assurer de supprimer NativeLib.dll du répertoire de l'application actuelle, créez un répertoire x86 et un répertoire x64 et placez les versions respectives de la bibliothèque native dans chacune d'elles. Exécutez-le et il devrait maintenant fonctionner sur 32 et 64 bits.

Si vous ne voulez pas une autre appdomain et vous êtes prêt à vivre avec le code désapprouvée (qui peut aller, mais est toujours en .net 4) vous pouvez faire:

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

StartClass.Start(); 

Bien sûr, il y a Attention, il dépend du fait que les assemblages sont généralement liés tardivement, donc si avant de créer votre domaine d'application, vous utilisez les types natifs qu'il va probablement casser. Il y a aussi des façons de rendre ceci plus générique, vous pourriez par exemple écrire un wrapper exe qui amorce un assemblage chargé par retard contenant votre vrai code, ce qui signifie qu'il fonctionnerait de manière plus générique.

Bien sûr que vous voulez que ce soit une bibliothèque que vous pourriez avoir à aller avec une botte assemblage de cerclage un gâchis avec chemin privé du appdomain à dire un constructeur statique, peut-être pas une chose très poli de le faire;)

+0

Bonne réponse. Je pense que je pourrais être en mesure d'accomplir quelque chose de similaire en accroissant l'événement AssemblyResolve de l'AppDomain principal, mais je n'ai pas encore eu l'occasion de l'essayer. – JimEvans

+0

En effet, cela fonctionnera probablement aussi :) – tyranid

0

Compilez les versions x64 et x86, créez les sig PInvoke pour eux séparément, et créez simplement une méthode pour chaque sig que vous voulez utiliser qui vérifie IntPtr.Size et appelle le pinvoke correct pour le bitness actuel. Sauf si je me trompe, mais je crois que c'est ainsi que cela se fait, je ne peux pas me rappeler si vous avez besoin d'une couche supplémentaire d'indirection en ce que vous faites 32 et 64 bits avec les sigs pinvoke respectifs et la réflexion charge la bonne en fonction de IntPtr.Size au lieu de mettre les sigs pinvoke ensemble dans le même binaire.

+0

C'est fonctionnellement identique à LoadLibrary(), Fonctions API GetProcAddress() et Marshal.GetDelegateForFunctionPointer(). S'il vous plaît voir le deuxième paragraphe du message original. – JimEvans

Questions connexes