• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            動態(tài)鏈接庫DLL實現(xiàn)了庫的共享,體現(xiàn)了代碼重用的思想。我們可以把廣泛的、具有共性的、能夠多次被利用的函數(shù)和類定義在庫中。這樣,在再次使用這些函數(shù)和類的時候,就不再需要重新添加與這些函數(shù)和類相關的代碼。具有共性的問題大致有哪些呢?筆者歸納如下:

              (1)通用的算法

              圖像處理、視頻音頻解碼、壓縮與解壓縮、加密與解密通常采用某些特定的算法,這些算法較固定且在這類程序中往往經(jīng)常被使用。

              (2)純資源DLL

              我們可以從DLL中獲取資源,對于一個支持多種語言的應用程序而言,我們可以判斷操作系統(tǒng)的語言,并自動為應用程序加載與OS對應的語言。這是多語言支持應用程序的一般做法。

              (3)通信控制DLL

              串口、網(wǎng)口的通信控制函數(shù)如果由DLL提供則可以使應用程序輕松不少。在工業(yè)控制、modem程序甚至socket通信中,經(jīng)常使用通信控制DLL。

              本節(jié)將給出DLL的三個典型應用實例。

              7.1 算法DLL

              我們直接用讀者的一個提問作為例子。

              宋寶華先生,您好!

              我在dev.yesky.com上看到你連載的《VC++動態(tài)鏈接庫編程》,覺得非常好。我以前主要是用Delphi的,C/C++學過,對Win32和VCL比較熟悉,但是沒有接觸過VC++,對MFC很陌生。這段時間和一個同學合作做光學成像的計算機模擬,用到傅立葉變換,手里面有例程是VC++寫的。我們的界面是用Delphi開發(fā),需要將其傅立葉變換功能提出做一個DLL供Delphi調(diào)用。苦于不懂MFC,試了很多方法,都不成功,最后只得采用折衷方案,簡單修改一下程序,傳一個參數(shù)進去,當作exe來調(diào)用,才沒有耽擱后續(xù)進程。

              ……

              謝謝!

                    致

              禮!

                     某某

              學習過較高級別數(shù)學(概率統(tǒng)計與隨機過程)、信號與線性系統(tǒng)及數(shù)字信號處理的讀者應該知道,傅立葉變換是一種在信號分析中常用的算法,用于時域和頻域的相互轉(zhuǎn)換。FFT變換算法通用而有共性,我們適宜把它集成在一個DLL中。

              隨后,這位讀者提供了這樣的一個函數(shù):

            /* 函數(shù)名稱:FFT()
            * 參數(shù):
            * complex<double> * TD - 指向時域數(shù)組的指針
            * complex<double> * FD - 指向頻域數(shù)組的指針
            * r -2的冪數(shù),即迭代次數(shù)
            * 返回值: 無。
            * 說明:該函數(shù)用來實現(xiàn)快速傅立葉變換
            */

            void FFT(complex<double> * TD, complex<double> * FD, int r)
            {
             LONG count; // 傅立葉變換點數(shù)
             int i,j,k; // 循環(huán)變量
             int bfsize,p; // 中間變量
             double angle; // 角度
             complex<double> *W,*X1,*X2,*X;
             count = 1 << r; //傅立葉變換點數(shù)

             // 分配運算所需存儲器

             W = new complex<double>[count / 2];
             X1 = new complex<double>[count];
             X2 = new complex<double>[count];

             // 計算加權系數(shù)

             for(i = 0; i < count / 2; i++)
             {
              angle = -i * PI * 2 / count;
              W[i] = complex<double> (cos(angle), sin(angle));
             }

             // 將時域點寫入X1

             memcpy(X1, TD, sizeof(complex<double>) * count);

             // 采用蝶形算法進行快速傅立葉變換

             for(k = 0; k < r; k++)
             {
              for(j = 0; j < 1 << k; j++)
              {
               bfsize = 1 << (r-k);
               for(i = 0; i < bfsize / 2; i++)
               {
                p = j * bfsize;
                X2[i + p] = X1[i + p] + X1[i + p + bfsize / 2];
                X2[i + p + bfsize / 2] = (X1[i + p] - X1[i + p + bfsize / 2]) * W[i * (1<<k)];
               }
              }
              X = X1;
              X1 = X2;
              X2 = X;
             }

             // 重新排序

             for(j = 0; j < count; j++)
             {
              p = 0;
              for(i = 0; i < r; i++)
              {
               if (j&(1<<i))
               {
                p+=1<<(r-i-1);
               }
              }
              FD[j]=X1[p];
             }

             // 釋放內(nèi)存

             delete W;
             delete X1;
             delete X2;
            }

              既然有了FFT這個函數(shù),我們要把它做在DLL中,作為DLL的一個接口將是十分簡單的,其步驟如下:

              (1)利用MFC向?qū)Ы⒁粋€非MFC DLL;

              (2)在工程中添加fft.h和fft.cpp兩個文件;

              fft.h的源代碼為:

            #ifndef FFT_H
            #define FFT_H

            #include <complex>

            using namespace std;
            extern "C" void __declspec(dllexport) __stdcall FFT(complex<double> * TD, complex<double> * FD, int r);

            #define PI 3.1415926
            #endif

            fft.cpp的源代碼為:

            /* 文件名:fft.cpp */

            #include "fft.h"
            void __stdcall FFT(complex<double> * TD, complex<double> * FD, int r)
            {
             …//讀者提供的函數(shù)代碼
            }

              在任何編程語言中使用Win32 API LoadLibrary都可以加載這個DLL,而使用GetProcAddress(hDll, "FFT")則可以獲得函數(shù)FFT的地址,讀者所提到的Delphi當然也不例外。

              這個DLL中有兩點需要注意:

              (1)使用extern "C"修飾函數(shù)聲明,否則,生成的DLL只能供C++調(diào)用;

              (2)使用__stdcall修飾函數(shù)聲明及定義,__stdcall是Windows API的函數(shù)調(diào)用方式。
            7.2純資源DLL

              我們在應用程序中產(chǎn)生如圖18所示的資源(對話框),單擊此處下載本工程


            圖18 中文對話框

              在與這個應用程序相同的工作區(qū)里利用MFC向?qū)Ы蓚€簡單的DLL,把應用工程中的資源全選后分別拷貝到ChineseDll和EngLishDll,在EnglishDll工程的資源文件中搜索下面的語句:

            /////////////////////////////////////////////////////////////////////////////

            // Chinese (P.R.C.) resources

            #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
            #ifdef _WIN32
            LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
            #pragma code_page(936)
            #endif //_WIN32

              將其改為:

            /////////////////////////////////////////////////////////////////////////////
            // English (U.S.) resources

            #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
            #ifdef _WIN32

            LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US

            #pragma code_page(1252)
            #endif //_WIN32

              并將其中所有的中文翻譯為英文。這個DLL為我們提供了如圖19所示的對話框資源。


            圖19英文對話框

              修改應用工程的InitInstance()函數(shù),在

            CResourceDllCallDlg dlg;
            m_pMainWnd = &dlg;
            int nResponse = dlg.DoModal();

              之前(即對話框顯示之前)添加如下代碼:

            //獲取操作系統(tǒng)的語言

            WORD wLangPID = PRIMARYLANGID( GetSystemDefaultLangID() );
            if( LANG_CHINESE == wLangPID )
            {
             hLanguageDll = LoadLibrary( "ChineseDll.dll" ); //加載中文資源
            }
            else
            {
             hLanguageDll = LoadLibrary( "EnglishDll.dll" ); //加載英文資源
            }

            if( NULL == hLanguageDll )
            {
             AfxMessageBox( "Load DLL failure" );
             return FALSE;
            }
            AfxSetResourceHandle( hLanguageDll ); //設置當前的資源句柄

              這樣的應用程序?qū)⒕哂凶赃m應性質(zhì),在中文OS中顯示中文資源,在英文OS中則顯示英文資源。
            7.3通信控制DLL

              我們在這里舉一個串口通信類的例子。

              也許您需要了解一點串口通信的背景知識,其實串口到處都看得到,譬如PC機的COM口即為串行通訊口(簡稱串口)。如圖20,打開Windows的設備管理器,我們看到了COM口。

              在Windows系統(tǒng),需通過DCB(Device Control Block)對串口進行配置。利用Windows API GetCommState函數(shù)可以獲取串口當前配置;利用SetCommState函數(shù)則可以設置串口通訊的參數(shù)。

              串行通信通常按以下四步進行:

              (1)打開串口;

              (2)配置串口;

              (3)數(shù)據(jù)傳送;

              (4)關閉串口。


            圖20 PC的串口

              由此可見,我們需要給串口控制DLL提供如下四個接口函數(shù):

            //打開指定的串口,其參數(shù)port為端口號

            BOOL ComOpen(int port); //在這個函數(shù)里使用默認的參數(shù)設置串口

            //將打開的串口關閉

            void ComClose(int port);

            //將串口接收緩沖區(qū)中的數(shù)據(jù)放到buffer中

            int GetComData(char *buf, int buf_len);

            //將指定長度的數(shù)據(jù)發(fā)送到串口

            int SendDataToCom(LPBYTE buf,int buf_Len);

              下面給出了DLL接口的主要源代碼框架:

            //com.h:com類通信接口

            class AFX_EXT_CLASS com
            {
             public:
              ComOpen(int port)
              {
               …
              }
              int SendDataToCom(LPBYTE buf,int buf_Len)
              {
               …
              }
              int GetComData(char *buf, int buf_len)
              {
               …
              }
              void ComClose()
              {
               …
              }
             }

              我們編寫一控制臺程序來演示DLL的調(diào)用:

            #include <iostream>
            #include <exception>

            using namespace std;

            #include <windows.h>
            #include "com.h" //包含DLL中導出類的頭文件 int main(int argc, char *argv[])
            {
             try
             {
              char str[] = "com_class test";
              com com1;
              com1.ComOpen (1);
              for(int i=0; i<100; i++) //以同步方式寫com的buffer
              {
               Sleep(500);
               com1.SendDataToCom (str,strlen(str));
              }
              com1.ComClose ();
             }
             catch(exception &e)
             {
              cout << e.what() << endl;
             }
             return 0;
            }

              DLL的編寫與調(diào)用方法及主要應用皆已講完,在下一節(jié)里,我們將看到比較“高深”的主題――DLL木馬。曾幾何時,DLL木馬成為了病毒的一種十分重要的形式,是DLL的什么特性使得它能夠成為一種病毒?下一節(jié)我們將揭曉謎底。
            posted @ 2009-06-09 21:20 wrh 閱讀(304) | 評論 (0)編輯 收藏
            前文我們對非MFC DLL和MFC規(guī)則DLL進行了介紹,現(xiàn)在開始詳細分析DLL的最后一種類型――MFC擴展DLL。

              6.1概論

              MFC擴展DLL與MFC規(guī)則DLL的相同點在于在兩種DLL的內(nèi)部都可以使用MFC類庫,其不同點在于MFC擴展DLL與應用程序的接口可以是MFC的。MFC擴展DLL的含義在于它是MFC的擴展,其主要功能是實現(xiàn)從現(xiàn)有MFC庫類中派生出可重用的類。MFC擴展DLL使用MFC 動態(tài)鏈接庫版本,因此只有用共享MFC 版本生成的MFC 可執(zhí)行文件(應用程序或規(guī)則DLL)才能使用MFC擴展DLL。

              從前文可知,MFC規(guī)則DLL被MFC向?qū)ё詣犹砑恿艘粋€CWinApp的對象,而MFC擴展DLL則不包含該對象,它只是被自動添加了DllMain 函數(shù)。對于MFC擴展DLL,開發(fā)人員必須在DLL的DllMain函數(shù)中添加初始化和結束代碼。

              從下表我們可以看出三種DLL對DllMain入口函數(shù)的不同處理方式:

            DLL類型 入口函數(shù)
            非 MFC DLL 編程者提供DllMain函數(shù)
            MFC規(guī)則 DLL CWinApp對象的InitInstance 和 ExitInstance
            MFC擴展 DLL MFC DLL向?qū)蒁llMain 函數(shù)

              對于MFC擴展DLL,系統(tǒng)會自動在工程中添加如下表所示的宏,這些宏為DLL和應用程序的編寫提供了方便。像AFX_EXT_CLASS、AFX_EXT_API、AFX_EXT_DATA這樣的宏,在DLL和應用程序中將具有不同的定義,這取決于_AFXEXT宏是否被定義。這使得在DLL和應用程序中,使用統(tǒng)一的一個宏就可以表示出輸出和輸入的不同意思。在DLL中,表示輸出(因為_AFXEXT被定義,通常是在編譯器的標識參數(shù)中指定/D_AFXEXT);在應用程序中,則表示輸入(_AFXEXT沒有定義)。

            定義
            AFX_CLASS_IMPORT __declspec(dllexport)
            AFX_API_IMPORT __declspec(dllexport)
            AFX_DATA_IMPORT __declspec(dllexport)
            AFX_CLASS_EXPORT __declspec(dllexport)
            AFX_API_EXPORT __declspec(dllexport)
            AFX_DATA_EXPORT __declspec(dllexport)
            AFX_EXT_CLASS #ifdef _AFXEXT
             AFX_CLASS_EXPORT
            #else
             AFX_CLASS_IMPORT
            AFX_EXT_API #ifdef _AFXEXT
             AFX_API_EXPORT
            #else
             AFX_API_IMPORT
            AFX_EXT_DATA #ifdef _AFXEXT
             AFX_DATA_EXPORT
            #else
             AFX_DATA_IMPORT

              6.2 MFC擴展DLL導出MFC派生類

              在這個例子中,我們將產(chǎn)生一個名為“ExtDll”的MFC擴展DLL工程,在這個DLL中導出一個對話框類,這個對話框類派生自MFC類CDialog。

              使用MFC向?qū)蒑FC擴展DLL時,系統(tǒng)會自動添加如下代碼:

            static AFX_EXTENSION_MODULE ExtDllDLL = { NULL, NULL };
            extern "C" int APIENTRY

            DllMain( HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved )
            {
             // Remove this if you use lpReserved

             UNREFERENCED_PARAMETER( lpReserved );

             //說明:lpReserved是一個被系統(tǒng)所保留的參數(shù),對于隱式鏈接是一個非零值,對于顯式鏈接值是零

             if (dwReason == DLL_PROCESS_ATTACH)
             {
              TRACE0( "EXTDLL.DLL Initializing!\n" );
              // Extension DLL one-time initialization
              if ( !AfxInitExtensionModule( ExtDllDLL, hInstance ))
               return 0;
               // Insert this DLL into the resource chain
              new CDynLinkLibrary( ExtDllDLL );
             }
             else if (dwReason == DLL_PROCESS_DETACH)
             {
              TRACE0( "EXTDLL.DLL Terminating!\n" );
              // Terminate the library before destructors are called
              AfxTermExtensionModule( ExtDllDLL );
             }
             return 1; // ok
            }

              這一段代碼含義晦澀,我們需要對其進行解讀:

              (1)上述代碼完成MFC擴展DLL的初始化和終止處理;

              (2)初始化期間所創(chuàng)建的 CDynLinkLibrary 對象使MFC擴展 DLL 可以將 DLL中的CRuntimeClass 對象或資源導出到應用程序;

              (3)AfxInitExtensionModule函數(shù)捕獲模塊的CRuntimeClass 結構和在創(chuàng)建 CDynLinkLibrary 對象時使用的對象工廠(COleObjectFactory 對象);

              (4)AfxTermExtensionModule函數(shù)使 MFC 得以在每個進程與擴展 DLL 分離時(進程退出或使用AfxFreeLibrary卸載DLL時)清除擴展 DLL;

              (5)第一條語句static AFX_EXTENSION_MODULE ExtDllDLL = { NULL, NULL };定義了一個AFX_EXTENSION_MODULE類的靜態(tài)全局對象,AFX_EXTENSION_MODULE的定義如下:

            struct AFX_EXTENSION_MODULE
            {
             BOOL bInitialized;
             HMODULE hModule;
             HMODULE hResource;
             CRuntimeClass* pFirstSharedClass;
             COleObjectFactory* pFirstSharedFactory;
            };

              由AFX_EXTENSION_MODULE的定義我們可以更好的理解(2)、(3)、(4)點。

              在資源編輯器中添加一個如圖15所示的對話框,并使用MFC類向?qū)槠涮砑右粋€對應的類CExtDialog,系統(tǒng)自動添加了ExtDialog.h和ExtDialog.cpp兩個頭文件。


            圖15 MFC擴展DLL中的對話框

              修改ExtDialog.h中CExtDialog類的聲明為:

            class AFX_EXT_CLASS CExtDialog : public CDialog
            {
             public:
              CExtDialog( CWnd* pParent = NULL );
              enum { IDD = IDD_DLL_DIALOG };
             protected:
              virtual void DoDataExchange( CDataExchange* pDX );
              DECLARE_MESSAGE_MAP()
            };

              這其中最主要的改變是我們在class AFX_EXT_CLASS CExtDialog語句中添加了“AFX_EXT_CLASS”宏,則使得DLL中的CExtDialog類被導出。
            6.3 MFC擴展DLL的加載

              6.3.1 隱式加載

              我們在6.2工程所在的工作區(qū)中添加一個LoadExtDllDlg工程,用于演示MFC擴展DLL的加載。在LoadExtDllDlg工程中添加一個如圖16所示的對話框,這個對話框上包括一個“調(diào)用DLL”按鈕。


            圖16 MFC擴展DLL調(diào)用工程中的對話框

              在與圖16對應對話框類實現(xiàn)文件的頭部添加:

            // LoadExtDllDlg.cpp : implementation file
            //

            #include "..\ExtDialog.h"
            #pragma comment( lib, "ExtDll.lib" )

            而“調(diào)用DLL”按鈕的單擊事件的消息處理函數(shù)為:

            void CLoadExtDllDlg::OnDllcallButton()
            {
             CExtDialog extDialog;
             extDialog.DoModal();
            }

              當我們單擊“調(diào)用DLL”的時候,彈出了如圖15的對話框。

              為提供給用戶隱式加載(MFC擴展DLL一般使用隱式加載,具體原因見下節(jié)),MFC擴展DLL需要提供三個文件:

              (1)描述DLL中擴展類的頭文件;

              (2)與動態(tài)鏈接庫對應的.LIB文件;

              (3)動態(tài)鏈接庫.DLL文件本身。

              有了這三個文件,應用程序的開發(fā)者才可充分利用MFC擴展DLL。

              6.3.2 顯示加載

              顯示加載MFC擴展DLL應使用MFC全局函數(shù)AfxLoadLibrary而不是WIN32 API中的LoadLibrary。AfxLoadLibrary 最終也調(diào)用了 LoadLibrary這個API,但是在調(diào)用之前進行了線程同步的處理。

              AfxLoadLibrary 的函數(shù)原型與 LoadLibrary完全相同,為:

            HINSTANCE AFXAPI AfxLoadLibrary( LPCTSTR lpszModuleName );

              與之相對應的是,MFC 應用程序應使用AfxFreeLibrary 而非FreeLibrary 卸載MFC擴展DLL。AfxFreeLibrary的函數(shù)原型也與 FreeLibrary完全相同,為:

            BOOL AFXAPI AfxFreeLibrary( HINSTANCE hInstLib );

              如果我們把上例中的“調(diào)用DLL”按鈕單擊事件的消息處理函數(shù)改為:

            void CLoadExtDllDlg::OnDllcallButton()
            {
             HINSTANCE hDll = AfxLoadLibrary( "ExtDll.dll" );
             if(NULL == hDll)
             {
              AfxMessageBox( "MFC擴展DLL動態(tài)加載失敗" );
              return;
             }

             CExtDialog extDialog;
             extDialog.DoModal();
             AfxFreeLibrary(hDll);
            }

              則工程會出現(xiàn)link錯誤:

            LoadExtDllDlg.obj : error LNK2001: unresolved external symbol "__declspec(dllimport) public: virtual __thiscall CExtDialog::~CExtDialog(void)" (__imp_??1CExtDialog@@UAE@XZ)

            LoadExtDllDlg.obj : error LNK2001: unresolved external symbol "__declspec(dllimport) public: __thiscall CExtDialog::CExtDialog(class CWnd *)" (__imp_??0CExtDialog@@QAE@PAVCWnd@@@Z)

              提示CExtDialog的構造函數(shù)和析構函數(shù)均無法找到!是的,對于派生MFC類的MFC擴展DLL,當我們要在應用程序中使用DLL中定義的派生類時,我們不宜使用動態(tài)加載DLL的方法。

              6.4 MFC擴展DLL加載MFC擴展DLL

              我們可以在MFC擴展DLL中再次使用MFC擴展DLL,但是,由于在兩個DLL中對于AFX_EXT_CLASS、AFX_EXT_API、AFX_EXT_DATA宏的定義都是輸出,這會導致調(diào)用的時候出現(xiàn)問題。

              我們將會在調(diào)用MFC擴展DLL的DLL中看到link錯誤:

            error LNK2001: unresolved external symbol ….......

              因此,在調(diào)用MFC擴展DLL的MFC擴展DLL中,在包含被調(diào)用DLL的頭文件之前,需要臨時重新定義AFX_EXT_CLASS的值。下面的例子顯示了如何實現(xiàn):

            //臨時改變宏的含義“輸出”為“輸入”

            #undef AFX_EXT_CLASS
            #undef AFX_EXT_API
            #undef AFX_EXT_DATA
            #define AFX_EXT_CLASS AFX_CLASS_IMPORT
            #define AFX_EXT_API AFX_API_IMPORT
            #define AFX_EXT_DATA AFX_DATA_IMPORT

            //包含被調(diào)用MFC擴展DLL的頭文件

            #include "CalledDLL.h"

            //恢復宏的含義為輸出

            #undef AFX_EXT_CLASS
            #undef AFX_EXT_API
            #undef AFX_EXT_DATA
            #define AFX_EXT_CLASS AFX_CLASS_EXPORT
            #define AFX_EXT_API AFX_API_EXPORT
            #define AFX_EXT_DATA AFX_DATA_EXPORT
            6.5 MFC擴展DLL導出函數(shù)和變量

              MFC擴展DLL導出函數(shù)和變量的方法也十分簡單,下面我們給出一個簡單的例子。

              我們在MFC向?qū)傻腗FC擴展DLL工程中添加gobal.h和global.cpp兩個文件:

            //global.h:MFC擴展DLL導出變量和函數(shù)的聲明

            extern "C"
            {
             int AFX_EXT_DATA total; //導出變量
             int AFX_EXT_API add( int x, int y ); //導出函數(shù)
            }

            //global.cpp:MFC擴展DLL導出變量和函數(shù)定義

            #include "StdAfx.h"
            #include "global.h"

            extern "C" int total;
            int add(int x,int y)
            {
             total = x + y;
             return total;
            }

              編寫一個簡單的控制臺程序來調(diào)用這個MFC擴展DLL:

            #include <iostream.h>
            #include <afxver_.h>

            //AFX_EXT_DATA、AFX_EXT_API宏的定義在afxver_.h頭文件中

            #pragma comment ( lib, "ExtDll.lib" )
            #include "..\global.h"

            int main(int argc, char* argv[])
            {
             cout << add(2,3) << endl;
             cout << total;
             return 0;
            }

              運行程序,在控制臺上看到:

              5

              5

              另外,在Visual C++下建立MFC擴展DLL時,MFC DLL向?qū)詣由?def文件。因此,對于函數(shù)和變量,我們除了可以利用AFX_EXT_DATA、AFX_EXT_API宏導出以外,在.def文件中定義導出也是一個很好的辦法。與之相比,在.def文件中導出類卻較麻煩。通常需要從工程生成的.map文件中獲得類的所有成員函數(shù)被C++編譯器更改過的標識符,并且在.def文件中導出這些“奇怪”的標識符。因此,MFC擴展DLL通常以AFX_EXT_CLASS宏直接聲明導出類。

              6.6 MFC擴展DLL的應用

              上述各小節(jié)所舉MFC擴展DLL的例子均只是為了說明某方面的問題,沒有真實地體現(xiàn)“MFC擴展” 的內(nèi)涵,譬如6.2派生自CDialog的類也不具備比CDialog更強的功能。MFC擴展DLL的真實內(nèi)涵體現(xiàn)在它提供的類雖然派生自MFC類,但是提供了比MFC類更強大的功能、更豐富的接口。下面我們來看一個具體的例子(單擊此處下載本工程)。

              我們知道static控件所對應的CStatic類不具備設置背景和文本顏色的接口,這使得我們不能在對話框或其它用戶界面上自由靈活地修改static控件的顏色風格,因此我們需要一個提供了SetBackColor和SetTextColor接口的CStatic派生類CMultiColorStatic。
             
              這個類的聲明如下:

            class AFX_EXT_CLASS CMultiColorStatic : public CStatic
            {
             // Construction

             public:
              CMultiColorStatic();
              virtual ~CMultiColorStatic();
              // Attributes
             protected:
              CString m_strCaption;
              COLORREF m_BackColor;
              COLORREF m_TextColor;
              // Operations
             public:
              void SetTextColor( COLORREF TextColor );
              void SetBackColor( COLORREF BackColor );
              void SetCaption( CString strCaption );

              // Generated message map functions
             protected:
              afx_msg void OnPaint();
              DECLARE_MESSAGE_MAP()
            };

              在這個類的實現(xiàn)文件中,我們需要為它提供WM_PAINT消息的處理函數(shù)(這是因為顏色的設置依賴于WM_PAINT消息):

            BEGIN_MESSAGE_MAP(CMultiColorStatic, CStatic)

            //{{AFX_MSG_MAP(CMultiColorStatic)
             ON_WM_PAINT() //為這個類定義WM_PAINT消息處理函數(shù)
            //}}AFX_MSG_MAP
            END_MESSAGE_MAP()

              下面是這個類中的重要成員函數(shù):

            //為CMultiColorStatic類添加“設置文本顏色”接口

            void CMultiColorStatic::SetTextColor( COLORREF TextColor )
            {
             m_TextColor = TextColor; //設置文字顏色
            }

            //為CMultiColorStatic類添加“設置背景顏色”接口

            void CMultiColorStatic::SetBackColor( COLORREF BackColor )
            {
             m_BackColor = BackColor; //設置背景顏色
            }

            //為CMultiColorStatic類添加“設置標題”接口

            void CMultiColorStatic::SetCaption( CString strCaption )
            {
             m_strCaption = strCaption;
            }

            //重畫Static,顏色和標題的設置都依賴于這個函數(shù)

            void CMultiColorStatic::OnPaint()
            {
             CPaintDC dc(this); // device context for painting
             CRect rect;
             GetClientRect( &rect );
             dc.SetBkColor( m_BackColor );
             dc.SetBkMode( TRANSPARENT );
             CFont *pFont = GetParent()->GetFont();//得到父窗體的字體
             CFont *pOldFont;
             pOldFont = dc.SelectObject( pFont );//選用父窗體的字體
             dc.SetTextColor( m_TextColor );//設置文本顏色
             dc.DrawText( m_strCaption, &rect, DT_CENTER );//文本在Static中央
             dc.SelectObject( pOldFont );
            }

              為了驗證CMultiColorStatic類,我們制作一個基于對話框的應用程序,它包含一個如圖17所示的對話框。該對話框上包括一個static控件和三個按鈕,這三個按鈕可分別把static控件設置為“紅色”、“藍色”和“綠色”。


            圖17 擴展的CStatic類調(diào)用演示

              下面看看應如何編寫與這個對話框?qū)念悺?br>
              包含這種Static的對話框類的聲明如下:

            #include "..\MultiColorStatic.h"
            #pragma comment ( lib, "ColorStatic.lib" )

            // CCallDllDlg dialog

            class CCallDllDlg : public CDialog
            {
             public:
              CCallDllDlg(CWnd* pParent = NULL); // standard constructor
              enum { IDD = IDD_CALLDLL_DIALOG };
              CMultiColorStatic m_colorstatic; //包含一個CMultiColorStatic的實例
             protected:
              virtual void DoDataExchange(CDataExchange* pDX);//DDX/DDV support
              HICON m_hIcon;

             // Generated message map functions
             //{{AFX_MSG(CCallDllDlg)

             virtual BOOL OnInitDialog();
             afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
             afx_msg void OnPaint();
             afx_msg HCURSOR OnQueryDragIcon();
             afx_msg void OnRedButton();
             afx_msg void OnBlueButton();
             afx_msg void OnGreenButton();
            //}}AFX_MSG
            DECLARE_MESSAGE_MAP()
            };

              下面是這個類中與使用CMultiColorStatic相關的主要成員函數(shù):

            void CCallDllDlg::DoDataExchange(CDataExchange* pDX)
            {
             CDialog::DoDataExchange(pDX);
             //{{AFX_DATA_MAP(CCallDllDlg)
              DDX_Control(pDX, IDC_COLOR_STATIC, m_colorstatic);
             //使m_colorstatic與IDC_COLOR_STATIC控件關聯(lián)
             //}}AFX_DATA_MAP
            }

            BOOL CCallDllDlg::OnInitDialog()
            {
             …
             // TODO: Add extra initialization here

             // 初始static控件的顯示

             m_colorstatic.SetCaption("最開始為黑色");
             m_colorstatic.SetTextColor(RGB(0,0,0));
             return TRUE; // return TRUE unless you set the focus to a control
            }

            //設置static控件文本顏色為紅色

            void CCallDllDlg::OnRedButton()
            {
             m_colorstatic.SetCaption( "改變?yōu)榧t色" );
             m_colorstatic.SetTextColor( RGB( 255, 0, 0 ) );
             Invalidate( TRUE ); //導致發(fā)出WM_PAINT消息
            }

            //設置static控件文本顏色為藍色

            void CCallDllDlg::OnBlueButton()
            {
             m_colorstatic.SetCaption( "改變?yōu)樗{色" );
             m_colorstatic.SetTextColor( RGB( 0, 0, 255 ) );
             Invalidate( TRUE ); //導致發(fā)出WM_PAINT消息
            }

            //設置static控件文本顏色為綠色

            void CCallDllDlg::OnGreenButton()
            {
             m_colorstatic.SetCaption( "改變?yōu)榫G色" );
             m_colorstatic.SetTextColor( RGB(0,255,0) );
             Invalidate( TRUE ); //導致發(fā)出WM_PAINT消息
            }

              至此,我們已經(jīng)講解完成了所有類型的動態(tài)鏈接庫,即非MFC DLL、MFC規(guī)則DLL和MFC擴展DLL。下一節(jié)將給出DLL的三個工程實例,與讀者朋友們共同體會DLL的應用范圍和使用方法。
            posted @ 2009-06-09 21:17 wrh 閱讀(752) | 評論 (0)編輯 收藏
            第4節(jié)我們對非MFC DLL進行了介紹,這一節(jié)將詳細地講述MFC規(guī)則DLL的創(chuàng)建與使用技巧。

              另外,自從本文開始連載后,收到了一些讀者的e-mail。有的讀者提出了一些問題,筆者將在本文的最后一次連載中選取其中的典型問題進行解答。由于時間的關系,對于讀者朋友的來信,筆者暫時不能一一回復,還望海涵!由于筆者的水平有限,文中難免有錯誤和紕漏,也熱誠歡迎讀者朋友不吝指正!

              5. MFC規(guī)則DLL

              5.1 概述

              MFC規(guī)則DLL的概念體現(xiàn)在兩方面:

              (1) 它是MFC的
              
              “是MFC的”意味著可以在這種DLL的內(nèi)部使用MFC;

              (2) 它是規(guī)則的

              “是規(guī)則的”意味著它不同于MFC擴展DLL,在MFC規(guī)則DLL的內(nèi)部雖然可以使用MFC,但是其與應用程序的接口不能是MFC。而MFC擴展DLL與應用程序的接口可以是MFC,可以從MFC擴展DLL中導出一個MFC類的派生類。

              Regular DLL能夠被所有支持DLL技術的語言所編寫的應用程序調(diào)用,當然也包括使用MFC的應用程序。在這種動態(tài)連接庫中,包含一個從CWinApp繼承下來的類,DllMain函數(shù)則由MFC自動提供。

              Regular DLL分為兩類:

              (1)靜態(tài)鏈接到MFC 的規(guī)則DLL

              靜態(tài)鏈接到MFC的規(guī)則DLL與MFC庫(包括MFC擴展 DLL)靜態(tài)鏈接,將MFC庫的代碼直接生成在.dll文件中。在調(diào)用這種DLL的接口時,MFC使用DLL的資源。因此,在靜態(tài)鏈接到MFC 的規(guī)則DLL中不需要進行模塊狀態(tài)的切換。

              使用這種方法生成的規(guī)則DLL其程序較大,也可能包含重復的代碼。

              (2)動態(tài)鏈接到MFC 的規(guī)則DLL

              動態(tài)鏈接到MFC 的規(guī)則DLL 可以和使用它的可執(zhí)行文件同時動態(tài)鏈接到 MFC DLL 和任何MFC擴展 DLL。在使用了MFC共享庫的時候,默認情況下,MFC使用主應用程序的資源句柄來加載資源模板。這樣,當DLL和應用程序中存在相同ID的資源時(即所謂的資源重復問題),系統(tǒng)可能不能獲得正確的資源。因此,對于共享MFC DLL的規(guī)則DLL,我們必須進行模塊切換以使得MFC能夠找到正確的資源模板。

              我們可以在Visual C++中設置MFC規(guī)則DLL是靜態(tài)鏈接到MFC DLL還是動態(tài)鏈接到MFC DLL。如圖8,依次選擇Visual C++的project -> Settings -> General菜單或選項,在Microsoft Foundation Classes中進行設置。


            圖8 設置動態(tài)/靜態(tài)鏈接MFC DLL

              5.2 MFC規(guī)則DLL的創(chuàng)建

              我們來一步步講述使用MFC向?qū)?chuàng)建MFC規(guī)則DLL的過程,首先新建一個project,如圖9,選擇project的類型為MFC AppWizard(dll)。點擊OK進入如圖10所示的對話框。


            圖9 MFC DLL工程的創(chuàng)建


            圖10所示對話框中的1區(qū)選擇MFC DLL的類別。

              2區(qū)選擇是否支持automation(自動化)技術, automation 允許用戶在一個應用程序中操縱另外一個應用程序或組件。例如,我們可以在應用程序中利用 Microsoft Word 或Microsoft Excel的工具,而這種使用對用戶而言是透明的。自動化技術可以大大簡化和加快應用程序的開發(fā)。

              3區(qū)選擇是否支持Windows Sockets,當選擇此項目時,應用程序能在 TCP/IP 網(wǎng)絡上進行通信。 CWinApp派生類的InitInstance成員函數(shù)會初始化通訊端的支持,同時工程中的StdAfx.h文件會自動include <AfxSock.h>頭文件。

              添加socket通訊支持后的InitInstance成員函數(shù)如下:

            BOOL CRegularDllSocketApp::InitInstance()
            {
             if (!AfxSocketInit())
             {
              AfxMessageBox(IDP_SOCKETS_INIT_FAILED);
              return FALSE;
             }
             return TRUE;
            }

              4區(qū)選擇是否由MFC向?qū)ё詣釉谠创a中添加注釋,一般我們選擇“Yes,please”。


            圖10 MFC DLL的創(chuàng)建選項

            5.3 一個簡單的MFC規(guī)則DLL

              這個DLL的例子(屬于靜態(tài)鏈接到MFC 的規(guī)則DLL)中提供了一個如圖11所示的對話框。


            圖11 MFC規(guī)則DLL例子

              在DLL中添加對話框的方式與在MFC應用程序中是一樣的。 在圖11所示DLL中的對話框的Hello按鈕上點擊時將MessageBox一個“Hello,pconline的網(wǎng)友”對話框,下面是相關的文件及源代碼,其中刪除了MFC向?qū)ё詣由傻慕^大多數(shù)注釋(下載本工程):

              第一組文件:CWinApp繼承類的聲明與實現(xiàn)

            // RegularDll.h : main header file for the REGULARDLL DLL

            #if !defined(AFX_REGULARDLL_H__3E9CB22B_588B_4388_B778_B3416ADB79B3__INCLUDED_)
            #define AFX_REGULARDLL_H__3E9CB22B_588B_4388_B778_B3416ADB79B3__INCLUDED_

            #if _MSC_VER > 1000
            #pragma once
            #endif // _MSC_VER > 1000

            #ifndef __AFXWIN_H__
            #error include ’stdafx.h’ before including this file for PCH
            #endif
            #include "resource.h" // main symbols

            class CRegularDllApp : public CWinApp
            {
             public:
              CRegularDllApp();
              DECLARE_MESSAGE_MAP()
            };

            #endif

            // RegularDll.cpp : Defines the initialization routines for the DLL.

            #include "stdafx.h"
            #include "RegularDll.h"

            #ifdef _DEBUG
            #define new DEBUG_NEW
            #undef THIS_FILE
            static char THIS_FILE[] = __FILE__;
            #endif

            BEGIN_MESSAGE_MAP(CRegularDllApp, CWinApp)
            END_MESSAGE_MAP()

            /////////////////////////////////////////////////////////////////////////////

            // CRegularDllApp construction

            CRegularDllApp::CRegularDllApp()
            {
            }

            /////////////////////////////////////////////////////////////////////////////
            // The one and only CRegularDllApp object

            CRegularDllApp theApp;

              分析:

              在這一組文件中定義了一個繼承自CWinApp的類CRegularDllApp,并同時定義了其的一個實例theApp。乍一看,您會以為它是一個MFC應用程序,因為MFC應用程序也包含這樣的在工程名后添加“App”組成類名的類(并繼承自CWinApp類),也定義了這個類的一個全局實例theApp。

              我們知道,在MFC應用程序中CWinApp取代了SDK程序中WinMain的地位,SDK程序WinMain所完成的工作由CWinApp的三個函數(shù)完成:

            virtual BOOL InitApplication( );

            virtual BOOL InitInstance( );

            virtual BOOL Run( ); //傳說中MFC程序的“活水源頭”

              但是MFC規(guī)則DLL并不是MFC應用程序,它所繼承自CWinApp的類不包含消息循環(huán)。這是因為,MFC規(guī)則DLL不包含CWinApp::Run機制,主消息泵仍然由應用程序擁有。如果DLL 生成無模式對話框或有自己的主框架窗口,則應用程序的主消息泵必須調(diào)用從DLL 導出的函數(shù)來調(diào)用PreTranslateMessage成員函數(shù)。

              另外,MFC規(guī)則DLL與MFC 應用程序中一樣,需要將所有 DLL中元素的初始化放到InitInstance 成員函數(shù)中。

              第二組文件 自定義對話框類聲明及實現(xiàn)

            #if !defined(AFX_DLLDIALOG_H__CEA4C6AF_245D_48A6_B11A_A5521EAD7C4E__INCLUDED_)
            #define AFX_DLLDIALOG_H__CEA4C6AF_245D_48A6_B11A_A5521EAD7C4E__INCLUDED_

            #if _MSC_VER > 1000
            #pragma once
            #endif // _MSC_VER > 1000
            // DllDialog.h : header file
            /////////////////////////////////////////////////////////////////////////////
            // CDllDialog dialog

            class CDllDialog : public CDialog
            {
             // Construction
             public:
              CDllDialog(CWnd* pParent = NULL); // standard constructor
              enum { IDD = IDD_DLL_DIALOG };
             protected:
              virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
              // Implementation
             protected:
              afx_msg void OnHelloButton();
              DECLARE_MESSAGE_MAP()
            };
            #endif

            // DllDialog.cpp : implementation file

            #include "stdafx.h"
            #include "RegularDll.h"
            #include "DllDialog.h"
            #ifdef _DEBUG
            #define new DEBUG_NEW
            #undef THIS_FILE
            static char THIS_FILE[] = __FILE__;
            #endif

            /////////////////////////////////////////////////////////////////////////////
            // CDllDialog dialog

            CDllDialog::CDllDialog(CWnd* pParent /*=NULL*/)
            : CDialog(CDllDialog::IDD, pParent)
            {}

            void CDllDialog::DoDataExchange(CDataExchange* pDX)
            {
             CDialog::DoDataExchange(pDX);
            }

            BEGIN_MESSAGE_MAP(CDllDialog, CDialog)
             ON_BN_CLICKED(IDC_HELLO_BUTTON, OnHelloButton)
            END_MESSAGE_MAP()

            /////////////////////////////////////////////////////////////////////////////
            // CDllDialog message handlers

            void CDllDialog::OnHelloButton()
            {
             MessageBox("Hello,pconline的網(wǎng)友","pconline");
            }

              分析:

              這一部分的編程與一般的應用程序根本沒有什么不同,我們照樣可以利用MFC類向?qū)碜詣訛閷υ捒蛏系目丶砑邮录FC類向?qū)д諛訒深愃芆N_BN_CLICKED(IDC_HELLO_BUTTON, OnHelloButton)的消息映射宏。

              第三組文件 DLL中的資源文件

            //{{NO_DEPENDENCIES}}

            // Microsoft Developer Studio generated include file.

            // Used by RegularDll.rc

            //

            #define IDD_DLL_DIALOG 1000

            #define IDC_HELLO_BUTTON 1000

              分析:

              在MFC規(guī)則DLL中使用資源也與在MFC應用程序中使用資源沒有什么不同,我們照樣可以用Visual C++的資源編輯工具進行資源的添加、刪除和屬性的更改。

              第四組文件 MFC規(guī)則DLL接口函數(shù)

            #include "StdAfx.h"
            #include "DllDialog.h"

            extern "C" __declspec(dllexport) void ShowDlg(void)
            {
             CDllDialog dllDialog;
             dllDialog.DoModal();
            }

              分析:

              這個接口并不使用MFC,但是在其中卻可以調(diào)用MFC擴展類CdllDialog的函數(shù),這體現(xiàn)了“規(guī)則”的概類。

              與非MFC DLL完全相同,我們可以使用__declspec(dllexport)聲明或在.def中引出的方式導出MFC規(guī)則DLL中的接口。
            5.4 MFC規(guī)則DLL的調(diào)用

              筆者編寫了如圖12的對話框MFC程序(下載本工程)來調(diào)用5.3節(jié)的MFC規(guī)則DLL,在這個程序的對話框上點擊“調(diào)用DLL”按鈕時彈出5.3節(jié)MFC規(guī)則DLL中的對話框。


            圖12 MFC規(guī)則DLL的調(diào)用例子

              下面是“調(diào)用DLL”按鈕單擊事件的消息處理函數(shù):

            void CRegularDllCallDlg::OnCalldllButton()
            {
             typedef void (*lpFun)(void);
             HINSTANCE hDll; //DLL句柄
             hDll = LoadLibrary("RegularDll.dll");
             if (NULL==hDll)
             {
              MessageBox("DLL加載失敗");
             }

             lpFun addFun; //函數(shù)指針
             lpFun pShowDlg = (lpFun)GetProcAddress(hDll,"ShowDlg");
             if (NULL==pShowDlg)
             {
              MessageBox("DLL中函數(shù)尋找失敗");
             }
             pShowDlg();
            }

              上述例子中給出的是顯示調(diào)用的方式,可以看出,其調(diào)用方式與第4節(jié)中非MFC DLL的調(diào)用方式?jīng)]有什么不同。

              我們照樣可以在EXE程序中隱式調(diào)用MFC規(guī)則DLL,只需要將DLL工程生成的.lib文件和.dll文件拷入當前工程所在的目錄,并在RegularDllCallDlg.cpp文件(圖12所示對話框類的實現(xiàn)文件)的頂部添加:

            #pragma comment(lib,"RegularDll.lib")
            void ShowDlg(void);

              并將void CRegularDllCallDlg::OnCalldllButton() 改為:

            void CRegularDllCallDlg::OnCalldllButton()
            {
             ShowDlg();
            }

              5.5 共享MFC DLL的規(guī)則DLL的模塊切換

              應用程序進程本身及其調(diào)用的每個DLL模塊都具有一個全局唯一的HINSTANCE句柄,它們代表了DLL或EXE模塊在進程虛擬空間中的起始地址。進程本身的模塊句柄一般為0x400000,而DLL模塊的缺省句柄為0x10000000。如果程序同時加載了多個DLL,則每個DLL模塊都會有不同的HINSTANCE。應用程序在加載DLL時對其進行了重定位。

              共享MFC DLL(或MFC擴展DLL)的規(guī)則DLL涉及到HINSTANCE句柄問題,HINSTANCE句柄對于加載資源特別重要。EXE和DLL都有其自己的資源,而且這些資源的ID可能重復,應用程序需要通過資源模塊的切換來找到正確的資源。如果應用程序需要來自于DLL的資源,就應將資源模塊句柄指定為DLL的模塊句柄;如果需要EXE文件中包含的資源,就應將資源模塊句柄指定為EXE的模塊句柄。

              這次我們創(chuàng)建一個動態(tài)鏈接到MFC DLL的規(guī)則DLL(下載本工程),在其中包含如圖13的對話框。


            圖13 DLL中的對話框

              另外,在與這個DLL相同的工作區(qū)中生成一個基于對話框的MFC程序,其對話框與圖12完全一樣。但是在此工程中我們另外添加了一個如圖14的對話框。


            圖14 EXE中的對話框

              圖13和圖14中的對話框除了caption不同(以示區(qū)別)以外,其它的都相同。

              尤其值得特別注意,在DLL和EXE中我們對圖13和圖14的對話框使用了相同的資源ID=2000,在DLL和EXE工程的resource.h中分別有如下的宏:

            //DLL中對話框的ID

            #define IDD_DLL_DIALOG 2000

            //EXE中對話框的ID

            #define IDD_EXE_DIALOG 2000

              與5.3節(jié)靜態(tài)鏈接MFC DLL的規(guī)則DLL相同,我們還是在規(guī)則DLL中定義接口函數(shù)ShowDlg,原型如下:

            #include "StdAfx.h"
            #include "SharedDll.h"

            void ShowDlg(void)
            {
             CDialog dlg(IDD_DLL_DIALOG); //打開ID為2000的對話框
             dlg.DoModal();
            }

              而為應用工程主對話框的“調(diào)用DLL”的單擊事件添加如下消息處理函數(shù):

            void CSharedDllCallDlg::OnCalldllButton()
            {
             ShowDlg();
            }

              我們以為單擊“調(diào)用DLL”會彈出如圖13所示DLL中的對話框,可是可怕的事情發(fā)生了,我們看到是圖14所示EXE中的對話框!
            驚訝?

              產(chǎn)生這個問題的根源在于應用程序與MFC規(guī)則DLL共享MFC DLL(或MFC擴展DLL)的程序總是默認使用EXE的資源,我們必須進行資源模塊句柄的切換,其實現(xiàn)方法有三:

              方法一 在DLL接口函數(shù)中使用:

            AFX_MANAGE_STATE(AfxGetStaticModuleState());

              我們將DLL中的接口函數(shù)ShowDlg改為:

            void ShowDlg(void)
            {
             //方法1:在函數(shù)開始處變更,在函數(shù)結束時恢復
             //將AFX_MANAGE_STATE(AfxGetStaticModuleState());作為接口函數(shù)的第一//條語句進行模塊狀態(tài)切換

             AFX_MANAGE_STATE(AfxGetStaticModuleState());
             CDialog dlg(IDD_DLL_DIALOG);//打開ID為2000的對話框
             dlg.DoModal();
            }

              這次我們再點擊EXE程序中的“調(diào)用DLL”按鈕,彈出的是DLL中的如圖13的對話框!嘿嘿,彈出了正確的對話框資源。

              AfxGetStaticModuleState是一個函數(shù),其原型為:

            AFX_MODULE_STATE* AFXAPI AfxGetStaticModuleState( );

              該函數(shù)的功能是在棧上(這意味著其作用域是局部的)創(chuàng)建一個AFX_MODULE_STATE類(模塊全局數(shù)據(jù)也就是模塊狀態(tài))的實例,對其進行設置,并將其指針pModuleState返回。

              AFX_MODULE_STATE類的原型如下:

            // AFX_MODULE_STATE (global data for a module)

            class AFX_MODULE_STATE : public CNoTrackObject
            {
             public:
              #ifdef _AFXDLL
               AFX_MODULE_STATE(BOOL bDLL, WNDPROC pfnAfxWndProc, DWORD dwVersion);
               AFX_MODULE_STATE(BOOL bDLL, WNDPROC pfnAfxWndProc, DWORD dwVersion,BOOL bSystem);
              #else
               AFX_MODULE_STATE(BOOL bDLL);
              #endif
              ~AFX_MODULE_STATE();

              CWinApp* m_pCurrentWinApp;
              HINSTANCE m_hCurrentInstanceHandle;
              HINSTANCE m_hCurrentResourceHandle;
              LPCTSTR m_lpszCurrentAppName;

              … //省略后面的部分
            }

              AFX_MODULE_STATE類利用其構造函數(shù)和析構函數(shù)進行存儲模塊狀態(tài)現(xiàn)場及恢復現(xiàn)場的工作,類似匯編中call指令對pc指針和sp寄存器的保存與恢復、中斷服務程序的中斷現(xiàn)場壓棧與恢復以及操作系統(tǒng)線程調(diào)度的任務控制塊保存與恢復。

              許多看似不著邊際的知識點居然有驚人的相似!

              AFX_MANAGE_STATE是一個宏,其原型為:

            AFX_MANAGE_STATE( AFX_MODULE_STATE* pModuleState )

              該宏用于將pModuleState設置為當前的有效模塊狀態(tài)。當離開該宏的作用域時(也就離開了pModuleState所指向棧上對象的作用域),先前的模塊狀態(tài)將由AFX_MODULE_STATE的析構函數(shù)恢復。

              方法二 在DLL接口函數(shù)中使用:

            AfxGetResourceHandle();

            AfxSetResourceHandle(HINSTANCE xxx);

              AfxGetResourceHandle用于獲取當前資源模塊句柄,而AfxSetResourceHandle則用于設置程序目前要使用的資源模塊句柄。

              我們將DLL中的接口函數(shù)ShowDlg改為:

            void ShowDlg(void)
            {
             //方法2的狀態(tài)變更
             HINSTANCE save_hInstance = AfxGetResourceHandle();
             AfxSetResourceHandle(theApp.m_hInstance);
             CDialog dlg(IDD_DLL_DIALOG);//打開ID為2000的對話框
             dlg.DoModal();

             //方法2的狀態(tài)還原
             AfxSetResourceHandle(save_hInstance);
            }

              通過AfxGetResourceHandle和AfxSetResourceHandle的合理變更,我們能夠靈活地設置程序的資源模塊句柄,而方法一則只能在DLL接口函數(shù)退出的時候才會恢復模塊句柄。方法二則不同,如果將ShowDlg改為:

            extern CSharedDllApp theApp; //需要聲明theApp外部全局變量

            void ShowDlg(void)
            {
             //方法2的狀態(tài)變更
             HINSTANCE save_hInstance = AfxGetResourceHandle();
             AfxSetResourceHandle(theApp.m_hInstance);

             CDialog dlg(IDD_DLL_DIALOG);//打開ID為2000的對話框
             dlg.DoModal();

             //方法2的狀態(tài)還原

             AfxSetResourceHandle(save_hInstance);

             //使用方法2后在此處再進行操作針對的將是應用程序的資源

             CDialog dlg1(IDD_DLL_DIALOG); //打開ID為2000的對話框
             dlg1.DoModal();
            }

              在應用程序主對話框的“調(diào)用DLL”按鈕上點擊,將看到兩個對話框,相繼為DLL中的對話框(圖13)和EXE中的對話框(圖14)。

              方法三 由應用程序自身切換

              資源模塊的切換除了可以由DLL接口函數(shù)完成以外,由應用程序自身也能完成(下載本工程)。

              現(xiàn)在我們把DLL中的接口函數(shù)改為最簡單的:

            void ShowDlg(void)
            {
             CDialog dlg(IDD_DLL_DIALOG); //打開ID為2000的對話框
             dlg.DoModal();
            }

              而將應用程序的OnCalldllButton函數(shù)改為:

            void CSharedDllCallDlg::OnCalldllButton()
            {
             //方法3:由應用程序本身進行狀態(tài)切換
             //獲取EXE模塊句柄

             HINSTANCE exe_hInstance = GetModuleHandle(NULL);

             //或者HINSTANCE exe_hInstance = AfxGetResourceHandle();
             //獲取DLL模塊句柄

             HINSTANCE dll_hInstance = GetModuleHandle("SharedDll.dll");
             AfxSetResourceHandle(dll_hInstance); //切換狀態(tài)
             ShowDlg(); //此時顯示的是DLL的對話框
             AfxSetResourceHandle(exe_hInstance); //恢復狀態(tài)

             //資源模塊恢復后再調(diào)用ShowDlg
             ShowDlg(); //此時顯示的是EXE的對話框
            }

              方法三中的Win32函數(shù)GetModuleHandle可以根據(jù)DLL的文件名獲取DLL的模塊句柄。如果需要得到EXE模塊的句柄,則應調(diào)用帶有Null參數(shù)的GetModuleHandle。

              方法三與方法二的不同在于方法三是在應用程序中利用AfxGetResourceHandle和AfxSetResourceHandle進行資源模塊句柄切換的。同樣地,在應用程序主對話框的“調(diào)用DLL”按鈕上點擊,也將看到兩個對話框,相繼為DLL中的對話框(圖13)和EXE中的對話框(圖14)。

              在下一節(jié)我們將對MFC擴展DLL進行詳細分析和實例講解,歡迎您繼續(xù)關注本系列連載。
            posted @ 2009-06-09 21:16 wrh 閱讀(266) | 評論 (0)編輯 收藏
            4.1一個簡單的DLL

              第2節(jié)給出了以靜態(tài)鏈接庫方式提供add函數(shù)接口的方法,接下來我們來看看怎樣用動態(tài)鏈接庫實現(xiàn)一個同樣功能的add函數(shù)。

              如圖6,在VC++中new一個Win32 Dynamic-Link Library工程dllTest(單擊此處下載本工程)。注意不要選擇MFC AppWizard(dll),因為用MFC AppWizard(dll)建立的將是第5、6節(jié)要講述的MFC 動態(tài)鏈接庫。


            圖6 建立一個非MFC DLL

              在建立的工程中添加lib.h及l(fā)ib.cpp文件,源代碼如下:

            /* 文件名:lib.h */

            #ifndef LIB_H
            #define LIB_H
            extern "C" int __declspec(dllexport)add(int x, int y);
            #endif

            /* 文件名:lib.cpp */

            #include "lib.h"
            int add(int x, int y)
            {
             return x + y;
            }

              與第2節(jié)對靜態(tài)鏈接庫的調(diào)用相似,我們也建立一個與DLL工程處于同一工作區(qū)的應用工程dllCall,它調(diào)用DLL中的函數(shù)add,其源代碼如下:

            #include <stdio.h>
            #include <windows.h>

            typedef int(*lpAddFun)(int, int); //宏定義函數(shù)指針類型
            int main(int argc, char *argv[])
            {
             HINSTANCE hDll; //DLL句柄
             lpAddFun addFun; //函數(shù)指針
             hDll = LoadLibrary("..\\Debug\\dllTest.dll");
             if (hDll != NULL)
             {
              addFun = (lpAddFun)GetProcAddress(hDll, "add");
              if (addFun != NULL)
              {
               int result = addFun(2, 3);
               printf("%d", result);
              }
              FreeLibrary(hDll);
             }
             return 0;
            }

              分析上述代碼,dllTest工程中的lib.cpp文件與第2節(jié)靜態(tài)鏈接庫版本完全相同,不同在于lib.h對函數(shù)add的聲明前面添加了__declspec(dllexport)語句。這個語句的含義是聲明函數(shù)add為DLL的導出函數(shù)。DLL內(nèi)的函數(shù)分為兩種:

              (1)DLL導出函數(shù),可供應用程序調(diào)用;

              (2) DLL內(nèi)部函數(shù),只能在DLL程序使用,應用程序無法調(diào)用它們。

              而應用程序?qū)Ρ綝LL的調(diào)用和對第2節(jié)靜態(tài)鏈接庫的調(diào)用卻有較大差異,下面我們來逐一分析。

              首先,語句typedef int ( * lpAddFun)(int,int)定義了一個與add函數(shù)接受參數(shù)類型和返回值均相同的函數(shù)指針類型。隨后,在main函數(shù)中定義了lpAddFun的實例addFun;

              其次,在函數(shù)main中定義了一個DLL HINSTANCE句柄實例hDll,通過Win32 Api函數(shù)LoadLibrary動態(tài)加載了DLL模塊并將DLL模塊句柄賦給了hDll;

              再次,在函數(shù)main中通過Win32 Api函數(shù)GetProcAddress得到了所加載DLL模塊中函數(shù)add的地址并賦給了addFun。經(jīng)由函數(shù)指針addFun進行了對DLL中add函數(shù)的調(diào)用;

              最后,應用工程使用完DLL后,在函數(shù)main中通過Win32 Api函數(shù)FreeLibrary釋放了已經(jīng)加載的DLL模塊。

              通過這個簡單的例子,我們獲知DLL定義和調(diào)用的一般概念:

              (1)DLL中需以某種特定的方式聲明導出函數(shù)(或變量、類);

              (2)應用工程需以某種特定的方式調(diào)用DLL的導出函數(shù)(或變量、類)。

              下面我們來對“特定的方式進行”闡述。

              4.2 聲明導出函數(shù)

              DLL中導出函數(shù)的聲明有兩種方式:一種為4.1節(jié)例子中給出的在函數(shù)聲明中加上__declspec(dllexport),這里不再舉例說明;另外一種方式是采用模塊定義(.def) 文件聲明,.def文件為鏈接器提供了有關被鏈接程序的導出、屬性及其他方面的信息。

              下面的代碼演示了怎樣同.def文件將函數(shù)add聲明為DLL導出函數(shù)(需在dllTest工程中添加lib.def文件):

            ; lib.def : 導出DLL函數(shù)

            LIBRARY dllTest

            EXPORTS

            add @ 1

              .def文件的規(guī)則為:

              (1)LIBRARY語句說明.def文件相應的DLL;

              (2)EXPORTS語句后列出要導出函數(shù)的名稱。可以在.def文件中的導出函數(shù)名后加@n,表示要導出函數(shù)的序號為n(在進行函數(shù)調(diào)用時,這個序號將發(fā)揮其作用);

              (3).def 文件中的注釋由每個注釋行開始處的分號 (;) 指定,且注釋不能與語句共享一行。

              由此可以看出,例子中l(wèi)ib.def文件的含義為生成名為“dllTest”的動態(tài)鏈接庫,導出其中的add函數(shù),并指定add函數(shù)的序號為1。

              4.3 DLL的調(diào)用方式

              在4.1節(jié)的例子中我們看到了由“LoadLibrary-GetProcAddress-FreeLibrary”系統(tǒng)Api提供的三位一體“DLL加載-DLL函數(shù)地址獲取-DLL釋放”方式,這種調(diào)用方式稱為DLL的動態(tài)調(diào)用。

              動態(tài)調(diào)用方式的特點是完全由編程者用 API 函數(shù)加載和卸載 DLL,程序員可以決定 DLL 文件何時加載或不加載,顯式鏈接在運行時決定加載哪個 DLL 文件。

              與動態(tài)調(diào)用方式相對應的就是靜態(tài)調(diào)用方式,“有動必有靜”,這來源于物質(zhì)世界的對立統(tǒng)一。“動與靜”,其對立與統(tǒng)一竟無數(shù)次在技術領域里得到驗證,譬如靜態(tài)IP與DHCP、靜態(tài)路由與動態(tài)路由等。從前文我們已經(jīng)知道,庫也分為靜態(tài)庫與動態(tài)庫DLL,而想不到,深入到DLL內(nèi)部,其調(diào)用方式也分為靜態(tài)與動態(tài)。“動與靜”,無處不在。《周易》已認識到有動必有靜的動靜平衡觀,《易.系辭》曰:“動靜有常,剛柔斷矣”。哲學意味著一種普遍的真理,因此,我們經(jīng)常可以在枯燥的技術領域看到哲學的影子。

              靜態(tài)調(diào)用方式的特點是由編譯系統(tǒng)完成對DLL的加載和應用程序結束時 DLL 的卸載。當調(diào)用某DLL的應用程序結束時,若系統(tǒng)中還有其它程序使用該 DLL,則Windows對DLL的應用記錄減1,直到所有使用該DLL的程序都結束時才釋放它。靜態(tài)調(diào)用方式簡單實用,但不如動態(tài)調(diào)用方式靈活。

              下面我們來看看靜態(tài)調(diào)用的例子(單擊此處下載本工程),將編譯dllTest工程所生成的.lib和.dll文件拷入dllCall工程所在的路徑,dllCall執(zhí)行下列代碼:

            #pragma comment(lib,"dllTest.lib")

            //.lib文件中僅僅是關于其對應DLL文件中函數(shù)的重定位信息

            extern "C" __declspec(dllimport) add(int x,int y);

            int main(int argc, char* argv[])
            {
             int result = add(2,3);
             printf("%d",result);
             return 0;
            }

              由上述代碼可以看出,靜態(tài)調(diào)用方式的順利進行需要完成兩個動作:

              (1)告訴編譯器與DLL相對應的.lib文件所在的路徑及文件名,#pragma comment(lib,"dllTest.lib")就是起這個作用。

              程序員在建立一個DLL文件時,連接器會自動為其生成一個對應的.lib文件,該文件包含了DLL 導出函數(shù)的符號名及序號(并不含有實際的代碼)。在應用程序里,.lib文件將作為DLL的替代文件參與編譯。

              (2)聲明導入函數(shù),extern "C" __declspec(dllimport) add(int x,int y)語句中的__declspec(dllimport)發(fā)揮這個作用。

              靜態(tài)調(diào)用方式不再需要使用系統(tǒng)API來加載、卸載DLL以及獲取DLL中導出函數(shù)的地址。這是因為,當程序員通過靜態(tài)鏈接方式編譯生成應用程序時,應用程序中調(diào)用的與.lib文件中導出符號相匹配的函數(shù)符號將進入到生成的EXE 文件中,.lib文件中所包含的與之對應的DLL文件的文件名也被編譯器存儲在 EXE文件內(nèi)部。當應用程序運行過程中需要加載DLL文件時,Windows將根據(jù)這些信息發(fā)現(xiàn)并加載DLL,然后通過符號名實現(xiàn)對DLL 函數(shù)的動態(tài)鏈接。這樣,EXE將能直接通過函數(shù)名調(diào)用DLL的輸出函數(shù),就象調(diào)用程序內(nèi)部的其他函數(shù)一樣。
            4.4 DllMain函數(shù)

              Windows在加載DLL的時候,需要一個入口函數(shù),就如同控制臺或DOS程序需要main函數(shù)、WIN32程序需要WinMain函數(shù)一樣。在前面的例子中,DLL并沒有提供DllMain函數(shù),應用工程也能成功引用DLL,這是因為Windows在找不到DllMain的時候,系統(tǒng)會從其它運行庫中引入一個不做任何操作的缺省DllMain函數(shù)版本,并不意味著DLL可以放棄DllMain函數(shù)。

              根據(jù)編寫規(guī)范,Windows必須查找并執(zhí)行DLL里的DllMain函數(shù)作為加載DLL的依據(jù),它使得DLL得以保留在內(nèi)存里。這個函數(shù)并不屬于導出函數(shù),而是DLL的內(nèi)部函數(shù)。這意味著不能直接在應用工程中引用DllMain函數(shù),DllMain是自動被調(diào)用的。

              我們來看一個DllMain函數(shù)的例子(單擊此處下載本工程)。

            BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
            {
             switch (ul_reason_for_call)
             {
              case DLL_PROCESS_ATTACH:
               printf("\nprocess attach of dll");
               break;
              case DLL_THREAD_ATTACH:
               printf("\nthread attach of dll");
               break;
              case DLL_THREAD_DETACH:
               printf("\nthread detach of dll");
               break;
              case DLL_PROCESS_DETACH:
               printf("\nprocess detach of dll");
               break;
             }
             return TRUE;
            }

              DllMain函數(shù)在DLL被加載和卸載時被調(diào)用,在單個線程啟動和終止時,DLLMain函數(shù)也被調(diào)用,ul_reason_for_call指明了被調(diào)用的原因。原因共有4種,即PROCESS_ATTACH、PROCESS_DETACH、THREAD_ATTACH和THREAD_DETACH,以switch語句列出。

              來仔細解讀一下DllMain的函數(shù)頭BOOL APIENTRY DllMain( HANDLE hModule, WORD ul_reason_for_call, LPVOID lpReserved )。

              APIENTRY被定義為__stdcall,它意味著這個函數(shù)以標準Pascal的方式進行調(diào)用,也就是WINAPI方式;

              進程中的每個DLL模塊被全局唯一的32字節(jié)的HINSTANCE句柄標識,只有在特定的進程內(nèi)部有效,句柄代表了DLL模塊在進程虛擬空間中的起始地址。在Win32中,HINSTANCE和HMODULE的值是相同的,這兩種類型可以替換使用,這就是函數(shù)參數(shù)hModule的來歷。

              執(zhí)行下列代碼:

            hDll = LoadLibrary("..\\Debug\\dllTest.dll");
            if (hDll != NULL)
            {
             addFun = (lpAddFun)GetProcAddress(hDll, MAKEINTRESOURCE(1));
             //MAKEINTRESOURCE直接使用導出文件中的序號
             if (addFun != NULL)
             {
              int result = addFun(2, 3);
              printf("\ncall add in dll:%d", result);
             }
             FreeLibrary(hDll);
            }

              我們看到輸出順序為:

            process attach of dll
            call add in dll:5
            process detach of dll

              這一輸出順序驗證了DllMain被調(diào)用的時機。

              代碼中的GetProcAddress ( hDll, MAKEINTRESOURCE ( 1 ) )值得留意,它直接通過.def文件中為add函數(shù)指定的順序號訪問add函數(shù),具體體現(xiàn)在MAKEINTRESOURCE ( 1 ),MAKEINTRESOURCE是一個通過序號獲取函數(shù)名的宏,定義為(節(jié)選自winuser.h):

            #define MAKEINTRESOURCEA(i) (LPSTR)((DWORD)((WORD)(i)))
            #define MAKEINTRESOURCEW(i) (LPWSTR)((DWORD)((WORD)(i)))
            #ifdef UNICODE
            #define MAKEINTRESOURCE MAKEINTRESOURCEW
            #else
            #define MAKEINTRESOURCE MAKEINTRESOURCEA

              4.5 __stdcall約定

              如果通過VC++編寫的DLL欲被其他語言編寫的程序調(diào)用,應將函數(shù)的調(diào)用方式聲明為__stdcall方式,WINAPI都采用這種方式,而C/C++缺省的調(diào)用方式卻為__cdecl。__stdcall方式與__cdecl對函數(shù)名最終生成符號的方式不同。若采用C編譯方式(在C++中需將函數(shù)聲明為extern "C"),__stdcall調(diào)用約定在輸出函數(shù)名前面加下劃線,后面加“@”符號和參數(shù)的字節(jié)數(shù),形如_functionname@number;而__cdecl調(diào)用約定僅在輸出函數(shù)名前面加下劃線,形如_functionname。

              Windows編程中常見的幾種函數(shù)類型聲明宏都是與__stdcall和__cdecl有關的(節(jié)選自windef.h):

            #define CALLBACK __stdcall //這就是傳說中的回調(diào)函數(shù)
            #define WINAPI __stdcall //這就是傳說中的WINAPI
            #define WINAPIV __cdecl
            #define APIENTRY WINAPI //DllMain的入口就在這里
            #define APIPRIVATE __stdcall
            #define PASCAL __stdcall

              在lib.h中,應這樣聲明add函數(shù):

            int __stdcall add(int x, int y);

              在應用工程中函數(shù)指針類型應定義為:

            typedef int(__stdcall *lpAddFun)(int, int);

              若在lib.h中將函數(shù)聲明為__stdcall調(diào)用,而應用工程中仍使用typedef int (* lpAddFun)(int,int),運行時將發(fā)生錯誤(因為類型不匹配,在應用工程中仍然是缺省的__cdecl調(diào)用),彈出如圖7所示的對話框。


            圖7 調(diào)用約定不匹配時的運行錯誤

              圖8中的那段話實際上已經(jīng)給出了錯誤的原因,即“This is usually a result of …”。

              單擊此處下載__stdcall調(diào)用例子工程源代碼
            4.6 DLL導出變量

              DLL定義的全局變量可以被調(diào)用進程訪問;DLL也可以訪問調(diào)用進程的全局數(shù)據(jù),我們來看看在應用工程中引用DLL中變量的例子(單擊此處下載本工程)。

            /* 文件名:lib.h */

            #ifndef LIB_H
            #define LIB_H
            extern int dllGlobalVar;
            #endif

            /* 文件名:lib.cpp */

            #include "lib.h"
            #include <windows.h>

            int dllGlobalVar;

            BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
            {
             switch (ul_reason_for_call)
             {
              case DLL_PROCESS_ATTACH:
               dllGlobalVar = 100; //在dll被加載時,賦全局變量為100
               break;
              case DLL_THREAD_ATTACH:
              case DLL_THREAD_DETACH:
              case DLL_PROCESS_DETACH:
               break;
             }
             return TRUE;
            }

              ;文件名:lib.def

              ;在DLL中導出變量

            LIBRARY "dllTest"

            EXPORTS

            dllGlobalVar CONSTANT

            ;或dllGlobalVar DATA

            GetGlobalVar

              從lib.h和lib.cpp中可以看出,全局變量在DLL中的定義和使用方法與一般的程序設計是一樣的。若要導出某全局變量,我們需要在.def文件的EXPORTS后添加:

              變量名 CONSTANT   //過時的方法

              或

              變量名 DATA     //VC++提示的新方法

              在主函數(shù)中引用DLL中定義的全局變量:

            #include <stdio.h>
            #pragma comment(lib,"dllTest.lib")

            extern int dllGlobalVar;

            int main(int argc, char *argv[])
            {
             printf("%d ", *(int*)dllGlobalVar);
             *(int*)dllGlobalVar = 1;
             printf("%d ", *(int*)dllGlobalVar);
             return 0;
            }

              特別要注意的是用extern int dllGlobalVar聲明所導入的并不是DLL中全局變量本身,而是其地址,應用程序必須通過強制指針轉(zhuǎn)換來使用DLL中的全局變量。這一點,從*(int*)dllGlobalVar可以看出。因此在采用這種方式引用DLL全局變量時,千萬不要進行這樣的賦值操作:

            dllGlobalVar = 1;

              其結果是dllGlobalVar指針的內(nèi)容發(fā)生變化,程序中以后再也引用不到DLL中的全局變量了。

              在應用工程中引用DLL中全局變量的一個更好方法是:

            #include <stdio.h>
            #pragma comment(lib,"dllTest.lib")

            extern int _declspec(dllimport) dllGlobalVar; //用_declspec(dllimport)導入
            int main(int argc, char *argv[])
            {
             printf("%d ", dllGlobalVar);
             dllGlobalVar = 1; //這里就可以直接使用, 無須進行強制指針轉(zhuǎn)換
             printf("%d ", dllGlobalVar);
             return 0;
            }

              通過_declspec(dllimport)方式導入的就是DLL中全局變量本身而不再是其地址了,筆者建議在一切可能的情況下都使用這種方式。

              4.7 DLL導出類

              DLL中定義的類可以在應用工程中使用。

              下面的例子里,我們在DLL中定義了point和circle兩個類,并在應用工程中引用了它們(單擊此處下載本工程)。

            //文件名:point.h,point類的聲明

            #ifndef POINT_H
            #define POINT_H
            #ifdef DLL_FILE
             class _declspec(dllexport) point //導出類point
            #else
             class _declspec(dllimport) point //導入類point
            #endif
            {
             public:
              float y;
              float x;
              point();
              point(float x_coordinate, float y_coordinate);
            };

            #endif

            //文件名:point.cpp,point類的實現(xiàn)

            #ifndef DLL_FILE
             #define DLL_FILE
            #endif

            #include "point.h"

            //類point的缺省構造函數(shù)

            point::point()
            {
             x = 0.0;
             y = 0.0;
            }

            //類point的構造函數(shù)

            point::point(float x_coordinate, float y_coordinate)
            {
             x = x_coordinate;
             y = y_coordinate;
            }

            //文件名:circle.h,circle類的聲明

            #ifndef CIRCLE_H
            #define CIRCLE_H
            #include "point.h"
            #ifdef DLL_FILE
            class _declspec(dllexport)circle //導出類circle
            #else
            class _declspec(dllimport)circle //導入類circle
            #endif
            {
             public:
              void SetCentre(const point &centrePoint);
              void SetRadius(float r);
              float GetGirth();
              float GetArea();
              circle();
             private:
              float radius;
              point centre;
            };

            #endif

            //文件名:circle.cpp,circle類的實現(xiàn)

            #ifndef DLL_FILE
            #define DLL_FILE
            #endif
            #include "circle.h"
            #define PI 3.1415926

            //circle類的構造函數(shù)

            circle::circle()
            {
             centre = point(0, 0);
             radius = 0;
            }

            //得到圓的面積

            float circle::GetArea()
            {
             return PI *radius * radius;
            }

            //得到圓的周長

            float circle::GetGirth()
            {
             return 2 *PI * radius;
            }

            //設置圓心坐標

            void circle::SetCentre(const point &centrePoint)
            {
             centre = centrePoint;
            }

            //設置圓的半徑

            void circle::SetRadius(float r)
            {
             radius = r;
            }

              類的引用:

            #include "..\circle.h"  //包含類聲明頭文件

            #pragma comment(lib,"dllTest.lib");

            int main(int argc, char *argv[])
            {
             circle c;
             point p(2.0, 2.0);
             c.SetCentre(p);
             c.SetRadius(1.0);
             printf("area:%f girth:%f", c.GetArea(), c.GetGirth());
             return 0;
            }

              從上述源代碼可以看出,由于在DLL的類實現(xiàn)代碼中定義了宏DLL_FILE,故在DLL的實現(xiàn)中所包含的類聲明實際上為:

            class _declspec(dllexport) point //導出類point
            {
             …
            }

              和

            class _declspec(dllexport) circle //導出類circle
            {
             …
            }

              而在應用工程中沒有定義DLL_FILE,故其包含point.h和circle.h后引入的類聲明為:

            class _declspec(dllimport) point //導入類point
            {
             …
            }

              和

            class _declspec(dllimport) circle //導入類circle
            {
             …
            }

              不錯,正是通過DLL中的

            class _declspec(dllexport) class_name //導出類circle 
            {
             …
            }

              與應用程序中的

            class _declspec(dllimport) class_name //導入類
            {
             …
            }

              匹對來完成類的導出和導入的!

              我們往往通過在類的聲明頭文件中用一個宏來決定使其編譯為class _declspec(dllexport) class_name還是class _declspec(dllimport) class_name版本,這樣就不再需要兩個頭文件。本程序中使用的是:

            #ifdef DLL_FILE
             class _declspec(dllexport) class_name //導出類
            #else
             class _declspec(dllimport) class_name //導入類
            #endif

              實際上,在MFC DLL的講解中,您將看到比這更簡便的方法,而此處僅僅是為了說明_declspec(dllexport)與_declspec(dllimport)匹對的問題。

              由此可見,應用工程中幾乎可以看到DLL中的一切,包括函數(shù)、變量以及類,這就是DLL所要提供的強大能力。只要DLL釋放這些接口,應用程序使用它就將如同使用本工程中的程序一樣!

              本章雖以VC++為平臺講解非MFC DLL,但是這些普遍的概念在其它語言及開發(fā)環(huán)境中也是相同的,其思維方式可以直接過渡。 接下來,我們將要研究MFC規(guī)則DLL。
            posted @ 2009-06-09 21:15 wrh 閱讀(169) | 評論 (0)編輯 收藏
            1.概論

              先來闡述一下DLL(Dynamic Linkable Library)的概念,你可以簡單的把DLL看成一種倉庫,它提供給你一些可以直接拿來用的變量、函數(shù)或類。在倉庫的發(fā)展史上經(jīng)歷了“無庫-靜態(tài)鏈接庫-動態(tài)鏈接庫”的時代。靜態(tài)鏈接庫與動態(tài)鏈接庫都是共享代碼的方式,如果采用靜態(tài)鏈接庫,則無論你愿不愿意,lib中的指令都被直接包含在最終生成的EXE文件中了。但是若使用DLL,該DLL不必被包含在最終EXE文件中,EXE文件執(zhí)行時可以“動態(tài)”地引用和卸載這個與EXE獨立的DLL文件。靜態(tài)鏈接庫和動態(tài)鏈接庫的另外一個區(qū)別在于靜態(tài)鏈接庫中不能再包含其他的動態(tài)鏈接庫或者靜態(tài)庫,而在動態(tài)鏈接庫中還可以再包含其他的動態(tài)或靜態(tài)鏈接庫。

              對動態(tài)鏈接庫,我們還需建立如下概念:

              (1)DLL 的編制與具體的編程語言及編譯器無關

              只要遵循約定的DLL接口規(guī)范和調(diào)用方式,用各種語言編寫的DLL都可以相互調(diào)用。譬如Windows提供的系統(tǒng)DLL(其中包括了Windows的API),在任何開發(fā)環(huán)境中都能被調(diào)用,不在乎其是Visual Basic、Visual C++還是Delphi。

              (2)動態(tài)鏈接庫隨處可見

              我們在Windows目錄下的system32文件夾中會看到kernel32.dll、user32.dll和gdi32.dll,windows的大多數(shù)API都包含在這些DLL中。kernel32.dll中的函數(shù)主要處理內(nèi)存管理和進程調(diào)度;user32.dll中的函數(shù)主要控制用戶界面;gdi32.dll中的函數(shù)則負責圖形方面的操作。

              一般的程序員都用過類似MessageBox的函數(shù),其實它就包含在user32.dll這個動態(tài)鏈接庫中。由此可見DLL對我們來說其實并不陌生。

              (3)VC動態(tài)鏈接庫的分類

              Visual C++支持三種DLL,它們分別是Non-MFC DLL(非MFC動態(tài)庫)、MFC Regular DLL(MFC規(guī)則DLL)、MFC Extension DLL(MFC擴展DLL)。

              非MFC動態(tài)庫不采用MFC類庫結構,其導出函數(shù)為標準的C接口,能被非MFC或MFC編寫的應用程序所調(diào)用;MFC規(guī)則DLL 包含一個繼承自CWinApp的類,但其無消息循環(huán);MFC擴展DLL采用MFC的動態(tài)鏈接版本創(chuàng)建,它只能被用MFC類庫所編寫的應用程序所調(diào)用。

              由于本文篇幅較長,內(nèi)容較多,勢必需要先對閱讀本文的有關事項進行說明,下面以問答形式給出。

              問:本文主要講解什么內(nèi)容?

              答:本文詳細介紹了DLL編程的方方面面,努力學完本文應可以對DLL有較全面的掌握,并能編寫大多數(shù)DLL程序。

              問:如何看本文?

              答:本文每一個主題的講解都附帶了源代碼例程,可以隨文下載(每個工程都經(jīng)WINRAR壓縮)。所有這些例程都由筆者編寫并在VC++6.0中調(diào)試通過。

              當然看懂本文不是讀者的最終目的,讀者應親自動手實踐才能真正掌握DLL的奧妙。

              問:學習本文需要什么樣的基礎知識?

              答:如果你掌握了C,并大致掌握了C++,了解一點MFC的知識,就可以輕松地看懂本文。

              2.靜態(tài)鏈接庫

              對靜態(tài)鏈接庫的講解不是本文的重點,但是在具體講解DLL之前,通過一個靜態(tài)鏈接庫的例子可以快速地幫助我們建立“庫”的概念。


            圖1 建立一個靜態(tài)鏈接庫

              如圖1,在VC++6.0中new一個名稱為libTest的static library工程(單擊此處下載本工程),并新建lib.h和lib.cpp兩個文件,lib.h和lib.cpp的源代碼如下:

            //文件:lib.h

            #ifndef LIB_H
            #define LIB_H
            extern "C" int add(int x,int y);   //聲明為C編譯、連接方式的外部函數(shù)
            #endif

            //文件:lib.cpp

            #include "lib.h"
            int add(int x,int y)
            {
             return x + y;
            }

              編譯這個工程就得到了一個.lib文件,這個文件就是一個函數(shù)庫,它提供了add的功能。將頭文件和.lib文件提交給用戶后,用戶就可以直接使用其中的add函數(shù)了。

              標準Turbo C2.0中的C庫函數(shù)(我們用來的scanf、printf、memcpy、strcpy等)就來自這種靜態(tài)庫。

              下面來看看怎么使用這個庫,在libTest工程所在的工作區(qū)內(nèi)new一個libCall工程。libCall工程僅包含一個main.cpp文件,它演示了靜態(tài)鏈接庫的調(diào)用方法,其源代碼如下:

            #include <stdio.h>
            #include "..\lib.h"
            #pragma comment( lib, "..\\debug\\libTest.lib" )  //指定與靜態(tài)庫一起連接

            int main(int argc, char* argv[])
            {
             printf( "2 + 3 = %d", add( 2, 3 ) );
            }

              靜態(tài)鏈接庫的調(diào)用就是這么簡單,或許我們每天都在用,可是我們沒有明白這個概念。代碼中#pragma comment( lib , "..\\debug\\libTest.lib" )的意思是指本文件生成的.obj文件應與libTest.lib一起連接。如果不用#pragma comment指定,則可以直接在VC++中設置,如圖2,依次選擇tools、options、directories、library files菜單或選項,填入庫文件路徑。圖2中加紅圈的部分為我們添加的libTest.lib文件的路徑。


            圖2 在VC中設置庫文件路徑

              這個靜態(tài)鏈接庫的例子至少讓我們明白了庫函數(shù)是怎么回事,它們是哪來的。我們現(xiàn)在有下列模糊認識了:

              (1)庫不是個怪物,編寫庫的程序和編寫一般的程序區(qū)別不大,只是庫不能單獨執(zhí)行;

              (2)庫提供一些可以給別的程序調(diào)用的東東,別的程序要調(diào)用它必須以某種方式指明它要調(diào)用之。

              以上從靜態(tài)鏈接庫分析而得到的對庫的懵懂概念可以直接引申到動態(tài)鏈接庫中,動態(tài)鏈接庫與靜態(tài)鏈接庫在編寫和調(diào)用上的不同體現(xiàn)在庫的外部接口定義及調(diào)用方式略有差異。

            3.庫的調(diào)試與查看

              在具體進入各類DLL的詳細闡述之前,有必要對庫文件的調(diào)試與查看方法進行一下介紹,因為從下一節(jié)開始我們將面對大量的例子工程。

              由于庫文件不能單獨執(zhí)行,因而在按下F5(開始debug模式執(zhí)行)或CTRL+F5(運行)執(zhí)行時,其彈出如圖3所示的對話框,要求用戶輸入可執(zhí)行文件的路徑來啟動庫函數(shù)的執(zhí)行。這個時候我們輸入要調(diào)用該庫的EXE文件的路徑就可以對庫進行調(diào)試了,其調(diào)試技巧與一般應用工程的調(diào)試一樣。


            圖3 庫的調(diào)試與“運行”

              通常有比上述做法更好的調(diào)試途徑,那就是將庫工程和應用工程(調(diào)用庫的工程)放置在同一VC工作區(qū),只對應用工程進行調(diào)試,在應用工程調(diào)用庫中函數(shù)的語句處設置斷點,執(zhí)行后按下F11,這樣就單步進入了庫中的函數(shù)。第2節(jié)中的libTest和libCall工程就放在了同一工作區(qū),其工程結構如圖4所示。


            圖4 把庫工程和調(diào)用庫的工程放入同一工作區(qū)進行調(diào)試

              上述調(diào)試方法對靜態(tài)鏈接庫和動態(tài)鏈接庫而言是一致的。所以本文提供下載的所有源代碼中都包含了庫工程和調(diào)用庫的工程,這二者都被包含在一個工作區(qū)內(nèi),這是筆者提供這種打包下載的用意所在。

              動態(tài)鏈接庫中的導出接口可以使用Visual C++的Depends工具進行查看,讓我們用Depends打開系統(tǒng)目錄中的user32.dll,看到了吧?紅圈內(nèi)的就是幾個版本的MessageBox了!原來它真的在這里啊,原來它就在這里啊!


            圖5 用Depends查看DLL

              當然Depends工具也可以顯示DLL的層次結構,若用它打開一個可執(zhí)行文件則可以看出這個可執(zhí)行文件調(diào)用了哪些DLL。

              好,讓我們正式進入動態(tài)鏈接庫的世界,先來看看最一般的DLL,即非MFC DLL。
            posted @ 2009-06-09 21:14 wrh 閱讀(343) | 評論 (0)編輯 收藏
             1.
            BOOL   MyDlg::PreCreateWindow(CREATESTRUCT&   cs)    
              {  
              //   TODO:   Add   your   specialized   code   here   and/or   call   the   base   class  
               
              cs.style   &=   ~WS_MAXIMIZEBOX;  
               
              return   CDialog::PreCreateWindow(cs);  
              }


            2.ModifyStyle(WS_MAXIMIZEBOX,0);
            posted @ 2009-06-07 19:21 wrh 閱讀(744) | 評論 (0)編輯 收藏
            一修改標題欄的方法  
              1發(fā):  
                BOOL   CMainFrame::PreCreateWindow(CREATESTRUCT&   cs)  
              {  
              if(   !CFrameWnd::PreCreateWindow(cs)   )  
              return   FALSE;  
              //   TODO:   Modify   the   Window   class   or   styles   here   by   modifying  
              //     the   CREATESTRUCT   cs  
                                cs.style=WS_OVERLAPPEDWINDOW;     //缺了這個就不行了  
                                cs.lpszName="比較兩個數(shù)的大小";//標題  
               
              return   TRUE;  
              }  
              2發(fā):也可以在文檔類里面用SetTitle("比較兩個數(shù)的大小").  
              3發(fā):把下面的代碼放在App類的InitInstance()函數(shù)里面  
                      ((CMainFram*)AfxGetMainWnd())->SetWindowText("比較兩個數(shù)的大小");  
              二修改AfxMessageBox的標題欄  
              1發(fā):int   MessageBox   (LPCTSTR   lpszText,   LPCTSTR   lpszCaption   =   NULL,   UINT   nType   =   MB_OK)  
                      第二個參數(shù)就是你要的標題欄字串  
              2發(fā):Resources的String   Table中AFX_IDS_APP_TITLE就是AfxMessageBox標題欄的要顯示的標題,你    
               
                    可以直接修改里面的字符串就可以了哈.
            posted @ 2009-06-07 10:40 wrh 閱讀(1855) | 評論 (0)編輯 收藏

             

            VC++(AfxMessageBox實例)

            int AfxMessageBox( LPCTSTR lpszText, UINT nType = MB_OK, UINT nIDHelp = 0 );
            MB_ABORTRETRYIGNORE 消息框中顯示Abort、Retry、Ignore按鈕
            MB_OK 顯示OK按鈕                  //AfxMessageBox的用法
            MB_OKCANCEL 顯示OK、Cancel按鈕
            MB_RETRYCANCEL 顯示Retry、Cancel按鈕
            MB_YESNO 顯示Yes、No按鈕
            MB_YESNOCANCEL 顯示Yes、No、Cancel按鈕

            圖標風格
            MB_ICONINFORMATION 顯示一個i圖標,表示提示
            MB_ICONEXCLAMATION 顯示一個驚嘆號,表示警告
            MB_ICONSTOP 顯示手形圖標,表示警告或嚴重錯誤
            MB_ICONQUESTION 顯示問號圖標,表示疑問

            Example:  AfxMessageBox(nStringID, MB_YESNO|MB_ICONSTOP);
            if(AfxMessageBox(strTemp,MB_YESNO|MB_ICONQUESTION)==IDNO) {... };

            Return Value: Zero if there is not enough memory to display the message box;
            otherwise, one of the following values is returned:
                *IDABORT   The Abort button was selected.
                *IDCANCEL   The Cancel button was selected.
                *IDIGNORE   The Ignore button was selected.
                *IDNO   The No button was selected.
                *IDOK   The OK button was selected.
                *IDRETRY   The Retry button was selected.
                *IDYES   The Yes button was selected.

            posted @ 2009-06-07 10:39 wrh 閱讀(3389) | 評論 (0)編輯 收藏
            Q:如何在對話框中加入工具條在 OnInitDialog 中加入下面代碼:
            BOOL CYourDlg::OnInitDialog()
            {
            CDialog::OnInitDialog();
            // Create the toolbar. To understand the meaning of the styles used, you
            // can take a look at the MSDN for the Create function of the CToolBar class.
            ToolBar.Create(this, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_TOOLTIPS |CBRS_FLYBY | CBRS_BORDER_BOTTOM);
            // I have assumed that you have named your toolbar''s resource as IDR_TOOLBAR1.
            // If you have given it a different name, change the line below to accomodate
            // that by changing the parameter for the LoadToolBar function.
            ToolBar.LoadToolBar(IDR_TOOLBAR1);
            CRect rcClientStart;
            CRect rcClientNow;
            GetClientRect(rcClientStart);
            // To reposition and resize the control bar
            RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST,0, reposQuery, rcClientNow);
            CPoint ptOffset(rcClientNow.left - rcClientStart.left,rcClientNow.top-rcClientStart.top);
            CRect rcChild;
            CWnd* pwndChild = GetWindow(GW_CHILD);
            while (pwndChild)
            {
            pwndChild->GetWindowRect(rcChild);
            ScreenToClient(rcChild);
            rcChild.OffsetRect(ptOffset);
            pwndChild->MoveWindow(rcChild, FALSE);
            pwndChild = pwndChild->GetNextWindow();
            }
            CRect rcWindow;
            GetWindowRect(rcWindow);
            rcWindow.right += rcClientStart.Width() - rcClientNow.Width();
            rcWindow.bottom += rcClientStart.Height() - rcClientNow.Height();
            MoveWindow(rcWindow, FALSE);
            // And position the control bars
            RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST, 0);
            return TRUE;  // return TRUE  unless you set the focus to a control
            }
            
            Q:如何改變對話框的形狀?

            可用下面一些函數(shù):
            CreatePolygonRgn
            CreateRectRgn
            CreateRoundRectRgn 等.
              CRgn m_rgn;  // Put this in your dialog''s header file. i.e. a member variable
            // This Gets the size of the Dialog: This piece of code is to be placed in the
            // OnInitDialog Function of your dialog.
            CRect rcDialog
            GetClientRect(rcDialog);
            // The following code Creates the area and assigns it to your Dialog
            m_rgn.CreateEllipticRgn(0, 0, rcDialog.Width(), rcDialogHeight());
            SetWindowRgn(GetSafeHwnd(), (HRGN) m_rgn, TRUE);
            
            Q:如何實現(xiàn)非客戶區(qū)移動?

            可用下面二種方法
            // Handler for WM_LBUTTONDOWN message
            void CYourDialog::OnLButtonDown(UINT nFlags, CPoint point)
            {
            CDialog::OnLButtonDown(nFlags, point);
            PostMessage( WM_NCLBUTTONDOWN, HTCAPTION, MAKELPARAM( point.x, point.y));
            }
            // Handler for WM_NCHITTEST message
            LONG CYourDialog::OnNcHitTest( UINT uParam, LONG lParam )
            {
            int xPos = LOWORD(lParam);
            int yPos = HIWORD(lParam);
            UINT nHitTest = CDialog::OnNcHitTest(CSize(xPos, yPos));
            return (nHitTest == HTCLIENT) ? HTCAPTION : nHitTest;
            }
            

            Q:如何使對話框初始為最小化狀態(tài)?

            在 OnInitDialog 中加入下面代碼:
            SendMessage(WM_SYSCOMMAND, SC_MAXIMIZE, NULL);
            Q:如何限定對話框大小范圍?

            在 WM_SIZING中加入下面代碼:
            void CYourDialog::OnSizing(UINT fwSide, LPRECT pRect)
            {
            if(pRect->right - pRect->left <=200)
            pRect->right = pRect->left + 200;
            if(pRect->bottom - pRect->top <=200)
            pRect->bottom = pRect->top + 200;
            CDialog::OnSizing(fwSide, pRect);
            }
            
            Q:如何在對話框中加入狀態(tài)條?

            定義 CStatusBar 變量:
            CStatusBar m_StatusBar;
            定義狀態(tài)條指定狀態(tài):
            static UINT BASED_CODE indicators[] =
            {
            ID_INDICATOR_CAPS,
            ID_INDICATOR_NUM
            };
            
            在 OnInitDialog 中加入下面代碼:
              m_StatusBar.CreateEx(this,SBT_TOOLTIPS,WS_CHILD|WS_VISIBLE|CBRS_BOTTOM,AFX_IDW_STATUS_BAR);
            // Set the indicators namely caps and nums lock status
            m_StatusBar.SetIndicators(indicators,sizeof(indicators)/sizeof(UINT));
            CRect rect;
            GetClientRect(&rect);
            m_StatusBar.SetPaneInfo(0,ID_INDICATOR_CAPS,SBPS_NORMAL,rect.Width()/2);
            m_StatusBar.SetPaneInfo(1,ID_INDICATOR_NUM,SBPS_STRETCH ,rect.Width()/2);
            RepositionBars(AFX_IDW_CONTROLBAR_FIRST,AFX_IDW_CONTROLBAR_LAST,ID_INDICATOR_NUM);
            m_StatusBar.GetStatusBarCtrl().SetBkColor(RGB(180,180,180));
            /////////////////////////////////////////////////////////////////////////////////////////////
            1. 如何有效地使初始窗口不顯示
            當我們想讓窗口初始時不顯示時,通常會用ShowWindow(SW_HIDE) ,但實際上還是在啟動是可以看到窗口一閃而過的痕跡。所以,可以使用下面的方法來實現(xiàn)它:
            (1.1)先在構造函數(shù)中設置布樂變量 visible值為false.
            visible = false;
            (1.2)重載 WM_WINDOWPOSCHANGING,并添加下面代碼:
            void CTest_deleteDlg::OnWindowPosChanging(WINDOWPOS FAR* lpwndpos)
            {
            if(!visible)
            lpwndpos->flags &= ~SWP_SHOWWINDOW;
            CDialog::OnWindowPosChanging(lpwndpos);
            }
            
            (1.3)然后設布爾visible變量值為true,并在要顯示窗口時,再用ShowWindow(SW_SHOW)既可。
            visible = true;
            ShowWindow(SW_SHOW);
            
            2. 對話框的全屏顯示
            對話框的全屏顯示可以在OnInitDialog()中用 SetWindowPos 和 HWND_TOPMOST 來實現(xiàn)對話框的重新大小。
            BOOL CFullScrDlgDlg::OnInitDialog()
            {
            CDialog::OnInitDialog();
            //...
            int cx, cy;
            HDC dc = ::GetDC(NULL);
            cx = GetDeviceCaps(dc,HORZRES) +
            GetSystemMetrics(SM_CXBORDER);
            cy = GetDeviceCaps(dc,VERTRES) +
            GetSystemMetrics(SM_CYBORDER);
            ::ReleaseDC(0,dc);
            //去除標題和邊框
            SetWindowLong(m_hWnd, GWL_STYLE,
            GetWindowLong(m_hWnd, GWL_STYLE) &
            (~(WS_CAPTION | WS_BORDER)));
            // 置對話框為最頂端并擴充到整個屏幕
            ::SetWindowPos(m_hWnd, HWND_TOPMOST,
            -(GetSystemMetrics(SM_CXBORDER)+1),
            -(GetSystemMetrics(SM_CYBORDER)+1),
            cx+1,cy+1, SWP_NOZORDER);
            //...
            return TRUE;
            }
            
            3. 如何在2K/xp下使窗口獲取焦點
            在2K/XP下我們可以用 AttachThreadInput 和SetForegroundWindow來有效的獲取焦點。
            //捕捉并設置當前焦點窗口為我們的窗口
            AttachThreadInput(
            GetWindowThreadProcessId(
            ::GetForegroundWindow(),NULL),
            GetCurrentThreadId(),TRUE);
            //置我們的為焦點窗口
            SetForegroundWindow();
            SetFocus();
            //釋放thread
            AttachThreadInput(
            GetWindowThreadProcessId(
            ::GetForegroundWindow(),NULL),
            GetCurrentThreadId(),FALSE);
            
            4. 使你的對話框位于最頂端
            可以直接在 OnInitDialog()中用SetWindowPos來實現(xiàn)。
            SetWindowPos(&this->wndTopMost,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE);
            5. 如何動態(tài)放大/縮小對話框
            還是可以用SetWindowPos或MoveWindow來實現(xiàn)它。
            void CTest_deleteDlg::OnMakeSmall()
            {
            SetWindowPos(NULL,0,0,200,200,SWP_NOZORDER|SWP_NOMOVE);
            }
            void CTest_deleteDlg::OnExpand()
            {
            SetWindowPos(NULL,0,0,500,300,SWP_NOZORDER|SWP_NOMOVE);
            }
            
            或:
            //伸、縮在IDC_DYCREDITS和IDC_COPYRIGHT兩STATIC控件間,做為分隔線
            BOOL CAbout::OnInitDialog()
            {
            CDialog::OnInitDialog();
            //"關于"對話框中對話框可收縮效果
            CRect Rect1,Rect2; 							     //對話框收縮時大小
            GetDlgItem(IDC_DYCREDITS)->GetWindowRect(Rect1);
            GetDlgItem(IDC_COPYRIGHT)->GetWindowRect(Rect2);
            m_nReducedHeight = Rect1.Height()+(Rect1.top -Rect2.bottom)/2; //收縮后窗體高度
            dlgRect.bottom -= (Rect1.Height()+(Rect1.top -Rect2.bottom)/2);
            MoveWindow(&dlgRect);				              //如果要顯示對話框起始動態(tài)效果的話,不能使用該句
            m_bVertical=false;                                //默認收縮對話框
            }
            // ---------------------------------------------------------
            //	名稱: OnMore
            //	功能: 是否允許顯示
            //	變量: 無
            //	返回: 無
            //	編寫: 徐景周,2002.4.8
            // ---------------------------------------------------------
            void CAbout::OnMore()
            {
            m_bVertical = !m_bVertical;
            if(m_bVertical == FALSE)	//不顯示
            {
            SetDlgItemText(ID_MORE,_T("更多>>"));
            SizeWindow(m_nReducedHeight,true);
            //		m_DyCredits.EndScrolling();              //停止?jié)L動
            }
            else						//顯示
            {
            SetDlgItemText(ID_MORE,_T("<<隱藏"));
            SizeWindow(m_nReducedHeight,false);
            m_DyCredits.StartScrolling();			//開始滾動
            }
            UpdateWindow();
            }
            // ---------------------------------------------------------
            //	名稱: SizeWindow
            //	功能: 伸展或收縮對話框
            //	變量: ReduceHeight-收縮高度,bExtend-是否伸展
            //	返回: 無
            //	編寫: 徐景周,2002.4.8
            // ---------------------------------------------------------
            void CAbout::SizeWindow(int ReduceHeight, bool bExtend)
            {
            CRect rc;
            GetWindowRect(&rc);
            if(bExtend)
            {
            for (int i= 0; i < ReduceHeight; i++)
            {
            rc.bottom--;
            MoveWindow(&rc);
            }
            }
            else
            {
            for (int i= 0; i < ReduceHeight; i++)
            {
            rc.bottom++;
            MoveWindow(&rc);
            }
            }
            }
            
            6. 如何讓對話框回到屏幕中來
            當對話框被拖離屏幕時,可用下面代碼使其回到屏幕中。
            SendMessage(DM_REPOSITION);
            注:它必須是頂端窗口且不是child窗口。

            7. 如何給對話框添加或去掉最大/最小化按鈕
            在OnCreate()或OnInitDialog() 改變其顯示風格既可。
            int CTest_deleteDlg::OnCreate(LPCREATESTRUCT lpCreateStruct)
            {
            if (CDialog::OnCreate(lpCreateStruct) == -1)
            return -1;
            // TODO: Add your specialized creation code here
            SetWindowLong(this->m_hWnd,GWL_STYLE,
            GetWindowLong(this->m_hWnd,GWL_STYLE) |
            WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
            return 0;
            }
            
            或用:
            ModifyStyle (NULL, WS_MAXIMIZEBOX);
            8. 改變鼠標指針
            可以在OnSetCursor中實現(xiàn).
            BOOL CTest_deleteDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
            {
            // TODO: Add your message handler code here and/or call default
            SetCursor(AfxGetApp()->LoadStandardCursor(IDC_UPARROW));
            // Now we return instead of calling the base class
            return 0;
            // return CDialog::OnSetCursor(pWnd, nHitTest, message);
            }
            
            9. 改變對話框的前景和背景色
            可以在InitInstance()中實現(xiàn)。
            //紅色背景、綠色前景
            SetDialogBkColor(RGB(255,0,0),RGB(0,255,0));
            
            10. 在任務條上不顯示圖標
            先從CWinApp繼承類中建立一個不顯示的頂級窗口.
            CFrameWnd *abc=new CFrameWnd();
            abc->Create(0,0,WS_OVERLAPPEDWINDOW);
            CNoTaskBarIconDlg dlg(abc);
            m_pMainWnd = &dlg;
            int nResponse = dlg.DoModal();
            if (nResponse == IDOK)
            {
            }
            else if (nResponse == IDCANCEL)
            {
            }
            delete abc;
            
            在 OnInitDialog中修改顯示風格 WS_EX_APPWINDOW.
            BOOL CNoTaskBarIconDlg::OnInitDialog()
            {
            CDialog::OnInitDialog();
            ModifyStyleEx(WS_EX_APPWINDOW,0);
            SetIcon(m_hIcon, TRUE);  // Set big icon
            SetIcon(m_hIcon, FALSE); // Set small icon
            // TODO: Add extra initialization here
            return TRUE;  // return TRUE  unless you set the focus to a control
            }
            
            11. 加入上、下文幫助
            在 OnInitDialog 修改顯示風格,加入上、下文HLP幫助顯示.
            BOOL HelpDialog::OnInitDialog()
            {
            ModifyStyleEx(0, WS_EX_CONTEXTHELP);
            return CDialog::OnInitDialog();
            }
            
            重載OnHelpInfo(...),用顯示相關幫助信息
            BOOL HelpDialog::OnHelpInfo(HELPINFO* pHelpInfo)
            {
            short state = GetKeyState (VK_F1);
            if (state < 0)   // F1 key is down, get help for the dialog
            return CDialog::OnHelpInfo(pHelpInfo);
            else
            {    // F1 key not down, get help for specific control
            if (pHelpInfo->dwContextId)
            WinHelp (pHelpInfo->dwContextId,
            HELP_CONTEXTPOPUP);
            return TRUE;
            }
            }
            
            posted @ 2009-06-06 19:09 wrh 閱讀(508) | 評論 (0)編輯 收藏

            在昨天屏蔽鍵盤和鼠標事件的的基礎上繼續(xù)做的深加工,還是在OnInDialog()里面添加代碼

            這次要用到的函數(shù)是:BOOL SetWindowPos( const CWnd* pWndInsertAfter, int x, int y, int cx, int cy, UINT nFlags );

            這個函數(shù)是用來確定程序窗口是不是頂置。

            const CWnd* pWndInsertAfter 這個參數(shù)就是確定要不要頂置的參數(shù)。在MSDN里面它的可選參數(shù)有下:

          1. wndBottom   
          2. wndTop  
          3. wndTopMost   
          4. wndNoTopMost

            今天要用的參數(shù)是wndTopMost這個參數(shù)意思是不管你怎么弄程序窗口都在最頂端。

            int x, int y, 參數(shù)是你要放置在屏幕的位置

            int cx, int cy 窗口的大小,一般就調(diào)用系統(tǒng)窗口大小就行。

            UINT nFlags SWP_SHOWWINDOW 我們用他的這個值,意思是顯示窗口

            調(diào)用系統(tǒng)屏幕大小函數(shù)是GetSystemMetrics(int index); 里面參數(shù)可以是SM_CXSCREEN和CY_SCREEN分別表示X、Y最大值

            好 ,下面就是代碼

            int cxScreen,cyScreen;

            cxScreen=GetSystemMetrics(SM_CXSCREEN);

            cyScreen=GetSystemMetrics(SM_CYSCREEN);

            SetWindowPos(&wndTopMost,0,0,cxScreen,cyScreen,SWP_SHOWWINDOW);

             

            可以隨便創(chuàng)建一個基于對話框的MFC程序,然后在OnInitDialog()里面加上這段代碼。試試看吧

          5. posted @ 2009-06-06 19:03 wrh 閱讀(5262) | 評論 (0)編輯 收藏
            僅列出標題
            共25頁: First 11 12 13 14 15 16 17 18 19 Last 

            導航

            <2025年5月>
            27282930123
            45678910
            11121314151617
            18192021222324
            25262728293031
            1234567

            統(tǒng)計

            常用鏈接

            留言簿(19)

            隨筆檔案

            文章檔案

            收藏夾

            搜索

            最新評論

            閱讀排行榜

            評論排行榜

            99999久久久久久亚洲| 女人香蕉久久**毛片精品| 国内精品伊人久久久久777| 久久人人爽人人爽人人爽| 久久精品午夜一区二区福利| 国产亚洲精午夜久久久久久| 久久热这里只有精品在线观看| 久久99国产精品久久久| 亚洲精品国产第一综合99久久| 久久精品国产99久久无毒不卡| 久久艹国产| 精品午夜久久福利大片| 久久亚洲中文字幕精品一区| 欧美久久综合性欧美| 香蕉久久夜色精品升级完成| 久久99精品国产99久久6| 久久精品无码专区免费青青| 人妻无码久久精品| 日本道色综合久久影院| 亚洲精品无码久久久久去q| 少妇久久久久久被弄到高潮| 国产日产久久高清欧美一区| 久久婷婷色香五月综合激情 | 一本一道久久精品综合| 久久久午夜精品福利内容| 久久精品亚洲精品国产欧美| 久久久精品国产sm调教网站| 精品人妻伦九区久久AAA片69| 人人狠狠综合久久亚洲高清| 精品综合久久久久久88小说| 91久久精品国产免费直播| 久久免费视频观看| 欧美精品一区二区精品久久| 国产精品久久久久久久久| 国产高潮国产高潮久久久| 国产aⅴ激情无码久久| 尹人香蕉久久99天天拍| 亚洲精品NV久久久久久久久久| 久久久久亚洲AV成人网人人网站| 欧美久久综合性欧美| 久久久精品视频免费观看|