資源MFC DLL的制作[在DLL中封裝MFC對話框]


資源MFC DLL的制作[在DLL中封裝MFC對話框]

分類: Windows

UISTYLER中沒有樹列表控件的吧?對UG的二次開發中會常需要樹列表控件,解決之道可以使用MFC中的樹列表控件。 

所涉及的知識: 
(1)MFC模塊狀態的切換 
(2)在DLL中封裝一個非模態對話框,可被任何WIN32程序調用 
(3)非模態對話框的銷毀 
(4)怎樣將層次特征的信息自私樹狀列表控件中顯示 

一.要做的一些准備 
1.粗通一些MFC和DLL的知識。 
得知道什么是窗口對象,什么是程序對象,什么是事件,什么是消息,什么是消息映射及怎樣實現消息映射。對於DLL,知道怎樣輸出一些函數就差不多了,怎樣在應用程序中加載一個DLL,並使用它們提供的接口。

2.控件通知消息(Notification message) 
控件中發生了一些事件,是在控件的父窗口中響應事件,而不是在控件的窗口中響應。程序實現時,就相應的將這些事件的處理統統放在控件對象的父窗口對象的消息成員函數中。控件通知消息只適用於標准的窗口控件如按鈕、列表框、組合框、編輯框,以及樹狀視圖、列表視圖等公共控件。例如,單擊或雙擊一個控件、在控件中選擇部分文本、操作控件的滾動條都會產生通知消息。

3.用到的控件是Tree Control 
MSDN中的說法: 
MFC 提供了兩個封裝樹控件 (Tree Control) 的類:CTreeCtrl 和CTreeView。 
CTreeView的使用過於復雜。Tree Control則是它的簡化版本,主要用做對話框上的控件。 

4. 采用的DLL格式: 
由於我們只是想使用DLL導出的對話框資源,而且還想着有可能在非MFC環境中使用該對話框資源,規則的MFC DLL可以勝任,就沒必要使用MFC擴展DLL了,另外如果使用了擴展的MFC DLL,那么你的應用程序也必須使用MFC庫。

5.開發工具 
能不用VC6就不用,用VS.net吧,它更支持ANSI C/C++,功能也更強大

 

 

二.建立一個可以包含MFC對話框的DLL 
樹形控件僅能以子窗口的形式出現,它要依附對話框這樣的父窗口。 可以在UG二次開發的DLL項目中直接添加對話框資源和對話框類來實現,手段雖不怎么高明,但能實現所需的功能。但我覺得最好將與UG沒有多大關系的功能分離出來,作為一個模塊單獨實現,這也是軟件工程所鼓勵的方式。 

本文的方法是首先做一個DLL,讓包含一個MFC對話框,這個DLL可以在UG二次開發的項目中被加載。被包含在DLL中的對話框通常稱為資源對話框。 

下面為建立可以包含對話框資源的DLL的過程。 
1.建一個MFC Regular DLL項目 . 
VS.net(沒找到英文版,菜單只能給出中文名稱)的建立一個DLL的過程如下: 
[1]菜單 文件→新建→項目,在彈出的項目對話框的左欄,選擇Visual C++項目,在右欄選擇MFC DLL。然后在下面的文本框中輸入項目的名字,確定,進入MFC DLL向導。 
[2]MFC DLL向導,在"應用程序設置"中,選擇'使用共享MFC DLL的規則DLL",完成向導設置后,生成一個空的MFC DLL項目。 
[3]菜單:項目→添加資源,在添加資源對話框中,選擇"Dialog",然后點擊按鈕"新建"。VS.net會自動切換到資源視圖界面,刪去默認的"OK"和"CANCEL" 按鈕。 
[4]將默認的對話框ID修改成你能記得住的名字。 
[5]添加對話框類CTreeDlg。 
//-------------------- 
以上過程都是IDE在工作,下面就要動腦,動手了。 
//------------------- 
[6] 建立輸出函數ShowTreeDlg 
DLL是無法自動進入內存開始運行的,要被其它可執行文件的加載才可以。對話框是在DLL中創建的,我們期望在UG二次開發的項目中,在某個UIStyler控件的觸發下彈出這個DLL中創建的對話框。而常規的MFC DLL是無法導出MFC對象給其它應用程序使用的,只能通過輸出函數來做。 

