COM - 기본( 등록, 사용 )

2007/12/04 21:33

1) 만들어 놓은 클래스 라이브러리를 멤버변수로 선언하여 사용하게 되면 강한 결합을 하게 된다.
2) 그래서, 인터페이스를 사용하여 약한 결합을 만드는데 이게 바로 COM이 하는 역할이다.
3) 3개의 함수는 QueryInterface, AddRef, Release 를 반드시 구현해 줘야 한다.
4) 컴포넌트가 객체생성을 책임져야 하므로 반드시 IUnknown 를 최상위 부모로 하여 상속되어야 한다.
5) CreateInstance, DllGetClassObject 를 사용하여 객체를 생성해줘야 한다.
6) 이모든 과정을 단순화 한 것이 ATL, COM+ 이다...그래도 어렵다..ㅜ_ㅜ..

- regsvr32 DLL명
- Com.dll 을 레지스트리(HKEY_CLASSES_ROOT\CLSID) 에 등록해준다.

1. IFace.h ( 인터페이스ID, 클래스ID 를 등록한다 )
[ more.. | less.. ]

#ifndef _IFACE_H
#define _IFACE_H#undef _UNICODE
#undef UNICODE

#include <windows.h>

#ifndef interface
#define interface struct
#endif

// 모든 interface는 반드시 IUnknown 에서 상속되어야 한다.
// 모든 전화기의 interface
interface IPhone : public IUnknown
{
    virtual void Calling( char* number ) = 0;
};

// 모든 계산기의 interface
interface ICalc : public IUnknown
{
    virtual int Plus( int a, int b )  = 0;
    virtual int Minus( int a, int b ) = 0;
};

// 모든 interface의 고유한 번호를 부여 해야 한다.
// {10A4AA2B-EEBE-4491-B91E-F610AA3DB6C3}
static const IID IID_IPhone = // Interface ID
{ 0x10a4aa2b, 0xeebe, 0x4491, { 0xb9, 0x1e, 0xf6, 0x10, 0xaa, 0x3d, 0xb6, 0xc3 } };

// {76AAF296-23B4-43de-8107-7381DEADD769}
static const IID IID_ICalc =
{ 0x76aaf296, 0x23b4, 0x43de, { 0x81, 0x7, 0x73, 0x81, 0xde, 0xad, 0xd7, 0x69 } };

// Com 자체에도 ID를 부여한다.
// {026E86AC-95FA-4166-B77E-764BFAE33D4C}
static const CLSID CLSID_AnyCall =    // class ID
{ 0x26e86ac, 0x95fa, 0x4166, { 0xb7, 0x7e, 0x76, 0x4b, 0xfa, 0xe3, 0x3d, 0x4c } };

#endif    // _IFACE_H


 2. AnyCall.h ( 인터페이스를 제공한다. )
[ more.. | less.. ]
#ifndef _ANYCALL_H
#define _ANYCALL_H #include "IFACE.h"
#include <iostream>
using namespace std;

class AnyCall : public IPhone, public ICalc
{
    LONG m_ref;
public:
    AnyCall() : m_ref(0) { cout << "Created AnyCall" << endl; }
    ~AnyCall() { cout << "Destroyed AnyCall" << endl; }

    virtual void Calling( char* number );
    virtual int Plus( int a, int b );
    virtual int Minus( int a, int b );

    // 결국 모든 인터페이스는 IUnknown의 자식이므로
    // 모든 COM에는 아래 3개의 함수가 들어 있어야 한다.
    virtual ULONG __stdcall AddRef();
    virtual ULONG __stdcall Release();
    virtual HRESULT __stdcall QueryInterface( const IID& iid, void** p );
};

// 객체 생성을 클라이언트가 직접 할 경우에는 반드시 AnyCall.h 가 전달되어야 한다.
// 즉, COM 이 변경되면 Client 도 다시 빌드해야 한다. - COM이 스스로의 객체를 만들어
// 줄 수 있어야 한다.
// 내부적으로 이미 선언되어 있다.
/*
#ifdef DLLSOURCE
    #define DLLAPI _declspec(dllexport)
#else
    #define DLLAPI _declspec(dllimport)
#endif

extern "C" DLLAPI IPhone* CreateInstance();
*/

#endif // _ANYCALL_H


3. AnyCall.cpp ( IUnknown 의 3개의 가상함수를 구현하고 기능을 구현해준다. )
[ more.. | less.. ]

#define DLLSOURCE
#include "AnyCall.h"
#include "Registry.h"

void AnyCall::Calling( char* number )
{
    cout << "Calling with Anycall - " << number << endl;
}
int AnyCall::Plus( int a, int b )
{
    return a + b;
}
int AnyCall::Minus( int a, int b )
{
    return a - b;
}

ULONG __stdcall AnyCall::AddRef()
{
    return InterlockedIncrement( &m_ref );
}
ULONG __stdcall AnyCall::Release()
{
    if( InterlockedDecrement( &m_ref ) == 0 )
    {
        delete this;
        return 0;
    }

    return m_ref;
}

