COM

C++ 2011. 1. 24. 23:53

ATL ActiveX 만들기 - Part1. 프로젝트 구성 : http://blog.greenmaru.com/entry/greenmarux-activex

ATL ActiveX 만들기 - Part2. 컨트롤 구현 : http://blog.greenmaru.com/entry/greenmarux-activex-p2

ATL ActiveX 만들기 - Part3. 이벤트(Connection point) 구현 : http://blog.greenmaru.com/entry/greenmarux-activex-p3

ATL ActiveX 만들기 - Part4. 관리자권한 UAC Elevation : http://blog.greenmaru.com/entry/greenmarux-activex-p4

 

ActiveX/COM 강좌(카페 가입필요) : http://cafe.naver.com/gisdev.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=936

 

Component Development with Visual C++ & ATL

    : http://www.synch3d.com/wiki/moin/moin.cgi/_c0_fc_ba_b4_bc_b1_c0_c7_20Component_20Development_20with_20Visual_20C_2b_2b_20with_20ATL

DCOM 샘플

    : http://msdn.microsoft.com/ko-kr/library/3ts2dxc8.aspx

Connecting Through Windows Firewall

    :http://msdn.microsoft.com/en-us/library/Aa389286

 

C++에서의 VARIANT 사용법

    :http://www.serious-code.net/moin.cgi/UsingVariant

 

    

COM은 내부적으로 유니코드를 사용한다.

 

 

 

COM 객체 (COM 개체 라도고 불림)

    : IUnknown이나 IUnknown을 상속한 인터페이스를 구현한 클래스의 인스턴스    

COM 컴포넌트 (COM 서버, COM 객체 서버, COM 구성요소 라고도 불림)

    : COM 객체(IUnknown의 구현 클래스)가 들어있는 파일(DLL 또는 EXE)의 실행 인스턴스

COM 컴포넌트 종류 (실행 위치에 따른 분류)

    in-process server : 클라이언트와 같은 프로세스 안에서 생성되고 실행되는 서버 (DLL 파일)

    out-process server : 클라이언트와 별개의 프로세스가 생성되어 실행되는 서버 (EXE 파일)

        local server        : 서버가 클라이언트와 같은 시스템에서 실행되는 경우

        remote server(DCOM)    : 서버가 클라이언트와 다른 시스템에서 실행되는 경우. Distributed COM

 

    **.remote server를 대리 프로세스(surrogate process, dllhost.exe)안에서 실행되는

        in-process server로 구현할수도 있지만 out-process server로 구현하는 것이 일반적임.

        

    

GUID (Globally Unique Identifier)

    모든 인터페이스와 COM 객체는 하나의 GUID값을 갖는데    각각 IID, CLSID 라고 한다.

        IID(interface identifier) : 인터페이스의 GUID

        CLSID(class identifier) : COM 객체의 GUID

    GUID는 UUID(universally unique identifier)라고도 하며 guiddef.h에 128 bit의 GUID 구조체가 정의 되어있다.

    GUID 생성은 guidgen.exe 혹은 uuidgen.exe 로 생성할 수 있다.

        guidgen.exe는 그래픽용이며 uuidgen.exe는 콘솔용이다.

        두 파일은 모두 기본적으로 C:\Program Files\Microsoft Visual Studio 9.0\Common7\Tools 에 위치한다.

        guidgen.exe는 Visual Studio의 "Tools > Create GUID" 메뉴로 실행시킬 수도 있다.

        

    GUID 관련 함수

        GUID 비교 함수들    : IsEqualGUID(), IsEqualIID(), IsEqualCLSID()

        GUID를 문자열로 변환: StringFromGUID2()

        

IUnknown 인터페이스

    COM 객체 인터페이스들의 공통 기능을 갖는 인터페이스. (Extension Interface 디자인 패턴)

    QueryInterface(), AddRef(), Release() 를 갖고 있다.

    QueryInterface()

        : COM 객체에서 제공하는 인터페이스의 포인터를 돌려주는 메소드

        HRESULT QueryInterface(

            REFIID iid,            //획득할 인터페이스의 IID

            void ** ppvObject    //획득할 인터페이스를 담을 인터페이스 포인터

        );

            ex) IXxx* pIXxx;

                pIUnknown->QueryInterface(IID_IXxx, (void**)&pIXxx);

        결국, Client에서 COM 객체 사용을 위해 IID와 해당 인터페이스 클래스를 제공받아야 한다.

        보통 QueryInterface()를 통해 인터페이스 포인터를 구한 후엔 IUnknown 인터페이스 포인터를

        사용할 필요가 없으므로 Release() 한다.

        

    AddRef(), Release()

        : COM 객체가 자신의 소멸시점을 알기 위해 자기 자신에 대한

        참조 카운터(reference counter)를 증감하기 위한 메소드.

        Client에서 직접 호출하여 참조 카운팅을 하며 참조 카운트가 0이 될때 COM 객체는 스스로 소멸한다.

        QueryInterface() 실행시 참조 카운터는 자동으로 1이 된다.

        

        **주의.인터페이스 포인터를 다른 인터페이스 포인터에 대입하거나

             함수에서 리턴하여 상요하는 경우 AddRef()를 해줘야 한다.

             사용이 끝난 인터페이스 포인터에 대해선 Release() 한다.

        **주의.AddRef(), Release()도 시스템 리소스를 사용하기 때문에 불필요한 사용은 성능 저하를 가져온다.

          

            

    Client에서 직접 참조 카운팅에 대한 불편함을 위해 스마트 포인터 클래스를 작성하여 사용할 수 있다.

    (boost의 shared_ptr 과 처럼 사용할 커스텀 클래스)

        [CSmartPtr]        : 참조 카운팅 기능

            [SmartPtr.h]

        [CSmartQIPtr]    : 참조 카운팅 + QueryInterface 기능

            [SmartQIPtr.h]

      

 

 

    

    

COM 컴포넌트 등록 방법

    in-process server

        등록) regsvr32.exe XXXX.dll

        해제) regsvr32.exe /u XXXX.dll

    out-process server

        등록) XXXX.exe /regserver

        등록) XXXX.exe -regserver

        해제) XXXX.exe /unregserver

        해제) XXXX.exe -unregserver

        

