다음을 통해 공유


TN038: MFC/OLE IUnknown 구현

참고

다음 기술 노트는 온라인 설명서에 먼저 포함되어 있었으므로 업데이트되지 않았습니다.따라서 일부 절차 및 항목은 만료되거나 올바르지 않을 수 있습니다.최신 정보를 보려면 온라인 설명서 색인에서 관심 있는 항목을 검색하는 것이 좋습니다.

OLE 2의 핵심은 "OLE 구성 요소 개체 모델", 또는 COM입니다. COM은 협력하는 개체의 상호 통신 방법에 대한 표준을 정의합니다. 여기에는 메서드가 개체에 발송되는 방법을 포함하여 "개체"가 어떻게 나타나는지에 대한 세부 정보가 포함됩니다. COM은 모든 COM 호환 클래스가 파생되는 기본 클래스를 정의합니다. 이 기본 클래스는 IUnknown입니다. IUnknown 인터페이스는 C++ 클래스로 언급되지만 COM은 한 언어에만 특정된 것은 아닙니다. C, PASCAL 또는 COM 개체의 이진 레이아웃을 지원할 수 있는 다른 언어에서 구현할 수 있습니다.

OLE는 IUnknown에서 파생된 모든 클래스를 "인터페이스"로 참조합니다. IUnknown과 같은 "인터페이스"는 해당 구현을 포함하지 않기 때문에 중요한 차이가 있습니다. 이러한 구현이 수행하는 작업에 대한 고유 정보가 아닌, 개체가 통신하는 것으로 프로토콜을 간단하게 정의합니다. 이것은 최대한의 유연성을 허용하는 시스템의 경우에는 합리적인 수준입니다. MFC/c + + 프로그램의 기본 동작을 구현하는 것이 MFC의 역할입니다.

IUnknown에 대한 MFC의 구현을 이해하기 위해서는 먼저 이 인터페이스가 무엇인지 알아야 합니다. 간단한 버전의 IUnknown은 아래에 정의되어 있습니다.

class IUnknown
{
public:
    virtual HRESULT QueryInterface(REFIID iid, void** ppvObj) = 0;
    virtual ULONG AddRef() = 0;
    virtual ULONG Release() = 0;
};

참고

__stdcall와 같은 필요한 특정 호출 규칙 설명은 이 그림의 경우 생략되어 있습니다.

AddRef릴리스 멤버 함수는 개체의 메모리 관리를 제어합니다. COM은 개체를 추적하기 위해 참조 횟수 구성표를 사용합니다. 개체는 C++에서와 마찬가지로 직접 참조되지 않습니다. 대신, COM 개체는 항상 포인터를 통해 참조됩니다. 소유자가 사용을 마쳤을 때 개체를 해제하려면 개체의 릴리스 멤버가 호출됩니다(operator delete를 사용하는 것이 아니라 기존 C++ 개체에 사용된 것처럼 호출됨). 참조 카운팅 메커니즘은 여러 개의 참조부터 단일 개체까지 관리 될 수 있도록 합니다.. AddRefRelease의 구현은 개체에 참조 횟수를 유지합니다. 개체는 참조 횟수가 0이 될 때까지 삭제되지 않습니다.

AddRef릴리스는 구현이라는 측면에서 매우 간단합니다. 간단한 구현은 다음과 같습니다.

ULONG CMyObj::AddRef() 
{ 
    return ++m_dwRef; 
}

ULONG CMyObj::Release() 
{ 
    if (--m_dwRef == 0) 
    {
        delete this; 
        return 0;
    }
    return m_dwRef;
}

QueryInterface 멤버 함수는 좀 더 흥미롭습니다. 유일한 멤버 함수가 AddRefRelease인 개체에는 큰 관심이 없습니다. 개체에 IUnknown이 제공하는 것보다 더 많은 작업을 수행하도록 하는 것이 좋습니다. 이 경우에는 QueryInterface가 유용합니다. 같은 개체에 다른 "인터페이스"를 얻을 수 있습니다. 이 인터페이스는 일반적으로 IUnknown에서 파생되며 새 멤버 함수를 추가하여 기능을 추가합니다. COM 인터페이스는 인터페이스에 선언된 멤버 변수를 갖지 않으며 모든 멤버 함수는 순수 가상 함수로 선언됩니다. 예를 들면 다음과 같습니다.

class IPrintInterface : public IUnknown
{
public:
    virtual void PrintObject() = 0;
};

IUnknown만 있는 경우 IPrintInterface를 가져오려면 IPrintInterface의 IID를 사용하여 QueryInterface를 호출합니다. IID는 인터페이스를 고유하게 식별하는 128 비트 번호입니다. 사용자 또는 OLE가 정의하는 각 인터페이스에 대한 IID이 있습니다. pUnk이 IUnknown 개체의 포인터인 경우 여기에서 IPrintInterface를 검색하는 코드는 다음과 같을 수 있습니다.

