要求:托管处调用非托管的log,并显示再TextBox上。
具体做法如下:
【c#部分】
1. 托管端放一个TextBox文本框textBox1,定义一个方法解释传递过来的信息:
public void ShowMessageToTextBox(object msg)
{
this.textBox1.BeginInvoke(new Action(() =>
{
this.textBox1.AppendText(msg.ToString() + "\r\n");
}));
}
2. 定义一个委托方法:因为只打印log,所以可以使用C#提供的公共委托 public delegate void Action<in T>(T obj);【如果需要带参数可以使用 public delegate TResult Func<out TResult>();】
private Action<object> m_logAction; // Log打印
3. 让委托和TextBox打印函数相关联:
m_logAction = ShowMessageToTextBox;
4. 定义委托的执行的方法:后面就可以直接调用ShowMessage这个方法进行信息的打印
/// <summary>
/// 打印log
/// </summary>
/// <param name="Msg">显示的信息</param>
private void ShowMessage(string Msg)
{
if (null == Msg || Msg.Length <= 0)
{
return;
}
m_logAction?.Invoke(Msg);
}
【c++部分】
1. 先定义导出函数部分:
/// 定义C++的导出规则:C规则编译指定的代码
#ifdef __cplusplus
extern "C" {
#endif
// 中间写入导出类型、回调函数和其它内容等
/// 结束C++的导出规则定义:C规则编译指定的代码
#ifdef __cplusplus
} // extern "C"
#endif
2. 宏定义:导出/导入方法:
#ifdef MYDLL_EXPORTS
#define AFX_GENIFC_API __declspec(dllexport)
#else
#define AFX_GENIFC_API __declspec(dllimport)
#endif
3. 定义函数的入栈出栈字节序: __stdcall 或者 __cdecl ,注意,这里的字节序一定要和C#中的一致,且后面C++的具体实现也需要一致,否则调用很可能崩溃、乱码或者失败
#define MCALL __cdecl
4. 定义回调函数:
typedef void(__cdecl* LogCallBack)(const wchar_t* data);
5. /// <summary>
/// 注册回调函数:并在CPP中实现
/// </summary>
/// <param name="hInstance"> 操作句柄,非必要参数,和C#要对齐 </param>
/// <param name="LogCallBack">回调参数,必要参数</param>
AFX_GENIFC_API void MCALL RegistLog_Display_CallBack(void* hInstance, LogCallBack logcb);
AFX_GENIFC_API void MCALL RegistLog_Display_CallBack(void* hInstance, LogCallBack logcb)
{
CPhone* handle = (CPhone*)hInstance;
handle->SetLogCallBack(logcb); // 这里才是真正的传log信息回调函数
}
6. 如果是类,需要定义回调的成员变量:
LogCallBack m_log_cb;
7. 设置log回调函数:
void CPhone::SetLogCallBack(LogCallBack cb)
{
m_log_cb = cb;
}
8. 执行C++这边的打印函数:
void CPhone::PrintLog(const char* writeFmt, ...)
{
USES_CONVERSION;
if (m_log_cb == NULL)
{
return;
}
va_list marker;
va_start( marker, writeFmt ); // Initialize variable arguments.
int len = _vscprintf( writeFmt,marker ) + 1; // _vscprintf doesn't count terminating '\0'
char* buffer = new char[len];
SecureZeroMemory(buffer, len);
vsprintf_s( buffer, len, writeFmt, marker );
wchar_t* wideChar = A2W(buffer); /*如果是宽字节,则需要转换成宽字节,否则可以直接输出;这里要注意字节需要和第4步定义回调函数typedef void(__cdecl* LogCallBack)(const wchar_t* data);的参数类型一定要一致*/
m_log_cb(wideChar);
delete[] buffer;
va_end( marker );
}
【C#对接接口部分】
1. 对接的回调函数声明:
/// 注意:C++中定义的MCALL是__stdcall还是__cdecl ,这里CallingConvention一定要对齐;另外,回调函数的参数和返回类型也一定要对齐,不能有任何的偏差:
[UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl, CharSet = CharSet.Auto)]
public delegate void LogCallBack(string lpszStr);
2. 定义导出回调
[DllImport(dllName, EntryPoint = "RegistLog_Display_CallBack", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern bool RegistLog_Display_CallBack(IntPtr pHandle, LogCallBack cb);
【使用】
在类中可以这样使用:
1. 定义回调: LogCallBack cb;
2. 绑定回调: cb += ShowMessage; // ShowMessage是前面的C#定义,采用m_logAction委托执行函数。
3. 注册回调: RegistLog_Display_CallBack(Handle, cb);
4. 回调保持激活状态:GC.KeepAlive(cb); // 这个一定要加上,否则可能定义一个局部变量被垃圾回收后导致回调异常