^(코딩캣)^ = @"코딩"하는 고양이;
썸네일 이미지
Windows 쉘 익스텐션 개발 가이드 - (0) 목차
입문자를 위한 Windows 쉘 익스텐션(Shell Extension) 개발 가이드 본 게시물은 ‘codeproject.com’에 게시된 “The Complete Idiot's Guide to Writing Shell Extensions” 시리즈를 우리말로 번역한 것입니다. 원문의 주소는 “https://www.codeproject.com/script/Articles/MemberArticles.aspx?amid=152”입니다. 원문은 2000년에 작성되었지만 네이티브 수준에서 Windows 운영체제가 근본적으로 바뀌지 않는 이상 현재에도 여전히 유효한 내용입니다. 다만 소스코드가 Visual C++ 6.0을 기준으로 작성되었기 때문에 현재 버전의 Visual Studio에서 자동으로 생성해주는 코드의 형..
API/COM
2020. 12. 25. 09:00

Windows 쉘 익스텐션 개발 가이드 - (0) 목차

API/COM
2020. 12. 25. 09:00

입문자를 위한 Windows 쉘 익스텐션(Shell Extension) 개발 가이드

본 게시물은 ‘codeproject.com’에 게시된 “The Complete Idiot's Guide to Writing Shell Extensions” 시리즈를 우리말로 번역한 것입니다.

원문의 주소는 “https://www.codeproject.com/script/Articles/MemberArticles.aspx?amid=152”입니다. 원문은 2000년에 작성되었지만 네이티브 수준에서 Windows 운영체제가 근본적으로 바뀌지 않는 이상 현재에도 여전히 유효한 내용입니다. 다만 소스코드가 Visual C++ 6.0을 기준으로 작성되었기 때문에 현재 버전의 Visual Studio에서 자동으로 생성해주는 코드의 형태와는 다소 차이가 있을 수 있음을 감안하시기 바랍니다.

또한 본 게시물은 원문을 최대한 직역하는 것을 지향하고 있으나, 우리말로 읽었을 때 보다 매끄럽게 하기 위하여 부득이 의역, 어순 조정 및 어휘 조정이 있음을 양해 바랍니다.

 

Part 0. 목차(Index)

입문자 가이드 시리즈의 분량이 다소 많아진 관계로, 각 글에 대한 목차와 빠른 참조를 제공할 수 있는 인덱스 글을 별도로 게시하게 되었습니다. 헌신적인 분들이 필자의 글을 다음과 같이 번역 및 게재하여 주셨습니다.

 

파트 1. 쉘 익스텐션(Shell Extension)을 작성하기 위한 단계별 튜토리얼

[그림 1] - [파트 1]에서 다룰 쉘 익스텐션의 실행 결과.

파트 1은 쉘 익스텐션의 개요를 보고 이를 어떻게 디버그할 것인지에 대해 설명하고 있습니다. 예제 프로그램에는 컨텍스트 메뉴에 텍스트 파일에 대한 확장을 추가해보는 기능이 포함되어 있습니다.

파트 1로 바로가기

 

파트 2. 여러 파일 선택 시 작동되는 쉘 익스텐션(Shell Extension)

[그림 2] - [파트 2]에서 다룰 쉘 익스텐션의 실행 결과.

파트 2는 한 번에 여러 개의 파일을 선택하고 컨텍스트 메뉴를 열었을 때 작동되는 쉘 익스텐션을 작성하는 방법에 대해 설명하고 있습니다. 예제 프로그램에는 DLL 파일에 대해 레지스트리에 ‘등록(register)’ 및 ‘등록 해제(unregister)’하는 명령을 컨텍스트 메뉴에 추가하는 기능이 포함되어 있습니다.

파트 2로 바로가기

 

파트 3. 파일에 대한 ‘팝업’ 정보를 보여주는 쉘 익스텐션(Shell Extension)

[그림 3] - [파트 3]에서 다룰 쉘 익스텐션의 실행 결과.

파트 3은 텍스트 파일에 대한 ‘인포팁(infotip)’을 사용자화 하는 QueryInfo 확장에 대해 설명하고 있습니다. 또한 쉘 익스텐션에서 MFC를 사용하는 방법에 대해 설명하고 있습니다.

파트 3으로 바로가기

 

파트 4. 사용자 정의 드래그/드롭을 제공하는 쉘 익스텐션(Shell Extension)

[그림 4] - [파트 4]에서 다룰 쉘 익스텐션의 실행 결과.

파트 4는 Windows 탐색기에서 마우스 오른쪽 버튼을 누른 상태로 사용자가 드래그 앤 드롭을 하였을 때 컨텍스트 메뉴에 나타나는 항목을 추가하는 방법에 대해 설명하고 있습니다. 예제 프로그램은 해당 파일에 대한 하드 링크를 만드는 유틸리티입니다. (참고: 이 확장은 Windows 2000 이상에서만 작동됩니다. 하지만 컴파일과 실행하는 것까지는 이전 버전의 Windows에서도 가능합니다. 그 방법은 본문에 포함되어 있습니다.)

파트 4로 바로가기

 

파트 5. 파일에 대한 등록 정보(속성) 대화상자에 페이지를 추가하는 쉘 익스텐션(Shell Extension)

[그림 5] - [파트 5]에서 다룰 쉘 익스텐션의 실행 결과.

파트 5는 Windows 탐색기의 등록 정보(또는 속성) 대화상자에 새로운 페이지를 추가하는 방법에 대해 설명하고 있습니다. 예제 프로그램에는 독자 여러분이 직접 파일의 생성된 날짜, 수정된 날짜 및 마지막으로 액세스된 날짜를 수정할 수 있는 페이지를 추가시키는 기능이 포함되어 있습니다.

파트 5로 바로가기

 

파트 6. ‘보내기’ 메뉴에서 사용될 수 있는 쉘 익스텐션(Shell Extension)

[그림 6] - [파트 6]에서 다룰 쉘 익스텐션의 실행 결과.

파트 6은 ‘보내기’ 메뉴에 추가될 수 있는 드롭 핸들러 확장에 대해 설명하고 있습니다. 예제 프로그램은 “Send To Any Folder” 도구의 클론입니다.

파트 6으로 바로가기

 

파트 7. 컨텍스트 메뉴에 그림 출력 및 디렉토리 여백에 마우스 우클릭 시 메뉴 확장

[그림 7] - [파트 7]에서 다룰 쉘 익스텐션의 실행 결과(1).
[그림 8] - [파트 7]에서 다룰 쉘 익스텐션의 실행 결과(2).

파트 7은 두 가지 주제에 대해 다루어 볼 것입니다. 하나는 컨텍스트 메뉴에 그림을 출력하는 것이고, 다른 하나는 Windows 탐색기에서 파일을 선택하지 않고 그 여백에 대해 마우스 오른쪽을 클릭하였을 때 나타나는 컨텍스트 메뉴를 확장하는 방법입니다. 예제 프로그램도 두 가지 기능을 포함하고 있습니다. 하나는 .bmp 파일을 선택 후 마우스 오른쪽 클릭을 하였을 때 컨텍스트 메뉴에 이미지 썸네일(thumbnail)을 출력하는 비트맵 뷰어이고, 다른 하나는 파일 또는 폴더를 선택하지 않고 탐색기의 여백을 마우스 오른쪽으로 클릭했을 때 나타나는 컨텍스트 메뉴에 간단한 항목을 하나 추가하는 쉘 익스텐션입니다.

파트 7로 바로가기

 

파트 8. 컨텍스트 메뉴에 그림 출력 및 디렉토리 여백에 마우스 우클릭 시 메뉴 확장

[그림 9] - [파트 8]에서 다룰 쉘 익스텐션의 실행 결과.

파트 8은 Windows 2000 이상의 운영체제에서 Windows 탐색기의 ‘자세히’ 보기 모드일 때 나타나는 열을 추가하는 방법에 대해 설명하고 있습니다. 예제 프로그램은 .mp3 파일에 대해 ID3v1 태그 데이터를 보여주는 열을 추가하는 기능을 포함하고 있습니다. (이 쉘 익스텐션은 Windows 2000 이상에서 작동합니다.)

파트 8로 바로가기

 

파트 9. 특정 파일에 대해 아이콘 표시를 사용자화 하기

[그림 10] - [파트 9]에서 다룰 쉘 익스텐션의 실행 결과.

파트 9는 파일에 따라 아이콘 표시를 사용자화하는 방법에 대해 설명하고 있습니다. 예제 프로그램은 텍스트 파일에 대해 그 용량에 따라 4가지 다른 아이콘으로 보여주는 기능을 포함하고 있습니다.

파트 9로 바로가기

 

카테고리 “API/COM”
more...
썸네일 이미지
COM의 소개(파트 2) - COM 서버의 이면 (9) [完]
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을 기준으로 작성되어 있기 때문에 후속 버전..
API/COM
2020. 10. 8. 20:36

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”
more...
COM의 소개(파트 2) - COM 서버의 이면 (8)
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을 기준으로 작성되어 있기 때문에 후속 버전..
API/COM
2020. 10. 8. 19:45

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

API/COM
2020. 10. 8. 19:45

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) [完]

 

간단한 사용자 인터페이스

실제로 클래스팩토리의 예제를 확인하기 위하여, 본 글에 수록된 샘플 프로젝트를 살펴보는 것부터 시작하겠습니다. 이 프로젝트는 CSimpleMsgBoxImpl이라는 이름의 coclass에서 ISimpleMsgBox 인터페이스를 구현하고 있는 DLL 서버입니다.

 

인터페이스 정의

우리가 만든 새로운 인터페이스의 이름을 ISimpleMsgBox로 정하겠습니다. 모든 인터페이스들과 마찬가지로 이 인터페이스도 IUnknown에서 파생되어야 합니다. 이 인터페이스에는 단 하나의 메소드인 DoSimpleMsgBox가 있습니다. 이 메소드는 표준 반환형인 HRESULT를 반환함을 숙지하시기 바랍니다. 여러분이 작성하는 모든 메소드들은 HRESULT를 반환형으로 삼아야 합니다. 그 외 다른 형식으로 호출자에게 값을 반환하고자 할 때는 포인터 파라미터를 사용해야 합니다.

struct ISimpleMsgBox : public IUnknown {
    /* IUnknown에서 정의된 메소드들 */
    ULONG AddRef();
    ULONG Release();
    HRESULT QueryInterface(REFIID riid, void ** ppv);

    /* ISimpleMsgBox 메소드 */
    HRESULT DoSimpleMsgBox(HWND hWndParent, BSTR bsMessageText);
};

struct declspec(uuid(“{7D51904D-1645-4a8c-BDE0-0F4A44FC38C4}”)) ISimpleMsgBox;

 

__declspec가 적혀있는 줄은 ISimpleMsgBox 심볼(symbol)에 GUID를 부여하고 있습니다. 이렇게 하면 GUID는 나중에 __uuidof 연산자를 통해 얻을 수 있습니다. __declspec__uuidof는 Microsoft C++ 확장입니다.

DoSimpleMsgBox의 두 번째 파라미터는 BSTR 형입니다. BSTR은 “바이너리 문자열(binary string)”을 의미하며, 고정 크기 바이트들의 시퀸스를 나타냅니다(역자: 즉 한 글자당 2바이트짜리가 계속 이어져 있다는 뜻입니다). BSTR은 주로 Visual Basic이나 Windows Scripting Host와 같은 스크립팅 클라이언트에서 사용됩니다.