IPrintInterface* pPrint = NULL;
if (pUnk->QueryInterface(IID_IPrintInterface, 
    (void**)&pPrint) == NOERROR)
{
    pPrint->PrintObject();
    pPrint->Release();   
        // release pointer obtained via QueryInterface
}

이것은 아주 쉽지만 IPrintInterface 및 IUnknown 인터페이스를 지원하는 개체를 어떻게 구현합니까? 이 경우 IPrintInterface가 IPrintInterface를 구현하여 IUnknown에서 직접 파생되기 때문에 간단합니다. IUnknown은 자동으로 지원됩니다. 예를 들면 다음과 같습니다.

class CPrintObj : public CPrintInterface
{
    virtual HRESULT QueryInterface(REFIID iid, void** ppvObj);
    virtual ULONG AddRef();
    virtual ULONG Release();
    virtual void PrintObject();
};

AddRef릴리스의 구현은 위에 구현된 것들과 정확하게 동일할 것입니다. CPrintObj::QueryInterface는 다음과 같습니다.

HRESULT CPrintObj::QueryInterface(REFIID iid, void FAR* FAR* ppvObj)
{
    if (iid == IID_IUnknown || iid == IID_IPrintInterface)
    {
        *ppvObj = this;
        AddRef();
        return NOERROR;
    }
    return E_NOINTERFACE;
}

이 그림에서 볼 수 있듯이 IID(인터페이스 식별자)가 인식되면 포인터가 개체로 반환되고, 그렇지 않으면 오류가 반환됩니다. 또한 성공적인 QueryInterface는 암시적 AddRef를 발생시킨다는 것에도 주목합니다. 물론 CEditObj::Print를 구현해야 할 수 있습니다. IUnknown 인터페이스에서 IPrintInterface가 직접 파생되었기 때문에 간단합니다. 그러나 둘 다 IUnknown에서 파생된 두 가지 인터페이스를 지원 하려는 경우 다음 사항을 고려하십시오.

class IEditInterface : public IUnkown
{
public:
    virtual void EditObject() = 0;
};

C++ 다중 상속을 포함하여 IEditInterface 및 IPrintInterface를 지원하는 클래스를 구현하는 여러 가지 방법이 있지만 이 참고는 이 기능을 구현하기 위한 중첩된 클래스 사용에 중점을 둡니다.

class CEditPrintObj
{
public:
    CEditPrintObj();

    HRESULT QueryInterface(REFIID iid, void**);
    ULONG AddRef();
    ULONG Release();
    DWORD m_dwRef;

    class CPrintObj : public IPrintInterface
    {
    public:
        CEditPrintObj* m_pParent;
        virtual HRESULT QueryInterface(REFIID iid, void** ppvObj);
        virtual ULONG AddRef();
        virtual ULONG Release();
    } m_printObj;

    class CEditObj : public IEditInterface
    {
    public:
        CEditPrintObj* m_pParent;
        virtual ULONG QueryInterface(REFIID iid, void** ppvObj);
        virtual ULONG AddRef();
        virtual ULONG Release();
    } m_editObj;
};

전체 구현은 아래에 포함되어 있습니다.

CEditPrintObj::CEditPrintObj()
{
    m_editObj.m_pParent = this;
    m_printObj.m_pParent = this;
}

ULONG CEditPrintObj::AddRef() 
{ 
    return ++m_dwRef;
}

CEditPrintObj::Release()
{
    if (--m_dwRef == 0)
    {
        delete this;
        return 0;
    }
    return m_dwRef;
}

HRESULT CEditPrintObj::QueryInterface(REFIID iid, void** ppvObj)
{
    if (iid == IID_IUnknown || iid == IID_IPrintInterface)
    {
        *ppvObj = &m_printObj;
        AddRef();
        return NOERROR;
    }
    else if (iid == IID_IEditInterface)
    {
        *ppvObj = &m_editObj;
        AddRef();
        return NOERROR;
    }
    return E_NOINTERFACE;
}

ULONG CEditPrintObj::CEditObj::AddRef() 
{ 
    return m_pParent->AddRef(); 
}

ULONG CEditPrintObj::CEditObj::Release() 
{ 
    return m_pParent->Release(); 
}

HRESULT CEditPrintObj::CEditObj::QueryInterface(
    REFIID iid, void** ppvObj) 
{ 
    return m_pParent->QueryInterface(iid, ppvObj); 
}

ULONG CEditPrintObj::CPrintObj::AddRef() 
{ 
    return m_pParent->AddRef(); 
}