向工程中添加兩個文件ExportFunc.h和ExportFunc.CPP,我打算將輸出函數的聲明和定義統一放在這兩個文件中,便於管理。代碼實現如下: 
//-------------------------------------------- 
//ExportFunc.h, 
//聲明欲輸出的函數 
//------------------------------------------- 
#ifndef _EXPORTFunc_H 
#define _EXPORTFunc_H 
#ifdef _cplusplus 
extern "C"{ 
#endif 
void ShowTreeDlg(HWND hMainWnd); 
#ifdef _cplusplus 

#endif 
#endif 

//-------------------------------------------- 
//ExportFunc.cpp文件 
//定義輸出函數 
//------------------------------------------- 
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 
//功能:DLL的輸出函數,當其他應用程序加載該DLL后,調用這個函數,可以顯示該DLL內建的對話框 
//輸入參數1:HWND hMainWnd,對話框父窗口的句柄 
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 
void ShowTreeDlg(HWND hMainWnd) 

  AFX_MANAGE_STATE(AfxGetStaticModuleState()); 
  CTreeDlg *pTreeDlg=new CTreeDlg; 
  CWnd * pMainWnd=CWnd:: FromHandle(hMainWnd); 
  ASSERT(pMainWnd); 
  BOOL retValue=pTreeDlg->Create(IDD_TREE,pMainWnd); 
  if(!retValue) 
  { 
    AfxMessageBox("創建包含樹列表控件的對話框失敗了!"); 
  } 
  pTreeDlg->ShowWindow(SW_SHOW); 

下面是模塊定義文件的內容,通常我們是使用_declspec(dllexport)直接修飾輸出的函數,這樣導出的接口很容易就被查看DLL文件的工具觀察到,保密性不夠好。為了向外界隱藏你的DLL對外接口的名稱,只有def文件可以做。 
; DlgWithTreeCtrl.def : 聲明 DLL 的模塊參數。 

LIBRARY "DlgWithTreeCtrl" 

EXPORTS 
    ; 此處可以是顯式導出 
   ShowTreeDlg @1 NONAME

 

知識點1:MFC模塊狀態的切換 
上貼,在ExportFunc.CPP中,定義輸出函數ShowTreeDlg時,函數體開頭一句是AFX_MANAGE_STATE(AfxGetStaticModuleState()); 
對MFC DLL不熟悉的兄弟在二次開發中使用MFC的庫時,常常會丟了這句,以致出現一些莫名奇妙的錯誤。有的兄弟能記着添加它,但不見得就知道它的作用。不要求非要知道它的作用,但如果你會開車,又懂得修車,這樣會更好。 

[1]模塊的定義:一段可執行的程序,其程序代碼,數據,資源被加載到內存中了(這種加載可以是操作系統來做,也可以是已被加載到內存中的模塊來做),這段程序進入內存后,系統會為之建立一個數據結構來管理,這個數據結構在WINDOWS中,就是PE文件頭。一個活動在內存中的EXE文件是不是模塊?一個活動在內存中的DLL文件是不是模塊?它們都是。 

[2]MFC模塊:使用了MFC庫的模塊。 

[3]MFC模塊的狀態:是一個數據結構,里面存了許多與模塊相關的數據,這些數據具體是什么,我也不清楚,但我知道我的DLL中所包含的對話框是存於資源模板中的,而資源模板是存於這個MFC模塊的狀態數據結構中的。只關心這一點就可以了。 

[4]一個MFC程序運行時,默認狀態下,當它需要資源時,比如它需要一個對話框,那么它會從自身的模塊狀態中找到資源模板,然后從資源模板中找到相應的對話框,它是怎么找到自己所需要的對話框模板的呢?根據對話框ID,這是一個整型數。 
然而,當一個MFC的應用程序在運行時,可能要加載多個MFC模塊。譬如程序A.exe在運行時,加載了B.dll。在B.dll中提供了一個輸出函數ShowDlg,函數體定義如下: 
//------------------------------------------------------------------------- 
//該函數顯示一個非模態對話框,寫法極簡,僅作示例 
//------------------------------------------------------------------------- 
void ShowDlg(void) 

       CDlg *pDlg = new CDlg; 
       pDlg->Create(IDD_DLG,NULL); 
       pDlg->ShowWindow(SW_SHOW); 

我們可以看到,B.dll中肯定是存在一個ID為IDD_DLG的對話框模板的,按照上面所說的,這個對話框模板被存到了B.dll的狀態模塊中了。當A.exe中調用這個輸出函數時,運行到pDlg->Create(IDD_DLG,NULL)時,它就會去自己的模塊狀態中去查找標號為IDD_DLG的對話框模板,結果有可能找得到,也有可能找不到。假如A.exe的模塊狀態中有一個對話框模板的ID恰好等於IDD_DLG時,就能找到,但彈出的對話框並不是你在B.dll中定義的那個。當A.exe的模塊狀態中沒有ID恰好等於IDD_DLG的對話框時,那就是找不到,程序會報錯。 

[5]怎么解決 
在[4]中,當程序A調用B.dll中的輸出函數ShowDlg時,也就是說程序A進入了函數ShowDlg。倘若在ShowDlg的入口點處,把應用程序默認的自身模塊狀態切換成B.dll的模塊狀態,這樣再查找標號為IDD_DLG的對話框時,就會從B.dll的模塊狀態中查找了,結果就能找到。這個切換語句就是在ShowDlg函數體的第一句處添加:AFX_MANAGE_STATE(AfxGetStaticModuleState());

 

三.測試這個包含對話框資源的DLL 
由於這個Dll是采用MFC Regular DLL的格式寫的,MSDN中說它可以被任何WIN32程序調用,要測試它,簡單起見,只需寫個Console程序來測即可。 

任務:測試DlgWithTreeCtrl.dll 
所需的文件:ExportFunc.h,DlgWithTreeCtrl.lib,DlgWithTreeCtrl.dll。 

常規的測試步驟 
[1]菜單 文件→新建→項目,在彈出的項目對話框的左欄,選擇Visual C++項目,在右欄選擇Win32控制台項目。然后在下面的文本框中輸入項目的名字testDll,確定,進入Win32控制台程序向導。 

[2]Win32控制台程序向導中,在"應用程序設置"中,選擇'控制台應用程序",沒有必要讓你的控制台程序支持MFC,其它都采用默值,完成向導設置。 

[3]如果此時testDll項目是debug模式的,將DlgWithTreeCtrl.lib和DlgWithTreeCtrl.dll拷貝到testDll目錄中的debug文件夾下。 

[4]testDll的主程序文件testDll.cpp中內容如下: 
//--------------------------- 
//testDll.cpp 
//-------------------------- 
#include "stdafx.h" 
#include "..\DlgWithTreeCtrl\ExportFunc.h" 
#pragma comment(lib, "..\\DlgWithTreeCtrl\\release\\DlgWithTreeCtrl.lib") 

void main(void) 

  ShowTreeDlg(NULL); 


[5]編譯連接,運行。運行結果是一個控制台窗口出現,然后一個對話框一閃就不見了。呵呵,對話框確實被顯示了,只是控制台程序瞬間就結束了,所以對話框就被自動銷毀了。可以在main函數中加上一句: 
MessageBox(NULL,"stop",NULL,MB_OK); 
讓控制台程序不那么快就消亡。 

最終結果如下圖:

 

四. 還需要繼續考慮的問題 
我在DlgWithTreeCtrl.dll中做的那個輸出函數,new了一個對話框的對象,程序中從始至終都沒有釋放所new的(堆)空間。會不會內存泄露?這不是一個小問題。換個說法:MFC的窗口對象是怎樣從系統中清除的呢?要弄清楚這個問題,必須先搞清楚窗口對象的成分。 

一個MFC窗口對象包括兩方面的內容:一是窗口對象封裝的窗口,即存放在m_hWnd成員中的HWND(窗口句柄),二是窗口對象本身是一個C++對象。要刪除一個MFC窗口對象,應該先刪除窗口對象封裝的窗口,然后刪除窗口對象本身。也就是窗口的C++對象的生存期要比窗口長。 

[1]窗口的刪除 
刪除窗口最直接方法是調用CWnd:: DestroyWindow或全局API函數:: DestroyWindow,前者封裝了后者的功能。前者不僅會調用后者,而且會使成員m_hWnd保存的HWND無效(NULL)。如果DestroyWindow刪除的是一個父窗口或擁有者窗口,則該函數會先自動刪除所有的子窗口或被擁有者,然后再刪除父窗口或擁有者。在一般情況下,在程序中不必直接調用DestroyWindow來刪除窗口,因為MFC會自動調用DestroyWindow來刪除窗口。例如,當用戶退出應用程序時,會產生WM_CLOSE消息,該消息會導致MFC自動調用CWnd:: DestroyWindow來刪除主框架窗口。當用戶在對話框內按了OK或Cancel按鈕時,可調用CWnd:: DestroyWindow來刪除對話框及其控件。 

[2]窗口對象的刪除 
窗口對象本身的刪除則根據對象創建方式的不同,分為兩種情況。在MFC編程中,會使用大量的窗口對象,有些窗口對象以變量的形式嵌入在別的對象內或以局部變量的形式創建在堆棧上,有些則用new操作符創建在堆中。 

對於一個以變量形式創建的窗口對象,程序員不必關心它的刪除問題,因為該對象的生命期總是有限的,若該對象是某個對象的成員變量,它會隨着父對象的消失而消失,若該對象是一個局部變量,那么它會在函數返回時被清除。 

對於一個在堆中動態創建的窗口對象,其生命期卻是任意長的。因為用new在堆中創建對象,就不能忘記用delete刪除對象。有些MFC窗口對象具有自動清除的功能。不需要程序員顯示的調用delete來刪除它。 
------------------------------------------------------------------------------------------------------ 
------------------------------------------------------------------------------------------------------ 
[3]對於我new的那個非模態的對話框,該如何銷毀呢? 
這個問題纏繞我好幾天,至今為止還是沒有解決的。經測試,發現上面來自網上的和來自MSDN的資料說的不正確。 

例如這句話:"用戶退出應用程序時,會產生WM_CLOSE消息,該消息會導致MFC自動調用CWnd:: DestroyWindow來刪除主框架窗口。" 

經過測試,當我點了對話框窗口的系統菜單上的"關閉"按鈕后,產生WM_CLOSE消息,而它並沒有調用CWnd:: DestroyWindow來銷毀窗口!!!!!! 

這個問題最終是在這里得到解決的,去這里看看: 
http://community.csdn.net/Expert/topic/4025/4025795.xml?temp=.989773

 

五. 開始制作帶有樹形列表控件的對話框了 
插語:回頭看了看那么長的篇幅,許多東西想整的再細致一點,實在有心無力了.也許用MFC做界面真的太繁瑣了。這可能也是許多人更熱衷於學VB,DELPHI,而逃避VC的原因吧。總之Windows給了人們一個直觀的界面,也浪費了無數程序員大好的青春,他們本來可以做更多有用的事,僅因為程序的門臉而廢腦筋。 

前面的問題都解決了,下面就一馬平川了。打開前面完成的DlgWithTreeCtrl項目。 
[1]切換到資源視圖,從工具箱中拖一個tree control到對話框面板上 

[2]試着隨便修改這個tree control的屬性,試了一遍就基本知道一個tree control有哪些風格。 

[3]在原對話框類CTreeDlg中添加一個控件變量m_treeCtrl,使用添加變量向導進行添加,這樣IDE會自動在類CTreeDlg的實現代碼中添加DDX語句,從而將控件對象與控件連接起來。 

[4]剩下的工作就是如何操縱這個tree control了。 
假如我們在UG下開發一套標准件庫,各零件模型都可以產生了,也就是一大堆DLL,每個DLL產生一種或幾種零件。我們需要一個窗體專門用來顯示整個標准件庫所有零件的信息,當在列表中選中一種零件時,就可以在UG中自動創建它。這樣,采用樹形列表是最為直觀的了。

 

六.與樹形列表相對應的數據結構的設計 
樹形列表中的條目是由根,分枝,葉構成的。葉結點中存儲零件信息,我們應該怎樣將信息寫到樹形列表中對應的條目里,這是使用樹形控件首先要考慮的問題,不然只有一個空殼界面是沒什么意義的。 

我想做一個數據結構,用它來存儲根,分枝和葉的信息,我們自然會想到它應該是一棵樹,可以采用C++編寫這樣一個樹的類,稱之為信息樹。如果我們在對話框類添加一個指向這樣一個信息樹對象的指針,那么就可以通過深度優先遍歷實現樹狀信息在樹形列表中的顯示了。 

[1]信息樹節點的設計 
//------------------------------ 
//信息樹的節點 
//------------------------------ 
typedef struct INFO_NODE_s 

int state; // 
char name[_STR_LENGTH]; //節點對應條目的名稱 

void *pObj; //指向要在樹列表控件中顯示其信息的對象 
void *pOtherObj ; //備用指針 

//每個節點都有一個ID鏈表,來表示它在樹中的位置 
//根結點第一個子節點就是鏈表的第一個單元 
CListTemplate<int> *pPath; 

//指向存儲子節點的鏈表對象的指針 
INFO_NODE_s * pParent; 
CListTemplate<INFO_NODE_s *> *pSubNodeList; 
}INFO_NODE_s,*INFO_NODE_p_s; 

