COM, ATL

COM 개체 구현 실습

디버그정 2008. 8. 14. 20:50
http://dislab.hufs.ac.kr/lecture/cp/2003/com_local_server.ppt 문서의 HTML 버전입니다.
G o o g l e은 웹문서를 수집(crawl)하면서 자동으로 문서의 HTML 버전을 생성합니다.

COM 개체 구현 실습 

AddBack Local Server 예제 프로그램 작성

 
 
 
 
 

2  

AddBack Local Server 구현 

  • Visual C++ 개발 환경의 [File/New] 메뉴 항목을 선택
  • [New] 대화 상자의 [Properties] 탭에서 [Win32 Application] 항목 선택
  • [Location] 텍스트 상자에 앞에서 생성한 AddBack 폴더를 지정
  • [Project] 텍스트 상자에 ‘AddBack’이라고 입력
  • [Location] 텍스트 상자 끝에 추가된 ‘AddBack’을 ‘OutProc’으로 변경
  • [OK] 명령 단추를 클릭
 
 
 
 
 

3

 
 
 
 
 

4  

  • 앞에서 생성한 AddEnd.cpp Factory.cpp 파일을 OutProc 폴더에 복사한 , 파일을 AddBack 프로젝트에 추가
  • AddBack 프로젝트에 ‘AddBack.cpp’ 파일을 생성하여 추가한다
  • AddBack.cpp 파일에 다음과 같은 6개의 파일을 포함시킨다

    #include <windows.h>

    #include <ole2.h>

    #include "..\AddBack.h"

    #include "..\Factory.h"

    #include "..\Guid.h" 

    #include "..\Guid.cpp"

 
 
 
 
 

5  

  • WinMain 함수 구현
 

    HINSTANCE g_hInstance;

    DWORD g_dwRegister;

    CFAddBack* g_pFactory; 

    BOOL OpenFactory(void);

    BOOL CloseFactory(void);

    void RegisterServer (void);

    void UnregisterServer (void);

    BOOL SetRegKeyValue(LPTSTR pszKey, LPTSTR pszSubkey, LPTSTR pszValue); 

    int APIENTRY WinMain(HINSTANCE hInstance,

                         HINSTANCE hPrevInstance,

                         LPSTR     lpCmdLine,

                         int       nCmdShow)

    {

     // TODO: Place code here.

        HRESULT hr;

        MSG msg; 

        hr = CoInitialize(NULL);

        if(FAILED(hr)) {

                MessageBox(NULL,

                    "COM 라이브러리를 초기화할 수 없습니다!",

                    NULL, MB_OK);

                return 0;

        }

 
 
 
 
 

6  

       // EXE 화일은 DllGetClassObject, DllCanUnloadNow, DllRegisterServer,

       // DllUnregisterServer 함수를 노출시키지 못한다.

      

       // 명령행에서 WinMain 함수에 넘어온 매개변수의 값이 RegServer 또는 UnregServer 옵션을

       // 포함하고 있는 지를 검사하여 레지스트리에 등록 또는 해제하는 함수를 호출 

        if (lstrcmpiA(lpCmdLine, "-RegServer") == 0 ||

                    lstrcmpiA(lpCmdLine, "/RegServer") == 0) {

                g_hInstance = hInstance;

                RegisterServer();

                return 0;

        }

        else

        if (lstrcmpiA(lpCmdLine, "-UnregServer") == 0 ||

                    lstrcmpiA(lpCmdLine, "/UnregServer") == 0) {

                UnregisterServer();

                return 0;

        } 

        if (lstrcmpiA(lpCmdLine, "-Embedding") == 0 ||

                    lstrcmpiA(lpCmdLine, "/Embedding") == 0)

                OpenFactory(); 

        while(GetMessage(&msg, NULL, 0, 0)) {

                TranslateMessage(&msg);

                DispatchMessage(&msg);

        } 

        CloseFactory();

        ::CoUninitialize(); 

        return 0;

    }

 
 
 
 
 

7  

  • ROT(Running Object Table)에 클래스 팩토리 COM 개체를 등록하는 코드 구현
 

    BOOL OpenFactory(void) // CoGetClassObject에서 호출

    {     // (ROT 참조후 없으면 호출)

        BOOL bOK = FALSE;

        HRESULT hr; 

        g_pFactory = new CFAddBack;

        if(g_pFactory != NULL) {

            g_pFactory->AddRef();

            hr = ::CoRegisterClassObject(CLSID_AddBack, g_pFactory,

                          CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE,

                          &g_dwRegister);

            bOK = SUCCEEDED(hr);

            if(!bOK) {

                g_pFactory->Release();

                delete g_pFactory;

            }

        }

        else

            bOK = FALSE; 

        return bOK;

    }

 
 
 
 
 

8  

    BOOL CloseFactory(void)

    {

        BOOL bOK = TRUE;

        HRESULT hr; 

        if (g_dwRegister != 0) {

            hr = CoRevokeClassObject(g_dwRegister);

            if (FAILED(hr))

                bOK = FALSE;

        }

        if(g_pFactory)

            g_pFactory->Release(); 

        return bOK;

    }

 
 
 
 
 