ULONG CEditPrintObj::CPrintObj::Release() 
{ 
    return m_pParent->Release(); 
}

HRESULT CEditPrintObj::CPrintObj::QueryInterface(
    REFIID iid, void** ppvObj) 
{ 
    return m_pParent->QueryInterface(iid, ppvObj); 
}

대부분의 IUnknown 구현은 CEditPrintObj::CEditObj 및 CEditPrintObj::CPrintObj에 코드를 복제하는 대신 CEditPrintObj 클래스에 배치됩니다. 그러면 코드 양이 줄어들고 버그를 방지할 수 있습니다. IUnknown 인터페이스에서는 QueryInterface를 호출하여 개체가 지원할 수 있는 모든 인터페이스를 검색할 수 있으며 각 인터페이스에서 동일한 작업을 수행할 수 있다는 점이 중요합니다. 즉, 각 인터페이스에서 제공되는 모든 QueryInterface 함수가 정확히 동일하게 동작해야 합니다. 이러한 포함된 개체가 "외부 개체"에 있는 구현을 호출하도록 후방 포인터가 사용됩니다(m_pParent). M_pParent 포인터는 CEditPrintObj 생성자 동안에 초기화됩니다. 그런 다음 CEditPrintObj::CPrintObj::PrintObject 및 CEditPrintObj::CEditObj::EditObject를 구현합니다. 개체를 편집하는 기능을 추가하기 위해 많은 코드가 추가되었습니다. 다행히 단일 멤버 함수만 있는 인터페이스는 매우 드문 경우이며(발생하기는 하지만) 이 경우 EditObject 및 PrintObject는 대개 단일 인터페이스에 결합됩니다.

간단한 시나리오에 대한 많은 설명과 많은 코드가 있습니다. MFC/OLE 클래스는 간단한 대안을 제공합니다. MFC 구현은 메시지 맵과 래핑된 Windows 메시지 동작 방식과 비슷한 기법을 사용합니다. 이 기능을 인터페이스 맵이라고 부르며 다음 섹션에서 설명합니다.

MFC 인터페이스 맵

MFC/OLE는 개념과 실행에서 MFC의 "메시지 맵" 및 "디스패치 맵"과 유사한 "인터페이스 맵"의 구현을 포함합니다. MFC 인터페이스 맵의 핵심 기능은 아래와 같습니다.

또한 인터페이스 맵은 다음과 같은 고급 기능을 지원합니다.

  • 집계 가능한 COM 개체를 만들기 위한 지원

  • 집계 개체를 사용하여 COM 개체의 구현에서 대한 지원

  • 구현은 Hookable하고 확장 가능합니다.

집합체에 대한 자세한 내용은 Aggregation 항목을 참조하십시오.

MFC 인터페이스 맵을 지원은 CCmdTarget 클래스에서 시작합니다. CCmdTarget "has-a" 참조 횟수와 IUnknown 구현과 관련된 모든 멤버 함수(예를 들어 참조 횟수는 CCmdTarget에 있음). OLE COM을 지원하는 클래스를 만들려면 CCmdTarget에서 클래스를 파생하고 CCmdTarget의 멤버 함수뿐 아니라 다양한 매크로를 사용하여 원하는 인터페이스를 구현합니다. 위의 예제처럼 MFC는 인터페이스 구현을 정의하기 위해 중첩된 클래스를 사용합니다. 일부 반복 코드를 제거하는 다수의 매크로뿐 아니라 IUnknown에 대한 표준 구현을 통해 더 쉽게 수행할 수 있습니다.

MFC 인터페이스 맵을 사용해서 클래스를 구현하려면

  1. 클래스를 CCmdTarget에서 직접 또는 간접적으로 파생합니다.

  2. 파생된 클래스 정의에서 DECLARE_INTERFACE_MAP 함수를 사용합니다.

  3. 지원하려는 각 인터페이스는 클래스 정의에서 BEGIN_INTERFACE_PARTEND_INTERFACE_PART 매크로를 사용합니다.

  4. 구현 파일에서는 BEGIN_INTERFACE_MAPEND_INTERFACE_MAP 매크로를 사용하여 클래스의 인터페이스 맵을 정의합니다.

  5. 지원되는 각 IID에 대해 BEGIN_INTERFACE_MAPEND_INTERFACE_MAP 매크로 사이에 INTERFACE_PART 매크로를 사용하여 해당 IID를 클래스의 특정 "파트"에 매핑합니다.

  6. 사용자가 지원하는 인터페이스를 나타내는 각 중첩된 클래스를 구현합니다.

  7. METHOD_PROLOGUE 매크로를 사용하여 부모 항목인 CCmdTarget 파생 개체에 액세스합니다.

  8. AddRef, ReleaseQueryInterface는 이러한 함수(ExternalAddRef, ExternalReleaseExternalQueryInterface)의 CCmdTarget 구현에 위임할 수 있습니다.