[2]信息樹的類聲明 
/////////////////////////////////////////////////////////// 
//信息樹類 
////////////////////////////////////////////////////////// 
class CInfoTree 

protected: 
INFO_NODE_p_s m_pRoot; //根節點 
public: 
CInfoTree(void); 
~CInfoTree(void); 
//----------------------------------------------- 
//接口 
//----------------------------------------------- 
public: 
INFO_NODE_p_s GetRoot(); //取指向根節點的指針 

static INFO_NODE_p_s MallocNode(); //分配一個新的節點 

//用戶輸入結點名稱,ID字符串,指向用戶對象的指針來分配新結點 
static INFO_NODE_p_s MallocNode(char *name,char *nodeIdStr,void *pObj,void *pOtherObj); 

static void FreeNode(INFO_NODE_p_s p); //銷毀一個節點 

INFO_NODE_p_s Insert(INFO_NODE_p_s p); //向樹中插入節點 
INFO_NODE_p_s Delete(INFO_NODE_p_s p); //從樹中刪除節點 
};

 

七 利用樹形列表控件來顯示信息樹 
[1]深度優先遍歷樹 
為了在控件中顯示樹的信息,要寫一個遞歸函數來遍歷信息樹。 
//------------------------------------------------------------- 
// 功能:遍歷信息樹,在樹列表控件中顯示信息 
//------------------------------------------------------------- 
void CTreeDlg:FSTraverInfoTree(INFO_NODE_p_s p,HTREEITEM hItem) 