// 하나의 인터페이스를 사용 다른 인터페이스를 얻기 위해 호출하는 함수
HRESULT __stdcall AnyCall::QueryInterface( const IID& iid, void** ppv )
{
    if( iid == IID_IUnknown )    // operator==(IID&, IID&) 가 미리 구현되어 있다.
    {
        // IPhone으로 해줘도 된다. 멤버 data가 없다.
        *ppv = static_cast<IPhone*>(this);   
    }
    else if( iid == IID_IPhone )
    {
        *ppv = static_cast<IPhone*>(this);
    }
    else if( iid == IID_ICalc )
    {
        *ppv = static_cast<ICalc*>(this);
    }
    else
    {
        // 지원하지 않은 인터페이스를 요청 했다.
        *ppv = 0;
        return E_NOINTERFACE;
    }

    static_cast<IPhone*>(*ppv)->AddRef();

    return S_OK;
}
/* 무조건 IPhone 만 리턴하므로 원하는것을 리턴받아야 한다.
IPhone* CreateInstance()
{
    IPhone* p = new AnyCall;

    p->AddRef();

    return p;
}
*/



static HMODULE g_hModule = NULL ;
const int CLSID_STRING_SIZE = 39;

const char szFriendlyName[] = "AnyCall Example";
const char szVerIndProgID[] = "AnyCall.Control";
const char szProgID[] = "AnyCall.Control.1";

BOOL APIENTRY DllMain(HINSTANCE hModule, DWORD dwReason, LPVOID lpReserved)
{   
    switch(dwReason)   
    {       
    case DLL_PROCESS_ATTACH:
        g_hModule = hModule;
    break;   
    }

    return TRUE;
}

// CreateInstance를 대체할 함수..
// #define STDAPI extern "C" HRESULT __stdcall
STDAPI DllGetClassObject(const CLSID& clsid, const IID& iid, void** ppv)
{
    //if( clsid == CLSID_AnyCall ) 라면 객체생성을 한다.
    AnyCall* p = new AnyCall;

    if ( iid == IID_IUnknown )
    {
        *ppv = static_cast<IPhone*>(p);
    }
    else if ( iid == IID_IPhone )
    {
        *ppv = static_cast<IPhone*>(p);
    }
    else if ( iid == IID_ICalc )
    {
        *ppv = static_cast<ICalc*>(p);
    }
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }

    reinterpret_cast<IUnknown*>(*ppv)->AddRef();

    return S_OK;
}


STDAPI DllRegisterServer()
{
    // Get server location.
    char szCLSID[CLSID_STRING_SIZE];
    char szModule[512] ;
    char szKey[64] ;

    DWORD dwResult = ::GetModuleFileName(
                            g_hModule,
                            szModule,
                            sizeof(szModule)/sizeof(char)) ;

    if(dwResult == 0)
        return E_FAIL;


    // CLSID\{504966F2-40AB-4bff-916A-15E3E91424B3}키를 조합하기 위한 루틴
    ClsidToChar(CLSID_AnyCall, szCLSID, sizeof(szCLSID));
    strcpy(szKey, "CLSID") ;
    strcat(szKey, szCLSID) ;
 
    SetRegistryInform(szKey, NULL, szFriendlyName);
    SetRegistryInform(szKey, "InprocServer32", szModule);
    SetRegistryInform(szKey, "ProgID", szProgID);
    SetRegistryInform(szKey, "VersionIndependentProgID",szVerIndProgID) ;

    // HKEY_CLASSES_ROOT\LittleSum Example
    SetRegistryInform(szVerIndProgID, NULL, szFriendlyName) ;
    SetRegistryInform(szVerIndProgID, "CLSID", szCLSID) ;
    SetRegistryInform(szVerIndProgID, "CurVer", szProgID) ;

    // HKEY_CLASSES_ROOT\LittleSum.Control.1
    SetRegistryInform(szProgID, NULL, szFriendlyName) ;
    SetRegistryInform(szProgID, "CLSID\\", szCLSID) ;

    return S_OK ;
}

STDAPI DllUnregisterServer()
{
    // Build the key CLSID\\{...}
    char szCLSID[CLSID_STRING_SIZE];
    char szKey[64] ;

    ClsidToChar(CLSID_AnyCall, szCLSID, sizeof(szCLSID));
    strcpy(szKey, "CLSID\\") ;
    strcat(szKey, szCLSID) ;

    // CLSID Key - CLSID\{...} 삭제.
    LONG lResult = DeleteRegistryKey(HKEY_CLASSES_ROOT, szKey) ;

    // version-independent ProgID Key 삭제.
    lResult = DeleteRegistryKey(HKEY_CLASSES_ROOT, szVerIndProgID) ;

    // ProgID key 삭제.
    lResult = DeleteRegistryKey(HKEY_CLASSES_ROOT, szProgID) ;

    return S_OK ;
}


4. Registry.h( 레지스트리 함수를 제공한다. )
[ more.. | less.. ]
#ifndef __Registry_H__
#define __Registry_H__ void ClsidToChar(const CLSID& clsid, char* szCLSID, int length);
BOOL SetRegistryInform(const char* szKey, const char* szSubkey, const char* szValue);
DWORD DeleteRegistryKey(HKEY hStartKey , const char* pKeyName );