이 인터페이스는 CSimpleMsgBoxImpl이라는 이름의 C++ 클래스로 구현됩니다. 클래스의 선언은 다음과 같습니다.

class CSimpleMsgBoxImpl : public ISimpleMsgBox {
public:
CSimpleMsgBoxImpl();
virtual ~CSimpleMsgBoxImpl();

    // IUnknown 메소드
    ULONG AddRef();
    ULONG Release();
    HRESULT QueryInterface( REFIID riid, void** ppv );

    // ISimpleMsgBox methods
    HRESULT DoSimpleMsgBox( HWND hwndParent, BSTR bsMessageText );

protected:
    ULONG m_uRefCount;
};

class __declspec(uuid("{7D51904E-1645-4a8c-BDE0-0F4A44FC38C4}")) CSimpleMsgBoxImpl;

 

COM 클라이언트가 SimpleMsgBox 형식의 COM 객체를 생성하고자 할 때 코드는 다음과 같이 작성합니다.

ISimpleMsgBox* pIMsgBox;
HRESULT hr;

hr = CoCreateInstance(__uuidof(CSimpleMsgBoxImpl), // coclass의 CLSID
    NULL, // 객체 통합하지 않음
    CLSCTX_INPROC_SERVER, // 인 프로세스 DLL 서버
    __uuidof(ISimpleMsgBox), // 우리가 원하는 인터페이스에 대한 IID
    (void**) &pIMsgBox); // 인터페이스 포인터의 주소

 

클래스팩토리

 

클래스팩토리 구현

SimpleMsgBox 팩토리는 C++ 클래스로 구현되어 있으며, 예상하다시피 CSimpleMsgBoxClassFactory로 명명합니다.

class CSimpleMsgBoxClassFactory : public IClassFactory {
public:
    CSimpleMsgBoxClassFactory();
    virtual ~CSimpleMsgBoxClassFactory();

    // IUnknown 메소드
    ULONG AddRef();
    ULONG Release();
    HRESULT QueryInterface(REFIID riid, void** ppv);

    // IClassFactory 메소드
    HRESULT CreateInstance(IUnknown* pUnkOuter, REFIID riid, void ** ppv);
    HRESULT LockServer(BOOL fLock);

protected:
    ULONG m_uRefCount;
};

 

생성자, 소멸자 및 IUnknown서 보았던 예제들과 거의 같게 작동됩니다. 여러분이 예상하듯이 IClassFactory의 메소드이며 새롭게 추가된 LockServer는 비교적 단순합니다.

HRESULT CSimpleMsgBoxClassFactory::LockServer(BOOL fLock) {
    fLock ? g_uDllLockCount++ : g_uDllLockCount--;
    return S_OK;
}

 

지금부터는 흥미로운 내용인 CreateInstance에 대해 살펴보겠습니다. 이 메소드는 새로운 CSimpleMsgBoxImpl 객체를 만드는 역할을 합니다. 이 메소드의 원형과 파라미터에 대해 자세히 보겠습니다.

HRESULT CSimpleMsgBoxClassFactory::CreateInstance(IUnknown * pUnkOuter, REFIID riid, void ** ppv);

 

pUnkOuter는 새 객체가 결합될 때에 한하여 사용됩니다. 그리고 이는 “외부(outer)” COM 객체를 가리키는 포인터입니다. 즉, 새로운 객체를 포함하게 될 기존의 객체입니다. 객체 결함은 본 글의 범위를 벗어나는 주제이므로 우리의 예제 객체는 결합을 지원하지 않을 것입니다.

riidppvQueryInterface에서와 동일하게 사용됩니다. 각각 COM 클라이언트가 요청하고 있는 인터페이스에 대한 IID이고, 인터페이스 포인터를 보관할 수 있는 포인터 크기의 버퍼입니다.

여기 CreateInstance 구현 예가 있습니다. 먼저 파라미터 유효성 검증과 초기화부터 시작합니다.

