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

Chapter 4. Query Interface

by 곰네Zip 2014. 10. 27.

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

 

1. 인터페이스에 대한 질의

   클라이언트가 COM에 접근하려면 인터페이스 이름을 알아야 한다. 그러나 이름을 직접적으로 사용하는

  방법은 인터페이스 이름의 중복이 나타날 확률이 높다.

     -> 중복 인터페이스 이름이 생기지 않도록 하거나, 인터페이스 이름 대신 중복되지 않는 무언가

        (인터페이스 ID)를 사용해야 한다.

 

   - 모든 COM은 IUnknown이라는 인터페이스를 노출해, IUnknown의 QueryInterface에 InterfaceID를 인자

     로 넘겨주면, 해당 인터페이스를 반환하도록 한다.

 

   - IUnknown의 원형 

  interface IUnknown

  {

     virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv) = 0;

     virtual ULONG __stdcall AddRef() = 0;

     virtual ULONG __stdcall Release() = 0;

  }

    IUnknown의 구현에 필요한 규칙

     1) 모든 COM컴포넌트는 IUnknown인터페이스를 구현해야만 한다.

     2) 모든 COM인터페이스는 IUnknown의 세 함수로 시작한다.

       -> IUnknown을 파생받아야 한다. 그러면 vtable에 IUnknown의 3 함수가 가장 먼저 나타남.

     3) IUnknown은 COM객체의 생명주기를 제어하는 수단을 제공해야 한다. (AddRef()와 Release())

 

2. IUnknown 포인터 획득

  QueryInterface()를 사용하려면 IUnknown 포인터를 사용하기에 IUnknown을 먼저 얻어와야 한다. 

 HRESULT hr = pI->QueryInterface(IID_ISimpleCalc,(void**) &pISc);

  [Query Interface하는 예]

  pI는 IUnknown의 포인터. IID_ISimpleCalc의 경우 유일하게 정의된 키 값. 이를 통해 pISc에 유일한 인터페이스가 들어간다.

  IUnknown 포인터는 CreateInstance()함수를 이용하여 구한다.

 

3. QueryInterface

  컴포넌트가 찾는 인터페이스를 지원하면, 해당 인터페이스의 포인터를 반환한다. (아니면 에러코드)

  1) QueryInterface 사용하기

    QueryInterface는 특정 인터페이스를 찾는 경우에 사용됨. 인터페이스 포인터 값은 처음에 NULL로 세팅

   되어야 함. QueryInterface가 인터페이스를 못 찾을경우 NULL을 반환하는 것으로 가정하기 때문임.

 

  2) QueryInterface의 구현

   QueryInterface에 IID를 인자로 받아서 해당 인터페이스에 대한 포인터를 반환한다.

   예) 

인터페이스 구현 

 interface IFunc1 : IUnknown{ ... };

 interface IFunc2 : IUnknown{ ... };

 class CMyClass : public IFunc1, IFunc2

 {

      ...

 };

 일 경우에, QueryInterface 구현은

 HRESULT __stdcall CMyClass::QueryInterface(const IID& iid, void** ppv)

 {

    if( iid == IID_IUnknown){

       *ppv = static_cast<IUnknown*>(this);

    }

    else if( iid == IID_IFunc1){

       *ppv = static_cast<IFunc1*>(this);

    }

    else if( iid == IID_IFunc2){

       *ppv = static_cast<IFunc2*>(this);

    }

    else{

       *ppv = NULL;

       return E_NOINTERFACE;

    }

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

    return S_OK;

 }

   case문 대신에 if-then-else를 사용한다. iid가 구조체이므로, case문에 사용이 불가능하다.

 

  3) 캐스팅

   위의 예제에서 캐스팅은 중요하다. ppv의 값은 클래스에 따라서 변하게 됨. 위의 예제에서 IID_IUnknown

  일 때, 캐스팅이 아래와 같다. 

  *ppv = static_cast<IUnknown*>(this);

   이 경우 this를 IUnknown으로의 캐스팅이 애매모호 하다. (IFunc1, IFunc2 모두 IUnknown에서 파생되었

  다.) 그래서 위 방식보다 다음과 같이 하는 것이 더 좋다. 

  *ppv = static_cast<IUnknown*>(static_cast<IFunc1*>(this);

  또는

  *ppv = static_cast<IUnknown*>(static_cast<IFunc2*>(this);

 

  4) 실전 프로그램

    - CreateInstance()를 이용하여 클래스 객체와 클라이언트의 분리.

        -> 클라이언트에서는 인터페이스만 이용하여 클래스 객체에 접근

    - CreateInstance함수의 구현부 

 IUnknown* CreateInstance(){

    IUnknown *pI = static_cast<IFunc1*>( new CMyClass );

    pI->AddRef(); //컴포넌트가 생성되었으므로 컴포넌트 갯수를 1 증가.

    return pI;

 }

 

    - QueryInterface()를 통해 클래스 객체인 CMyClass에 구현된 모든 인터페이스에 접근 가능하다.

      *인터페이스 가져오는 방법. (IUnknown을 이용) 

 IFunc1* pIFunc1 = NULL;

 hr = pIUnknown->QueryInterface(IID_IFunc1, (void**)&pIFunc1);

    - 위 구문에 이어 다음과 같은 동작도 가능함. (pIFunc1이 유효한 상태)

 IFunc2* pIFunc2 = NULL;

 IUnknown* pIUnknown2 = NULL;

 hr = pIFunc1->QueryInterface(IID_IFunc2, (void**)&pIFunc2); //유효한 IFunc2인터페이스 가져옴

 hr = pIFunc1->QueryInterface(IID_IUnknown,(void**)&pIUnknown2); //유효한 IUnknown!

      반드시 IUnknown을 이용하여 가져와야 하는건 아님. (QueryInterface구현을 생각해 보자)

      pIFunc1에서 QueryInterface를 이용하여 가져온 IUnknown2는 IUnknown과 같은 값이다.

 

    * 위 내용을 바탕으로 구현한 QueryInterface의 규정과 규칙에 대한 설명

     1) 컴포넌트의 인스턴스는 항상 동일한 IUnknown인터페이스를 가진다. 항상 클라이언트는 같은 값의

        IUnknown인터페이스 포인터를 가짐

     2) 같은 컴포넌트에 대해 이미 한번 획득한 인터페이스는 계속적으로 획득 가능하다.

     3) 인터페이스를 사용한 후에 반드시 해지를 해야한다. 다시 같은 인터페이스를 획득하여야 할 일이 생

        겨도, 이미 획득한 인터페이스는 다시 획득하는것이 가능하다.

     4) 인터페이스를 획득하였으면, 역으로 획득하는것이 가능하다. (IFunc1에서 IFunc2를 획득하였으면, 그

        역인 IFunc2에서 IFunc1이 획득 가능하다.)

     5) 인터페이스 IA에서 IB를 획득하였고, IB에서 IC를 획득하였다면, IA에서 IC를 얻는것은 항상 성공적이

        어야 한다.

 

 *소스보기

 

내용 출처 : COM Bible

 

반응형

댓글