在VS2010上使用C#调用非托管C++生成的DLL文件(图文讲解)
背景
在项目过程中,有时候你需要调用非C#编写的DLL文件,尤其在使用一些第三方通讯组件的时候,通过C#来开发应用软件时,就需要利用DllImport特性进行方法调用。本篇文章将引导你快速理解这个调用的过程。
步骤
1. 创建一个CSharpInvokeCPP的解决方案:
2. 创建一个C++的动态库项目:
3. 在应用程序设置中,选择“DLL”,其他按照默认选项:
最后点击完成,得到如图所示项目:
我们可以看到这里有一些文件,其中dllmain.cpp作为定义DLL应用程序的入口点,它的作用跟exe文件有个main或者WinMain入口函数是一样的,它就是作为DLL的一个入口函数,实际上它是个可选的文件。它是在静态链接时或动态链接时调用LoadLibrary和FreeLibrary时都会被调用。详细内容可以参考(http://blog.csdn.net/benkaoya/archive/2008/06/02/2504781.aspx)。
4. 现在我们打开CSharpInvokeCPP.CPPDemo.cpp文件:
现在我们加入以下内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
// CSharpInvokeCPP.CPPDemo.cpp : 定义 DLL 应用程序的导出函数。 // #include "stdafx.h" extern "C" __declspec ( dllexport ) int Add( int x, int y) { return x + y; } extern "C" __declspec ( dllexport ) int Sub( int x, int y) { return x - y; } extern "C" __declspec ( dllexport ) int Multiply( int x, int y) { return x * y; } extern "C" __declspec ( dllexport ) int Divide( int x, int y) { return x / y; } |
extern "C" 包含双重含义,从字面上即可得到:首先,被它修饰的目标是“extern”的;其次,被它修饰的目标是“C”的。而被extern "C"修饰的变量和函数是按照C语言方式编译和连接的。
__declspec(dllexport)的目的是为了将对应的函数放入到DLL动态库中。
extern "C" __declspec(dllexport)加起来的目的是为了使用DllImport调用非托管C++的DLL文件。因为使用DllImport只能调用由C语言函数做成的DLL。
5. 编译项目程序,最后在Debug目录生成CSharpInvokeCPP.CPPDemo.dll和CSharpInvokeCPP.CPPDemo.lib
我们用反编译工具PE Explorer查看下该DLL里面的方法:
可以发现对外的公共函数上包含这四种“加减乘除”方法。
6. 现在来演示下如何利用C#项目来调用非托管C++的DLL,首先创建C#控制台应用程序:
7. 在CSharpInvokeCSharp.CSharpDemo项目上新建一个CPPDLL类,编写以下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public class CPPDLL { [DllImport( "CSharpInvokeCPP.CPPDemo.dll" )] public static extern int Add( int x, int y); [DllImport( "CSharpInvokeCPP.CPPDemo.dll" )] public static extern int Sub( int x, int y); [DllImport( "CSharpInvokeCPP.CPPDemo.dll" )] public static extern int Multiply( int x, int y); [DllImport( "CSharpInvokeCPP.CPPDemo.dll" )] public static extern int Divide( int x, int y); } |
DllImport作为C#中对C++的DLL类的导入入口特征,并通过static extern对extern “C”进行对应。
8. 另外,记得把CPPDemo中生成的DLL文件拷贝到CSharpDemo的bin目录下,你也可以通过设置【项目属性】->【配置属性】->【常规】中的输出目录:
这样编译项目后,生成的文件就自动输出到CSharpDemo中了。
9. 然后在Main入口编写测试代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
static void Main( string [] args) { int result = CPPDLL.Add(10, 20); Console.WriteLine( "10 + 20 = {0}" , result); result = CPPDLL.Sub(30, 12); Console.WriteLine( "30 - 12 = {0}" , result); result = CPPDLL.Multiply(5, 4); Console.WriteLine( "5 * 4 = {0}" , result); result = CPPDLL.Divide(30, 5); Console.WriteLine( "30 / 5 = {0}" , result); Console.ReadLine(); } |
运行结果:
方法得到调用。
10. 以上的方法只能通过静态方法对于C++中的函数进行调用。那么怎样通过静态方法去调用C++中一个类对象中的方法呢?现在我在CPPDemo项目中添加一个头文件userinfo.h:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class UserInfo { private : char * m_Name; int m_Age; public : UserInfo( char * name, int age) { m_Name = name; m_Age = age; } virtual ~UserInfo(){ } int GetAge() { return m_Age; } char * GetName() { return m_Name; } }; |
在CSharpInvokeCPP.CPPDemo.cpp中,添加一些代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
#include "malloc.h" #include "userinfo.h" typedef struct { char name[32]; int age; } User; UserInfo* userInfo; extern "C" __declspec ( dllexport ) User* Create( char * name, int age) { User* user = (User*) malloc ( sizeof (User)); userInfo = new UserInfo(name, age); strcpy (user->name, userInfo->GetName()); user->age = userInfo->GetAge(); return user; } |
这里声明一个结构,包括name和age,这个结构是用于和C#方面的结构作个映射。
注意:代码中的User*是个指针,返回也是一个对象指针,这样做为了防止方法作用域结束后的局部变量的释放。
strcpy是个复制char数组的函数。
11. 在CSharpDemo项目中CPPDLL类中补充代码:
1
2
3
4
5
6
7
8
9
10
11
|
[DllImport( "CSharpInvokeCPP.CPPDemo.dll" )] public static extern IntPtr Create( string name, int age); [StructLayout(LayoutKind.Sequential)] public struct User { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string Name; public int Age; } |
其中这里的结构User就和C++中的User对应。
12. 在Program.cs中补充代码:
IntPtr ptr = CPPDLL.Create("李平", 27); CPPDLL.User user = (CPPDLL.User)Marshal.PtrToStructure(ptr, typeof(CPPDLL.User)); Console.WriteLine("Name: {0}, Age: {1}", user.Name, user.Age);
注意:红色字体部分,这里结构指针首先转换成IntPtr句柄,然后通过Marshal.PtrToStructrue转换成你所需要的结构。
运行结果:
最后附上我的源代码:CSharpInvokeCPP.rar,希望对大家有所帮助:)
出处:http://www.cnblogs.com/liping13599168/archive/2011/03/31/2000320.html
=======================================================================================
自己整理一些其他类型的参数的调用:
C++的DLL中传递字符串指针
查询第三方提供的帮助说明文档,传递的函数格式如下
MOSS_EXPORT ULONG_32 STDCALL MOSS_GetPlayedTime ( IN const USER_LOGIN_ID_INFO_S * pstUserLoginIDInfo, IN const CHAR * pcChannelCode, OUT CHAR * pszTime ) 获得当前播放时间(字符串型)。 参数 [IN] pstUserLoginIDInfo 用户登录ID信息标识。 [IN] pcChannelCode 播放通道编码。 [OUT] pszTime 存放当前播放时间值,缓冲区长度应不小于::MOSS_TIME_LEN(32)。 返回错误码
定义接口函数
[DllImport("xp_frame.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] public static extern UInt32 MOSS_GetPlayedTime(ref USER_LOGIN_ID_INFO_S stUserLoginInfo, byte[] szChannelCode, IntPtr ptrTime);
调用方法:
IntPtr ptrCurrTime = Marshal.AllocHGlobal(sizeof(char) * MOSSSDK.MOSS_TIME_LEN); ulRet = MOSSSDK.MOSS_GetPlayedTime(ref stUserLoginIDInfo, bytChannelCode, ptrCurrTime); String strTime = Marshal.PtrToStringAnsi(ptrCurrTime); Marshal.FreeHGlobal(ptrCurrTime); if (!String.IsNullOrEmpty(strTime.TrimEnd('\0'))) { dt = Convert.ToDateTime(strTime, dtFormat); }
C++的DLL中传递结构体和结构体指针
查询第三方提供的帮助说明文档,传递的函数格式如下
MOSS_EXPORT ULONG_32 STDCALL MOSS_SlaveRecordRetrieval ( IN USER_LOGIN_ID_INFO_S * pstUserLogIDInfo, IN BOOL_T bExtDomainDev, IN REC_QUERY_INFO_S * pstSDKRecQueryInfo, IN QUERY_PAGE_INFO_S * pstQueryPageInfo, OUT RSP_PAGE_INFO_S * pstRspPageInfo, OUT RECORD_FILE_INFO_S * pstSDKRecordFileInfo ) 摄像机的备用存储录像检索 参数 [IN] pstUserLogIDInfo 用户登录ID信息标识 [IN] bExtDomainDev 是否外域共享推送的摄像机 [IN] pstSDKRecQueryInfo 回放检索消息数据结构,对于外域摄像机,szCamCode为摄像机共享编码 [IN] pstQueryPageInfo 请求分页信息 [OUT] pstRspPageInfo 返回分页信息 [OUT] pstSDKRecordFileInfo 录像文件信息数据结构体 返回错误码
根据查看上面的参数我们知道,只有bExtDomainDev是一个bool类型的,其他的都是结构体,
我们看看USER_LOGIN_ID_INFO_S结构体的定义
成员变量:
CHAR szUserCode [MOSS_USER_CODE_LEN]
CHAR szUserLoginCode [MOSS_RES_CODE_LEN]
CHAR szUserIpAddress [MOSS_IPADDR_LEN]
详细描述:
用户登录ID信息结构
查看REC_QUERY_INFO_S的结构体定义:
成员变量 CHAR szCamCode [MOSS_RES_CODE_LEN] TIME_SLICE_S stQueryTimeSlice ULONG_32 ulDomainLevel ULONG_32 ulIndistinctQuery ULONG_32 ulType BOOL_T bIsAddToInnerTable CHAR szReserve [16] 详细描述 回放相关检索消息数据结构
我们看到上面的结构体其实还是一个嵌套,包含一个TIME_SLICE_S结构体,如下:
成员变量
CHAR szBeginTime [MOSS_TIME_LEN]
CHAR szEndTime [MOSS_TIME_LEN]
详细描述
基本时间片信息
在查看QUERY_PAGE_INFO_S结构体的定义:
成员变量
ULONG_32 ulPageRowNum
ULONG_32 ulPageFirstRowNumber
BOOL_T bQueryCount
详细描述
分页请求信息,待查询数据的每条数据项对应一个序号。序号从1开始,连续增加。
查看RSP_PAGE_INFO_S的结构体定义如下:
成员变量
ULONG_32 ulRowNum
ULONG_32 ulTotalRowNum
详细描述
分页响应信息
最后还要一个RECORD_FILE_INFO_S结构体的定义
成员变量
CHAR szFileName [MOSS_FILE_NAME_LEN]
CHAR szStartTime [MOSS_TIME_LEN]
CHAR szEndTime [MOSS_TIME_LEN]
ULONG_32 ulSize
CHAR szSpec [MOSS_DESC_LEN]
详细描述
录像文件信息(查询录像文件列表时返回)
我们先定义接口
[DllImport("MOSS_sdk.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] public static extern UInt32 MOSS_SlaveRecordRetrieval(ref USER_LOGIN_ID_INFO_S stUserLogIDInfo, int bExtDomainDev, ref REC_QUERY_INFO_S stSDKRecQueryInfo, ref QUERY_PAGE_INFO_S stQueryPageInfo, ref RSP_PAGE_INFO_S pstRspPageInfo, IntPtr pstSDKRecordFileInfo);
我们在定义需要使用到的结构体
[StructLayout(LayoutKind.Sequential)] public struct USER_LOGIN_ID_INFO_S { /** 用户编码 */ [MarshalAs(UnmanagedType.ByValArray, SizeConst = MOSSSDK.MOSS_USER_CODE_LEN)] public byte[] szUserCode; /** 用户登录ID,是用户登录后服务器分配的,它是标记一次用户登录的唯一标识 */ [MarshalAs(UnmanagedType.ByValArray, SizeConst = MOSSSDK.MOSS_RES_CODE_LEN)] public byte[] szUserLoginCode; /** 用户登录的客户端IP地址 */ [MarshalAs(UnmanagedType.ByValArray, SizeConst = MOSSSDK.MOSS_IPADDR_LEN)] public byte[] szUserIpAddress; } [StructLayout(LayoutKind.Sequential)] public struct REC_QUERY_INFO_S { /** 摄像头编码*/ [MarshalAs(UnmanagedType.ByValArray, SizeConst = MOSSSDK.MOSS_RES_CODE_LEN)] public byte[] szCamCode; /** 检索的起始/结束时间 */ public TIME_SLICE_S stQueryTimeSlice; /* 录像的域级别计数: 用于国标协议跨域回放 */ public UInt32 uiDomainLevel; /* Begin add by zhengyibing(01306), 2014-04-19 for 新国标修订*/ /* 新增模糊查询类型 #INDISTINCT_QUERY_TYPE_E */ public UInt32 uiIndistinctQuery; /* 新增录像检索类型 #RECORD_QUERY_TYPE_E */ public UInt32 uiType; /** 保留字段 */ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] public byte[] szReserve; } [StructLayout(LayoutKind.Sequential)] public struct TIME_SLICE_S { /** 开始时间 格式为"hh:mm:ss"或"YYYY-MM-DD hh:mm:ss", 视使用情况而定 */ [MarshalAs(UnmanagedType.ByValArray, SizeConst = MOSSSDK.MOSS_TIME_LEN)] public byte[] szBeginTime; /** 结束时间 格式为"hh:mm:ss"或"YYYY-MM-DD hh:mm:ss", 视使用情况而定 */ [MarshalAs(UnmanagedType.ByValArray, SizeConst = MOSSSDK.MOSS_TIME_LEN)] public byte[] szEndTime; public static TIME_SLICE_S GetInstance() { TIME_SLICE_S stTimeSlice = new TIME_SLICE_S(); stTimeSlice.szBeginTime = new byte[MOSSSDK.MOSS_TIME_LEN]; stTimeSlice.szEndTime = new byte[MOSSSDK.MOSS_TIME_LEN]; return stTimeSlice; } } [StructLayout(LayoutKind.Sequential)] public struct REC_QUERY_INFO_S { /** 摄像头编码*/ [MarshalAs(UnmanagedType.ByValArray, SizeConst = MOSSSDK.MOSS_RES_CODE_LEN)] public byte[] szCamCode; /** 检索的起始/结束时间 */ public TIME_SLICE_S stQueryTimeSlice; /* 录像的域级别计数: 用于国标协议跨域回放 */ public UInt32 uiDomainLevel; /* Begin add by zhengyibing(01306), 2014-04-19 for 新国标修订*/ /* 新增模糊查询类型 #INDISTINCT_QUERY_TYPE_E */ public UInt32 uiIndistinctQuery; /* 新增录像检索类型 #RECORD_QUERY_TYPE_E */ public UInt32 uiType; /** 保留字段 */ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] public byte[] szReserve; } [StructLayout(LayoutKind.Sequential)] public struct QUERY_PAGE_INFO_S { /** 分页查询中每页的最大条目数, 不能为0, 也不能大于#MOSS_PAGE_QUERY_ROW_MAX_NUM */ public UInt32 ulPageRowNum; /** 分页查询中第一条数据的序号(即查询从第ulPageFirstRowNumber条数据开始的符合条件的数据), 取值符合ULONG类型的范围即可 */ public UInt32 ulPageFirstRowNumber; /** 是否查询条目总数, BOOL_TRUE时查询; BOOL_FALSE时不查询 */ public UInt32 bQueryCount; } [StructLayout(LayoutKind.Sequential)] public struct RECORD_FILE_INFO_S { /** 文件名 */ [MarshalAs(UnmanagedType.ByValArray, SizeConst = MOSSSDK.MOSS_FILE_NAME_LEN)] public byte[] szFileName; /** 文件起始时间, 满足"%Y-%m-%d %H:%M:%S"格式, 长度限定为24字符 */ [MarshalAs(UnmanagedType.ByValArray, SizeConst = MOSSSDK.MOSS_TIME_LEN)] public byte[] szStartTime; /** 文件结束时间, 满足"%Y-%m-%d %H:%M:%S"格式, 长度限定为24字符 */ [MarshalAs(UnmanagedType.ByValArray, SizeConst = MOSSSDK.MOSS_TIME_LEN)] public byte[] szEndTime; /** 文件大小, 目前暂不使用 */ public UInt32 ulSize; /** 描述信息, 可不填 */ [MarshalAs(UnmanagedType.ByValArray, SizeConst = MOSSSDK.MOSS_DESC_LEN)] public byte[] szSpec; }
最后我们看看如何调用的
QUERY_PAGE_INFO_S stPageInfo = new QUERY_PAGE_INFO_S(); RSP_PAGE_INFO_S stRspPageInfo = new RSP_PAGE_INFO_S(); REC_QUERY_INFO_S stRecQueryInfo = new REC_QUERY_INFO_S(); stRecQueryInfo.szReserve = new byte[20]; stRecQueryInfo.szCamCode = new byte[MOSSSDK.MOSS_RES_CODE_LEN]; Encoding.UTF8.GetBytes(m_cameraCode, 0, Encoding.UTF8.GetByteCount(m_cameraCode), stRecQueryInfo.szCamCode, 0); stRecQueryInfo.stQueryTimeSlice.szBeginTime = new byte[MOSSSDK.MOSS_TIME_LEN]; Encoding.Default.GetBytes(strBegin, 0, Encoding.Default.GetByteCount(strBegin), stRecQueryInfo.stQueryTimeSlice.szBeginTime, 0); stRecQueryInfo.stQueryTimeSlice.szEndTime = new byte[MOSSSDK.MOSS_TIME_LEN]; Encoding.Default.GetBytes(strEnd, 0, Encoding.Default.GetByteCount(strEnd), stRecQueryInfo.stQueryTimeSlice.szEndTime, 0); stRecQueryInfo.uiIndistinctQuery = 3; IntPtr ptrRecFileList = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(RECORD_FILE_INFO_S)) * 30); stPageInfo.ulPageFirstRowNumber = ulBeginNum; stPageInfo.ulPageRowNum = 30; ulRet = MOSSSDK.MOSS_SlaveRecordRetrieval(ref stUserLoginIDInfo, 1, ref stRecQueryInfo, ref stPageInfo, ref stRspPageInfo, ptrRecFileList); RECORD_FILE_INFO_S stRecFileItem; for (int i = 0; i < stRspPageInfo.ulRowNum; ++i) { IntPtr ptrTemp = IntPtr.Zero; if (SdkManager.Is64Bit) { ptrTemp = new IntPtr(ptrRecFileList.ToInt64() + Marshal.SizeOf(typeof(RECORD_FILE_INFO_S)) * i); } else { ptrTemp = new IntPtr(ptrRecFileList.ToInt32() + Marshal.SizeOf(typeof(RECORD_FILE_INFO_S)) * i); } stRecFileItem = (RECORD_FILE_INFO_S)Marshal.PtrToStructure(ptrTemp, typeof(RECORD_FILE_INFO_S)); string strFile = Encoding.UTF8.GetString(stRecFileItem.szFileName); RecFileList.Add(stRecFileItem); }
再看一个使用示例
MOSS_EXPORT ULONG_32 STDCALL MOSS_StartPlayer ( IN USER_LOGIN_ID_INFO_S * pstUserLoginIDInfo, IN ULONG_32 ulPlayWndNum, INOUT PLAY_WND_INFO_S * pstPlayWndInfo ) 启动播放器组件。 参数 [IN] pstUserLoginIDInfo 用户登录ID信息标识 [IN] ulPlayWndNum 播放窗格数量,具体取值视实际情况而定 [IN][OUT] pstPlayWndInfo 播放窗格编码结构体指针。 返回错误码
在看看USER_LOGIN_ID_INFO_S结构体的定义
成员变量:
CHAR szUserCode [MOSS_USER_CODE_LEN]
CHAR szUserLoginCode [MOSS_RES_CODE_LEN]
CHAR szUserIpAddress [MOSS_IPADDR_LEN]
详细描述:
用户登录ID信息结构
查看PLAY_WND_INFO_S结构体的定义
结构体成员变量说明:
CHAR szPlayWndCode[MOSS_RES_CODE_LEN]
播放窗格资源编码
那么我们先定义结构体和接口:
[StructLayout(LayoutKind.Sequential)] public struct USER_LOGIN_ID_INFO_S { /** 用户编码 */ [MarshalAs(UnmanagedType.ByValArray, SizeConst = MOSSSDK.MOSS_USER_CODE_LEN)] public byte[] szUserCode; /** 用户登录ID,是用户登录后服务器分配的,它是标记一次用户登录的唯一标识 */ [MarshalAs(UnmanagedType.ByValArray, SizeConst = MOSSSDK.MOSS_RES_CODE_LEN)] public byte[] szUserLoginCode; /** 用户登录的客户端IP地址 */ [MarshalAs(UnmanagedType.ByValArray, SizeConst = MOSSSDK.MOSS_IPADDR_LEN)] public byte[] szUserIpAddress; } [StructLayout(LayoutKind.Sequential)] public struct PLAY_WND_INFO_S { [MarshalAs(UnmanagedType.ByValArray, SizeConst = MOSSSDK.MOSS_RES_CODE_LEN)] public byte[] szPlayWndCode; } //调用接口定义 [DllImport("MOSS_sdk.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] public static extern UInt32 MOSS_StartPlayer(ref USER_LOGIN_ID_INFO_S stUserLoginInfo, UInt32 ulPlayWndNum, IntPtr ptrPlayWndInfo);
调用方式:
IntPtr ptrPlayWndInfo = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(PLAY_WND_INFO_S))); ulRet = CommonFunc.StartPlayer(ref stUserLoginInfo, 1, ptrPlayWndInfo);
自己比较一下上面两种结构体和结构体指针的传递的用法。
C++的DLL中传递回调函数指针
如下,设置回调函数指针,触发某个事件时,会把消息通知到应用程序。查看第三方提供的帮助文档说明:
MOSS_EXPORT ULONG_32 STDCALL MOSS_SetRunMsgCB ( IN RUN_INFO_CALLBACK_EX_PF pfnRunInfoFunc )
设置接收消息或者异常消息的回调函数。
参数
[IN] pfRunInfoFunc 消息或者异常消息回调函数的指针。
返回错误码
再次查看RUN_INFO_CALLBACK_EX_PF参数类型的定义,如下
typedef VOID(STDCALL* RUN_INFO_CALLBACK_EX_PF) (IN const USER_LOGIN_ID_INFO_S *pstUserLoginIDInfo, IN ULONG_32 ulRunInfoType, IN VOID *pParam) 消息或者异常消息回调函数的指针类型 参数 [IN] pstUserLoginIDInfo 用户登录ID信息标识。 [IN] ulRunInfoType 消息或者异常消息类型,对应::RUN_INFO_TYPE_E枚举中的值。 [IN] pParam 存放消息或者异常消息数据的缓冲区指针(所存放的数据与消息或异常消息类型有关)。 返回无。
知道了这些,我们先回调方法的接口调用
[DllImport("xp_frame.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] public static extern UInt32 MOSS_SetRunMsgCB(IntPtr ptrRunInfoFunc);
再定义一个回调函数的delegate,你也可以使用Action或者Func的用法来代替这个delegate
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Ansi)] public delegate void RUN_INFO_CALLBACK_EX_PF(ref USER_LOGIN_ID_INFO_S pstUserLoginIDInfo, UInt32 ulRunInfoType, IntPtr pParam);
定义自己的回调方法
public void callBackMsg(ref USER_LOGIN_ID_INFO_S stUserLoginIDInfo, uint ulRunInfoType, IntPtr ptrParam) { switch ((RUN_INFO_TYPE_E)ulRunInfoType) { case RUN_INFO_TYPE_E.RUN_INFO_Wan: break; case RUN_INFO_TYPE_E.RUN_INFO_Error: { RUN_INFO_EX_S stDownLoadInfo = (RUN_INFO_EX_S)Marshal.PtrToStructure(ptrParam, typeof(RUN_INFO_EX_S)); MessageBox.Show("运行的错误信息"); } break; case RUN_INFO_TYPE_E.RUN_INFO_DOWN_END: { //下载消息 DownLoadComplete(ptrParam); } break; default: break; } }
其中RUN_INFO_TYPE_E枚举对应的我就不一一列举出来,只写了个别的事件处理。
设置回调函数,如下:
IntPtr ptrCallbakc = Marshal.GetFunctionPointerForDelegate(callBackMsg); uint ulResult = MOSSSDK.MOSS_SetRunMsgCB(ptrCallbakc);
这样就可以收到消息了。
关注我】。(●'◡'●)
如果,您希望更容易地发现我的新博客,不妨点击一下绿色通道的【因为,我的写作热情也离不开您的肯定与支持,感谢您的阅读,我是【Jack_孟】!
本文来自博客园,作者:jack_Meng,转载请注明原文链接:https://www.cnblogs.com/mq0036/p/6027819.html
【免责声明】本文来自源于网络,如涉及版权或侵权问题,请及时联系我们,我们将第一时间删除或更改!
posted on 2016-11-03 18:58 jack_Meng 阅读(6784) 评论(0) 编辑 收藏 举报