HTREEITEM h; 
if(p==NULL) 

return; 

//向對話框中的控件對象m_treeCtrl中添加各結點信息 
h = m_treeCtrl.InsertItem(p->name,0,0,hItem); 

INFO_NODE_p_s tempPtr; 
NODE_POSITION(INFO_NODE_p_s) iteratorPtr=p->pSubNodeList->GetHeadPosition(); 
while(iteratorPtr != NULL) 

tempPtr = p->pSubNodeList->GetNext([$iteratorPtr)] 
DFSTraverInfoTree(tempPtr,h); 



[2]修改原來的DLL 
由調用該DLL的應用程序傳入信息樹對象,在對話框的初始化函數中,將信息樹對象中包含的信息在樹狀列表控件中顯示出來。 

為了得到應用程序傳入的信息樹對象,需要在DLL中的對話框類中添加一個信息樹的成員變量,再將輸出函數修改為: 
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 
//功能:DLL的輸出函數,當其他應用程序加載該DLL后,調用這個函數,可以顯示該DLL內建的對話框 
//輸入參數1:HWND hMainWnd,對話框父窗口的句柄 
//輸入參數2:指向信息樹對象的指針,信息樹對象中封裝了要在控件中顯示的信息 
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 
void ShowTreeDlg(HWND hMainWnd,CInfoTree *pInfoTree) 

AFX_MANAGE_STATE(AfxGetStaticModuleState()); 
CTreeDlg *pTreeDlg=new CTreeDlg; 
pTreeDlg->m_pInfoTree = pInfoTree; 
CWnd * pMainWnd=CWnd:: FromHandle(hMainWnd); 
ASSERT(pMainWnd); 
BOOL retValue=pTreeDlg->Create(IDD_TREE_DLG, NULL); 
if(!retValue) 

AfxMessageBox("創建包含樹列表控件的對話框失敗了!"); 

pTreeDlg->ShowWindow(SW_SHOW); 

[3]在列表中顯示樹的信息 
BOOL CTreeDlg:: OnInitDialog() 

CDialog:: OnInitDialog(); 
INFO_NODE_p_s p = m_pInfoTree->GetRoot(); 
DFSTraverInfoTree(p,NULL); 

return TRUE; 
}

 

八。程序運行結果 
將用來測試該資源DLL的控制台程序作下改動: 
void main(void) 

  CInfoTree *pInfoTree=NULL; 
  pInfoTree= new CInfoTree; 

  INFO_NODE_p_s p=CInfoTree::MallocNode("0","0",NULL,NULL); 
  pInfoTree->Insert(p); 

  p=CInfoTree::MallocNode("0-1","0-1",NULL,NULL); 
  pInfoTree->Insert(p); 

  p=CInfoTree::MallocNode("0-2","0-2",NULL,NULL); 
  pInfoTree->Insert(p); 

  p=CInfoTree::MallocNode("0-1-1","0-1-1",NULL,NULL); 
  pInfoTree->Insert(p); 

  p=CInfoTree::MallocNode("0-1-2","0-1-2",NULL,NULL); 
  pInfoTree->Insert(p); 

  p=CInfoTree::MallocNode("0-1-3","0-1-3",NULL,NULL); 
  pInfoTree->Insert(p); 

  ShowTreeDlg(NULL,pInfoTree); 
  //加上一個消息對話框不讓主程序自動退出 
  MessageBox(NULL,"stop1",NULL,MB_OK); 



程序運行結果如下:

九。做個小小的總結 
大體把對話框資源DLL的制作和MFC的tree control的使用的學習過程記錄了下來。作為學習,而不是應用,所以只敘述了基本的思路,具體實現過程,還需自己動手的。 

在項目使用這個控件還是有幾個問題需要解決 
[1].控件界面的美化。譬如每個列表項上都可以貼上小圖標的。還有添加右鍵菜單之類的 
[2]交互修改列表中各項的名稱 
[3].資源DLL中的對話框能夠響應用戶交互的消息,但如何將消息處理的結果反饋給調用它的應用程序呢?比如,我從列表控件中刪除了一項,在資源DLL中也只能是將控件中顯示的項去掉了,該項所對應的數據對象還活動在內存中,該怎樣通知應用程序,讓它把數據對象銷毀

