14 votes

Comment exporter une classe C++ pour l'utiliser dans une application C# ?

J'ai créé un projet Dll C++ qui contient une classe "myCppClass" et j'ai essayé de l'exporter par Dll en utilisant le code suivant comme décrit par : http://msdn.microsoft.com/en-us/library/a90k134d(v=vs.80).aspx

class __declspec(dllexport) CExampleExport : //public CObject
{ ... class definition ... };

J'ai omis "public CObject" car cela nécessite afx.h et implique qu'il s'agit d'une DLL MFC. Je ne sais pas si c'est une bonne chose ou non mais cela diffère des paramètres par défaut du projet DLL.

D'après la documentation ci-dessus, je suis amené à croire que toutes les "fonctions publiques et variables membres" sont disponibles à l'importation. Comment puis-je réaliser cela en C# ? Peut-on simplement instancier la classe ?

Edit : Je viens de réaliser que le titre du post peut être trompeur. L'accent devrait être mis sur l'importation de DllImport en C# et sur le fait que j'ai suivi la documentation correctement en C++.

17voto

Zooba Points 6440

C# ne peut pas importer directement les classes C++ (qui sont en fait des interfaces C dont le nom a été modifié).

Vous avez le choix entre exposer la classe via COM, créer un wrapper géré à l'aide de C++/CLI ou exposer une interface de style C. Je recommande le wrapper géré, car c'est le plus simple et il offre la meilleure sécurité de type.

Une interface de style C ressemblerait à ceci (avertissement : code non testé) :

extern "C" __declspec(dllexport)
void* CExampleExport_New(int param1, double param2)
{
    return new CExampleExport(param1, param2);
}

extern "C" __declspec(dllexport)
int CExampleExport_ReadValue(void* this, int param)
{
    return ((CExampleExport*)this)->ReadValue(param)
}

Un wrapper de style C++/CLI ressemblerait à ceci (avertissement : code non testé) :

ref class ExampleExport
{
private:
    CExampleExport* impl;
public:
    ExampleExport(int param1, double param2)
    {
        impl = new CExampleExport(param1, param2);
    }

    int ReadValue(int param)
    {
        return impl->ReadValue(param);
    }

    ~ExampleExport()
    {
        delete impl;
    }
};

12voto

Zarat Points 932

Pour autant que je sache, C# ne peut interagir qu'avec les interfaces COM. Heureusement, il n'est pas nécessaire que ce soit un objet COM complet avec un registre, il peut s'agir de n'importe quelle classe C++ implémentant IUnknown.

Faites donc quelque chose comme ça en C++ :

#include <Windows.h>

// Generate with from VisualStudio Tools/Create Guid menu
static const GUID IID_MyInterface = 
{ 0xefbf7d84, 0x3efe, 0x41e0, { 0x95, 0x2e, 0x68, 0xa4, 0x4a, 0x3e, 0x72, 0xca } };

struct MyInterface: public IUnknown
{
    // add your own functions here
    // they should be virtual and __stdcall
    STDMETHOD_(double, GetValue)() = 0;
    STDMETHOD(ThrowError)() = 0;
};

class MyClass: public MyInterface
{
    volatile long refcount_;

public:
    MyClass(): refcount_(1) { }

    STDMETHODIMP QueryInterface(REFIID guid, void **pObj) {
        if(pObj == NULL) {
            return E_POINTER;
        } else if(guid == IID_IUnknown) {
            *pObj = this;
            AddRef();
            return S_OK;
        } else if(guid == IID_MyInterface) {
            *pObj = this;
            AddRef();
            return S_OK;
        } else {
            // always set [out] parameter
            *pObj = NULL;
            return E_NOINTERFACE;
        }
    }

    STDMETHODIMP_(ULONG) AddRef() {
        return InterlockedIncrement(&refcount_);
    }

    STDMETHODIMP_(ULONG) Release() {
        ULONG result = InterlockedDecrement(&refcount_);
        if(result == 0) delete this;
        return result;
    }

    STDMETHODIMP_(DOUBLE) GetValue() {
        return 42.0;
    }

    STDMETHODIMP ThrowError() {
        return E_FAIL;
    }
};

extern "C" __declspec(dllexport) LPUNKNOWN WINAPI CreateInstance()
{
    return new MyClass();
}

Et du côté du C#, vous faites quelque chose comme ça :

[ComImport]
[Guid("EFBF7D84-3EFE-41E0-952E-68A44A3E72CA")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface MyInterface
{
    [PreserveSig] double GetValue();
    void ThrowError();
}

class Program
{
    [DllImport("mylib.dll")]
    static extern MyInterface CreateInstance();

    static void Main(string[] args)
    {
        MyInterface iface = CreateInstance();
        Console.WriteLine(iface.GetValue());
        try { iface.ThrowError(); }
        catch(Exception ex) { Console.WriteLine(ex); }
        Console.ReadKey(true);
    }
}

Vous pouvez faire à peu près tout ce que vous voulez de cette façon, tant que la communication entre C++ et C# passe par l'interface virtuelle.

5voto

Hans Passant Points 475940

Vous ne pouvez pas créer une instance de classe C++ par le biais de pinvoke à partir de C#. Il s'agit d'un détail d'implémentation gênant, seul le compilateur C++ sait quelle quantité de mémoire doit être allouée et quand et comment appeler correctement le constructeur et le destructeur. La taille de l'objet est de loin le point le plus difficile à résoudre, il n'existe aucun moyen de la rendre fiable.

Si vous ne pouvez pas aplatir la classe C++ en méthodes statiques, vous devez écrire un wrapper géré. Cela se fait avec le langage C++/CLI, vous écrivez une "classe ref" qui stocke l'objet de la classe non gérée sous forme de pointeur, créé dans le constructeur et supprimé dans le destructeur et le finisseur.

0voto

ulatekh Points 190

En fait, vous pouvez vous référer directement aux noms tronqués, en utilisant la fonction Point d'entrée de l DllImport attribut. Voir cette réponse pour plus de détails.

0voto

user434209 Points 21

C# et C++ ne sont PAS compatibles ABI comme C++ et Delphi, donc vous ne pouvez pas exporter des membres de classe virtuels (méthodes) et les déclarer purement virtuels du côté appelant et les invoquer, car C# ne peut pas gérer les vtbl d'objets C++. Je vous suggère d'envelopper vos classes C++ par COM, afin d'obtenir un autre effet secondaire positif, à savoir que d'autres langages compatibles COM peuvent également utiliser vos classes.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X