TN033: wersja DLL biblioteki MFC

W tej notatce opisano sposób używania MFCxx.DLLMFCxxD.DLL bibliotek dynamicznych linków MFC i (gdzie xx jest numerem wersji MFC) udostępnionych bibliotek linków dynamicznych z aplikacjami MFC i bibliotekami DLL rozszerzeń MFC. Aby uzyskać więcej informacji na temat zwykłych bibliotek DLL MFC, zobacz Używanie MFC jako części biblioteki DLL.

Ta uwaga techniczna obejmuje trzy aspekty bibliotek DLL. Ostatnie dwa są przeznaczone dla bardziej zaawansowanych użytkowników:

Jeśli interesuje Cię tworzenie biblioteki DLL przy użyciu MFC, które mogą być używane z aplikacjami innych niż MFC (nazywanymi zwykłą biblioteką MFC DLL), zapoznaj się z uwagami technicznymi 11.

Omówienie obsługi biblioteki MFCxx.DLL: terminologia i pliki

Zwykła biblioteka MFC DLL: używasz regularnej biblioteki MFC DLL do kompilowania autonomicznej biblioteki DLL przy użyciu niektórych klas MFC. Interfejsy w granicach aplikacji/bibliotek DLL to interfejsy "C", a aplikacja kliencka nie musi być aplikacją MFC.

Zwykłe biblioteki DLL MFC są wersją bibliotek DLL obsługiwanych w MFC 1.0. Zostały one opisane w notatce technicznej 11 i przykładzie DLLScreenCapMFC Advanced Concepts .

Uwaga

Od wersji 4.0 języka Visual C++ termin USRDLL jest przestarzały i został zastąpiony zwykłą biblioteką MFC DLL, która statycznie łączy się z MFC. Możesz również utworzyć regularną bibliotekę DLL MFC, która dynamicznie łączy się z MFC.

Biblioteka MFC 3.0 (i nowsza) obsługuje zwykłe biblioteki MFC z wszystkimi nowymi funkcjami, w tym klasAMI OLE i Database.

AFXDLL: Nazywana również udostępnioną wersją bibliotek MFC. Jest to nowa obsługa bibliotek DLL dodana w MFC 2.0. Sama biblioteka MFC znajduje się w wielu bibliotekach DLL (opisanych poniżej). Aplikacja kliencka lub biblioteka DLL dynamicznie łączy wymagane biblioteki DLL. Interfejsy w granicach aplikacji/bibliotek DLL to interfejsy klas C++/MFC. Aplikacja kliencka MUSI być aplikacją MFC. Ta biblioteka DLL obsługuje wszystkie funkcje MFC 3.0 (wyjątek: unicode nie jest obsługiwany dla klas baz danych).

Uwaga

W wersji 4.0 programu Visual C++ ten typ biblioteki DLL jest określany jako "Biblioteka DLL rozszerzenia".

Ta uwaga będzie używana MFCxx.DLL do odwoływania się do całego zestawu bibliotek DLL MFC, który obejmuje:

  • Debugowanie: MFCxxD.DLL (połączone) i MFCSxxD.LIB (statyczne).

  • Wydanie: MFCxx.DLL (połączone) i MFCSxx.LIB (statyczne).

  • Debugowanie Unicode: MFCxxUD.DLL (połączone) i MFCSxxD.LIB (statyczne).

  • Wydanie Unicode: MFCxxU.DLL (połączone) i MFCSxxU.LIB (statyczne).

Uwaga

Biblioteki MFCSxx[U][D].LIB są używane w połączeniu z bibliotekami DLL udostępnionymi MFC. Te biblioteki zawierają kod, który musi być statycznie połączony z aplikacją lub biblioteką DLL.

Aplikacja łączy się z odpowiednimi bibliotekami importu:

  • Debugowania: MFCxxD.LIB

  • Wydania: MFCxx.LIB

  • Debugowanie Unicode: MFCxxUD.LIB

  • Wydanie Unicode: MFCxxU.LIB

Biblioteka DLL rozszerzenia MFC to biblioteka DLL rozszerzająca MFCxx.DLL (lub inne biblioteki DLL udostępnione MFC). Tutaj architektura składników MFC rozpoczyna się. Jeśli utworzysz przydatną klasę z klasy MFC lub utworzysz inny zestaw narzędzi przypominający MFC, możesz umieścić ją w dll. Biblioteka DLL używa metody MFCxx.DLL, podobnie jak ostateczna aplikacja kliencka. Biblioteka DLL rozszerzenia MFC umożliwia wielokrotnego użytku klasy liści, klasy bazowe wielokrotnego użytku oraz klasy widoków wielokrotnego użytku i dokumentów.

Zalety i wady

Dlaczego należy używać udostępnionej wersji MFC

  • Użycie biblioteki udostępnionej może skutkować mniejszymi aplikacjami. (Minimalna aplikacja, która używa większości biblioteki MFC, jest mniejsza niż 10 0000).

  • Udostępniona wersja MFC obsługuje biblioteki DLL rozszerzeń MFC i zwykłe biblioteki DLL MFC.

  • Szybsze jest tworzenie aplikacji korzystającej z udostępnionych bibliotek MFC niż statycznie połączonej aplikacji MFC. To dlatego, że nie jest konieczne połączenie samego MFC. Dotyczy to szczególnie kompilacji, w DEBUG których konsolidator musi kompaktować informacje debugowania. Gdy aplikacja zostanie połączona z biblioteką DLL, która zawiera już informacje o debugowaniu, jest mniej informacji debugowania, aby skompaktować.

