2011-01-19 7 views
11

He creado un proyecto de C++ DLL que contiene una clase "myCppClass" y trataron de DLL exportarlo usando el siguiente código como se describe por: http://msdn.microsoft.com/en-us/library/a90k134d(v=vs.80).aspx¿Cómo dllexport una clase de C++ para su uso en una aplicación de C#

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

He omitido el "público CObject" ya que requiere afx.h e implica que es un MFC Dll. No estoy seguro de si esto es bueno o no, pero difería de la configuración predeterminada del proyecto DLL.

De la documentación vinculada anterior me hacen creer que todas las "funciones públicas y variables de miembros" están disponibles para la importación. ¿Cómo logro esto en C#? ¿Simplemente puede crear una instancia de la clase?

Editar: Me acabo de dar cuenta de que el título de la publicación puede ser engañoso. El énfasis debe estar en DllImport-ing desde C# y asegurándose de haber seguido la documentación correctamente en C++

+1

Tendrá que hacer visible la DLL COM. Publico esto como un comentario porque no tengo tiempo para dar una explicación más larga. –

Respuesta

13

C# no puede importar directamente clases C++ (que son efectivamente interfaces C maltratadas).

Sus opciones son exponer la clase a través de COM, crear un contenedor administrado usando C++/CLI o exponer una interfaz de estilo C. Recomendaría el contenedor administrado, ya que es más fácil y dará la mejor seguridad de tipo.

Una interfaz de estilo C sería algo como esto (advertencia: código no probado):

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

Una envoltura ++/estilo CLI C se vería así (advertencia: código no probado):

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; 
    } 
}; 
+0

No puede 'dynamic_cast' from' void * '. También podría usar el tipo de puntero real, C# usará 'IntPtr' en las firmas p/invoke. –

+0

@Ben Tienes razón. Lo cambié de un molde '(T *)' cuando la corrección ... cambió ahora. También podría usar el tipo de puntero real, pero prefiero enfatizar la falta de seguridad en este enfoque. – Zooba

+0

Si prefiere un molde de estilo de plantilla de C++, entonces 'static_cast' es el que se debe usar. –

4

No puede crear una instancia de clase C++ mediante pinvoke desde C#. Este es un detalle de implementación problemático, solo el compilador C++ sabe cuánta memoria necesita asignarse y cuándo y cómo llamar correctamente al constructor y al destructor. El tamaño del objeto es, con mucho, la tuerca más difícil de romper, no hay manera de hacerlo confiable.

Si no puede aplanar la clase C++ en métodos estáticos, entonces necesita escribir un contenedor gestionado. Eso se hace con el lenguaje C++/CLI, escribiría una "clase de referencia" que tiene el objeto de clase no administrado almacenado como un puntero, creado en el constructor y eliminado en el destructor y el finalizador.

9

Hasta donde yo sé, C# solo puede interoperar con las interfaces COM. Afortunadamente no necesita ser un objeto COM completo con registro, puede ser cualquier clase simple de C++ implementando IUnknown.

Así que hacer algo como esto 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(); 
} 

Y en el lado C# que hacer algo como esto:

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

Usted puede hacer casi cualquier cosa que desee de esta manera, siempre como la comunicación entre C++ y C# pasa por la interfaz virtual.

0

C# y C++ no son ABI compatible como C++ y Delphi, por lo que no se puede exportar miembros de la clase virtuales (métodos), lo cuenten puramente virtual en el lado llamante un invocarlos, debido a que C# no puede manejar de VTBL de objetos C++. Le sugiero que ajuste sus clases de C++ mediante COM, por lo que tiene otro efecto secundario positivo, que otros lenguajes COM compatibles también pueden usar sus clases.

Cuestiones relacionadas