메모
다음 기술 정보는 온라인 설명서에 처음 포함되었으므로 업데이트되지 않았습니다. 따라서 일부 절차와 항목이 만료되거나 올바르지 않을 수 있습니다. 최신 정보는 온라인 설명서 인덱스의 관심 항목을 검색하는 것이 좋습니다.
OLE 2의 핵심은 "OLE 구성 요소 개체 모델" 또는 COM입니다. COM은 협력 개체가 서로 통신하는 방법에 대한 표준을 정의합니다. 여기에는 개체에 메서드를 디스패치하는 방법을 포함하여 "개체"의 모양에 대한 세부 정보가 포함됩니다. 또한 COM은 모든 COM 호환 클래스가 파생되는 기본 클래스를 정의합니다. 이 기본 클래스는 IUnknown . IUnknown 인터페이스는 C++ 클래스라고 하지만 COM은 하나의 언어에만 해당되지 않습니다. C, PASCAL 또는 COM 개체의 이진 레이아웃을 지원할 수 있는 다른 언어로 구현할 수 있습니다.
OLE는 IUnknown 파생된 모든 클래스를 "인터페이스"라고 합니다. IUnknown 같은 "인터페이스"는 구현을 수행하지 않으므로 이는 중요한 차이점입니다. 개체가 통신하는 프로토콜을 정의하기만 하면 되며, 이러한 구현이 수행하는 작업의 세부 사항은 정의되지 않습니다. 이는 최대의 유연성을 허용하는 시스템에 적합합니다. MFC/C++ 프로그램에 대한 기본 동작을 구현하는 것은 MFC의 작업입니다.
MFC의 IUnknown 구현을 이해하려면 먼저 이 인터페이스가 무엇인지 이해해야 합니다. 간단한 버전의 IUnknown 아래에 정의되어 있습니다.
class IUnknown
{
public:
virtual HRESULT QueryInterface(REFIID iid, void** ppvObj) = 0;
virtual ULONG AddRef() = 0;
virtual ULONG Release() = 0;
};
메모
이 그림에서는 __stdcall 같은 특정 필수 호출 규칙 세부 정보가 제외됩니다.
AddRef 및 Release 멤버 함수는 개체의 메모리 관리를 제어합니다. COM은 참조 계산 체계를 사용하여 개체를 추적합니다. 개체는 C++에서와 같이 직접 참조되지 않습니다. 대신 COM 개체는 항상 포인터를 통해 참조됩니다. 소유자가 개체를 사용하여 작업을 완료할 때 개체를 해제하기 위해 개체의 Release 멤버가 호출됩니다(기존 C++ 개체에 대해 수행되는 것처럼 연산자 삭제를 사용하는 것과 반대). 참조 계산 메커니즘을 사용하면 단일 개체에 대한 여러 참조를 관리할 수 있습니다. AddRef 및 Release 구현하면 개체에 대한 참조 수가 유지됩니다. 개체는 참조 수가 0에 도달할 때까지 삭제되지 않습니다.
AddRef 및 릴리스 구현 관점에서 매우 간단합니다. 간단한 구현은 다음과 같습니다.
ULONG CMyObj::AddRef()
{
return ++m_dwRef;
}
ULONG CMyObj::Release()
{
if (--m_dwRef == 0)
{
delete this;
return 0;
}
return m_dwRef;
}
QueryInterface 멤버 함수는 좀 더 흥미롭습니다. AddRef 및 Release 멤버 함수만 있는 개체를 갖는 것은 그리 흥미롭지 않습니다. IUnknown 제공하는 것보다 더 많은 작업을 수행하도록 개체에 지시하는 것이 좋습니다. 여기서 QueryInterface 유용합니다. 동일한 개체에서 다른 "인터페이스"를 가져올 수 있습니다. 이러한 인터페이스는 일반적으로 IUnknown 파생되며 새 멤버 함수를 추가하여 추가 기능을 추가합니다. COM 인터페이스에는 인터페이스에 선언된 멤버 변수가 없으며 모든 멤버 함수는 순수 가상으로 선언됩니다. 예를 들어
class IPrintInterface : public IUnknown
{
public:
virtual void PrintObject() = 0;
};
IUnknown 만 있는 경우 IPrintInterface를 얻으려면 의 IID를 사용하여 IPrintInterface를 호출하십시오.
IID 인터페이스를 고유하게 식별하는 128비트 숫자입니다. 사용자 또는 OLE가 정의하는 각 인터페이스마다 IID가 있습니다.
pUnkIUnknown 개체에 대한 포인터인 경우 IPrintInterface를 검색하는 코드는 다음과 같습니다.
IPrintInterface* pPrint = NULL;
if (pUnk->QueryInterface(IID_IPrintInterface, (void**)&pPrint) == NOERROR)
{
pPrint->PrintObject();
pPrint->Release();
// release pointer obtained via QueryInterface
}
매우 간단해 보이지만 IPrintInterface와 IUnknown 인터페이스를 모두 지원하는 개체를 구현하는 방법은 이 경우 IPrintInterface가 iUnknown 직접 파생되기 때문에 간단합니다. IPrintInterface를 구현하여 IPrintInterface를 구현하면 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도 구현해야 합니다. IPrintInterface가 IUnknown 인터페이스에서 직접 파생되었기 때문에 이는 간단합니다. 그러나 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 메시지가 메시지 맵으로 래핑되는 방식과 유사한 기술을 사용합니다. 이 기능을 Interface Maps라고 하며 다음 섹션에서 설명합니다.
MFC 인터페이스 맵
MFC/OLE에는 개념 및 실행에서 MFC의 "메시지 맵" 및 "디스패치 맵"과 유사한 "인터페이스 맵"의 구현이 포함되어 있습니다. MFC 인터페이스 맵의 핵심 기능은 다음과 같습니다.
QueryInterface 데이터 기반 구현
또한 인터페이스 맵은 다음과 같은 고급 기능을 지원합니다.
집계 가능한 COM 개체 만들기 지원
COM 개체 구현에서 집계 개체 사용 지원
구현이 연결 가능하고 확장 가능
집계에 대한 자세한 내용은 집계 항목을 참조하세요.
MFC의 인터페이스 맵 지원은 CCmdTarget 클래스에 루팅됩니다.
CCmdTarget "" 참조 수뿐만 아니라 IUnknown 구현과 연결된 모든 멤버 함수(예: 참조 수는 CCmdTarget)입니다. OLE COM을 지원하는 클래스를 만들려면 CCmdTarget 클래스를 파생시키고 CCmdTarget 멤버 함수뿐만 아니라 다양한 매크로를 사용하여 원하는 인터페이스를 구현합니다. MFC의 구현에서는 중첩 클래스를 사용하여 위의 예제와 마찬가지로 각 인터페이스 구현을 정의합니다. 이는 반복 코드 중 일부를 제거하는 여러 매크로뿐만 아니라 IUnknown의 표준 구현을 통해 더 쉽게 수행할 수 있습니다.
인터페이스 맵 기본 사항
MFC의 인터페이스 맵을 사용하여 클래스를 구현하려면
클래스를
CCmdTarget에서 직접 또는 간접적으로 파생시키세요.파생 클래스 정의에서
DECLARE_INTERFACE_MAP함수를 사용합니다.지원하려는 각 인터페이스에 대해 클래스 정의에서 BEGIN_INTERFACE_PART 및 END_INTERFACE_PART 매크로를 사용합니다.
구현 파일에서 BEGIN_INTERFACE_MAP 및 END_INTERFACE_MAP 매크로를 사용하여 클래스의 인터페이스 맵을 정의합니다.
지원되는 각 IID에 대해 BEGIN_INTERFACE_MAP END_INTERFACE_MAP 매크로 간의 INTERFACE_PART 매크로를 사용하여 해당 IID를 클래스의 특정 "부분"에 매핑합니다.
지원하는 인터페이스를 나타내는 중첩된 각 클래스를 구현합니다.
METHOD_PROLOGUE 매크로를 사용하여 부모
CCmdTarget파생 개체에 액세스합니다.AddRef, Release및 QueryInterface는 이러한 함수(
CCmdTarget,ExternalAddRef, 및ExternalRelease)의ExternalQueryInterface구현에 위임할 수 있습니다.
위의 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_PART 및 END_INTERFACE_PART 매크로는 중첩 클래스를 정의합니다. 이 경우 CEditObj 및 CPrintObj라는 이름으로 중첩된 클래스를 정의합니다(X는 "C"로 시작하는 전역 클래스 및 "I"로 시작하는 인터페이스 클래스와 중첩된 클래스를 구분하는 데만 사용됨). 이러한 클래스의 두 개의 중첩 멤버인 m_CEditObj 및 m_CPrintObj 각각 생성됩니다. 매크로는 AddRef, Release및 QueryInterface 함수를 자동으로 선언합니다. 따라서 이 인터페이스와 관련된 함수인 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를 m_CPrintObj에 연결하고, IID_IEditInterface를 m_CEditObj에 각각 연결합니다.
CCmdTarget()의 CCmdTarget::ExternalQueryInterface 구현에서는 이 맵을 사용하여 요청 시 m_CPrintObj m_CEditObj 포인터를 반환합니다.
IID_IUnknown에 대한 항목을 포함할 필요는 없습니다. 프레임워크는 IID_IUnknown이 요청될 때 맵의 첫 번째 인터페이스(이 경우, m_CPrintObj)를 사용합니다.
BEGIN_INTERFACE_PART 매크로가 AddRef, Release 및 QueryInterface 함수를 자동으로 선언했지만 여전히 구현해야 합니다.
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 버전에서 해당 인터페이스의 포함된 특수화를 상속하는 것만으로는 MFC의 기본 제공 OLE 인터페이스 구현을 다시 사용할 수 없습니다. METHOD_PROLOGUE 매크로를 사용하여 포함하는 CCmdTarget파생 객체에 접근하는 것은 파생 객체에서 포함된 객체가 CCmdTarget을/를 가져야 한다는 것을 의미하기 때문에 불가능합니다. 예를 들어 XAdviseSink는 COleClientItem::XAdviseSink 개체의 맨 위에서 특정 오프셋에 의존하므로 COleClientItemMFC의 구현에서 포함된 XMyAdviseSink를 파생시킬 수 없습니다.
메모
그러나 MFC의 기본 동작을 원하는 모든 함수에 대해 MFC 구현에 위임할 수 있습니다. 이 작업은 IOleInPlaceFrame 클래스에서 COleFrameHook(XOleInPlaceFrame)의 MFC 구현에서 수행됩니다(많은 함수에 대해 m_xOleInPlaceUIWindow 위임). 이 디자인은 많은 인터페이스를 구현하는 개체의 런타임 크기를 줄이기 위해 선택되었습니다. 이전 섹션에서 m_pParent 사용한 방식과 같이 백 포인터가 필요하지 않습니다.
집계 및 인터페이스 맵
MFC는 독립 실행형 COM 개체를 지원하는 것 외에도 집계를 지원합니다. 집계 자체는 너무 복잡해서 여기서 논의할 항목이 없습니다. 집계에 대한 자세한 내용은 집계 항목을 참조하세요. 이 참고에서는 프레임워크 및 인터페이스 맵에 기본 제공되는 집계에 대한 지원을 간단히 설명합니다.
집계를 사용하는 방법에는 두 가지가 있습니다. (1) 집계를 지원하는 COM 개체를 사용하고 (2) 다른 개체로 집계할 수 있는 개체를 구현합니다. 이러한 기능을 "집계 개체 사용" 및 "개체 집계 가능 만들기"라고 할 수 있습니다. MFC는 둘 다 지원합니다.
집계 개체 사용
집계 개체를 사용하려면 집계를 QueryInterface 메커니즘에 연결하는 방법이 있어야 합니다. 즉, 집계 개체는 개체의 네이티브 부분인 것처럼 동작해야 합니다. 따라서 중첩된 개체가 IID에 매핑되는 INTERFACE_PART 매크로 외에도 MFC의 인터페이스 맵 메커니즘과 어떻게 연결되는지에 따라 집계 개체를 CCmdTarget 파생 클래스의 일부로 선언할 수도 있습니다. 이렇게 하려면 INTERFACE_AGGREGATE 매크로가 사용됩니다. 이렇게 하면 인터페이스 맵 메커니즘에 통합될 멤버 변수(IUnknown 또는 파생 클래스에 대한 포인터여야 합니다)를 지정할 수 있습니다. 포인터가 NULL이 아닐 때 CCmdTarget::ExternalQueryInterface가 호출되면, 요청된 가 IID 개체 자체가 지원하는 네이티브 IID중 하나가 아닌 경우에, 프레임워크는 자동으로 집계 개체의 CCmdTarget 멤버 함수를 호출합니다.
INTERFACE_AGGREGATE 매크로를 사용하려면
집계 개체에 대한 포인터를 포함하는 멤버 변수(
IUnknown*)를 선언합니다.이름별로 멤버 변수를 참조하는 인터페이스 맵에 INTERFACE_AGGREGATE 매크로를 포함합니다.
특정 시점(일반적으로
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_lpAggrInner 변수는 생성자에서 NULL로 초기화됩니다. 프레임워크는 QueryInterface기본 구현에서 NULL 멤버 변수를 무시합니다.
OnCreateAggregates 실제로 집계 개체를 만드는 데 적합합니다.
COleObjectFactoryMFC 구현 외부에서 개체를 만드는 경우 명시적으로 호출해야 합니다. 집계 가능한 개체를 만드는 방법에 대한 논의가 다뤄질 때, CCmdTarget::OnCreateAggregates 집계를 만드는 이유와 CCmdTarget::GetControllingUnknown의 사용법이 명확해질 것입니다.
이 기술은 집계 개체가 지원하는 모든 인터페이스와 네이티브 인터페이스를 개체에 제공합니다. 집계 객체에서 지원하는 인터페이스 중 일부만 사용하려면 해시태그 CCmdTarget::GetInterfaceHook을 재정의할 수 있습니다. 이렇게 하면 QueryInterface비슷하게 매우 낮은 수준의 후크 기능을 사용할 수 있습니다. 일반적으로 집계에서 지원하는 모든 인터페이스를 원합니다.
개체 구현 집계 가능 만들기
개체를 집계 가능하게 하려면, AddRef, Release, QueryInterface 구현이 "제어용 알 수 없음"에 위임되어야 합니다. 즉, 개체의 일부가 되기 위해서는, AddRef, Release, QueryInterface를 또한 IUnknown에서 파생된 다른 개체에 위임해야 합니다. 이 "알 수 없는 제어"는 개체를 만들 때, 즉 COleObjectFactory구현에 제공됩니다. 이를 구현하면 약간의 오버헤드가 발생하며, 경우에 따라 바람직하지 않은 경우도 있으므로 MFC는 이를 선택적으로 만듭니다. 개체를 집계할 수 있도록 하려면 개체의 생성자에서 CCmdTarget::EnableAggregation 호출합니다.
또한 개체에서 집계를 사용하는 경우 올바른 "알 수 없는 제어"를 집계 개체에 전달해야 합니다. 일반적으로 이 IUnknown 포인터는 집계를 만들 때 개체에 전달됩니다. 예를 들어 pUnkOuter 매개 변수는 CoCreateInstance사용하여 만든 개체에 대해 "알 수 없는 제어"입니다. 올바른 "알 수 없는 제어" 포인터는 CCmdTarget::GetControllingUnknown호출하여 검색할 수 있습니다. 그러나 해당 함수에서 반환된 값은 생성자 중에 유효하지 않습니다.
CCmdTarget::OnCreateAggregates 구현에서 만들어진 경우에도 GetControllingUnknown의 반환 값이 신뢰할 수 있는 COleObjectFactory을 재정의할 때만 집계를 만드는 것이 좋습니다. 이러한 이유로 그렇게 하는 것이 권장됩니다.
또한 인공 참조 수를 추가하거나 해제할 때 개체가 올바른 참조 수를 조작해야 합니다. 이 경우를 확인하려면 항상 ExternalAddRef와 ExternalRelease대신 InternalRelease과 InternalAddRef을 호출하십시오. 집계를 지원하는 클래스에서 InternalRelease 또는 InternalAddRef 호출하는 경우는 드뭅니다.
참조 자료
고유한 인터페이스를 정의하거나 프레임워크의 OLE 인터페이스 구현을 재정의하는 등 OLE를 고급으로 사용하려면 기본 인터페이스 맵 메커니즘을 사용해야 합니다.
이 섹션에서는 이러한 고급 기능을 구현하는 데 사용되는 각 매크로 및 API에 대해 설명합니다.
CCmdTarget::EnableAggregation - 함수 설명
void EnableAggregation();
발언
이 형식의 개체에 대한 OLE 집계를 지원하려면 파생 클래스의 생성자에서 이 함수를 호출합니다. 이렇게 하면 집계 가능한 개체에 필요한 특수 IUnknown 구현이 준비됩니다.
CCmdTarget::ExternalQueryInterface - 함수 설명
DWORD ExternalQueryInterface(
const void FAR* lpIID,
LPVOIDFAR* ppvObj
);
매개 변수
lpIID
IID에 대한 원거리 포인터(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가 있는 클래스는 구현 파일(.CPP)에서 BEGIN_INTERFACE_MAP 및 END_INTERFACE_MAP 매크로를 사용하여 인터페이스 맵을 정의해야 합니다.
BEGIN_INTERFACE_PART 및 END_INTERFACE_PART - 매크로 설명
BEGIN_INTERFACE_PART(localClass, iface);
END_INTERFACE_PART(localClass)
매개 변수
localClass
인터페이스를 구현하는 클래스의 이름입니다.
iface
이 클래스가 구현하는 인터페이스의 이름
발언
클래스가 구현할 각 인터페이스에 대해 BEGIN_INTERFACE_PART 및 END_INTERFACE_PART 쌍이 있어야 합니다. 이러한 매크로는 사용자가 정의하는 OLE 인터페이스에서 파생된 로컬 클래스와 해당 클래스의 포함된 멤버 변수를 정의합니다. AddRef, Release및 QueryInterface 멤버가 자동으로 선언됩니다. 구현되는 인터페이스의 일부인 다른 멤버 함수에 대한 선언을 포함해야 합니다(이러한 선언은 BEGIN_INTERFACE_PART 매크로와 END_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.H에서 복사되어 사소한 수정이 가해졌습니다. OLE2.H에서 복사하면 해결하기 어려운 오류를 줄일 수 있습니다.
BEGIN_INTERFACE_MAP 및 END_INTERFACE_MAP - 매크로 설명
BEGIN_INTERFACE_MAP(theClass, baseClass)
END_INTERFACE_MAP
매개 변수
클래스
인터페이스 맵을 정의할 클래스입니다.
baseClass
클래스로부터이 파생된 클래스입니다.
발언
BEGIN_INTERFACE_MAP 및 END_INTERFACE_MAP 매크로는 구현 파일에서 인터페이스 맵을 실제로 정의하는 데 사용됩니다. 구현되는 각 인터페이스에 대해 하나 이상의 INTERFACE_PART 매크로 호출이 있습니다. 클래스에서 사용하는 각 집계에 대해 하나의 INTERFACE_AGGREGATE 매크로 호출이 있습니다.
INTERFACE_PART — 매크로 설명
INTERFACE_PART(theClass, iid, localClass)
매개 변수
클래스
인터페이스 맵을 포함하는 클래스의 이름입니다.
iid
IID는 포함된 클래스에 매핑될 것입니다.
localClass
로컬 클래스의 이름입니다('X'보다 작음).
발언
이 매크로는 개체가 지원하는 각 인터페이스에 대해 BEGIN_INTERFACE_MAP 매크로와 END_INTERFACE_MAP 매크로 사이에 사용됩니다. 이를 통해 theClass 및 localClass 표시된 클래스의 멤버에 IID를 매핑할 수 있습니다. 'm_x'은 localClass 자동으로 추가됩니다. 둘 이상의 IID 단일 멤버와 연결될 수 있습니다. 이는 "가장 파생된" 인터페이스만 구현하고 모든 중간 인터페이스도 제공하려는 경우에 매우 유용합니다. 이 예제의 좋은 예는 IOleInPlaceFrameWindow 인터페이스입니다. 계층 구조는 다음과 같습니다.
IUnknown
IOleWindow
IOleUIWindow
IOleInPlaceFrameWindow
개체가 IOleInPlaceFrameWindow을 구현하는 경우, 클라이언트는 실제로 구현하는 "가장 파생된" 인터페이스 QueryInterface 외에도 IOleUIWindow, IOleWindow, 또는 IUnknown인터페이스 중 어느 하나에 IOleInPlaceFrameWindow할 수 있습니다. 이 작업을 처리하려면 두 개 이상의 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)
매개 변수
클래스
인터페이스 맵을 포함하는 클래스의 이름입니다.
에그
집계할 멤버 변수의 이름입니다.
발언
이 매크로는 클래스가 집계 개체를 사용하고 있음을 프레임워크에 알리는 데 사용됩니다. BEGIN_INTERFACE_PART 매크로와 END_INTERFACE_PART 매크로 사이에 표시되어야 합니다. 집계 개체는 IUnknown파생된 별도의 개체입니다. 집계 및 INTERFACE_AGGREGATE 매크로를 사용하면 집계에서 지원하는 모든 인터페이스가 개체에서 직접 지원되는 것처럼 보이게 할 수 있습니다. theAggr 인수는 IUnknown 파생된 클래스의 멤버 변수 이름입니다(직접 또는 간접적으로). 모든 INTERFACE_AGGREGATE 매크로는 인터페이스 맵에 배치할 때 INTERFACE_PART 매크로를 따라야 합니다.
참고 항목
숫자로 된 기술 노트
범주별 기술 정보