HRESULT CSimpleMsgBoxClassFactory::CreateInstance(IUnknown * pUnkOuter, REFIID riid, void ** ppv) {
    // 객체 결합을 사용하지 않을 것이므로 pUnkOuter는 항상 NULL이어야 합니다.
    if (pUnkOuter != NULL)
        return CLASS_E_NOAGGREGATION;

    // ppv가 실제 void * 형 포인터를 가리키고 있는지 검사합니다.
    if (IsBadWritePtr(ppv, sizeof(void *)))
        return E_POINTER;

    *ppv = NULL;

 

파라미터가 유효함을 확인하였기 때문에 우리는 새로운 객체를 생성할 수 있습니다.

CSimpleMsgBoxImpl* pMsgbox;

// 새로운 COM 객체를 생성합니다.
pMsgbox = new CSimpleMsgBoxImpl;

if (NULL == pMsgbox) return E_OUTOFMEMORY;

 

마지막으로 우리는 새롭게 생성되는 객체에 대해 클라이언트가 요청한 형식의 인터페이스로 QI를 합니다. QI가 실패하면 객체는 사용할 수 없는 것이므로 우리는 이를 해제합니다.

HRESULT hrRet;

// 클라이언트가 요청한 형식의 인터페이스로 새로 만든 객체에 대해 QI합니다.
hrRet = pMsgbox->QueryInterface(riid, ppv);

// QI가 실패하면 클라이언트가 사용할 수 없는 객체이므로 이를 해제합니다.
// 왜냐하면 객체로부터 원하는 인터페이스 형식을 얻을 수 없기 때문입니다.
if (FAILED(hrRet)) delete pMsgbox;

return hrRet;

 

DllGetClassObject

DllGetClassObject의 내부를 좀 더 자세히 들여다 보겠습니다. 원형은 다음과 같습니다.

HRESULT DllGetClassObject(REFCLSID rclsid, REFIID riid, void ** ppv);

 

rclsid는 COM 클라이언트가 필요로 하는 coclass의 CLSID입니다. 이 함수는 해당 coclass에 대한 클래스팩토리를 반드시 반환해야 합니다.

riidppv는 QI에서 언급한 파라미터와 같습니다. 이 경우 riid는 COM 라이브러리가 요청하는 클래스팩토리 객체에 대한 IID, 즉 IID_IClassFactory입니다.

DllGetClassObject가 새로운 COM 객체로서 클래스 팩토리를 반환하기 때문에, 이 코드는 IClassFactory::CreateInstance와 비슷해 보입니다. 유효성 검사와 초기화로 시작하는 부분을 살펴보겠습니다.

HRESULT DllGetClassObject(REFCLSID rclsid, REFIID riid, void ** ppv) {

    // 클라이언트가 CSimpleMsgBoxImpl에 대한 팩토리를 요청하고 있는지 검사합니다.
    if (!InlineIsEqualGUID(rclsid, __uuidof(CSimpleMsgBoxImpl)))
        return CLASS_E_CLASSNOTAVAILABLE;

    // ppv가 실제 void * 형 버퍼를 가리키고 있는지 검사합니다.
    if ( IsBadWritePtr ( ppv, sizeof(void*) ))
        return E_POINTER;

    *ppv = NULL;

 

먼저 if 구문이 rclsid 파라미터를 검사합니다. 우리의 COM 서버는 단 하나의 coclass만을 가지고 있기 때문에, rclsid는 반드시 CSimpleMsgBoxImpl에 대한 CLSID여야 합니다. __uuidof 연산자는 앞서 __deslcped(uuid()) 선언으로 CSimpleMsgBoxImpl 클래스에 부여된 GUID를 가져오는 작동을 합니다. InlineIsEqualsGUID는 두 개의 GUID가 서로 같은지 여부를 확인하는 인라인 함수입니다.

다음 단계는 클래스팩토리 생성 단계입니다.

CSimpleMsgBoxClassFactory* pFactory;

// 새로운 클래스팩토리를 생성합니다.
pFactory = new CSimpleMsgBoxClassFactory;

if (pFactory == NULL)
        return E_OUTOFMEMORY;

 

이 부분이 CreateInstance와 다소 다른 부분입니다. CreateInstance로 돌아가서, 우리는 QI의 호출이 실패하였을 때 COM 객체를 할당 해제하였습니다. 하지만 이 부분은 그렇지 않습니다.

우리는 스스로를 우리가 만든 COM 객체(클래스팩토리)의 클라이언트라고 생각할 수 있습니다. 그래서 우리는 이 객체의 레퍼런스 카운트를 1로 만들기 위해 AddRef를 호출합니다. 그 다음 우리는 QI를 호출합니다. QI가 성공하면 이 객체에 한번 더 AddRef가 이루어져서 레퍼런스 카운트가 2가 됩니다. QI가 실패하면 레퍼런스 카운트는 1로 감소합니다.

QI가 호출된 후 우리는 클래스팩토리 객체의 사용을 더 이상 안 할 것이기 때문에 Release를 호출합니다. QI가 실패하면 이 클래스팩토리의 레퍼런스카운트가 0이 되어 스스로 할당 해제합니다. 결국 결과는 같습니다.

// 클래스팩토리를 사용하고 있는 동안에는 AddRef()를 한 번 해줍니다.
pFactory->AddRef();

HRESULT hrRet;

// 클래스팩토리에 대해 COM 클라이언트가 요청한 형태의 인터페이스가 있는지 QI합니다.
hrRet = pFactory->QueryInterface(riid, ppv);

// 클래스팩토리의 사용을 끝냈으므로 Release합니다.
pFactory->Release();

return hrRet;

 

QueryInterface 다시 살펴보기

필자는 이전에 QI의 구현을 보인 바 있습니다. 그러나 실전에서는 COM 객체가 꼭 IUnknown만을 구현하지 않기 때문에 클래스팩토리의 QI는 다시 살펴보는 것이 좋습니다. 먼저 우리는 ppv 버퍼가 유효한지 검사하고 이를 초기화합니다.

HRESULT CSimpleMsgBoxClassFactory::QueryInterface(REFIID riid, void ** ppv) {
HRESULT hrRet = S_OK;

    // ppv가 실제로 void * 형 버퍼를 참조하고 있는 지 검사합니다.
    if (IsBadWritePtr(ppv, sizeof(void *)))
        return E_POINTER;

    // 표준 QI 초기화: *ppv를 NULL로 설정합니다.
    *ppv = NULL;

 

그 다음 우리는 riid를 검사하여 해당 클래스팩토리에서 구현하고 있는 IUnknown 또는 IClassFactory 중 하나의 것인지를 확인합니다.

// COM 클라이언트가 우리자 지원할 수 있는 형태의 인터페이스를 요청하고 있다면 *ppv 설정
if (InlineIsEqualGUID(riid, IID_IUnknown)) {
    *ppv = (IUnknown*) this;
} else if (InlineIsEqualGUID(riid, IID_IClassFactory)) {
    *ppv = (IClassFactory *) this;
} else {
    hrRet = E_NOINTERFACE;
}

 

마지막으로 riid가 가리키는 인터페이스가 우리가 지원할 수 있는 인터페이스일 때 우리는 AddRef를 호출하여 인터페이스 포인터의 레퍼런스 카운트를 1만큼 증가시킵니다. 그 다음 인터페이스 포인터를 반환합니다.

// 인터페이스 포인터를 반환할 수 있게 되었다면, AddRef 호출
if (hrRet == S_OK) {
        ((IUnknown *) *ppv)->AddRef();
}

return hrRet;

 

ISimpleMsgBox 구현

우리의 소스 코드는 ISimpleMsgBox가 가진 유일한 메소드인 DoSimpleMsgBox를 가지고 있습니다. 우리는 bsMessageTextTCHAR로 변환하기 위해 마이크로소프트의 확장 클래스인 _bstr_t를 처음으로 사용해 봅니다.

HRESULT CSimpleMsgBoxImpl::DoSimpleMsgBox(HWND hwndParent, BSTR bsMessageText) {
    _bstr_t bsMsg = bsMessageText;
    LPCTSTR szMsg = (TCHAR *) bsMsg;  // 필요하다면 _bstr_t 문자열을 TCHSR로 변환합니다.
    // 변환 후에 우리는 메시지 박스를 보여주고 값을 반환합니다.
    MessageBox(hwndParent, szMsg, _T("Simple Message Box"), MB_OK);
    return S_OK;
}

 

계속 읽기

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

다음 게시글: COM의 소개(파트 2) – COM 서버의 이면 (9) [完]

 

카테고리 “API/COM”
more...
COM의 소개(파트 2) - COM 서버의 이면 (7)
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을 기준으로 작성되어 있기 때문에 후속 버전..
API/COM
2020. 10. 8. 15:33

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

API/COM
2020. 10. 8. 15:33

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이 COM 객체를 생성하거나 파괴하기 위한 언어 독립적인 과정을 갖는다고 설명하였습니다. COM 클라이언트는 COM 객체를 새로 만들기 위하여 CoCreateInstance를 호출합니다. 지금부터는 우리는 이것을 서버 사이드에서 보도록 하겠습니다.

여러분이 coclass를 구현할 때마다, 여러분은 또한 가장 최초의 coclass 인스턴스 생성을 담당하는 동반 coclass를 작성하게 됩니다. 이 동반 coclass는 해당 coclass의 ‘클래스팩토리(class factory)’라고 부르며 이것의 유일한 목적은 COM 객체를 생성하는 것입니다. 클래스팩토리를 갖는 이유 또한 언어 독립성을 지키기 위함입니다. COM 그 자체는 COM 객체를 생성할 수 없습니다. 왜냐하면 그렇게 하는 것은 언어 독립적(language independent)이지도 않고, 구현 독립적(implementation independent)이지도 않기 때문입니다.

COM 클라이언트가 COM 객체를 생성할 때, COM 라이브러리는 COM 서버에 있는 클래스팩토리에게 이를 요청합니다. 그러면 클래스팩토리는 COM 클라이언트가 반환 받을 수 있는 COM 객체를 생성합니다. 이와 같은 연동 메커니즘이 바로 내보내기되는 함수인 DllGetClassObject입니다.

사족: “클래스팩토리(class factory)”와 “클래스 객체(class object)”는 사실상 같은 대상을 지칭하는 말입니다. 그러나 두 용어 모두 클래스팩토리의 목적을 정확하게 묘사하지는 못하고 있습니다. 왜냐하면 클래스팩토리는 COM 클래스를 만드는 것이 아니라 COM 객체를 만들기 때문입니다. 따라서 “클래스팩토리(class factory)”라는 용어를 접했을 때 여러분은 머릿속으로 “객체 팩토리(object factory)”로 치환해서 생각하는 것이 도움이 될 수 있습니다. (사실 MFC에서는 클래스팩토리 구현체에 COleObjectFactory라는 이름을 붙임으로써 실제로 이를 시전한 바 있습니다.) 그러나 공식적인 용어가 “클래스팩토리”인 만큼, 필자도 본 글에서는 “클래스팩토리” 용어에 따르겠습니다.

COM 라이브러리가 DllGetClassObject를 호출할 때 클라이언트가 요구한 CLSID를 함께 전달합니다. COM 서버는 요청 받은 CLSID에 해당하는 클래스팩토리를 생성하고 이를 전달할 책임이 있습니다. 클래스팩토리는 그 자체로 coclass이고 IClassFactory 인터페이스를 구현하고 있습니다. DllGetClassObject의 작동이 성공하면 COM 서버는 COM 라이브러리에게 IClassFactory의 포인터를 반환합니다. 그러면 IClassFactory의 메소드를 통해 클라이언트가 요구한 COM 객체 인스턴스가 만들어집니다.

IClassFactory 인터페이스는 다음과 같이 생겼습니다.

struct IClassFactory : public IUnknown {
    HRESULT CreateInstance(IUnknown * pUnkOuter, REFIID riid, void ** ppvObject);
    HRESULT LockServer(BOOL fLock);
};

 

CreateInstance는 새로운 COM 객체를 생성하는 메소드입니다. LockServer는 필요 시 COM 라이브러리가 COM 서버에 대한 레퍼런스 카운트를 증감할 수 있게 합니다.

 

계속 읽기

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

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

 

카테고리 “API/COM”
more...
COM의 소개(파트 2) - COM 서버의 이면 (6)
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을 기준으로 작성되어 있기 때문에 후속 버전..
API/COM
2020. 10. 8. 15:25

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

API/COM
2020. 10. 8. 15:25

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 서버가 작동을 하기 위해서는 무엇보다도 Windows 레지스트리에 적절하게 등록되어 있어야만 합니다. 여러분이 레지스트리 편집기를 통해 HKEY_CLASSES_ROOT\CLSID를 탐색하면 무수히 많은 하위 키들을 보게 되실 것입니다. HKCR\CLSID는 현재 컴퓨터에서 사용 가능한 모든 COM 서버들을 리스트로서 보관하고 있습니다.

대개 DllRegisterServer를 통해 COM 서버가 등록이 되면, 표준적인 레지스트리 포맷에 따라 COM 서버의 GUID에서 따온 새로운 키가 CLSID 하위에 추가됩니다. 레지스트리 포맷의 GUID란 다음과 같은

{067DF822-EAB6-11CF-B56E-00A0244D5087}

처럼 중괄호와 하이픈이 포함된 것입니다. 문자는 대문자도 되고 소문자도 됩니다.

이 레지스트리 키의 기본값은 인간이 읽을 수 있는 형태로 적힌 coclass 이름입니다. 이 값은 Visual C++에서 함께 제공하고 있는 OLE/COM Object Viewer와 같이 사용자 인터페이스에 출력하기 적합한 형태여야 합니다.

GUID 이름으로 된 하위 키의 하위에는 더 많은 정보들이 보관될 수 있습니다. 여러분이 생성해야하는 이러한 레지스트리 키의 종류는 대부분 여러분이 만들고 있는 COM 서버의 종류에 달려 있습니다.

우리가 만들고 있는 간단한 ‘인 프로세스 서버’ 의 목적에 따르면 우리는 InProcServer32라는 이름을 갖는 하나의 레지스트리만 있으면 됩니다.

InProcServer32 레지스트리 키는 두 개의 문자열을 포함하고 있습니다. 하나는 기본값으로서 COM 서버 DLL 파일의 전체 경로를 나타내고, 다른 하나는 스레드 모델을 지정하는 ThreadingModel이라는 값입니다. 스레드 모델은 본 글의 범위를 벗어나므로 싱글 스레드 COM 서버를 만들고자 할 때 사용되는 스레드 모델은 Apartment라고만 알아두시기 바랍니다.

 

계속 읽기

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

다음 게시글: COM의 소개(파트 2) – COM 서버의 이면 (7)

 

카테고리 “API/COM”
more...
COM의 소개(파트 2) - COM 서버의 이면 (5)
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을 기준으로 작성되어 있기 때문에 후속 버전..
API/COM
2020. 10. 8. 15:18

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

API/COM
2020. 10. 8. 15:18

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) [完]

 

CoCreateInstance의 내부

필자의 이전 COM 소개글로 돌아가서, 우리는 COM 클라이언트가 COM 객체를 요청할 때 이를 생성하는 CoCreateInstance를 보았습니다. 클라이언트의 관점에서 이는 블랙박스입니다. 단지 적절한 파라미터를 제공하여 CoCreateInstance를 호출하기만 하면, 짜잔! 여러분은 COM 객체를 얻을 수 있습니다. 물론 어떤 흑마법(?)도 포함되어있지 않습니다. COM 서버를 적재하고, 요청한 COM 객체를 생성하고, 그리고 요청한 인터페이스의 형태로 번환해주기까지 잘 정의된 처리과정이 수행될 뿐입니다.

다음은 그 처리과정의 간략한 개요입니다. 몇 가지 낯선 용어들이 있습니다만, 걱정할 필요가 없습니다. 필자는 이후 절에서 모두 다룰 것이기 때문입니다.

1. 클라이언트 프로그램이 원하는 IIDCLSID를 전달하여 CoCreateInstance를 호출합니다.

2. COM 라이브러리는 HKEY\CLASSES_ROOT\CLSID의 하위 키들 중 해당 COM 서버의 CLSID를 찾습니다. 이 하위 키에는 COM 서버의 등록 정보가 들어 있습니다.

3. COM 라이브러리는 COM 서버 DLL의 전체 경로를 읽고 COM 클라이언트의 프로세스 공간 속으로 이 DLL을 적재합니다.

4. COM 라이브러리는 COM 클라이언트가 요청한 coclass를 만들 수 있는 클래스팩토리를 요청하기 위하여 COM 서버에 있는 DllGetClassObject 함수를 호출합니다.

5. 서버는 클래스팩토리를 생성하여 이를 DllGetClassObject를 통해 반환합니다.

6. COM 라이브러리는 클래스팩토리에 있는 CreateInstance를 호출하여 COM 클라이언트가 요청했던 COM 객체를 생성합니다.

7. CoCreateInstance는 COM 객체로부터 COM 클라이언트가 요청했던 인터페이스 포인터를 반환합니다.

 

계속 읽기

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

다음 게시글: COM의 소개(파트 2) – COM 서버의 이면 (6)

 

카테고리 “API/COM”
more...
COM의 소개(파트 2) - COM 서버의 이면 (4)
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을 기준으로 작성되어 있기 때문에 후속 버전..
API/COM
2020. 10. 8. 15:07

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

API/COM
2020. 10. 8. 15:07

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) [完]

 

IUnknown부터 시작하여 인터페이스 구현하기

모든 인터페이스는 IUnknown으로부터 파생됨을 떠올려 봅시다. 왜냐하면 IUnknown은 COM 객체의 두 가지 기본 기능인 레퍼런스 카운트와 인터페이스 쿼리를 다루고 있기 때문입니다. 여러분이 coclass를 작성할 때, 여러분은 또은 여러분의 필요에 맞게 IUnknown을 구현해야 합니다. IUnknown만을 구현하는 coclass 예제를 살펴봅시다. 이는 여러분이 작성할 수 있는 coclass 중 가장 간단한 클래스입니다. 우리는 CUnknownImpl이라는 이름을 가진 C++ 클래스에서 IUnknown을 구현할 것입니다. 클래스의 선언은 다음과 같습니다.

class CUnknownImpl : public IUnknown {
public:
    CUnknownImpl(); // 생성자
    virtual ~CUnknownImpl(); // 소멸자

    /* 다음 3개는 IUnknown 인터페이스로부터 유래된 메소드 */
    ULONG AddRef();
    ULONG Release();
    HRESULT QueryInterface(REFIID riid, void ** ppv);

protected:
    UINT m_uRefCount; // COM 객체의 레퍼런스 카운트
};

 

생성자와 소멸자

생성자와 소멸자는 COM 서버의 레퍼런스 카운트를 증감합니다.

extern UINT g_uDllRefCount;

CUnknownImpl::CUnknownImpl() {
    m_uRefCount = 0;
    g_uDllRefCount++;
}

CUnknownImpl::~CUnknownImpl() {
    g_uDllRefCount--;
}

 

생성자는 새로운 COM 객체가 생성될 때 호출됩니다. 그러므로 COM 서버가 메모리에 남아있게 하도록 COM 서버의 레퍼런스 카운트를 1만큼 증가시킵니다. 또한 생성자는 COM 객체의 레퍼런스 카운트를 0으로 초기화합니다. COM 객체가 소멸될 때, 소멸자는 서버의 레퍼런스 카운트를 1만큼 감소시킵니다.

 

AddRef와 Release

이들 두 메소드는 COM 객체의 수명을 제어하는 메소드입니다. AddRef는 간단하게 구현 가능합니다.

ULONG CUnknownImpl::AddRef() {
    return ++m_uRefCount;
}

 

AddRef는 COM 객체의 레퍼런스 카운트를 1만큼 증가시키고, 증가된 값을 반환합니다. 다만 ReleaseAddRef만큼 간단하지는 않습니다.

ULONG CUnknownImpl::Release() {
    ULONG uRet = --m_uRefCount;

    if (m_uRefCount == 0) delete this;
    return uRet;
}

 

COM 객체의 레퍼런스 카운트를 감소시키는 것뿐만 아니라, Release는 더 이상 명시적인 참조가 없는 경우 객체 스스로를 파괴합니다. Release는 또한 갱신된 레퍼런스 카운트를 반환합니다. 다만 이와 같은 Release 구현은 COM 객체가 힙(heap) 영역에 생성되었음을 가정하고 있습니다. 여러분이 COM 객체를 스택이나 전역 범위에 생성하였을 경우 COM 객체가 스스로를 소멸시키려 할 때 모든 것들이 엉망이 되고 맙니다.

이제 여러분의 클라이언트 앱에서 왜 AddRefRelease 메소드가 적절하게 호출되어야 하는지에 대한 이유가 명확해야 합니다. 여러분이 이들 메소드를 정확하게 호출해주지 않는다면 여러분이 사용하고 있는 COM 객체가 너무 빨리 소멸될 수도 있고, 아예 소멸되지 않을 수도 있습니다. COM 객체가 너무 일찍 소멸되어 버리면 이후 COM 서버 전체가 메모리에서 날아가버릴 수 있습니다. 이는 여러분의 어플리케이션이 서버에 접속을 시도할 때 충돌을 야기합니다.

여러분이 멀티스레드 프로그래밍을 해본 경험이 있다면, 여러분은 왜 변수를 증감할 때 InterlockedIncrement, InterlockedDecrement 대신에 단순 증감연산자 ++--를 쓰는지 의아해 하실 것입니다. 싱글 스레드 COM 서버에서 증감연산자 ++--를 사용하는 것은 전적으로 안전합니다. 왜냐하면 클라이언트 어플리케이션이 멀티 스레드이고 메소드 호출이 제각각의 스레드에서 이루어진다고 해도, COM 라이브러리는 이러한 메소드 호출을 직렬화하기 때문입니다. 이것은 어떤 하나의 메소드 호출이 시작되면, 메소드를 호출한 다른 스레드들은 앞서 호출된 메소드 실행이 끝날 때까지 잠겨 있음을 뜻합니다. COM 라이브러리는 우리가 만들고 있는 COM 서버에 한 번에 하나 이상의 스레드가 진입하지 않을 것임을 보장합니다.

 

QueryInterface

QueryInterface, 줄여서 QI는 COM 클라이언트가 하나의 COM 객체에서 다양한 인터페이스들을 요청할 때 사용됩니다.

우리의 예제 coclass는 단지 하나의 인터페이스만을 구현하고 있기 때문에, 우리가 다룰 QI는 매우 단순할 것입니다. QI는 두 가지 파라미터를 받습니다. 하나는 요청하는 인터페이스의 IID이고 다른 하나는 쿼리 작업이 성공하였을 때 인터페이스 포인터를 보관하게 될 포인터 크기의 버퍼입니다.

HRESULT CUnknownImpl::QueryInterface(REFIID riid, void ** ppv) {
    HRESULT hrRet = S_OK;

    // 표준 QI 초기화로서 ppv를 NULL 참조하게 설정합니다.
    *ppv = NULL;

    if (IsQeualIID(riid, IID_IUnknown)) {
    // COM 클라이언트가 요청하는 인터페이스가 이 coclass에서 지원 가능한 형식이면
        *ppv = (IUnknown *)this;
    } else {
        // COM 클라이언트가 요청하는 인터페이스 형식이 이 coclass와 호환되지 않으면
        hrRet = E_NOINTERFACE;
    }

     // 인터페이스 포인터를 반환할 수 있다면 AddRef를 호출합니다.
    if (hrRet == S_OK) {
        ((IUnknown *)*ppv)->AddRef();
    }

    return hrRet;
}

 

예제 소스의 QI에는 다음의 세 가지 작업이 포함되어 있습니다.

1. 반환을 위해 전달된 포인터 파라미터를 NULL로 초기화합니다: *ppv = NULL;

2. COM 클라이언트가 요청한 riid 값이 우리의 coclass가 구현하고 있는 인터페이스의 IID 중에 있는지를 확인합니다: if (IsEqualIID(riid, IID_IUnknown))

3. 요청한 형식의 인터페이스를 우리의 coclass가 구현하고 있다면, COM 객체의 레퍼런스 카운트를 증가시킵니다: ((IUnknown *)*ppv)->AddRef();

 

AddRef를 호출하는 것은 매우 중요합니다. 다음의 문장

*ppv = (IUnknown *)this;

은 COM 객체에 대한 새로운 참조를 형성합니다. 그러므로 우리는 AddRef를 호출함으로써 이 객체에게 새로운 참조 관계가 추가되었음을 알려야만 합니다.

AddRef를 호출하는데 IUnknown * 형으로 캐스팅하는 것이 이상하게 보일 수는 있습니다. 그러나 특별한 coclass의 QI에서 *ppvIUnknown *이 아닐 수 있습니다. 그러므로 캐스트하여 호출하는 습관을 들이는 것이 좋습니다.

이제 우리는 DLL 서버의 내부적인 세부 사항을 일부 다루어 보았습니다, 다음 절에서는 처음으로 돌아가서 COM 클라이언트가 CoCreateInstance를 실행하였을 때 우리가 만들고 있는 COM 서버가 어떻게 사용되는지에 대해 살펴보겠습니다.

 

계속 읽기

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

다음 게시글: COM의 소개(파트 2) – COM 서버의 이면 (5)

 

카테고리 “API/COM”
more...
COM의 소개(파트 2) - COM 서버의 이면 (3)
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을 기준으로 작성되어 있기 때문에 후속 버전..
API/COM
2020. 10. 8. 07:48

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

API/COM
2020. 10. 8. 07:48

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) [完]

 