Dlaczego nie należy używać udostępnionej wersji MFC:

  • Wysłanie aplikacji korzystającej z biblioteki udostępnionej wymaga wysłania MFCxx.DLL programu i innych bibliotek. MFCxx.DLL pakiet jest bezpłatnie redystrybucyjny, taki jak wiele bibliotek DLL, ale nadal musisz zainstalować bibliotekę DLL w programie INSTALACYJNYm. Ponadto należy wysłać inne biblioteki redystrybucyjne używane zarówno przez program, jak i biblioteki DLL MFC.

Jak napisać bibliotekę DLL rozszerzenia MFC

Biblioteka DLL rozszerzenia MFC to biblioteka DLL zawierająca klasy i funkcje, które rozszerzają funkcjonalność klas MFC. Biblioteka DLL rozszerzenia MFC używa udostępnionych bibliotek DLL MFC w taki sam sposób, w jaki aplikacja ich używa, z kilkoma dodatkowymi zagadnieniami:

  • Proces kompilacji jest podobny do kompilowania aplikacji korzystającej z udostępnionych bibliotek MFC z kilkoma dodatkowymi opcjami kompilatora i konsolidatora.

  • Biblioteka DLL rozszerzenia MFC nie ma klasy pochodnej CWinApp.

  • Biblioteka DLL rozszerzenia MFC musi zawierać specjalną bibliotekę DllMain. AppWizard dostarcza DllMain funkcję, którą można zmodyfikować.

  • Biblioteka DLL rozszerzenia MFC zwykle udostępnia procedurę inicjowania w celu utworzenia CDynLinkLibrarypliku , jeśli typy lub zasoby biblioteki DLL rozszerzenia MFC są eksportowane CRuntimeClass do aplikacji. Klasa pochodna CDynLinkLibrary klasy może być używana, jeśli dane poszczególnych aplikacji muszą być utrzymywane przez bibliotekę DLL rozszerzenia MFC.

Te zagadnienia zostały opisane bardziej szczegółowo poniżej. Zapoznaj się również z przykładem DLLHUSKMFC Advanced Concepts . Pokazano w nim, jak:

  • Tworzenie aplikacji przy użyciu bibliotek udostępnionych. (DLLHUSK.EXE to aplikacja MFC, która dynamicznie łączy się z bibliotekami MFC i innymi bibliotekami DLL).

  • Skompiluj bibliotekę DLL rozszerzenia MFC. (Pokazuje, jak specjalne flagi, takie jak _AFXEXT get used podczas kompilowania biblioteki DLL rozszerzenia MFC).

  • Utwórz dwa przykłady bibliotek DLL rozszerzeń MFC. Jeden pokazuje podstawową strukturę biblioteki DLL rozszerzenia MFC z ograniczonymi eksportami (TESTDLL1), a drugi pokazuje eksportowanie całego interfejsu klasy (TESTDLL2).

Zarówno aplikacja kliencka, jak i wszystkie biblioteki DLL rozszerzeń MFC muszą używać tej samej wersji programu MFCxx.DLL. Postępuj zgodnie z konwencjami bibliotek DLL MFC i podaj zarówno wersję debugowania, jak i wydania (/release) biblioteki DLL rozszerzenia MFC. Dzięki temu programy klienckie mogą tworzyć zarówno wersje debugowania, jak i wydawania swoich aplikacji oraz łączyć je z odpowiednią wersją debugowania lub wydania wszystkich bibliotek DLL.

Uwaga

Ponieważ problemy z zarządzaniem i eksportowaniem nazw języka C++, lista eksportu z biblioteki DLL rozszerzenia MFC może się różnić między wersjami debugowania i wydania tej samej biblioteki DLL i bibliotek DLL dla różnych platform. MFCxx.DLL Wydanie ma około 2000 wyeksportowanych punktów wejścia; debugowanie MFCxxD.DLL ma około 3000 wyeksportowanych punktów wejścia.

Szybka uwaga dotycząca zarządzania pamięcią

Sekcja zatytułowana "Zarządzanie pamięcią" pod koniec tej uwagi technicznej opisuje implementację MFCxx.DLL z udostępnioną wersją MFC. Informacje, które należy wiedzieć, aby zaimplementować tylko bibliotekę DLL rozszerzenia MFC, zostały opisane tutaj.

MFCxx.DLL wszystkie biblioteki DLL rozszerzeń MFC załadowane do przestrzeni adresowej aplikacji klienckiej będą używać tego samego alokatora pamięci, ładowania zasobów i innych stanów globalnych MFC, tak jakby znajdowały się w tej samej aplikacji. Jest to istotne, ponieważ biblioteki DLL inne niż MFC i zwykłe biblioteki DLL MFC, które statycznie łączą się ze specyfikacją MFC, robią dokładnie odwrotnie: każda biblioteka DLL przydziela z własnej puli pamięci.

Jeśli biblioteka DLL rozszerzenia MFC przydziela pamięć, ta pamięć może swobodnie przeplatać się z dowolnym innym obiektem przydzielonym przez aplikację. Ponadto jeśli aplikacja korzystająca z udostępnionych bibliotek MFC ulegnie awarii, system operacyjny zachowuje integralność dowolnej innej aplikacji MFC, która współużytkuje bibliotekę DLL.

Podobnie inne stany MFC "globalne", takie jak bieżący plik wykonywalny do ładowania zasobów, również są udostępniane między aplikacją kliencją, wszystkimi bibliotekami DLL rozszerzeń MFC i MFCxx.DLL samym sobą.

