Search
Duplicate
🧩

삽질 2 : COM의 위치투명성. Running Object Table

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

배경

삽질 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_); ...
C++
복사
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; } }
C++
복사
상당히 길다. 하지만 알고보면 단순해서 위 기본 시나리오에서 달라질 게 없다. 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(); ...
C++
복사
확연히 줄어든 코드. 게다가 서버측에서 만들었던 클래스 모니커 코드를 그대로 사용하기 때문에 의미론 상, 코드 가독성을 놓고 보아도 훨 뛰어나다. 아이, 좋아라~

위치 투명성 : location transparency

하지만 COM하면 위치 투명성, 즉 클라이언트에서는 서버가 in process에 있건, out-of process에 있건, remote에 있건 상관안하고 쓸수 있다는 게 자랑 아니던가. 코드가 짧아졌다지만 여전히 이 위치 투명성하고는 거리가 멀다.
그런 이유로 한번 더 웹을 뒤지다 눈에 걸린 함수, CoGetClassObject()CoCreateInstance()는 서버 인스턴스를 생성하기에 아니겠다 싶어 딴놈을 찾다 눈에 걸린 함수다. 슬쩍보니 CoCreateInstance()랑 매개변수 목록도 똑같네. 어디, 이놈으로 바꿔보자.

최종 버전/클라이언트 코드

... hr = ::CoGetClassObject(CLSID_Communicator, CLSCTX_LOCAL_SERVER, 0, IID_ICommunicator, (LPVOID*)&spCommunicator_); ...
C++
복사
이제는 달랑 한줄이다. 오, 행복해라. 근데 이 함수 어디서 많이 봤던거 같다. 맞다. out of process COM하면 항상 나오던 그 함수였다. 여기까지오니 이제 서버측 코드도 눈에 거슬린다. 또다시 웹을 뒤지니 항시 지나치던 CoRegisterClassObject()가 입질을 하는구만. 윽, 등록(Register)이란 말이 생략한 위치가 바로 ROT였구나. 이제 좀 감이 온다.

최종 버전/서버측 코드

... return CoRegisterClassObject(CLSID_Communicator, dynamic_cast<ICommunicator*>(this), CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &rotRegisterVal_); ...
C++
복사
당연히 서버 종료시에는 등록 해제를 해야하고, 이를 위해 CoRevokeClassObject()를 사용한다.
허탈하기도 하고, 뿌듯하기도 하고. 내부 동작을 확인했다는데 의미를 둘 수도 있겠지만, 그간 상당히 보아온 코드를 싸그리 잊고 먼길로 돌아서왔다는 것은 움...이건 어떻게 해석해야 할까. 음냐.