資源MFC DLL的制作[在DLL中封裝MFC對話框]

分類: Windows

UISTYLER中沒有樹列表控件的吧?對UG的二次開發中會常需要樹列表控件,解決之道可以使用MFC中的樹列表控件。 

所涉及的知識: 
(1)MFC模塊狀態的切換 
(2)在DLL中封裝一個非模態對話框,可被任何WIN32程序調用 
(3)非模態對話框的銷毀 
(4)怎樣將層次特征的信息自私樹狀列表控件中顯示 

一.要做的一些准備 
1.粗通一些MFC和DLL的知識。 
得知道什么是窗口對象,什么是程序對象,什么是事件,什么是消息,什么是消息映射及怎樣實現消息映射。對於DLL,知道怎樣輸出一些函數就差不多了,怎樣在應用程序中加載一個DLL,並使用它們提供的接口。

2.控件通知消息(Notification message) 
控件中發生了一些事件,是在控件的父窗口中響應事件,而不是在控件的窗口中響應。程序實現時,就相應的將這些事件的處理統統放在控件對象的父窗口對象的消息成員函數中。控件通知消息只適用於標准的窗口控件如按鈕、列表框、組合框、編輯框,以及樹狀視圖、列表視圖等公共控件。例如,單擊或雙擊一個控件、在控件中選擇部分文本、操作控件的滾動條都會產生通知消息。

3.用到的控件是Tree Control 
MSDN中的說法: 
MFC 提供了兩個封裝樹控件 (Tree Control) 的類:CTreeCtrl 和CTreeView。 
CTreeView的使用過於復雜。Tree Control則是它的簡化版本,主要用做對話框上的控件。 

4. 采用的DLL格式: 
由於我們只是想使用DLL導出的對話框資源,而且還想着有可能在非MFC環境中使用該對話框資源,規則的MFC DLL可以勝任,就沒必要使用MFC擴展DLL了,另外如果使用了擴展的MFC DLL,那么你的應用程序也必須使用MFC庫。

5.開發工具 
能不用VC6就不用,用VS.net吧,它更支持ANSI C/C++,功能也更強大

 

 

二.建立一個可以包含MFC對話框的DLL 
樹形控件僅能以子窗口的形式出現,它要依附對話框這樣的父窗口。 可以在UG二次開發的DLL項目中直接添加對話框資源和對話框類來實現,手段雖不怎么高明,但能實現所需的功能。但我覺得最好將與UG沒有多大關系的功能分離出來,作為一個模塊單獨實現,這也是軟件工程所鼓勵的方式。 

本文的方法是首先做一個DLL,讓包含一個MFC對話框,這個DLL可以在UG二次開發的項目中被加載。被包含在DLL中的對話框通常稱為資源對話框。 

下面為建立可以包含對話框資源的DLL的過程。 
1.建一個MFC Regular DLL項目 . 
VS.net(沒找到英文版,菜單只能給出中文名稱)的建立一個DLL的過程如下: 
[1]菜單 文件→新建→項目,在彈出的項目對話框的左欄,選擇Visual C++項目,在右欄選擇MFC DLL。然后在下面的文本框中輸入項目的名字,確定,進入MFC DLL向導。 
[2]MFC DLL向導,在"應用程序設置"中,選擇'使用共享MFC DLL的規則DLL",完成向導設置后,生成一個空的MFC DLL項目。 
[3]菜單:項目→添加資源,在添加資源對話框中,選擇"Dialog",然后點擊按鈕"新建"。VS.net會自動切換到資源視圖界面,刪去默認的"OK"和"CANCEL" 按鈕。 
[4]將默認的對話框ID修改成你能記得住的名字。 
[5]添加對話框類CTreeDlg。 
//-------------------- 
以上過程都是IDE在工作,下面就要動腦,動手了。 
//------------------- 
[6] 建立輸出函數ShowTreeDlg 
DLL是無法自動進入內存開始運行的,要被其它可執行文件的加載才可以。對話框是在DLL中創建的,我們期望在UG二次開發的項目中,在某個UIStyler控件的觸發下彈出這個DLL中創建的對話框。而常規的MFC DLL是無法導出MFC對象給其它應用程序使用的,只能通過輸出函數來做。 