9  

  • WinMain 함수 앞에 카운터로 사용할 전역 변수 정의
 

    HINSTANCE g_hInstance;

    DWORD g_dwRegister;

    CFAddBack* g_pFactory;

    LONG g_cObjects = 0; // COM 개체 카운터

    LONG g_cLocks = 0; // 로크 카운터 

    BOOL OpenFactory(void);

    BOOL CloseFactory(void);

    void RegisterServer (void);

    void UnregisterServer (void);

    BOOL SetRegKeyValue(LPTSTR pszKey, LPTSTR pszSubkey, LPTSTR pszValue); 

    int APIENTRY WinMain(HINSTANCE hInstance, 

  • AddBack 프로젝트에 ‘Add.h’ 헤더 파일을 생성하고 앞에서 정의한 전역 변수 선언
 

    extern LONG g_cObjects;

    extern LONG g_cLocks;

    void CloseExe (void);

 
 
 
 
 

10  

  • CFAddBack::CreateInstance 메소드에 COM 개체 카운터를 반영하는 코드 추가

    HRESULT __stdcall CFAddBack::CreateInstance(LPUNKNOWN pUnkOuter,

                                      REFIID riid, LPVOID* ppv)

    {

        HRESULT hr = E_FAIL;

        CAddBack* pAddBack = NULL;

        *ppv = NULL; 

        if(pUnkOuter != NULL)

            hr = CLASS_E_NOAGGREGATION;

        else {

            pAddBack = new CAddBack;

            if(pAddBack != NULL) {

                ++g_cObjects;      // COM 개체 카운터를 증가시킨다.

                // 클라이언트에서 요청한 인터페이스 포인터를

                // CAddBack COM 개체에게 요청한다.

                hr = pAddBack->QueryInterface(riid, ppv);

                if(FAILED(hr)) {

                    --g_cObjects;  // 실패한 경우 COM 개체 카운터를 감소시킨다.

                    delete pAddBack;

                    CloseExe();    // 가능하다면 종료한다.

                }

            }

            else

                hr = E_OUTOFMEMORY;

        }

        return hr;

    }

 
 
 
 
 

11  

  • CFAddBack::LockServer 메소드에 Lock Counter를 반영하는 코드 추가

    HRESULT __stdcall CFAddBack::LockServer(BOOL bLock)

    {

        if(bLock)

            ++g_cLocks;

        else

            --g_cLocks; 

        return S_OK;

    }

  • AddEnd.cpp 파일에 Add.h 헤더 파일을 포함시킨다
 

    #include "..\AddEnd.h"

    #include "Add.h" 

    #include "..\AddBack_i.c"

 
 
 
 
 

12  

  • CAddBack::Release 메소드에 COM 개체 카운터를 반영하는 코드를 추가

    ULONG __stdcall CAddBack::Release(void)

    {

        if(--m_cRef == 0) {

            // COM 개체 카운터를 감소시킨다.

            --g_cObjects;

            delete this;

            // 가능하다면 종료한다.

            CloseExe();

        }

        return m_cRef;

    }

  • CloseExe 함수를 구현

    void CloseExe (void)

    {

        if (g_cObjects == 0 && g_cLocks == 0)

            PostQuitMessage(0);

    }

 
 
 
 
 

13  

  • AddBack COM 컴포넌트를 레지스트리에 등록 및 해제하는 RegisterServer와 UnregisterServer 함수 구현
 

    void RegisterServer (void)

    {

        TCHAR szID[129];

        TCHAR szCLSID[129];

        TCHAR szModulePath[MAX_PATH];

        wchar_t wszCLSID[129]; 

        GetModuleFileName(g_hInstance, szModulePath,

                          sizeof(szModulePath)/sizeof(TCHAR));

        StringFromGUID2(CLSID_AddBack, wszCLSID, 128);

        wcstombs(szID, wszCLSID, 128) ; 

        lstrcpy(szCLSID, TEXT("CLSID\\"));

        lstrcat(szCLSID, szID);

        SetRegKeyValue(TEXT("AddBack.AddBack.1"), NULL,

                       TEXT("AddBack Component"));

        SetRegKeyValue(TEXT("AddBack.AddBack.1"), TEXT("CLSID"), szID); 

        SetRegKeyValue(szCLSID, NULL, TEXT("AddBack Component"));

        SetRegKeyValue(szCLSID, TEXT("ProgID"),

                       TEXT("AddBack.AddBack.1"));

        // 인-프로세스 서버와 다른 부분

        SetRegKeyValue(szCLSID, TEXT("LocalServer32"), szModulePath);

    }

 
 
 
 
 

14  

    void UnregisterServer (void)

    {

        TCHAR szID[129];

        TCHAR szCLSID[129];

        TCHAR szTemp[129];

        wchar_t wszCLSID[129]; 

        StringFromGUID2(CLSID_AddBack, wszCLSID, 128);

        wcstombs(szID, wszCLSID, 128) ; 

        lstrcpy(szCLSID, TEXT("CLSID\\"));

        lstrcat(szCLSID, szID); 

        RegDeleteKey(HKEY_CLASSES_ROOT, TEXT("AddBack.AddBack.1\\CLSID"));

        RegDeleteKey(HKEY_CLASSES_ROOT, TEXT("AddBack.AddBack.1")); 

        // 인-프로세스 서버와 다른 부분

        wsprintf(szTemp, TEXT("%s\\%s"), szCLSID, TEXT("LocalServer32"));

        RegDeleteKey(HKEY_CLASSES_ROOT, szTemp);

        wsprintf(szTemp, TEXT("%s\\%s"), szCLSID, TEXT("ProgID"));

        RegDeleteKey(HKEY_CLASSES_ROOT, szTemp);

        RegDeleteKey(HKEY_CLASSES_ROOT, szCLSID);

    }

 
 
 
 
 

15  

    BOOL SetRegKeyValue(LPTSTR pszKey, LPTSTR pszSubkey, LPTSTR pszValue)

    {

        BOOL bOk = FALSE;

        LONG ec;

        HKEY hKey;

        TCHAR szKey[256]; 

        lstrcpy(szKey, pszKey); 

        if(NULL != pszSubkey) {

            lstrcat(szKey, TEXT("\\"));

            lstrcat(szKey, pszSubkey);

        } 

        ec = RegCreateKeyEx(HKEY_CLASSES_ROOT, szKey, 0, NULL,

                    REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS,

                    NULL, &hKey, NULL); 

        if(ERROR_SUCCESS == ec) {

            if(NULL != pszValue) {

                ec = RegSetValueEx(hKey, NULL, 0, REG_SZ, (BYTE *)pszValue,

                                (lstrlen(pszValue)+1)*sizeof(TCHAR));

            }

            if(ERROR_SUCCESS == ec)

                bOk = TRUE;

            RegCloseKey(hKey);

        } 

        return bOk;

    }

 
 
 
 
 

16  

  • AddBack 프로젝트를 build하여 AddBack.exe 파일 생성
  • AddBack COM 컴포넌트를 레지스트리에 등록
    • AddBack /regserver
    • 이것을 수행하기 위해서는 예전에 등록하였던 것을 해제하고 새로 등록해야 한다.
  • AddFront 애플리케이션을 실행하여 AddBack COM 컴포넌트 실행 확인
    • AddBack 컴포넌트를 생성할 수 없다는 에러 발생
    • AddBack Local Server에 인터페이스를 marshalling하는 Proxy/Stub DLL이 없기 때문
 
 
 
 
 

17  

Proxy/Stub DLL 생성 

MIDL 컴파일러가 생성한 코드 사용

  • Visual C++ 개발 환경의 [File/New] 메뉴 항목 선택
  • [New] 대화 상자의 [Project] 탭에서 [Win32 Dynamic Link Library] 항목 선택
  • [Location] 텍스트 상자에 앞에서 생성한 AddBack 폴더 지정
  • [Project] 텍스트 상자에 ‘Marshal’이라고 입력
  • [Add to current workspace] 옵션 선택
  • [OK] 버튼 클릭
 
 
 
 
 

18

 
 
 
 
 

19  

  • MIDL 컴파일러가 생성한 AddBack_i.c, AddBack_p.c, 그리고 DllData.c 파일을 Marshal 프로젝트에 포함시킨다
 
 
 
 
 

20  

  • 프로젝트에 RPC runtime import library 추가하기 위해 [Project/Settings] 메뉴 항목을 선택하여 [Project Settings] 대화 상자의 [Link] 탭을 선택한 [Object/libraries modules] 텍스트 상자에 rpcrt4.lib 파일을 추가한다
 
 
 
 
 

21  

  • [Project Settings] 대화 상자 왼쪽의 트리뷰에서 DllData.c 항목을 선택하고, 오른쪽 [C/C++] 탭에서 [Preprocessor definitions] 텍스트 상자에 ‘REGISTER_PROXY_DLL’을 추가한다. 이것은 생성된 Proxy/Stub 코드가 자기 등록을 할 수 있게 하기 위한 것이다.
 
 
 
 
 

22  

  • Marshal 프로젝트에 ‘Marshal.def’ 파일을 생성하여 추가
 

    LIBRARY  Marshal.dll 

    DESCRIPTION 'Proxy/Stub DLL' 

    EXPORTS

                    DllGetClassObject @1 PRIVATE

                    DllCanUnloadNow  @2 PRIVATE

                    GetProxyDllInfo  @3 PRIVATE

                    DllRegisterServer @4 PRIVATE

                    DllUnregisterServer @5 PRIVATE 

  • Marshal 프로젝트를 build 하여 Marshal.dll 파일을 생성
  • regsvr32.exe를 사용하여 Proxy/Stub DLL COM 컴포넌트를 레지스트리에 등록
    • regsvr32 Marshal.dll
  • AddFront 애플리케이션을 실행하여 AddBack COM 컴포넌트가 제대로 실행되는지 확인