web-dev-qa-db-fra.com

Comment appeler une bibliothèque C # à partir de Native C ++ (à l'aide de C ++ \ CLI et IJW)

Contexte: Dans le cadre d'une mission plus vaste, je dois rendre une bibliothèque C # accessible au code C++ et C non managé. Pour essayer de répondre à cette question moi-même, j'ai appris le C++/CLI ces derniers jours/semaines.

Il semble y avoir un certain nombre de façons différentes de réaliser l'utilisation d'une DLL C # à partir de C++ et C non gérés. Certaines des réponses en bref semblent être: l'utilisation des services Interlope, l'utilisation de .com. et regasm, utilisant PInvoke (qui semble passer de C # à C++ uniquement), et utilisant IJW dans le C++/CLR (qui semble être des services Interlope). Je pense qu'il serait préférable de mettre en place une bibliothèque qui est peut-être un wrapper CLR qui utilise IJW pour appeler ma DLL C # au nom du code C++ et C natif.

Particularités: J'ai besoin de passer des valeurs de chaîne ainsi que int à une DLL C # à partir du code c ++, et retourner void.

Pertinence: De nombreuses entreprises ont de nombreuses excuses pour mélanger et assortir C++, C et C #. Performances: le code non managé est généralement plus rapide, les interfaces: les interfaces managées sont généralement plus faciles à maintenir, à déployer et sont souvent plus faciles à regarder, nous disent également les gestionnaires. Le code hérité nous oblige aussi. C'était là (comme la montagne que nous avons escaladée). Alors que les exemples sur la façon d'appeler une bibliothèque C++ à partir de C # sont nombreux. Des exemples de la façon d'appeler des bibliothèques C # à partir de code C++ sont difficiles à trouver via Google, surtout si vous voulez voir le code 4.0+ mis à jour.

Logiciel: C #, C++/CLR, C++, C, Visual Studio 2010 et .NET 4.0

Détails de la question: OK question en plusieurs parties:

  1. Y a-t-il un avantage à utiliser des objets com? Ou le PInvoke? Ou une autre méthode? (J'ai l'impression que la courbe d'apprentissage ici sera tout aussi abrupte, même si je trouve plus d'informations sur le sujet dans Google Land. IJW semble promettre ce que je veux qu'il fasse. Dois-je abandonner la recherche d'une solution IJW et se concentrer sur cela à la place?) (Avantage/inconvénient?)

  2. Ai-je raison d'imaginer qu'il existe une solution où j'écris un wrapper qui utilise IJW dans le C++/CLR? Où puis-je trouver plus d'informations sur ce sujet et ne pas dire que je n'ai pas assez utilisé Google ou consulté MSDN sans me dire où vous l'avez vu? (Je pense que je préfère cette option, dans l'effort d'écrire du code clair et simple.)

  3. Un rétrécissement de la portée des questions: je pense que mon véritable problème et besoin est de répondre à la question plus petite qui suit: comment configurer une bibliothèque C++/CLR qu'un fichier C++ non géré peut utiliser dans Visual Studio. Je pense que si je pouvais simplement instancier une classe C++ managée dans du code C++ non managé, alors je pourrais être capable de résoudre le reste (interface et encapsulation, etc.). Je m'attends à ce que ma folie principale soit d'essayer de configurer des références/# inclut etc. dans Visual Studio, pensant clairement que je pourrais avoir d'autres idées fausses. Peut-être que la réponse à tout cela pourrait être juste un lien vers un tutoriel ou des instructions qui m'aideront à ce sujet.

Recherche: J'ai googlé et Binged encore et encore avec un certain succès. J'ai trouvé de nombreux liens qui vous montrent comment utiliser une bibliothèque non gérée à partir du code C #. Et je dois admettre qu'il y a eu des liens qui montrent comment le faire en utilisant des objets com. Peu de résultats étaient visés pour VS 2010.

