Microsoft MVP성태의 닷넷 이야기
VC++: 35. COM 이벤트에서 반환값을 가진 콜백 정의 [링크 복사], [링크+제목 복사],
조회: 22458
글쓴 사람
정성태 (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

비밀번호

댓글 작성자
 




... 76  77  78  79  80  81  82  83  84  85  86  87  88  [89]  90  ...
NoWriterDateCnt.TitleFile(s)
11710정성태10/2/201822052.NET Framework: 794. C# - 같은 모양, 다른 값의 한글 자음을 비교하는 호환 분해 [5]
11709정성태9/30/201820411개발 환경 구성: 402. .NET Core 콘솔 응용 프로그램을 docker로 실행/디버깅하는 방법 [1]
11708정성태9/30/201822593개발 환경 구성: 401. .NET Core 콘솔 응용 프로그램을 배포(publish) 시 docker image 자동 생성 [2]파일 다운로드1
11707정성태9/30/201823931오류 유형: 489. ASP.NET Core를 docker에서 실행 시 "Failed with a critical error." 오류 발생 [1]
11706정성태9/29/201820073개발 환경 구성: 400. Synology NAS(DS216+II)에서 실행한 gcc의 Segmentation fault [2]
11705정성태9/29/201820922개발 환경 구성: 399. Synology NAS(DS216+II)에 gcc 컴파일러 설치
11704정성태9/29/201824939기타: 73. Synology NAS 신호음(beep) 끄기 [1]파일 다운로드1
11703정성태9/27/201819683개발 환경 구성: 398. Blazor 환경 구성 후 빌드 속도가 너무 느리다면? [2]
11702정성태9/26/201816889사물인터넷: 44. 넷두이노(Netduino)의 네트워크 설정 방법
11701정성태9/26/201822631개발 환경 구성: 397. 공유기를 일반 허브로 활용하는 방법파일 다운로드1
11700정성태9/21/201820706Graphics: 25. Unity - shader의 직교 투영(Orthographic projection) 행렬(UNITY_MATRIX_P)을 수작업으로 구성
11699정성태9/21/201819197오류 유형: 488. Add-AzureAccount 실행 시 "No subscriptions are associated with the logged in account in Azure Service Management (RDFE)." 오류
11698정성태9/21/201820508오류 유형: 487. 윈도우 성능 데이터를 원격 SQL에 저장하는 경우 "Call to SQLAllocConnect failed with %1." 오류 발생
11697정성태9/20/201819383Graphics: 24. Unity - unity_CameraWorldClipPlanes 내장 변수 의미
11696정성태9/19/201820289.NET Framework: 793. C# - REST API를 이용해 NuGet 저장소 제어파일 다운로드1
11695정성태9/19/201825441Graphics: 23. Unity - shader의 원근 투영(Perspective projection) 행렬(UNITY_MATRIX_P)을 수작업으로 구성
11694정성태9/17/201819656오류 유형: 486. nuget push 호출 시 405 Method Not Allowed 오류 발생
11693정성태9/16/201822844VS.NET IDE: 128. Unity - shader 코드 디버깅 방법
11692정성태9/13/201823129Graphics: 22. Unity - shader의 Camera matrix(UNITY_MATRIX_V)를 수작업으로 구성
11691정성태9/13/201820078VS.NET IDE: 127. Visual C++ / x64 환경에서 inline-assembly를 매크로 어셈블리로 대체하는 방법 - 두 번째 이야기
11690정성태9/13/201823051사물인터넷: 43. 555 타이머의 단안정 모드파일 다운로드1
11689정성태9/13/201822352VS.NET IDE: 126. 디컴파일된 소스에 탐색을 사용하도록 설정(Enable navigation to decompiled sources)
11688정성태9/11/201817702오류 유형: 485. iisreset - The data is invalid. (2147942413, 8007000d) 오류 발생
11687정성태9/11/201819576사물인터넷: 42. 사물인터넷 - 트랜지스터 다중 전압 테스트파일 다운로드1
11686정성태9/8/201818610사물인터넷: 41. 다중 전원의 소스를 가진 회로파일 다운로드1
11685정성태9/6/201818581사물인터넷: 40. 이어폰 소리를 capacitor로 필터링파일 다운로드1
... 76  77  78  79  80  81  82  83  84  85  86  87  88  [89]  90  ...