위의 CPrintEditObj 예제는 다음과 같이 구현될 수 있습니다.

class CPrintEditObj : public CCmdTarget
{
public:
    // member data and member functions for CPrintEditObj go here

// Interface Maps
protected:
    DECLARE_INTERFACE_MAP()

    BEGIN_INTERFACE_PART(EditObj, IEditInterface)
        STDMETHOD_(void, EditObject)();
    END_INTERFACE_PART(EditObj)

    BEGIN_INTERFACE_PART(PrintObj, IPrintInterface)
        STDMETHOD_(void, PrintObject)();
    END_INTERFACE_PART(PrintObj)
};

위의 선언은 CCmdTarget에서 파생되는 클래스를 만듭니다. DECLARE_INTERFACE_MAP 매크로는 이 클래스는 사용자 지정 인터페이스 맵이 있다고 프레임 워크에 알려 줍니다. 또한 BEGIN_INTERFACE_PARTEND_INTERFACE_PART 매크로는 중첩된 클래스를 정의하며, 이름은 CEditObj 및 CPrintObj입니다(X는 "C"로 시작하는 전역 클래스 및 "I"로 시작하는 인터페이스 클래스와 중첩된 클래스를 구분하는 데만 사용됩니다). 이러한 클래스의 두 개의 중첩된 멤버인 m_CEditObj 및 m_CPrintObj가 각각 만들어집니다. 매크로가 AddRef, ReleaseQueryInterface 함수를 자동으로 선언하므로 EditObject 및 PrintObject 인터페이스에 대한 함수만 선언하면 됩니다. OLE 매크로 STDMETHOD가 사용되어 _stdcall 및 가상 키보드가 대상 플랫폼에 맞게 제공됩니다.

이 클래스에 대한 인터페이스 맵을 구현하려면

BEGIN_INTERFACE_MAP(CPrintEditObj, CCmdTarget)
    INTERFACE_PART(CPrintEditObj, IID_IPrintInterface, PrintObj)
    INTERFACE_PART(CPrintEditObj, IID_IEditInterface, EditObj)
END_INTERFACE_MAP()

그러면 IID_IPrintInterface IID를 m_CPrintObj에, IID_IEditInterface를 m_CEditObj에 각각 연결합니다. QueryInterface(CCmdTarget::ExternalQueryInterface)의 CCmdTarget 구현은 이 맵을 사용하여 요청 시 m_CPrintObj 및 m_CEditObj 에 포인터를 반환합니다. IID_IUnknown에 대한 항목을 포함할 필요는 없습니다. 프레임워크는 IID_IUnknown이 요청될 때 맵에서 첫 번째 인스턴스(이 경우 m_CPrintObj)를 사용합니다.

BEGIN_INTERFACE_PART 매크로는 사용자를 대신하여 AddRef, ReleaseQueryInterface 함수를 자동으로 선언하기는 하지만 여전히 함수를 구현해야 합니다.

ULONG FAR EXPORT CEditPrintObj::XEditObj::AddRef()
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    return pThis->ExternalAddRef();
}

ULONG FAR EXPORT CEditPrintObj::XEditObj::Release()
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    return pThis->ExternalRelease();
}

HRESULT FAR EXPORT CEditPrintObj::XEditObj::QueryInterface(
    REFIID iid, void FAR* FAR* ppvObj)
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    return (HRESULT)pThis->ExternalQueryInterface(&iid, ppvObj);
}

void FAR EXPORT CEditPrintObj::XEditObj::EditObject()
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    // code to "Edit" the object, whatever that means...
}

CEditPrintObj::CPrintObj를 위한 구현은 위의 CEditPrintObj::CEditObj에 대한 정의와 유사한 것입니다. 이러한 함수를 자동으로 생성하는 데 사용할 수 있는 매크로를 만들 수는 있지만(그러나 이 경우 MFC/OLE 개발 이전), 매크로가 두 줄 이상의 코드를 생성할 때 중단점을 설정하기가 어려워집니다. 따라서 이 코드는 수동으로 확장됩니다.

메시지 맵의 프레임워크 구현을 사용하면 다음과 같이 필요하지 않은 많은 작업이 있습니다.

  • QueryInterface 구현

  • AddRef 및 릴리스 구현

  • 사용자 인터페이스 모두에 대해 이러한 기본 제공 메서드 중 하나를 선언합니다.