向工程中添加兩個文件ExportFunc.h和ExportFunc.CPP,我打算將輸出函數的聲明和定義統一放在這兩個文件中,便於管理。代碼實現如下: 
//-------------------------------------------- 
//ExportFunc.h, 
//聲明欲輸出的函數 
//------------------------------------------- 
#ifndef _EXPORTFunc_H 
#define _EXPORTFunc_H 
#ifdef _cplusplus 
extern "C"{ 
#endif 
void ShowTreeDlg(HWND hMainWnd); 
#ifdef _cplusplus 

#endif 
#endif 

//-------------------------------------------- 
//ExportFunc.cpp文件 
//定義輸出函數 
//------------------------------------------- 
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 
//功能:DLL的輸出函數,當其他應用程序加載該DLL后,調用這個函數,可以顯示該DLL內建的對話框 
//輸入參數1:HWND hMainWnd,對話框父窗口的句柄 
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 
void ShowTreeDlg(HWND hMainWnd) 

  AFX_MANAGE_STATE(AfxGetStaticModuleState()); 
  CTreeDlg *pTreeDlg=new CTreeDlg; 
  CWnd * pMainWnd=CWnd:: FromHandle(hMainWnd); 
  ASSERT(pMainWnd); 
  BOOL retValue=pTreeDlg->Create(IDD_TREE,pMainWnd); 
  if(!retValue) 
  { 
    AfxMessageBox("創建包含樹列表控件的對話框失敗了!"); 
  } 
  pTreeDlg->ShowWindow(SW_SHOW); 

下面是模塊定義文件的內容,通常我們是使用_declspec(dllexport)直接修飾輸出的函數,這樣導出的接口很容易就被查看DLL文件的工具觀察到,保密性不夠好。為了向外界隱藏你的DLL對外接口的名稱,只有def文件可以做。 
; DlgWithTreeCtrl.def : 聲明 DLL 的模塊參數。 

LIBRARY "DlgWithTreeCtrl" 

EXPORTS 
    ; 此處可以是顯式導出 
   ShowTreeDlg @1 NONAME

 

知識點1:MFC模塊狀態的切換 
上貼,在ExportFunc.CPP中,定義輸出函數ShowTreeDlg時,函數體開頭一句是AFX_MANAGE_STATE(AfxGetStaticModuleState()); 
對MFC DLL不熟悉的兄弟在二次開發中使用MFC的庫時,常常會丟了這句,以致出現一些莫名奇妙的錯誤。有的兄弟能記着添加它,但不見得就知道它的作用。不要求非要知道它的作用,但如果你會開車,又懂得修車,這樣會更好。 

[1]模塊的定義:一段可執行的程序,其程序代碼,數據,資源被加載到內存中了(這種加載可以是操作系統來做,也可以是已被加載到內存中的模塊來做),這段程序進入內存后,系統會為之建立一個數據結構來管理,這個數據結構在WINDOWS中,就是PE文件頭。一個活動在內存中的EXE文件是不是模塊?一個活動在內存中的DLL文件是不是模塊?它們都是。 

[2]MFC模塊:使用了MFC庫的模塊。 

[3]MFC模塊的狀態:是一個數據結構,里面存了許多與模塊相關的數據,這些數據具體是什么,我也不清楚,但我知道我的DLL中所包含的對話框是存於資源模板中的,而資源模板是存於這個MFC模塊的狀態數據結構中的。只關心這一點就可以了。 

[4]一個MFC程序運行時,默認狀態下,當它需要資源時,比如它需要一個對話框,那么它會從自身的模塊狀態中找到資源模板,然后從資源模板中找到相應的對話框,它是怎么找到自己所需要的對話框模板的呢?根據對話框ID,這是一個整型數。 
然而,當一個MFC的應用程序在運行時,可能要加載多個MFC模塊。譬如程序A.exe在運行時,加載了B.dll。在B.dll中提供了一個輸出函數ShowDlg,函數體定義如下: 
//------------------------------------------------------------------------- 
//該函數顯示一個非模態對話框,寫法極簡,僅作示例 
//------------------------------------------------------------------------- 
void ShowDlg(void) 

       CDlg *pDlg = new CDlg; 
       pDlg->Create(IDD_DLG,NULL); 
       pDlg->ShowWindow(SW_SHOW); 

我們可以看到,B.dll中肯定是存在一個ID為IDD_DLG的對話框模板的,按照上面所說的,這個對話框模板被存到了B.dll的狀態模塊中了。當A.exe中調用這個輸出函數時,運行到pDlg->Create(IDD_DLG,NULL)時,它就會去自己的模塊狀態中去查找標號為IDD_DLG的對話框模板,結果有可能找得到,也有可能找不到。假如A.exe的模塊狀態中有一個對話框模板的ID恰好等於IDD_DLG時,就能找到,但彈出的對話框並不是你在B.dll中定義的那個。當A.exe的模塊狀態中沒有ID恰好等於IDD_DLG的對話框時,那就是找不到,程序會報錯。 

[5]怎么解決 
在[4]中,當程序A調用B.dll中的輸出函數ShowDlg時,也就是說程序A進入了函數ShowDlg。倘若在ShowDlg的入口點處,把應用程序默認的自身模塊狀態切換成B.dll的模塊狀態,這樣再查找標號為IDD_DLG的對話框時,就會從B.dll的模塊狀態中查找了,結果就能找到。這個切換語句就是在ShowDlg函數體的第一句處添加:AFX_MANAGE_STATE(AfxGetStaticModuleState());

 

三.測試這個包含對話框資源的DLL 
由於這個Dll是采用MFC Regular DLL的格式寫的,MSDN中說它可以被任何WIN32程序調用,要測試它,簡單起見,只需寫個Console程序來測即可。 

任務:測試DlgWithTreeCtrl.dll 
所需的文件:ExportFunc.h,DlgWithTreeCtrl.lib,DlgWithTreeCtrl.dll。 

常規的測試步驟 
[1]菜單 文件→新建→項目,在彈出的項目對話框的左欄,選擇Visual C++項目,在右欄選擇Win32控制台項目。然后在下面的文本框中輸入項目的名字testDll,確定,進入Win32控制台程序向導。 

[2]Win32控制台程序向導中,在"應用程序設置"中,選擇'控制台應用程序",沒有必要讓你的控制台程序支持MFC,其它都采用默值,完成向導設置。 

[3]如果此時testDll項目是debug模式的,將DlgWithTreeCtrl.lib和DlgWithTreeCtrl.dll拷貝到testDll目錄中的debug文件夾下。 

[4]testDll的主程序文件testDll.cpp中內容如下: 
//--------------------------- 
//testDll.cpp 
//-------------------------- 
#include "stdafx.h" 
#include "..\DlgWithTreeCtrl\ExportFunc.h" 
#pragma comment(lib, "..\\DlgWithTreeCtrl\\release\\DlgWithTreeCtrl.lib") 

void main(void) 

  ShowTreeDlg(NULL); 


[5]編譯連接,運行。運行結果是一個控制台窗口出現,然后一個對話框一閃就不見了。呵呵,對話框確實被顯示了,只是控制台程序瞬間就結束了,所以對話框就被自動銷毀了。可以在main函數中加上一句: 
MessageBox(NULL,"stop",NULL,MB_OK); 
讓控制台程序不那么快就消亡。 

最終結果如下圖:

 

四. 還需要繼續考慮的問題 
我在DlgWithTreeCtrl.dll中做的那個輸出函數,new了一個對話框的對象,程序中從始至終都沒有釋放所new的(堆)空間。會不會內存泄露?這不是一個小問題。換個說法:MFC的窗口對象是怎樣從系統中清除的呢?要弄清楚這個問題,必須先搞清楚窗口對象的成分。 

一個MFC窗口對象包括兩方面的內容:一是窗口對象封裝的窗口,即存放在m_hWnd成員中的HWND(窗口句柄),二是窗口對象本身是一個C++對象。要刪除一個MFC窗口對象,應該先刪除窗口對象封裝的窗口,然后刪除窗口對象本身。也就是窗口的C++對象的生存期要比窗口長。 

[1]窗口的刪除 
刪除窗口最直接方法是調用CWnd:: DestroyWindow或全局API函數:: DestroyWindow,前者封裝了后者的功能。前者不僅會調用后者,而且會使成員m_hWnd保存的HWND無效(NULL)。如果DestroyWindow刪除的是一個父窗口或擁有者窗口,則該函數會先自動刪除所有的子窗口或被擁有者,然后再刪除父窗口或擁有者。在一般情況下,在程序中不必直接調用DestroyWindow來刪除窗口,因為MFC會自動調用DestroyWindow來刪除窗口。例如,當用戶退出應用程序時,會產生WM_CLOSE消息,該消息會導致MFC自動調用CWnd:: DestroyWindow來刪除主框架窗口。當用戶在對話框內按了OK或Cancel按鈕時,可調用CWnd:: DestroyWindow來刪除對話框及其控件。 

[2]窗口對象的刪除 
窗口對象本身的刪除則根據對象創建方式的不同,分為兩種情況。在MFC編程中,會使用大量的窗口對象,有些窗口對象以變量的形式嵌入在別的對象內或以局部變量的形式創建在堆棧上,有些則用new操作符創建在堆中。 

對於一個以變量形式創建的窗口對象,程序員不必關心它的刪除問題,因為該對象的生命期總是有限的,若該對象是某個對象的成員變量,它會隨着父對象的消失而消失,若該對象是一個局部變量,那么它會在函數返回時被清除。 

對於一個在堆中動態創建的窗口對象,其生命期卻是任意長的。因為用new在堆中創建對象,就不能忘記用delete刪除對象。有些MFC窗口對象具有自動清除的功能。不需要程序員顯示的調用delete來刪除它。 
------------------------------------------------------------------------------------------------------ 
------------------------------------------------------------------------------------------------------ 
[3]對於我new的那個非模態的對話框,該如何銷毀呢? 
這個問題纏繞我好幾天,至今為止還是沒有解決的。經測試,發現上面來自網上的和來自MSDN的資料說的不正確。 

例如這句話:"用戶退出應用程序時,會產生WM_CLOSE消息,該消息會導致MFC自動調用CWnd:: DestroyWindow來刪除主框架窗口。" 

經過測試,當我點了對話框窗口的系統菜單上的"關閉"按鈕后,產生WM_CLOSE消息,而它並沒有調用CWnd:: DestroyWindow來銷毀窗口!!!!!! 

這個問題最終是在這里得到解決的,去這里看看: 
http://community.csdn.net/Expert/topic/4025/4025795.xml?temp=.989773

 

五. 開始制作帶有樹形列表控件的對話框了 
插語:回頭看了看那么長的篇幅,許多東西想整的再細致一點,實在有心無力了.也許用MFC做界面真的太繁瑣了。這可能也是許多人更熱衷於學VB,DELPHI,而逃避VC的原因吧。總之Windows給了人們一個直觀的界面,也浪費了無數程序員大好的青春,他們本來可以做更多有用的事,僅因為程序的門臉而廢腦筋。 

前面的問題都解決了,下面就一馬平川了。打開前面完成的DlgWithTreeCtrl項目。 
[1]切換到資源視圖,從工具箱中拖一個tree control到對話框面板上 

[2]試着隨便修改這個tree control的屬性,試了一遍就基本知道一個tree control有哪些風格。 

[3]在原對話框類CTreeDlg中添加一個控件變量m_treeCtrl,使用添加變量向導進行添加,這樣IDE會自動在類CTreeDlg的實現代碼中添加DDX語句,從而將控件對象與控件連接起來。 

[4]剩下的工作就是如何操縱這個tree control了。 
假如我們在UG下開發一套標准件庫,各零件模型都可以產生了,也就是一大堆DLL,每個DLL產生一種或幾種零件。我們需要一個窗體專門用來顯示整個標准件庫所有零件的信息,當在列表中選中一種零件時,就可以在UG中自動創建它。這樣,采用樹形列表是最為直觀的了。

 

六.與樹形列表相對應的數據結構的設計 
樹形列表中的條目是由根,分枝,葉構成的。葉結點中存儲零件信息,我們應該怎樣將信息寫到樹形列表中對應的條目里,這是使用樹形控件首先要考慮的問題,不然只有一個空殼界面是沒什么意義的。 

我想做一個數據結構,用它來存儲根,分枝和葉的信息,我們自然會想到它應該是一棵樹,可以采用C++編寫這樣一個樹的類,稱之為信息樹。如果我們在對話框類添加一個指向這樣一個信息樹對象的指針,那么就可以通過深度優先遍歷實現樹狀信息在樹形列表中的顯示了。 

[1]信息樹節點的設計 
//------------------------------ 
//信息樹的節點 
//------------------------------ 
typedef struct INFO_NODE_s 

int state; // 
char name[_STR_LENGTH]; //節點對應條目的名稱 

void *pObj; //指向要在樹列表控件中顯示其信息的對象 
void *pOtherObj ; //備用指針 

//每個節點都有一個ID鏈表,來表示它在樹中的位置 
//根結點第一個子節點就是鏈表的第一個單元 
CListTemplate<int> *pPath; 

//指向存儲子節點的鏈表對象的指針 
INFO_NODE_s * pParent; 
CListTemplate<INFO_NODE_s *> *pSubNodeList; 
}INFO_NODE_s,*INFO_NODE_p_s; 

