TN033: MFC의 DLL 버전

이 참고에서는 MFC 애플리케이션 및 MFC 확장 DLL을 사용하여 공유된 동적 링크 라이브러리 및 MFCxxD.DLL (여기서 xx는 MFC 버전 번호) 사용 MFCxx.DLL 방법을 설명합니다. 일반 MFC DLL에 대한 자세한 내용은 DLL의 일부로 MFC 사용을 참조하세요.

이 기술 참고 사항은 DLL의 세 가지 측면을 다룹니다. 마지막 두 가지는 고급 사용자를 위한 것입니다.

MFC가 아닌 애플리케이션(일반 MFC DLL이라고 함)과 함께 사용할 수 있는 MFC를 사용하여 DLL빌드하려는 경우 Technical Note 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에 동적으로 연결되는 일반 MFC DLL을 빌드할 수도 있습니다.

MFC 3.0 이상에서는 OLE 및 데이터베이스 클래스를 비롯한 모든 새로운 기능을 사용하여 일반 MFC DLL을 지원합니다.

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"이라고 합니다.

이 참고는 다음을 포함하는 전체 MFC DLL 집합을 참조하는 데 사용합니다 MFCxx.DLL .

  • 디버그: MFCxxD.DLL (결합) 및 MFCSxxD.LIB (정적).

  • 릴리스: MFCxx.DLL (결합) 및 MFCSxx.LIB (정적).

  • 유니코드 디버그: MFCxxUD.DLL (결합) 및 MFCSxxD.LIB (정적)

  • 유니코드 릴리스: MFCxxU.DLL (결합) 및 MFCSxxU.LIB (정적).

참고 항목

라이브러리는 MFCSxx[U][D].LIB MFC 공유 DLL과 함께 사용됩니다. 이러한 라이브러리에는 애플리케이션 또는 DLL에 정적으로 연결해야 하는 코드가 포함되어 있습니다.

애플리케이션은 해당 가져오기 라이브러리에 연결됩니다.

  • 디버그: MFCxxD.LIB

  • 릴리스: MFCxx.LIB

  • 유니코드 디버그: MFCxxUD.LIB

  • 유니코드 릴리스: 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은 MFC 클래스의 기능을 확장하는 클래스 및 함수를 포함하는 DLL입니다. MFC 확장 DLL은 애플리케이션에서 사용하는 것과 동일한 방식으로 공유 MFC DLL을 사용하며, 몇 가지 추가 고려 사항이 있습니다.

  • 빌드 프로세스는 몇 가지 추가 컴파일러 및 링커 옵션과 함께 공유 MFC 라이브러리를 사용하는 애플리케이션을 빌드하는 것과 유사합니다.

  • MFC 확장 DLL에는 파생 클래스가 CWinApp없습니다.

  • MFC 확장 DLL은 특수 DllMain를 제공해야 합니다. AppWizard는 수정할 수 있는 DllMain 함수를 제공합니다.

  • MFC 확장 DLL은 일반적으로 MFC 확장 DLL이 형식 또는 리소스를 애플리케이션으로 CDynLinkLibrary내보내는 경우 초기화 루틴을 만들어 줍니다 CRuntimeClass . MFC 확장 DLL에서 애플리케이션별 데이터를 기본 확인해야 하는 경우 파생 클래스 CDynLinkLibrary 를 사용할 수 있습니다.

이러한 고려 사항은 아래에 자세히 설명되어 있습니다. MFC 고급 개념 샘플 DLLHUSK도 참조하세요. 다음을 수행하는 방법을 보여 줍니다.

  • 공유 라이브러리를 사용하여 애플리케이션을 빌드합니다. (DLLHUSK.EXE MFC 라이브러리 및 기타 DLL에 동적으로 연결되는 MFC 애플리케이션입니다.)

  • MFC 확장 DLL을 빌드합니다. (MFC 확장 DLL을 빌드할 때 사용되는 것과 같은 _AFXEXT 특수 플래그를 보여 줍니다.)

  • MFC 확장 DLL의 두 가지 예제를 빌드합니다. 하나는 제한된 내보내기(TESTDLL1)가 있는 MFC 확장 DLL의 기본 구조를 보여 줍니다. 다른 하나는 전체 클래스 인터페이스(TESTDLL2) 내보내기를 보여 줍니다.

