본문 바로가기
Programming/Tips(C++,C#)

Chapter 03. COM 객체 구현

by 곰네Zip 2014. 10. 29.

*이 포스팅은 개인 학습을 위해 내용을 정리한 것이 목적입니다.

 

크게 COM 컴포넌트의 구현은 다음의 4과정으로 구분할 수 있다.

1) COM인터페이스 정의

2) COM 객체클래스 구현

3) 클래스팩토리 클래스 구현 <-  여기까지 이번 chapter

-----------------------------------------------------------

4) COM 컴포넌트 구현     <- 여기는 다음 chapter

 

3.1 COM인터페이스 정의

  - 인터페이스 : 클라이언트와 서버컴포넌트가 서로 커뮤니케이션하기 위한 규약.

    C++언어에서 순수 가상함수만을 포함하는 추상클래스로 표현. 즉, 가상함수테이블 메모리구조를 가짐

  - 추상클래스를 사용하지 않는 이유 : 언어에 독립적이어야 한다. 그래서 IDL을 사용

  - COM/DCOM은 IDL과 인터페이스를 통해 컴포넌트가 언어독립적인 특징을 갖도록 하는 것임

  3.1.1 표준 인터페이스

    - COM에는 두 종류의 인터페이스가 있다. 표준 인터페이스와 커스텀인터페이스

     1) 표준인터페이스 : COM에 미리 정의되어 있음. IUnknown, IClassFactory, IDispatch, IOleControl등

     2) 커스텀인터페이스 : 사용자가 정의한 인터페이스

 

  3.1.2 커스텀 인터페이스 정의

    - 커스텀 인터페이스 정의를 위해서는 IDL을 사용해야 함.

    - COM에서 사용하는 IDL은 OSF(open source foundation)와 RPC(remote procedure call)에서 사용하

     던 IDL을 확장한 것으로 C++과 구문이 유사하다.

    - 각 인터페이스는 헤더(attribute정보 포함)와 본체(인터페이스 정의 구문)로 구성된다

 //MyClass.idl

 //인터페이스헤더

 [

        object,         //IMyClass인터페이스가 COM인터페이스임을 의미

        uuid( $UUID ) // IMyClass인터페이스의 IID

 ]

 //인터페이스 본체

 interface IMyClass : IUnknown

 {

       import "unknwn.idl";

       HRESULT Func([in, string]wchar_t* val, [out, string]wchar_t** msg);

 };

       > in : COM객체로 이동하는 인수를 의미, out : COM객체가 반환하는 인수를 의미

 

  3.1.3 MIDL컴파일러

    - 정의된 IDL구문은 MIDL컴파일러를 이용하여 컴파일 된다. 만약 myTest.idl을 컴파일 하면, 

 myTest.h      //C/C++에서 사용할 수 있는 인터페이스 클래스의 코드가 정의됨

 myTest_i.c    // IDL파일에 정의된 모든 IID를 정의한 코드가 저장됨

 myTest_p.c   // 이 파일과 dlldata.c에는 커스텀 인터페이스의 마샬링 코드가 정의된 프록시/스텁

 dlldata.c      // 코드가 저장됨

  위와 같이 파일들이 나온다. (커맨드 라인에서 입력하려면 VS의 명령프롬프트를 이용하자.)

    - __stdcall 키워드

      i) VisualC++의 확장키워드. 해당 함수가 파스칼 호출 규약을 사용하게 한다.

       * 파스칼호출규약 : 호출된 함수가 리턴되기 전에 스스로 생성된 매개변수 삭제

      ii) C++규약은 파스칼 호출규약과 다르게 함수를 호출한 함수에서 함수 호출 후 삭제

      iii) COM인터페이스에 포함되는 모든 메소드는 파스칼 규약 준수.

 

 *함수호출규약 

 호출규약

 인수전달

 스택정리

 name mangling규칙

 __cdecl

 오른쪽 먼저

 호출자

 _함수명

 __stdcall

 오른쪽 먼저

 함수

 _함수명@인수크기

 __fastcall

 ECX,EDX에 우선전달, 나머진 오른쪽먼저

 함수

 @함수명@인수크기

 thiscall

 오른쪽먼저, this는 ecx레지스터로전달

 함수

 C++네이밍

 naked

 오른쪽먼저

 함수

 없음

  c방식 함수 호출과, pascal방식의 함수 호출의 차이 

  (pascal은 win16에서, win32에서는 가변매개인자 함수를 제외하곤 __stdcall임)

 규약  인수를 스택에 집어넣는 순서  스택정리

 pascal

 왼쪽->오른쪽

 피호출자

 cdecl

 오른쪽->왼쪽

 호출자

 stdcall

 오른쪽->왼쪽

 피호출자

   (참조 : http://blog.mosaicstory.net/m/post/93#)

 

3.2 COM객체클래스 구현

  - COM객체가 인터페이스를 구현하는 방법 2가지

    i) 인터페이스 구현 클래스를 포함하는 방식으로 구현

    ii) COM객체가 인터페이스를 상속하는 방법으로 구현

  3.2.1 인터페이스 포함 방법

     - 구문은 다소 복잡하지만 디버깅이 쉽다.

 

  3.2.2 인터페이스 상속 방법

     - COM객체 클래스를 인터페이스 상속 방법으로 구현하는것은 쉽다. 인터페이스에서 파생하는 COM객

      체 클래스를 정의하면 됨

 

  3.2.3 다중인터페이스 구현 - 인터페이스 포함방법

     - 하나의 COM객체가 여러 인터페이스를 통해 서비스를 제공하는 경우, 인터페이스 포함방법으로 구현

      하면 인터페이스별로 각기 다른 인터페이스 포인터 주소를 가진다. 그래서 요청한 인터페이스에 대한 

      주소값이 다 다르다(디버깅시 유리). 그러나 포함된 클래스마다 IUnknown인터페이스를 모두 구현.

 

  3.2.4 다중인터페이스 구현 - 인터페이스 상속방법

     - 노출하고자 하는 인터페이스에서 다중상속되는 COM클래스를 정의하면 됨. 단, 다른 인터페이스의 경

      우 주소가 다르다. 강제 형 변환을 해 주어야 하지만, 최근에는 컴파일러에서 지원.

 

  3.2.5 CLSID정의

     - 클래스별로 고유한 CLSID를 가지게 하기 위해, GUIDGEN툴을 이용해서 구하면 됨.

 