[2]信息樹的類聲明 
/////////////////////////////////////////////////////////// 
//信息樹類 
////////////////////////////////////////////////////////// 
class CInfoTree 

protected: 
INFO_NODE_p_s m_pRoot; //根節點 
public: 
CInfoTree(void); 
~CInfoTree(void); 
//----------------------------------------------- 
//接口 
//----------------------------------------------- 
public: 
INFO_NODE_p_s GetRoot(); //取指向根節點的指針 

static INFO_NODE_p_s MallocNode(); //分配一個新的節點 

//用戶輸入結點名稱,ID字符串,指向用戶對象的指針來分配新結點 
static INFO_NODE_p_s MallocNode(char *name,char *nodeIdStr,void *pObj,void *pOtherObj); 

static void FreeNode(INFO_NODE_p_s p); //銷毀一個節點 

INFO_NODE_p_s Insert(INFO_NODE_p_s p); //向樹中插入節點 
INFO_NODE_p_s Delete(INFO_NODE_p_s p); //從樹中刪除節點 
};

 

七 利用樹形列表控件來顯示信息樹 
[1]深度優先遍歷樹 
為了在控件中顯示樹的信息,要寫一個遞歸函數來遍歷信息樹。 
//------------------------------------------------------------- 
// 功能:遍歷信息樹,在樹列表控件中顯示信息 
//------------------------------------------------------------- 
void CTreeDlg:FSTraverInfoTree(INFO_NODE_p_s p,HTREEITEM hItem) 