#endif


5. Registry.cpp( 레지스트리 함수를 제공한다. )
[ more.. | less.. ]
#undef UNICODE
#undef _UNICODE
#include <windows.h>
#include "registry.h"

// CLSID 를 문자열로 변경해 준다.
void ClsidToChar(const CLSID& clsid, char* szCLSID, int length)
{
    LPOLESTR wszCLSID = NULL ;
    HRESULT hr = StringFromCLSID(clsid, &wszCLSID) ;

    wcstombs(szCLSID, wszCLSID, length) ;

    CoTaskMemFree(wszCLSID) ;
}

BOOL SetRegistryInform(const char* szKey, const char* szSubkey, const char* szValue)
{
    HKEY hKey;
    char szBuf[1024] ;

    // 레지스터리의 키 와 서버 키의 값을 szBuf에 기록한다.
    strcpy(szBuf, szKey) ;

    if (szSubkey != NULL)
    {
        strcat(szBuf, "") ;
        strcat(szBuf, szSubkey ) ;
    }

    // szBuf에 기록된 키 값의 정보를 가지고 키를 생성한다.
    long lResult = RegCreateKeyEx(HKEY_CLASSES_ROOT,
                                szBuf,
                                0,
                                NULL,
                                REG_OPTION_NON_VOLATILE,
                                KEY_ALL_ACCESS, NULL,
                                &hKey,
                                NULL) ;

    if (lResult != ERROR_SUCCESS)
    {
        return FALSE ;
    }
   
    RegSetValueEx(hKey,
                NULL,
                0,
                REG_SZ,
                (BYTE *)szValue,
                strlen(szValue)+1) ;

    RegCloseKey(hKey) ;

    return TRUE ;
}

DWORD DeleteRegistryKey(HKEY hStartKey , const char* pKeyName )
{
      DWORD   dwRtn, dwSubKeyLength;
      LPTSTR  pSubKey = NULL;
      TCHAR   szSubKey[256];
      HKEY    hKey;

      // NULL 값 혹은 pKeyName에 정보가 없다면 리턴한다.
      if ( pKeyName &&  lstrlen(pKeyName))
      {
            if( (dwRtn=RegOpenKeyEx(hStartKey,pKeyName,
                0, KEY_ENUMERATE_SUB_KEYS | DELETE, &hKey )) == ERROR_SUCCESS)
            {
                while (dwRtn == ERROR_SUCCESS )
                {
                    dwSubKeyLength = 256;
                    dwRtn=RegEnumKeyEx(
                              hKey,
                              0,       // always index zero
                              szSubKey,
                              &dwSubKeyLength,
                              NULL,
                              NULL,
                              NULL,
                              NULL
                            );

                    if(dwRtn == ERROR_NO_MORE_ITEMS)
                    {
                        dwRtn = RegDeleteKey(hStartKey, pKeyName);
                        break;
                    }
                    else if(dwRtn == ERROR_SUCCESS)
                    dwRtn=DeleteRegistryKey(hKey, szSubKey);
                }
               
                RegCloseKey(hKey);
            }
        }
        else
            dwRtn = ERROR_BADKEY;

        return dwRtn;
}


6. Anycall.def ( dllexport 역할을 해준다. )
[ more.. | less.. ]
LIBRARY Com.dll
EXPORTS
                DllGetClassObject   PRIVATE
                DllRegisterServer   PRIVATE
                DllUnregisterServer PRIVATE

1. UseCom ( Com 을 사용하는 방법 - dll로드가 아닌 레지스트리에 등록된 COM을 사용한다. )
[ more.. | less.. ]

#undef _UNICODE
#undef UNICODE

#include <iostream>
#include <windows.h>
#include "../Com/IFace.h" // 인터페이스를 제공하는 헤더파일.
using namespace std;

typedef IPhone*(*CREATEINSTANCE)();

int main()
{
    CoInitialize(0); // COM 라이브러리 초기화

    IPhone* phone = 0;

    // 레지스트리에 가서 CLSID를 찾은 후에 해당 DLL을 load하고
    // DllGetClassObject 함수를 찾은 후에 호출해서 객체를 만든다.
    HRESULT ret = CoGetClassObject( CLSID_AnyCall, // COM의 CLSID
                                    CLSCTX_INPROC_SERVER, // DLL에 있다.
                                    NULL,            // 통합(NULL 사용안함)
                                    IID_IPhone,        // 원하는 인터페이스 ID
                                    (void**)&phone);    // 인터페이스를 담을 포인터.

    if ( FAILED(ret ) )
    {
        cout << "Error" << endl;
        return 0;
    }

    phone->Calling("010-111-2222");

    ICalc* pCalc = 0;
    ret = phone->QueryInterface( IID_ICalc, (void**)&pCalc);

    cout << pCalc->Plus(1,2) << endl;
   
    pCalc->Release();
    phone->Release();

    CoUninitialize();    // 초기화 해제.
}