Share via


TN033:MFC 的 DLL 版本

此附注說明如何使用 MFCxx.DLLMFCxxD.DLL (其中 xx 是 MFC 版本號碼) 與 MFC 應用程式和 MFC 擴充功能 DLL 共用的動態連結程式庫。 如需一般 MFC DLL 的詳細資訊,請參閱 使用 MFC 作為 DLL 的一部分。

此技術注意事項涵蓋 DLL 的三個層面。 最後兩個適用于更進階的使用者:

如果您想要使用 MFC 建置可與非 MFC 應用程式搭配使用的 DLL(稱為一 般 MFC DLL ),請參閱 技術附注 11

MFCxx.DLL 支援概觀:術語和檔案

一般 MFC DLL :您可以使用一般 MFC DLL,使用某些 MFC 類別來建置獨立 DLL。 跨應用程式/DLL 界限的介面是 「C」 介面,用戶端應用程式不一定是 MFC 應用程式。

一般 MFC DLL 是 MFC 1.0 中支援的 DLL 版本。 其描述于 技術附注 11 和 MFC 進階概念範例 DLLScreenCap

注意

自 Visual C++ 4.0 版起,USRDLL 一詞 已經過時,並且已由靜態連結至 MFC 的一般 MFC DLL 取代。 您也可以建置一般 MFC DLL,以動態方式連結至 MFC。

MFC 3.0 (和更新版本)支援一般 MFC DLL,其中包含所有新功能,包括 OLE 和 Database 類別。

AFXDLL :也稱為 MFC 程式庫的共用版本。 這是 MFC 2.0 中新增的新 DLL 支援。 MFC 程式庫本身位於數個 DLL 中(如下所述)。 用戶端應用程式或 DLL 會動態連結它所需的 DLL。 跨應用程式/DLL 界限的介面是 C++/MFC 類別介面。 用戶端應用程式必須是 MFC 應用程式。 此 DLL 支援所有 MFC 3.0 功能(例外狀況:資料庫類別不支援 UNICODE)。

注意

從 Visual C++ 4.0 版開始,這種 DLL 類型稱為「擴充 DLL」。

此附注將用來 MFCxx.DLL 參考整個 MFC DLL 集合,其中包括:

  • 偵錯: MFCxxD.DLL (結合)和 MFCSxxD.LIB (靜態)。

  • 發行: MFCxx.DLL (合併)和 MFCSxx.LIB (靜態)。

  • Unicode 偵錯: MFCxxUD.DLL (結合)和 MFCSxxD.LIB (靜態)。

  • Unicode 版本: MFCxxU.DLL (合併)和 MFCSxxU.LIB (靜態)。

注意

連結 MFCSxx[U][D].LIB 庫會與 MFC 共用 DLL 搭配使用。 這些程式庫包含的程式碼必須以靜態方式連結至應用程式或 DLL。

應用程式會連結至對應的匯入程式庫:

  • 調試: MFCxxD.LIB

  • 釋放: MFCxx.LIB

  • Unicode 偵錯: MFCxxUD.LIB

  • Unicode 版本: MFCxxU.LIB

MFC 擴充 DLL 是擴充 MFCxx.DLL 的 DLL (或其他 MFC 共用 DLL)。 在這裡,MFC 元件架構會開始執行。 如果您從 MFC 類別衍生有用的類別,或建置另一個類似 MFC 的工具組,您可以將它放在 DLL 中。 您的 DLL 會使用 MFCxx.DLL ,與最終用戶端應用程式一樣。 MFC 延伸模組 DLL 允許可重複使用的分葉類別、可重複使用的基類,以及可重複使用的檢視和檔類別。

優缺點

為何您應該使用 MFC 的共用版本

  • 使用共用程式庫可能會導致較小的應用程式。 (使用大部分 MFC 程式庫的最小應用程式小於 10K)。

  • MFC 的共用版本支援 MFC 擴充 DLL 和一般 MFC DLL。

  • 建置使用共用 MFC 程式庫的應用程式比靜態連結的 MFC 應用程式更快。 這是因為不需要連結 MFC 本身。 在連結器必須壓縮偵錯資訊的組建中 DEBUG ,尤其如此。 當您的應用程式連結到已經包含偵錯資訊的 DLL 時,要壓縮的偵錯資訊較少。

為何您不應該使用 MFC 的共用版本:

  • 寄送使用共用程式庫的應用程式需要您隨附 MFCxx.DLL 于程式和其他程式庫。 MFCxx.DLL 可以像許多 DLL 一樣自由轉散發,但您仍然必須在安裝程式中安裝 DLL。 此外,您必須寄送程式本身和 MFC DLL 所使用的其他可轉散發程式庫。

