title: "삽질 2 : COM의 위치투명성. Running Object Table"
description: "COM의 위치투명성. Running Object Table. 동일한 내용의 코드를 세가지 버전으로 작성해내는 프로그래머의 모범적 자세... 라고 표현하면 나름 위로가 되기도 하겠지만, 최종 코드를 만들고 나니 이미 숱하게 보았던, 하지만 언제나 쉽사리 지나쳤던 바로 그 루틴이었다는데서 느끼는 허탈감"
cleanUrl: /sw-engineer/com-running-object-table
ogImage: ""
floatFirstTOC: right

동일한 내용의 코드를 세가지 버전으로 작성해내는 프로그래머의 모범적 자세... 라고 표현하면 나름 위로가 되기도 하겠지만, 최종 코드를 만들고 나니 이미 숱하게 보았던, 하지만 언제나 쉽사리 지나쳤던 바로 그 루틴이었다는데서 느끼는 허탈감.

배경

삽질 1 : 좆 Windows Messaging. COM에서의 프로세스간 동기/비동기 호출 의 그것과 동일하다. out of process에서 동작하는 COM Server와 통신하는 COM Client. 인스턴스로 올라간 COM Server에 client가 달라붙기 위해서 Running Object Table에 Server를 등록하고, client는 이 테이블에서 해당 Server를 찾아 연결하기까지의 내용. 알고보면 IPC(Inter Process Communication)를 이루는 가장 쉬운 방법(동기화 문제는 물론이요, 개체 기반 프로그래밍 패러다임까지 그대로 보존한 채 이를 이루기에)이 되겠다.

첫 번째 버전

위 시나리오를 그대로 따른 코드다. ROT와 이에 따른 Moniker에 대한 개념만 갖고 있다면 대강 reference 짜깁기해서 얻어낼 수 있는 코드이겠다.

서버측 코드

...
CComPtr<IMoniker> spMoniker;
HRESULT hr = ::CreateClassMoniker(CLSID_Communicator, &spMoniker);

CComPtr<IRunningObjectTable> spRot;
hr = GetRunningObjectTable(0, &spRot);
if(FAILED(hr)) { return hr; }

hr = spRot->Register(ROTFLAGS_REGISTRATIONKEEPSALIVE,
                     dynamic_cast<ICommunicator*>(this),
                     spMoniker,
                     &rotRegisterVal_);
...

CLSID_Communicator는 서버측 클래스 개체의 클래스 GUID이고 ICommunicator는 서버가 노출할 인터페이스이다. 클래스 모니커를 하나 만들어 서버 인스턴스(this: 서버 자신)를 ROT에 등록하는 모습이 되겠다.

클라이언트측 코드

CComPtr<ICommunicator>    spCommunicator_;
...
CComPtr<IRunningObjectTable> spTable;
CComPtr<IEnumMoniker>        spEnumMoniker;
CComPtr<IMoniker>            spMoniker;

HRESULT hr = E_FAIL;
if(GetRunningObjectTable(0, &spTable) != S_OK) { return hr; }

spTable->EnumRunning(&spEnumMoniker);
spEnumMoniker->Reset();

CComPtr<IUnknown>            spUnk;

while(spEnumMoniker->Next(1, &spMoniker, NULL) == S_OK)
{
    hr = spTable->GetObject(spMoniker, &spUnk);
    spMoniker.Release();

    if(FAILED(hr))
    {
        if(spUnk) { spUnk.Release(); }
        continue;
    }

    hr = spUnk.QueryInterface(&spCommunicator_);
    spUnk.Release();

    if(FAILED(hr))
    {
        if(spCommunicator_) { spCommunicator_.Release(); }
        continue;
    }
    else { break; }
}

상당히 길다. 하지만 알고보면 단순해서 위 기본 시나리오에서 달라질 게 없다. ROT에 등록된 개체를 하나씩 조사하여 ICommunicator를 구현한 개체를 찾는 것 뿐이다.

두 번째 버전/클라이언트 코드

헌데, '이야, 내 생각대로 구현한게 잘 돌아가네?' 하며 잘 쓰고 있다가 불연듯 코드가 너저분하다는 생각이 드는거다. 특히나 client쪽에서. 편리한거 좋아하는 MS에서 이런 흔한 시나리오를 위한 utility 함수를 안만들었을리 없지, 생각하며 웹을 뒤져보니 BindMoniker()란 함수가 눈에 띈다. 특정 모니커와 바인딩을 한다... 위 클라이언트 코드가 하는 일과 맞아떨어지는 듯한. 아니나 다를까, 다음과 같이 확 줄어든 코드가 가능해진다.

...
CComPtr<IMoniker> spMoniker;
HRESULT hr = ::CreateClassMoniker(CLSID_Communicator, &spMoniker);
if(FAILED(hr)) { return hr; }

CComPtr<IUnknown> spUnk;
hr = ::BindMoniker(spMoniker,
                   0,
                   IID_ICommunicator,
                   (LPVOID*)&spCommunicator_);
spMoniker.Release();
...

확연히 줄어든 코드. 게다가 서버측에서 만들었던 클래스 모니커 코드를 그대로 사용하기 때문에 의미론 상, 코드 가독성을 놓고 보아도 훨 뛰어나다. 아이, 좋아라~

위치 투명성 : location transparency

하지만 COM하면 위치 투명성, 즉 클라이언트에서는 서버가 in process에 있건, out-of process에 있건, remote에 있건 상관안하고 쓸수 있다는 게 자랑 아니던가. 코드가 짧아졌다지만 여전히 이 위치 투명성하고는 거리가 멀다.

그런 이유로 한번 더 웹을 뒤지다 눈에 걸린 함수, CoGetClassObject()CoCreateInstance()는 서버 인스턴스를 생성하기에 아니겠다 싶어 딴놈을 찾다 눈에 걸린 함수다. 슬쩍보니 CoCreateInstance()랑 매개변수 목록도 똑같네. 어디, 이놈으로 바꿔보자.