또한 프레임워크는 내부적으로 메시지 맵을 사용합니다. 이렇게 하면 사용자가 특정 인터페이스를 이미 지원하는 프레임워크 클래스(예: COleServerDoc)에서 파생할 수 있으며 프레임워크에서 제공하는 인터페이스 대체 또는 추가를 제공합니다. 이렇게 할 수 있는 이유는 기본 클래스에서 인터페이스 맵 상속이 프레임워크에서 완전히 지원되기 때문입니다. 이것은 BEGIN_INTERFACE_MAP이 기본 클래스의 이름을 두 번째 매개 변수로 사용하는 이유입니다.

참고

일반적으로 MFC 버전에서 해당 인터페이스의 OLE 인터페이스의 포함된 특수화를 상속하여 MFC 기본 제공 구현의 구현을 다시 사용하는 것이 불가능합니다.포함된 CCmdTarget 파생 개체에 대한 액세스 권한을 얻는 데 METHOD_PROLOGUE 매크로를 사용하는 것은 CCmdTarget 파생 개체의 포함된 개체에 대한 고정 오프셋을 의미하므로 불가능합니다.즉, 예를 들어 XAdviseSink가 COleClientItem 개체 맨 위의 특정 오프셋에 있기 때문에 COleClientItem::XAdviseSink의 MFS 구현에서 포함된 XMyAdviseSink를 파생할 수 없습니다.

참고

그러나 MFC의 기본 동작에서 원하는 모든 기능에 대한 MFC 구현을 위임할 수 있습니다.COleFrameHook 클래스(많은 함수에 대한 m_xOleInPlaceUIWindow로 위임)의 IOleInPlaceFrame(XOleInPlaceFrame)에 대한 MFC 구현에서 수행됩니다.이 디자인은 여러 인터페이스를 구현하는 개체의 런타임 크기를 줄이기 위해 선택되었으며 이전 단원에서 m_pParent가 사용된 방법과 같이 백 포인터가 필요하지 않습니다.

집합체 및 인터페이스 맵

독립 실행형 COM 개체를 지원할 뿐 아니라 MFC는 집합체도 지원합니다. 집합체 자체는 이곳에서 논의하기에 너무 복잡한 항목입니다. 집합체에 대한 자세한 내용은 집합체 항목을 참조하십시오. 이 메모는 프레임워크 및 인터페이스 맵에 기본적으로 제공되는 집계 지원에 대해 간단히 설명합니다.

집계를 사용하는 방법은 두 가지가 있습니다. (1) 집계를 지원하는 COM 개체를 사용하는 방법 (2) 다른 개체에서 집계할 수 있는 개체를 구현하는 방법 이러한 기능은 "집계 개체 사용" 및 "집계 가능 개체 만들기"라고 합니다. MFC 모두를 지원합니다.

집계 개체 사용

집계 개체를 사용하려면 집계를 QueryInterface 메커니즘으로 연결하는 방법이 필요합니다. 즉, 집계 개체는 개체의 네이티브 부분인 것처럼 동작해야 합니다. MFC 인터페이스 매핑 메커니즘에 이것을 어떻게 연결합니까? INTERFACE_PART 매크로 외에 중첩된 개체가 IID에 매핑된 경우 집계 개체를 CCmdTarget 파생된 클래스의 일부로 선언할 수도 있습니다. 이를 위해서는 INTERFACE_AGGREGATE 매크로가 사용됩니다. 이렇게 하면 사용자가 멤버 변수(IUnknown 또는 파생된 클래스에 대한 포인터여야 함)를 지정할 수 있으며 이 멤버 변수는 인터페이스 맵 메커니즘과 통합됩니다. CCmdTarget::ExternalQueryInterface이 호출될 때 포인터가 NULL이 아닌 경우 요청된 IID이 CCmdTarget 개체 자체에서 지원되는 네이티브 IID 중 하나가 아니면 프레임워크는 집계 개체의 QueryInterface 멤버 함수를 자동으로 호출합니다.

INTERFACE_AGGREGATE 매크로를 사용하려면

  1. 집계 개체에 대한 포인터가 포함된 멤버 변수(IUnknown*)를 선언합니다.

  2. 이름으로 멤버 변수를 참조하는 인터페이스 맵에 INTERFACE_AGGREGATE 매크로를 포함합니다.

  3. 특정 시점에서(대개 CCmdTarget::OnCreateAggregates 동안), 멤버 변수를 NULL 이외의 형식으로 초기화합니다.

예를 들면 다음과 같습니다.

class CAggrExample : public CCmdTarget
{
public:
    CAggrExample();

protected:
    LPUNKNOWN m_lpAggrInner;
    virtual BOOL OnCreateAggregates();

    DECLARE_INTERFACE_MAP()
    // "native" interface part macros may be used here
};

CAggrExample::CAggrExample()
{
    m_lpAggrInner = NULL;
}

