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

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

비밀번호

댓글 작성자
 




... 16  17  18  19  20  [21]  22  23  24  25  26  27  28  29  30  ...
NoWriterDateCnt.TitleFile(s)
13126정성태9/15/20227476.NET Framework: 2049. C# 11 - 정적 메서드에 대한 delegate 처리 시 cache 적용
13125정성태9/14/20227718.NET Framework: 2048. C# 11 - 구조체 필드의 자동 초기화(auto-default structs)
13124정성태9/13/20227507.NET Framework: 2047. Golang, Python, C#에서의 CRC32 사용
13123정성태9/8/20227861.NET Framework: 2046. C# 11 - 멤버(속성/필드)에 지정할 수 있는 required 예약어 추가
13122정성태8/26/20227831.NET Framework: 2045. C# 11 - 메서드 매개 변수에 대한 nameof 지원
13121정성태8/23/20225668C/C++: 157. Golang - 구조체의 slice 필드를 Reflection을 이용해 변경하는 방법
13120정성태8/19/20227313Windows: 209. Windows NT Service에서 UI를 다루는 방법 [3]
13119정성태8/18/20226879.NET Framework: 2044. .NET Core/5+ 프로젝트에서 참조 DLL이 보관된 공통 디렉터리를 지정하는 방법
13118정성태8/18/20225663.NET Framework: 2043. WPF Color의 기본 색 영역은 (sRGB가 아닌) scRGB [2]
13117정성태8/17/20227885.NET Framework: 2042. C# 11 - 파일 범위 내에서 유효한 타입 정의 (File-local types)파일 다운로드1
13116정성태8/4/20228335.NET Framework: 2041. C# - Socket.Close 시 Socket.Receive 메서드에서 예외가 발생하는 문제파일 다운로드1
13115정성태8/3/20228716.NET Framework: 2040. C# - ValueTask와 Task의 성능 비교 [1]파일 다운로드1
13114정성태8/2/20228883.NET Framework: 2039. C# - Task와 비교해 본 ValueTask 사용법파일 다운로드1
13113정성태7/31/20228205.NET Framework: 2038. C# 11 - Span 타입에 대한 패턴 매칭 (Pattern matching on ReadOnlySpan<char>)
13112정성태7/30/20228568.NET Framework: 2037. C# 11 - 목록 패턴(List patterns) [1]파일 다운로드1
13111정성태7/29/20228325.NET Framework: 2036. C# 11 - IntPtr/UIntPtr과 nint/nuint의 통합파일 다운로드1
13110정성태7/27/20228382.NET Framework: 2035. C# 11 - 새로운 연산자 ">>>" (Unsigned Right Shift)파일 다운로드1
13109정성태7/27/20229784VS.NET IDE: 177. 비주얼 스튜디오 2022를 이용한 (소스 코드가 없는) 닷넷 모듈 디버깅 - "외부 원본(External Sources)" [1]
13108정성태7/26/20227731Linux: 53. container에 실행 중인 Golang 프로세스를 디버깅하는 방법 [1]
13107정성태7/25/20226821Linux: 52. Debian/Ubuntu 계열의 docker container에서 자주 설치하게 되는 명령어
13106정성태7/24/20226616오류 유형: 819. 닷넷 6 프로젝트의 "Conditional compilation symbols" 기본값 오류
13105정성태7/23/20227924.NET Framework: 2034. .NET Core/5+ 환경에서 (프로젝트가 아닌) C# 코드 파일을 입력으로 컴파일하는 방법 - 두 번째 이야기 [1]
13104정성태7/23/202211002Linux: 51. WSL - init에서 systemd로 전환하는 방법
13103정성태7/22/20227493오류 유형: 818. WSL - systemd-genie와 관련한 2가지(systemd-remount-fs.service, multipathd.socket) 에러
13102정성태7/19/20226956.NET Framework: 2033. .NET Core/5+에서는 구할 수 없는 HttpRuntime.AppDomainAppId
13101정성태7/15/202215930도서: 시작하세요! C# 10 프로그래밍
... 16  17  18  19  20  [21]  22  23  24  25  26  27  28  29  30  ...