코딩캣: 코딩하는 고양이.
COM의 소개(파트 2) - COM 서버의 이면 (9) [完]
API/COM
2020. 10. 8. 20:36

COM의 소개(파트 2) – COM 서버의 이면

본 게시물은 ‘codeproject.com’에 게시된 글 ‘Introduction to COM Part II - Behind the Scenes of a COM Server’을 번역한 것입니다.

원 게시물은 https://www.codeproject.com/Articles/901/Introduction-to-COM-Part-II-Behind-the-Scenes-of-a에 게재되어 있습니다. 최대한 원문에 적힌 의도를 반영하고자 하였으나, 우리말로 읽었을 때 보다 자연스럽게 하고자 부득이 어순과 어휘를 조정한 부분도 있음을 양해 바랍니다.

또한 본 게시물에서 언급하고 있는 예제 소스 코드는 Visual C++ 6.0을 기준으로 작성되어 있기 때문에 후속 버전의 Visual Studio(또는 Visual Studio .NET)에서 자동 생성되는 COM 코드와는 다소 차이가 있음을 감안하고 읽으시기 바랍니다.

  1. COM의 소개(파트 2) – COM 서버의 이면 (1)
  2. COM의 소개(파트 2) – COM 서버의 이면 (2)
  3. COM의 소개(파트 2) – COM 서버의 이면 (3)
  4. COM의 소개(파트 2) – COM 서버의 이면 (4)
  5. COM의 소개(파트 2) – COM 서버의 이면 (5)
  6. COM의 소개(파트 2) – COM 서버의 이면 (6)
  7. COM의 소개(파트 2) – COM 서버의 이면 (7)
  8. COM의 소개(파트 2) – COM 서버의 이면 (8)
  9. COM의 소개(파트 2) – COM 서버의 이면 (9) [完]

 

우리의 COM 서버를 사용하는 COM 클라이언트

이제 우리는 COM 서버를 모두 마쳤습니다. 이것을 어떻게 사용할까요? 우리의 인터페이스는 사용자 인터페이스로서, C 및 C++ 클라이언트에서만 사용할 수 있습니다. 물론 우리가 만든 coclass는 IDispatch를 구현하여 Visual Basic, Windows Scripting hsost, 웹 페이지, Perl 등 다른 어느 언어에서도 사실상 사용이 가능합니다. 그러나 이와 관련해서는 다른 글에서 설명하는 것으로 미뤄두겠습니다. 필자는 ISimpleMsgBox를 사용하는 간단한 어플리케이션을 제공하였습니다.

.....

[Test MsgBox COM Server] 메뉴는 CSimpleMsgBoxImpl 객체를 생성하고 DoSimpleMsgBox를 호출합니다. 이것은 간단한 메소드이기 때문에, 소스 코드는 그다지 길지 않습니다. 우리는 먼저 CoCreateInstance를 사용하여 COM 객체를 생성합니다.

void DoMsgBoxTest(HWND hMainWnd) {
ISimpleMsgBox* pIMsgBox;
HRESULT hr;

hr = CoCreateInstance(__uuidof(CSimpleMsgBoxImpl), // coclass의 CLSID
                        NULL, // 객체 결합은 하지 않음
                        CLSCTX_INPROC_SERVER, // 인 프로세스 서버만 사용
                        __uuidof(ISimpleMsgBox), // 우리가 원하는 형태의 인터페이스
                        (void **)&pIMsgBox); // 인터페이스 포인터를 보관할 버퍼

    if (FAILED(hr)) return;

 

그 다음 우리는 DoSimpleMsgBox를 호출하고 인터페이스 포인터를 참조 해제합니다.

    pIMsgBox->DoSimpleMsgBox(hMainWnd, _bstr_t(TEXT("Hello COM!")));
    pIMsgBox->Release();
}

 

이것이 전부입니다. 소스 코드를 보면 많은 TRACE 구문이 있어서 디버거를 통해 어플리케이션을 실행할 때 서버의 각 메소드 중 어느 것이 실행되고 있는지를 확인할 수 있습니다.

[File] 메뉴의 또 다른 항목에서는 CoFreeUnusedLibraries API를 호출하고 있어서 여러분이 서버의 DllCanUnloadNow가 호출되는 것을 직접 보실 수 있습니다.

 

기타 사항

 