Kompilowanie biblioteki DLL rozszerzenia MFC

Za pomocą aplikacji AppWizard można utworzyć projekt DLL rozszerzenia MFC i automatycznie generuje odpowiednie ustawienia kompilatora i konsolidatora. Generuje również DllMain funkcję, którą można zmodyfikować.

Jeśli konwertujesz istniejący projekt na bibliotekę DLL rozszerzenia MFC, zacznij od standardowych ustawień kompilacji przy użyciu udostępnionej wersji MFC. Następnie wprowadź następujące zmiany:

  • Dodaj /D_AFXEXT do flag kompilatora. W oknie dialogowym Właściwości projektu wybierz kategorię Preprocesor języka C/C++>. Dodaj _AFXEXT do pola Define Macros (Definiowanie makr), oddzielając poszczególne elementy średnikami.

  • Usuń przełącznik kompilatora /Gy . W oknie dialogowym Właściwości projektu wybierz kategorię Generowanie kodu C/C++>. Upewnij się, że właściwość Enable Function-Level Linking nie jest włączona. To ustawienie ułatwia eksportowanie klas, ponieważ konsolidator nie usuwa funkcji nieużywanych. Jeśli oryginalny projekt utworzył regularną bibliotekę MFC DLL, która jest statycznie połączona z MFC, zmień opcję kompilatora /MT (lub ) na /MD (lub /MTd/MDd).

  • Skompiluj bibliotekę eksportu z opcją /DLL LINK. Ta opcja jest ustawiana podczas tworzenia nowego elementu docelowego i określania biblioteki Win32 Dynamic-Link jako typu docelowego.

Zmienianie plików nagłówków

Zwykle celem biblioteki DLL rozszerzenia MFC jest wyeksportowanie niektórych typowych funkcji do co najmniej jednej aplikacji, która może używać tej funkcji. Zasadniczo klasy eksportuje biblioteki DLL i funkcje globalne do użycia przez aplikacje klienckie.

Aby upewnić się, że każda funkcja składowa jest oznaczona jako odpowiednia do importu lub eksportu, należy użyć specjalnych deklaracji __declspec(dllexport) i __declspec(dllimport). Gdy aplikacje klienckie używają klas, chcesz, aby były deklarowane jako __declspec(dllimport). Gdy biblioteka DLL rozszerzenia MFC zostanie skompilowana, funkcje powinny zostać zadeklarowane jako __declspec(dllexport). Skompilowana biblioteka DLL musi również wyeksportować funkcje, aby programy klienckie mogły je powiązać w czasie ładowania.

Aby wyeksportować całą klasę, użyj AFX_EXT_CLASS jej w definicji klasy. Struktura definiuje to makro tak, jak __declspec(dllexport) w przypadku _AFXDLL_AFXEXT i jest definiowane, ale definiuje je tak, jak __declspec(dllimport) w przypadku, gdy _AFXEXT nie jest zdefiniowane. _AFXEXT jest definiowany tylko podczas kompilowania biblioteki DLL rozszerzenia MFC. Przykład:

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

Nie eksportuj całej klasy

Czasami możesz wyeksportować tylko poszczególne niezbędne elementy członkowskie klasy. Na przykład w przypadku wyeksportowania klasy pochodnej CDialogmoże być konieczne wyeksportowanie konstruktora i wywołania DoModal . Te elementy członkowskie można wyeksportować przy użyciu pliku DEF biblioteki DLL, ale można również użyć AFX_EXT_CLASS w taki sam sposób w przypadku poszczególnych elementów członkowskich, które należy wyeksportować.

Przykład:

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

Gdy to zrobisz, może wystąpić dodatkowy problem, ponieważ nie eksportujesz wszystkich elementów członkowskich klasy. Problem polega na tym, że makra MFC działają. Kilka makr pomocników MFC faktycznie deklaruje lub definiuje elementy członkowskie danych. Biblioteka DLL musi również wyeksportować te elementy członkowskie danych.

Na przykład makro DECLARE_DYNAMIC jest definiowane w następujący sposób podczas kompilowania biblioteki DLL rozszerzenia MFC:

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

Wiersz, który rozpoczyna static AFX_DATA deklarowanie obiektu statycznego wewnątrz klasy. Aby poprawnie wyeksportować tę klasę i uzyskać dostęp do informacji środowiska uruchomieniowego z pliku EXE klienta, należy wyeksportować ten obiekt statyczny. Ponieważ obiekt statyczny jest zadeklarowany za pomocą modyfikatora AFX_DATA, należy zdefiniować AFX_DATA tylko tak, jak __declspec(dllexport) podczas kompilowania biblioteki DLL. Zdefiniuj go tak, jak __declspec(dllimport) podczas tworzenia pliku wykonywalnego klienta.

Jak wspomniano powyżej, AFX_EXT_CLASS jest już zdefiniowany w ten sposób. Wystarczy ponownie zdefiniować AFX_DATA definicję, aby być taka sama jak AFX_EXT_CLASS w przypadku definicji klasy.

Przykład:

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

MFC zawsze używa symbolu AFX_DATA na elementach danych, które definiuje w makrach, więc ta technika będzie działać w przypadku wszystkich takich scenariuszy. Na przykład będzie działać dla DECLARE_MESSAGE_MAP.

Uwaga

Jeśli eksportujesz całą klasę, a nie wybrane elementy członkowskie klasy, statyczne składowe danych zostaną automatycznie wyeksportowane.

Możesz użyć tej samej techniki, aby automatycznie wyeksportować CArchive operator wyodrębniania dla klas używających makr DECLARE_SERIAL i IMPLEMENT_SERIAL. Wyeksportuj operator archiwum, nawiasując deklaracje klas (znajdujące się w pliku nagłówka) przy użyciu następującego kodu:

#undef AFX_API
#define AFX_API AFX_EXT_CLASS

/* your class declarations here */

#undef AFX_API
#define AFX_API

Ograniczenia _AFXEXT

Możesz użyć symbolu _AFXEXT wstępnego procesora dla bibliotek DLL rozszerzeń MFC, o ile nie masz wielu warstw bibliotek DLL rozszerzeń MFC. Jeśli masz biblioteki DLL rozszerzeń MFC, które wywołuje lub pochodzą z klas we własnych bibliotekach DLL rozszerzeń MFC, które następnie pochodzą z klas MFC, musisz użyć własnego symbolu preprocesora, aby uniknąć niejednoznaczności.

Problem polega na tym, że w systemie Win32 należy jawnie zadeklarować dowolne dane, aby __declspec(dllexport) wyeksportować je z biblioteki DLL i __declspec(dllimport) zaimportować je z biblioteki DLL. Podczas definiowania _AFXEXTnagłówków MFC upewnij się, że AFX_EXT_CLASS są poprawnie zdefiniowane.

Jeśli masz wiele warstw, jeden symbol, taki jak AFX_EXT_CLASS nie jest wystarczający: biblioteka DLL rozszerzenia MFC może wyeksportować własne klasy, a także zaimportować inne klasy z innej biblioteki DLL rozszerzenia MFC. Aby rozwiązać ten problem, użyj specjalnego symbolu preprocesora, który wskazuje, że tworzysz samą bibliotekę DLL, zamiast używać biblioteki DLL. Załóżmy na przykład, że dwie biblioteki DLL rozszerzeń MFC, A.DLLi B.DLL. Każda z nich eksportuje odpowiednio niektóre klasy w A.H systemach i B.H. B.DLLużywa klas z klasy .A.DLL Pliki nagłówków będą wyglądać mniej więcej tak:

/* 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 ... */ };

Po A.DLL utworzeniu jest kompilowany za pomocą /DA_IMPL polecenia i B.DLL kompilowany za pomocą /DB_IMPLpolecenia . Używając oddzielnych symboli dla każdej biblioteki DLL, CExampleB jest eksportowany i CExampleA importowany podczas kompilowania B.DLL. CExampleA jest eksportowany podczas kompilowania A.DLL i importowania, gdy jest używany przez B.DLL lub innego klienta.

Tego typu warstw nie można wykonać w przypadku używania wbudowanych AFX_EXT_CLASS i _AFXEXT preprocesorowych symboli. Opisana powyżej technika rozwiązuje ten problem w taki sam sposób, jak w przypadku MFC. MFC używa tej techniki podczas tworzenia bibliotek DLL rozszerzeń OLE, bazy danych i sieci MFC.

Nadal nie eksportuje całej klasy

Ponownie musisz zachować szczególną ostrożność, gdy nie eksportujesz całej klasy. Upewnij się, że niezbędne elementy danych utworzone przez makra MFC są prawidłowo eksportowane. Można to zrobić, ponownie definiując AFX_DATA makro określonej klasy. Ponownie zdefiniuj ją za każdym razem, gdy nie eksportujesz całej klasy.

Przykład:

// 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

Oto kod, który należy umieścić w głównym pliku źródłowym dla biblioteki DLL rozszerzenia MFC. Powinien on pochodzić po standardzie obejmuje. Gdy używasz aplikacji AppWizard do tworzenia plików startowych dla biblioteki DLL rozszerzenia MFC, dostarcza on za Ciebie.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
}

Wywołanie służące do AfxInitExtensionModule przechwytywania klas środowiska uruchomieniowego modułu (CRuntimeClass struktur) i jego fabryk obiektów (COleObjectFactory obiektów) do późniejszego CDynLinkLibrary użycia podczas tworzenia obiektu. Wywołanie AfxTermExtensionModule (opcjonalne) umożliwia MFC wyczyszczenie biblioteki DLL rozszerzenia MFC po odłączeniu każdego procesu (co ma miejsce w przypadku zakończenia procesu lub zwolnienia biblioteki DLL przez FreeLibrary wywołanie) z biblioteki DLL rozszerzenia MFC. Ponieważ większość bibliotek DLL rozszerzeń MFC nie jest ładowana dynamicznie (zwykle są one połączone za pośrednictwem bibliotek importu), wywołanie AfxTermExtensionModule zwykle nie jest konieczne.

Jeśli aplikacja ładuje biblioteki DLL rozszerzeń MFC i zwalnia je dynamicznie, pamiętaj o wywołaniu AfxTermExtensionModule metody , jak pokazano powyżej. Pamiętaj również, aby używać AfxLoadLibrary funkcji i AfxFreeLibrary (zamiast funkcji LoadLibrary Win32 i FreeLibrary), jeśli aplikacja używa wielu wątków lub jeśli dynamicznie ładuje bibliotekę DLL rozszerzenia MFC. Użycie AfxLoadLibrary i AfxFreeLibrary zapewnienie, że kod uruchamiania i zamykania, który jest wykonywany, gdy biblioteka DLL rozszerzenia MFC jest ładowana i zwalniana, nie powoduje uszkodzenia globalnego stanu MFC.