Références: J'ai lu de nombreux articles. J'ai essayé de travailler sur les plus pertinents. Certains semblent terriblement proches de la réponse, mais je n'arrive pas à les faire travailler. Je soupçonne que la chose qui me manque est incroyablement petite, comme une mauvaise utilisation du mot clé ref, ou une instruction #include ou using, ou une utilisation abusive de l'espace de noms, ou une utilisation incorrecte de la fonction IJW, ou un paramètre manquant VS doit gérer la compilation correctement, etc. Vous vous demandez donc pourquoi ne pas inclure le code? Eh bien, je sens que je ne suis pas dans un endroit où je comprends et j'attends le code que je dois travailler. Je veux être dans un endroit où je le comprends, quand j'y arriverai peut-être alors j'aurai besoin d'aide pour le réparer. Je vais inclure au hasard deux des liens mais je ne suis pas autorisé à les montrer tous à mon niveau actuel de Hitpoint.

http://www.codeproject.com/Articles/35437/Moving-Data-between-Managed-Code-and-Unmanaged-Cod

Cela appelle le code du code managé et non managé dans les deux sens allant de C++ à Visual Basic et retour via C++ CLR, et bien sûr, je suis intéressé par C # .: http://www.codeproject.com/Articles/9903/Appel-Managed-Code-from-Unmanaged-Code-and-vice

43
amalgamate

J'ai trouvé quelque chose qui commence au moins à répondre à ma propre question. Les deux liens suivants contiennent des fichiers wmv de Microsoft qui illustrent l'utilisation d'une classe C # en C++ non managé.

Ce premier utilise un objet COM et un regasm: http://msdn.Microsoft.com/en-us/vstudio/bb892741 .

Cette seconde utilise les fonctionnalités de C++/CLI pour encapsuler la classe C #: http://msdn.Microsoft.com/en-us/vstudio/bb892742 . J'ai pu instancier une classe c # à partir du code managé et récupérer une chaîne comme dans la vidéo. Cela a été très utile, mais cela ne répond qu'aux 2/3 de ma question, car je veux instancier une classe avec un périmètre de chaîne dans une classe c #. Comme preuve de concept, j'ai modifié le code présenté dans l'exemple pour la méthode suivante et j'ai atteint cet objectif. Bien sûr, j'ai également ajouté une méthode modifiée {public string PickDate (string Name)} pour faire quelque chose avec la chaîne de nom pour me prouver que cela fonctionnait.

wchar_t * DatePickerClient::pick(std::wstring nme)
{
    IntPtr temp(ref);// system int pointer from a native int
    String ^date;// tracking handle to a string (managed)
    String ^name;// tracking handle to a string (managed)
    name = gcnew String(nme.c_str());
    wchar_t *ret;// pointer to a c++ string
    GCHandle gch;// garbage collector handle
    DatePicker::DatePicker ^obj;// reference the c# object with tracking handle(^)
    gch = static_cast<GCHandle>(temp);// converted from the int pointer 
    obj = static_cast<DatePicker::DatePicker ^>(gch.Target);
    date = obj->PickDate(name);
    ret = new wchar_t[date->Length +1];
    interior_ptr<const wchar_t> p1 = PtrToStringChars(date);// clr pointer that acts like pointer
    pin_ptr<const wchar_t> p2 = p1;// pin the pointer to a location as clr pointers move around in memory but c++ does not know about that.
    wcscpy_s(ret, date->Length +1, p2);
    return ret;
}

Une partie de ma question était: Quoi de mieux? D'après ce que j'ai lu dans de nombreux efforts pour rechercher la réponse, les objets COM sont considérés comme plus faciles à utiliser et l'utilisation d'un wrapper permet à la place un meilleur contrôle. Dans certains cas, l'utilisation d'un wrapper peut (mais pas toujours) réduire la taille du thunk, car les objets COM ont automatiquement une empreinte de taille standard et les wrappers sont seulement aussi gros qu'ils doivent l'être.