서버 수명 관리

DLL 서버의 특이한 것은, 그들 스스로 메모리에서 얼마나 머물러 있을 것인지를 제어한다는 것입니다. 일반적인 DLL 파일은 수동적이어서, 전적으로 그 DLL을 사용하는 어플리케이션에서 적재할 것인지, 적재 해제할 것인지에 따릅니다. 기술적으로 COM으로 만든 DLL 서버도 근본이 DLL 파일이기 때문에 수동적입니다. 그러나 COM 라이브러리는 서버가 COM 라이브러리에게 적재 해제해도 되는지를 결정할 수 있도록 기회를 주는 메커니즘을 가지고 있습니다.

이 작업은 앞서 설명한 내보내기된 함수 DllCanUnloadNow가 수행합니다. 이 함수의 원형은 다음과 같습니다.

HRESULT DllCanUnloadNow();

 

COM 서버를 사용하던 클라이언트 어플리케이션이 유휴 시간 동안 COM 라이브러리 API인 CoFreeUnusedLibraries를 실행하면, 어플리케이션이 적재하고 있는 모든 DLL 서버에게 하나씩 DllCanUnloadNow 함수를 실행하면서 각각 적재 해제 가능 여부를 확인합니다.

COM 서버가 좀 더 적재되어 있고자 한다면 S_FALSE를 반환하고, 서버가 더 이상 메모리에 남아있을 이유가 없다고 판단하면 S_OK를 반환하여 COM 라이브러리가 자신을 적재 해제하게 합니다.

COM 서버 스스로 메모리에서 적재 해제되어도 좋은지 여부를 확인하는 방법에는 레퍼런스 카운트가 있습니다. 레퍼런스 카운트를 사용한 DllCanUnloadNow는 다음과 같이 구현할 수 있습니다.

extern UINT g_uDllRefCount; // COM 서버가 어플리케이션에 참조된 횟수

HRESULT DllCanUnloadNow() {
    return (g_uDllRefCount > 0) ? S_FALSE : S_OK;
}

 

다음 절에서 레퍼런스 카운트가 어떻게 유지되는지 살펴보겠습니다.

 

계속 읽기

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

다음 게시글: COM의 소개(파트 2) – COM 서버의 이면 (4)

 

카테고리 “API/COM”
more...
COM의 소개(파트 2) - COM 서버의 이면 (2)
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을 기준으로 작성되어 있기 때문에 후속 버전..
API/COM
2020. 10. 8. 07:41

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

API/COM
2020. 10. 8. 07:41

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 서버인 ‘인 프로세스 서버(in-process server)’ 형태의 서버에 대해 살펴보겠습니다.

‘인 프로세스(in-process)’라는 말은 클라이언트 프로그램의 프로세스 영역에 COM 서버가 적재(load)된다는 뜻입니다. ‘인 프로세스(줄여서 in-proc)’ 서버는 항상 DLL 파일 형태로 존재하고, 클라이언트 프로그램이 설치된 컴퓨터와 같은 컴퓨터에 설치되어야 합니다.

 

인 프로세서 서버는 COM 라이브러리가 사용하기 전에 다음의 두 조건을 만족해야 합니다.

1. HKEY_CLASSES_ROOT\CLSID의 하위 키로 적절하게 등록되어 있어야 합니다.

2. DllGetClassObject라는 이름의 함수를 내보내야(export)합니다.

 

위의 두 조건은 여러분이 인 프로세스 서버를 작동시키기 위하여 필요한 최소한의 조건입니다. HKEY_CLASSES_ROOT\CLSID의 하위 키 이름은 COM 서버의 GUID여야 합니다. 그리고 키는 COM 서버의 위치와 스레드 모델에 대한 값을 반드시 가지고 있어야 합니다.

DllGetClassObject 함수는 COM 라이브러리가 CoCreateInstance API에 따라 작업을 수행할 때 호출되는 함수입니다.

COM 서버는 또한 다음의 세 가지 함수를 내보내야(export) 합니다.

DllCanUnloadNow
COM 서버가 메모리에서 적재 해제될 수 있는지 COM 라이브러리에서 확인하고자 할 때 호출되는 함수입니다.
DllRegisterServer
regsvr32와 같은 설치 유틸리티가 COM 서버를 등록하는 과정에서 호출되는 함수입니다.
DllUnregisterServer
regsvr32와 같은 설치 제거 유틸리티가 COM 서버를 등록 해제할 때 DllRegisterServer가 생성한 레지스트리 키를 삭제하기 위해 호출되는 함수입니다.

 

물론 위와 같은 함수들을 단순히 내보낸다고 해서 되는 것은 아니고, COM 기술 사항을 준수해야 COM 라이브러리와 COM 클라이언트에서 이 COM 서버를 사용할 수 있습니다.

 

계속 읽기

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

다음 게시글: COM의 소개(파트 2) – COM 서버의 이면 (3)

 

