2016-05-06 9 views
1

Ich möchte eine C# -Bibliothek aus C++ aufrufen.Wie Sie eine C++ - delegierte Funktion aus C# aufrufen, wenn C++ - Aufruf C# funktioniert?

Die C# -Bibliothek, fordern Sie eine delegierte Funktion, , um die Ergebnisse zu melden.

Vielleicht ist mein Zweck verwirrend: die Konzeption ist, C++ Call C# -Funktion, und die C# -Funktion würde eine Callback-Funktion aus C++ aufrufen.

Ich wurde in der C# Call C++ - Callback-Funktion blockiert, die COM-Interop ist für mich geheimnisvoll.

Mein Beispiel-Code sein:

C# -Code:

using System; 
using System.Collections.Generic; 
using System.Text; 
using System.Runtime.InteropServices; 

namespace CSharpLibraryNameSpace 
{ 
    // Interface declaration. 
    public delegate int NativeDelegateType(int x); 
    //public delegate int NativeDelegateType([In, MarshalAs(UnmanagedType.LPStr)] string arg1); 

    public interface ManagedInterface 
    { 
     int Add(int Number1, int Number2); 

     int CalltheCallbackFun(NativeDelegateType callbackFun); 
    }; 


    // Interface implementation. 
    public class ManagedCSharpClass : ManagedInterface 
    { 
     public int Add(int Number1, int Number2) 
     { 
      Console.Write("Add\n"); 
      return Number1 + Number2; 
     } 

     public int 
      CalltheCallbackFun(
      /*[MarshalAs(UnmanagedType.FunctionPtr)]*/ NativeDelegateType callbackFun) 
     { 
      Console.Write("BB\n"); 
      string str; 
      str = "AAA"; 
      unsafe 
      { 
       fixed (char* p = str) 
       { 
        Console.Write("before call callbackFun\n"); 

        callbackFun(0x01); 
       } 
      } 
      return 0; 
     } 
    } 
} 

Der C++ Code:

#include <windows.h> 
// Import the type library. 

#import "CSharpLibrary.tlb" raw_interfaces_only 
using namespace CSharpLibrary; 

typedef void (__stdcall * C_Callback)(int); 

__declspec(dllexport) int __stdcall theCallback(void) 
{ 
    return 0; 
}/*theCallback*/ 

class CPPcallback :public _NativeDelegateType 
{ 
public: 
    CPPcallback(){}; 
      virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount( 
      /* [out] */ UINT *pctinfo) 
     { 
      return E_NOTIMPL; 
     } 

     virtual HRESULT STDMETHODCALLTYPE GetTypeInfo( 
      /* [in] */ UINT iTInfo, 
      /* [in] */ LCID lcid, 
      /* [out] */ ITypeInfo **ppTInfo) 
     { 

      if (ppTInfo == NULL) 
       return E_INVALIDARG; 
      *ppTInfo = NULL; 

      if(iTInfo != 0) 
       return DISP_E_BADINDEX; 

      AddRef();  // AddRef and return pointer to cached 
           // typeinfo for this object. 
      *ppTInfo = NULL; 

      return NOERROR; 
     } 

     virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames( 
     /* [in] */ REFIID riid, 
     /* [size_is][in] */ LPOLESTR *rgszNames, 
     /* [in] */ UINT cNames, 
     /* [in] */ LCID lcid, 
     /* [size_is][out] */ DISPID *rgDispId) 
     { 
      return E_NOTIMPL; 
     } 

     virtual /* [local] */ HRESULT STDMETHODCALLTYPE Invoke( 
     /* [in] */ DISPID dispIdMember, 
     /* [in] */ REFIID riid, 
     /* [in] */ LCID lcid, 
     /* [in] */ WORD wFlags, 
     /* [out][in] */ DISPPARAMS *pDispParams, 
     /* [out] */ VARIANT *pVarResult, 
     /* [out] */ EXCEPINFO *pExcepInfo, 
     /* [out] */ UINT *puArgErr) 
     { 
      return 0; 
     } 