HTREEITEM h; 
if(p==NULL) 

return; 

//向對話框中的控件對象m_treeCtrl中添加各結點信息 
h = m_treeCtrl.InsertItem(p->name,0,0,hItem); 

INFO_NODE_p_s tempPtr; 
NODE_POSITION(INFO_NODE_p_s) iteratorPtr=p->pSubNodeList->GetHeadPosition(); 
while(iteratorPtr != NULL) 

tempPtr = p->pSubNodeList->GetNext([$iteratorPtr)] 
DFSTraverInfoTree(tempPtr,h); 



[2]修改原來的DLL 
由調用該DLL的應用程序傳入信息樹對象,在對話框的初始化函數中,將信息樹對象中包含的信息在樹狀列表控件中顯示出來。 

為了得到應用程序傳入的信息樹對象,需要在DLL中的對話框類中添加一個信息樹的成員變量,再將輸出函數修改為: 
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 
//功能:DLL的輸出函數,當其他應用程序加載該DLL后,調用這個函數,可以顯示該DLL內建的對話框 
//輸入參數1:HWND hMainWnd,對話框父窗口的句柄 
//輸入參數2:指向信息樹對象的指針,信息樹對象中封裝了要在控件中顯示的信息 
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 
void ShowTreeDlg(HWND hMainWnd,CInfoTree *pInfoTree) 

AFX_MANAGE_STATE(AfxGetStaticModuleState()); 
CTreeDlg *pTreeDlg=new CTreeDlg; 
pTreeDlg->m_pInfoTree = pInfoTree; 
CWnd * pMainWnd=CWnd:: FromHandle(hMainWnd); 
ASSERT(pMainWnd); 
BOOL retValue=pTreeDlg->Create(IDD_TREE_DLG, NULL); 
if(!retValue) 

AfxMessageBox("創建包含樹列表控件的對話框失敗了!"); 

pTreeDlg->ShowWindow(SW_SHOW); 

[3]在列表中顯示樹的信息 
BOOL CTreeDlg:: OnInitDialog() 

CDialog:: OnInitDialog(); 
INFO_NODE_p_s p = m_pInfoTree->GetRoot(); 
DFSTraverInfoTree(p,NULL); 

return TRUE; 
}

 

八。程序運行結果 
將用來測試該資源DLL的控制台程序作下改動: 
void main(void) 

  CInfoTree *pInfoTree=NULL; 
  pInfoTree= new CInfoTree; 

  INFO_NODE_p_s p=CInfoTree::MallocNode("0","0",NULL,NULL); 
  pInfoTree->Insert(p); 

  p=CInfoTree::MallocNode("0-1","0-1",NULL,NULL); 
  pInfoTree->Insert(p); 

  p=CInfoTree::MallocNode("0-2","0-2",NULL,NULL); 
  pInfoTree->Insert(p); 

  p=CInfoTree::MallocNode("0-1-1","0-1-1",NULL,NULL); 
  pInfoTree->Insert(p); 

  p=CInfoTree::MallocNode("0-1-2","0-1-2",NULL,NULL); 
  pInfoTree->Insert(p); 

  p=CInfoTree::MallocNode("0-1-3","0-1-3",NULL,NULL); 
  pInfoTree->Insert(p); 

  ShowTreeDlg(NULL,pInfoTree); 
  //加上一個消息對話框不讓主程序自動退出 
  MessageBox(NULL,"stop1",NULL,MB_OK); 



程序運行結果如下:

九。做個小小的總結 
大體把對話框資源DLL的制作和MFC的tree control的使用的學習過程記錄了下來。作為學習,而不是應用,所以只敘述了基本的思路,具體實現過程,還需自己動手的。 

在項目使用這個控件還是有幾個問題需要解決 
[1].控件界面的美化。譬如每個列表項上都可以貼上小圖標的。還有添加右鍵菜單之類的 
[2]交互修改列表中各項的名稱 
[3].資源DLL中的對話框能夠響應用戶交互的消息,但如何將消息處理的結果反饋給調用它的應用程序呢?比如,我從列表控件中刪除了一項,在資源DLL中也只能是將控件中顯示的項去掉了,該項所對應的數據對象還活動在內存中,該怎樣通知應用程序,讓它把數據對象銷毀


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM