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);
}
}
測試結果
可以看到窗體風格之類的也沒有什麼太大的問題,一個簡單的類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();
}