BOOL CAggrExample::OnCreateAggregates()
{
    // wire up aggregate with correct controlling unknown
    m_lpAggrInner = CoCreateInstance(CLSID_Example,
        GetControllingUnknown(), CLSCTX_INPROC_SERVER,
        IID_IUnknown, (LPVOID*)&m_lpAggrInner);
    if (m_lpAggrInner == NULL)
        return FALSE;
    // optionally, create other aggregate objects here
    return TRUE;
}

BEGIN_INTERFACE_MAP(CAggrExample, CCmdTarget)
    // native "INTERFACE_PART" entries go here
    INTERFACE_AGGREGATE(CAggrExample, m_lpAggrInner)
END_INTERFACE_MAP()

m_lpAggrInne 변수는 생성자에 의해 NULL로 초기화됩니다. 프레임 워크는 QueryInterface 기본 구현에서 NULL 멤버 변수를 무시합니다. OnCreateAggregates은 집계 개체를 만들기 좋은 곳입니다. COleObjectFactory의 MFC 구현 외부에 개체를 만들 경우 이를 명시적으로 호출해야 합니다. CCmdTarget::GetControllingUnknown을 사용할 뿐 아니라 CCmdTarget::OnCreateAggregates에서 집계를 만드는 이유는 집계 가능한 개체 만들기에 대한 설명에서 명확히 알 수 있습니다.

이 기법을 통해 집계 개체가 지원하는 모든 인터페이스 및 네이티브 인터페이스가 개체에 제공됩니다. 집계를 지원하는 인터페이스의 하위 집합만 원하는 경우 CCmdTarget::GetInterfaceHook을 재정의할 수 있습니다. 이렇게 하면 매우 낮은 수준의 hookability가 가능하며 QueryInterface와 유사합니다. 일반적으로 집계에서 지원되는 모든 인터페이스가 필요할 수 있습니다.

집계 가능한 개체 구현을 만들기

집계가 가능한 개체의 경우 AddRef, ReleaseQueryInterface의 구현은 "제어 알 수 없음"에 위임해야 합니다. 즉, 개체의 일부가 되기 위해서는 AddRef, ReleaseQueryInterfaceIUnknown에서 파생된 다른 개체에 위임해야 합니다. "제어 알 수 없음"은 개체가 생성되면 개체에 제공됩니다. 즉, COleObjectFactory 구현에 제공됩니다. 이것을 구현하면 소량의 오버헤드가 있으며 경우에 따라 바람직하지 않으므로 MFC는 이것을 옵션으로 만듭니다. 개체를 집계할 수 있도록 하려면 개체 생성자에서 CCmdTarget::EnableAggregation을 호출합니다.

개체가 집계를 사용하는 경우에도 올바른 "제어 알 수 없음"을 집계 개체에 전달해야 합니다. 일반적으로 이 IUnknown 포인터는 집계가 생성될 때 개체에 전달됩니다. 예를 들어 CoCreateInstance을 사용하여 만든 개체에 대한 pUnkOuter 매개 변수는 "제어 알 수 없음"입니다. 올바른 "제어 알 수 없음" 포인터는 CCmdTarget::GetControllingUnknown을 호출하여 검색할 수 있습니다. 하지만 해당 함수에서 반환되는 값은 생성자 중 잘못된 값입니다. 이러한 이유로 CCmdTarget::OnCreateAggregates의 재정의에서만 집계를 만들 것을 제안합니다. 여기서 COleObjectFactory 구현에서 만들어진 경우에도 GetControllingUnknown의 반환 값은 안정적입니다.

또한 인위적인 참조 횟수를 추가하거나 해제할 때 개체가 올바른 참조 횟수를 조작하는 것이 중요합니다. 이 경우에 해당하는지 확인하려면 InternalReleaseInternalAddRef 대신 항상 ExternalAddRefExternalRelease를 호출합니다. 집합체를 지원하는 클래스에 InternalRelease 또는 InternalAddRef를 호출하는 경우는 매우 드뭅니다.

참고 자료

자체 인터페이스 정의 또는 기본 인터페이스 맵 메커니즘을 사용해야 하는 OLE 인터페이스의 프레임워크 구현 재정의 같은 OLE의 고급 사용입니다.

이 섹션에서는 이러한 고급 기능을 구현하는 데 사용되는 각 매크로 및 API에 대해 설명합니다.

CCmdTarget::EnableAggregation — 함수 설명

void EnableAggregation();

설명

이 형식의 개체에 대한 OLE 집합체를 지원하려는 경우 파생된 클래스의 생성자에서 이 함수를 호출합니다. 그러면 집계 가능한 개체에 필요한 특수한 IUnknown 구현이 준비됩니다.

CCmdTarget::ExternalQueryInterface — 함수 설명

DWORD ExternalQueryInterface( 
   const void FAR* lpIID, 
   LPVOID FAR* ppvObj 
);

