TN065: OLE 자동화 서버에 대한 이중 인터페이스 지원

참고 항목

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

이 참고에서는 MFC 기반 OLE Automation 서버 애플리케이션에 이중 인터페이스 지원을 추가하는 방법을 설명합니다. ACDUAL 샘플은 이중 인터페이스 지원을 보여 줍니다. 이 노트의 예제 코드는 ACDUAL에서 가져옵니다. 이 메모에 설명된 매크로(예: DECLARE_DUAL_ERRORINFO, DUAL_ERRORINFO_PART 및 IMPLEMENT_DUAL_ERRORINFO)는 ACDUAL 샘플의 일부이며 MFCDUAL.H에서 찾을 수 있습니다.

이중 인터페이스

OLE 자동화를 사용하면 인터페이스, VTBL 인터페이스 또는 이중 인터페이스(둘 다 포함)를 구현 IDispatch 할 수 있지만 Microsoft는 노출된 모든 OLE Automation 개체에 이중 인터페이스를 구현하는 것이 좋습니다. 이중 인터페이스는 -only 또는 VTBL 전용 인터페이스에 비해 IDispatch상당한 이점이 있습니다.

  • 바인딩은 VTBL 인터페이스를 통해 컴파일 시간에 또는 런타임 IDispatch에 수행할 수 있습니다.

  • VTBL 인터페이스를 사용할 수 있는 OLE 자동화 컨트롤러는 향상된 성능을 활용할 수 있습니다.

  • 인터페이스를 사용하는 기존 OLE 자동화 컨트롤러는 IDispatch 계속 작동합니다.

  • VTBL 인터페이스는 C++에서 더 쉽게 호출할 수 있습니다.

  • Visual Basic 개체 지원 기능과의 호환성을 위해서는 이중 인터페이스가 필요합니다.

CCmdTarget 기반 클래스에 이중 인터페이스 지원 추가

이중 인터페이스는 실제로 .에서 IDispatch파생된 사용자 지정 인터페이스일 뿐입니다. 기반 클래스에서 CCmdTarget이중 인터페이스 지원을 구현하는 가장 간단한 방법은 먼저 MFC 및 ClassWizard를 사용하여 클래스에서 일반 디스패치 인터페이스를 구현한 다음 나중에 사용자 지정 인터페이스를 추가하는 것입니다. 대부분의 경우 사용자 지정 인터페이스 구현은 MFC IDispatch 구현에 다시 위임합니다.

먼저 서버에 대한 ODL 파일을 수정하여 개체에 대한 이중 인터페이스를 정의합니다. 이중 인터페이스를 정의하려면 Visual C++ 마법사에서 생성하는 문 대신 DISPINTERFACE 인터페이스 문을 사용해야 합니다. 기존 DISPINTERFACE 문을 제거하는 대신 새 인터페이스 문을 추가합니다. 양식을 유지하면 DISPINTERFACE ClassWizard를 사용하여 개체에 속성과 메서드를 계속 추가할 수 있지만 인터페이스 문에 해당하는 속성과 메서드를 추가해야 합니다.

이중 인터페이스에 대한 인터페이스 문에는 OLEAUTOMATIONDUAL 특성이 있어야 하며 인터페이스는 에서 IDispatch파생되어야 합니다. GUIDGEN 샘플을 사용하여 이중 인터페이스에 대한 IID를 만들 수 있습니다.

[ uuid(0BDD0E81-0DD7-11cf-BBA8-444553540000), // IID_IDualAClick
    oleautomation,
    dual
]
interface IDualAClick : IDispatch
    {
    };

인터페이스 문이 준비되면 메서드 및 속성에 대한 항목 추가를 시작합니다. 이중 인터페이스의 경우 이중 인터페이스의 메서드 및 속성 접근자 함수가 HRESULT를 반환하고 해당 반환 값을 특성[retval,out]이 있는 매개 변수로 전달하도록 매개 변수 목록을 다시 정렬해야 합니다. 속성의 경우 ID가 동일한 읽기(propget) 및 쓰기(propput) 액세스 함수를 모두 추가해야 합니다. 예를 들어:

[propput, id(1)] HRESULT text([in] BSTR newText);
[propget, id(1)] HRESULT text([out, retval] BSTR* retval);

메서드 및 속성이 정의되면 coclass 문의 인터페이스 문에 대한 참조를 추가해야 합니다. 예시:

[ uuid(4B115281-32F0-11cf-AC85-444553540000) ]
coclass Document
{
    dispinterface IAClick;
    [default] interface IDualAClick;
};

ODL 파일이 업데이트되면 MFC의 인터페이스 맵 메커니즘을 사용하여 개체 클래스의 이중 인터페이스에 대한 구현 클래스를 정의하고 MFC 메커니즘 QueryInterface 에서 해당 항목을 만듭니다. ODL의 INTERFACE_PART 인터페이스 문에 있는 각 항목의 블록에 하나의 항목과 디스패치 인터페이스에 대한 항목이 필요합니다. propput 특성이 있는 각 ODL 항목에는 이름이 지정된 put_propertyname함수가 필요합니다. propget 특성이 있는 각 항목에는 이름이 지정된 get_propertyname함수가 필요합니다.

이중 인터페이스에 대한 구현 클래스를 정의하려면 개체 클래스 정의에 블록을 추가 DUAL_INTERFACE_PART 합니다. 예시:

BEGIN_DUAL_INTERFACE_PART(DualAClick, IDualAClick)
    STDMETHOD(put_text)(THIS_ BSTR newText);
    STDMETHOD(get_text)(THIS_ BSTR FAR* retval);
    STDMETHOD(put_x)(THIS_ short newX);
    STDMETHOD(get_x)(THIS_ short FAR* retval);
    STDMETHOD(put_y)(THIS_ short newY);
    STDMETHOD(get_y)(THIS_ short FAR* retval);
    STDMETHOD(put_Position)(THIS_ IDualAutoClickPoint FAR* newPosition);
    STDMETHOD(get_Position)(THIS_ IDualAutoClickPoint FAR* FAR* retval);
    STDMETHOD(RefreshWindow)(THIS);
    STDMETHOD(SetAllProps)(THIS_ short x, short y, BSTR text);
    STDMETHOD(ShowWindow)(THIS);
END_DUAL_INTERFACE_PART(DualAClick)

이중 인터페이스를 MFC의 QueryInterface 메커니즘에 연결하려면 인터페이스 맵에 항목을 추가 INTERFACE_PART 합니다.

BEGIN_INTERFACE_MAP(CAutoClickDoc, CDocument)
    INTERFACE_PART(CAutoClickDoc, DIID_IAClick, Dispatch)
    INTERFACE_PART(CAutoClickDoc, IID_IDualAClick, DualAClick)
END_INTERFACE_MAP()

다음으로 인터페이스의 구현을 입력해야 합니다. 대부분의 경우 기존 MFC IDispatch 구현에 위임할 수 있습니다. 예시:

STDMETHODIMP_(ULONG) CAutoClickDoc::XDualAClick::AddRef()
{
    METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
    return pThis->ExternalAddRef();
}

STDMETHODIMP_(ULONG) CAutoClickDoc::XDualAClick::Release()
{
    METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
    return pThis->ExternalRelease();
}

STDMETHODIMP CAutoClickDoc::XDualAClick::QueryInterface(
    REFIID iid,
    LPVOID* ppvObj)
{
    METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
    return pThis->ExternalQueryInterface(&iid, ppvObj);
}

STDMETHODIMP CAutoClickDoc::XDualAClick::GetTypeInfoCount(
    UINT FAR* pctinfo)
{
    METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
    LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE);
    ASSERT(lpDispatch != NULL);
    return lpDispatch->GetTypeInfoCount(pctinfo);
}

STDMETHODIMP CAutoClickDoc::XDualAClick::GetTypeInfo(
    UINT itinfo,
    LCID lcid,
    ITypeInfo FAR* FAR* pptinfo)
{
    METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
    LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE);
    ASSERT(lpDispatch != NULL);

    return lpDispatch->GetTypeInfo(itinfo, lcid, pptinfo);
}

STDMETHODIMP CAutoClickDoc::XDualAClick::GetIDsOfNames(
    REFIID riid,
    OLECHAR FAR* FAR* rgszNames,
    UINT cNames,
    LCID lcid,
    DISPID FAR* rgdispid)
{
    METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
    LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE);
    ASSERT(lpDispatch != NULL);

    return lpDispatch->GetIDsOfNames(riid, rgszNames, cNames, lcid, rgdispid);
}