COM 클라이언트 사용 과정

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

    2. COM 객체의 CLSID를 구함.

    3. COM 객체의 인스턴스 생성.

    4. COM 객체가 제공하는 서비스 사용.

     (COM 객체가 지원하는 인터페이스의 포인터를 구하여 해당 인터페이스가 제공하는 메소드 호출)

    5. COM 객체 사용 종료시 COM 라이브러리 초기화를 해제

    

 

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

        : CoInitialize(), CoInitializeEx()

        

        COM 라이브러리 초기화를 위해 스레드에서 CoInitializeEx()를 한번만 호출하면 된다.

        CoInitializeEx()의 첫번째 인자는 항상 NULL이며

        두번째 인자는 스레딩 모델을 결정하는 COINT 열거형 값이 지정된다. (일단 COINIT_APARTMENTTHREADED 로 지정)

        

        CoInitializeEx()는 DCOM를 사용할 수 있어야 하며 다음 매크로를 지정해야 한다.

            #define _WIN32_DCOM 혹은 #define _WIN32_WINNT 0x0400

        DCOM 이전에는 CoInitialize()가 사용되으며 CoInitializeEx()의 사용이 권장된다.

            (CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)는 CoInitialize(NULL)와 동일하다.)

        

        COM 라이브러리의 메모리 관련 함수들은 초기화 없이 사용할 수 있다.

            ex) CoTaskMemAlloc(), COTaskMemFree(), CoTaskMemRealloc(), IMalloc 인터페이스 메소드들, GoGetMalloc() 등..

            

    2. COM 객체의 CLSID를 구함

        CLSID 구하는 방법

            방법1) COM 객체의 소스에서 직접 확인

            방법2) Visual Studio의 "Tools > OLE/COM Object Viewer" 로 확인

                없다면 다음 주소에서 다운 받음.

                [Windows 2000 Resource Kit Tool : OLE/COM Object Viewer (oleview.exe)]

                http://www.microsoft.com/downloads/en/details.aspx?familyid=5233b70d-d9b2-4cb5-aeb6-45664be858b6&displaylang=en

      

                [OLE/COM Object Viewer] 설명. (페이지 하단에 있음. "OLE/COM Object Viewer" 로 검색)

                http://www.autoitscript.com/autoit3/docs/intro/ComRef.htm

 

        ProgID

            : COM 객체는 CLSID에 대응되는 ProgID를 제공한다.

            레지스트리(regedit)의 HKEY_CLLASSES_ROOT 및에 COM 객체의 ProgID가 있고

            그 아래 CLSID 서브키에서 실제 CLSID를 볼 수 있다.

            ProgID는 "<컴포넌트 또는 라이브러리명>.<객체명>.<버전>"형식으로 조합된다.

                ex) Exel.Application.11

            

        CLSIDFromProgID()

            : ProgID로 부터 CLSID를 구하는 함수

    

    3. COM 객체의 인스턴스 생성.

        : CoCreateInstance(), CoCreateInstanceEx()

        

        CoCreateInstance()를 통해 COM 객체의 인스턴스를 생성하고 IUnknown 인터페이스 포인터를 구한다.

        

        STDAPI CoCreateInstance(

            REFCLSID rclsid,        //COM 객체의 CLSID

            LPUNKNOWN pUnkOuter,    //외부 COM 객체의 IUnknown 포인터(통합(aggregation)으로 COM 객체 재사용시 사용됨)

            DWORD dwClsContext,        //서버 컨텍스트

            REFIID riid,            //요청 인터페이스의 IID

            LPVOID * ppv            //리턴될 인터페이스 포인터

        );

 

        CoCreateInstance()를 호출하면 내부에서 CoGetClassObject()를 호출하는데

        CoGetClassObject()는 clsid에 맞는 COM 객체를 찾고 dwClsContext를 참조하여

        COM 객체가 포함되어 있는 COM 컴포넌트 경로명을 찾는다.

        이때 dwClsContext가

            CLSCTX_INPROC_SERVER 인 경우

                : in-process server(DLL파일)이라는 의미로

                레지스트리의 "HKEY_CLLASSES_ROOT > CLSID > "clsid값" > InprocServer32" 에서

                COM 컴포넌트 경로명을 구한다.

            CLSCTX_LOCAL_SERVER 인 경우

                : out-process server(EXE파일) 이라는 의미로

                레지스트리의 "HKEY_CLLASSES_ROOT > CLSID > "clsid값" > LocalServer32" 에서

                COM 컴포넌트 경로명을 구한다.

            CLSCTX_REMOTE_SERVER 인 경우

                : out-process server이며 remote server(DCOM) 라는 의미이다.

                [###################]

            CLSCTX_ALL 인 경우

                : CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER 를 의미

                즉, 어디에 있건 사용할 수 있도록 위치 투명성을 보장해주는 constant이다.

                이 경우 먼저 CLSCTX_INPROC_SERVER로 찾은 후 없으면 CLSCTX_LOCAL_SERVER 를 찾는다.

            CLSCTX_SERVER 인 경우

                : CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER

        로 처리 된다. (그외 옵션들은 MSDN의 CLSCTX enumeration 혹은 CLSCTX 참조)

        

        이제 CoGetClassObject()는

            in-process server인 경우

                CoLoadLibrary()를 호출하며 LoadLibrary()를 통해 COM객체의 DLL을 메모리에 로드한다.

                또한 GetProcAddress()를 호출하여 DLL안의 DllGetClassObject()를 호출하여

                IClassFactory 인터페이스 포인터를 구하고 이 인터페이스의 CreateInstance()를 호출하여

                ppv값(IUnkown 인터페이스 포인터)를 구한다.

            

            out-process server인 경우

                [###################]

        로 동작한다.

        

        IClassFactory를 사용한 다는 말은 CoCreateInstance()를 안쓰고

        직접 내부 과정을 코딩하여 사용함을 뜻한다.

        

    4. COM 객체가 제공하는 서비스 사용.

     (COM 객체가 지원하는 인터페이스의 포인터를 구하여 해당 인터페이스가 제공하는 메소드 호출)

      

     획득한 인터페이스 포인터를 통해 COM 객체가 제공하는 메소드를 호출한다.

        

        **주의. out-process server나 remote server(DCOM)의 경우 서로 다른 프로세스에서 실행되므로

            COM객체에서 할당된 메모리를 Client에서 받아 사용한 후 해제해야할 때가 있다면

            반드시 CoTaskMemAlloc()으로 메모리를 할당하고 Client에서 CoTaskMemFree()로 해제해야 한다.

    

    5. COM 객체 사용 종료시 COM 라이브러리 초기화를 해제

        : CoUninitialize()

        

        어플리케이션이 종료되기 전에 CoUninitialize()를 호출하여 COM 라이브러리를 해제한다.

        

 

 

COM 객체 구현

    실제 구현시엔 ATL 등을 사용해서 편리하게 작성하지 아래 내용처럼 피곤하게 구현하지 않는다.

    아래 내용은 쌩 C++로 작성되는 절차익 때문에 개념을 위해 읽어두면 되는 정도이다.

    

    COM 객체 구현 단계

        1. COM 인터페이스 정의

        2. COM 객체 클래스 구현 (CoClass : Component Object Class)

        3. Class Factory 클래스 구현

        4. COM 컴포넌트 구현

 

    1. COM 인터페이스 정의

        : 인터페이스 클래스로 직접 정의하기도 하지만 종속적이지 않은 인터페이스 정의를 위해

        IDL(Interface Definition Language)를 사용할 수 있다.

        

        1) 표준 인터페이스 (standard interface)

            COM에 미리 정의되어 있는 인터페이스

            IUnknown, IClassFactory, IDispatch, IConnectionPointContainer,

            IConnectionPoint, IEnumVARIANT, IOleControl, IPropertyPage, IErrorInfo, IRowset, ICommand 등..

            표준 인터페이스의 IDL은 SDK에서 제공된다.

                ex) IUnknown은 unknwn.idl 파일 (파일위치 예: C:\Program Files\Microsoft SDKs\Windows\v7.1\Include)

            

        2) 커스텀 인터페이스 정의

            개발자의 필요에 따라 정의한 인터페이스. 반드시 IDL로 정의하도록 한다.

            ex) Test.idl 파일

                

                //인터페이스 Header

                [

                 object,

                 uuid(86A698E9-A172-496a-9911-9CD4EAADB12A),

                ]

                //인터페이스 Body                

                interface ITest : IUnknown

                {

                    import "unknwn.idl";

                    HRESULT testFunc1(

                        [in, string]wchar_t* name,

                        [out, string]wchar_t** message);

                };

 

            midl.exe 를 사용하야 .idl 파일을 컴파일 한다.

                (파일 위치 예 : C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin)

                (cl.exe를 찾기 때문에 콘솔에서

                먼저 C:\Program Files\Microsoft Visual Studio 9.0\VC\bin\vcvars32.bat 를 실행시킨 후 midl 사용)

                ex) midl Test.idl

                    실행 결과 Test.h, Test_i.c, Test_p.c, dlldata.c 파일 생성

                

                    Test.h        : 인터페이스들

                    Test_i.c    : IID

                    Test_p.c    : proxy/stub 코드 및 marshalling/unmarshalling 코드

                    dlldata.c    : proxy/stub dll을 레지스트리에 등록하는데 필요한

                            DllMain, DllRegisterServer, DllUnregisterServer 등의 함수 정의되어 있다.

                            dlldata.c 컴파일시 REGISTER_PROXY_DLL 매크로를 정의하면 이런 함수들이 생성된다.

                

                ex) midl /header Test.h Test.idl

                    헤드파일을 지정한 이름데로 생성

                    

            COM 인터페이스의 모든 메소드는 파스칼 호출 규약(Pascal calling convention : __stdcall)를 사용한다.

            만약 사용하지 않는다면 파스칼 호출 규약(__stdcall)을 사용하는 Client는 해당 COM 인터페이스를 사용하지 못한다.

        

    2. COM 객체 클래스 구현 (CoClass : Component Object Class)

        구현방법1. COM 객체 클래스(CoClass)가 인터페이스 구현 클래스를 포함하는 방식

            구문은 복잡하지만 디버깅이 쉬워진다.

            ex)

                class CXxx : public IUnknown

                {

                ..

                private:

                    class CImplXxx : public IXxx

                    {

                    ..

                    private:

                        CXxx* m_pXxx;

                    };

                private:

                    CImplXxx m_ImplXxx;

                };

            

        구현방법2. COM 객체 클래스(CoClass)가 인터페이스를 상속하여 구현하는 방식

            ex)

                class CXx : public IXxx

                {

                ..

                };

                

        COM 객체 클래스 구현은 필요에 따라 여러가지 인터페이스를 사용할 수도 있다.

        

    

    3. 클래스 팩토리 클래스 구현

        클래스 팩토리 COM 객체와 COM 객체는 1:1로 대응되며

        클래스 팩토리 COM 객체를 통해 COM 객체의 인스턴스를 생성한다.

        (클래스 팩토리 COM 객체 는 클래스 팩토리 클래스를 말한다. 용어 참..쩝..)

        IClassFactory를 상속하여 구현하며 CreateInstance()와 LockServer() 를 구현한다.

        (IClassFactory는 IUnknown을 상속하므로 결국 QueryInstance(), AddRef(), Release()도 구현해야 한다)

        

        CreateInstance(): 주어진 IID에 해당하는 COM오브젝트 생성 및 포인터를 client에게 전달하는 함수

        LockServer()    : client가 직접 클래스 팩토리 메모리를 해제 여부를 지정할 함수

            [############] 4장 인-프로세스 서버 COM 컴포넌트 에서..

        

    4. COM 컴포넌트 구현

        1) in-process server 구현 (DLL 파일의 경우)

            기본 DLL 작성 절차에 따라 만든다.

            결국 DLL안에 DLL 관련 함수들과

                DLLMain(), DllCanUnloadNow(), DllGetClassObject(), DllRegisterServer(), DllUnregisterServer() 구현

            COM 객체 클래스 구현(CoClass), 클래스 팩토리 클래스 구현이 들어있게 된다.

            

            DllGetClassObject()

                : 클래스 오브젝트 생성 및 인터페이스 포인터를 client에게 전달하는 함수

                (클래스 팩토리 클래스를 사용할 수도 있다.)

            DllCanUnloadNow()

                : OS가 CoFreeUnusedLibraries()를 주기적으로 호출하고 이 함수는 다시 DLL의 DllCanUnloadNow()를 호출한다.

                DllCanUnloadNow()는 객체 카운터가 0이면 DLL Unload를 수행해도 된다는 의미로 S_OK를 리턴하고

                이렇게 S_OK가 리턴되면 CoFreeUnusedLibraries()에서 FreeLibrary()를 호출해 메로리를 해제한다.

                

            DllRegisterServer()

                : DLL 등록시 (c:\> regsvr32 XXXX.dll) DllRegisterServer() 함수가 호출되며

                이 함수는 COM 콤퍼넌트 사용을 위한 레지스트리 등록을 수행하는 코드를 작성한다.

            DllUnregisterServer()

                : DLL 등록 해제시 (c:\> regsvr32 /u XXXX.dll) DllUnregisterServer() 함수가 호출되며

                이 함수는 레지스트리에 등록된 COM 콤퍼넌트 정보를 삭제한다.

                

            그외

                #include <objbase.h>

                미리 컴파일된 헤더 파일(precompiled header file) 사용 해제

                

        

        2) out-process server 구현 (EXE 파일의 경우)

            

            [LPC, RPC]

            COM은 두 프로세스 간의 통신 수단(Inter-Process Communication, IPC)으로

            LPC(Local Procedure Call)와 RPC(Remote Procedure Call)을 사용한다.

            LPC는 Client가 로컬 서버의 out-process server와 통신시 사용되며

            RPC는 Client가 원격 서버의 out-process server와 통신시 사용된다. (DCOM:remote server)

            

            [통신 방식]

            프로세스간 인터페이스 포인터가 넘겨질때 OS는

            Server 영역에 stub 생성, IPC 채널 생성, Client 영역에 proxy 생성 후 IPC 채널을 연결시킨다.

            (proxy/stub은 데이터를 NDR(Network Data Representation)이라는 패킷으로 포장하여 통신한다)

            

            [marshalling, unmarshalling]

            out-process server의 경우 Client와 별도의 프로세스로 동작하기때문에

            서로 다른 메모리를 사용하는데 이때문에 마샬링(marshalling)이 사용된다.

 

            데이터 전송을 위해 proxy가 표준형식(NDR)으로 변환하는 것을 마샬링(marshalling)이라 하며

            stub에서 데이터를 받아 적절한 형식으로 변환하는 것을 언마샬링(unmarshalling)이라고 한다.

            (그래서 remote server 방식이 가장 느린편이다.)

            

            [MIDL]

            MIDL 컴파일러는 64bit OS 지원이 기본이라 32bit OS를 지원하는 proxy/stub 생성시엔

            /env win32 옵션을 지정해야 한다.

                ex) midl /env win32 /Oic /h Test_h.h Test.idl

                

            [proxy/stub DLL 생성]

            1. Win32 DLL 프로젝트 생성

            2. Module-Definition File(.def) 파일 작성

                ex) XXXX.def 파일

                    LIBRARY        XXXXProxy

                    EXPORTS

                        DllGetClassObject    PRIVATE

                        DllCanUnloadNow        PRIVATE

                        GetProxyDllInfo        PRIVATE

                        DllRegisterServer    PRIVATE

                        DllUnregisterServer    PRIVATE

            3. XXXX.idl 파일을 작성 및 MIDL 컴파일

                ex) midl /Oic /env win32 /h XXXX_h.h XXXX.idl

                생성된 파일들을 프로젝트에 추가

            4. REGISTER_PROXY_DLL 매크로 정의

                "프로젝트 속성창 > C/C++ > Preprocessor > Preprocessor Definitions" 에서

                REGISTER_PROXY_DLL 매크로 추가

            5. rpcrt4.lib 추가

                "프로젝트 속성창 > Linker > Input > Additional Dependencies" 에서

                rpcrt4.lib 추가

            6. 프로젝트 빌드 후 생성된 .dll 파일을 레지스트리에 등록한다.

                ex) regsvr32 XXXX.dll

                

            [IDL attribute]

            [Microsoft Interface Definition Language] : http://msdn.microsoft.com/en-us/library/aa367091(v=vs.85).aspx

            

            pointer

                ref        : 포인터는 레퍼런스이며 메서드 안에서 포인터 값의 미변경을 의미 (작고 빠름)

                unique    : 포인터는 독립적인 고유 메모리를 가리킴을 의미.

                         하지만 메소드 안에서 포인터 값의 변경 가능하기 때문에 마샬링 코드는 서버에서

                         포인터가 가르키는 데이터를 클라이언트로 부터 복사해 와야 함

                ptr        : 메서드 안에서 포인터 값이 변경될수 있으며 공유 메모리 영역을 가르킬수 있음을 의미(가장 복잡)

                

                ex) HRESULT method([in, ref] long* arg1, [out, unique] long* arg2);

                

                pointer_default : 기본으로 사용할 포인터 매개변수 attribute

                ex)

                [

                    uuid(XXX- ...),

                    object,

                    pointer_default(unique);

                ]

                

            array

                size_is    : 복사되어야 할 배열 요소의 전체 개수

                max_is    : 0번째부터 복사되어야 할 최대 배열 요소 지정

                first_is, last_is : 복사 시작 및 끝의 배열 요소 지정

                ex) HRESULT Swap([in] short e1, [in] short e2, [in, out, first_is(e1), last_is(e2)] long numarray[]);

                ex) HRESULT Swap([in] short e1, [in] short e2, [in, out, first_is(e1), last_is(e2)] long* numarray);

                

            

            user-defined data type

                ex)

                typedef struct

                {

                    int i;

                } UserDefineTest;

                [

                    object,

                    ...

                ]

                interface IXxx : IUnknown

                {

                    HRESULT Method1([in] UserDefineTest arg);

                    HRESULT Method1([out] UserDefineTest* arg);

                };

                

            interface pointer

                iid_is : 인터페이스 포인터 리턴시 사용하며 void*는 금지되어있으므로 IUnknown* 을 사용한다.

                ex) HRESULT GetInterface([in] const IID& iid, [out, iid_is(iid)] IUnknown** ppv);

                

            

            tip. DLL로부터 IDL 뽑아내기 : http://ldsqwert1.blog.me/140025704199?Redirect=Log

            

            

            [out-process server COM 컴포넌트 구현]

            

            1. WinMain 작성

                숨겨진 윈도우가 생성되며 메세지 루프를 통해 로직을 처리하게 된다.

                #define _WIN32_DCOM

                #include <windows.h>

                #include <objbase.h>

                ..

                WinMain(.., LPSTR lpCmdLine, ..)

                {

                    ..

                    //1.COM 라이브러리 초기화

                    ::CoInitializeEx(..);    

                

                    //2.메세지 루프    

                    while(GetMessage(&msg, NULL, 0, 0)) {    //혹은 PeakMessage()

                        TranslateMessage(&msg);

                        DispatchMessage(&msg);

                    }

                    

                    //3.COM 라이브러리 해제

                    ::CoUninitialize();

                    ..

                }

                

            2. CoRegisterClass(), CoRevokeClassObject()

                out-process server는 COM 컴포넌트 로드시 레지스트리의 COM 컴포넌트 경로명에

                "-Embedding"을 추가하여 WinMain의 매개변수(lpCmdLine)로 넘기는데

                이를 체크하여 COM 객체에 대응되는 클래스 팩토리 COM 객체를 등록하는 작업을 정의한다.

                

                ex) WinMAin() 안에서..

                    ..

                    if (lstrcmpiA(lpCmdLine, "-Embedding")==0 || lstrcmpiA(lpCmdLine, "/Embedding")==0)

                        OpenFactory();    //클래스 팩토리 COM 객체 등록

                    ..

                    CloseFactory();    //클래스 팩토리 COM 객체 해제

                    ::CoUninitialize();

                        

                클래스 팩토리 COM 객체를 등록하기 위해서 CoRegisterClassObject()를 사용하고

                해제시에 CoRevokeClassObject()를 사용한다.

                (위의 ex에선 각각 OpenFactory(), CloseFactory() 안에서 구현된다.)

                

                    

            3. 프로세스 종료

                종료 처리를 위해선 COM 객체가 스스로 자신에 대한 카운팅을 해야 한다.

                보통 Lock 카운터와 COM 객체 카운터를 사용하며

                클래스 팩토리 클래스의 CreateInstance()에서 COM 객체 카운팅 (QueryInterface()부분)을 하고

                LockServer() 에서 Lock 카운팅을 한다.

                COM 객체의 Release() 부분에서 COM 객체 카운터가 0이면 PostQuitMessage(0)를 호출하여

                프로세스를 종료하는 식이다.

                

            4. 레지스트리 등록/해제

                out-process server는 실행시 인자값을 분석하여

                in-process server의 DllRegisterServer()와 DllUnregisterServer()를 구현한다.

                에를 들면 "c:\> XXXX.exe /RegServer" 라고 등록하고 "/UnregServer"로 해제한다면

                WinMain에서 다음과 같은 형식으로 구현될 수 있다.

                if (lstrcmpiA(lpCmdLine, "/RegServer")==0 || lstrcmpiA(lpCmdLine, "-RegServer")) {

                    g_hInstance = hInstance;

                    RegisterServer();    //DllRegisterServer()처럼 레지스트리 등록 구현 함수

                    return 0;

                }                    

                if (lstrcmpiA(lpCmdLine, "/UnregServer")==0 || lstrcmpiA(lpCmdLine, "-UnregServer")) {

                    g_hInstance = hInstance;

                    UnregisterServer();    //DllUnregisterServer()처럼 레지스트리 등록 구현 함수

                    return 0;

                }

                

 

                                