설명

매개 변수

  • lpIID
    IID에 대한 far 포인터(QueryInterface에 대한 첫 번째 인수)

  • ppvObj
    IUnknown*에 대한 포인터(QueryInterface에 대한 두 번째 인수)입니다.

설명

클래스가 구현하는 각 인터페이스에 대한 IUnknown 구현에서 이 함수를 호출합니다. 이 함수는 사용자 개체의 인터페이스 맵을 기반으로 QueryInterface의 표준 데이터 기반 구현을 제공합니다. HRESULT로 반환 값을 캐스팅해야 합니다. 개체가 집계된 경우 이 함수는 로컬 인터페이스 맵을 사용하는 대신 "제어 중인 IUnknown"을 호출합니다.

CCmdTarget::ExternalAddRef — 함수 설명

DWORD ExternalAddRef();

설명

클래스가 구현하는 각 인터페이스에 대한 IUnknown::AddRef 구현에서 이 함수를 호출합니다. 반환 값은 CCmdTarget 개체에 대해 새 참조 횟수를 나타냅니다. 개체가 집계된 경우 이 함수는 로컬 참조 횟수를 조작하는 대신 "제어 중인 IUnknown"을 호출합니다.

CCmdTarget::ExternalRelease — 함수 설명

DWORD ExternalRelease();

설명

클래스가 구현하는 각 인터페이스에 대한 IUnknown::Release 구현에서 이 함수를 호출합니다. 반환 값은 개체에 대해 새 참조 횟수를 나타냅니다. 개체가 집계된 경우 이 함수는 로컬 참조 횟수를 조작하는 대신 "제어 중인 IUnknown"을 호출합니다.

DECLARE_INTERFACE_MAP — 매크로 설명

DECLARE_INTERFACE_MAP

설명

인터페이스 맵이 포함될 CCmdTarget에서 파생되는 클래스에서 이 매크로를 사용합니다. DECLARE_MESSAGE_MAP과 거의 같은 방법으로 사용됩니다. 이 매크로 호출은 일반적으로 헤더(.H) 파일의 클래스 정의에 배치해야 합니다. DECLARE_INTERFACE_MAP가 있는 클래스는 BEGIN_INTERFACE_MAPEND_INTERFACE_MAP 매크로를 사용하여 구현 파일(.CPP)에 있는 인터페이스 맵을 정의해야 합니다.

BEGIN_INTERFACE_PART and END_INTERFACE_PART — 매크로 설명

BEGIN_INTERFACE_PART( 
   localClass,
   iface 
);
END_INTERFACE_PART( 
   localClass 
)

설명

매개 변수

  • localClass
    인터페이스를 구현하는 클래스의 이름

  • iface
    이 클래스가 구현하는 인터페이스의 이름

설명

사용자의 클래스가 구현할 각 인터페이스에는 BEGIN_INTERFACE_PARTEND_INTERFACE_PART 쌍이 있어야 합니다. 이러한 매크로는 사용자가 정의하는 OLE 인터페이스에서 파생된 로컬 클래스뿐 아니라 해당 클래스의 포함된 멤버 변수를 정의합니다. AddRef, 릴리스, 및 QueryInterface 멤버는 자동으로 선언합니다. 구현되는 인터페이스에 속하는 다른 멤버 함수에 대한 선언이 포함되어야 합니다(이러한 선언은 BEGIN_INTERFACE_PARTEND_INTERFACE_PART 매크로 사이에 배치됩니다).

iface 인수는 IAdviseSink 또는 IPersistStorage(또는 사용자 지정 인터페이스)와 같이 구현하려는 OLE 인터페이스입니다.

localClass 인수는 정의될 로컬 클래스의 이름입니다. 'X'는 자동으로 이름에 추가됩니다. 이 명명 규칙은 동일한 이름의 전역 클래스와의 충돌을 방지하기 위해 사용됩니다. 또한 포함된 멤버의 이름은 'm_x' 접두사가 있다는 점을 제외하고 localClass 이름과 같습니다.

예를 들면 다음과 같습니다.

BEGIN_INTERFACE_PART(MyAdviseSink, IAdviseSink)
   STDMETHOD_(void,OnDataChange)(LPFORMATETC, LPSTGMEDIUM);
   STDMETHOD_(void,OnViewChange)(DWORD, LONG);
   STDMETHOD_(void,OnRename)(LPMONIKER);
   STDMETHOD_(void,OnSave)();
   STDMETHOD_(void,OnClose)();
END_INTERFACE_PART(MyAdviseSink)

IAdviseSink에서 파생된 XMyAdviseSink라는 로컬 클래스와 m_xMyAdviseSink.Note라는 선언된 클래스의 멤버를 정의합니다.

참고