     virtual HRESULT STDMETHODCALLTYPE QueryInterface(/* [in] */ REFIID riid, 
       /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject) 
     { 
      if (riid == IID_IUnknown) 
      { 
       *ppvObject = static_cast<IUnknown*>(this); 
       AddRef(); 
       return S_OK; 
      } 

      if (riid == IID_IDispatch) { 
       *ppvObject = static_cast<IDispatch*>(this); 
       AddRef(); 
       return S_OK; 
      } 

      //if (riid == IID_CPPcallback) 
      { 
       *ppvObject = static_cast<CPPcallback*>(this); 
       AddRef(); 
       return S_OK; 
      } 

      *ppvObject = NULL; 
      return E_NOINTERFACE; 
     } 

     virtual ULONG STDMETHODCALLTYPE AddRef(void) 
     { 
      return InterlockedIncrement(&_refCount); 
     } 

     virtual ULONG STDMETHODCALLTYPE Release(void) 
     { 
      return InterlockedDecrement(&_refCount); 
     } 

private: 
     long _refCount; 
}; 

int main(int argc, char *argv[]) 
{ 
    // Initialize COM. 
    HRESULT hr = CoInitialize(NULL); 

    // Create the interface pointer. 
    ManagedInterfacePtr CSharpDLLPtr(__uuidof(ManagedCSharpClass)); 

    long lResult = 0; 

    // Call the Add method. 
    CSharpDLLPtr->Add(5, 10, &lResult); 
    long aa; 
    aa = 3; 
    CPPcallback cppcallback; 


    CSharpDLLPtr->CalltheCallbackFun(&cppcallback, &aa); 
    wprintf(L"The result is %d\n", lResult); 


    // Uninitialize COM. 
    CoUninitialize(); 
    return 0; 
} 

Die C++ ruft C# Funktion funktioniert. Aber die Linie CSharpDLLPtr-> CalltheCallbackFun (& cppcallback, & aa);

würde in die Funktion QueryInterface, GetTypeInfo dann zurück zu C++ Hauptfunktion direkt gehen. Das ist die Zeile in C#

Console.Write("before call callbackFun\n"); 

würde nicht erreicht werden.

Wie soll ich eine C++ - Callback-Funktion in C# registrieren?

Antwort

3

Ich mag nicht COM Ansatz (zu viele Code und Registrierungen) und ich würde Ihnen empfehlen, stattdessen die PInvoke und Managed CLI zu verwenden.

Aber in Ihrem Fall (vorausgesetzt, Sie haben bereits große COM Infrastruktur gebaut) kann ich empfehlen, kleine PInvoke spezifische Abhilfe zu verwenden. Die Idee besteht darin, pointer an Ihre CallBack Funktion als IntPtr (void* C++ gleichwertig) zu übergeben. Und dann auf C# Seite konvertiert es zurück zu Delegate mit

Marshal.GetDelegateForFunctionPointer

COM wird IntPtr nicht verderben. Ich habe überprüft, wie IntPtr in COM Schnittstelle konvertiert. Es stellt sich heraus, dass es auf x64 wird LongLong und für x86 ist es 10. Dieser Typ ist also ideal, um Zeiger zu halten (Übrigens können Sie Zeiger auf Ihre Klasse innerhalb Callback übergeben). Das bedeutet, dass die AnyCPU Konfiguration nutzlos ist.

Hier Codebeispiel C# rewriten Teil:

using System; 
using System.Runtime.InteropServices; 

namespace CSharpLibraryNameSpace 
{  
    // Any delegate decorated as for PInoke 
    public delegate int NativeDelegateType([MarshalAs(UnmanagedType.LPWStr)] string strMsg); 

    // Interface declaration. 
    [ComVisible(true)] 
    public interface ManagedInterface 
    { 
     int Add(int Number1, int Number2); 

     int CalltheCallbackFun(IntPtr callbackFnPtr); 
    }; 


    // Interface implementation. 
    [ComVisible(true)] 
    public class ManagedCSharpClass : ManagedInterface 
    { 
     public int Add(int Number1, int Number2) 
     { 
      Console.Write("Inside MANAGED Add Num1={0} Num2={1}\n", Number1, Number2); 
      return Number1 + Number2; 
     } 

     public int CalltheCallbackFun(IntPtr callbackFnPtr) 
     { 
      Console.Write("Inside MANAGED CalltheCallbackFun Before Call ptr={0}\n", callbackFnPtr); 

      //Convert IntPtr to Delegate 
      NativeDelegateType callback = 
       Marshal.GetDelegateForFunctionPointer(callbackFnPtr, 
        typeof(NativeDelegateType)) as NativeDelegateType; 

      int nRet = callback("Message from C# :)"); 

      Console.Write("Inside MANAGED CalltheCallbackFun After Call Result={0}\n", nRet); 

      return nRet; 
     } 
    } 
} 