Plik AFXDLLX.H nagłówka zawiera specjalne definicje struktur używanych w bibliotekach DLL rozszerzeń MFC, takich jak definicja i CDynLinkLibraryAFX_EXTENSION_MODULE .

Globalne rozszerzenieDLL musi być zadeklarowane, jak pokazano. W przeciwieństwie do 16-bitowej wersji MFC można przydzielić pamięć i wywołać funkcje MFC w tym czasie, ponieważ MFCxx.DLL element jest w pełni inicjowany przez czas DllMain wywoływania.

Udostępnianie zasobów i klas

Proste biblioteki DLL rozszerzeń MFC wymagają tylko wyeksportowania kilku funkcji o niskiej przepustowości do aplikacji klienckiej i nic więcej. Więcej bibliotek DLL intensywnie korzystających z interfejsu użytkownika może chcieć wyeksportować zasoby i klasy języka C++ do aplikacji klienckiej.

Eksportowanie zasobów odbywa się za pośrednictwem listy zasobów. W każdej aplikacji jest singly połączona lista CDynLinkLibrary obiektów. Jeśli szukasz zasobu, większość standardowych implementacji MFC, które ładują zasoby, najpierw przyjrzyj się bieżącemu modułowi zasobów (AfxGetResourceHandle), a jeśli nie znaleziono, przejdź do listy CDynLinkLibrary obiektów próbujących załadować żądany zasób.

Dynamiczne tworzenie obiektów języka C++ przy użyciu nazwy klasy C++ jest podobne. Mechanizm deserializacji obiektów MFC musi zawierać wszystkie CRuntimeClass zarejestrowane obiekty, aby można je było odtworzyć dynamicznie tworząc obiekt C++ wymaganego typu na podstawie tego, co było przechowywane wcześniej.

Jeśli chcesz, aby aplikacja kliencka korzystała z klas w dll rozszerzenia MFC, które są DECLARE_SERIAL, należy wyeksportować klasy, aby były widoczne dla aplikacji klienckiej. To również zrobić, przechodząc do CDynLinkLibrary listy.

W przykładzie DLLHUSKMFC Advanced Concepts lista wygląda następująco:

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

Wpis MFCxx.DLL zazwyczaj znajduje się na liście zasobów i klas. MFCxx.DLL zawiera wszystkie standardowe zasoby MFC, w tym ciągi monitów dla wszystkich standardowych identyfikatorów poleceń. Umieszczenie jej na końcu listy umożliwia zarówno biblioteki DLL, jak i samą aplikację kliencją poleganie na udostępnionych zasobach w MFCxx.DLLobiekcie zamiast posiadania własnych kopii.

Scalanie zasobów i nazw klas wszystkich bibliotek DLL do przestrzeni nazw aplikacji klienckiej ma wadę, którą należy zachować, uważając, jakie identyfikatory lub nazwy wybierasz. Tę funkcję można wyłączyć, nie eksportując zasobów ani CDynLinkLibrary obiektu do aplikacji klienckiej. Przykład DLLHUSK zarządza przestrzenią nazw zasobów udostępnionych przy użyciu wielu plików nagłówków. Aby uzyskać więcej wskazówek dotyczących używania udostępnionych plików zasobów, zobacz Technical Note 35 (Uwaga techniczna 35 ).

Inicjowanie biblioteki DLL

Jak wspomniano powyżej, zazwyczaj należy utworzyć CDynLinkLibrary obiekt w celu wyeksportowania zasobów i klas do aplikacji klienckiej. Musisz podać wyeksportowany punkt wejścia, aby zainicjować bibliotekę DLL. Co najmniej, jest to rutyna void , która nie przyjmuje żadnych argumentów i nie zwraca nic, ale może to być coś, co lubisz.

Każda aplikacja kliencka, która chce używać biblioteki DLL, musi wywołać tę procedurę inicjowania, jeśli używasz tej metody. Możesz również przydzielić ten CDynLinkLibrary obiekt w obiekcie DllMain tuż po wywołaniu metody AfxInitExtensionModule.

Procedury inicjowania muszą utworzyć CDynLinkLibrary obiekt w stercie bieżącej aplikacji, podłączony do informacji dll rozszerzenia MFC. Można to zrobić, definiując funkcję podobną do następującej:

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

W tym przykładzie nazwa procedury InitXxxDLL może być niczym, czego potrzebujesz. Nie musi to być extern "C"element , ale ułatwia utrzymanie listy eksportu.

Uwaga

Jeśli używasz biblioteki DLL rozszerzenia MFC z regularnej biblioteki MFC DLL, musisz wyeksportować tę funkcję inicjowania. Ta funkcja musi być wywoływana ze standardowej biblioteki MFC DLL przed użyciem dowolnych klas lub zasobów bibliotek DLL rozszerzeń MFC.

Eksportowanie wpisów

Prostym sposobem eksportowania klas jest użycie __declspec(dllimport)__declspec(dllexport) i w każdej klasie i funkcji globalnej, którą chcesz wyeksportować. Jest to o wiele łatwiejsze, ale jest mniej wydajne niż nazywanie każdego punktu wejścia w pliku DEF, jak opisano poniżej. Dzieje się tak, ponieważ masz mniejszą kontrolę nad eksportowanych funkcjami. I nie można wyeksportować funkcji według porządkowych. TESTDLL1 i TESTDLL2 użyć tej metody do wyeksportowania swoich wpisów.