클라이언트 애플리케이션과 모든 MFC 확장 DLL은 모두 동일한 버전의 MFCxx.DLL을 사용해야 합니다. MFC DLL의 규칙을 따르고 MFC 확장 DLL의 디버그 및 릴리스(/release) 버전을 모두 제공합니다. 이 방법을 사용하면 클라이언트 프로그램이 해당 애플리케이션의 디버그 및 릴리스 버전을 모두 빌드하고 모든 DLL의 적절한 디버그 또는 릴리스 버전과 연결할 수 있습니다.

참고 항목

C++ 이름 관리 및 내보내기 문제가 있기 때문에 MFC 확장 DLL의 내보내기 목록은 서로 다른 플랫폼에 대해 동일한 DLL의 디버그 버전과 릴리스 버전 간에 다를 수 있습니다. 릴리스 MFCxx.DLL 에는 약 2,000개 내보낸 진입점이 있으며, 디버그 MFCxxD.DLL 에는 약 3,000개 내보낸 진입점이 있습니다.

메모리 관리에 대한 빠른 정보

이 기술 노트의 끝부분에 있는 "메모리 관리"라는 제목의 섹션에서는 공유 버전의 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을 빌드한 경우 (또는) 컴파일러 옵션을 /MD (또는/MTd/MDd)로 변경 /MT 합니다.

  • LINK 옵션을 사용하여 내보내기 라이브러리를 /DLL 빌드합니다. 이 옵션은 새 대상을 만들고 Win32 동적 연결 라이브러리를 대상 유형으로 지정할 때 설정됩니다.

헤더 파일 변경

MFC 확장 DLL의 일반적인 목표는 해당 기능을 사용할 수 있는 하나 이상의 애플리케이션에 몇 가지 일반적인 기능을 내보내는 것입니다. 기본적으로 DLL은 클라이언트 애플리케이션에서 사용할 클래스 및 전역 함수를 내보냅니다.

각 멤버 함수가 가져오기 또는 내보내기를 위해 적절하게 표시되도록 하려면 특수 선언을 __declspec(dllexport)__declspec(dllimport)사용하고 . 클라이언트 애플리케이션에서 클래스를 사용하는 경우 클래스를 .로 __declspec(dllimport)선언하려고 합니다. MFC 확장 DLL 자체가 빌드되면 함수를 .로 __declspec(dllexport)선언해야 합니다. 빌드된 DLL은 클라이언트 프로그램이 로드 시 함수에 바인딩할 수 있도록 함수도 내보내야 합니다.

전체 클래스를 내보내려면 클래스 정의에 사용합니다 AFX_EXT_CLASS . 프레임워크는 이 매크로 __declspec(dllexport) 를 정의하고 _AFXEXT 정의할 때 _AFXDLL 로 정의하지만 정의되지 않은 경우 _AFXEXT__declspec(dllimport) 정의합니다. _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도 이러한 데이터 멤버를 내보내야 합니다.

예를 들어 DECLARE_DYNAMIC 매크로는 MFC 확장 DLL을 빌드할 때 다음과 같이 정의됩니다.

#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_EXT_CLASS 다시 정의 AFX_DATA 하기만 하면 됩니다.

예시:

#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 클래스에서 파생되는 고유한 MFC 확장 DLL의 클래스를 호출하거나 해당 클래스에서 파생되는 MFC 확장 DLL이 있는 경우 모호성을 방지하기 위해 고유한 전처리기 기호를 사용해야 합니다.

문제는 Win32에서 DLL에서 데이터를 내보내고 __declspec(dllimport) DLL에서 가져올 데이터를 __declspec(dllexport) 명시적으로 선언해야 한다는 것입니다. 정의 _AFXEXT할 때 MFC 헤더는 올바르게 정의되었는지 확인 AFX_EXT_CLASS 합니다.

여러 계층이 있는 경우 하나의 기호로 AFX_EXT_CLASS 는 충분하지 않습니다. MFC 확장 DLL은 자체 클래스를 내보내고 다른 MFC 확장 DLL에서 다른 클래스를 가져올 수도 있습니다. 이 문제를 해결하려면 DLL을 사용하는 대신 DLL 자체를 빌드하고 있음을 나타내는 특수 전처리기 기호를 사용합니다. 예를 들어 두 개의 MFC 확장 DLL A.DLLB.DLL. 각 클래스는 각각 일부 클래스를 A.HB.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 에 대해 별도의 기호를 사용하여 내보내고 CExampleA 빌드 B.DLL할 때 가져옵니다. CExampleA 는 빌드 A.DLL 할 때 내보내고 다른 클라이언트에서 사용할 B.DLL 때 가져옵니다.