3.3 클래스팩토리 클래스 구현

  3.3.1 왜 클래스 팩토리를 구현해야 하는가?

     - 클래스 팩토리 : COM클래스의 인스턴스를 생성하는 공장으로, 또하나의 COM객체.

        > CreateInstance()를 호출될 때, 이미 해당 클래스팩토리 COM객체가 결정되었다. 1팩토리 1객체.

 

  3.3.2  클래스팩토리 COM객체 구현

     - IClassFactory를 상속받은 클래스를 정의하고, IUnknown의 3개의 메소드 및 CreateInstance()와

      LockServer()메소드를 구현하면 된다.

       주의할 것은 IClassFactory를 상속받으려면 unknwn.h를 포함할것. 안하면 컴파일 안됨.

      예)

 class CMyFactory : public IClassFactory{

 public:

     CMyFactory();

     ~CMyFactory();

    //IUnknown methods

    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID* ppv);

    ULONG STDMETHODCALLTYPE AddRef();

    ULONG STDMETHODCALLTYPE Release();

    //IClassFactory methods

    HRESULT STDMETHODCALLTYPE CreateInstance(LPUNKNOWN pUnkOuter,

                                                                           REFIID riid, LPVOID* ppv);

    HRESULT STDMETHODCALLTYPE LockServer(BOOL bLock);

 private:

    DWORD m_cRef;

 } 

     해당 코드의 세부 구현은 chapter 4에서 다룬다. 다만 여기서, CreateInstance만 확인

예제


 CMyFactory::CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, LPVOID* ppv){

        HRESULT hr = E_FAIL;

CHello* pHello = NULL;

*ppv = NULL;


if( pUnkOuter != NULL){

hr = CLASS_E_NOAGGREGATION;

}

else{

pHello = new CHello;

if(pHello != NULL){

hr = pHello->QueryInterface(riid, ppv);

if(FAILED(hr)){

delete pHello;

}

}

else

{

hr = E_OUTOFMEMORY;

}

}

return hr;

}

     > 여기에서 pUnkOuter가 != NULL인 경우 처리가 별도로 되어 있다. 원래 pUnkOuter는 NULL이 들어온다. 단, COM서버를 통합하는 경우에만 pUnkOuter가 NULL이 아니다. ( http://gomnezip.tistory.com/329 참조)

     클래스팩토리의 동작에 관한 설명도 다른 포스팅에 있다. 그리고 추후에 다시 다룰예정.

 

출처 - CBD, Component Development with Visual C++,ATL

반응형

댓글