求助:如何封装C++的中的回调函数供C#调用

GoingNaive 2014-07-26 07:02:46
我现在有一个C++写的dll文件,以及和它相关的.h文件,没有.cpp文件。我的问题描述如下:

.h中定义了一个unmanaged class


class CThostFtdcMdSpi
{
virtual void OnRspError(CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast) {};
}


其中CThostFtdcRspInfoField定义为:

struct CThostFtdcRspInfoField
{
int ErrorID;
char ErrorMsg[81];
};


还有个专门的方法注册派生自回调接口类的实例

virtual void RegisterSpi(CThostFtdcMdSpi *pSpi) = 0;


上面那个CThostFtdcMdSpi是所谓回调接口类,OnRspError是回调函数。调用这个回调函数的函数指针在dll内部实现,我没有源代码。

现在我想用C++/CLI把这个dll封装一下,供C#程序调用。但是不知道如何处理这个回调函数。我想最好是能把这个回调函数转换成.net中的事件。还望各位大神指导,谢谢。

我本人不是专门搞程序的,为了方便自己的工作才想这样搞一下,请见谅。


...全文
380 11 打赏 收藏 转发到动态 举报
写回复
用AI写文章
11 条回复
切换为时间正序
请发表友善的回复…
发表回复
GoingNaive 2014-07-29
  • 打赏
  • 举报
回复
引用 10 楼 iyomumx 的回复:
CThostFtdcRspInfoField是结构,不需要声明为句柄形式,删掉^就可以了
感谢大侠耐心而及时的解答!
iyomumx 2014-07-29
  • 打赏
  • 举报
回复
CThostFtdcRspInfoField是结构,不需要声明为句柄形式,删掉^就可以了
GoingNaive 2014-07-28
  • 打赏
  • 举报
回复
引用 8 楼 GoingNaive 的回复:
[quote=引用 7 楼 iyomumx 的回复:] 定义非托管类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);
	}
}
更正一下: 定义非托管类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);
	}
}
GoingNaive 2014-07-28
  • 打赏
  • 举报
回复
引用 7 楼 iyomumx 的回复:
你的静态方法应当接受一个类似 CThostFtdcMdSpi* sender 的参数,而在引发事件时应当通过这个参数找到或者创建对应的托管类包装,并将这个托管包装类作为sender参数传递给事件。
感谢回复,不过我还是没能理解。我现在采取这样的办法,不知道是否可行: 定义非托管类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的位置。大侠能不能看出为什么编译出错?
GoingNaive 2014-07-28
  • 打赏
  • 举报
回复
引用 2 楼 iyomumx 的回复:
感谢你的讲解和贴心的代码demo。我看见你把RspErrorCallback作为了CThostFtdcMdSpiImpl构造函数的参数,但是我有很多个OnXxx回调函数,把函数指针都放进构造函数的参数是不是不太好啊
iyomumx 2014-07-28
  • 打赏
  • 举报
回复
引用 6 楼 GoingNaive 的回复:
[quote=引用 4 楼 iyomumx 的回复:] [quote=引用 3 楼 GoingNaive 的回复:] [quote=引用 2 楼 iyomumx 的回复:]
感谢你的讲解和贴心的代码demo。我看见你把RspErrorCallback作为了CThostFtdcMdSpiImpl构造函数的参数,但是我有很多个OnXxx回调函数,把函数指针都放进构造函数的参数是不是不太好啊[/quote] 那你可以换个思路,就是用静态函数。声明一个托管的静态函数,接受引发回调的非托管类的指针、事件名或者ID(比如用enum)、其他参数(可以使用void*或者...),在函数方法体中引发对应的托管事件,解析并传递对应的参数。[/quote] 是不是在托管类中定义静态方法,静态方法接受来自非托管类的参数,再在静态方法中引发事件?我摸索了一下,发现这样并不行,因为在静态方法中无法使用this指针,就无法使用OnXxx(this, gcnew XxxEventArgs(...))来引发事件。[/quote] 你的静态方法应当接受一个类似 CThostFtdcMdSpi* sender 的参数,而在引发事件时应当通过这个参数找到或者创建对应的托管类包装,并将这个托管包装类作为sender参数传递给事件。
GoingNaive 2014-07-28
  • 打赏
  • 举报
回复
引用 4 楼 iyomumx 的回复:
[quote=引用 3 楼 GoingNaive 的回复:] [quote=引用 2 楼 iyomumx 的回复:]
感谢你的讲解和贴心的代码demo。我看见你把RspErrorCallback作为了CThostFtdcMdSpiImpl构造函数的参数,但是我有很多个OnXxx回调函数,把函数指针都放进构造函数的参数是不是不太好啊[/quote] 那你可以换个思路,就是用静态函数。声明一个托管的静态函数,接受引发回调的非托管类的指针、事件名或者ID(比如用enum)、其他参数(可以使用void*或者...),在函数方法体中引发对应的托管事件,解析并传递对应的参数。[/quote] 是不是在托管类中定义静态方法,静态方法接受来自非托管类的参数,再在静态方法中引发事件?我摸索了一下,发现这样并不行,因为在静态方法中无法使用this指针,就无法使用OnXxx(this, gcnew XxxEventArgs(...))来引发事件。
GoingNaive 2014-07-28
  • 打赏
  • 举报
回复
引用 4 楼 iyomumx 的回复:
[quote=引用 3 楼 GoingNaive 的回复:] [quote=引用 2 楼 iyomumx 的回复:]
感谢你的讲解和贴心的代码demo。我看见你把RspErrorCallback作为了CThostFtdcMdSpiImpl构造函数的参数,但是我有很多个OnXxx回调函数,把函数指针都放进构造函数的参数是不是不太好啊[/quote] 那你可以换个思路,就是用静态函数。声明一个托管的静态函数,接受引发回调的非托管类的指针、事件名或者ID(比如用enum)、其他参数(可以使用void*或者...),在函数方法体中引发对应的托管事件,解析并传递对应的参数。[/quote] 因为我不是程序员,编程知识很有限,只能达到模仿的程度,所以可否麻烦你写出简单的示例代码,十分感谢!
iyomumx 2014-07-28
  • 打赏
  • 举报
回复
引用 3 楼 GoingNaive 的回复:
[quote=引用 2 楼 iyomumx 的回复:]
感谢你的讲解和贴心的代码demo。我看见你把RspErrorCallback作为了CThostFtdcMdSpiImpl构造函数的参数,但是我有很多个OnXxx回调函数,把函数指针都放进构造函数的参数是不是不太好啊[/quote] 那你可以换个思路,就是用静态函数。声明一个托管的静态函数,接受引发回调的非托管类的指针、事件名或者ID(比如用enum)、其他参数(可以使用void*或者...),在函数方法体中引发对应的托管事件,解析并传递对应的参数。
iyomumx 2014-07-27
  • 打赏
  • 举报
回复
有点奇怪,如果单纯的要注册回调的话,用函数指针就可以了,特地用一个接口类来实现回调不像是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);
			};
			//其他操作
		}
	}
}
save4me 2014-07-26
  • 打赏
  • 举报
回复
参考: C#调用C++动态库一些重点问题
引用
C#中上面的回调函数只支持stdcall,C++库默认是cdecl, 一般简单处理办法是将C++库的回调接口修饰为_stdcall
具体内容,包括C#回掉函数的处理,见上面链接的文章

110,533

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