Bardziej wydajną metodą jest wyeksportowanie każdego wpisu przez nadanie mu nazwy w pliku DEF. Ta metoda jest używana przez MFCxx.DLLprogram . Ponieważ eksportujemy selektywnie z naszej biblioteki DLL, musimy zdecydować, które interfejsy chcemy wyeksportować. Trudno jest określić nazwy mangled do konsolidatora w postaci wpisów w pliku DEF. Nie eksportuj żadnej klasy języka C++, chyba że naprawdę musisz mieć dla niej link symboliczny.

Jeśli wcześniej próbowano wyeksportować klasy języka C++ z plikiem DEF, możesz chcieć utworzyć narzędzie do automatycznego generowania tej listy. Można to zrobić przy użyciu dwuetapowego procesu łączenia. Połącz bibliotekę DLL raz bez eksportów i zezwól konsolidatorowi na generowanie pliku MAP. Plik MAP zawiera listę funkcji, które należy wyeksportować. W przypadku zmieniania kolejności można użyć go do wygenerowania wpisów EXPORT dla pliku DEF. Lista eksportu dla MFCxx.DLL bibliotek DLL rozszerzeń OLE i bazy danych MFC, kilka tysięcy na liczbie, została wygenerowana przy użyciu takiego procesu (chociaż nie jest w pełni automatyczna i wymaga ręcznego dostrajania co jakiś czas).

CWinApp a CDynLinkLibrary

Biblioteka DLL rozszerzenia MFC nie ma CWinAppwłasnego obiektu pochodnego. Zamiast tego musi działać z obiektem CWinApppochodnym aplikacji klienckiej. Oznacza to, że aplikacja kliencka jest właścicielem głównej pompy komunikatów, pętli bezczynności itd.

Jeśli biblioteka DLL rozszerzenia MFC musi obsługiwać dodatkowe dane dla każdej aplikacji, możesz utworzyć nową klasę CDynLinkLibrary i utworzyć ją w procedurze opisanej InitXxxDLL powyżej. Podczas uruchamiania biblioteka DLL może sprawdzić listę obiektów bieżącej CDynLinkLibrary aplikacji, aby znaleźć ten dla tej konkretnej biblioteki DLL rozszerzenia MFC.

Korzystanie z zasobów w implementacji biblioteki DLL

Jak wspomniano powyżej, domyślne ładowanie zasobów przeprowadzi listę CDynLinkLibrary obiektów, które szukają pierwszego pliku EXE lub biblioteki DLL zawierającej żądany zasób. Wszystkie interfejsy API MFC i cały kod wewnętrzny są używane AfxFindResourceHandle do chodzenia po liście zasobów w celu znalezienia dowolnego zasobu, niezależnie od tego, gdzie się znajduje.

Jeśli chcesz tylko załadować zasoby z określonego miejsca, użyj interfejsów AfxGetResourceHandle API i AfxSetResourceHandle zapisz stary uchwyt i ustaw nowy uchwyt. Pamiętaj, aby przywrócić stary uchwyt zasobu przed powrotem do aplikacji klienckiej. Przykładowa TESTDLL2 używa tego podejścia do jawnego ładowania menu.

Przejście do listy ma pewne wady: jest nieco wolniejsze i wymaga zarządzania zakresami identyfikatorów zasobów. Ma to zaletę, że aplikacja kliencka, która łączy się z kilkoma bibliotekami DLL rozszerzeń MFC, może używać dowolnego zasobu dostarczonego przez bibliotekę DLL bez konieczności określania uchwytu wystąpienia biblioteki DLL. AfxFindResourceHandle to interfejs API służący do chodzenia po liście zasobów w celu wyszukania danego dopasowania. Przyjmuje nazwę i typ zasobu, a następnie zwraca uchwyt zasobu, w którym najpierw znajduje zasób lub wartość NULL.

Pisanie aplikacji korzystającej z wersji biblioteki DLL

Wymagania aplikacji

Aplikacja korzystająca z udostępnionej wersji MFC musi przestrzegać kilku podstawowych reguł:

  • Musi mieć CWinApp obiekt i przestrzegać standardowych reguł pompy komunikatów.

  • Należy go skompilować przy użyciu zestawu wymaganych flag kompilatora (patrz poniżej).

  • Musi ona łączyć się z bibliotekami importu MFCxx. Ustawiając wymagane flagi kompilatora, nagłówki MFC określają w czasie połączenia bibliotekę, z którą aplikacja powinna się łączyć.

  • Aby uruchomić plik wykonywalny, MFCxx.DLL musi znajdować się w ścieżce lub w katalogu systemowym systemu Windows.

Kompilowanie przy użyciu środowiska programistycznego

Jeśli używasz wewnętrznego pliku makefile z większością standardowych wartości domyślnych, możesz łatwo zmienić projekt, aby skompilować wersję biblioteki DLL.

W poniższym kroku przyjęto założenie, że masz poprawnie działającą aplikację MFC połączoną z NAFXCWD.LIB (na potrzeby debugowania) i NAFXCW.LIB (dla wydania) i chcesz ją przekonwertować, aby korzystała z udostępnionej wersji biblioteki MFC. Uruchamiasz środowisko programu Visual Studio i masz wewnętrzny plik projektu.

  1. W menu Projekty wybierz pozycję Właściwości. Na stronie Ogólne w obszarze Wartości domyślne projektu ustaw klasy programu Microsoft Foundation na używanie MFC w udostępnionej biblioteki DLL (MFCxx(d).dll).

Kompilowanie za pomocą narzędzia NMAKE