Visual C++의 COM 지원 : 예약어 및 클래스

    #import

        : CLSID를 구하기 위한 CLSIDFromProgID()를 사용할 필요가 없다.

        ex) CLSIDFromProgID() 사용

            CLSID clsid;

            hr = ::CLSIDFromProgID(L"HelloServer.Hello..1", &clsid);

        ex) #import 사용

            #import "progid:HelloServer.Hello.1" no_namespce

        

        좀더 자세한 사용방법은 아래 type library 부분에 있다.

        참조:

            [#import Directive (C/C++)] : http://msdn.microsoft.com/en-us/library/8etzzkb6.aspx

            [#import Attributes (C/C++)] : http://msdn.microsoft.com/en-us/library/298h7faa.aspx

 

    Type Library (형식 라이브러리) 와 #import

        COM 객체나 그안의 인터페이스 정보를 소스 코드로 제공하지 않고

        .TLB 파일이라는 type library로 제공할 수 있다.

        (즉, type library란 COM 객체에 대한 정보를 저장하고 있는 이진 파일)

        

        type library는 ODL(Object Description Languege)라는 스크립트 언어로 .ODL파일을 작성하고

        MkTypLib.exe로 .OLD를 컴파일하여 .TLB 라는 type library 파일을 생성한다.

        

        결국 순수 C++로 COM 컴포넌트 구현시 많은 작업을 해야하는 부담감을 덜어주며

        type library를 통해 손쉽게 COM Client 를 구현할수 있게 해준다.

        

        [type library 생성]

        type library 사용을 위해선 IDL에 여러 구문들을 추가해야 하는데

        이는 ATL 마법사들이 자동으로 생성해준다.

        type library가 지정된 IDL파일(.idl)을 MIDL.exe로 컴파일시

        4가지 파일 외에 type library 파일(.tlb)을 생성해준다.

        (type library 정보는 레지스트리 HKEY_CLASSES_ROOT\TypeLib 에 저장됨)

    

        [#import 사용]    

        type libaray를 읽어 COM 객체와 인터페이스가 기술된 .TLH, .TLI 파일을 생성하기 위해서

            (.TLH 파일 : 스마트 포인커 클래스, 인터페이스 선언부를 갖음)

            (.TLI 파일 : COM 객체의 메소드 호출을 구현한 코드를 갖음)

        #import 문을 사용하는 3가지 방법이 있다.

            방법1) 파일 직접 지정

                #import "HelloServer.dll"

                .TLB, DLL, EXE 파일을 지정할 수 있다.

                파일이 위치한 경로를 찾는 순서는 다음과 같다.

                    1. 현재 디렉토리

                    2. PATH 환경변수에 지정된 경로

                    3. LIB 환경변수에 지정된 경로

                    4. /I 컴파일 옵션에 지정된 경로

            

            방법2) progid 지정

                #import "progid:HelloServer.Hello.1"

                

            방법3) type library ID 지정

                #import "libid:84A56321-1580-..."

        참조 : [#import Directive (C/C++)] : http://msdn.microsoft.com/en-us/library/8etzzkb6.aspx

      

                

        #import 문엔 여러가지 속성을 정할 수 있다.

        참조 : [#import Attributes (C/C++)] : http://msdn.microsoft.com/en-us/library/298h7faa.aspx

            no_namespace

                : 생성되는 헤더 파일(.TLH)엔 기본적으로 라이브러리 명으로 namespace를 사용하는데

                이 namespace 문을 생성하지 않도록 지정한다.

            named_guids

                : .TLH 파일에 type library, COM 객체, 인터페이스 들에 대한 GUID가 정의되도록 한다.

                __uuidof를 사용하면 GUID 값이 필요한 경우는 없다.

                

    __declspec(uuid())

        : COM 객체나 인터페이스에 GUID 지정이 가능

        ex) struct __declspec(uuid("8FK49F-3FK.....")) IXxx;

        

    __uuidof()

        : 지정된 클래스명이나 인터페이스에 대한 GUID를 얻어온다.

         COM 객체 인스턴스 생성을 단순화 시켜준다.

         COM 객체 인터페이스 사용시 AddRef()와 Release()를 사용할 필요가 없어진다.

        ex) 기존 코드

            IUnknown* pUnk = NULL;

            IXxx* pXxx = NULL;

            hr = ::CoCreateInstance(clsid, NULL, CLSCTX_ALL, IID_IUnknown, (void**)&pUnk);

            pUnk->QueryInterface(IID_IHello, (void**)&pIXxx);

        ex) __uuidof 사용

            IXxxPtr pIXxx(__uuidof(Xxx));

            

    __declspec(property)

        : 이 확장 속성이 지정된 변수가 포인터 멤버 선택 연산자(->)를 사용하여 참조될때

        perperty로 지정된 함수를 호출하도록 해준다

        (non-static virtual data member 에 사용 가능)

        

        ex) __declspec(property(get=Getname,put=Putname)) _bstr_t name;

            

            wchar_t* n = (wchar_t*)pIXxx->name;            은

            wchar_t* n = (wchar_t*)pIXxx->Getname();    으로 실행된다.

            

            pIXxx->name = (BSTR)name;        은

            pIXxx->Putname((BSTR)name);        으로 실행된다.

            

    _com_ptr_t

        : COM 인터페이스 포인터를 캡슐화한 스마트 포인터 클래스

        

        정의 방법

            _COM_SMARTPTR_TYPEDEF(IXxx, __uuidof(IXxx)); 로 정의하면

            IXxxPtr이 라는 타입 이름으로 사용할 수 있다.

            즉, 인터페이스명(첫번째 인자)에 Ptr이 붙는 형식으로 정의된다.

            

        COM 객체의 인스턴스 생성 방법

                    struct_declspec(uuid("39RLEFJ-D9FJ-.......")) Xxx;

                    extern "C" const GUID __declspec(selectary) CLSID_Xxx = {0x39FKDJ, .... };

 

            방법1)

                    IXxxPtr pIXxx(__uuidof(Xxx));

                    또는

                    IXxxPtr pIXxx(CLSID_Xxx);

                    또는

                    IXxxPtr pIXxx("HelloServer.Hello.1");

                    

            방법4)

                    IXxxPtr pIXxx;

 

                    pIXxx.CreateInstance(__uuidof(Xxx));

                    혹은

                    pIXxx.CreateInstance(CLSID_Xxx);

                    혹은

                    pIXxx.CreateInstance("HelloServer.Hello.1");

 

        [. 과 -> ]

        _com_ptr_t 클래스는 멤버함수 접근시엔 . 을 사용하고 (ex: pIXxx.CreateInstance(..);    )

        COM 객체 속성/메소드 이용시엔 -> 를 사용한다. (ex: pIXxx->name;    )

            

        _com_ptr_t는 기본적으로 CLSCTX_ALL 을 사용하며 기본생성자 및 .CreateInstance() 사용시 두번째 인자를 주어 제한할 수 있다.

            ex) IXxx pIXxx(__uuidof(Xxx), CLSCTX_INPROC_SERVER);

                

        [2개의 인터페이스를 제공하는 COM 객체 사용시]

        만약 하나의 COM 객체가 2개의 인터페스를 노출할 경우

        다음 처럼 사용하면 별개의 COM 객체 인스턴스를 생성하게 된다.

            IXxxAPtr pIXxxA(__uuidof(Xxx));

            IXxxBPtr pIXxxB(__uuidof(Xxx));

        이때, 동일한 COM 객체의 인스턴스를 사용하려면 다음과 같이 사용한다.

            IXxxAPtr pIXxxA(__uuidof(Xxx));

            IXxxBPtr pIXxxB = pIXxxA;

        즉, IXxxBPtr pIXxxB = pIXxxA; 코드는

            IXxxBPtr pIXxxB;

            pIXxx.QueryInterface(__uuidof(IXxxB), &pIXxxB);

        와 같다.

        

        임의적인 Release()를 하고 싶다면 _com_ptr_t 인스턴스 변수에 0을 대입하고나 Release() 를 직접 호출한다.

            ex) pIXxx = 0; 또는 pIXxx.Release();

            

    _com_error

        : _com_ptr_t 를 사용할때 리턴값(HRESULT) 체크가 필요 없이

        try~catch문을 사용할 수 있으며 _com_error를 catch하여 에러를 확인할 수 있도록 해준다.

        

        인위적인 exception 발생을 위해 _com_issue_error()와 _com_issue_errorex()를 사용할 수 있다.

            _com_issue_error : HRESULT 값을 받아 예외를 던진다.

            _com_issue_errorex : HRESULT, 인터페이스 포인터, IID를 받아 IErrorInfo 객체를 구성하여 예외를 던진다.

        _com_error는 ErrorMessage, Error, Source, Description 멤버를 갖고 있다.

            ErrorMessage: 에러 메세지 문자열 리턴

            Error        : HRESULT 값 리턴

            Source        : IErrorInfo::GetSource() 호출 결과(_bstr_t)인 에러 소스 리턴

            Description    : IErrorInfo::GetDescription() 호출 결과(_bstr_t_인 에러 소스 설명을 리턴

 

        

        ex) 기존 코드

            HRESULT hr = ::CoCreateInstance(clsid, NULL, CLSCTX_ALL, IID_IUnknown, (void**)&pUnk);

            if (hr != S_OK) cout << "CoCreateInstance FAILED" << endl;

            

        ex) _com_error 사용

            try {

                //COM 관련 함수나 인터페이스 메서드 호출 코드들        

            }

            catch (_com_error& e) {

                cout << e.ErrorMessage() << endl;

            }

 

    _bstr_t, _variant_t

        BSTR, VARIANT를 캡슐화한 클래스로 리소스 할당/해제 관리 등을 편리하게 해준다.

    

    COM 지원 전역 함수 : ConvertStringToBSTR(), ConvertBSTRToString()

        COM은 BSTR이나 _bstr_t로 문자열이 저장되기 때문에 다른 문자열 타입으로의 변환이 필요한데

        comutil.h 의 _com_util 네임스페이스에 문자변환 함수를 제공하고 있다.

        ex)

            wchar_t* str;

            pIXxx->name = _com_util::ConvertStringToBSTR((char*)str);    //string -> BSTR

            str = (wchar_t*)_com_util::ConvertBSTRToString(pIXxx->name);//BSTR -> string

            

 

            