Le thunk (comme je l'ai utilisé ci-dessus) fait référence à l'espace-temps et aux ressources utilisés entre C # et C++ dans le cas de l'objet COM, et entre C++/CLI et C++ natif dans le cas du codage à l'aide d'un C++/CLI Wrapper. Donc, une autre partie de ma réponse devrait inclure un avertissement selon lequel le franchissement de la limite du thunk plus qu'absolument nécessaire est une mauvaise pratique, l'accès à la limite du thunk à l'intérieur d'une boucle n'est pas recommandé, et qu'il est possible de configurer un wrapper de manière incorrecte afin qu'il double les thunks (franchit la frontière deux fois où un seul thunk est requis) sans que le code ne semble incorrect pour un novice comme moi.

Deux notes sur les wmv. Premièrement: certaines images sont réutilisées dans les deux, ne vous y trompez pas. Au début, ils semblent identiques, mais ils couvrent différents sujets. Deuxièmement, il existe des fonctionnalités bonus telles que le marshaling qui font maintenant partie de la CLI qui ne sont pas couvertes par les wmv.

Modifier:

Notez qu'il y a une conséquence pour vos installations, votre wrapper c ++ ne sera pas trouvé par le CLR. Vous devrez soit confirmer que l'application c ++ s'installe dans n'importe quel répertoire/chaque qui l'utilise, soit ajouter la bibliothèque (qui devra alors être fortement nommée) au GAC au moment de l'installation. Cela signifie également que dans les deux cas, dans les environnements de développement, vous devrez probablement copier la bibliothèque dans chaque répertoire où les applications l'appellent.

4
amalgamate

Vous pouvez le faire assez facilement.

  1. Créer un combo .h/.cpp
  2. Activez/clr sur le fichier .cpp nouvellement créé. (CPP -> Clic droit -> Propriétés)
  3. Définissez le chemin de recherche des "répertoires #using supplémentaires" pour pointer vers votre dll C #.

Native.h

void NativeWrapMethod();

Native.cpp

#using <mscorlib.dll>
#using <MyNet.dll>

using namespace MyNetNameSpace;

void NativeWrapMethod()
{
    MyNetNameSpace::MyManagedClass::Method(); // static method
}

Voilà les bases de l'utilisation d'une bibliothèque C # à partir de C++\CLI avec du code natif. (Il suffit de référencer Native.h si nécessaire et d'appeler la fonction.)

L'utilisation du code C # avec du code managé C++\CLI est à peu près la même.

Il y a beaucoup de désinformation à ce sujet, donc, espérons-le, cela évite à quelqu'un beaucoup de tracas. :)


Je l'ai fait dans: VS2010 - VS2012 (Cela fonctionne probablement aussi dans VS2008.)

33
Smoke

MISE À JOUR 2018

Il semble que la solution ne fonctionne pas pour Visual Studio 2017 et versions ultérieures. Malheureusement, je ne travaille pas actuellement avec Visual Studio et ne peux donc pas mettre à jour cette réponse par moi-même. Mais kaylee a publié une version mise à jour de mon réponse , merci!

MISE À JOUR FIN

Si vous souhaitez utiliser COM, voici ma solution à ce problème:

Bibliothèque C #

Tout d'abord, vous avez besoin d'une bibliothèque compatible COM.

  • Vous en avez déjà un? Parfait, vous pouvez ignorer cette partie.

  • Vous avez accès à la bibliothèque? Assurez-vous qu'il est compatible COM en suivant les étapes.

    1. Assurez-vous que vous avez coché l'option "Register for COM interop" dans les propriétés de votre projet. Propriétés -> Build -> Scroll down -> Register for COM interop

Les captures d'écran suivantes montrent où vous trouvez cette option.

Screenshot Project Properties Build

  1. Toutes les interfaces et classes qui devraient être disponibles doivent avoir un GUID

    namespace NamespaceOfYourProject
    {
        [Guid("add a GUID here")]
        public interface IInterface
        {
            void Connect();
    
            void Disconnect();
        }
    }
    
    namespace NamespaceOfYourProject
    {
         [Guid("add a GUID here")]
         public class ClassYouWantToUse: IInterface
         {
             private bool connected;
    
             public void Connect()
             {
                 //add code here
             }
    
             public void Disconnect()
             {
                 //add code here
             }
         }
    }
    

C'est donc à peu près ce que vous avez à faire avec votre code C #. Continuons avec le code C++.

C++

  1. Tout d'abord, nous devons importer la bibliothèque C #.

Après avoir compilé votre bibliothèque C #, il devrait y avoir un fichier .tlb.

#import "path\to\the\file.tlb"

Si vous importez ce nouveau fichier créé dans votre fichier.cpp, vous pouvez utiliser votre objet comme variable locale.

#import "path\to\the\file.tlb"

int _tmain(int argc, _TCHAR* argv[])
{
    CoInitialize(NULL);

    NamespaceOfYourProject::IInterfacePtr yourClass(__uuidof(NamespaceOfYourProject::ClassYouWantToUse));

    yourClass->Connect();

    CoUninitialize();
}
  1. Utilisation de votre classe comme attribut.

Vous remarquerez que la première étape ne fonctionne qu'avec une variable locale. Le code suivant montre comment l'utiliser comme attribut. Relatif à this question.

Vous aurez besoin du CComPtr, qui se trouve dans atlcomcli.h. Incluez ce fichier dans votre fichier d'en-tête.

CPlusPlusClass.h

#include <atlcomcli.h> 
#import "path\to\the\file.tlb"

class CPlusPlusClass
{
public:
    CPlusPlusClass(void);
    ~CPlusPlusClass(void);
    void Connect(void);

private:
    CComPtr<NamespaceOfYourProject::IInterface> yourClass;
}

CPlusPlusClass.cpp

CPlusPlusClass::CPlusPlusClass(void)
{
    CoInitialize(NULL);

    yourClass.CoCreateInstance(__uuidof(NamespaceOfYourProject::ClassYouWantToUse));

}

CPlusPlusClass::~CPlusPlusClass(void)
{
    CoUninitialize();
}

void CPlusPlusClass::Connect(void)
{
    yourClass->Connect();
}

C'est ça! Amusez-vous avec vos classes C # en C++ avec COM.

20
0lli.rocks

La meilleure façon absolue que j'ai trouvée de le faire est de créer un pont c ++/cli qui connecte le code c # à votre C++ natif. Je ne recommanderais pas d'utiliser des objets COM pour résoudre votre problème. Vous pouvez le faire avec 3 projets différents.

  • Premier projet: bibliothèque C #
  • Deuxième projet: pont C++/CLI (cela enveloppe la bibliothèque C #)
  • Troisième projet: application native C++ qui utilise le deuxième projet

Un bon visuel/tutoriel à ce sujet peut être trouvé ici , et la meilleure solution complète que j'ai trouvée pour le faire peut être trouvée ici ! Entre ces deux liens et un peu de grain, vous devriez être en mesure de créer un pont C++/CLI qui vous permet d'utiliser du code C # dans votre C++ natif.

8
jsmith

Le réponse de 0lli.rocks est malheureusement obsolète ou incomplet. Mon collègue m'a aidé à faire fonctionner cela, et pour être franc, un ou deux détails de la mise en œuvre n'étaient pas si évidents. Cette réponse corrige les lacunes et doit être directement copiable dans Visual Studio 2017 pour votre propre usage.

Mises en garde : Je n'ai pas pu faire fonctionner cela pour C++/WinRT, juste un FYI. Toutes sortes d'erreurs de compilation dues à l'ambiguïté de l'interface IUnknown. J'avais également des problèmes pour que cela fonctionne pour une implémentation de bibliothèque au lieu de l'utiliser dans le principal de l'application. J'ai essayé de suivre les instructions de 0lli.rocks spécifiquement, mais je n'ai jamais pu le compiler.

Étape 01: Créez votre bibliothèque C #


Voici celui que nous utiliserons pour la démo:

using System;
using System.Runtime.InteropServices;

namespace MyCSharpClass
{
    [ComVisible(true)] // Don't forget 
    [ClassInterface(ClassInterfaceType.AutoDual)] // these two lines
    [Guid("485B98AF-53D4-4148-B2BD-CC3920BF0ADF")] // or this GUID
    public class TheClass
    {
        public String GetTheThing(String arg) // Make sure this is public
        {
            return arg + "the thing";
        }
    }
}

Étape 02 - Configurez votre bibliothèque C # pour la visibilité COM


Sous-étape A - S'inscrire à l'interopérabilité COM

enter image description here

Sous-étape B - Rendre l'assemblage COM visible

enter image description here

Étape 3 - Créez votre bibliothèque pour le fichier .tlb


Vous voulez probablement le faire comme Release pour AnyCPU à moins que vous ayez vraiment besoin de quelque chose de plus spécifique.

Étape 4 - Copiez le fichier .tlb Dans l'emplacement source de votre projet C++


enter image description here

Étape 5 - Importez le fichier .tlb Dans votre projet C++


#include "pch.h"
#include <iostream>
#include <Windows.h>
#import "MyCSharpClass.tlb" raw_interfaces_only

int wmain() {
    return 0;
}

Étape 6 - Ne paniquez pas lorsque Intellisense échoue


enter image description here

Il va encore se construire. Vous verrez encore plus de code en rouge une fois que nous aurons implémenté la classe réelle dans le projet C++.

Étape 7 - Générez votre projet C++ pour générer le fichier .tlh


Ce fichier ira dans votre répertoire de construction d'objet intermédiaire une fois que vous aurez construit la première fois

enter image description here

Étape 8 - Évaluez le fichier .tlh Pour obtenir des instructions de mise en œuvre


Il s'agit du fichier .tlh Qui est généré dans le dossier d'objet intermédiaire. Ne le modifiez pas.

// Created by Microsoft (R) C/C++ Compiler Version 14.15.26730.0 (333f2c26).
//
// c:\users\user name\source\repos\consoleapplication6\consoleapplication6\debug\mycsharpclass.tlh
//
// C++ source equivalent of Win32 type library MyCSharpClass.tlb
// compiler-generated file created 10/26/18 at 14:04:14 - DO NOT EDIT!

//
// Cross-referenced type libraries:
//
//  #import "C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscorlib.tlb"
//

#pragma once
#pragma pack(Push, 8)

#include <comdef.h>

namespace MyCSharpClass {

//
// Forward references and typedefs
//

struct __declspec(uuid("48b51671-5200-4e47-8914-eb1bd0200267"))
/* LIBID */ __MyCSharpClass;
struct /* coclass */ TheClass;
struct __declspec(uuid("1ed1036e-c4ae-31c1-8846-5ac75029cb93"))
/* dual interface */ _TheClass;

//
// Smart pointer typedef declarations
//

_COM_SMARTPTR_TYPEDEF(_TheClass, __uuidof(_TheClass));

//
// Type library items
//

struct __declspec(uuid("485b98af-53d4-4148-b2bd-cc3920bf0adf"))
TheClass;
    // [ default ] interface _TheClass
    // interface _Object

struct __declspec(uuid("1ed1036e-c4ae-31c1-8846-5ac75029cb93"))
_TheClass : IDispatch
{
    //
    // Raw methods provided by interface
    //

      virtual HRESULT __stdcall get_ToString (
        /*[out,retval]*/ BSTR * pRetVal ) = 0;
      virtual HRESULT __stdcall Equals (
        /*[in]*/ VARIANT obj,
        /*[out,retval]*/ VARIANT_BOOL * pRetVal ) = 0;
      virtual HRESULT __stdcall GetHashCode (
        /*[out,retval]*/ long * pRetVal ) = 0;
      virtual HRESULT __stdcall GetType (
        /*[out,retval]*/ struct _Type * * pRetVal ) = 0;
      virtual HRESULT __stdcall GetTheThing (
        /*[in]*/ BSTR arg,
        /*[out,retval]*/ BSTR * pRetVal ) = 0;
};

} // namespace MyCSharpClass

#pragma pack(pop)

Dans ce fichier, nous voyons ces lignes pour la méthode publique que nous voulons utiliser:

virtual HRESULT __stdcall GetTheThing (
        /*[in]*/ BSTR arg,
        /*[out,retval]*/ BSTR * pRetVal ) = 0;

Cela signifie que la méthode importée attendra une chaîne d'entrée de type BSTR et un pointeur vers BSTR pour la chaîne de sortie que la méthode importée renverra en cas de succès. Vous pouvez les configurer comme ceci, par exemple:

BSTR thing_to_send = ::SysAllocString(L"My thing, or ... ");
BSTR returned_thing;

Avant de pouvoir utiliser la méthode importée, nous devrons la construire. Du fichier .tlh, Nous voyons ces lignes:

namespace MyCSharpClass {

//
// Forward references and typedefs
//

struct __declspec(uuid("48b51671-5200-4e47-8914-eb1bd0200267"))
/* LIBID */ __MyCSharpClass;
struct /* coclass */ TheClass;
struct __declspec(uuid("1ed1036e-c4ae-31c1-8846-5ac75029cb93"))
/* dual interface */ _TheClass;

//
// Smart pointer typedef declarations
//

_COM_SMARTPTR_TYPEDEF(_TheClass, __uuidof(_TheClass));

//
// Type library items
//

struct __declspec(uuid("485b98af-53d4-4148-b2bd-cc3920bf0adf"))
TheClass;
    // [ default ] interface _TheClass
    // interface _Object

Tout d'abord, nous devons utiliser l'espace de noms de la classe, qui est MyCSharpClass

Ensuite, nous devons déterminer le pointeur intelligent à partir de l'espace de noms, qui est _TheClass + Ptr; cette étape n'est pas évidente à distance, car elle n'est nulle part dans le fichier .tlh.

Enfin, nous devons fournir le paramètre de construction correct pour la classe, qui est __uuidof(MyCSharpClass::TheClass)

Se retrouver avec,

MyCSharpClass::_TheClassPtr obj(__uuidof(MyCSharpClass::TheClass));

Étape 9 - Initialiser COM et tester la bibliothèque importée


Vous pouvez le faire avec CoInitialize(0) ou quel que soit votre initialiseur COM spécifique.

#include "pch.h"
#include <iostream>
#include <Windows.h>
#import "MyCSharpClass.tlb" raw_interfaces_only

int wmain() {
    CoInitialize(0); // Init COM
    BSTR thing_to_send = ::SysAllocString(L"My thing, or ... ");
    BSTR returned_thing;
    MyCSharpClass::_TheClassPtr obj(__uuidof(MyCSharpClass::TheClass)); 
    HRESULT hResult = obj->GetTheThing(thing_to_send, &returned_thing);

    if (hResult == S_OK) {
        std::wcout << returned_thing << std::endl;
        return 0;
    }
    return 1;
}

Encore une fois, ne paniquez pas quand Intellisense panique. Vous êtes en territoire Black Magic, Voodoo et Thar Be Dragons, alors continuez!

enter image description here

6
kayleeFrye_onDeck

J'ai fait beaucoup de recherches et j'ai trouvé un article relativement récent de Microsoft détaillant comment cela peut être fait (il y a beaucoup d'anciennes informations flottant). De l'article lui-même:

L'exemple de code utilise les API d'hébergement CLR 4 pour héberger CLR dans un projet C++ natif, charger et appeler des assemblys .NET

https://code.msdn.Microsoft.com/CppHostCLR-e6581ee

Fondamentalement, il le décrit en deux étapes:

  1. Charger le CLR dans un processus
  2. Chargez votre assemblage.
0
gremwell