前段时间做了一个通过C# 封装中间件调用C风格dll 提供给winform 窗口使用的任务,一下先把问题描述下,将解决问题的思路记录下
问题1:C# 能否调用C++动态库?
答案是肯定的,调用方式的话我在下面写上我前段时间解决的例子:
//外部方法
[DllImport(@"ButelAgentAdapter.dll", EntryPoint = "ButelTryInitVedio", CharSet = CharSet.Ansi,
CallingConvention = CallingConvention.StdCall)]
[DllImport(@"ButelAgentAdapter.dll", EntryPoint = "ButelTryInitVedio", CharSet = CharSet.Ansi,
CallingConvention = CallingConvention.StdCall)]
///
/// 初始化SDK
///
/// 第三方API
public static extern int ButelTryInitVedio(BUTELCONECTEVENTCALLBACK handleL, int nAgentType, int nTypeParam);
public delegate void BUTELCONECTEVENTCALLBACK(int type, IntPtr data, string msg, string szExtendSignalInfo);
public static BUTELCONECTEVENTCALLBACK callback;
/// 使用
public bool InitSDK()
{
callback = butelconectevent_callback; //为方法名
if (0 != ButelTryInitVedio(callback, 3, 1))
{
return false;
}
return true;
}
C#定义外部结构体
[StructLayout(LayoutKind.Sequential)]
public struct AgentInfo
{
public Int32 m_callEvent;
public Int32 m_agentStatus;
public Int32 agentCallType;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public char[] dstNube; //被叫号码
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public char[] dstNickName;//被叫昵称
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public char[] localNube; //主叫
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public char[] localNickName;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)]
public char[] sid; //会话id
};
//指针转化成结构体的方式
static public Object BytesToStruct(IntPtr buffer, AgentInfo obj)
{
var result = Marshal.PtrToStructure(buffer, obj.GetType());
return result;
}
//带有窗口处理的C风格函数声明
[DllImport(@"ButelAgentAdapter.dll", EntryPoint = "ButelSetVideoWindow", CharSet=CharSet.Ansi,
CallingConvention = CallingConvention.StdCall)]
public static extern int ButelSetVideoWindow(Int32 localWindow, Int32 remoteWindow);
//将C# winform窗口转化成窗口句柄
static public IntPtr PictureBoxtoIntPtr(ref PictureBox obj)
{
return obj.Handle;
}
//带有窗口处理的C风格函数声明
[DllImport(@"ButelAgentAdapter.dll", EntryPoint = "ButelSetVideoWindow", CharSet=CharSet.Ansi,
CallingConvention = CallingConvention.StdCall)]
public static extern int ButelSetVideoWindow(Int32 localWindow, Int32 remoteWindow);
//将C# WPF窗口转化成窗口句柄
public IntPtr FormtoIntPtr()
{
return ((HwndSource)PresentationSource.FromVisual(romateTextBox)).Handle;
}
在这里要说明下WPF 和 winform窗口的区别 wpf 窗口是依赖
WPF和winform最大的区别在于WPF底层使用的DirectX,winform底层使用的是GDI+
首先上面已经说名了WPF 和winforms不是一种底层库,所以我们在使用中尽量给MFC的接口尽量传入winforms的句柄赋值
所以需用WPF 里面嵌入 winform控件在通过wpf将子控件赋值给mfc接口,办法如下使用usercontrol进行自定义控件绘制
private UserControl1 romateTextBox = new UserControl1(); 声明自定义控件
ButelSetVideoWindow((Int32)m_localpictureBox.Handle, (Int32)romateTextBox.Cv_Main.Handle);
将自定义控件里面的子控件赋值给C++接口进行图像绘制
首先第一个问题说明了C++里面有回调函数,说明整个代码执行是在多线程情况下进行的,所以我们通过回调函数里面的状态来通知C#窗口接收数据(事件通知)
C#定义的三个事件
public event VideoCallStateChangedEventHandler VideoCallStateChanged;
public event RecordCallbackEventHanlder RecordCallback;
public event LocationChangedEventHanlder LocationChanged;
//委托
public delegate void SendToParent();
private void sendVideoServer()
{
//线程的相关操作
SendToParent send1 = new SendToParent(sendVideoConnect);
this.BeginInvoke(send1);
}
private void sendVideoConnect()
{
VideoCallStateChanged(VideoCallState.Connected, "呼叫成功");
}
//通过C++执行回调函数后发送事件处理来驱动主程序数据改变
private void butelconectevent_callback(int type, IntPtr data, string msg, string szExtendSignalInfo)
{
m_agentinfo = (AgentInfo)BytesToStruct(data, m_agentinfo);
if (m_agentinfo.m_callEvent == (int)CallEvent.ON_INIT_SUCCESS)
{
linkthread = new Thread(new ThreadStart(ConnServer));
linkthread.IsBackground = true;
linkthread.Start();
}
if(m_agentinfo.m_callEvent == (int)CallEvent.ON_CONNECT)
{
Thread linkthreadobj = new Thread(new ThreadStart(sendVideoServer));
linkthreadobj.IsBackground = true;
linkthreadobj.Start();
}
if (m_agentinfo.m_callEvent == (int)CallEvent.ON_DISCONNECT)
{
Thread linkthreadobj = new Thread(new ThreadStart(sendVideoEndServer));
linkthreadobj.IsBackground = true;
linkthreadobj.Start();
}
}
//委托
public delegate void SendToParent();
private void ConnServer()
{
//线程的相关操作
SendToParent send1 = new SendToParent(ConnServerRes);
this.BeginInvoke(send1);
}
private void ConnServerRes()
{
ButelTryEnableCamera(true);
ButelSetVideoWindow((Int32)m_localpictureBox.Handle, (Int32)romateTextBox.Cv_Main.Handle); // 属于主线程,传入后相当于C++方面的线程需要处理
//初始化音频和视频窗口
var keystr = common.Common.GetValue("userconfig", "key");
var username = common.Common.GetValue("userconfig", "username");
var userpw = common.Common.GetValue("userconfig", "userpw");
var alias = common.Common.GetValue("userconfig", "alias");
//初始化坐席
ButelTryLogin(keystr, username, userpw, alias);
}