ATL (Active Template Library)

    COM 컴포넌트를 쉽게 구현하기 위한 Library.

    

    STL처럼 템플릿 기반이라 실제 객체를 정의하기 전엔 컴파일에 포함되지 않으므로

    작고 빠르고 확장성을 갖는 COM 컴포넌트를 만들 수 있다.

    

    또한, 필수적인 대부분의 기본 코드들을 자동으로 생성해주거나 구현되어있어 편리하고 반복작업을 줄여준다.

    

    [ATL 생성방법 overview]

    1. ATL 프로젝트 생성

        ATL 프로젝트 생성시 주요 구조는 다음과 같다. (project name : ATLTest, DLL 상용)

            ATLTest project : COM 컴포넌트 작성을 위한 프로젝트

            ATLTestPS project:

                proxy/stub DLL을 얻을 수 있는 프로젝트.

                프로젝트 생성시 proxy/stub 병합을 체크하면 생성되지 않는다.

                out-process server 의 경우 proxy/stub DLL이 필요하므로 별도 생성한다.

                DCOM인 경우는 proxy/stub 병합으로 생성한다.

            ATLTest.cpp    : DllRegisterServer 등의 기본 구현 코드

            dllmain.cpp : DllMain 의 기본 구현 코드

            ATLTest.def    : DLL export 정의 파일

            ATLTest.idl    : IDL 파일

            ATLTest.rgs    : 레지스트리 등록 파일

            ATLTest.rc    : 리소스 파일

            resource.h    : 리소스 헤더 파일

            stdafx.h    : 기본 ATL 헤더 파일

            stdafx.cpp    : 빈 파일

            

    2. ATL COM 객체 생성

        Class 추가로 ATL Simple Object를 선택한다.

        특이사항:ATL Simple Object 생성시 Options > Interface 에서 Custom 선택

            Custom    : IUnknown을 상속하는 인터페이스가 생성됨

            Dual    : IDispatch를 상속하는 인터페이스가 생성됨.

                     COM Client와 Automation 컨트롤러의 접근을 모두 허용하게 됨.

 

    3. ATL COM 객체 구현

        Class View에서 해당 인터페이스 선택후 Add Method... 혹은 Add Property... 로 멤버 추가후 구현

 

 

    [ATL 기반 클래스]                

    자동으로 생성되는 AtlModule 클래스는 종류에 따라 다음 클래스들을 상속 받는다.

        DLL파일 : CAtlDllModuleT, EXE 파일 : CAtlExeModuleT, 서비스 : CAtlServiceModuleT

        

    생성한 COM 클래스는 CComObjectRootEx와 CComCoClass를 상속받는다.

        CComObjectRootEx    : IUnknown이 구현된 클래스.

        CComCoClass        : 클래스 팩토리 기능의 클래스.

    

    IDispatchImpl        : Automation 지원을 위한 IDispatch 구현 클래스.

    CComQIPrt는 CComPtr보다 발전한(QueryInterface() 지원) 클래스지만

    IUnknown 인터페이스를 직접 사용하지 못하기 때문에 이럴 경우엔 CComPtr를 사용해야 한다.

        CComPtr            : 스마트 포인터 지원 template 클래스. AddRef(), Release의 자동 관리.

        CComQIPtr            : 스마트 포인터 지원과 QueryInterface 기능의 클래스.

    CComPtr 사용시 주의사항은

        &연산자 사용시 멤버 포인터 p는 NULL이어야 한다.

        -> 연산자 사용시 멤버 포인터 p는 NULL이 아니어야 한다.

        Release()를 직접 호출시엔 ->Release()대신 .Release()로 호출해야 한다.

 

 

    [ATL_NO_VTABLE]

    생성한 COM객체 클래스에 정의된 ATL_NO_VTABLE(__declspec(novtable))는

    클래스 생성자/소멸자에서 가상함수 테이블 포인터(vptr)의 초기화를 막아주어 속도/메모리 향상을 얻을 수 있다.

    만약 가상함수를 호출해야 한다면 FinalConstruct() 에서 처리하면 COM 객체의 인스턴스가 초기화되어있음을 보장받을 수 있다.

    해당 속성을 제거하려면 _ATL_DISABLE_NO_VTABLE를 define하면 된다.

    

    [ATL 데이터 타입 클래스]

    CComBSTR    : BSTR 데이터형에 대한 클래스

    CComVariant    : VARIANT 데이터형에 대한 클래스

    

    [ATL COM 관련 Attribute]

    Attribute는 C++ 코드와 함께 지정되어 특성을 지정할수 있게 해준다.

    ex)

        //Attribute

        [

            coclass,

            threading("apartment"),

            vi_progid("HelloServer.Hello"),

            progid("HelloServer.Hello.1"),

            version(1.0),

            uuid("D83KDDFAKF-FA3F-AD32-423F.....)"),

            helpstring("Hello COM 객체")            

        ]

        class ATL_NO_VTABLE CHello : public IHello

        {

        ...

        };

        

        /* Attribute 설명 차례데로..

            coclass        : CHello는 COM 객체 클래스

            threading    : 스레딩 모델

            vi_progid    : 버전 독립적 프로그램 ID

            progid        : 프로그램 ID

            version        : 버전

            uuid        : COM 클래스의 CLSID

            helpstring    : 도움말 문자열

        */

 

    COM/IDL/OLE DB Consumer/Complier Attribute가 있다.

        Attributes by Group : http://msdn.microsoft.com/en-us/library/1zx2e6c7.aspx

 

 

WTL (Windows Template Library)

    WTL : http://wtl.sourceforge.net/

    GUI부분을 제공하는 ATL의 확장 라이브러리로서 Win32 API와 MFC의 중간적인 성격을 갖음.

    ATL/WTL 응용 프로그램 마법사 제공, 풍푸한 UI/컨트롤 클래스 제공

    GDI/DC 클래스 제공, DDX(Dynamic Data Exchange) 메커니즘 제공

      

    

    WTL 관련 강좌

        ATL/WTL : http://jacking75.cafe24.com/WTL/Index.htm

        WinAPI-WTL : http://www.winapi.co.kr/project/library/wtl/wtl.htm

        

    DDX(Dialog Data Exchange, Dynamic Data Exchange)

        컨트롤과 데이터 멤버 사이의 동적 데이터 교환 방법 제공. (MFC로부터 채용)

        DDX를 사용할 클래스는 CWinDataExchange를 상속받아 DoDataExchange() 구현하고

        DDX_로 시작하는 매크로를 통해 기능을 지원함.

        BEGIN_DDX_MAP(), END_DDX_MAP() 사이에 DDX_TEXT(), DDX_INT()등을 넣어 구현할 수 있다.

        참고 : http://jacking75.cafe24.com/WTL/70.htm

        

        ex) 사용방법 (수작업)

            1. #include <atlmisc.h>, #include <atlddx.h>

            2. 생성된 Dialog나 View 클래스에서 public CWinDataExchange<자기자신> 추가 상속

            3. BEGIN_DDX_MAP() 정의

            4.

            

            

        

    DDV(Dialog Data Validation)

        컨트롤의 데이터가 유효한지 검사하는 기능 제공

        DDV_로 시작하는 매크로를 사용하여 DDX 처럼 정의됨.

 

Automation (자동화)

    프로그램에 구현된 개체를 조작할 수 있도록 노출시키는 기술.

    (IDispatch를 사용하며 보통 dual interface로 구현한다.)

    

    [장단점]

    자동화 컴포넌트는 script 언어에서도 사용할 수 있으며 OS에서 마샬링을 제공하므로 proxy/stub DLL이 필요 없다.

    IDispatch 로 인한 간접적인 접근으로 속도 저하가 있어서 MS는 Dual Interface 사용을 권장한다.

    

    [Dual Interface]

    IDispatch와 커스텀 인터페이스 동시 노출하여 script 에선 IDispatch로 접근하고

    C++ 클라이언트에선 커스텀 인터페이스를 사용하도록 하는 방안

    즉, C++ 프로그램에서 가상 함수 테이브을 통해 직접 합수 호출할 수 있게 하고

    매크로(script) 언어에선 IDispatch의 Invoke()를 통해 함수 호출할 수 있도록 한다.

    

    dual interface의 사용이 일반적이고 쉬운 구현이다.

      

      

    

    [용어]

        자동화 컴포넌트/서버 : 자동화 서비스를 제공하는 어플리케이션

        자동화 컨트롤러/클라이언트 : 자동화 서비스를 사용하는 어플리케이션

        자동화 객체 : 자동화 서비스를 제공하는 객체

        

    [자동화 제공 기능]

        메서드(Method) : 자동화 컴포넌트의 메소드 호출

        속성(Property) : 자동화 컴포넌트의 속성(변수) 사용

        이벤트(Event) : 자동화 컴포넌트측에서 이벤트 발생시 자동화 클라이언트로 이벤트 전달 기능

        컬렉션(Collection): 여러 객체의 집합으로 메소드와 속성을 갖음 (ex. 엑셀의 Workbooks 컬렉션)

        

    [data type (데이터 타입)]

        [VARIANT 데이터 타입 (구조체)]

        서로 다른 데이터 형들을 union으로 갖고 있는 구조체.

        자동화 객체와 컨트롤러 사이의 통신시 사용될 수 있는 데이터 형을 갖고 있다.

        

        vt 멤버변수     : 데이터 형에 대한 값을 갖으며(VARTYPE) 비었을 경우 VT_EMPTY 값을 갖음.

        VariantInit()    : VARIANT 구조체 초기화 함수

        VariantChangeType() : VARIANT 를 다른 데이터 형으로 변환시 사용

        

        [BSTR 데이터 타입]

        wide charactor 배열.

        문자열 할당/해제시 SysAllocString(), SysFreeString()을 사용한다.

        (BSTR bstr = L"string"; 은 문자열 개수가 제대로 지정되지 않기 때문에 사용 불가)

        ex)

            wchar_t str[] = L"string";

            BSTR bstr = SysAllocString(str);    //할당

            SysFreeString(bstr);                //해제

        ex)     

            //CString -> BSTR 변환

            CString str = _T("hi");

            BSTR bstr = str.AllocSysString();

            

            //BSTR -> CString 변환

            CString str = (LPCWSTR)bstr;

        

        [SAFEARRAY 데이터 타입]

        SAFEARRAY와 SAFEARRAYBOUND 구조체로 배열을 표현한다.

        SafeArray로 시작하는 함수들을 제공하며 MFC에선 COleSafeArray 클래스를 제공한다.

            

            

    [IDispatch]

    IUnknown을 상속하며 GetTypeInfoCount(), GetTypeInfo(), GetIDsOfNames(), Invoke()를 갖고 있다.

    그러므로 AddRef(), Release(), QueryInterface() 외에 Invoke(), GetIDsOfNames()를 구현해야 한다.

        GetIDsOfNames()    : 메소드/속성의 DISPID(디스패치 식별자)를 구해주는 함수

        Invoke()        : DISPID 를 통해 메소드/속성을 호출한다.

        

    [IDispatchEx]

    동적인 메소드/속성의 추가를 지원하는 인터페이스.

    ATL에서는 아직 지원하지 않는다. (JScript 등에서 지원)

    

    [late binding, early binding    (후기바인딩, 초기 바인딩)]

    late binding : runtime시 DISPID가 정해지기 때문에 속도가 느리고 에러처리가 필요하다.

    early binding : 컴파일시 data type을 검사할수 있지만 반드시 type library(.tlb)를 제공받아야 한다.

    

    [ISupportErrorInfo]

    ATL Simple Object 생성시 ISupportErrorInfo에 체크하면 자동화 컨트롤러에게

    상세한 에러내용을 전달하는 기능을 구현할 수 있다. (HRESULT만으론 부족한 경우 사용)

    

    ISupportErrorInfo 사용 체크시 InterfaceSupportsErrorInfo() 함수가 생성되며

    인터페이스가 상세한 에러 정보를 제공하는지 여부를 알려주는 역할을 한다.

    (지원하는 경우 S_OK 리턴)

    

    ex)

        STDMETHODIMP CHello::InterfaceSupportsErrorInfo(REFIID riid)

        {

            static const IID* arr[] =

            {

                &IID_IHello

            };

        

            for (int i=0; i < sizeof(arr) / sizeof(arr[0]); i++)

            {

                if (InlineIsEqualGUID(*arr[i],riid)) //이걸 없애야 에러 메세지가 잘 전달됨.(000000..00324같은 메세지 피하기)

                    return S_OK;

            }

            return S_FALSE;

        }

    

    이제 에러라 판단되는 곳에서 상세한 에러 메세지를 세팅하면 된다.

    ex)

        STDMETHODIMP CHello::hello(BSTR str, BSTR* retStr)

        {

            if (false)    //그냥 예제인지라..

            {

                CComPtr<ICreateErrorInfo> pCreateErrInfo;        //에러 정보 채우기 위한 인터페이스 객체 생성

                CComQIPtr<IErrorInfo, &IID_IErrorInfo> pErrInfo;//에러 지정

                

                HRESULT hr = ::CreateErrorInfo(&pCreateErrInfo);//에러 정보 개체를 생성(OleAuto32.dll에 존재)

                if(SUCCEEDED(hr))

                {

                // 2. 인터페이스 메서드를 사용하여 에러 정보 세팅

                    pCreateErrInfo->SetSource(OLESTR("ATLHello.Hello.1"));        //에러난 progID

                    pCreateErrInfo->SetDescription(OLESTR("에러 났습니다."));    //에러 설명

                    pErrInfo = pCreateErrInfo;

                    ::SetErrorInfo(0, pErrInfo);    //에러 정보 개체를 스레드와 연결함

                }

                return E_INVALIDARG;

            }

        

            // TODO: Add your implementation code here

            CString cStr = (LPTSTR)str;    

            MessageBox(NULL, cStr, _T("hello"), MB_OK);

        

            *retStr = SysAllocString(_T("response Hello"));

            return S_OK;

        }

 