STDMETHODIMP CAutoClickDoc::XDualAClick::Invoke(
    DISPID dispidMember,
    REFIID riid,
    LCID lcid,
    WORD wFlags,
    DISPPARAMS FAR* pdispparams,
    VARIANT FAR* pvarResult,
    EXCEPINFO FAR* pexcepinfo,
    UINT FAR* puArgErr)
{
    METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
    LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE);
    ASSERT(lpDispatch != NULL);

    return lpDispatch->Invoke(dispidMember, riid, lcid,
        wFlags, pdispparams, pvarResult, pexcepinfo, puArgErr);
}

개체의 메서드 및 속성 접근자 함수의 경우 구현을 입력해야 합니다. 메서드 및 속성 함수는 일반적으로 ClassWizard를 사용하여 생성된 메서드에 다시 위임할 수 있습니다. 그러나 변수에 직접 액세스하도록 속성을 설정하는 경우 값을 가져와 변수에 넣는 코드를 작성해야 합니다. 예시:

STDMETHODIMP CAutoClickDoc::XDualAClick::put_text(BSTR newText)
{
    METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
    // MFC automatically converts from Unicode BSTR to
    // Ansi CString, if necessary...
    pThis->m_str = newText;
    return NOERROR;
}

STDMETHODIMP CAutoClickDoc::XDualAClick::get_text(BSTR* retval)
{
    METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
    // MFC automatically converts from Ansi CString to
    // Unicode BSTR, if necessary...
    pThis->m_str.SetSysString(retval);
    return NOERROR;
}

이중 인터페이스 포인터 전달

이중 인터페이스 포인터를 전달하는 것은 간단하지 않으며, 특히 호출 CCmdTarget::FromIDispatch해야 하는 경우 그렇습니다. FromIDispatch 는 MFC의 IDispatch 포인터에서만 작동합니다. 이 작업을 해결하는 한 가지 방법은 MFC에서 설정한 원래 IDispatch 포인터를 쿼리하고 해당 포인터를 필요한 함수에 전달하는 것입니다. 예시:

STDMETHODIMP CAutoClickDoc::XDualAClick::put_Position(
    IDualAutoClickPoint FAR* newPosition)
{
    METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
    LPDISPATCH lpDisp = NULL;
    newPosition->QueryInterface(IID_IDispatch, (LPVOID*)&lpDisp);
    pThis->SetPosition(lpDisp);
    lpDisp->Release();
    return NOERROR;
}

이중 인터페이스 메서드를 통해 포인터를 다시 전달하기 전에 MFC IDispatch 포인터에서 이중 인터페이스 포인터로 변환해야 할 수 있습니다. 예시:

STDMETHODIMP CAutoClickDoc::XDualAClick::get_Position(
    IDualAutoClickPoint FAR* FAR* retval)
{
    METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
    LPDISPATCH lpDisp;
    lpDisp = pThis->GetPosition();
    lpDisp->QueryInterface(IID_IDualAutoClickPoint, (LPVOID*)retval);
    return NOERROR;
}

애플리케이션의 형식 라이브러리 등록

AppWizard는 OLE Automation 서버 애플리케이션의 형식 라이브러리를 시스템에 등록하는 코드를 생성하지 않습니다. 형식 라이브러리를 등록하는 다른 방법이 있지만 OLE 형식 정보를 업데이트할 때 애플리케이션에서 형식 라이브러리를 등록하도록 하는 것이 편리합니다. 즉, 애플리케이션이 독립 실행형으로 실행됩니다.

애플리케이션이 독립 실행형으로 실행 될 때마다 애플리케이션의 형식 라이브러리를 등록 하려면:

  • AFXCTL을 포함합니다. 표준의 H에는 헤더 파일인 STDAFX가 포함됩니다. H, 함수의 정의에 액세스합니다 AfxOleRegisterTypeLib .

  • 애플리케이션의 InitInstance 함수에서 호출을 찾습니다 COleObjectFactory::UpdateRegistryAll. 이 호출에 따라 형식 라이브러리의 이름과 함께 형식 라이브러리에 해당하는 LIBID를 지정하는 호출AfxOleRegisterTypeLib을 추가합니다.

    // When a server application is launched stand-alone, it is a good idea
    // to update the system registry in case it has been damaged.
    m_server.UpdateRegistry(OAT_DISPATCH_OBJECT);
    
    COleObjectFactory::UpdateRegistryAll();
    
    // DUAL_SUPPORT_START
        // Make sure the type library is registered or dual interface won't work.
        AfxOleRegisterTypeLib(AfxGetInstanceHandle(),
            LIBID_ACDual,
            _T("AutoClik.TLB"));
    // DUAL_SUPPORT_END
    

