cv::namedWindow, GLFWwindow以及其他程序嵌入到MFC中的教程

MFC雖然很老, 不美觀, 不跨平臺, 但是在Windows系統中, 利用MFC做功能驗證的界面, 還是很快很方便的. 因爲它老, 所以有很多解決方案可以利用, 因爲它是MS提供的界面庫, 所以在Windows上很容易實現, 並且和Windows系統結合很緊密. 比如說, 窗口消息等, 在MFC中是很方便實現的. 基於上面的種種原因, 利用MFC作爲功能驗證的一個”殼” 是很好的工具.

當然, 難免就會遇到不少工程問題. 例如利用glfwCreateWindow創建出來的窗口, 怎麼讓它嵌入到MFC中. 以及經常使用OpenCV的朋友, 利用cv::namedWindow函數, 創建的圖像/視頻顯示窗口也是彈出式的, 怎麼讓它嵌入在MFC中的某個位置. 以及, 有時候想創建一個多進程程序, 讓創建的進程嵌入在MFC中運行等.

1. 準備工作

首先, 你首先得有 glfw 的源碼 , OpenCV庫, 以及一個Visual Studio(我使用的是VS2013). 另外, 在VS13中, MFC已經拋棄了多字節字符集, 如果在MFC工程中想要使用多字節字符集, 需要下載一個多字節字符集支持包. 下載好了, 安裝即可.

然後, 創建一個基於對話框的MFC工程, 創建好功能後, 編輯界面, 簡單的添加一個控件就行, 我添加的是Picture Control, 在工具箱裏面拖進來調整大小就好. 再給 <開始> 按鍵添加一個按鍵響應函數. 雙擊<開始> 按鍵就行了. 界面示圖如下:

 


UI


最後, 在工程裏面配置一下OpenCV相關的包含目錄和庫目錄以及依賴項.

 

2. OpenCV窗口嵌入MFC

對當前我要分享問題感興趣的朋友, 應該不會對OpenCV的配置有問題吧. 如果有問題的話, 搜索一下, CSDN上面也有很多人對相關問題由詳細的描述.

在前面添加的<開始>按鍵響應函數中, 添加入下述代碼.

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

<code class="language-cpp hljs ">#include <opencv2\opencv.hpp>

void CaboutMFCDlg::OnBnClickedButton1()

{

    // TODO:  在此添加控件通知處理程序代碼

    CRect rect;

    // IDC_STATIC是剛剛在界面中加入的Picture Control的ID

    GetDlgItem(IDC_STATIC)->GetWindowRect(&rect);

 

    // 創建cv窗口並重置窗口大小

    cv::namedWindow("view", cv::WINDOW_NORMAL);

    cv::resizeWindow("view", rect.Width(), rect.Height());

 

    // 設置依附關係, 將cv窗口嵌入MFC主要是下述代碼起作用了.

    HWND hWnd = (HWND)cvGetWindowHandle("view");

    HWND hParent = ::GetParent(hWnd);

    ::SetParent(hWnd, GetDlgItem(IDC_STATIC)->m_hWnd);

    ::ShowWindow(hParent, SW_HIDE);

 

    // 循環讀取文件夾中的圖片並顯示. 僅僅作爲功能驗證而已.

    cv::Mat img;

    int index = 0;

    char filename[128] = { 0 };

    while (true) {

        sprintf_s(filename, "..\\DragonBaby\\0%03d.jpg", ++index);

        img = cv::imread(filename);

        if ((img.cols <= 0) || (img.rows <= 0)) {

            break;

        }

        cv::imshow("view", img);

        cv::waitKey(30);

    }

 

    cv::destroyWindow("view");

}</opencv2\opencv.hpp></code>

其中真正關鍵的代碼就六行, 別的都是一些可有可無的代碼. 當然, 這只是一個簡單的示例而已. 當你要使用OpenCV時, 肯定不單是爲了這樣循環查看圖片而已. 但, 通過上面的示例可以給我們一個啓發, 就是完全可以將OpenCV的處理進程與界面分離, 兩者相互沒有過多的影響. MFC只是作爲一個”殼”用來展示而已. 因此, 可以將上述代碼再進行完善一下.