COM 매크로

COM 소스 코드에서는 자세한 구현을 숨기고 있는 몇 가지 매크로들이 존재하고 있고 이는 C와 C++ 클라이언트들이 동일한 선언을 할 수 있도록 해 줍니다. 본 글에서 필자는 그러한 매크로들을 사용한 적은 없습니다, 그러나 예제 소스 코드에서는 이를 사용하고 있습니다. 그래서 여러분은 이것들이 무슨 의미인지를 알아둘 필요가 있습니다. 여기 ISimpleMsgBox의 구현이 있습니다.

struct ISimpleMsgBox : public IUnknown {
    // IUnknown 메소드
    STDMETHOD_(ULONG, AddRef)() PURE;
    STDMETHOD_(ULONG, Release)() PURE;
    STDMETHOD(QueryInterface)(REFIID riid, void** ppv) PURE;

    // ISimpleMsgBox 메소드
    STDMETHOD(DoSimpleMsgBox)(HWND hwndParent, BSTR bsMessageText) PURE;
};

 

STDMETHOD 매크로는 virtual 키워드를 포함하고 있고, 반환 값이 HRESULT이며 __stdcall 호출 규약임을 자동으로 작성해줍니다. STDMETHOD_도 같습니다 그러나 여러분이 반환형을 직접 지정해야 합니다. PURE는 C++에서 완전 추상 함수를 의미하는 = 0 구문을 대신합니다.

STDMETHODSTDMETHOD_ 매크로로 선언된 함수는 각각 STDMETHODIMPSTDMETHODIMP_ 매크로를 써서 구현합니다. 예를 들어, STDMETHOD 매크로로 선언한 DoSimpleMsgBox은 다음과 같이 구현합니다.

STDMETHODIMP CSimpleMsgBoxImpl::DoSimpleMsgBox(HWND hwndParent, BSTR bsMessageText) {
    // ...
}

 

마지막으로 표준 내보내기 함수(standard exported function)는 다음과 같이 STDAPI 매크로로 선언합니다.

STDAPI DllRegisterServer()

 

STDAPI는 반환형과 호출 규약을 포함하고 있습니다.

STDAPI를 사용하면서 한 가지 번거롭게 된 것은 STDAPI의 치환에 의해 코드가 확장되는 구조상 여러분이 이 함수에 __declspec(dllexport)를 넣을 수 없다는 것입니다. 여러분은 내보내기 할 함수들에 대해 .def 파일을 사용해야 합니다.

 

COM 서버 등록과 등록 해제

필자가 앞서 언급하였듯이 COM 서버는 DllRegisterServerDllUnregisterServer 함수를 구현합니다. 그들의 역할은 우리가 만든 서버를 COM에게 알려주는 레지스트리 키들을 생성하거나 삭제하는 것입니다. 레지스트리에 대해 설명하기에는 지루하므로, 필자는 이를 반복하지는 않을 것입니다. 그러나 DllRegisterServer가 생성한 레지스트리 키에 대한 목록은 다음과 같습니다.

 

키 이름 키의 값
HKEY_CLASSES_ROOT  
    CLSID  
        {7D51904E-1645-4a8c-BDE0-0F4A44FC38C4} (기본값)="SimpleMsgBox class"
            InProcServer32 (기본값)="DLL 경로";ThreadingModel="Apartment"

 

예제 코드에 대한 참고 사항

동봉된 예제 코드는 COM 서버와 이를 시험하는 클라이언트 어플리케이션으로 구성되어 있습니다. SimpleComSvr.dsw는 워크스페이스 파일로서, COM 서버와 COM 클라이언트를 동시에 열어볼 수 있습니다. 워크스페이스와 같은 경로에 있는 두 개의 헤더 파일은 두 프로젝트에서 공동으로 사용되는 파일입니다. 두 프로젝트는 각각의 하위 디렉토리에 있습니다.

공동으로 쓰이는 헤더 파일은 다음과 같습니다.

1. ISimpleMsgBox.h: ISimpleMsgBox 인터페이스를 선언합니다.