카테고리 “API/COM”
more...
COM의 소개(파트 2) - COM 서버의 이면 (1)
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을 기준으로 작성되어 있기 때문에 후속 버전..
API/COM
2020. 10. 7. 17:00

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

API/COM
2020. 10. 7. 17:00

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) [完]

COMIntro2_src.zip
0.02MB

COMIntro2_demo.zip
0.04MB

 

본 게시글의 목적

필자의 첫 번째 COM 소개 글로써, 필자는 COM을 이제 막 배우기 시작하였거나, 기초를 이해하고자 하는 프로그래머들을 위한 튜토리얼을 작성하였습니다. 본 게시글에서는 서버 사이드 측면에서 COM을 살펴보고, 여러분이 직접 COM 인터페이스와 COM 서버를 작성하기 위해 필요한 단계들에 대해 설명할 것입니다. 물론 COM 라이브러리에서 호출했을 때 COM 서버에서 어떤 일들이 일어나는지도 자세히 살펴볼 것입니다.

 

도입

독자 여러분이 이미 필자의 이전 글을 읽어 보셨다면, 여러분은 클라이언트로서 COM을 사용할 때 어떤 것들이 포함되어야 하는지에 대해 정통할 것입니다.

이제 COM을 다른 측면에서 접근해 볼 차례입니다. 이미 컴파일되어 숨어있는 COM 서버입니다. 필자는 이제 아무 클래스 라이브러리도 포함되어 있지 않은 백지 상태의 C++ 언어 프로젝트에서부터 어떻게 COM 서버를 작성하는지에 대해 차근차근 설명하겠습니다.

물론 요즘에는 이러한 접근법이 큰 필요성은 없습니다만, 컴파일되어 숨게 되는 COM 서버를 만드는데 있어 전체적인 코드를 살펴보는 것은, COM 서버에서 일어나는 모든 일들을 확실히 이해하는데 가장 좋은 방법입니다.

본 글에서는 여러분이 C++ 언어를 습득하였다고 보고, 또한 필자가 앞서 적은 글을 통해 COM에서 사용하는 용어와 개념들을 숙지하였다고 보고 내용을 작성하겠습니다. 본 글은 다음과 같은 절(section)으로 구성되어 있습니다.

 

《COM 서버 훑어보기》

COM 서버에 필요한 기초 요구 사항들을 설명합니다.

 

《서버 수명 관리》

COM 서버가 메모리에 적재되는 기간이 어떻게 제어되는지에 대해 설명합니다.

 

《IUnknown으로부터 인터페이스 구현하기》

C++ 클래스에서 인터페이스의 구현을 어떻게 작성하는지 보여주고, IUnknown에 포함된 메소드들의 목적에 대해 설명합니다.

 

《CoCreateInstance의 내부》

독자 여러분이 CoCreateInstance를 호출하면 어떤 일들이 일어나는지에 대해 살펴보겠습니다.

 

《COM 객체를 만드는 클래스팩토리》

여러분의 클라이언트 프로그램이 사용할 수 있도록 COM 객체가 생성되는 과정에 대해 설명합니다.

 

《간단한 사용자 인터페이스》

이전 절들에서 설명한 개념들을 활용하는 예제 코드입니다.

 

《우리가 만든 COM 서버를 사용하는 COM 클라이언트》

우리가 만든 COM 서버를 테스트할 수 있는 간단한 COM 클라이언트 예제입니다.

 

《기타 세부 사항》

소스코드와 디버깅에 대한 참고 사항입니다.

 

카테고리 “API/COM”
more...
썸네일 이미지
COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (8) [完]
COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? 본 게시물은 ‘codeproject.com’에 게시된 글 ‘Introduction to COM - What It Is and How to Use It.’을 번역한 것입니다. 원 게시물은 https://www.codeproject.com/Articles/633/Introduction-to-COM-What-It-Is-and-How-to-Use-It에 게재되어 있습니다. 최대한 원문에 적힌 의도를 반영하고자 하였으나, 우리말로 읽었을 때 보다 자연스럽게 하고자 부득이 어순과 어휘를 조정한 부분도 있음을 양해 바랍니다. 또한 본 게시물에서 언급하고 있는 예제 소스 코드는 Visual C++ 6.0을 기준으로 작성되어 있기 때문에 후속 버전의 V..
API/COM
2020. 10. 4. 18:04

COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (8) [完]

API/COM
2020. 10. 4. 18:04

COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가?

본 게시물은 ‘codeproject.com’에 게시된 글 ‘Introduction to COM - What It Is and How to Use It.’을 번역한 것입니다.

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

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

  1. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (1)
  2. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (2)
  3. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (3)
  4. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (4)
  5. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (5)
  6. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (6)
  7. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (7)
  8. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (8) [完]

 

HRESULT 다루기

필자는 SUCCEEDEDFAILED 매크로를 사용하여, 오류를 처리하는 방법 몇 가지를 간단하게 언급하였습니다. 지금부터는 COM 메소드가 반환하는 HRESULT를 가지고 좀 더 자세히 다루어 보겠습니다.

HRESULT는 32비트 부호 있는 정수입니다. 음수가 아닌 값은 성공하였음을 나타내고, 음수 값은 실패를 나타냅니다. HRESULT는 (1) 성공 또는 실패를 나타내는 ‘심각도 비트(severity bit)’, (2) ‘패실리티 코드(facility code)’, (3) ‘상태 코드(status code)’의 세 가지 필드를 갖습니다(역자: facility code의 적절한 우리말을 찾지 못하여 그냥 그대로 적었습니다).

‘facility’는 HRESULT가 유래된 컴포넌트 또는 프로그램을 의미합니다. 마이크로소프트는 다양한 컴포넌트에 facility code를 배정하고 있습니다. COM은 이 중 하나이고, 작업 스케줄러도 이 중 하나입니다. ’코드’는 16비트 필드이며 그 자체로 어떤 의미를 내재하지는 않습니다. 즉, 코드는 숫자가 될 수도 있고, GetLastError가 반환하는 값과 비슷하게 어떤 의미가 될 수도 있는데 이는 그때그때 다릅니다.

여러분이 winerror.h 파일을 살펴본다면, 수 많은 HRESULT들이 적혀 있음을 알 수 있습니다. 그리고 이들의 이름은 [facility]_[severity]_[description]의 구조로 되어 있음을 알 수 있습니다. 아무 컴포넌트에서나 반환 가능한 일반적인 HRESULTE_OUTOFMEMORY와 같이 그 이름에 facility를 갖고 있지 않습니다.

예를 들어,

REGDB_E_READREGDB
패실리티(facility)가 REGDB(레지스트리 데이터베이스), 심각도는 E(오류), 코드는 이 경우 상황을 설명하는 READREGDB입니다. 조합하면 중대한 오류로서, 데이터베이스를 읽지 못함을 의미합니다.
S_OK
패실리티(facility)는 일반(generic)이고, 심각도는 S(성공)입니다. OK는 현 상황의 상태를 나타내며, 조합하면 모든 것이 정상이라는 뜻입니다.

다행히도, winerror.h를 열어놓지 않아도 HRESULT의 의미를 찾아볼 수 있습니다. 윈도우 운영체제에 내장된 패실리티(facility)는 ‘오류 값 찾기 도구(Error Lookup Tool)’를 이용하여 찾을 수 있습니다.

여러분이 CoCreateInstance를 호출하기 전에 CoInitialize 호출하는 것을 깜빡하였다면, CoCreateInstance0x800401F0을 반환할 것입니다. 독자 여러분은 Error Lookup 도구에 이 값을 검색할 것이고, “CoInitialize가 호출되지 않았습니다.”라는 설명을 보게 됩니다.

Error Lookup 도구를 사용하면 HRESULT 값을 문자열로 된 설명으로 확인할 수 있다.

여러분은 또한 디버거에서 HRESULT 설명을 찾을 수 있습니다. 여러분의 HRESULT 형 변수를 hres라고 이름 붙였다면, 변수 감시 창(Watch 창)에 “hres,hr”을 입력함으로써 해당 변수의 값을 볼 수 있습니다. “,hr”은 Visual C++에게 그 변수의 값을 HRESULT로 간주하고 이에 해당하는 텍스트 설명으로 보여주도록 지정합니다.

디버그 중일 때 Watch 화면을 통해 HRESULT 값을 상수 이름으로 볼 수 있다.

 

참고자료

《Essential COM》 by Don Box (ISBN 0-201-63446-5)

여러분이 한 번쯤은 알고 싶어하실 COM 사양과 IDL(interface, definition language)에 대한 모든 것이 나와 있습니다. 특히 처음 두 챕터에는 COM 스펙과 그리고 이것이 해결하고자 했던 문제에 대해 자세히 나와 있습니다.

《MFC Internals》 by George Shepherd and Scot Wingo (ISBN 0-201-40721-3)

MFC의 COM 지원에 대해 심도 깊은 내용을 포함하고 있습니다.

《Beginning ATL 3 COM Programming》 by Richard Grimes 외 공저 (ISBN 1-861001-20-7)

이 책은 ATL을 사용하여 여러분이 직접 COM 컴포넌트를 개발할 경우에 대하여 상세히 적고 있습니다.

 

카테고리 “API/COM”
more...
COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (7)
COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? 본 게시물은 ‘codeproject.com’에 게시된 글 ‘Introduction to COM - What It Is and How to Use It.’을 번역한 것입니다. 원 게시물은 https://www.codeproject.com/Articles/633/Introduction-to-COM-What-It-Is-and-How-to-Use-It에 게재되어 있습니다. 최대한 원문에 적힌 의도를 반영하고자 하였으나, 우리말로 읽었을 때 보다 자연스럽게 하고자 부득이 어순과 어휘를 조정한 부분도 있음을 양해 바랍니다. 또한 본 게시물에서 언급하고 있는 예제 소스 코드는 Visual C++ 6.0을 기준으로 작성되어 있기 때문에 후속 버전의 V..
API/COM
2020. 10. 4. 14:13

COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (7)

API/COM
2020. 10. 4. 14:13

COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가?

본 게시물은 ‘codeproject.com’에 게시된 글 ‘Introduction to COM - What It Is and How to Use It.’을 번역한 것입니다.

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

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

  1. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (1)
  2. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (2)
  3. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (3)
  4. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (4)
  5. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (5)
  6. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (6)
  7. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (7)
  8. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (8) [完]

 

함께 해 봅시다 – 예제 코드

아래 두 예제를 통해 본 글에서 다루었던 COM 개념들을 시연해보겠습니다.

 

단일 인터페이스를 가지고 COM 객체를 사용하기

첫 번째 예제는 하나의 인터페이스를 노출하고 있는 COM 객체를 어떻게 사용하는지를 보여주고 있습니다. 이 예제는 여러분이 앞으로 마주치게 될 소스 코드 중 가장 간단한 것입니다. 이 코드는 현재 바탕화면으로 지정된 그림파일의 이름을 얻기 위하여 쉘에 부속된 액티브 데스크톱 coclass를 사용합니다. 이 코드가 작동되기 위해서 여러분은 윈도우 운영체제에 액티브 데스크톱 기능이 설치되어 있어야 합니다.

이 예제에는 다음과 같은 단계가 포함됩니다.

1. COM 라이브러리를 초기화하기

2. 액티브 데스크톱과 상호작용하기 위해 사용되는 COM 객체를 생성하고, IActiveDesktop 인터페이스에 대한 포인터를 얻기

3. COM 객체에 있는 GetWallpaper 메소드를 호출하기