在該解決方案下, 再創建一個命令行工程. 配置好OpenCV. 因爲我們要使用命令行參數進行參數傳遞, 所以需要把工程的改爲使用多字節字符集. 更改方式: 右擊工程名–> 屬性 –> 配置屬性 –> 常規 –> 字符集, 選擇使用多字節字符集.

好, 下面開始寫代碼, 整理如下:
首先還是改MFC中按鍵響應函數, 修改如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

<code class="language-cpp hljs ">PROCESS_INFORMATION pi;

void CaboutMFCDlg::OnBnClickedButton1()

{

    // TODO:  在此添加控件通知處理程序代碼

    STARTUPINFO startupinfo;

    memset(&startupinfo, '\0', sizeof(startupinfo));

    startupinfo.cb = sizeof(startupinfo);

    //設置進程創建時不顯示窗口

    // startupinfo.dwFlags = STARTF_USESHOWWINDOW; /*startf_useposition*/

    // startupinfo.wShowWindow = SW_HIDE;

 

    char* CommandLine = new char[128];

    memset(CommandLine, '\0', 128);

    // 主進程窗口句柄

    HWND mainWnd = AfxGetMainWnd()->m_hWnd;

    // 顯示控件句柄

    HWND viewWnd = GetDlgItem(IDC_STATIC)->m_hWnd;

    CRect rect;

    GetDlgItem(IDC_STATIC)->GetWindowRect(&rect);

    // 將參數寫入命令行, 傳遞給馬上要創建的進程

    sprintf(CommandLine, "%d %d %d %d", mainWnd, viewWnd, rect.Width(), rect.Height());

 

    BOOL b = CreateProcess("..\\Debug\\OpenCVProc.exe", CommandLine, NULL, NULL, FALSE, NULL, NULL, NULL, &startupinfo, &pi);

    if (!b)

        MessageBox("創建進程失敗!");

</code>

然後, 在新創建的命令行工程中, 添加下述代碼:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

<code class="language-cpp hljs ">#include <opencv2\opencv.hpp>

#include <windows.h>

int _tmain(int argc, _TCHAR* argv[])

{

    int width = 0;

    int height = 0;

    HWND mainWnd = NULL;

    HWND viewWnd = NULL;

    char* commandline = GetCommandLine();

    // 從命令行中獲取主進程傳遞來的參數

    sscanf(commandline, "%d %d %d %d", &mainWnd, &viewWnd, &width, &height);

 

    // 創建cv窗口並重置窗口大小

    cv::namedWindow("view", cv::WINDOW_NORMAL);

    cv::resizeWindow("view", width, height);

 

    // 設置依附關係, 將cv窗口嵌入MFC主要是下述代碼起作用了.

    HWND hWnd = (HWND)cvGetWindowHandle("view");

    HWND hParent = ::GetParent(hWnd);

    ::SetParent(hWnd, viewWnd);

    ::ShowWindow(hParent, SW_HIDE);

 

    // 循環讀取文件夾中的圖片並顯示. 僅僅作爲功能驗證而已.

    cv::Mat img;

    int index = 0;

    char filename[128] = { 0 };

    while (true) {

        sprintf_s(filename, "..\\DragonBaby\\0%03d.jpg", ++index);

        img = cv::imread(filename);

        if ((img.cols <= 0) || (img.rows <= 0)) {

            break;

        }

        cv::imshow("view", img);

        cv::waitKey(30);

    }

    cv::destroyWindow("view");

 

    return 0;

}</windows.h></opencv2\opencv.hpp></code>

分別編譯, 然後就運行MFC程序, 點擊開始, 效果如下:

效果1

作爲調試用時, 命令行的調試信息輸出是必不可少的. 所以難看的黑框就只有先忍着吧. 到最後展示階段, 該黑框可以將按鍵響應函數中兩行註釋掉的代碼打開註釋即可讓難看的黑框不再彈出來了. 最終結果如下所示:


結果


代碼中需要說明的三點, 首先<喎�"/kf/ware/vc/" target="_blank" class="keylink">vc3Ryb25nPiwg1NrD/MHu0NCyzsr9tKu13cqxLCC0sL/avuSx+jxjb2RlPkhXTkQ8L2NvZGU+ysfX986qPGNvZGU+aW50PC9jb2RlPsC0tKu13bXELiC+38zlss68+zxhIGhyZWY9"https://msdn.microsoft.com/zh-cn/library/cc438768%28v=vs.71%29.aspx">MSDN該段說明, 可以知道, 在Win32中, HWND是32位的一個ID. 所以可以使用"%d"格式來進行傳輸. 其次, 在MFC的按鍵響應函數中, 有一個變量PROCESS_INFORMATION pi是作爲全局變量放在函數體外面的. 原因是創建新進程之後, 難免會涉及到通信問題, 最簡單的辦法就是窗口消息. 通過該變量可以實現消息傳遞. 使用PostThreadMessage(pi.dwThreadId, WM_TEST, wParam, lParam)函數傳遞消息到新進程中, 其中WM_TEST是自定義的消息. 最後, 在命令行進程中, 命令行第一個參數, 是MFC進程的窗口句柄, 可以利用該句柄發送消息到MFC進程. 使用SendMessage(mainWnd, WM_TEST, wParam, lParam)函數.

 

3. GLFWwindow嵌入MFC

3.1 配置GLFW

之前在一個小項目中, 用到了TI所提供的DLP-ALC-LIGHTCRAFTER-SDK-2.0(簡稱DLP), 該SDK提供源碼, 通過結構光projector + Point Grey攝像頭進行掃描, 得到點雲, 然後進行三維重建. 需要創建一個界面用於展示. 而在DLP中, 點雲顯示是將GLFWwindow進行了封裝用於顯示. 源碼中, 用於創建窗口的函數是glfwCreateWindow(width, height, title.c_str(), NULL, NULL), 使用上述方法得不到理想的效果. 所以只能另闢蹊徑. 很慶幸, Google到一個比較好的解決方案. 很感謝該博主, 成功的完成了預期的功能.

由於我原始項目錯綜複雜, 不利於直接呈現出該問題的解決. 所以下面我們一步一步的完成所需要的功能. 在前面的鏈接中下載glfw的源碼, 在GitHub上面下載下來即可. 另外, 需要下載CMake, 並且假定你電腦已經安裝了VS.

下載下來的glfw源碼文件夾如下圖所示:


glfw


下載好CMake之後, 安裝. 在開始菜單能夠找到 CMake(cmake-gui)的快捷方式. 打開CMake, 如下圖所示:


cmake


在 “Where is the source code:” 之後選擇你下載的glfw路徑, 如我上圖所示, 我的路徑就是E:/glfw/glfw-master, 在E:/glfw目錄下新建一個文件夾, 命名爲glfw-build, 將該文件夾路徑填入”Where to build the binaries:”, 然後點擊 . 會出現下述選擇窗口, 選擇(當然, 我電腦安裝的VS13, 所以選擇該條目, 你對應選擇你所安裝的VS就好). 然後選擇 . 然後將下圖中BUILD_SHARED_LIBS勾選上, 再次點擊:


configure


然後點擊, 會提示 Generating done. 搞定之後, 打開E:/glfw/glfw-build後你會看到如下畫面, 熟練的雙擊GLFW.sln就可以使用VS打開該工程了. VS打開之後, 爽快的按下F7. VS就開始工作了. 在E:\glfw\glfw-build\src\Debug路徑下, 會看到生成的一些文件. 都是很熟悉的東西吧. lib文件以及dll文件. VS示圖如下, 並按照圖中選項找到simple示例:


VS


按照上圖, 找到simple, 右擊彈出下拉菜單, 依次選擇<調試> –> <啓動新實例>. 會得到下圖展示的一個DEMO效果. 也許你會得到一個錯誤, 提示在E:/glfw/glfw-huild/src/Debug 裏面看到的glfw3.dll文件找不到. 解決辦法很簡單, 將該文件複製到C:\Windows\System32中去, 或者將E:/glfw/glfw-huild/src/Debug 目錄加入環境變量Path中. 第一種辦法好像需要重啓一次才行.


simple

 

3.2 修改代碼

在simple工程中, 提供了源碼, 打開simple.c可以看到其實現代碼. 能夠找到下述代碼:

?

1

2

3

4

5

6

7

8

9

<code class="language-c hljs ">glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);

glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);

 

window = glfwCreateWindow(640, 480, "Simple example", NULL, NULL);

if (!window)

{

    glfwTerminate();

    exit(EXIT_FAILURE);

}</code>

其中窗口的創建, 就是使用函數glfwCreateWindow. 在VS中, 找到glfwCreateWindow函數的定義位置, 是在 glfw3.h文件中, 新加入一個函數glfwCreateWindowEx聲明, 如下:


define


在原本glfwCreateWindow函數的參數列表中新加入了參數int hParent. 新加入的參數, 本應該是HWND類型, 但該類型定義於Windows.h中, 本着儘可能少的改動代碼, 以int代替了HWND類型, 具體原因類似於第二節中所述.

 

現在打開win32_platform.h文件, 找到其中struct _GLFWwindowWin32定義所在的位置, 新加入HWND handleParent, 用來保存父窗口的句柄作爲參數傳遞給創建窗口的函數. 如下圖所示:


新加入參數


修改好參數結構體之後, 現在定位glfwCreateWindow函數的定義, 定義於文件window.c中. 複製glfwCreateWindow函數的定義, 粘貼在glfwCreateWindow函數的定義的下方, 更改函數名爲glfwCreateWindowEx並加入參數int hParent. 在該函數的實現中找到_glfwPlatformCreateWindow函數的調用地方, 在其前方加入下述代碼:

 

