나의 COM(Component Object Model) 경험담 #5 드디어다섯번째까지왔습니다. 얻으신것이있었나요? 없었다구요? ㅜㅜ ;; 어쨌든그건상관없습니다. 이번은내용이좀깁니다. 그렇다고할게많은것은아닙니다. 자세한건나중에뒤에보시면아실테고. 생각보다많은분들이좋아해주셨습니다. 기분이좋았습니다. 잠시개인적인얘기를하려고합니다. 저의일과는이렇습니다. 회사퇴근해서밥먹고 인어아가씨 본다음 ( 이거정말재미있습니다. 요즘은이거보는낙으로살죠 ^^) 바로 9시뉴스보면서세상이어떻게돌아가나봅니다. 그리고요며칠 (4일동안인가봅니다.) 이글쓰느라보통 3 ~ 4시간보내고새벽 1시나 3시쯤에글데브피아에올린다음잡니다. 그래서비몽사몽간에글을씁니다. 따라서주절주절앞뒤안맞는얘기도많았을겁니다. 하루를쉬니머리가조금은맑아졌습니다. 그래도앞뒤가잘안맞을겁니다.( 원래이렇습니다. 비몽사몽어쩌고는다핑계입니다. ^^) 오늘은시간이꽤걸릴것같습니다. 잠못잘지도모릅니다. 회사에서요즘밤에뭐하느냐고의심의눈초리로째려봅니다. [ ㅡㅡ ] 잡담그만하고빨리하라구요? 넵! 알겠습니다. 그럼시작하겠습니다. 오늘설명하지않겠다. 설명은 #6 에서본격적으로하겠다. 오늘은그냥흐름을파악하면된다. 그냥이렇게만들면되는구나라고생각만하면될듯 싶다. ( 중간에설명이없더라도섭섭하게생각하지마라.)
COM 컴포넌트를만드는방법은다양하다. 사람이라는것이간사해서한번맛들인방법을끝까지고집하게된다. 그래서 ATL을사용해본사람은절대다른방법으로하려고하지않는다. 하지만, ATL은 COM을완벽하게지원하지않는다는걸알아야한다. 그렇다고 ATL에서지원하는것이상만들자신도없지만말이다. 그래도기분나쁘잖아 ~~. 있어보이는척하는거빼면시체인난데.. 그럴순없쥐 ~. 오늘여러분은개념이해하느라머리쥐어뜯을일이없다. 그냥아무생각없이따라하기만하면된다. 그럼 COM 서버한번만들어보자. ( 서버란말이나오니, 왠지서버프로그래머가된것같다. ^^) 먼저오늘만들놈에대해서미리어떤일을하는놈인지알고들어가면쉬울것이다. 이번에만들 COM 컴포넌트가하는일은두숫자를입력받아서더한결과를돌려준다. 간단하다. 더이상말이필요없다. 대부분의 COM 예제들이이런걸로알고있다. 그리고먼저밝혀둘것이있다. 이소스는 codeguru 사이트의 COM 란에서가져온소스를내나름대로조금편집한소스이다. 모든기능을죽이고우리나라정서 (?) 에맞게조금수정했다. ( 아무래도내가만들면사람들이믿지않을것같다.) 그럼시작해보자. 먼저새프로젝트를열자. 아주친숙한화면이나타났다. COM 을만든다고 ATL COM AppWizard 를선택하면안된다. Win32 DLL 을선택하고프로젝트명을넣자. 다끝났다면 OK 버튼을누른다.
An empty DLL project를선택하고 Finish 버튼을누른다. 자기본적인준비는끝났다. 그리고이제.idl 파일이필요하다. 인터페이스를정의하는파일이다. 그러려면 GUID도하나필요하다. GUID를쉽게만드는방법이있다. 실행에서다음과같이명령어를입력해보자.
그럼다음과같은창이하나뜬다. 말그대로 GUID 를랜덤하게계속만들수있다. 여기서하나를만들어서 Copy 버튼을누른다. 그리고소스에서 GUID 가필요한부분에서 Paste 하면나타난다. 새파일을하나열고다음과같이타이핑한다. 아니다. Copy & Paste 하면되겠다. import "unknwn.idl"; [ uuid(12d90058 C2A5 4950 8355 1DC0189FFD0D), helpstring(" 여기에필요한설명을적는다.") ] interface IAdd : IUnknown HRESULT SetFirstNum(long nfirst); HRESULT SetSecondNum(long nsecond); HRESULT GetSum([out, retval] long *pbuffer);
[ uuid(9d807a19 9A1A 4879 A7C0 6D3AFD04F7B8), helpstring(" 라이브러리에대한설명을적는다.") ] library AddComObjLib importlib("stdole32.tlb"); importlib("stdole2.tlb"); interface IAdd; 여기서잠시 IDL이뭔지알아보자. IDL(Interface Definition Language) 해석하면인터페이스정의언어란말이다. 일반적으로인터페이스는 C++ 언어로표현이가능했다. 즉, 순수가상함수로만들수도있다. 하지만, IDL 로만드는것이여러모로노가다작업을줄일수있다. 그리고그렇게어렵지도않다. 이언어는 MIDL(Microsoft Interface Definition Language) 컴파일러로컴파일할수있다. 도스창에서 midl IAdd.idl 이렇게실행하면컴파일된다. 여기서여러가지부산물들을얻을수있다. 우리가사용할수있는헤더파일도만들어준다. 그리고가장중요한것은나중에설명하겠지만, Proxy 와 Stub 코드를만들어준다는것이다. 그리고또한가지타입라이브러리를만들어준다는것이다. 대충이렇게알고넘어가자. 이부분도하루종일해야하는부분들이다. 이쪽을파다가는오늘실습은그냥포기해야한다.
그럼다시노가다작업으로들어가자. IAdd.idl 로파일이름을저장하고 Project 메뉴를사용해 서프로젝트에추가한다. 그럼다음과같이나타날것이다. Project 메뉴에서 Settings.. 메뉴를클릭한다. 다시거기서 Iadd.idl 파일을선택한다. 컴파일을먼저해보기위해서다.
Always use custom build step 를체크하고다음텝을선택한다. 위의그림과같이명령어를입력한다. 이과정이귀찮으면도스창에서직접 midl IAdd.idl 을직접입력해도상관없다. 결과는같은테니깐말이다.
그럼다음과같이같은폴더에 5 개의파일이덤으로생성된다. 그리고추가로 COM 컴포넌트를다만들고나서레지스트리에추가하는것도귀찮으니그것도설정을미리해버리자. 아래그림과같이프로젝트를선택하고마지막텝의 Post build step를선택하고명령어를입력한다. 그러면컴파일이끝나면알아서이명령어를실행시킨다.
그다음부터는아래의파일들을전부위의주석에나온파일이름대로다저장하자. 그리고다끝났다면 *.cpp 와 *.def 파일을프로젝트에추가한다. 자, 그럼지금부터노가다를좀해라. 나는노가다하는동안좀쉬어야겠다. 커피도고프고담배도고프다. 룰루랄라 ~ ~ ~ ~ ~ ~ ~
//////////////////////////////////////////////////////////////////// // AddComObj.h 파일 //////////////////////////////////////////////////////////////////// #include "IAdd.h" extern long g_ncomobjsinuse; class CAddComObj : public IAdd private: long m_nfirst, m_nsecond; //operands for addition long m_nrefcount; //for managing the reference count public: //IUnknown 인터페이스의메서드를구현한다. HRESULT stdcall QueryInterface(REFIID riid, void **ppobj); ULONG stdcall AddRef(); ULONG stdcall Release(); //IAdd 인터페이스의메서드들... HRESULT stdcall SetFirstNum( long nfirst); HRESULT stdcall SetSecondNum( long nsecond); HRESULT stdcall GetSum( long *pbuffer); CAddComObj() m_nrefcount=0; InterlockedIncrement(&g_nComObjsInUse); ; ~CAddComObj() InterlockedDecrement(&g_nComObjsInUse);
//////////////////////////////////////////////////////////////////// // AddComObj.cpp 파일 //////////////////////////////////////////////////////////////////// #include <objbase.h> #include "AddComObj.h" #include "IAdd_i.c" ///////////////////////////////////////////////////////////////////// // IAdd 인터페이스의메서드들을구현한다. // // SetFirstNum : 더할숫자들중첫번째수를지정한다. // SetSecondNum : 더할숫자들중두번째수를지정한다. // GetSum : 두숫자의합을얻어온다. ///////////////////////////////////////////////////////////////////// HRESULT stdcall CAddComObj::SetFirstNum(long nfirst) m_nfirst = nfirst; return S_OK; HRESULT stdcall CAddComObj::SetSecondNum(long nsecond) m_nsecond = nsecond; return S_OK; HRESULT stdcall CAddComObj::GetSum(long *pbuffer) *pbuffer = m_nfirst + m_nsecond; return S_OK; ///////////////////////////////////////////////////////////////////// // IUnknown 인터페이스의메서드들을구현한다. // 다음의 3개메서드가기본이쥐 ~~~~~ // AddRef()
// Release() // QueryInterface(REFIID riid, void **ppobj) ///////////////////////////////////////////////////////////////////// ULONG stdcall CAddComObj::AddRef() return InterlockedIncrement(&m_nRefCount); ULONG stdcall CAddComObj::Release() long nrefcount = 0; nrefcount = InterlockedDecrement(&m_nRefCount); // 참조카운트가없으면스스로해제한다. if (nrefcount == 0) delete this; return nrefcount; HRESULT stdcall CAddComObj::QueryInterface(REFIID riid, void **ppobj) if (riid == IID_IUnknown) *ppobj = static_cast<iadd*>(this); AddRef(); return S_OK; if (riid == IID_IAdd) *ppobj = static_cast<iadd*>(this); AddRef(); return S_OK;
*ppobj = NULL; return E_NOINTERFACE;
//////////////////////////////////////////////////////////////////// // AddComObjFactory.h 파일 //////////////////////////////////////////////////////////////////// extern long g_ncomobjsinuse; class CAddComObjFactory : public IClassFactory private: long m_nrefcount; public: CAddComObjFactory() m_nrefcount=0; InterlockedIncrement(&g_nComObjsInUse); ~CAddComObjFactory() InterlockedDecrement(&g_nComObjsInUse); // IUnknown 인터페이스의메서드들... HRESULT stdcall QueryInterface(REFIID riid, void **ppobj); ULONG stdcall AddRef(); ULONG stdcall Release(); ; // IClassFactory 인터페이스의메서드들... virtual HRESULT stdcall CreateInstance(IUnknown* punknownouter, const IID& iid, void** ppv) ; virtual HRESULT stdcall LockServer(BOOL block) ;
//////////////////////////////////////////////////////////////////// // AddComObjFactory.cpp 파일 //////////////////////////////////////////////////////////////////// #include <objbase.h> #include "AddComObjFactory.h" #include "AddComObj.h" ///////////////////////////////////////////////////////////////////// // IUnknown 인터페이스의메서드들을구현한다. // // AddRef // Release // QueryInterface ///////////////////////////////////////////////////////////////////// ULONG stdcall CAddComObjFactory::AddRef() return InterlockedIncrement(&m_nRefCount) ; ULONG stdcall CAddComObjFactory::Release() long nrefcount = 0; nrefcount = InterlockedDecrement(&m_nRefCount); if (nrefcount == 0) delete this; return nrefcount; HRESULT stdcall CAddComObjFactory::QueryInterface(const IID& iid, void** ppv) if ((iid == IID_IUnknown) (iid == IID_IClassFactory)) *ppv = static_cast<iclassfactory*>(this); else *ppv = NULL;
return E_NOINTERFACE; reinterpret_cast<iunknown*>(*ppv) >AddRef(); return S_OK; ///////////////////////////////////////////////////////////////////// // IClassFactory 인터페이스의메서드들을구현한다. // // LockServer // CreateInstance ///////////////////////////////////////////////////////////////////// HRESULT stdcall CAddComObjFactory::CreateInstance(IUnknown* punknownouter, const IID& iid, void** ppv) // Aggregation을사용하지않는다. if (punknownouter!= NULL) return CLASS_E_NOAGGREGATION; CAddComObj* pobject = new CAddComObj; if (pobject == NULL) return E_OUTOFMEMORY; return pobject >QueryInterface(iid, ppv); HRESULT stdcall CAddComObjFactory::LockServer(BOOL block) return E_NOTIMPL;
//////////////////////////////////////////////////////////////////// // AddComObjGuid.h 파일 //////////////////////////////////////////////////////////////////// #ifndef AddObjGuid_h #define AddObjGuid_h // // 4EE0DC95 64F3 4ad6 A1FC 191A9FAEA849 static const GUID CLSID_AddObject = 0x4ee0dc95, 0x64f3, 0x4ad6, 0xa1, 0xfc, 0x19, 0x1a, 0x9f, 0xae, 0xa8, 0x49 ; #endif
//////////////////////////////////////////////////////////////////// // Exports.cpp 파일 //////////////////////////////////////////////////////////////////// #include <objbase.h> #include "AddComObj.h" #include "AddComObjFactory.h" #include "AddComObjGuid.h" HMODULE g_hmodule = NULL; long g_ncomobjsinuse = 0; /////////////////////////////////////////////////////////////////////////////// // 여기가 DllMain 이다. // DLL_PROCESS_ATTACH : DLL이프로세스의주소영역에맵핑된다. /////////////////////////////////////////////////////////////////////////////// BOOL APIENTRY DllMain(HANDLE hmodule, DWORD dwreason, void* lpreserved) if (dwreason == DLL_PROCESS_ATTACH) g_hmodule = (HMODULE)hModule ; return TRUE ; /////////////////////////////////////////////////////////////////////////////// // COM 라이브러리의 CoGetClassObject 함수에서 DLL의 DllGetClassObject함수를호출하고, // 이함수에서실제로클래스팩토리 COM 개체를생성하게된다. /////////////////////////////////////////////////////////////////////////////// STDAPI DllGetClassObject(const CLSID& clsid, const IID& iid, void** ppv) if (clsid == CLSID_AddObject) // CAddComObjFactory 를생성한다. CAddComObjFactory *paddfact = new CAddComObjFactory;
if (paddfact == NULL) return E_OUTOFMEMORY; else return paddfact >QueryInterface(iid, ppv); return CLASS_E_CLASSNOTAVAILABLE; /////////////////////////////////////////////////////////////////////////////// // COM 라이브러리의 CoFreeUnusedLibraries 함수는 DLL의 DllCanUnloadNow 함수를호출한다. // 즉, DLL을해제시켜도좋은지물어본다. /////////////////////////////////////////////////////////////////////////////// STDAPI DllCanUnloadNow() if (g_ncomobjsinuse == 0) return S_OK; else return S_FALSE; ;Experts.def 파일이다.
DESCRIPTION "Simple COM object" EXPORTS DllGetClassObject DllCanUnloadNow DllRegisterServer DllUnregisterServer PRIVATE PRIVATE PRIVATE PRIVATE ////////////////////////////////////////////////////////////////////
// Registry.h 파일 //////////////////////////////////////////////////////////////////// #ifndef Registry_H #define Registry_H HRESULT RegisterServer(HMODULE hmodule, const CLSID& clsid, const char* szfriendlyname, const char* szverindprogid, const char* szprogid) ; HRESULT UnregisterServer(const CLSID& clsid, const char* szverindprogid, const char* szprogid) ; #endif ////////////////////////////////////////////////////////////////////
// Registry.cpp 파일 //////////////////////////////////////////////////////////////////// #include <windows.h> #include <objbase.h> #include "AddComObjGuid.h" #define AddObjProgId "IcoddyLib.Sum" extern HMODULE g_hmodule; BOOL HelperWriteKey(HKEY roothk, const char *lpsubkey, LPCTSTR val_name, DWORD dwtype, void *lpvdata, DWORD dwdatasize) HKEY hk; if (ERROR_SUCCESS!= RegCreateKey(roothk,lpSubKey,&hk) ) return FALSE; if (ERROR_SUCCESS!= RegSetValueEx(hk,val_name,0,dwType,(CONST BYTE *)lpvdata,dwdatasize)) return FALSE; if (ERROR_SUCCESS!= RegCloseKey(hk)) return FALSE; return TRUE; // COM 개체를시스템레지스트리에등록할때 Regsvr32.exe 에의해호출된다. HRESULT stdcall DllRegisterServer(void) WCHAR *lpwszclsid; char szbuff[max_path]=""; char szclsid[max_path]="", szinproc[max_path]="",szprogid[max_path]; char szdescriptionval[256]=""; StringFromCLSID(CLSID_AddObject, &lpwszclsid);
wsprintf(szclsid,"%s",lpwszclsid); wsprintf(szinproc,"%s\\%s\\%s","clsid",szclsid,"inprocserver32"); wsprintf(szprogid,"%s\\%s\\%s","clsid",szclsid,"progid"); wsprintf(szbuff,"%s","icoddy's sum"); wsprintf(szdescriptionval,"%s\\%s","clsid",szclsid); HelperWriteKey (HKEY_CLASSES_ROOT, szdescriptionval, NULL, REG_SZ, (void*)szbuff, lstrlen(szbuff)); GetModuleFileName(g_hModule, szbuff, sizeof(szbuff)); HelperWriteKey (HKEY_CLASSES_ROOT, szinproc, NULL, REG_SZ, (void*)szbuff, lstrlen(szbuff)); lstrcpy(szbuff,addobjprogid); HelperWriteKey (HKEY_CLASSES_ROOT, szprogid, NULL, REG_SZ, (void*)szbuff, lstrlen(szbuff)); wsprintf(szbuff,"%s","icoddy's sum"); HelperWriteKey (HKEY_CLASSES_ROOT, AddObjProgId, NULL, REG_SZ, (void*)szbuff, lstrlen(szbuff)); wsprintf(szprogid,"%s\\%s",addobjprogid,"clsid"); HelperWriteKey (HKEY_CLASSES_ROOT, szprogid, NULL, REG_SZ, (void*)szclsid, lstrlen(szclsid)); return 1; // COM 개체를시스템레지스트리에등록할때 Regsvr32.exe 에의해호출된다. HRESULT stdcall DllUnregisterServer(void)
char szkeyname[256]="",szclsid[256]=""; WCHAR *lpwszclsid; wsprintf(szkeyname,"%s\\%s",addobjprogid,"clsid"); RegDeleteKey(HKEY_CLASSES_ROOT,szKeyName); RegDeleteKey(HKEY_CLASSES_ROOT,AddObjProgId); StringFromCLSID(CLSID_AddObject, &lpwszclsid); wsprintf(szclsid,"%s",lpwszclsid); wsprintf(szkeyname,"%s\\%s\\%s","clsid",szclsid,"inprocserver32"); RegDeleteKey(HKEY_CLASSES_ROOT,szKeyName); wsprintf(szkeyname,"%s\\%s\\%s","clsid",szclsid,"progid"); RegDeleteKey(HKEY_CLASSES_ROOT,szKeyName); wsprintf(szkeyname,"%s\\%s","clsid",szclsid); RegDeleteKey(HKEY_CLASSES_ROOT,szKeyName); return 1; 여기가끝이다. 마지막으로컴파일을해보자. 이렇게나오면성공한거다. 진심으로축하한다. 드뎌 COM 컴포넌트를만든것이다. 그럼이제클라이언트프로그램을만들어서실제로잘동작하는지확인해야한다. 내가
이수고를덜겠다. 해보니깐잘된다. 밑에그림보이쥐? 그럼됐다. 저거조작한거아냐? 라 고의심하는사람은잘봐라. 결과가읽기속성으로되어있다. 그래도조작한거라고? 우쒸 ~ 그래조작했다. 어쩔건데? 여긴할게별로없으니소스나한번훑어보고지나가라. 결과보기버튼을누르면결과가나온다. void CAddComClientDlg::OnButtonSum() // TODO: Add your control notification handler code here UpdateData(TRUE); HRESULT hr; hr = CoInitialize(NULL); if(failed(hr)) AfxMessageBox("COM 라이브러리를초기화하지못했습니다."); SendMessage(WM_CLOSE); IcoddyLib::IAddPtr picoddylib; picoddylib.createinstance("icoddylib.sum"); picoddylib >SetFirstNum(m_nFirst); picoddylib >SetSecondNum(m_nSecond); m_nsum = picoddylib >GetSum(); UpdateData(FALSE); 오늘은따로설명을하지않겠다. 오늘이것을나에게요구한다면나그만둘거다. 협박하
는거다. 절대강요하지마라. 기분나쁘면언제든지그만둔다. 내맘이다.( 헛 ~~ 또돌날라 오넹 ) 자, 여러분들너무지쳤을것이다. 설명은다음경험담 #6 에서본격적으로하겠다. 열심히깊이팔준비를하고오면고맙겠다. 오늘의핵심은이과정을기억하라는것이다. 소스는나중일이다. 이과정만알면다음에 언제든지소스참조하면서만들면된다. 헐 ~~~ 날다샜다. 대충예상은했었지만, 정말이렇게될줄이야. TV 좀보다가씻고출근해야한다. 밥은뭐해먹지? 음.. 즉석미역국이있었네. 그거해먹어야겠다. 800원인데두개들어있다. 즉, 한개 400원꼴이니, 정말싸고맛있게먹는거다. 여러분도라면으로끼니떼우지말고이런거사먹어라. 이건 3분도안걸린다. 몸에도좋다. 아침먹는사람이그렇지않은사람보다수명이 10년이더길다고한다. 진짜다. 믿어라.( 사실은, 나도귀찮아서일주일에한번도아침못먹는다.) 그럼날밤새도끄떡없는건강한 (?) 프로그래머세계를꿈꾸며이만끝내야겠다. e mail : icoddy@hotmail.com msn id : icoddy@hotmail.com 박성규