4. GetWallpaper 호출이 성공하면 바탕화면으로 지정된 그림파일의 이름을 얻기

5. 인터페이스를 포인터에 대해 Release 호출하기

6. COM 라이브러리를 정리하고 사용 해제하기

WCHAR wszWallpaper[MAX_PATH];
CString strPath;
HRESULT hr;
IActiveDesktop * pIAD;

// 1. COM 라이브러리를 초기화하기(윈도우 운영체제로 하여금 DLL 적재하게 하기)
// 일반적으로 여러분은 이 작업을 InitInstance나 기타 초기 호출 코드에서 할 것입니다.
// MFC에서는 AfxOleInit()을 대신 사용합니다.
CoInitialize(NULL);

// 2. 쉘이 제공하는 액티브 데스크톱 coclass를 사용하여 COM 객체를 생성합니다.
// 4번째 파라미터는 우리가 어떤 인터페이스를 원하는지를 지정하는 옵션입니다.
hr = CoCreateInstance(CLSID_ActiveDesktop,
    NULL,
    CLSCTX_INPROC_SERVER,
    IID_IActiveDesktop,
    (void **)&pIAD);

if (SUCCEEDED(hr)) {
    // 3. COM 객체 생성되었다면, GetWallpaper를 호출합니다.
    hr = pIAD->GetWallpaper(wszWallpaper, MAX_PATH, 0);
    if (SUCCEEDED(hr)) {
    // 4. GetWallpaper 호출이 성공하였다면 파일 이름을 출력합니다
    // 유니코드 문자열을 출력하기 위해 wcout을 사용할 것입니다.
    // wcout은 cout과 동일하나 유니코드 버전입니다.
    std::wcout << L"Wallpaper path is \n" << wszWallpaper << std::endl << std::endl;
} else {
    std::cout << "GetWallpaper() failed." << std::endl << std::endl;
}
    // 인터페이스 포인터에 대해 사용 해제하기
    pIAD->Release();
} else {
    std::cout << "CoCreateInstance failed." << std::endl << std::endl;
}

// COM 라이브러리의 사용을 해제합니다. 
// MFC 개발의 경우 MFC자 알아서 하기 때문에 이 과정이 필요 없습니다.
CoUninitialize()

위 예제 코드에서 필자는 유니코드 문자열인 wszWallpaper를 출력하기 위하여 std::wcout을 사용하였습니다.

 

다중 인터페이스를 갖는 COM 객체 사용하기

두 번째 예제는 하나의 인터페이스를 노출하고 있는 COM 객체에 대해 QueryInterface를 어떻게 사용하는지를 보여주고 있습니다. 이 예제에서는 앞서 우리가 확인한 바탕화면 그림파일의 경로를 가지고 바로가기 아이콘을 만들기 위하여 쉘에 포함된 Shell Link coclass를 사용합니다.

이 예제는 다음과 같은 단계를 포함합니다.

1. COM 라이브러리를 초기화하기

2. 바로가기 아이콘을 만드는 COM 객체를 생성하고 IShellLink 인터페이스 포인터를 얻기

3. IShellLink 인터페이스의 SetPath 메소드 호출하기

4. COM 객체에 대해 QueryInterface를 호출하여 IPersistFile 인터페이스 포인터를 얻기

5. IPersistFileSave 메소드를 호출하기

6. 모든 인터페이스 포인터에 대해 Release하기

7. COM 라이브러리를 사용 해제하기

CString sWallpaper = wszWallpaper; // 바탕화면 경로를 ANSI 문자열로 변환
IShellLink * pISL;
IPersistFile * pIPF;

// 1. COM 라이브러리를 초기화하기(윈도우 운영체제로 하여금 DLL 적재하게 하기)
// 일반적으로 여러분은 이 작업을 InitInstance나 기타 초기 호출 코드에서 할 것입니다.
// MFC에서는 AfxOleInit()을 대신 사용합니다.
CoInitialize(NULL);

// 2. COM 객체를 사용하기 위하여 쉘에서 제공하는 Shell Link coclass를 사용합니다.
// 4번째 파라미터로 우리가 필요로 하는 인터페이스 포인터가 IShellLink임을 알려줍니다.
hr = CoCreateInstance(CLSID_ShellLink,
    NULL,
    CLSCTX_INPROC_SERVER,
    IID_IShellLink,
    (void **)&pISL);