STDMETHOD로 시작하는 줄은 OLE2에서 기본적으로 복사되고 약간 수정됩니다.OLE2. H에서 복사하면 해결하기 어려운 오류를 줄일 수 있습니다.

BEGIN_INTERFACE_MAP 및 END_INTERFACE_MAP — 매크로 설명

BEGIN_INTERFACE_MAP( 
   theClass,
   baseClass 
) 
END_INTERFACE_MAP

설명

매개 변수

  • theClass
    인터페이스 맵이 정의되는 클래스입니다.

  • baseClass
    theClass에서 클래스는 시작에서 파생됩니다.

설명

BEGIN_INTERFACE_MAPEND_INTERFACE_MAP 매크로는 인터페이스 맵을 실제로 정의하기 위해 구현 파일을 사용합니다. 구현되는 각 인터페이스에는 하나 이상의 INTERFACE_PART 매크로 호출이 있습니다. 클래스를 사용하는 각 집계에는 하나의 INTERFACE_AGGREGATE 매크로 호출이 있습니다.

INTERFACE_PART — 매크로 설명

INTERFACE_PART( 
   theClass,
   iid, 
   localClass 
)

설명

매개 변수

  • theClass
    인터페이스 맵이 들어 있는 클래스의 이름입니다.

  • iid
    IID는 포함된 클래스에 매핑할 수 있습니다.

  • localClass
    로컬 클래스의 이름('X'보다 작음)입니다.

설명

이 매크로는 BEGIN_INTERFACE_MAP 매크로와 개체가 지원하는 각 인터페이스에 대한 END_INTERFACE_MAP 매크로 사이에 사용됩니다. theClass 및 localClass로 표시된 클래스의 멤버에 IID를 매핑할 수 있습니다. 'm_x'는 localClass에 자동으로 추가됩니다. 하나 이상의 IID이 단일 멤버와 연결될 수 있습니다. "가장 많이 파생된" 인터페이스만 구현하는 중이며 중간 인터페이스도 모두 제공하려는 경우 가장 유용합니다. 이에 대한 좋은 예는 IOleInPlaceFrameWindow 인터페이스입니다. 해당 계층 구조는 다음과 같습니다.

IUnknown
    IOleWindow
        IOleUIWindow
            IOleInPlaceFrameWindow

개체가 IOleInPlaceFrameWindow를 구현하는 경우 클라이언트는 "가장 많이 파생된" 인터페이스 IOleInPlaceFrameWindow(실제 구현 중인 인터페이스) 외에 IOleUIWindow, IOleWindow 또는 IUnknown 등의 인터페이스에서 QueryInterface할 수 있습니다 . 이 문제를 해결하기 위해 둘 이상의 INTERFACE_PART 매크로를 사용하여 모든 기본 인터페이스를 IOleInPlaceFrameWindow 인터페이스로 매핑할 수 있습니다.

클래스 정의 파일의 경우:

BEGIN_INTERFACE_PART(CMyFrameWindow, IOleInPlaceFrameWindow)

클래스 구현 파일의 경우:

BEGIN_INTERFACE_MAP(CMyWnd, CFrameWnd)
    INTERFACE_PART(CMyWnd, IID_IOleWindow, MyFrameWindow)
    INTERFACE_PART(CMyWnd, IID_IOleUIWindow, MyFrameWindow)
    INTERFACE_PART(CMyWnd, IID_IOleInPlaceFrameWindow, MyFrameWindow)
END_INTERFACE_MAP

프레임 워크는 항상 필요하기 때문에 IUnknown를 담당합니다.

INTERFACE_PART — 매크로 설명

INTERFACE_AGGREGATE( 
   theClass,
   theAggr 
)

설명

매개 변수

  • theClass
    인터페이스 맵이 들어 있는 클래스의 이름입니다.

  • theAggr
    집계 멤버 변수의 이름입니다.

설명

이 매크로는 클래스에 집계 개체가 사용된다는 것을 프레임워크에 알리기 위해 사용됩니다. BEGIN_INTERFACE_PARTEND_INTERFACE_PART 매크로 사이에 와야 합니다 집계 개체는 IUnknown에서 파생된 별도 개체입니다. 집계 및 INTERFACE_AGGREGATE 매크로를 사용하면 집계를 지원하는 모든 인터페이스가 개체에서 직접 지원하는 것으로 나타나도록 만들 수 있습니다. theAggr 인수는 단순히 IUnknown에서 직간접적으로 파생된 클래스의 멤버 변수 이름입니다. 모든 INTERFACE_AGGREGATE 매크로는 인터페이스 맵에 놓여 있을 때 INTERFACE_PART 매크로를 따라야 합니다.

참고 항목

기타 리소스

번호별 기술 참고 사항

범주별 기술 참고 사항