기본 제공 AFX_EXT_CLASS_AFXEXT 전처리기 기호를 사용하는 경우 이러한 유형의 계층화는 수행할 수 없습니다. 위에서 설명한 기술은 MFC와 동일한 방식으로 이 문제를 해결합니다. MFC는 OLE, 데이터베이스 및 네트워크 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
}

나중에 개체를 만들 때 CDynLinkLibrary 사용할 모듈의 런타임 클래스(CRuntimeClass구조) 및 해당 개체 팩터리(COleObjectFactory개체)를 캡처하는 AfxInitExtensionModule 호출입니다. MFC 확장 DLL에서 각 프로세스가 분리될 때(프로세스가 종료되거나 DLL이 호출에 의해 FreeLibrary 언로드될 때) MFC 확장 DLL을 클린 수 있도록 하는 (선택 사항) 호출 AfxTermExtensionModule 입니다. 대부분의 MFC 확장 DLL은 동적으로 로드되지 않으므로(일반적으로 가져오기 라이브러리를 통해 연결됨) 일반적으로 호출 AfxTermExtensionModule 이 필요하지 않습니다.

애플리케이션이 동적으로 MFC 확장 DLL을 로드하고 해제하는 경우 위와 같이 호출 AfxTermExtensionModule 해야 합니다. 또한 애플리케이션이 여러 스레드를 사용하거나 MFC 확장 DLL을 동적으로 로드하는 경우 Win32 함수 및 (Win32 함수 FreeLibraryLoadLibrary 대신)를 사용해야 AfxLoadLibraryAfxFreeLibrary 합니다. MFC AfxLoadLibrary 확장 DLL이 로드되고 AfxFreeLibrary 언로드될 때 실행되는 시작 및 종료 코드가 전역 MFC 상태를 손상시키지 않도록 합니다.

헤더 파일에 AFXDLLX.H 는 MFC 확장 DLL에 사용되는 구조체(예: 정의 및 에 대한 정의)에 대한 AFX_EXTENSION_MODULE 특수 정의가 포함되어 있습니다 CDynLinkLibrary.

전역 extensionDLL 은 표시된 대로 선언해야 합니다. 16비트 버전의 MFC와 달리 호출될 때까지 완전히 초기화되므로 이 시간 MFCxx.DLL 동안 메모리를 할당하고 MFC 함수를 호출할 수 있습니다 DllMain .

리소스 및 클래스 공유

간단한 MFC 확장 DLL은 몇 가지 낮은 대역폭 함수만 클라이언트 애플리케이션으로 내보내고 그 이상은 내보내지 않아도 됩니다. 더 많은 사용자 인터페이스 집약적 DLL은 리소스 및 C++ 클래스를 클라이언트 애플리케이션으로 내보내려고 할 수 있습니다.

리소스 내보내기는 리소스 목록을 통해 수행됩니다. 각 애플리케이션에서 개체의 CDynLinkLibrary Singly 연결된 목록입니다. 리소스를 찾을 때 리소스를 로드하는 대부분의 표준 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 에는 모든 표준 명령 ID에 대한 프롬프트 문자열을 포함하여 모든 표준 MFC 리소스가 포함됩니다. 목록의 끝에 배치하면 DLL과 클라이언트 애플리케이션 자체가 자체 복사본이 있는 대신 공유 리소스에 MFCxx.DLL의존할 수 있습니다.

모든 DLL의 리소스 및 클래스 이름을 클라이언트 애플리케이션의 이름 공간에 병합하면 선택한 ID 또는 이름을 주의해야 한다는 단점이 있습니다. 리소스 또는 CDynLinkLibrary 개체를 클라이언트 애플리케이션으로 내보내지 않음으로써 이 기능을 사용하지 않도록 설정할 수 있습니다. 샘플은 DLLHUSK 여러 헤더 파일을 사용하여 공유 리소스 이름 공간을 관리합니다. 공유 리소스 파일 사용에 대한 자세한 팁은 기술 참고 35를 참조하세요.

DLL 초기화

위에서 멘션 일반적으로 리소스 및 클래스를 클라이언트 애플리케이션으로 CDynLinkLibrary 내보내는 개체를 만들려고 합니다. DLL을 초기화하려면 내보낸 진입점을 제공해야 합니다. 최소한 인수를 void 사용하지 않고 아무것도 반환하지 않는 루틴이지만 원하는 것이 될 수 있습니다.