형식 라이브러리 변경 내용을 수용하도록 프로젝트 빌드 설정 수정

형식 라이브러리를 다시 작성할 때마다 MkTypLib에서 UUID 정의를 포함하는 헤더 파일을 생성하도록 프로젝트의 빌드 설정을 수정하려면 다음을 수행합니다.

  1. 빌드 메뉴에서 설정 클릭한 다음 각 구성에 대한 파일 목록에서 ODL 파일을 선택합니다.

  2. OLE 형식 탭을 클릭하고 출력 헤더 파일 이름 필드에 파일 이름을 지정합니다. MkTypLib에서 기존 파일을 덮어쓰므로 프로젝트에서 아직 사용되지 않는 파일 이름을 사용합니다. [확인]을 클릭하여 빌드 설정 대화 상자를 닫습니다.

MkTypLib에서 생성된 헤더 파일의 UUID 정의를 프로젝트에 추가하려면 다음을 수행합니다.

  1. 표준에 MkTypLib에서 생성된 헤더 파일인 stdafx.h포함합니다.

  2. 새 파일 INITIIDS를 만듭니다. CPP를 사용하여 프로젝트에 추가합니다. 이 파일에는 OLE2를 포함한 후 MkTypLib에서 생성된 헤더 파일을 포함합니다. H 및 INITGUID. H:

    // initIIDs.c: defines IIDs for dual interfaces
    // This must not be built with precompiled header.
    #include <ole2.h>
    #include <initguid.h>
    #include "acdual.h"
    
  3. 빌드 메뉴에서 설정 클릭한 다음 INITIIDS를 선택합니다. 각 구성에 대한 파일 목록의 CPP입니다.

  4. C++ 탭을 클릭하고 미리 컴파일된 범주 헤더를 클릭한 다음 미리 컴파일된 헤더 라디오 단추를 사용하지 않음을 선택합니다. [확인]을 클릭하여 빌드 설정 대화 상자를 닫습니다.

형식 라이브러리에서 올바른 개체 클래스 이름 지정

Visual C++와 함께 제공된 마법사는 구현 클래스 이름을 잘못 사용하여 OLE-creatable 클래스에 대한 서버의 ODL 파일에서 coclass를 지정합니다. 이 작업은 작동하지만 구현 클래스 이름은 개체 사용자가 사용할 클래스 이름이 아닐 수 있습니다. 올바른 이름을 지정하려면 ODL 파일을 열고 각 coclass 문을 찾은 다음 구현 클래스 이름을 올바른 외부 이름으로 바꿉니다.

coclass 문이 변경되면 MkTypLib에서 생성된 헤더 파일에 있는 CLSID변수 이름이 그에 따라 변경됩니다. 새 변수 이름을 사용하도록 코드를 업데이트해야 합니다.

예외 및 자동화 오류 인터페이스 처리

자동화 개체의 메서드 및 속성 접근자 함수는 예외를 throw할 수 있습니다. 이 경우 이중 인터페이스 구현에서 처리하고 OLE Automation 오류 처리 인터페이스 IErrorInfo를 통해 예외에 대한 정보를 컨트롤러에 다시 전달해야 합니다. 이 인터페이스는 VTBL 인터페이스와 VTBL 인터페이스를 통해 IDispatch 자세한 상황별 오류 정보를 제공합니다. 오류 처리기를 사용할 수 있음을 나타내려면 인터페이스를 ISupportErrorInfo 구현해야 합니다.

오류 처리 메커니즘을 설명하기 위해 표준 디스패치 지원을 구현하는 데 사용되는 ClassWizard 생성 함수가 예외를 throw한다고 가정합니다. MFC의 구현 IDispatch::Invoke 은 일반적으로 이러한 예외를 catch하고 호출을 통해 Invoke 반환되는 EXCEPTINFO 구조로 변환합니다. 그러나 VTBL 인터페이스를 사용하는 경우 직접 예외를 catch해야 합니다. 이중 인터페이스 메서드를 보호하는 예제:

STDMETHODIMP CAutoClickDoc::XDualAClick::put_text(BSTR newText)
{
    METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
    TRY_DUAL(IID_IDualAClick)
    {
        // MFC automatically converts from Unicode BSTR to
        // Ansi CString, if necessary...
        pThis->m_str = newText;
        return NOERROR;
    }
    CATCH_ALL_DUAL
}

CATCH_ALL_DUAL 는 예외가 발생할 때 올바른 오류 코드를 반환합니다. CATCH_ALL_DUAL 는 인터페이스를 사용하여 ICreateErrorInfo MFC 예외를 OLE 자동화 오류 처리 정보로 변환합니다. (예제 CATCH_ALL_DUAL 매크로는 MFCDUAL 파일에 있습니다. ACDUAL 샘플의 H입니다. 예외DualHandleException를 처리하기 위해 호출하는 함수는 MFCDUAL 파일에 있습니다. CPP.) CATCH_ALL_DUAL 는 발생한 예외 유형에 따라 반환할 오류 코드를 결정합니다.

  • COleDispatchException - 이 경우 HRESULT 다음 코드를 사용하여 생성됩니다.

    hr = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, (e->m_wCode + 0x200));
    

    이렇게 하면 예외를 HRESULT 발생시킨 인터페이스에 대한 특정 항목이 만들어집니다. 오류 코드는 표준 OLE 인터페이스에 대한 시스템 정의 HRESULTs와의 충돌을 방지하기 위해 0x200 의해 오프셋됩니다.

  • CMemoryException - 이 경우 E_OUTOFMEMORY 반환됩니다.

  • 다른 예외 - 이 경우 E_UNEXPECTED 반환됩니다.

OLE 자동화 오류 처리기가 사용됨을 나타내려면 인터페이스도 구현 ISupportErrorInfo 해야 합니다.

먼저 자동화 클래스 정의에 코드를 추가하여 지원을 표시합니다 ISupportErrorInfo.

둘째, 자동화 클래스의 인터페이스 맵에 코드를 추가하여 구현 클래스를 MFC의 QueryInterface 메커니즘과 연결 ISupportErrorInfo 합니다. 이 문은 INTERFACE_PART 에 대해 정의된 클래스와 일치합니다 ISupportErrorInfo.

마지막으로 지원 ISupportErrorInfo하도록 정의된 클래스를 구현합니다.

(다음 항목ACDUAL 샘플에는 MFCDUAL.H에 포함된 이러한 세 단계, DUAL_ERRORINFO_PARTDECLARE_DUAL_ERRORINFOIMPLEMENT_DUAL_ERRORINFO모든 단계를 수행하는 데 도움이 되는 세 개의 매크로가 포함되어 있습니다.)

다음 예제에서는 지원 ISupportErrorInfo하도록 정의된 클래스를 구현합니다. CAutoClickDoc 는 자동화 클래스의 이름이며 IID_IDualAClickOLE Automation 오류 개체를 통해 보고된 오류의 원인인 인터페이스의 IID 입니다.

STDMETHODIMP_(ULONG) CAutoClickDoc::XSupportErrorInfo::AddRef()
{
    METHOD_PROLOGUE(CAutoClickDoc, SupportErrorInfo)
    return pThis->ExternalAddRef();
}

STDMETHODIMP_(ULONG) CAutoClickDoc::XSupportErrorInfo::Release()
{
    METHOD_PROLOGUE(CAutoClickDoc, SupportErrorInfo)
    return pThis->ExternalRelease();
}

STDMETHODIMP CAutoClickDoc::XSupportErrorInfo::QueryInterface(
    REFIID iid,
    LPVOID* ppvObj)
{
    METHOD_PROLOGUE(CAutoClickDoc, SupportErrorInfo)
    return pThis->ExternalQueryInterface(&iid, ppvObj);
}

STDMETHODIMP CAutoClickDoc::XSupportErrorInfo::InterfaceSupportsErrorInfo(
    REFIID iid)
{
    METHOD_PROLOGUE(CAutoClickDoc, SupportErrorInfo)
    return (iid == IID_IDualAClick) S_OK : S_FALSE;
}

참고 항목

번호별 기술 참고 사항
범주별 기술 참고 사항