Connection Point (커넥션 포인트)

    참조 : ATL ActiveX 만들기 - Part3. 이벤트(Connection point) 구현

        : http://blog.greenmaru.com/entry/greenmarux-activex-p3

    

    자동화 객체(server측)가 자동화 컨트롤러(client측)에게 이벤트를 보내는 방법

    source interface와 sink interface로 구성됨.

        source interface(outgoing interface) : 이벤트를 발생 부분. 이를 구현한 객체를 source object라 함

        sink interface(incomming interface) 이벤트를 받는 부분. 이를 구현한 객체를 sink object라 함.

    즉, client에서 sink object를 생성하고 server에선 source object로 sink object를 호출한다.

    

    [IProvideClassInfo2]

    자동화 컨트롤러는 자동화 객체에게 IProvideClassInfo2의 GetClassInfo()를 통해 source interface에 대한

    정보를 넘겨 받고 이 정보를 이용해 sink interface를 생성하며 sink interface는

    IDispatch 혹은 dual interface 로 구현된다.

    

    보통 #import로 type library를 얻으면 sink interface에 대한 정보를 얻게 되므로

    IProvideClassInfo2를 제공할 필요가 없지만 자동화 객체가 ActiveX인 경우 제공해야 한다.

    

    [IConnectionPointContainer, IConnectionPoint]

    자동화 컨트롤러는 sink interface를 자동화 객체에 넘겨주기 위해선

    자동화 객체에서 IConnectionPointContainer와 IConnectionPoint를 지원해야 한다.

    

    [connection point 연결과정]

    자동화 컨트롤러가 자동화 객체에게

    1.IConnectionPointContainer 인터페이스를 요청하고 리턴받는다.

    2.IConnectionPointContainer::FindConnectionPoint()를 호출하여 IConnectionPoint 포인터를 리턴받는다.

    3.sink object를 생성하고 sink object로 부터 sink interface pointer(IUnknown*)을 구한다.

    4.IConnectionPoint:Advise(IUnknown*)을 호출하여 sink interface pointer(IUnknown*)을 넘겨주어

        connection point 연결 성립시킨다.

    

    자동화 객체에서의 이벤트 발생시

    5.자동화 객체에서 이벤트 발생

    6.자동화 객체는 넘겨받은 sink interface pointer(IUnknown*)로 sink object의 IDispatch를 요청하고 리턴받는다.

    7.자동화 객체는 IDispatch::Invoke() 를 통해 이벤트 발생시 호출될 메소드를 실행시킨다.

    

    사용종료시

    8.자동화 컨트롤러는 사용이 끝난 자동화 객체에 대해 IConnectionPoint::Unadvise()를 호출하여

        connection point 연결을 종료시킨다.

 

    [ATL automation 객체의 이벤트 구현]

    1. 이벤트 인터페이스 정의

    2. IConnectionPointContainer 인터페이스 획득

    3. Connection Point 추가

    4. 이벤트 발생

    5. IProvideClassInfo2 인터페이스 획득(option)

    

    [Event Source Interface]

    event source interface 정의는 IDispatch 혹은 dual interface로 정의할 수 있다.

        VisualBasic에서 사용시 dual interface를 인식 못하므로 IDispath로 정의해야 한다.

        Visual c++에서 사용시 dual interface가 편리하다.

        그래서 보통 IDispatch와 dual interface로 모두 정의한다.

            

      

        

