前段时间做了一个通过C# 封装中间件调用C风格dll 提供给winform 窗口使用的任务,一下先把问题描述下,将解决问题的思路记录下
问题1:C# 能否调用C++动态库?
答案是肯定的,调用方式的话我在下面写上我前段时间解决的例子:
(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)]
/// <summary>
/// 初始化SDK
/// </summary>
/// <returns>第三方API</returns>
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;
}
(2)C++ 结构体在C#中使用
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;
}
(3)C# winform 窗体传给MFC窗体进行窗体重绘
//带有窗口处理的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;
}
(4) C# WPF窗体转化成MFC窗体进行窗口重绘
//带有窗口处理的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+
(5)WPF 给 MFC窗口绘制无法绘制在活动区问题
首先上面已经说名了WPF 和winforms不是一种底层库,所以我们在使用中尽量给MFC的接口尽量传入winforms的句柄赋值
所以需用WPF 里面嵌入 winform控件在通过wpf将子控件赋值给mfc接口,办法如下使用usercontrol进行自定义控件绘制
<UserControl x:Class="Land.Survey.VideoCallInterface.common.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:wfi ="clr-namespace:System.Windows.Forms.Integration;assembly=WindowsFormsIntegration"
xmlns:wf ="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
mc:Ignorable="d"
d:DesignHeight="768" d:DesignWidth="1024" Width="Auto" Height="Auto">
<wfi:WindowsFormsHost MinHeight ="600" MinWidth="1024" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,-5,0,0">
<wf:PictureBox x:Name="Cv_Main" Margin="0,-100,0,0"></wf:PictureBox>
</wfi:WindowsFormsHost>
</UserControl>
private UserControl1 romateTextBox = new UserControl1(); 声明自定义控件
ButelSetVideoWindow((Int32)m_localpictureBox.Handle, (Int32)romateTextBox.Cv_Main.Handle);
将自定义控件里面的子控件赋值给C++接口进行图像绘制
(6)通过线程解决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();
}
}
(7)通过委托实现子线程处理主线程对象
//委托
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);
}