?

1

<code class="language-c hljs ">window->win32.handleParent = hParent;</code>

效果如下:


傳參


現在, 沿着_glfwPlatformCreateWindow函數的函數調用一直找到API CreateWindowExW函數的調用地方, 位於win32_window.c文件定義的static int createWindow(_GLFWwindow* window, const _GLFWwndconfig* wndconfig)函數中被調用. 在 CreateWindowExW函數前加入下述代碼, 並將CreateWindowExW函數的倒數第四個參數改成window->win32.handleParent.

 

?

1

2

3

4

<code class="language-cpp hljs ">if (NULL != window->win32.handleParent) {

    exStyle = 0;

    style = WS_CHILDWINDOW | (wndconfig->visible ? WS_VISIBLE : 0);

}</code>

截圖如下:


code1


修改好了之後, 對代碼進行編譯, 還是運行simple示例進行驗證. 仍然可以得到前面原始代碼所展示的效果. 說明我們代碼的修改沒有對原本性能產生破壞.

 

3.3 效果驗證

本來, 預想是如同前一個例子, 在MFC按鍵響應函數中通過CreateProcess調用已經編譯好的simple.exe可執行程序, 完成界面的顯示. 可是一直無法成功. 通過CreateProcess調用sample文件夾中任意示例均無法成功調用. 一直沒有找到具體是爲什麼. 由於該步驟只是驗證功能. 所以就通過另一個辦法來完成驗證.

首先, 在原始MFC界面中加入三個Edit Control, 重新編寫<開始>按鍵響應函數, 代碼如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

<code class="language-cpp hljs ">void CaboutMFCDlg::OnBnClickedButton1()

{

    // TODO:  在此添加控件通知處理程序代碼

 

    HWND viewWnd = GetDlgItem(IDC_STATIC)->m_hWnd;

    CRect rect;

    GetDlgItem(IDC_STATIC)->GetWindowRect(&rect);

 

    CString str;

    str.Format("%d", viewWnd);

    GetDlgItem(IDC_EDIT1)->SetWindowText(str);

 

    str.Format("%d", rect.Width());

    GetDlgItem(IDC_EDIT2)->SetWindowText(str);

 

    str.Format("%d", rect.Height());

    GetDlgItem(IDC_EDIT3)->SetWindowText(str);

}</code>

點擊<開始>, 獲取用於顯示的Picture Control的HWND, 以及長寬. 分別顯示在三個Edit Control中. 然後在simple的代碼中寫死代碼完成該功能(恕小弟無能, 當前只能用這樣無奈的方式完成功能驗證了). simple.c中的代碼片段截取如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

<code class="language-cpp hljs ">glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);

glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);

 

// 將MFC中獲取到的三個值分別替代這三個變量.

int viewWnd = 5114720; // Picture Control的HWND

int width = 536; // Picture Control的寬

int height = 294; // Picture Control的高

window = glfwCreateWindowEx(width, height, "Simple example", NULL, NULL, viewWnd);

// 註釋掉原本創建窗口所調用的函數, 換作我們新增的創建窗口函數glfwCreateWindowEx

// window = glfwCreateWindow(640, 480, "Simple example", NULL, NULL);

if (!window)

{

    glfwTerminate();

    exit(EXIT_FAILURE);

}</code>