如何撰寫 MFC 擴充功能 DLL

MFC 擴充 DLL 是一個 DLL,其中包含用來擴充 MFC 類別功能的類別和函式。 MFC 擴充 DLL 會以應用程式使用共用 MFC DLL 的方式使用共用 MFC DLL,但有一些其他考慮:

  • 建置程式類似于建置使用共用 MFC 程式庫的應用程式,其中包含一些額外的編譯器和連結器選項。

  • MFC 延伸模組 DLL 沒有 CWinApp 衍生類別。

  • MFC 延伸模組 DLL 必須提供特殊的 DllMain 。 AppWizard 提供您可以修改的 DllMain 函式。

  • 如果 MFC 擴充功能 DLL 將類型或資源匯出 CRuntimeClass 至應用程式,MFC 擴充 DLL 通常會提供初始化常式來建立 CDynLinkLibrary 。 如果 MFC 延伸模組 DLL 必須維護個別應用程式資料,可以使用 的 CDynLinkLibrary 衍生類別。

以下將詳細說明這些考慮。 另請參閱 MFC 進階概念範例 DLLHUSK 。 其示範如何:

  • 使用共用程式庫建置應用程式。 ( DLLHUSK.EXE 是 MFC 應用程式,可動態連結至 MFC 程式庫和其他 DLL。

  • 建置 MFC 擴充功能 DLL。 (它顯示建置 MFC 擴充 DLL 時使用的特殊 _AFXEXT 旗標。

  • 建置 MFC 擴充 DLL 的兩個範例。 其中一個顯示具有有限匯出的 MFC 擴充 DLL 基本結構(TESTDLL1),另一個則顯示匯出整個類別介面 (TESTDLL2)。

用戶端應用程式和任何 MFC 擴充 DLL 都必須使用相同的版本 MFCxx.DLL 。 請遵循 MFC DLL 的慣例,並提供 MFC 擴充功能 DLL 的偵錯和發行版本本。 /release 這種做法可讓用戶端程式同時建置其應用程式的偵錯和發行版本本,並將它們與所有 DLL 的適當偵錯或發行版本本連結。

注意

由於 C++ 名稱管理與匯出問題,因此不同平臺之相同 DLL 偵錯和發行版本本的 MFC 擴充 DLL 匯出清單可能會不同。 發行 MFCxx.DLL 有大約 2000 個匯出進入點;偵錯 MFCxxD.DLL 有大約 3000 個匯出的進入點。

記憶體管理的快速注意事項

標題為「記憶體管理」一節,接近此技術附注的結尾,描述使用共用版本的 MFC 實 MFCxx.DLL 作。 這裡將說明您只需要實作 MFC 擴充功能 DLL 所需的資訊。

MFCxx.DLL 和載入用戶端應用程式位址空間的所有 MFC 擴充 DLL 都會使用相同的記憶體配置器、資源載入和其他 MFC「全域」狀態,就像它們位於相同的應用程式中一樣。 這很重要,因為靜態連結至 MFC 的非 MFC DLL 程式庫和一般 MFC DLL 會執行完全相同的動作:每個 DLL 都會配置出自己的記憶體集區。

如果 MFC 延伸模組 DLL 配置記憶體,則該記憶體可以自由混合任何其他應用程式佈建的物件。 此外,如果使用共用 MFC 程式庫的應用程式當機,作業系統會維護共用 DLL 的任何其他 MFC 應用程式的完整性。

同樣地,其他「全域」MFC 狀態,例如要從中載入資源的目前可執行檔,也會在用戶端應用程式、所有 MFC 擴充 DLL 和 MFCxx.DLL 本身之間共用。

建置 MFC 擴充功能 DLL

您可以使用 AppWizard 來建立 MFC 延伸模組 DLL 專案,並自動產生適當的編譯器和連結器設定。 它也會產生您可以修改的 DllMain 函式。

如果您要將現有專案轉換成 MFC 擴充功能 DLL,請從使用共用版本的 MFC 建置的標準設定開始。 然後進行下列變更:

  • 將 新增 /D_AFXEXT 至編譯器旗標。 在 [專案屬性] 對話方塊中,選取 [C/C++ > 預處理器 ] 類別。 新增 _AFXEXT 至 [ 定義宏] 欄位,以分號分隔每個專案。

  • 移除編譯器 /Gy 參數。 在 [專案屬性] 對話方塊中,選取 [C/C++ > 程式碼產生] 類別。 請確定 [啟用函式層級連結 ] 屬性未啟用。 此設定可讓您更輕鬆地匯出類別,因為連結器不會移除未參考的函式。 如果原始專案建置了靜態連結至 MFC 的一般 MFC DLL,請將 (或) 編譯器選項變更 /MT/MD (或 /MTd/MDd )。

  • 使用 LINK 選項建置匯出程式庫 /DLL 。 當您建立新的目標,並將 Win32 Dynamic-Link 程式庫指定為目標型別時,就會設定此選項。

變更標頭檔

MFC 擴充 DLL 的一般目標是將一些常見功能匯出至一或多個可使用該功能的應用程式。 基本上,DLL 會匯出類別和全域函式,以供用戶端應用程式使用。

若要確保每個成員函式都適當地標示為匯入或匯出,請使用特殊宣告 __declspec(dllexport)__declspec(dllimport) 。 當用戶端應用程式使用您的類別時,您希望它們宣告為 __declspec(dllimport) 。 當 MFC 擴充功能 DLL 本身建置時,函式應該宣告為 __declspec(dllexport) 。 建置的 DLL 也必須匯出函式,讓用戶端程式可以在載入時系結至函式。

若要匯出整個類別,請在 AFX_EXT_CLASS 類別定義中使用 。 架構會將這個宏 __declspec(dllexport) 定義為何時 _AFXDLL_AFXEXT 已定義,但將它 __declspec(dllimport) 定義為未定義時 _AFXEXT_AFXEXT 只有在建置 MFC 擴充功能 DLL 時才會定義。 例如:

class AFX_EXT_CLASS CExampleExport : public CObject
{ /* ... class definition ... */ };

未匯出整個類別

有時候,您可能只想要匯出類別的個別必要成員。 例如,如果您匯出 CDialog 衍生類別,您可能只需要匯出建構函式和 DoModal 呼叫。 您可以使用 DLL 的 DEF 檔案匯出這些成員,但也可以在您需要匯出的個別成員上以完全相同的方式使用 AFX_EXT_CLASS

例如:

class CExampleDialog : public CDialog
{
public:
    AFX_EXT_CLASS CExampleDialog();
    AFX_EXT_CLASS int DoModal();
    // rest of class definition
    // ...
};

當您這樣做時,可能會發生其他問題,因為您不會匯出 類別的所有成員。 問題在於 MFC 宏的運作方式。 MFC 的數個協助程式宏實際上會宣告或定義資料成員。 您的 DLL 也需要匯出這些資料成員。

例如,建置 MFC 擴充 DLL 時,DECLARE_DYNAMIC宏的定義如下:

#define DECLARE_DYNAMIC(class_name) \
protected: \
    static CRuntimeClass* PASCAL _GetBaseClass(); \
    public: \
    static AFX_DATA CRuntimeClass class##class_name; \
    virtual CRuntimeClass* GetRuntimeClass() const; \

開始宣告類別內靜態物件的行 static AFX_DATA 。 若要正確匯出這個類別並從用戶端 EXE 存取執行時間資訊,您需要匯出此靜態物件。 因為靜態物件是以 修飾詞 AFX_DATA 宣告,因此您只需要在建置 DLL 時定義 AFX_DATA__declspec(dllexport) 。 當您建置用戶端可執行檔時,請將其 __declspec(dllimport) 定義為 。

如上所述, AFX_EXT_CLASS 已以此方式定義。 您只需要重新定義 AFX_DATA ,才能與 AFX_EXT_CLASS 類別定義周圍相同。

例如:

#undef  AFX_DATA
#define AFX_DATA AFX_EXT_CLASS
class CExampleView : public CView
{
    DECLARE_DYNAMIC()
    // ... class definition ...
};
#undef  AFX_DATA
#define AFX_DATA

MFC 一律在其 AFX_DATA 宏內定義的資料項目上使用 符號,因此這項技術適用于所有這類案例。 例如,其適用于DECLARE_MESSAGE_MAP。

注意

如果您要匯出整個類別,而不是類別的選取成員,則會自動匯出靜態資料成員。

您可以使用相同的技術,針對使用DECLARE_SERIAL和IMPLEMENT_SERIAL宏的類別,自動匯出 CArchive 擷取運算子。 使用下列程式碼括住類別宣告(位於標頭檔)以匯出封存運算子:

#undef AFX_API
#define AFX_API AFX_EXT_CLASS

/* your class declarations here */

#undef AFX_API
#define AFX_API

_AFXEXT的限制

只要您沒有多層 MFC 延伸模組 DLL,您就可以使用 _AFXEXT MFC 擴充功能 DLL 的前置處理器符號。 如果您有 MFC 擴充 DLL 呼叫或衍生自您自己的 MFC 延伸模組 DLL,然後衍生自 MFC 類別,您必須使用自己的預處理器符號來避免模棱兩可。

問題是,在 Win32 中,您必須明確宣告任何資料, __declspec(dllexport) 以便從 DLL 匯出資料,並從 __declspec(dllimport) DLL 匯入資料。 當您定義 _AFXEXT 時,MFC 標頭會確定 AFX_EXT_CLASS 已正確定義。

當您有多個圖層時,有一個符號,例如 AFX_EXT_CLASS 還不夠:MFC 延伸模組 DLL 可能會匯出自己的類別,也可以從另一個 MFC 擴充 DLL 匯入其他類別。 若要解決此問題,請使用特殊的預處理器符號,指出您要建置 DLL 本身,而不是使用 DLL。 例如,假設有兩個 MFC 擴充 DLL、 A.DLLB.DLL 。 它們分別在 和 B.HA.H 匯出一些類別。 B.DLL 會使用 來自 A.DLL 的類別。 標頭檔看起來會像這樣:

/* A.H */
#ifdef A_IMPL
    #define CLASS_DECL_A   __declspec(dllexport)
#else
    #define CLASS_DECL_A   __declspec(dllimport)
#endif

class CLASS_DECL_A CExampleA : public CObject
{ /* ... class definition ... */ };

/* B.H */
#ifdef B_IMPL
    #define CLASS_DECL_B   __declspec(dllexport)
#else
    #define CLASS_DECL_B   __declspec(dllimport)
#endif

class CLASS_DECL_B CExampleB : public CExampleA
{ /* ... class definition ... */ };

建置時 A.DLL ,會使用 /DA_IMPL 建置和 建置時 B.DLL ,會使用 /DB_IMPL 來建置。 針對每個 DLL 使用不同的符號, CExampleB 匯出並在 CExampleAB.DLL 置 時匯入。 CExampleA 建置 A.DLL 和匯入時,由 B.DLL 或其他用戶端使用時匯出。

使用內 AFX_EXT_CLASS 建和 _AFXEXT 預處理器符號時,無法完成這種類型的分層。 上述技術會以 MFC 的相同方式解決這個問題。 MFC 會在建置其 OLE、Database 和 Network MFC 擴充 DLL 時使用這項技術。

仍然不匯出整個類別

同樣地,當您未匯出整個類別時,必須特別小心。 請確定 MFC 宏所建立的必要資料項目已正確匯出。 您可以重新定義 AFX_DATA 特定類別的宏來執行此動作。 每當您未匯出整個類別時,請重新定義它。

例如:

// A.H
#ifdef A_IMPL
    #define CLASS_DECL_A  _declspec(dllexport)
#else
    #define CLASS_DECL_A  _declspec(dllimport)
#endif

#undef  AFX_DATA
#define AFX_DATA CLASS_DECL_A

class CExampleA : public CObject
{
    DECLARE_DYNAMIC()
    CLASS_DECL_A int SomeFunction();
    // class definition
    // ...
};

#undef AFX_DATA
#define AFX_DATA

DllMain

以下是您應該在 MFC 擴充 DLL 的主要原始程式檔中放置的程式碼。 它應該在標準包含之後。 當您使用 AppWizard 建立 MFC 擴充 DLL 的入門檔案時,它會為您提供 DllMain

#include "afxdllx.h"

static AFX_EXTENSION_MODULE extensionDLL;

extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID)
{
   if (dwReason == DLL_PROCESS_ATTACH)
   {
      // MFC extension DLL one-time initialization
      if (!AfxInitExtensionModule(
             extensionDLL, hInstance))
         return 0;

      // TODO: perform other initialization tasks here
   }
   else if (dwReason == DLL_PROCESS_DETACH)
   {
      // MFC extension DLL per-process termination
      AfxTermExtensionModule(extensionDLL);

      // TODO: perform other cleanup tasks here
   }
   return 1;   // ok
}

AfxInitExtensionModule呼叫 會擷取模組的執行時間類別( CRuntimeClass 結構)及其物件處理站( COleObjectFactory objects),以便稍後在 CDynLinkLibrary 建立物件時使用。 允許 MFC 清除 MFC 延伸模組 DLL 時,每個進程卸離時(當進程結束時,或 DLL 被 FreeLibrary 呼叫卸載時)從 MFC 延伸模組 DLL 卸載時,就會進行 (選擇性) 呼叫 AfxTermExtensionModule 。 由於大部分的 MFC 擴充 DLL 都不會動態載入(通常透過匯入程式庫連結),所以通常不需要呼叫 AfxTermExtensionModule

如果您的應用程式會以動態方式載入並釋放 MFC 擴充 DLL,請務必呼叫 AfxTermExtensionModule ,如上所示。 此外,如果您的應用程式使用多個執行緒,或動態載入 MFC 擴充 DLL,請務必使用 AfxLoadLibraryAfxFreeLibrary (而不是 Win32 函 LoadLibrary 式和 FreeLibrary )。 使用 AfxLoadLibraryAfxFreeLibrary 確保載入和卸載 MFC 擴充功能 DLL 時所執行的啟動和關機程式碼不會損毀全域 MFC 狀態。

標頭檔 AFXDLLX.H 包含 MFC 擴充 DLL 中所使用之結構的特殊定義,例如 和 CDynLinkLibrary 的定義 AFX_EXTENSION_MODULE

全域 延伸模組DLL 必須宣告為如下所示。 不同于 16 位版本的 MFC,您可以在這段期間配置記憶體並呼叫 MFC 函式,因為 MFCxx.DLL 會在呼叫時 DllMain 完全初始化 。

共用資源和類別

簡單的 MFC 擴充 DLL 只需要將一些低頻寬函式匯出至用戶端應用程式,而僅此而已。 更多使用者介面密集 DLL 可能會想要將資源和 C++ 類別匯出至用戶端應用程式。

匯出資源是透過資源清單來完成。 在每個應用程式中都是一個單向連結的物件 CDynLinkLibrary 清單。 尋找資源時,載入資源的大部分標準 MFC 實作會先查看目前的資源模組 ( AfxGetResourceHandle ),如果找不到,則會逐步解說嘗試載入所要求資源的物件清單 CDynLinkLibrary

指定 C++ 類別名稱的 C++ 物件動態建立很類似。 MFC 物件還原序列化機制必須註冊所有 CRuntimeClass 物件,以便根據先前儲存的物件,動態建立所需類型的 C++ 物件來重新建構。

如果您想要讓用戶端應用程式在 MFC 擴充 DLL 中使用類別 DECLARE_SERIAL ,則必須匯出類別,讓用戶端應用程式看見這些類別。 它也是通過走 CDynLinkLibrary 清單來完成的。

在 MFC 進階概念範例 DLLHUSK 中,清單看起來會像這樣:

head ->   DLLHUSK.EXE   - or - DLLHUSK.EXE
               |                    |
          TESTDLL2.DLL         TESTDLL2.DLL
               |                    |
          TESTDLL1.DLL         TESTDLL1.DLL
               |                    |
               |                    |
           MFC90D.DLL           MFC90.DLL

專案 MFCxx.DLL 通常會在資源和類別清單中最後一個。 MFCxx.DLL 包含所有標準 MFC 資源,包括所有標準命令識別碼的提示字串。 將它放在清單的結尾可讓 DLL 和用戶端應用程式本身依賴 中的 MFCxx.DLL 共用資源,而不是有自己的複本。

將所有 DLL 的資源和類別名稱合併到用戶端應用程式的名稱空間中,有一個缺點是您必須小心您挑選的識別碼或名稱。 您可以將資源或 CDynLinkLibrary 物件匯出至用戶端應用程式,以停用此功能。 此 DLLHUSK 範例會使用多個標頭檔來管理共用資源名稱空間。 如需使用共用資源檔的詳細資訊,請參閱 技術附注 35

初始化 DLL

如上所述,您通常會想要建立 CDynLinkLibrary 物件,將資源和類別匯出至用戶端應用程式。 您必須提供匯出的進入點來初始化 DLL。 至少,這是一個 void 常式,不採用任何引數並傳回任何內容,但它可以是任何您想要的。

如果您使用此方法,每個想要使用 DLL 的用戶端應用程式都必須呼叫此初始化常式。 您也可以在呼叫 AfxInitExtensionModule 之後,于 中 DllMain 配置此 CDynLinkLibrary 物件。

初始化常式必須在目前應用程式的堆積中建立 CDynLinkLibrary 物件,並聯機到 MFC 擴充功能 DLL 資訊。 您可以定義如下的函式來執行此動作:

extern "C" extern void WINAPI InitXxxDLL()
{
    new CDynLinkLibrary(extensionDLL);
}

此範例中的常式名稱 InitXxxDLL 可以是您想要的任何專案。 它不需要 extern "C" 是 ,但它可讓匯出清單更容易維護。

注意

如果您使用一般 MFC DLL 的 MFC 擴充 DLL,則必須匯出此初始化函式。 使用任何 MFC 延伸模組 DLL 類別或資源之前,必須先從一般 MFC DLL 呼叫此函式。

匯出專案

匯出類別的簡單方式是在 __declspec(dllimport) 您想要匯出的每個類別和全域函式上使用 和 __declspec(dllexport) 。 這比較容易,但比命名 DEF 檔案中的每個進入點更有效率,如下所述。 這是因為您對匯出函式的控制較少。 而且,您無法依序數匯出函式。 TESTDLL1和TESTDLL2使用此方法匯出其專案。

更有效率的方法是在 DEF 檔案中命名專案,以匯出每個專案。 這個方法由 MFCxx.DLL 使用。 由於我們會選擇性地從 DLL 匯出,因此我們必須決定我們想要匯出的特定介面。 這很困難,因為您必須以 DEF 檔案中的專案形式指定連結器的完整名稱。 除非您真的需要有符號連結,否則請勿匯出任何 C++ 類別。

如果您之前曾嘗試使用 DEF 檔案匯出 C++ 類別,您可能想要開發工具來自動產生此清單。 您可以使用雙階段連結程式來完成。 一旦未匯出即可連結 DLL,並允許連結器產生 MAP 檔案。 MAP 檔案包含應該匯出的函式清單。 透過某些重新排列,您可以使用它來產生 DEF 檔案的 EXPORT 專案。 針對 和 OLE 和 Database MFC 擴充 DLL 的匯出清單 MFCxx.DLL ,數位為數千個,是透過這類程式產生(雖然它不是完全自動的,而且需要一段時間每一次手動調整一次)。

CWinApp 與 CDynLinkLibrary

MFC 延伸模組 DLL 沒有 CWinApp 自己的衍生物件。 相反地,它必須使用 CWinApp 用戶端應用程式的衍生物件。 這表示用戶端應用程式擁有主要訊息幫浦、閒置迴圈等等。

如果您的 MFC 延伸模組 DLL 需要維護每個應用程式的額外資料,您可以從 衍生新的類別 CDynLinkLibrary ,並在上述常式中 InitXxxDLL 建立它。 執行時,DLL 可以檢查目前應用程式的物件清單,以尋找該特定 MFC 擴充 DLL 的物件清單 CDynLinkLibrary

在 DLL 實作中使用資源

如上所述,預設資源負載會逐步解說物件清單 CDynLinkLibrary ,以尋找具有要求資源的第一個 EXE 或 DLL。 所有 MFC API 和所有內部程式碼都會使用 AfxFindResourceHandle 來逐步執行資源清單,以尋找任何資源,無論資源位於何處。

如果您想要只從特定位置載入資源,請使用 API AfxGetResourceHandleAfxSetResourceHandle 儲存舊的控制碼並設定新的控制碼。 返回用戶端應用程式之前,請務必先還原舊的資源控制碼。 範例TESTDLL2會使用此方法明確載入功能表。

執行清單有一些缺點:稍微慢一點,而且需要管理資源識別碼範圍。 它的優點是連結至數個 MFC 擴充 DLL 的用戶端應用程式可以使用任何 DLL 提供的資源,而不需要指定 DLL 實例控制碼。 AfxFindResourceHandle 是用來步行資源清單來尋找指定相符專案的 API。 它會取得資源的名稱和類型,並傳回資源控制碼,其中會先找到資源或 Null。

撰寫使用 DLL 版本的應用程式

應用程式需求

使用 MFC 共用版本的應用程式必須遵循一些基本規則:

  • 它必須有 CWinApp 物件,並遵循訊息幫浦的標準規則。

  • 它必須使用一組必要的編譯器旗標進行編譯(請參閱下文)。

  • 它必須與 MFCxx 匯入程式庫連結。 藉由設定必要的編譯器旗標,MFC 標頭會在連結時決定應用程式應該連結的程式庫。

  • 若要執行可執行檔, MFCxx.DLL 必須在路徑或 Windows 系統目錄中。

使用開發環境建置

如果您使用內部 makefile 搭配大部分的標準預設值,您可以輕鬆地變更專案以建置 DLL 版本。

下列步驟假設您有一個正確運作的 MFC 應用程式已連結至 NAFXCWD.LIB [偵錯] 和 NAFXCW.LIB [發行] ,而您想要將它轉換成使用 MFC 程式庫的共用版本。 您正在執行 Visual Studio 環境,並具有內部專案檔。

  1. 在 [ 專案] 功能表上,選取 [ 屬性 ]。 在 [專案預設值 ] 下的 [ 一般 ] 頁面中,將 Microsoft Foundation Classes 設定為 在共用 DLL 中使用 MFC (MFCxx(d)。dll。

使用 NMAKE 建置

如果您使用編譯器的外部 makefile 功能,或直接使用 NMAKE,則必須編輯 makefile 以支援必要的編譯器和連結器選項。

必要的編譯器旗標:

  • /D_AFXDLL /MD /D_AFXDLL

標準 MFC 標頭需要 _AFXDLL 定義符號。

  • /MD 應用程式必須使用 C 執行時間程式庫的 DLL 版本。

所有其他編譯器旗標都遵循 MFC 預設值(例如, _DEBUG 針對偵錯)。

編輯程式庫的連結器清單。 將 NAFXCWD.LIB 變更為 MFCxxD.LIB ,並將 NAFXCW.LIB 變更為 MFCxx.LIB。 把 LIBC.LIB 替換為 MSVCRT.LIB。 如同任何其他 MFC 程式庫一樣,請務必 MFCxxD.LIB 放在 任何 C 執行時間程式庫之前

選擇性地將 新增 /D_AFXDLL 至您的發行和偵錯資源編譯器選項(實際使用 /R 編譯資源的選項)。 此選項會藉由共用 MFC DLL 中存在的資源,讓您的最終可執行檔更小。

進行這些變更之後,需要完整重建。

建置範例

大部分的 MFC 範例程式都可以從 Visual C++ 或從命令列的共用 NMAKE 相容 MAKEFILE 建置。

若要將上述任何範例轉換成 , MFCxx.DLL 您可以將 MAK 檔案載入 Visual C++ 並設定 Project 選項,如上所述。 如果您使用 NMAKE 組建,您可以在 NMAKE 命令列上指定 AFXDLL=1 ,而且會使用共用 MFC 程式庫來建置範例。

MFC 進階概念範例 DLLHUSK 是以 MFC 的 DLL 版本所建置。 此範例不僅說明如何建置連結 MFCxx.DLL 的應用程式,也會說明 MFC DLL 封裝選項的其他功能,例如本技術注意事項稍後所述的 MFC 擴充 DLL。

封裝注意事項

DLL ( MFCxx.DLLMFCxxU.DLL ) 的版本可以自由轉散發。 DLL 的偵錯版本無法自由轉散發,而且應該只在應用程式開發期間使用。

偵錯 DLL 會提供偵錯資訊。 藉由使用 Visual C++ 偵錯工具,您可以追蹤應用程式的執行和 DLL。 發行 DLL ( MFCxx.DLLMFCxxU.DLL ) 不包含偵錯資訊。

如果您自訂或重建 DLL,則應該將其稱為 「MFCxx」 以外的專案。 MFC SRC 檔案 MFCDLL.MAK 描述組建選項,並包含重新命名 DLL 的邏輯。 需要重新命名檔案,因為這些 DLL 可能會由許多 MFC 應用程式共用。 讓自訂版本的 MFC DLL 取代系統上安裝的 MFC DLL,可能會中斷另一個使用共用 MFC DLL 的 MFC 應用程式。

不建議重建 MFC DLL。

如何實作 MFCxx.DLL

下一節說明如何實作 MFC DLL ( MFCxx.DLLMFCxxD.DLL )。 如果您只想搭配應用程式使用 MFC DLL,這裡的詳細資料也並不重要。 此處的詳細資料對於瞭解如何撰寫 MFC 擴充功能 DLL 並不重要,但瞭解此實作可協助您撰寫自己的 DLL。

實作概觀

MFC DLL 實際上是 MFC 延伸模組 DLL 的特殊案例,如上所述。 其具有大量類別的匯出。 我們在 MFC DLL 中做了一些額外的工作,使其比一般 MFC 擴充 DLL 更特別。

Win32 會執行大部分的工作

16 位版本的 MFC 需要一些特殊技術,包括堆疊區段上的個別應用程式資料、約 80x86 元件程式碼所建立的特殊區段、每個進程例外狀況內容和其他技術。 Win32 直接支援 DLL 中的每個進程資料,這是您大部分時間想要的資料。 在大多數情況下 MFCxx.DLL ,只會 NAFXCW.LIB 封裝在 DLL 中。 如果您查看 MFC 原始程式碼,您會發現幾個 #ifdef _AFXDLL 案例,因為不需要建立許多特殊案例。 在 Windows 3.1 上特別處理 Win32 的特殊案例(也稱為 Win32s)。 Win32s 不支援個別進程 DLL 資料。 MFC DLL 必須使用執行緒本機儲存體 (TLS) WIN32 API 來取得處理本機資料。

對程式庫來源、其他檔案的影響

版本對一般 MFC 類別庫來源和標頭的影響 _AFXDLL 相對較小。 主要標頭包含 AFXWIN.H 特殊版本檔案 ( AFXV_DLL.H ) 和額外的標頭檔 ( AFXDLL_.H )。 標頭 AFXDLL_.H 包含 CDynLinkLibrary 應用程式和 MFC 擴充 DLL 的類別和其他實作 _AFXDLL 詳細資料。 標頭 AFXDLLX.H 提供用於建置 MFC 擴充 DLL(如需詳細資料,請參閱上方)。

MFC SRC 中 MFC 程式庫的一般來源在 #ifdef 下 _AFXDLL 會有一些額外的條件式程式碼。 其他原始程式檔 ( DLLINIT.CPP ) 包含額外的 DLL 初始化程式碼,以及共用 MFC 版本的其他黏附。

為了建置 MFC 的共用版本,會提供其他檔案。 (如需如何建置 DLL 的詳細資訊,請參閱下方。

  • 兩個 DEF 檔案用於匯出偵錯 () 和發行 ( MFCxxD.DEFMFCxx.DEF ) 版本的 MFC DLL 進入點。

  • RC 檔案 ( MFCDLL.RC ) 包含所有標準 MFC 資源和 VERSIONINFO DLL 的資源。

  • 提供 CLW 檔案 ( MFCDLL.CLW ) 以允許使用 ClassWizard 流覽 MFC 類別。 此功能對 MFC 的 DLL 版本並不特別。

記憶體管理

使用 MFCxx.DLL 的應用程式會使用 由 提供的 MSVCRTxx.DLL 通用記憶體配置器,共用 C 執行時間 DLL。 應用程式、任何 MFC 擴充 DLL,以及 MFC DLL 都會使用此共用記憶體配置器。 透過使用共用 DLL 進行記憶體配置,MFC DLL 可以配置應用程式稍後釋放的記憶體,反之亦然。 因為應用程式和 DLL 都必須使用相同的配置器,所以您不應該覆寫 C++ 全域 operator newoperator delete 。 相同的規則適用于 C 執行時間記憶體配置常式的其餘部分(例如 mallocreallocfree 和其他)。

序數和類別__declspec(dllexport) 和 DLL 命名

我們不會使用 class__declspec(dllexport) C++ 編譯器的功能。 相反地,類別庫來源 ( MFCxx.DEFMFCxxD.DEF ) 會包含匯出清單。 只會匯出一組選取的進入點(函式和資料)。 不會匯出其他符號,例如 MFC 私人實作函式或類別。 所有匯出都是透過序數來完成,而不需要居民或非常駐名稱資料表中的字串名稱。

使用 class__declspec(dllexport) 可能是建置較小 DLL 的可行替代方案,但在 MFC 之類的大型 DLL 中,預設匯出機制具有效率和容量限制。

這一切的意義在於,我們可以封裝版本 MFCxx.DLL 中只有大約 800 KB 且不會危及大量執行或載入速度的功能。 MFCxx.DLL 如果未使用這項技術,則大小會更大 100 KB。 這項技術可讓您在 DEF 檔案結尾新增其他進入點。 它允許簡單的版本控制,而不會影響藉由序數匯出的速度和大小效率。 MFC 類別庫中的主要版本修訂將會變更程式庫名稱。 也就是說, MFC30.DLL 是包含 MFC 類別庫 3.0 版的可轉散發 DLL。 這個 DLL 的升級,例如,在假設的 MFC 3.1 中,DLL 會改為命名 MFC31.DLL 。 同樣地,如果您修改 MFC 原始程式碼以產生 MFC DLL 的自訂版本,請使用不同的名稱(最好是名稱中有一個沒有 「MFC」 的名稱)。

另請參閱

依編號顯示的技術提示
依分類區分的技術提示