Und C++ Client-Teil:

#import "CSharpLibrary.tlb" raw_interfaces_only 
using namespace CSharpLibrary; 

int SimpleCallbackFunction(const wchar_t* pszMsg) 
{ 
    wprintf(L"Inside C++ UNMANAGED Callback Param=\"%s\"\n", pszMsg); 
    return 77; 
} 

int main() 
{ 
    wprintf(L"Inside C++ UNMANAGED Start\n"); 

    // Initialize COM. 
    HRESULT hr = CoInitialize(NULL); 

    // Create the interface pointer. 
    ManagedInterfacePtr CSharpDLLPtr(__uuidof(ManagedCSharpClass)); 

    long lResult = 0; 

    // Call the Add method. 
    CSharpDLLPtr->Add(5, 10, &lResult); //lResult == 15 

    //! For x64 you need to convert to LongLong 
    CSharpDLLPtr->CalltheCallbackFun((long)SimpleCallbackFunction, &lResult); 
    wprintf(L"Inside C++ UNMANAGED Main result is %d\n", lResult); 

    // Uninitialize COM. 
    CoUninitialize(); 
    return 0; 
} 

Die Ausgabe lautet:

Inside C++ UNMANAGED Start 
Inside MANAGED Add Num1=5 Num2=10 
Inside MANAGED CalltheCallbackFun Before Call ptr=2625906 
Inside C++ UNMANAGED Callback Param="Message from C# :)" 
Inside MANAGED CalltheCallbackFun After Call Result=77 
Inside C++ UNMANAGED Main result is 77 
+0

Vielen Dank, aber ich sollte hier Kommentar: es gut ist, die Art zu ersetzen: var als NativeDelegateType für eine bessere Kompatibilität –

+0

@GaigerChen ich über das Ersetzen 'var' Typ nicht verstanden ? Betreffend Thema möchte ich hinzufügen, dass 'PInvoke' hat eine Menge Vorteile: Sie müssen nicht Wrapper schreiben, können Sie' C++ Arrays', 'LPCTSTR' und' Structs' übergeben ohne explizite Konvertierung, es schneller etc .. –

+0

An meiner Umgebung: VS2005, Verwendung von Var konnte nicht vom Compiler übergeben werden, aber NativeDelegateType konnte. –

1

Obwohl ich noch PInvoke zu verwenden empfehlen und Managed CLI oder zumindest von meiner vorherigennähernIch möchte hinzufügen COM Ansatz Antwort.

Verwenden COM wrappers für delegates ist keine gute Idee. Der folgende Code deklariert Callback als Interface von C# und nimmt ein Objekt, das diese interface implementiert.

using System; 
using System.Runtime.InteropServices; 

namespace CSharpLibraryNameSpace 
{ 
    //Callback Interface declaration 
    [ComVisible(true)] 
    public interface CallbackInterface1 
    { 
     int InvokeUnmanaged(string strMsg); 
    } 

    [ComVisible(true)] 
    public interface ManagedInterface 
    { 
     int Add(int Number1, int Number2); 
     int CalltheCallbackFun(CallbackInterface1 callback); 
    }; 


    // Interface implementation. 
    [ComVisible(true)] 
    public class ManagedCSharpClass : ManagedInterface 
    { 
     public int Add(int Number1, int Number2) 
     { 
      Console.Write("Inside MANAGED Add Num1={0} Num2={1}\n", Number1, Number2); 
      return Number1 + Number2; 
     } 

     public int CalltheCallbackFun(CallbackInterface1 callback) 
     { 
      Console.Write("Inside MANAGED CalltheCallbackFun Before Call\n"); 

      int nRet = callback.InvokeUnmanaged("Message from C# :)"); 

      Console.Write("Inside MANAGED CalltheCallbackFun After Call Result={0}\n", nRet); 

      return nRet; 
     } 
    } 
} 

Um diesen callback von C++ zu verwenden, benötigen Sie spezielles Objekt zu machen. Hier ist die gesamte Programmcode:

#import "CSharpLibrary.tlb" raw_interfaces_only 
using namespace CSharpLibrary; 

