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

비밀번호

댓글 작성자
 




... 106  107  108  109  110  111  112  [113]  114  115  116  117  118  119  120  ...
NoWriterDateCnt.TitleFile(s)
11100정성태11/7/201628871개발 환경 구성: 304. Wi-Fi Direct 지원 여부 확인 방법 [1]
11099정성태11/7/201630778.NET Framework: 620. C#에서 C/C++ 함수로 콜백 함수를 전달하는 예제 코드파일 다운로드1
11098정성태11/7/201620120오류 유형: 368. 빌드 이벤트에서 robocopy 사용 시 $(TargetDir) 매크로를 지정하는 경우 오류 발생
11097정성태11/7/201623055오류 유형: 367. go install: no install location for directory [...경로...] outside GOPATH
11096정성태11/6/201626838디버깅 기술: 83. PDB 파일을 수동으로 다운로드하는 방법
11095정성태11/6/201623097.NET Framework: 619. C# - Cognitive Services 중의 하나인 Face API를 사용해 얼굴 인식 및 흐림(blur) 효과 적용 [1]파일 다운로드1
11094정성태11/5/201624767VC++: 105. Visual Studio 2013/2015 - Ceemple OpenCV 확장을 이용한 웹캠 영상 출력
11093정성태11/4/201624692웹: 34. Edge 브라우저도 지원하는 클립보드 복사를 위한 자바스크립트 코드
11092정성태11/3/201631618.NET Framework: 618. C# - NAudio를 이용한 MP3 파일 재생 [5]파일 다운로드1
11091정성태11/3/201626320VC++: 104. std::call_once를 이용해 thread-safe한 Singleton 객체 생성파일 다운로드1
11090정성태11/1/201627783VC++: 103. C++ CreateTimerQueue, CreateTimerQueueTimer 예제 코드 [9]파일 다운로드1
11089정성태11/1/201626746디버깅 기술: 82. Windows 10을 위한 Symbol(PDB) 파일 내려받는 방법 [2]
11088정성태11/1/201630822.NET Framework: 617. C# - AForge.NET을 이용한 MP4 동영상 파일 재생 [7]파일 다운로드1
11087정성태11/1/201625202.NET Framework: 616. AForge.Video.FFMPEG를 최신 버전의 ffmpeg 파일로 의존성을 변경하는 방법파일 다운로드1
11086정성태11/1/201619080오류 유형: 366. The Microsoft Passport Container service terminated with the following error: General access denied error
11085정성태10/27/201633461.NET Framework: 615. C# - AForge.NET을 이용한 웹캠 영상 출력 [2]파일 다운로드1
11084정성태10/26/201621416오류 유형: 365. The User Profile Service service failed to the sign-in.
11083정성태10/26/201627984Windows: 131. 윈도우 10에서 사라진 "Adapters and Bindings" 네트워크 우선 순위 조정 기능 [1]
11082정성태10/26/201629918.NET Framework: 614. C# - DateTime.Ticks의 정밀도 [4]파일 다운로드1
11081정성태10/26/201620417오류 유형: 364. You need to fix your Microsoft Account for apps on your other devices to be able to launch apps and continue experiences on this device.
11080정성태10/24/201623565Windows: 130. Windows Server 2016 Nano 서버 설치 방법
11079정성태10/21/201620692Windows: 129. Windows Server 2016 설치 CD에 있는 Convert-WindowsImage.ps1 사용 방법 정리
11078정성태10/21/201622023Windows: 128. Windows Server 2016 Nano 서버 VHD 이미지 만드는 방법 - TP5 기준
11077정성태10/21/201620518오류 유형: 363. Active Directory 서버의 NETLOGON 서비스가 멈췄을 때 발생하는 문제
11076정성태10/21/201620093오류 유형: 362. 윈도우 백업 시 오류 - 0x80780040
11075정성태10/20/201621008Windows: 127. Convert-WindowsImage.ps1 사용 방법 정리
... 106  107  108  109  110  111  112  [113]  114  115  116  117  118  119  120  ...