if (SUCCEEDED(hr)) {
    // 3. 바탕화면 그림파일의 경로를 바로가기 대상으로 지정합니다.
    hr = pISL->SetPath(sWallpaper);
    
    if (SUCCEEDED(hr)) {
        // 4. 두 번째 인터페이스인 IPersistFile에 대한 포인터를 얻습니다.
        hr = pISL->QueryInterface(IID_IPersistFile, (void **)&pIPF);
        
        if (SUCCEEDED(hr)) {
        // 5. 바로가기 파일을 저장합니다. 첫 번째 파라미터는 유니코드 문자열입니다.
        hr = pIPF->Save(L”C:\\wallpaper.lnk”, FALSE);
        
        if (SUCCEEDED(hr)) {
            // 저장 성공한 경우 처리할 내용
        } else {
            // 저장 실패한 경우 처리할 내용
        }
        
        // 6. IPersistFile 인터페이스에 대한 포인터를 참조 해제합니다.
        pIPF->Release();
    } else {
        // QueryInterface 실패한 경우 처리할 내용
    }
    
    // 6. IShellLink 인터페이스에 대한 포인터를 참조 해제합니다.
    pISL->Release();
} else {
    // SetPath 실패한 경우 처리할 내용
}
카테고리 “API/COM”
more...
COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (6)
COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? 본 게시물은 ‘codeproject.com’에 게시된 글 ‘Introduction to COM - What It Is and How to Use It.’을 번역한 것입니다. 원 게시물은 https://www.codeproject.com/Articles/633/Introduction-to-COM-What-It-Is-and-How-to-Use-It에 게재되어 있습니다. 최대한 원문에 적힌 의도를 반영하고자 하였으나, 우리말로 읽었을 때 보다 자연스럽게 하고자 부득이 어순과 어휘를 조정한 부분도 있음을 양해 바랍니다. 또한 본 게시물에서 언급하고 있는 예제 소스 코드는 Visual C++ 6.0을 기준으로 작성되어 있기 때문에 후속 버전의 V..
API/COM
2020. 10. 4. 13:20

COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (6)

API/COM
2020. 10. 4. 13:20

COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가?

본 게시물은 ‘codeproject.com’에 게시된 글 ‘Introduction to COM - What It Is and How to Use It.’을 번역한 것입니다.

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

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

  1. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (1)
  2. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (2)
  3. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (3)
  4. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (4)
  5. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (5)
  6. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (6)
  7. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (7)
  8. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (8) [完]

 

주의 깊게 보세요 – 문자열 취급

COM 코드에서 문자열을 다루는 방법을 설명하기 위하여 잠시 다른 이야기를 해볼까 합니다. 독자 여러분이 Unicode와 ANSI 문자열이 어떻게 작동되는지에 대해 잘 알고 있고, 이 두 문자열 사이에서 어떻게 변환해야 하는지도 잘 알고 있다면 이 절을 건너뛰어도 됩니다. 그렇지 않은 경우 이 절을 주의 깊게 읽어보시기 바랍니다.

COM 메소드가 문자열을 반환할 때, 그 문자열은 유니코드로 되어있을 것입니다(물론 모든 메소드가 COM 기술 사항을 그대로 만족하면서 작성된다면 말입니다). 유니코드는 아스키코드처럼 문자를 인코딩하는 스키마 중 하나로서, 대부분의 문자는 2바이트 길이를 갖습니다(역자: 원문에는 모든 문자라 되어있지만, 자주 쓰이지 않는 한자(벽자)라든가 최근 도입되는 이모지 문자처럼 UTF-16으로 인코드할 때 1글자가 2바이트 이상인 문자도 있습니다). 독자 여러분이 문자열을 보다 처리하기 용이한 형태로 얻기 위해서 COM 메소드가 반환하는 문자열을 TCHAR 형 문자열로 변환하여야 합니다.

TCHAR라든가 _t로 시작하는 함수들(예를 들어 _tcscpy)은 컴파일러 설정에 따라 여러분이 하나의 소스코드로 유니코드 문자열과 ANSI 문자열 모두를 다룰 수 있도록 디자인된 것입니다. 대부분의 경우 여러분은 ANSI 문자열과 ANSI형 Windows API를 사용하는 코드를 작성하게 될 것입니다(역자: Windows NT, 2000 및 XP 이후로는 굳이 컴파일러의 설정을 바꾸지 않는 이상 TCHAR 형이 WCHAR 형에 맞추어져 있으므로 유니코드 문자열이 기본입니다. 그러나 printf 등의 C 함수들을 사용할 경우를 대비하여 유니코드 문자열을 ANSI 문자열로 변환하는 방법을 알아둘 필요는 있습니다.). 때문에 이 글의 대부분에서, 필자는 간결함을 위해 TCHAR 대신 char을 사용할 것입니다. 여러분은 다른 누군가가 작성한 코드를 마주하였을 때를 대비하여 TCHAR 타입에 대하여 확실하게 숙지하시기 바랍니다.

여러분이 COM 메소드로부터 유니코드 문자열을 전달받았을 때 여러분은 이를 다음 몇 가지 방법으로 char 형 문자열로 변환할 수 있습니다.

1. WideCharToMultiByte Windows API를 호출하기

2. CRT 함수인 wcstombs 호출하기

3. MFC에 한하여 CString 생성자 또는 할당 연산자 사용하기

4. ATL 문자열 변환 매크로 사용하기

 

WideCharToMultiByte

WideCharToMultiByte Windows API를 사용하여 여러분은 유니코드 문자열을 ANSI 문자열로 변환할 수 있습니다. 원형은 다음과 같습니다.

int WideCharToMultiByte(
    UINT CodePage,
    DWORD dwFlags,
    LPCWSTR lpWideCharStr,
    int cchWideChar,
    LPSTR lpMultiByteStr,
    int cbMultiByte,
    LPCSTR lpDefaultChar,
    LPBOOL lpUsedDefaultChar);

각 파라미터의 의미는 다음과 같습니다.

CodePage
유니코드 문자열을 ANSI 문자열로 변환할 때 대상이 되는 코드페이지입니다. 운영체제에서 현재설정된 ANSI 코드페이지로 변환하고자 할 때 여러분은 CP_ACP를 전달하면 됩니다(역자: 기본 로마자가 1바이트이고, 한글/한자/특수기호가 2바이트인 일반적인 한국어 ANSI 문자열은 CP 949입니다). 코드페이지는 256개의 문자 집합으로 되어 있습니다. 0부터 127번 문자는 항상 표준 아스키코드 문자에 대응합니다. 128번부터 255번 문자는 코드페이지마다 달라지며, 그래픽문자나 발음기호를 포함하는 로마자가 포함될 수 있습니다. 각 언어 또는 지역은 각자의 코드페이지를 갖습니다. 그러므로 강세표시된 문자를 올바르게 표시하기 위해서는 정확한 코드페이지를 지정하고 사용할 필요가 있습니다(역자: 한국어의 경우 CP 949를 맞추어 주어야 글자가 깨지지 않습니다).
dwFlags
조합된 유니코드 문자를 어떻게 다룰 것인지에 대한 방법을 지정합니다. 여기서 조합된 문자란, 기본 로마자에 강세표시나 발음기호 문자 등이 붙는 경우입니다. 예를 들어 è라는 문자에 대해 보겠습니다. 지정된 코드페이지에 이러한 문자가 정의되어 있다면, 아무 문제 없이 그대로 코드값만 바뀝니다. 그러나 해당 코드페이지에 이 문자가 정의되어 있지 않은 경우 Windows 운영체제는 이 문자를 다른 무언가로 바꾸어 나타낼 필요가 있습니다.
WC_COMPOSITECHECK 옵션을 전달하면, 이 API는 지정된 코드페이지에 그러한 문자가 정의되어 있는지를 사전에 검사할 수 있습니다. WC_SEPCHARS 옵션을 전달하면 Windows 운영체제는 위와 같은 글자를 기본 로마자(e)와 강세 표시(`)라는 2개의 문자로 쪼개어 표현할 수 있습니다. WC_DISCARDNS 옵션을 전달하면 Windows 운영체제는 위와 같은 문자에 대해 강세표시나 발음기호를 제거하고 기본 문자인 e로만 변환합니다. WC_DEFAULTCHAR 옵션을 전달하면 Windows 운영체제는 후술할 lpDefaultChar 파라미터로 전달된 기본 문자로 치환합니다. 이 파라미터의 기본값은 WC_SEPCHARS입니다.
lpWideCharStr
특정 코드페이지의 ANSI 문자열로 변환할 원본 유니코드 문자열입니다.
cchWideChar
유니코드 문자열인 lpWideCharStr의 길이입니다. 여러분은 대개 -1을 전달하게 될 것입니다. 이 경우 지정된 문자열이 NULL 문자로 끝난다는 것을 의미하고 API가 알아서 문자열의 길이를 측정합니다.
lpMultiByteStr
ANSI 문자열로 변환된 결과가 보관될 버퍼입니다.
cbMultiByte
lpMultiByteStr 버퍼의 크기입니다. 단위는 바이트입니다.
lpDefaultChar
이는 선택적으로 전달되는 파라미터입니다. dwFlags 매개변수가 WC_COMPOSITECHECK | WC_DEFAULTCHAR를 포함하고 있을 때, 특정 코드페이지에서 정의되지 않았던 문자는 이 매개변수에서 제시한 문자로 치환됩니다. 여러분이 이 매개변수에 NULL을 전달하면 운영체제에서 기본으로 정의된 문자로 치환될 것인데, 대체로 물음표(?) 문자가 나타날 것입니다.
lpUsedDefaultChar
이는 선택적으로 전달되는 파라미터입니다. BOOL 형 변수에 대한 포인터로서, ANSI 문자열로 변환할 때 호환되지 않는 문자가 있어서 앞서 설명한 lpDefaultChar가 사용되었다면 여기로 전달한 BOOL 형 변수의 값은 TRUE로 바뀔 것입니다. 이 정보가 필요없다면 NULL을 전달해도 됩니다.

매우 길고도 많은 매개변수 설명이었습니다. MSDN 문서에는 이보다 더 복잡한 설명들이 포함되어 있습니다. 이제 이 API의 사용 예를 보겠습니다.

// wszSomeString으로 유니코드 문자열을 이미 가지고 있다고 가정
wchar_t wszSomeString[MAX_PATH];
char szANSIString[MAX_PATH];

WideCharToMultiByte(CP_ACP, // 운영체제에 현재 설정된 ANSI 코드 페이지
    WC_COMPOSITECHECK, // 강세표시된 문자가 있는지 체크함
    wszSomeString, // 원본 유니코드 문자열
    -1, // wszSomeString이 NULL 문자로 끝나는 문자열임
    szANSIString, // char형 버퍼
    sizeof(szANSIString), // 버퍼의 크기(바이트)
    NULL, // 변환 실패 시 치환할 기본 문자 없음
    NULL); // 변환 실패한 문자 여부를 가져올 필요 없음

위와 같이 호출하면 szANSIString은 원본 유니코드 문자열에 대한 ANSI 버전의 사본을 갖게 됩니다.

 

wcstombs

CRT 함수인 wcstombs는 호출이 간단하지만 결국 WideCharToMultiByte를 호출하게 됩니다. 그러므로 결과는 동일합니다. wcstombs의 원형은 다음과 같습니다.

size_t wcstombs(char * mbstr, const wchar_t * wcstr, size_t count);

각 파라미터는 다음과 같습니다.

mbstr
ANSI 문자열을 갖게 되는 char 형 버퍼입니다.
wcstr
변환하고자 하는 원본 유니코드 문자열입니다.
count
mbstr 버퍼의 크기입니다. 단위는 바이트입니다.

wcstombs는 내부적으로 WideCharToMultiByte를 호출할 때 WC_COMPOSITECHECK | WC_SEPCHARS 옵션을 사용합니다. 앞의 예제를 다시 사용하면, 독자 여러분은 유니코드 문자열을 다음과 같이 변환할 수 있습니다.

wcstombs(szANSIString, wszSomeString, sizeof(szANSIString));

 

CString

MFC CString 클래스는 유니코드 문자열을 받아들일 수 있는 생성자와 할당 연산자를 포함하고 있습니다. 때문에 독자 여러분은 CString이 알아서 변환을 하도록 그냥 두면 됩니다. 예를 들면 다음과 같습니다.

CString str1(wscSomeString); // 생성자를 사용하여 변환
CString str2 = wszSomeString; // 대입 연산자를 사용하여 변환

 

ATL 매크로

ATL은 문자열 변환을 위한 유용한 매크로 집합을 포함하고 있습니다. 유니코드 문자열을 ANSI 문자열로 변환하기 위하여 W2A(wide to ANSI) 매크로를 사용합니다. 보다 더 정확하게 말하자면, 여러분은 OLE2A를 사용해야 합니다. 여기서 OLE는 COM이나 OLE에서 전달된 문자열을 의미합니다. 어쨌든, 이들 매크로를 사용하는 방법은 다음과 같습니다.

#include <atlconv.h>

// wszSomeString을 이미 가지고 있다고 가정
wchar_t wszSomeString[MAX_PATH];
char szANSIString[MAX_PATH];

USES_CONVERSION; // 매크로에서 사용되는 지역변수들을 선언
lstrcpy(szANSIString, OLE2A(wszSomeString));

OLE2A 매크로는 변환된 문자열에 대한 포인터를 반환합니다. 그런데 이 변환된 문자열은 임시변수인 스택 위치에 있는 버퍼에 보관되어 있습니다. 그러므로 우리는 lstrcpy를 사용하여 복사본을 만들어야 합니다. 또 다른 매크로로서 독자 여러분이 아셔야 할 것은 W2T(유니코드 문자열을 TCHAR 형 문자열로 변환) 및 W2CT(유니코드 문자열을 const TCHAR 형 문자열로 변환)가 있습니다.

위 예제코드에서 설명했던 OLE2A와 마찬가지로 OLE2CA(유니코드 문자열을 const char 형 문자열로 변환) 매크로도 있습니다. 위 예제코드의 경우 사실 OLE2CA를 사용하는 것이 더 알맞습니다. 왜냐하면 lstrcpy의 두 번째 파라미터가 const char * 형 문자열을 받기 때문입니다. 그러나 필자는 여러분에게 한번에 너무 과도한 정보를 전달하고 싶지는 않습니다.

 

유니코드로 고정하기

앞서 설명한 것과는 반대로, 여러분은 문자열에 대한 너무 복잡한 처리과정을 피하고자 COM으로부터 받은 문자열을 유니코드 문자열 그대로 보존할 수도 있습니다. (역자: 원 글이 작성된 시기가 2000년 ~ 2001년 사이임을 감안하여야 합니다. 이 당시 일반 사용자 운영체제는 ANSI 문자열이 기본인 Windows 9x가 주류였고, 원 글 작성자의 모국어가 영어인 만큼 1문자당 1바이트면 충분했습니다. 현재에는 윈도우 운영체제 자체가 유니코드에 기반한 다국어 환경을 기본으로 지원하기 때문에 TCHAR = WCHAR = wchar_t = 2 바이트가 시스템 기본 문자형입니다.) 특히 여러분이 콘솔 어플리케이션을 작성할 때 여러분은 std::wcout라는 전역 객체를 사용하여 유니코드 문자열을 출력할 수 있습니다. 사용 예는 다음과 같습니다.

std::wcout << wszSomeString;

std::wcout은 모든 문자열이 유니코드 문자열일 것이라 간주하고 있음을 숙지하시기 바랍니다. 여러분이 보통의 문자열(ANSI 문자열)을 출력하고자 할 때는 std::cout으로 출력합니다. 문자열 리터럴(문자열 상수)을 출력하고자 할 때 문자열 상수 앞에 접두어 L을 붙임으로써 유니코드 문자열임을 표시합니다. 예를 들어 다음과 같습니다.

std::wcout << L"The Oracle says…" << std::endl << wszOracleResponse;

독자 여러분이 유니코드 문자열을 그대로 쓰고자 할 때 몇 가지 제한 사항이 있습니다.

1. 유니코드에 대해 CRT 함수를 사용하고자 할 때 wcslen과 같이 유니코드 버전인 wcsXXX 함수를 사용해야 합니다.

2. 몇몇 예외적인 경우로서, 독자 여러분은 Windows 9x 환경에서 유니코드 문자열을 Windows API의 파라미터로 전달할 수 없습니다. 소스코드 변경 없이 9x와 NT 커널 모두에서 사용하고자 할 때, MSDN에 나온대로 TCHAR 문자형을 사용해야 합니다.

카테고리 “API/COM”
more...
COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (5)
COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? 본 게시물은 ‘codeproject.com’에 게시된 글 ‘Introduction to COM - What It Is and How to Use It.’을 번역한 것입니다. 원 게시물은 https://www.codeproject.com/Articles/633/Introduction-to-COM-What-It-Is-and-How-to-Use-It에 게재되어 있습니다. 최대한 원문에 적힌 의도를 반영하고자 하였으나, 우리말로 읽었을 때 보다 자연스럽게 하고자 부득이 어순과 어휘를 조정한 부분도 있음을 양해 바랍니다. 또한 본 게시물에서 언급하고 있는 예제 소스 코드는 Visual C++ 6.0을 기준으로 작성되어 있기 때문에 후속 버전의 V..
API/COM
2020. 10. 4. 13:16

COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (5)

API/COM
2020. 10. 4. 13:16

COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가?

본 게시물은 ‘codeproject.com’에 게시된 글 ‘Introduction to COM - What It Is and How to Use It.’을 번역한 것입니다.

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

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

  1. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (1)
  2. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (2)
  3. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (3)
  4. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (4)
  5. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (5)
  6. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (6)
  7. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (7)
  8. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (8) [完]

 

기본 인터페이스 – IUnknown

모든 COM 인터페이스는 IUnknown으로부터 파생됩니다. 이 인터페이스의 이름은 다소 오해의 소지가 있는데, 사실 이것은 ‘알 수 없는(unknown)’ 인터페이스라는 뜻이 아닙니다. 모든 COM 객체는 IUnknown을 구현하고 있기 때문에 여러분이 어떤 COM 객체에 대해 IUnknown 포인터를 가지고 있다면, 여러분은 그 포인터가 정확히 어떤 형식의 객체를 가리키고 있는지 알 수 없다는 것을 이 이름이 의미하고 있습니다.

IUnknown은 세 가지 메소드들을 갖습니다.

AddRef
COM 객체에게 레퍼런스 카운트를 증가할 것을 알려줍니다. 여러분이 인터페이스 포인터의 복사본을 만들고, 원래의 포인터와 복사된 포인터를 함께 사용하려 할 때 이 메소드를 호출할 수 있습니다. 그러나 본 글에서 우리는 AddRef를 굳이 사용할 필요는 없습니다.
Release
COM 객체에게 레퍼런스 카운트를 감소할 것을 알려줍니다. 앞서 언급한 Release 호출 예제를 참고하시기 바랍니다.
QueryInterface
COM 객체에게 인터페이스 포인터를 요청합니다. 여러분은 coclass가 하나 이상의 인터페이스를 구현하고 있을 때 이 메소드를 사용할 수 있습니다.

우리는 이미 예제 코드에서 Release의 사용을 보았습니다. 그런데 QueryInterface는 무엇일까요? 여러분이 CoCreateInstance를 사용하여 COM 객체를 만들 때, 여러분은 인터페이스 포인터를 얻게 됩니다. COM 객체가 IUnknown을 제외하고 하나 이상의 인터페이스를 구현하고 있을 때, 여러분은 QueryInterface를 사용하여 여러분이 필요로 하는 추가적인 인터페이스 포인터를 얻을 수 있습니다. QueryInterface의 원형은 다음과 같습니다.

HRESULT IUnknown::QueryInterface(
    REFIID iid,
    void ** ppv);

파라미터의 의미는 다음과 같습니다.

iid
여러분이 요청하게 될 인터페이스에 대한 IID입니다.
ppv
인터페이스 포인터에 대한 포인터입니다. COM 객체가 iid 파라미터로 지정한 인터페이스를 구현하고 있다면, 그에 대한 인터페이스 포인터가 이 매개변수를 통해 반환됩니다.
앞서 언급한 쉘 바로가기 예제를 봅시다. 쉘 바로가기를 만들 수 있는 coclass는 IShellLinkIPersistFile을 구현하고 있습니다. 여러분이 이미 해당 COM 객체에 대해 IShellLink형의 인터페이스 포인터를 가지고 있다면, 같은 COM 객체에게 IPersistFile형의 인터페이스 포인터도 얻을 수 있습니다. 그 방법은 다음과 같습니다.
HRESULT hr;
IShellLink * pISL;
IPersistFile * pIPF;

hr = pISL->QueryInterface(IID_IPersistFile, (void **)&pIPF);

그 다음 QueryInterface가 정상적으로 작동하였는지 확인하기 위하여 SUCCEEDED 또는 FAILED 매크로를 사용하여 hr의 값을 검사합니다. 다른 형식의 인터페이스 포인터를 얻는데 성공하였다면, 여러분은 이 새로운 인터페이스 포인터도 사용할 수 있습니다. 단, 다른 인터페이스처럼 사용이 끝났을 때 여러분은 필히 pIPF->Release()를 호출하여야 합니다.

카테고리 “API/COM”
more...
COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (4)
COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? 본 게시물은 ‘codeproject.com’에 게시된 글 ‘Introduction to COM - What It Is and How to Use It.’을 번역한 것입니다. 원 게시물은 https://www.codeproject.com/Articles/633/Introduction-to-COM-What-It-Is-and-How-to-Use-It에 게재되어 있습니다. 최대한 원문에 적힌 의도를 반영하고자 하였으나, 우리말로 읽었을 때 보다 자연스럽게 하고자 부득이 어순과 어휘를 조정한 부분도 있음을 양해 바랍니다. 또한 본 게시물에서 언급하고 있는 예제 소스 코드는 Visual C++ 6.0을 기준으로 작성되어 있기 때문에 후속 버전의 V..
API/COM
2020. 10. 4. 13:06

COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (4)

API/COM
2020. 10. 4. 13:06

COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가?

본 게시물은 ‘codeproject.com’에 게시된 글 ‘Introduction to COM - What It Is and How to Use It.’을 번역한 것입니다.

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

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

  1. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (1)
  2. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (2)
  3. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (3)
  4. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (4)
  5. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (5)
  6. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (6)
  7. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (7)
  8. COM의 소개(파트 1) – COM이 무엇이며, 어떻게 사용하는가? (8) [完]

 

COM 객체로 작업하기

모든 언어는 객체를 다루기 위한 고유한 방법을 가지고 있습니다. 예를 들어 C++에서 여러분은 객체를 스택에 정적할당할 수도 있고, new 키워드를 사용해 동적할당할 수도 있습니다. COM은 언어 중립적이기 때문에 COM 라이브러리는 자체적인 객체 관리 기능을 제공하고 있습니다. COM 객체와 C++ 객체는 다음과 같이 비교 가능합니다.

《객체를 새로 만들 때》

- C++에서는 new 연산자로 동적할당 하거나, 스택에 정적할당 합니다.

- COM에서는 COM 라이브러리에 있는 API를 호출합니다.

《객체를 삭제할 때》

- C++에서는 delete 연산자로 할당을 해제하거나, 스택에 정적할당된 객체에 대해서는 스코프(scope) 바깥으로 프로그램의 흐름을 이동합니다.

- COM에서 모든 객체는 자신의 레퍼런스 카운트를 가지고 있습니다. 호출자는 객체에게 언제 호출자가 실행되었는지를 반드시 알려주어야 합니다. COM 객체는 레퍼런스 카운트가 0이 되었을 때 스스로를 할당 해제합니다.

이제, 객체를 생성하고 파괴하는 두 단계 사이에 독자 여러분은 실질적으로 이 COM 객체를 사용할 수 있습니다. 독자 여러분이 COM 객체를 생성할 때, 여러분은 COM 라이브러리에게 어떤 인터페이스가 필요한지를 알려주게 됩니다. 또한 독자 여러분은 보통의 C++ 객체처럼 포인터를 사용하여 메소드들을 호출할 수 있습니다.

 

COM 객체를 생성하기

COM 객체를 생성하고 객체로부터 인터페이스를 얻기 위하여, 독자 여러분은 COM 라이브러리의 API인 CoCreateInstance를 호출하게 됩니다. CoCreateInstance의 원형은 다음과 같습니다.

HRESULT CoCreateInstance(
    REFCLSID rclsid,
    LPUNKNOWN pUnkOuter,
    DWORD dwClsContext,
    REFIID riid,
    LPVOID * ppv);

 

각 파라미터들의 의미는 다음과 같습니다.

rclsid
coclass의 CLSID입니다. 예를 들어 바로가기 아이콘을 만들 수 있는 COM 객체를 생성하기 위하여 여러분은 CLSID_ShellLink를 전달할 수 있습니다.
pUnkOuter
이 파라미터는 COM 객체들을 결합시킬 때 사용합니다. 여기서 결합은 이미 존재하는 coclass에 새로운 메소드들을 덧붙이는 작업을 의미합니다. 객체 결합을 하지 않을 것이므로 우리는 여기에 NULL을 전달할 것입니다.
dwClsContext
우리가 사용하고자 하는 COM 서버의 종류를 지정합니다. 본 글에서 우리는 가장 단순한 형태의 서버인 ‘인 프로세스 DLL(in-process DLL)’만을 사용할 것이기 때문에, 우리는 여기에 CLSCTX_INPROC_SERVER를 전달합니다. 한가지 주의할 것은, 독자 여러분은 ATL에서는 기본값으로 쓰이는 CLSCTX_ALL 속성을 사용해서는 안 된다는 것입니다. 왜냐하면 DCOM이 설치되지 않은 Windows 95 운영체제에서는 실패할 것이기 때문입니다.
riid
독자 여러분이 얻고자 하는 인터페이스의 IID입니다. 예를 들어, IShellLink 인터페이스의 포인터를 얻고자 할 때 독자 여러분은 IID_IShellLink를 전달할 수 있습니다.
ppv
인터페이스 포인터의 주소입니다. COM 라이브러리는 이 파라미터를 통해 요청한 인터페이스를 전달할 것입니다.

독자 여러분이 CoCreateInstance를 호출할 때, 이 함수는 레지스트리에서 CLSID를 탐색할 것입니다. 그리고 서버의 위치를 읽을 것이고 메모리로 서버를 적재하여 여러분이 요청한 coclass에 해당하는 인스턴스를 만들 것입니다.

아래 코드는 호출 예제입니다. CLSID_ShellLink에 해당하는 객체를 생성 및 초기화하고, 그 객체의 IShellLink 인터페이스 포인터를 요청하게 됩니다.

HRESULT hr;
IShellLink * pISL;

hr = CoCreateInstance(CLSID_ShellLink, // coclass의 CLSID
    NULL, // 통합과 관련된 것이므로 사용하지 않음
    CLSCTX_INPROC_SERVER, // 서버의 종류
    IID_IShellLink, // 인터페이스의 IID
    (void **)&pISL); // 우리가 얻고자 하는 인터페이스 포인터에 대한 포인터

if (SUCCEEDED(hr)) {
    // pISH을 사용한 메소드 호출
} else {
    // COM 객체를 생성할 수 없는 경우 hr은 오류 코드를 갖습니다
}

먼저 우리는 CoCreateInstance로부터 반환되는 값을 보관하기 위한 HRESULT를 선언하고, 또한 IShellLink 형식의 포인터도 선언하였습니다.

새로운 COM 객체를 생성하기 위해 CoCreateInstance를 호출합니다. hr이 성공을 나타내는 값을 갖고 있다면 SUCCEEDED 매크로는 TRUE 값을 반환할 것이고, 그렇지 않을 경우 SUCCEEDED 매크로는 FALSE 값을 반환할 것입니다. 코드가 실패했는지 여부를 판단하기 위하여 FAILED 매크로가 또한 제공됩니다.

 

COM 객체를 해제하기

앞서 설명한 바와 같이 독자 여러분은 COM 객체를 직접 해제할 수는 없습니다. 대신 여러분은 이 객체의 사용을 끝냈다고 알려주기만 합니다. 모든 COM 객체가 구현하고 있는 IUnknown 인터페이스는 Release 메소드를 가지고 있습니다. 독자 여러분은 COM 객체에 이 메소드를 호출함으로써 더 이상 사용하지 않음을 알려주면 됩니다. 독자 여러분이 일단 Release를 호출하였다면, COM 객체가 언제든지 메모리에서 사라질 수 있으므로 이후로 절대 인터페이스 포인터를 사용해서는 안됩니다.

만일 여러분의 어플리케이션이 여러가지 다양한 COM 객체를 사용하고 있다면, 인터페이스의 사용을 끝낼 때마다 Release를 호출하는 것이 매우 중요합니다. 여러분이 인터페이스의 사용을 해제하지 않는다면 COM 객체와 이들의 코드를 가지고 있는 DLL들도 메모리에 계속 남게 되어 여러분의 어플리케이션 실행에 불필요하게 붙어있게 됩니다. 여러분의 어플리케이션이 장시간 실행되고 있을 경우 여러분은 유휴시간동안 CoFreeUnusedLibraries를 실행할 것이 권장됩니다. 이 API는 더 이상 참조되지 않는 COM 서버를 메모리 적재 해제하여 여러분의 어플리케이션의 메모리 사용량을 줄여줍니다.

앞의 예제에 이어서 Release를 이렇게 사용하면 됩니다.

// 앞에서 적은 바와 같이 COM 객체를 생성한 후
if (SUCCEEDED(hr)) {
    // pISL을 사용하는 메소드 호출
    // COM 객체에게 우리가 사용을 끝냈음을 알림
    pISL->Release();
}

IUnknown 인터페이스에 대해서는 다음 절에서 충분히 설명하겠습니다.

카테고리 “API/COM”
more...

“API/COM” (33건)