DLL을 사용하려는 각 클라이언트 애플리케이션은 이 방법을 사용하는 경우 이 초기화 루틴을 호출해야 합니다. 호출AfxInitExtensionModule한 직후에 이 CDynLinkLibrary 개체를 할당할 DllMain 수도 있습니다.

초기화 루틴은 MFC 확장 DLL 정보에 연결된 현재 애플리케이션의 힙에 개체를 만들어야 CDynLinkLibrary 합니다. 다음과 같은 함수를 정의하여 수행할 수 있습니다.

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++ 클래스에 대한 기호 링크가 필요하지 않으면 C++ 클래스를 내보내지 마세요.

이전에 DEF 파일을 사용하여 C++ 클래스를 내보내려고 시도한 경우 이 목록을 자동으로 생성하는 도구를 개발할 수 있습니다. 2단계 링크 프로세스를 사용하여 수행할 수 있습니다. 내보내기 없이 DLL을 한 번 연결하고 링커가 MAP 파일을 생성하도록 허용합니다. MAP 파일에는 내보내야 하는 함수 목록이 포함되어 있습니다. 일부 다시 정렬을 사용하면 DEF 파일에 대한 EXPORT 항목을 생성하는 데 사용할 수 있습니다. OLE 및 데이터베이스 MFC 확장 DLL에 대한 MFCxx.DLL 내보내기 목록(수 천 개)은 이러한 프로세스로 생성되었습니다(완전 자동은 아니지만 가끔씩 손 튜닝이 필요).

CWinApp 및 CDynLinkLibrary

MFC 확장 DLL에는 CWinApp자체 파생 개체가 없습니다. 대신 클라이언트 애플리케이션의 CWinApp파생 개체로 작업해야 합니다. 즉, 클라이언트 애플리케이션은 기본 메시지 펌프, 유휴 루프 등을 소유합니다.

MFC 확장 DLL이 각 애플리케이션에 대한 추가 데이터를 기본 유지해야 하는 경우 새 클래스 CDynLinkLibrary 를 파생시키고 위의 루틴 설명에서 InitXxxDLL 만들 수 있습니다. 실행할 때 DLL은 현재 애플리케이션의 개체 목록을 검사 특정 MFC 확장 DLL에 대한 개체 목록을 CDynLinkLibrary 찾을 수 있습니다.

DLL 구현에서 리소스 사용

위에서 멘션 기본 리소스 로드는 요청된 리소스가 있는 첫 번째 EXE 또는 DLL을 찾는 개체 목록을 CDynLinkLibrary 안내합니다. 모든 MFC API 및 모든 내부 코드는 리소스 목록을 탐색하여 위치와 관계없이 리소스를 찾는 데 사용합니다 AfxFindResourceHandle .

특정 위치에서만 리소스를 로드하려면 API AfxGetResourceHandleAfxSetResourceHandle 를 사용하고 이전 핸들을 저장하고 새 핸들을 설정합니다. 클라이언트 애플리케이션으로 돌아가기 전에 이전 리소스 핸들을 복원해야 합니다. 샘플 TESTDLL2 메뉴를 명시적으로 로드하는 데 이 방법을 사용합니다.

목록을 탐색하는 경우 몇 가지 단점이 있습니다. 약간 느리며 리소스 ID 범위를 관리해야 합니다. 여러 MFC 확장 DLL에 연결하는 클라이언트 애플리케이션이 DLL 인스턴스 핸들을 지정하지 않고도 모든 DLL 제공 리소스를 사용할 수 있다는 장점도 있습니다. AfxFindResourceHandle은 리소스 목록을 검색하여 지정된 일치 항목을 찾는 데 사용되는 API입니다. 리소스의 이름과 형식을 사용하고 리소스를 처음 찾은 리소스 핸들 또는 NULL을 반환합니다.

DLL 버전을 사용하는 애플리케이션 작성

애플리케이션 요구 사항

MFC의 공유 버전을 사용하는 애플리케이션은 몇 가지 기본 규칙을 따라야 합니다.

  • 개체가 CWinApp 있어야 하며 메시지 펌프에 대한 표준 규칙을 따라야 합니다.

  • 필요한 컴파일러 플래그 집합을 사용하여 컴파일해야 합니다(아래 참조).

  • MFCxx 가져오기 라이브러리와 연결해야 합니다. 필요한 컴파일러 플래그를 설정하면 MFC 헤더는 링크 타임에 애플리케이션이 연결해야 하는 라이브러리를 결정합니다.

  • 실행 파일을 MFCxx.DLL 실행하려면 경로 또는 Windows 시스템 디렉터리에 있어야 합니다.