Jeśli używasz zewnętrznej funkcji makefile kompilatora lub bezpośrednio używasz narzędzia NMAKE, musisz edytować plik make, aby obsługiwał wymagane opcje kompilatora i konsolidatora.

Wymagane flagi kompilatora:

  • /D_AFXDLL /MD /D_AFXDLL

Standardowe nagłówki MFC wymagają zdefiniowania symbolu _AFXDLL .

  • /MD Aplikacja musi używać wersji dll biblioteki uruchomieniowej języka C.

Wszystkie inne flagi kompilatora są zgodne z wartościami domyślnymi MFC (na przykład _DEBUG w przypadku debugowania).

Edytuj listę bibliotek konsolidatora. Zmień NAFXCWD.LIB wartość na MFCxxD.LIB i zmień na NAFXCW.LIBMFCxx.LIB. Zamień LIBC.LIB na MSVCRT.LIB. Podobnie jak w przypadku każdej innej biblioteki MFC, ważne jest, aby MFCxxD.LIB zostały umieszczone przed wszystkimi bibliotekami środowiska uruchomieniowego języka C.

Opcjonalnie dodaj /D_AFXDLL zarówno opcje kompilatora zasobów wydania, jak i debugowania (te, które faktycznie kompiluje zasoby za pomocą polecenia /R). Ta opcja sprawia, że końcowy plik wykonywalny jest mniejszy, udostępniając zasoby, które znajdują się w bibliotekach DLL MFC.

Po wprowadzeniu tych zmian wymagana jest pełna ponowna kompilacja.

Kompilowanie przykładów

Większość przykładowych programów MFC można skompilować z poziomu języka Visual C++ lub z udostępnionego pliku MAKEFILE zgodnego z NMAKE z wiersza polecenia.

Aby przekonwertować dowolny z tych przykładów do użycia MFCxx.DLL, możesz załadować plik MAK do języka Visual C++ i ustawić opcje projektu zgodnie z powyższym opisem. Jeśli używasz kompilacji NMAKE, możesz określić AFXDLL=1 w wierszu polecenia NMAKE i utworzyć przykład przy użyciu udostępnionych bibliotek MFC.

Przykładowa biblioteka DLLHUSK dotycząca zaawansowanych pojęć MFC została skompilowana przy użyciu biblioteki DLL w wersji MFC. W tym przykładzie pokazano nie tylko, jak utworzyć aplikację połączoną z MFCxx.DLLprogramem , ale także ilustruje inne funkcje opcji pakowania bibliotek DLL MFC, takich jak biblioteki DLL rozszerzeń MFC opisane w dalszej części tej uwagi technicznej.

Notatki dotyczące tworzenia pakietów

Wersje wersji bibliotek DLL (MFCxx.DLL i MFCxxU.DLL) są bezpłatnie redystrybucyjne. Wersje debugowania bibliotek DLL nie są bezpłatnie redystrybucyjne i powinny być używane tylko podczas tworzenia aplikacji.

Biblioteki DLL debugowania są dostarczane z informacjami o debugowaniu. Korzystając z debugera Visual C++, można śledzić wykonywanie zarówno aplikacji, jak i biblioteki DLL. Biblioteki DLL wydania (MFCxx.DLL i MFCxxU.DLL) nie zawierają informacji o debugowaniu.

Jeśli dostosujesz lub ponownie skompilujesz biblioteki DLL, wywołaj je inaczej niż "MFCxx". Plik MFCDLL.MAK MFC SRC opisuje opcje kompilacji i zawiera logikę zmiany nazwy biblioteki DLL. Zmiana nazwy plików jest konieczna, ponieważ te biblioteki DLL są potencjalnie współużytkowane przez wiele aplikacji MFC. Posiadanie niestandardowej wersji bibliotek DLL MFC zastępuje te zainstalowane w systemie może spowodować przerwanie innej aplikacji MFC przy użyciu udostępnionych bibliotek DLL MFC.

Ponowne kompilowanie bibliotek DLL MFC nie jest zalecane.

Jak zaimplementowano bibliotekę MFCxx.DLL

W poniższej sekcji opisano sposób implementacji biblioteki MFC DLL (MFCxx.DLL i MFCxxD.DLL). Zrozumienie tutaj szczegółów nie jest również ważne, jeśli wszystko, co chcesz zrobić, to użyć biblioteki MFC DLL z aplikacją. Tutaj szczegółowe informacje nie są niezbędne do zrozumienia, jak napisać bibliotekę DLL rozszerzenia MFC, ale zrozumienie tej implementacji może pomóc w pisaniu własnej biblioteki DLL.

Omówienie implementacji

Biblioteka MFC DLL jest naprawdę specjalnym przypadkiem biblioteki DLL rozszerzenia MFC, jak opisano powyżej. Ma dużą liczbę eksportów dla dużej liczby klas. Istnieje kilka dodatkowych rzeczy, które robimy w dll MFC, które sprawiają, że jest jeszcze bardziej wyjątkowy niż zwykła biblioteka DLL rozszerzenia MFC.

Win32 wykonuje większość pracy

