Microsoft MVP성태의 닷넷 이야기
VC++: 35. COM 이벤트에서 반환값을 가진 콜백 정의 [링크 복사], [링크+제목 복사],
조회: 23521
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 1개 있습니다.)
(시리즈 글이 2개 있습니다.)
VC++: 35. COM 이벤트에서 반환값을 가진 콜백 정의
; https://www.sysnet.pe.kr/2/0/588

.NET Framework: 102. COM 개체의 이벤트를 구독하는 코드 제작
; https://www.sysnet.pe.kr/2/0/589





COM 이벤트에서 반환값을 가진 콜백 정의


사실, 이벤트라는 개념에서 반환값을 정의하는 것은 옳지 않습니다. 왜냐하면, 해당 반환값이 어떤 이벤트 핸들러에서 설정되었는지 알 수 없고, 또한 어떤 단계에서 반환값을 설정했다고 해도 그다음 이벤트 들에 대해 별도의 행위를 취할 수 있는 방법이 "기본적"으로는 제공되지 않기 때문입니다. (C#이나, ATL COM이나.)

물론, COM 개체(및 C#)에서 반환값을 아주 처리할 수 없는 것은 아닙니다. 자동 생성된 이벤트 코드 처리에 대한 정의를 바꿔주면 위에서 말했던 문제점들을 원하는 대로 바꿀 수 있습니다. 예를 들어, 다음과 같은 이벤트가 IDL 파일에 정의되었다고 가정하는 경우,

[
    uuid(AED7C2E7-C9C2-4D1B-A760-BE1A79C06EC2),
    helpstring("_ITestObjectControlEvents Interface")
]
dispinterface _ITestObjectControlEvents
{
    properties:
    methods:
       [id(0x01)] void OnMethodCalled( [in] int cookie, [out, retval] int *result ); 
};

VS.NET IDE에서 특정 COM 개체에 위의 이벤트를 구현하게 되면 다음과 같은 코드가 자동으로 생성됩니다.

[코드 1: 자동 생성된 이벤트 발생 코드]
HRESULT Fire_OnMethodCalled( int cookie,  int * result)
{
    HRESULT hr = S_OK;
    T * pThis = static_cast<T *>(this);
    int cConnections = m_vec.GetSize();

    for (int iConnection = 0; iConnection < cConnections; iConnection++)
    {
        pThis->Lock();
        CComPtr<IUnknown> punkConnection = m_vec.GetAt(iConnection);
        pThis->Unlock();

        IDispatch * pConnection = static_cast<IDispatch *>(punkConnection.p);

        if (pConnection)
        {
            CComVariant avarParams[1];
            avarParams[0] = cookie;
            CComVariant varResult;
            if(result)
            {
                varResult.byref = result;
                varResult.vt = ;
            }

            DISPPARAMS params = { avarParams, NULL, 1, 0 };
            hr = pConnection->Invoke(0x01, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &params, &varResult, NULL, NULL);
        }
    }
    return hr;
}

현재 VS.NET 2008에서 테스트해 보면, 위의 "varResult.vt = ;"와 같은 코드가 생성이 됩니다. 물론, ^^; 컴파일 오류가 발생하는데요. 이를 방지하기 위해서는 다음과 같이 수작업을 해줘야 합니다.

CComVariant avarParams[1];
avarParams[0] = cookie;
CComVariant varResult;
if(result)
{
    varResult.byref = result;
    varResult.vt = VT_I4 | VT_BYREF ;
}




이렇게 정의된 이벤트를 발생시키는 코드는 대강 다음과 같습니다.

STDMETHOD( DoCallback )()
{
    int result = 0;
    this->Fire_OnMethodCalled(5, &result);

    return S_OK;
}

보시는 것처럼, 만약 클라이언트 측에서 이벤트를 여러개 구독해 두었다면 적절한 반환값을 처리하는 것이 불가능합니다. Invoke를 여러 번 하는 동안, 특정 이벤트에서 반환값을 받았다고 해도, 다른 이벤트에 의해서 다시 재설정 될 여지가 있기 때문입니다.

원하는 데로 처리하기 위해서는 "[코드 1]"의 자동 생성된 이벤트 발생 코드를 변경해 주어야 합니다. 물론, 이러한 변경에 대해서는 다음 번에 자동 생성되는 시점에 영향을 받지 않도록 적절하게 구성을 해줘야 합니다.




여기서 하나 생각해 볼 것이 있다면... 반환값에 대한 처리입니다. 아시겠지만, 반환값은 IDL 구문에서 "[out,retval]" 특성이 지정된 변수에 대해 코드상으로 만들어지는데, VS.NET IDE에서 생성되는 자동화 코드는 이에 대해 적절하게 처리를 하지 못하고 있습니다.

"[코드 1]"에서 생성된 코드만 해도,

if (pConnection)
{
    CComVariant avarParams[1];
    avarParams[0] = cookie;
    CComVariant varResult;
    if(result)
    {
        varResult.byref = result;
        varResult.vt = VT_I4 | VT_BYREF;
    }

    DISPPARAMS params = { avarParams, NULL, 1, 0 };
    hr = pConnection->Invoke(...);
}

아쉽게도, 위와 같은 처리는 맞지 않습니다. 반환값을 받기 위해서는 varResult에 포인터를 넣는 코드에 상관없이 다음과 같은 식으로 처리해 줘야 합니다.

if (pConnection)
{
    CComVariant avarParams[1];
    avarParams[0] = cookie;
    CComVariant varResult;

    DISPPARAMS params = { avarParams, NULL, 1, 0 };
    hr = pConnection->Invoke(..., &varResult, ...);

    *result = varResult.intVal;    
}

얼핏 봐서는 varResult의 byref에 result 포인터 값을 넣어두고 Invoke를 호출했기 때문에 굳이 위와 같이 해주지 않고 "[코드 1]"에서와 같이 처리를 해도 되어야 할 것 같은데요. 이 해답은 Invoke의 정의를 보면 알 수 있습니다.

virtual /* [local] */ HRESULT STDMETHODCALLTYPE Invoke( 
    /* [in] */ DISPID dispIdMember,
    /* [in] */ REFIID riid,
    /* [in] */ LCID lcid,
    /* [in] */ WORD wFlags,
    /* [out][in] */ DISPPARAMS *pDispParams,
    /* [out] */ VARIANT *pVarResult,
    /* [out] */ EXCEPINFO *pExcepInfo,
    /* [out] */ UINT *puArgErr) = 0;

"[out]"으로 명시되어 있죠. 물론, 만약 이 값이 "[in, out]"이었다면 varResult에 미리 포인터를 할당해 두고 호출하는 것이 가능했겠지요.

저 같은 경우에, 최근까지 위와 같은 정도로만 반환값을 받을 수 있을 거라 생각했었는데,,, 얼마 전에 본 코드에서 새로운 방법을 보게 되었습니다.

그 코드에서는 "반환값" 자체도 결국 "인자"의 한 유형이기 때문에 Invoke에 전달되는 인자를 더 늘리는 식으로 해결한 것이었습니다.

if (pConnection)
{
    CComVariant avarParams[2];
    avarParams[1] = cookie;
    CComVariant varResult;
    if(result)
    {
        avarParams[0].byref = result;
        avarParams[0].vt = VT_I4 | VT_BYREF;
    }

    DISPPARAMS params = { avarParams, NULL, 2, 0 };
    hr = pConnection->Invoke(0x01, ..., DISPATCH_METHOD, ¶ms, &varResult, ...);
    int t = 0;
}

재미있는 방법이죠. ^^ params에 대해서는 "[in], [out]"이기 때문에 정상적으로 포인터 result 값이 avarParams[0].byref를 통해 마샬링 되어지고 최종적으로 결과값이 할당이 되어 나오게 됩니다.




지금까지, COM의 이벤트에서 반환값을 처리하는 방법에 대해서 알아봤습니다. 2가지 방법 모두 자동 생성되는 코드를 변경해야 한다는 것을 잊어서는 안됩니다.

다음에는 위의 2가지 방법에 대해 .NET과의 interop을 알아보도록 하겠습니다. ^^



[이 토픽에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 6/28/2021]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 166  167  168  169  170  171  172  173  174  175  [176]  177  178  179  180  ...
NoWriterDateCnt.TitleFile(s)
633정성태12/20/200826085기타: 24. RMClock for x64 [2]
632정성태12/19/200834544기타: 23. D820 - 배터리 없이 바이오스 업데이트 방법 [2]파일 다운로드1
631정성태12/10/200843538VC++: 36. Detours 라이브러리를 이용한 Win32 API - Sleep 호출 가로채기 [3]
630정성태12/9/200824372.NET Framework: 117. WPF - TreeView에서 항목이 펼쳐질 때 Cursors.Wait 사용파일 다운로드1
629정성태12/7/200833578.NET Framework: 116. 소켓 연결 시간 제한
628정성태12/6/200822122.NET Framework: 115. Marshal 타입 관련 2가지 자원 해제 메서드파일 다운로드1
627정성태12/6/200824759VS.NET IDE: 58. VS.NET IDE 팁 - 커서 위치 이동 [1]
626정성태12/6/200824835오류 유형: 65. TF53018: The application tier XXXXXXX is attempting to connect to a data tier with an incompatible version
625정성태12/6/200825032오류 유형: 64. TFS 2008 SP1 설치 - MsiApplyMultiplePatches returned 0x643
624정성태12/5/200825983.NET Framework: 114. WPF 이벤트에 속한 핸들러 확인 [2]파일 다운로드1
623정성태12/4/200830460디버깅 기술: 22. VS.NET SP1 + .NET Framework 소스 코드 디버깅 [2]파일 다운로드1
622정성태12/1/200832748오류 유형: 63. WPF - XamlParseException 대응 방법 [2]
621정성태11/30/200824764Team Foundation Server: 27. TeamBuild + VDPROJ 셋업 프로젝트 [1]
620정성태11/30/200823660디버깅 기술: 21. 올바른 이벤트 예외 정보 출력
619정성태11/30/200823794디버깅 기술: 20. 예외 처리를 방해하는 WPF Modal 대화창파일 다운로드1
618정성태11/29/200824097.NET Framework: 113. 이벤트에 속한 이벤트 핸들러 확인파일 다운로드1
617정성태11/26/200830281.NET Framework: 112. How to Interop DISPPARAMS [2]파일 다운로드2
616정성태11/26/200823567디버깅 기술: 19. C++/CLI - F11 디버깅 시의 변수 초기화파일 다운로드1
615정성태11/9/200833168.NET Framework: 111. WPF - Window, UserControl 클래스 상속 [1]
614정성태11/9/200833132.NET Framework: 110. WPF - 전역 예외 처리 [4]파일 다운로드1
613정성태11/8/200822662.NET Framework: 109. WPF - SystemColors 색상표파일 다운로드1
612정성태11/1/200828025.NET Framework: 108. WPF + WCF 환경에서는 DataContract를 권장 [1]
611정성태10/31/200822050오류 유형: 62. WPF - Visual Studio 2008 비정상 종료
610정성태10/24/200824623Team Foundation Server: 26. TFS 2008 SP1 + SQL Server 2008 설치
609정성태10/24/200828732.NET Framework: 107. WPF - RadioButton 데이터 바인딩 해제 현상파일 다운로드2
608정성태10/23/200822879오류 유형: 61. TFS 연결 오류: TF31003, TF30331 오류
... 166  167  168  169  170  171  172  173  174  175  [176]  177  178  179  180  ...