COM 컴포넌트 재사용 방법 : COM이 다른 COM을 포함하는 방법

    이진 파일의 다른 COM을 containment(포함)이나 aggregation(통합)으로 재사용할 수 있다.

    

    [containment (포함)]

    COM은 포함할 COM과 동일한 인터페이스를 작성하여 재작성하여 경유지 역할을 수행한다.

    즉, 포함할 COM의 인스턴스를 생성하여 실행을 위임(delegation)한다.

    

    [aggregation(통합)]

    포함할 COM의 interface pointer를 client에 직접 전달하여

    포함할 COM의 메소드가 직접 호출되도록 한다.

 

      

      

      

        

12장..컬렉션

 

      

      

 

          

        

        

ATL COM(2) - MFC 클라이언트 제작 : http://blog.naver.com/PostView.nhn?blogId=rakeion&logNo=120107319737&redirect=Dlog&widgetTypeCall=true

ATL ActiveX 만들기 : http://blog.greenmaru.com/entry/greenmarux-activex

ATL codeproject : http://www.codeproject.com/KB/atl/

'C++' 카테고리의 다른 글

LocalAlloc, GlobalAlloc, HeapAlloc, VirtualAlloc  (0) 2011.02.03
파일 핸들링 API  (0) 2011.02.03
MFC tip  (0) 2011.01.25
OLEDB  (0) 2011.01.24
MFC 기초 정리  (0) 2011.01.21
파일 비동기 IO 조작 (Async IO)  (0) 2011.01.18
SEH (Structured Exception Handling) 예외처리  (0) 2011.01.12
윈도우 GUI에서 콘솔(console) 띄우기  (0) 2011.01.11