参考: C#调用C++动态库一些重点问题
具体内容,包括C#回掉函数的处理,见上面链接的文章

解决方案 »

  1.   

    有点奇怪,如果单纯的要注册回调的话,用函数指针就可以了,特地用一个接口类来实现回调不像是C/C++的实现方式,反而更接近Java。抛开疑问,要使用这样的回调需要:
    1.继承这个接口类,重写OnRspError方法。
        由于在C++/CLI中托管类不能继承非托管类,必须由一个非托管类(假设为CThostFtdcMdSpiImpl)继承CThostFtdcMdSpi。
    2.注册回调时,传递一个CThostFtdcMdSpiImpl实例。
        注意对象的生存期管理。非托管类中不能储存托管句柄,但可以调用静态托管函数,或者使用Marshal类提供的GetFunctionPointerForDelegate获取并储存一个委托对应的函数指针。在OnRspError方法的实现中,你可以通过调用静态托管函数来间接引发托管事件,或者使用函数指针。参考实现(需要dll对应的lib文件方能通过链接,假定RegisterSpi是一个全局函数):
    #include "dll.h"using namespace System;
    using namespace System::Runtime::InteropServices;typedef void (*RspErrorCallback)(CThostFtdcRspInfoField*, int, bool);namespace CppInvoke
    {
    public ref class RspErrorEventArgs : public EventArgs
    {
    public:
    int ErrorID;
    String^ ErrorMsg;
    int RequestID;
    bool IsLast;
    RspErrorEventArgs(CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast)
    {
    this->ErrorID = pRspInfo->ErrorID;
    this->ErrorMsg = gcnew String(pRspInfo->ErrorMsg);
    this->RequestID = nRequestID;
    this->IsLast = bIsLast;
    }
    };

    class CThostFtdcMdSpiImpl : public CThostFtdcMdSpi
    {
    public:
    CThostFtdcMdSpiImpl(RspErrorCallback ecb)
    {
    this->RaiseRspError = ecb;
    }
    virtual void OnRspError(CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast)
    {
    this->RaiseRspError(pRspInfo, nRequestID, bIsLast);
    }
    void Test(void)
    {
    Console::WriteLine("Test");
    }
    private:
    RspErrorCallback RaiseRspError;
    };

    delegate void RspErrorDel(CThostFtdcRspInfoField*, int, bool);

    public ref class CThostFtdcMdSpiWrapper
    {
    private:
    CThostFtdcMdSpiImpl* nestImplClass;
    public:
    CThostFtdcMdSpiWrapper()
    {
    this->nestImplClass = new CThostFtdcMdSpiImpl(
    (RspErrorCallback)Marshal::GetFunctionPointerForDelegate(
    gcnew RspErrorDel
    (
    this,
    &CppInvoke::CThostFtdcMdSpiWrapper::RaiseRspError
    )
    ).ToPointer()
    );
    }
    ~CThostFtdcMdSpiWrapper()
    {
    delete nestImplClass;
    }
    event EventHandler<RspErrorEventArgs^>^ RspError;
    void RaiseRspError(CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast)
    {
    RspError(this, gcnew RspErrorEventArgs(pRspInfo, nRequestID, bIsLast));
    }
    static void RegisterSpi(CThostFtdcMdSpiWrapper^ spi)
    {
    ::RegisterSpi(spi->nestImplClass);
    }
    };
    }C#调用:
    using System;
    using CppInvoke;class Program
    {
    static void Main(string[] args)
    {
    using (var c = new CThostFtdcMdSpiWrapper())
    {
    CThostFtdcMdSpiWrapper.RegisterSpi(c);
    c.RspError += (_, e) =>
    {
    Console.WriteLine("ErrorId = {0}, ErrorMsg = \"{1}\", RequestId = {2}, IsLast = {3}", e.ErrorID, e.ErrorMsg, e.RequestID, e.IsLast);
    };
    //其他操作
    }
    }
    }
      

  2.   


    感谢你的讲解和贴心的代码demo。我看见你把RspErrorCallback作为了CThostFtdcMdSpiImpl构造函数的参数,但是我有很多个OnXxx回调函数,把函数指针都放进构造函数的参数是不是不太好啊
      

  3.   


    感谢你的讲解和贴心的代码demo。我看见你把RspErrorCallback作为了CThostFtdcMdSpiImpl构造函数的参数,但是我有很多个OnXxx回调函数,把函数指针都放进构造函数的参数是不是不太好啊那你可以换个思路,就是用静态函数。声明一个托管的静态函数,接受引发回调的非托管类的指针、事件名或者ID(比如用enum)、其他参数(可以使用void*或者...),在函数方法体中引发对应的托管事件,解析并传递对应的参数。
      

  4.   


    感谢你的讲解和贴心的代码demo。我看见你把RspErrorCallback作为了CThostFtdcMdSpiImpl构造函数的参数,但是我有很多个OnXxx回调函数,把函数指针都放进构造函数的参数是不是不太好啊那你可以换个思路,就是用静态函数。声明一个托管的静态函数,接受引发回调的非托管类的指针、事件名或者ID(比如用enum)、其他参数(可以使用void*或者...),在函数方法体中引发对应的托管事件,解析并传递对应的参数。因为我不是程序员,编程知识很有限,只能达到模仿的程度,所以可否麻烦你写出简单的示例代码,十分感谢!
      

  5.   


    感谢你的讲解和贴心的代码demo。我看见你把RspErrorCallback作为了CThostFtdcMdSpiImpl构造函数的参数,但是我有很多个OnXxx回调函数,把函数指针都放进构造函数的参数是不是不太好啊那你可以换个思路,就是用静态函数。声明一个托管的静态函数,接受引发回调的非托管类的指针、事件名或者ID(比如用enum)、其他参数(可以使用void*或者...),在函数方法体中引发对应的托管事件,解析并传递对应的参数。是不是在托管类中定义静态方法,静态方法接受来自非托管类的参数,再在静态方法中引发事件?我摸索了一下,发现这样并不行,因为在静态方法中无法使用this指针,就无法使用OnXxx(this, gcnew XxxEventArgs(...))来引发事件。
      

  6.   


    感谢你的讲解和贴心的代码demo。我看见你把RspErrorCallback作为了CThostFtdcMdSpiImpl构造函数的参数,但是我有很多个OnXxx回调函数,把函数指针都放进构造函数的参数是不是不太好啊那你可以换个思路,就是用静态函数。声明一个托管的静态函数,接受引发回调的非托管类的指针、事件名或者ID(比如用enum)、其他参数(可以使用void*或者...),在函数方法体中引发对应的托管事件,解析并传递对应的参数。是不是在托管类中定义静态方法,静态方法接受来自非托管类的参数,再在静态方法中引发事件?我摸索了一下,发现这样并不行,因为在静态方法中无法使用this指针,就无法使用OnXxx(this, gcnew XxxEventArgs(...))来引发事件。
    你的静态方法应当接受一个类似 CThostFtdcMdSpi* sender 的参数,而在引发事件时应当通过这个参数找到或者创建对应的托管类包装,并将这个托管包装类作为sender参数传递给事件。
      

  7.   


    感谢回复,不过我还是没能理解。我现在采取这样的办法,不知道是否可行:定义非托管类CMdSpi继承CThostFtdcMdSpiImpl,在其中存放一个gcroot<MdSpiWrapper^>,然后在构造函数中传入MdSpiWrapper的实例。在非托管函数OnRspError中调用托管函数RaiseRspErrorEvent。class CMdSpi : public CThostFtdcMdSpi
    {
    private:
    gcroot<MdSpiWrapper^> m_hMdSpi;public:
    CMdSpi(MdSpi ^mdSpi)
    {
    m_hMdSpi = mdSpi;
    } virtual void OnRspError(CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast)
    {
    m_hMdSpi->RaiseRspErrorEvent(pRspInfo, nRequestID, bIsLast);
    }
    }
    定义托管类MdSpiWrapper,在其中存放CMdSpi的指针。public ref class MdSpiWrapper
    {
    private:
    CMdSpi *m_pCMdSpi;public:
    event EventHandler<RspErrorEventArgs^> ^OnRspError;
    void RaiseRspErrorEvent(CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast)
    {
    OnRspError(this, gcnew RspErrorEventArgs(pRspInfo, nRequestID, bIsLast));
    }
    }
    其中,RspErrorEventArgs定义如下:public ref class RspErrorEventArgs : public EventArgs
    {
    public:
    ThostFtdcRspInfoField ^RspInfo;
    int RequestID;
    bool IsLast;
    RspErrorEventArgs(CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast)
    {
    RspInfo->ErrorID = pRspInfo->ErrorID;
    RspInfo->ErrorMsg = gcnew String(pRspInfo->ErrorMsg);
    RequestID = nRequestID;
    IsLast = bIsLast;
    }
    };
    其中,ThostFtdcRspInfoField定义如下:public value struct ThostFtdcRspInfoField
    {
    int ErrorID;
    [MarshalAs(UnmanagedType::ByValTStr, SizeConst = 81)]
    String ^ErrorMsg;
    };我现在不知道这样做到底行不行,因为我无法编译通过,而编译错误让我无法理解。编译器提示的错误是:
    error C2143: syntax error : missing ';' before '^'
    出错位置在RspErrorEventArgs中定义ThostFtdcRspInfoField ^RspInfo的位置。大侠能不能看出为什么编译出错?
      

  8.   


    更正一下:
    定义非托管类CMdSpi继承CThostFtdcMdSpi,在其中存放一个gcroot<MdSpiWrapper^>,然后在构造函数中传入MdSpiWrapper的实例。在非托管函数OnRspError中调用托管函数RaiseRspErrorEvent。class CMdSpi : public CThostFtdcMdSpi
    {
    private:
    gcroot<MdSpiWrapper^> m_hMdSpi;public:
    CMdSpi(MdSpiWrapper ^mdSpi)
    {
    m_hMdSpi = mdSpi;
    } virtual void OnRspError(CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast)
    {
    m_hMdSpi->RaiseRspErrorEvent(pRspInfo, nRequestID, bIsLast);
    }
    }
      

  9.   

    CThostFtdcRspInfoField是结构,不需要声明为句柄形式,删掉^就可以了