16-bitowa wersja MFC wymagała wielu specjalnych technik, w tym danych dla aplikacji w segmencie stosu, specjalnych segmentów utworzonych przez około 80x86 kod zestawu, konteksty wyjątków dla procesu i inne techniki. Win32 bezpośrednio obsługuje dane poszczególnych procesów w dll, co jest potrzebne przez większość czasu. W większości MFCxx.DLL przypadków plik DLL jest po prostu NAFXCW.LIB spakowany. Jeśli spojrzysz na kod źródłowy MFC, znajdziesz kilka #ifdef _AFXDLL przypadków, ponieważ nie ma wielu specjalnych przypadków, które należy wykonać. Specjalne przypadki, które istnieją specjalnie do obsługi Win32 w systemie Windows 3.1 (inaczej znane jako Win32s). Win32s nie obsługuje bezpośrednio danych DLL procesu. Biblioteka MFC DLL musi używać interfejsów API win32 magazynu wątkowego (TLS) do uzyskiwania danych lokalnych.

Wpływ na źródła biblioteki, dodatkowe pliki

Wpływ _AFXDLL wersji na normalne źródła i nagłówki biblioteki klas MFC jest stosunkowo niewielki. Istnieje specjalny plik wersji (AFXV_DLL.H) i dodatkowy plik nagłówka (AFXDLL_.H) dołączony przez główny AFXWIN.H nagłówek. Nagłówek AFXDLL_.H zawiera CDynLinkLibrary klasy i inne szczegóły implementacji zarówno _AFXDLL aplikacji, jak i bibliotek DLL rozszerzeń MFC. Nagłówek AFXDLLX.H jest udostępniany do tworzenia bibliotek DLL rozszerzeń MFC (zobacz powyżej, aby uzyskać szczegółowe informacje).

Zwykłe źródła biblioteki MFC w MFC SRC mają dodatkowy kod warunkowy w _AFXDLL #ifdef. Dodatkowy plik źródłowy (DLLINIT.CPP) zawiera dodatkowy kod inicjowania bibliotek DLL i inny klej dla udostępnionej wersji MFC.

Aby utworzyć udostępnioną wersję MFC, udostępniane są dodatkowe pliki. (Zobacz poniżej, aby uzyskać szczegółowe informacje na temat sposobu kompilowania biblioteki DLL).

  • Dwa pliki DEF są używane do eksportowania punktów wejścia biblioteki DLL MFC na potrzeby debugowania () i wersji (MFCxxD.DEFMFCxx.DEF) biblioteki DLL.

  • Plik RC (MFCDLL.RC) zawiera wszystkie standardowe zasoby MFC i VERSIONINFO zasób dla biblioteki DLL.

  • Plik CLW (MFCDLL.CLW) umożliwia przeglądanie klas MFC przy użyciu klasy ClassWizard. Ta funkcja nie jest konkretna dla wersji biblioteki DLL MFC.

Zarządzanie pamięcią

Aplikacja używająca używa MFCxx.DLL wspólnego alokatora pamięci udostępnionego przez MSVCRTxx.DLLbibliotekę DLL udostępnionego środowiska uruchomieniowego języka C. Aplikacja, wszystkie biblioteki DLL rozszerzeń MFC, a także biblioteki DLL MFC używają tego alokatora pamięci udostępnionej. Korzystając z udostępnionej biblioteki DLL na potrzeby alokacji pamięci, biblioteki DLL MFC mogą przydzielać pamięć, która zostanie później zwolniona przez aplikację lub odwrotnie. Ponieważ zarówno aplikacja, jak i biblioteka DLL muszą używać tego samego alokatora, nie należy zastępować globalnego operator new języka C++ ani operator delete. Te same reguły dotyczą pozostałych procedur alokacji pamięci w czasie wykonywania języka C (takich jak malloc, realloc, freei innych).

Ordinals and class __declspec(dllexport) and DLL naming (Ordinals and class __declspec(dllexport) and DLL naming (Ordinals and class __declspec(dllexport) and DLL naming

Nie używamy class__declspec(dllexport) funkcji kompilatora języka C++. Zamiast tego lista eksportów jest uwzględniana w źródłach bibliotek klas (MFCxx.DEF i MFCxxD.DEF). Eksportowany jest tylko wybrany zestaw punktów wejścia (funkcji i danych). Inne symbole, takie jak funkcje lub klasy implementacji prywatnej MFC, nie są eksportowane. Wszystkie eksporty są wykonywane przez porządkowe bez nazwy ciągu w tabeli nazwy rezydenta lub nierezydenta.

Użycie class__declspec(dllexport) może być opłacalną alternatywą dla tworzenia mniejszych bibliotek DLL, ale w dużej biblioteki DLL, takiej jak MFC, domyślny mechanizm eksportowania ma limity wydajności i pojemności.

Oznacza to, że możemy spakować dużą ilość funkcji w wydaniu MFCxx.DLL , która wynosi tylko około 800 KB bez naruszania dużej szybkości wykonywania lub ładowania. MFCxx.DLL byłoby 100 KB większe, gdyby ta technika nie została użyta. Technika umożliwia dodanie dodatkowych punktów wejścia na końcu pliku DEF. Umożliwia proste przechowywanie wersji bez naruszania szybkości i wydajności rozmiaru eksportu przez porządkowe. Wersje główne w bibliotece klas MFC zmienią nazwę biblioteki. Oznacza to, MFC30.DLL że jest to redystrybucyjny plik DLL zawierający wersję 3.0 biblioteki klas MFC. Uaktualnienie tej biblioteki DLL, powiedzmy, w hipotetycznym MFC 3.1 biblioteka DLL będzie zamiast tego nazwana MFC31.DLL . Ponownie, jeśli zmodyfikujesz kod źródłowy MFC w celu utworzenia niestandardowej wersji biblioteki MFC DLL, użyj innej nazwy (i najlepiej bez "MFC" w nazwie).

Zobacz też

Uwagi techniczne według numerów
Uwagi techniczne według kategorii