2. SimpleMsgBoxComDef.h: __declspec(uuid()) 선언을 포함하고 있습니다. 별도의 파일에 보관한 이유는, COM 클라이언트는 CSimpleMsgBoxImplGUID만을 필요로 하고 그 클래스의 선언은 필요하지 않기 때문입니다. GUID 선언을 별도의 파일에 놓는 것은 CSimpleMsgBoxImpl의 내부 구조에 의존하지 않고 COM 클라이언트가 GUID에만 접근하도록 하기 위합니다. COM 클라이언트에게 중요한 것은 인터페이스인 ISimpleMsgBox입니다. (역자: 그것을 구체적으로 구현한 CSimpleMsgBoxImpl에 접근할 필요가 없습니다. 클라이언트 입장에서는 그저 CSimpleMsgBoxImpl에 대한 GUID만 있으면 됩니다.)

앞서 언급했듯이 여러분은 서버에서 함수를 내보내기 위하여 .def 파일이 필요합니다. 예제 소스 코드에 첨부된 .def 파일은 다음과 같은 형태로 되어 있습니다.

EXPORTS
    DllRegisterServer   PRIVATE
    DllUnregisterServer PRIVATE
    DllGetClassObject   PRIVATE
    DllCanUnloadNow     PRIVATE

 

각 줄은 함수의 이름과 PRIVATE 키워드를 갖습니다. 이 키워드의 의미는, 함수는 내보내기되지만 ‘import lib’에는 포함되지 않는다는 의미입니다. 다시 설명하면, COM 클라이언트는 ‘import lib’를 링크시킨다고 하더라도 코드를 작성할 때 이러한 함수를 직접 호출할 수 없음을 의미합니다. 이는 필수적인 사항이고, PRIVATE 키워드를 뺀다면 링커는 오류를 발생시킬 수 있습니다.

 

서버에 중단점 설정하기

여러분이 COM 서버 코드에 중단점을 넣고자 하는 경우, 여러분은 두 가지 방법을 사용할 수 있습니다. 첫 번째 방법은 COM 서버의 프로젝트를 활성 프로젝트로 설정하고 디버깅을 시작합니다. Microsoft Visual C++은 디버깅 세션을 시작하기 위한 실행 파일을 요청할 것입니다. COM 클라이언트를 필히 ‘빌드(build)’한 후에 만들어진 실행 파일의 전체 경로를 입력하면 됩니다.

다른 방법으로는 ‘TestClient(COM 클라이언트 프로젝트)’를 활성 프로젝트로 설정하고 COM 서버 프로젝트가 COM 클라이언트에 의존성을 갖도록 프로젝트 의존성을 설정합니다. 즉, 여러분이 COM 서버 측의 코드를 수정한다면, 여러분이 클라이언트 프로젝트를 빌드할 때 이 서버 프로젝트도 자동으로 빌드됩니다. 또한 여러분이 COM 클라이언트를 디버그할 때 Microsoft Visual C++이 COM 서버의 심볼을 로드하게 됩니다.

프로젝트 의존성 다이얼로그는 다음과 같이 생겼습니다.

[Project] 메뉴의 [Dependencies...]를 클릭한다.

 

프로젝트 의존성(Project Dependencies) 다이얼로그.

 

COM 서버 심볼을 로드하기 위하여, ‘TestClient’ 프로젝트 설정을 열고 ‘Debug’ 탭을 연 다음, ‘Category’ 콤보 상자에서 ‘Additional DLLs’를 선택합니다. 새로운 진입점을 추가하기 위하여 리스트 상자의 항목을 클릭하고 COM 서버의 전체 경로를 입력합니다. 다음의 예시를 참조하시기 바랍니다.

.

[Project] 메뉴의 [Settings...]를 클릭한다.

..

DLL 의존성을 설정하기 위해 DLL의 경로를 입력한다(1).
DLL 의존성을 설정하기 위해 DLL의 경로를 입력한다(2).

DLL의 경로는 여러분이 소스 코드를 어디에 압축 해제하는가에 의존하여 달라질 것입니다.

 

마무리

이전 게시글: COM의 소개(파트 2) – COM 서버의 이면 (8)

이것으로 Component Object Model(COM) 기반 클래스를 정의하고 사용하기 위한 기본적인 사항에 대해 살펴보았습니다.

다음 시리즈로 Windows 네이티브 개발을 위한 입문자를 위한 Windows 쉘 익스텐션(Shell Extension) 개발 가이드가 준비되어 있습니다.

 

'API/COM' 카테고리의 다른 글
더 보기...
태그 : 
댓글