編譯simple工程生成可執行文件, 然後按照上面圖示的方式運行simple.可以看到如下結果.


這裏寫圖片描述

 

4. 其他程序嵌入MFC

該話題, 也是無意間在網站上瀏覽到相關的資料, 感覺很有趣, 就試着做了一下, 現在也整理出來和大家分享一下. 原作者 在他的博客中描述了一些, 但是不夠具體. 我在這兒更具體的描述一下. 另外, 在原作者描述的實現方式中, 主要是考慮所有功能均在一端實現, 被調用的exe完全不知道自己是被嵌入到MFC中在運行.

其中, 主要思想是, 調用CreateProcess後是可以得到被創建進程的信息, 其中就包括進程ID. 則可以通過枚舉進程ID進而得到被創建進程的窗口句柄. 然後就可以對該進程的窗口進行上述內容中的SetParent操作了. 在示例代碼中, 我是直接調用Windows自帶的記事本進行演示.

爲了方便展示, 此處直接使用全局變量. 定義了兩個變量, 分別保存進程句柄以及進程窗口句柄. 定義如下:

?

1

2

<code class="language-cpp hljs ">HWND apphwnd;

HANDLE handle;</code>

然後, 定義創建進程的函數, 創建成功後利用進程ID枚舉獲取窗口句柄, 代碼如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

<code class="language-cpp hljs ">// 回調函數, 枚舉獲取窗口句柄

int CALLBACK EnumWindowsProc(HWND hwnd, LPARAM param)

{

    DWORD pID;

    DWORD TpID = GetWindowThreadProcessId(hwnd, &pID);

    if (TpID == (DWORD)param)

    {

        apphwnd = hwnd;

        return false;

    }

    return true;

}

// 第一個參數是被調用進程的路徑, 第二格參數是需傳入的參數列表

HANDLE StartNewProcess(LPCTSTR program, LPCTSTR args)

{

    HANDLE hProcess = NULL;

    PROCESS_INFORMATION processInfo;

    STARTUPINFO startupInfo;

    ::ZeroMemory(&startupInfo, sizeof(startupInfo));

    startupInfo.cb = sizeof(startupInfo);

    startupInfo.dwFlags = STARTF_USESHOWWINDOW;

    startupInfo.wShowWindow = SW_HIDE;

    if (::CreateProcess(program, (LPTSTR)args,

        NULL,  // process security

        NULL,  // thread security

        FALSE, // no inheritance

        0,     // no startup flags

        NULL,  // no special environment

        NULL,  // default startup directory

        &startupInfo,

        &processInfo))

    { /* success */

        Sleep(50);//wait for the window of exe application created

        ::EnumWindows(&EnumWindowsProc, processInfo.dwThreadId);

        hProcess = processInfo.hProcess;

    } /* success */

    return hProcess;//Return HANDLE of process.

}</code>

當然, 該進程的關閉也是需要定義相關的函數.

?

1

2

3

<code class="language-cpp hljs ">BOOL CloseProcess() {

    return TerminateProcess(handle, 0);

}</code>

現在, 我們再次重新編寫<開始>按鍵響應函數, 代碼如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

<code class="language-cpp hljs ">void CaboutMFCDlg::OnBnClickedButton1()

{

    // TODO:  在此添加控件通知處理程序代碼

    handle = StartNewProcess("C:\\Windows\\notepad.exe", NULL);

 

    // CRect rect;

    // GetDlgItem(IDC_STATIC)->GetWindowRect(&rect);

    // ::MoveWindow(apphwnd, rect.left, rect.top, rect.Width(), rect.Height(), false);

    ::SetWindowLong(apphwnd, GWL_STYLE, WS_VISIBLE);

 

    HWND viewWnd = GetDlgItem(IDC_STATIC)->GetSafeHwnd();

    ::SetParent(apphwnd, viewWnd);

}</code>

代碼很簡單, 我就沒有寫註釋了. 其中被註釋掉的三行代碼, 本來應該是完成將新建的進程移動到指定位置, 但是移動之後, 會出現錯誤. 也沒有找到原因. 希望哪位朋友知道原因併成功解決了的話, 告訴我一聲. 謝謝.

該內容幾乎和上面給出原作者的示例一樣. 感興趣的朋友, 可以查看原作者的表述.

最後, 結果示例如下:


這裏寫圖片描述

 

OK, 打完收工.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章