개발 환경을 사용하여 빌드

대부분의 표준 기본값과 함께 내부 메이크파일을 사용하는 경우 프로젝트를 쉽게 변경하여 DLL 버전을 빌드할 수 있습니다.

다음 단계에서는 (디버그용) 및 NAFXCW.LIB (릴리스의 경우) 올바르게 작동하는 MFC 애플리케이션이 연결되어 NAFXCWD.LIB 있으며 MFC 라이브러리의 공유 버전을 사용하도록 변환하려고 합니다. Visual Studio 환경을 실행하고 있으며 내부 프로젝트 파일이 있습니다.

  1. 프로젝트 메뉴에서 속성을 선택합니다. 프로젝트 기본값 아래의 일반 페이지에서 공유 DLL(MFCxx(d).dll)에서 MFC를 사용하도록 Microsoft Foundation 클래스를 설정합니다.

NMAKE를 사용하여 빌드

컴파일러의 외부 메이크파일 기능을 사용하거나 NMAKE를 직접 사용하는 경우 필요한 컴파일러 및 링커 옵션을 지원하도록 메이크파일을 편집해야 합니다.

필수 컴파일러 플래그:

  • /D_AFXDLL /MD /D_AFXDLL

표준 MFC 헤더를 정의하려면 기호가 _AFXDLL 필요합니다.

  • /MD 애플리케이션은 C 런타임 라이브러리의 DLL 버전을 사용해야 합니다.

다른 모든 컴파일러 플래그는 MFC 기본값(예 _DEBUG : 디버그)을 따릅니다.

라이브러리의 링커 목록을 편집합니다. NAFXCWD.LIBMFCxxD.LIB 로 변경하고 NAFXCW.LIBMFCxx.LIB로 변경합니다. LIBC.LIBMSVCRT.LIB로 교체합니다. 다른 MFC 라이브러리와 마찬가지로 C 런타임 라이브러리 앞에 배치하는 MFCxxD.LIB 것이 중요합니다.

필요에 따라 릴리스 및 디버그 리소스 컴파일러 옵션(실제로 리소스를 사용하여 컴파일하는 옵션)에 추가 /D_AFXDLL 합니다 /R. 이 옵션을 사용하면 MFC DLL에 있는 리소스를 공유하여 최종 실행 파일을 더 작게 만듭니다.

이러한 변경 내용을 변경한 후에는 전체 다시 빌드가 필요합니다.

샘플 빌드

대부분의 MFC 샘플 프로그램은 Visual C++ 또는 명령줄의 공유 NMAKE 호환 MAKEFILE에서 빌드할 수 있습니다.

이러한 샘플을 사용하여 MFCxx.DLL변환하려면 MAK 파일을 Visual C++로 로드하고 위에서 설명한 대로 프로젝트 옵션을 설정할 수 있습니다. NMAKE 빌드를 사용하는 경우 NMAKE 명령줄에서 지정할 AFXDLL=1 수 있으며 공유 MFC 라이브러리를 사용하여 샘플을 빌드합니다.

MFC 고급 개념 샘플 DLLHUSK 는 DLL 버전의 MFC를 사용하여 빌드됩니다. 이 샘플은 연결된 MFCxx.DLL애플리케이션을 빌드하는 방법을 보여 줄 뿐만 아니라 이 기술 노트의 뒷부분에 설명된 MFC 확장 DLL과 같은 MFC DLL 패키징 옵션의 다른 기능도 보여 줍니다.

메모 패키징

