C#的WinForm中嵌入Qt界面库
为何会有如此奇葩需求
- 一处代码,多处运行
C#有两种写界面的框架,winFrom和WPF,这二者写的界面可以相互调用,但C#编写的界面组件在非托管C++虽然理论上可以,但是这种类似反射的机制使用其他来说特别不舒服。而对于编写组件式SDK而言,核心的界面组件可以在C++、C#、JAVA中使用而无需修改太多的代码是我们的终级目标。 - ocx 编写难度太大
使用MFC编写的OCX控件可以嵌套在各种高级语言甚至浏览器中,但对于新时代的开发人员而言,会MFC开发的越来越少,开发难度也比较大,开发MFC的性价比并不高。对于使用C++编写界面的人员来说,用Qt是大多数人的选择。 - 跨平台
ocx 不能跨平台,导致了无法在除windows操作系统下使用ocx控件,但是Qt的界面可以在跨平台环境下使用。
嵌入原理
界面的可视化本质上是使用绘图引擎绘制出来的,对于MFC、WinForm、Qt而言在编写界面库的本质上是没有太大的区别的,即都是基于windows的窗口句柄的绘制,windows SDK的很多函数都是需要指定窗口句柄(如控制窗口的消息循环、Hook钩子等)。其次解决消息发送问题。MFC、WinForm使用的是windows的消息机制,而Qt使用的信号槽机制(幸运的是Qt也可以接收windows的消息)
也就是说基于Qt编写界面dll理论上可以在MFC、Qt、JAVA、C# WinFrom以及WPF(这个还是要嵌入winfrom外壳)中使用,夸大点来说是可以在任何语言中调用。
实现步骤
使用qtwinmigrate开源库
正常情况下,qt的界面库必须是qt的主程序调用才行,但是qtwinmigrate库在内部解决了qt主程问题。我们只需要使用即可. qtwinmigrate
库的下载地址 https://github.com/qtproject/qt-solutions.git
使用步骤
- 新建一个C++的dll,将
qtwinmigrate
库的三个类(qwinhost、qmfcapp、qwinwidget三个类对应的头文件和cpp文件)文件拷贝到自己的工程目录下,并加入到工程中 在dll入口函数dllmain
中加入如下代码
BOOL WINAPI DllMain( HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpvReserved*/ )
{
static bool ownApplication = FALSE;
if ( dwReason == DLL_PROCESS_ATTACH )
{
//在dll加载的时候完成qapplication的创建,在内部通过SetWindowsHookEx这个函数解决消息的发送问题
ownApplication = QMfcApp::pluginInstance( hInstance );
}
if (dwReason == DLL_PROCESS_DETACH && ownApplication)
{
delete qApp;//退出的时候delete qApp;
}
return TRUE;
}
- 编写一个C++类来管理一个界面
class SampleWidget
{
public:
SampleWidget(void* ptr)
{
_win_widget=new QWinWidget((HWND)ptr);//通过句柄创建一个QWinWidget类,这个
QToolBar* tool_bar = new QToolBar(win);
tool_bar->addAction("open");
tool_bar->addAction("close");
QComboBox* combo_box = new QComboBox();
combo_box->addItem("zhang san");
combo_box->addItem("li si");
QVBoxLayout* box_layout = new QVBoxLayout(win);
box_layout->addWidget(tool_bar);
box_layout->addWidget(combo_box);
QListView* list_view = new QListView(win);
box_layout->addWidget(list_view);
win->setLayout(box_layout);
win->move(0, 0);
win->show();
}
~ SampleWidget()
{
delete _win_widget;
}
private:
QWinWidget* _win_widget;
}
- 使用Swig生成对应的cxx文件和相应的C#文件
notes:在前面的一系列文章中,对swig有相应的使用说明
//## 建立对应的sample.i文件
%module(directors="1") sampledll
%{
#include "SampleWidget.h"
%}
/* turn on director wrapping Callback */
%apply void *VOID_INT_PTR { void * }
%include "SampleWidget.h"
建立好swig的i文件之后执行命令
swig -c++ -csharp example.i //最简单的swig使用
执行之后将生成sample_wrap.cxx加入到dll工程中一起编译(smapledll是dll的名字),注意名字要跟你sample_wrap.cxx所在的工程名一致,负责会出现调用错误.
- 新建C# winFrom程序,将生成C#文件纳入工程中
public partial class Form1 : Form
{
SampleWidget _sample_widget;
public Form1()
{
InitializeComponent();
_sample_widget=new SampleWidget(this.Handle);
}
}
测试结果
![764534e58d46fb785b6f3affda1d166d.png](https://i-blog.csdnimg.cn/blog_migrate/dd1ad2da974da3f3fe3256a130c1fab4.png)
可以看到窗体风格之类的也没有什么太大的问题,一个简单的类com组件的控件就完成了(窗口大小这样不会自动布满屏幕,需要重写winfrom中的大小改变的方法,还有一种方法是在C++截获From1这个的句柄消息如以下方式(这样对C#研发人员来说就只要写一句代码即可)
static LRESULT CALLBACK WindowsProc(HWND win_hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_SIZE:
{
int nHeight = HIWORD(lParam);
int nWidth = LOWORD(lParam);
if (_win_widget)
{
_win_widget->setFixedSize(nWidth, nHeight);
}
break;
}
default:
return DefWindowProc(win_hwnd, message, wParam, lParam);
}
return 0;
}
SampleWidget(void* ptr,bool full_screen) //之前的Sample构造函数
{
_win_widget=new QWinWidget((HWND)ptr);//通过句柄创建一个QWinWidget类,这个
QToolBar* tool_bar = new QToolBar(win);
tool_bar->addAction("open");
tool_bar->addAction("close");
if(full_screen)
{
SetWindowLongPtr((HWND)parent, -4, (LONG_PTR)WindowsProc);//parent是之前传入的窗口句柄
}
QComboBox* combo_box = new QComboBox();
combo_box->addItem("zhang san");
combo_box->addItem("li si");
QVBoxLayout* box_layout = new QVBoxLayout(win);
box_layout->addWidget(tool_bar);
box_layout->addWidget(combo_box);
QListView* list_view = new QListView(win);
box_layout->addWidget(list_view);
win->setLayout(box_layout);
win->move(0, 0);
win->show();
}
![94584b0a7d607f512dda148c894a432c.png](https://i-blog.csdnimg.cn/blog_migrate/76230b5ac0172a7d375f02778b526037.png)