class CPPcallback :public CallbackInterface1 
{ 
public: 
    CPPcallback(){}; 
    virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount( 
     /* [out] */ UINT *pctinfo) 
    { 
     *pctinfo = 1; 
     return S_OK; 
    } 

    virtual HRESULT STDMETHODCALLTYPE GetTypeInfo( 
     /* [in] */ UINT iTInfo, 
     /* [in] */ LCID lcid, 
     /* [out] */ ITypeInfo **ppTInfo) 
    { 
     return E_NOTIMPL; 
    } 

    virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames( 
     /* [in] */ REFIID riid, 
     /* [size_is][in] */ LPOLESTR *rgszNames, 
     /* [in] */ UINT cNames, 
     /* [in] */ LCID lcid, 
     /* [size_is][out] */ DISPID *rgDispId) 
    { 
     return E_NOTIMPL; 
    } 

    virtual /* [local] */ HRESULT STDMETHODCALLTYPE Invoke( 
     /* [in] */ DISPID dispIdMember, 
     /* [in] */ REFIID riid, 
     /* [in] */ LCID lcid, 
     /* [in] */ WORD wFlags, 
     /* [out][in] */ DISPPARAMS *pDispParams, 
     /* [out] */ VARIANT *pVarResult, 
     /* [out] */ EXCEPINFO *pExcepInfo, 
     /* [out] */ UINT *puArgErr) 
    { 
     return E_NOTIMPL; 
    } 

    virtual HRESULT STDMETHODCALLTYPE QueryInterface(/* [in] */ REFIID riid, 
     /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject) 
    { 
     if (riid == IID_IUnknown) 
     { 
      *ppvObject = static_cast<IUnknown*>(this); 
      AddRef(); 
      return S_OK; 
     } 

     if (riid == IID_IDispatch) { 
      *ppvObject = static_cast<IDispatch*>(this); 
      AddRef(); 
      return S_OK; 
     } 

     if (riid == __uuidof(CallbackInterface1)) 
     { 
      *ppvObject = static_cast<CallbackInterface1*>(this); 
      AddRef(); 
      return S_OK; 
     } 

     *ppvObject = NULL; 
     return E_NOINTERFACE; 
    } 

    virtual ULONG STDMETHODCALLTYPE AddRef(void) 
    { 
     return InterlockedIncrement(&_refCount); 
    } 

    virtual ULONG STDMETHODCALLTYPE Release(void) 
    { 
     return InterlockedDecrement(&_refCount); 
    } 

    virtual HRESULT __stdcall InvokeUnmanaged (
     /*[in]*/ BSTR strMsg, 
     /*[out,retval]*/ long * pRetVal) override 
    { 
     wprintf(L"Inside C++ UNMANAGED Callback Param=\"%s\"\n", strMsg); 
     *pRetVal = 77; 
     return S_OK; 
    } 

private: 
    long _refCount; 
}; 

int main() 
{ 
    wprintf(L"Inside C++ UNMANAGED Start\n"); 

    // Initialize COM. 
    HRESULT hr = CoInitialize(NULL); 

    // Create the interface pointer. 
    ManagedInterfacePtr CSharpDLLPtr(__uuidof(ManagedCSharpClass)); 

    long nRes = 0; 

    // Call the Add method. 
    CSharpDLLPtr->Add(5, 10, &nRes); 

    //Callback holder instance 
    CPPcallback cppcallback; 

    //Call COM Managed method which calls our Callback 
    CSharpDLLPtr->CalltheCallbackFun(&cppcallback, &nRes); 
    wprintf(L"Inside C++ UNMANAGED Main result is %d\n", nRes); 

    // Uninitialize COM. 
    CoUninitialize(); 
    return 0; 
} 

Das Programm Ausgabe lautet:

Inside C++ UNMANAGED Start 
Inside MANAGED Add Num1=5 Num2=10 
Inside MANAGED CalltheCallbackFun Before Call 
Inside C++ UNMANAGED Callback Param="Message from C# :)" 
Inside MANAGED CalltheCallbackFun After Call Result=77 
Inside C++ UNMANAGED Main result is 77 

Wenn Sie IDispatch Implementierung hinter den Kulissen Code verstecken würde aus vorherigen Antwort als Code fast so kurz sein, aber Sie wird das Gesicht All das COM spezifische Objekte (wie BSTR, SAFEARRAY) es funktioniert definitiv langsam als PInvoke.