DLLMFCxx.DLL (및 MFCxxU.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.DLLNAFXCW.LIB 경우 DLL에 패키지됩니다. MFC 소스 코드를 살펴보면 특별한 경우가 많지 않으므로 몇 #ifdef _AFXDLL 가지 사례를 찾을 수 있습니다. 특히 Windows 3.1에서 Win32를 처리하는 특수한 경우(Win32s라고도 함) Win32s는 프로세스별 DLL 데이터를 직접 지원하지 않습니다. MFC DLL은 TLS(스레드 로컬 스토리지) Win32 API를 사용하여 프로세스 로컬 데이터를 가져와야 합니다.

라이브러리 원본, 추가 파일에 미치는 영향

버전이 _AFXDLL 일반 MFC 클래스 라이브러리 원본 및 헤더에 미치는 영향은 비교적 미미합니다. 기본 헤더에 포함된 AFXWIN.H 특수 버전 파일(AFXV_DLL.H) 및 추가 헤더 파일(AFXDLL_.H)이 있습니다. 헤더에는 AFXDLL_.H 애플리케이션 및 MFC 확장 DLL의 _AFXDLL 클래스 및 기타 구현 세부 정보가 포함 CDynLinkLibrary 됩니다. 헤더는 AFXDLLX.H MFC 확장 DLL을 빌드하기 위해 제공됩니다(자세한 내용은 위 참조).

MFC SRC의 MFC 라이브러리에 대한 일반 소스에는 #ifdef 아래에 몇 가지 추가 조건부 코드가 _AFXDLL 있습니다. 추가 소스 파일(DLLINIT.CPP)에는 공유 버전의 MFC에 대한 추가 DLL 초기화 코드 및 기타 붙이기를 포함합니다.

MFC의 공유 버전을 빌드하기 위해 추가 파일이 제공됩니다. DLL을 빌드하는 방법에 대한 자세한 내용은 아래를 참조하세요.

  • DLL의 디버그() 및 릴리스(MFCxxD.DEFMFCxx.DEF) 버전에 대한 MFC DLL 진입점을 내보내는 데 두 개의 DEF 파일이 사용됩니다.

  • RC 파일(MFCDLL.RC)에는 모든 표준 MFC 리소스와 VERSIONINFO DLL에 대한 리소스가 포함됩니다.

  • ClassWizard를 사용하여 MFC 클래스를 검색할 수 있도록 CLW 파일(MFCDLL.CLW)이 제공됩니다. 이 기능은 DLL 버전의 MFC에만 국한되지 않습니다.

메모리 관리

사용하는 MFCxx.DLL 애플리케이션은 공유 C 런타임 DLL에서 제공하는 MSVCRTxx.DLL공통 메모리 할당자를 사용합니다. 애플리케이션, 모든 MFC 확장 DLL 및 MFC DLL은 이 공유 메모리 할당자를 사용합니다. 메모리 할당에 공유 DLL을 사용하여 MFC DLL은 나중에 애플리케이션에서 해제하거나 그 반대로 해제되는 메모리를 할당할 수 있습니다. 애플리케이션과 DLL 모두 동일한 할당자를 사용해야 하므로 C++ 전역 operator new 또는 operator delete를 재정의해서는 안 됩니다. C 런타임 메모리 할당 루틴의 나머지 부분(예: malloc, reallocfree및 기타)에도 동일한 규칙이 적용됩니다.

서수 및 클래스 __declspec(dllexport) 및 DLL 이름 지정

C++ 컴파일러의 기능은 사용하지 class__declspec(dllexport) 않습니다. 대신 내보내기 목록이 클래스 라이브러리 원본(MFCxx.DEF 및)에 MFCxxD.DEF포함됩니다. 선택한 진입점 집합(함수 및 데이터)만 내보냅니다. MFC 프라이빗 구현 함수 또는 클래스와 같은 다른 기호는 내보내지지 않습니다. 모든 내보내기가 상주 또는 비거주 이름 테이블에 문자열 이름 없이 서수로 수행됩니다.

사용 class__declspec(dllexport) 은 더 작은 DLL을 빌드하기 위한 실행 가능한 대안일 수 있지만 MFC와 같은 대규모 DLL에서는 기본 내보내기 메커니즘에 효율성과 용량 제한이 있습니다.

모든 의미는 많은 실행 또는 로드 속도를 손상시키지 않고 릴리스에서 MFCxx.DLL 약 800KB의 많은 기능을 패키지할 수 있다는 것입니다. MFCxx.DLL 이 기술을 사용하지 않았다면 100KB 더 컸을 것입니다. 이 기술을 사용하면 DEF 파일의 끝에 진입점을 더 추가할 수 있습니다. 서수로 내보내는 속도와 크기 효율성을 손상시키지 않고 간단한 버전 관리가 가능합니다. MFC 클래스 라이브러리의 주 버전 수정 버전이 라이브러리 이름을 변경합니다. 즉, MFC30.DLL MFC 클래스 라이브러리의 버전 3.0을 포함하는 재배포 가능 DLL입니다. 가상 MFC 3.1에서 이 DLL을 업그레이드하면 DLL의 이름이 대신 지정 MFC31.DLL 됩니다. MFC 소스 코드를 수정하여 MFC DLL의 사용자 지정 버전을 생성하는 경우 다른 이름(이름에 "MFC"가 없는 이름)을 사용하는 것이 좋습니다.

참고 항목

번호별 기술 참고 사항
범주별 기술 참고 사항