일반적으로 ATL 기반의 ActiveX COM 개체들은 이벤트를 개체가 생성된 스레드 내에서 발생(Firing)시켜야 합니다.
그런데 프로젝트를 해보면 대체로 이벤트라는건 어떤 작업을 비동기적으로 수행하고 그 작업이 완료 되었을 때 발생하는 경우가 많아서 같은 스레드 내에서 윈도를 생성하여 메시지 펌프를 돌리지 않는 이상 보통은 스레드를 사용하게 되죠.

특히, ATL로 만든 COM 개체를 VB에서 쓸때 다른 스레드에서 이벤트 발생을 요청하면 VB 디버깅 모드에서는 잘 되다가도 컴파일하고 실행해보면 잘못된 페이지 오류(IPF : Invalid Page Fault)나 보호 모드 오류(GPF : General Protection Fault)가 발생하면서 런타임 오류 트랩의 기회도 없이 애플리케이션이 다운되어버리는데요, 역시나 MSDN에 답이 있네요.

<참고 링크>
[Q196026] PRB: Firing Event in Second Thread Causes IPF or GPF
[Q280512] SAMPLE : ATLCPImplMT encapsulates ATL event firing across COM apartments
[Donwload] http://download.microsoft.com/download/a/5/5/a552e657-1ecb-453d-9bae-17d0db333337/atlcpimplmt.exe

Q196026에 의하면 VB에서의 IPF, GPF 오류를 방지하기 위해 ATL 프로젝트내에 메시지용 윈도를 하나 생성하고 어떤 이벤트 발생을 윈도 메시지를 통하여 비동기적으로 수행하도록 합니다.

이 방법은 전달할 이벤트가 단순한 통지의 성격을 가질때 매우 편리합니다. 귀찮은 메시지 큐를 만들지 않아도 윈도가 알아서 다 해주고 개발자가 만드는 것보다는 훨씬 신뢰성이 있지요. 하지만 비동기 수행을 요하는 작업이 끝났을 때 어떤 복잡한 데이터를 전달해야 하거나 전달할 이벤트의 종류가 다양할 경우 윈도 프로시져에서 해야할 일이 너무 많아지고 무엇보다 그냥 스레드를 만들어 쓰는 경우가 많다는거죠.

Q280512를 보면 cross apartments, (정확하진 않지만) 쉽게 말해 스레드를 넘어 이벤트를 전달하기 위해 새로운 클래스를 제공합니다.
[Download]를 받아서 압축을 해제하면 ATLCPImplMT.h 라는 파일이 덜렁 나옵니다.
이 파일은 IConnectionPointImplMT 인터페이스를 제공하는데 ATL 프로젝트에 적용하는 방법은 다음과 같습니다.
※참고로 아래의 코드는 VC6기준입니다. .Net을 사용하신다면 위의 참고 링크 본문에 나와있으니 확인하시면 됩니다. 구성의 거의 같으니 어렵지 않게 고칠 수 있습니다.

1. 이 파일을 ATL 프로젝트 폴더에 복사하여 프로젝트에 포함하거나 ATL Include 경로에 복사합니다.
2. ATL 프로젝트의 이벤트 발생을 위한 프록시 클래스-보통 위저드가 생성한 경우 CProxy_이벤트인터페이스명-를 열고 윗부분에 include 합니다.

#include "ATLCPImplMT.h"

3. 프록시 클래스의 상속을 다음과 같이 수정합니다. (붉은색 부분만 바뀝니다.)

<수정전>
class CProxy_IEventFirerName : public IConnectionPointImpl<T, &DIID__IEventFirerName, CComDynamicUnkArray>
<수정후>
class CProxy_IEventFirerName : public IConnectionPointImplMT<T, &DIID__IEventFirerName, CComDynamicUnkArray>

4.  프록시 클래스의 위저드가 생성한 Fire_이벤트이름 함수에서 다음 코드를 찾아 수정합니다.

<수정전>
pT->Lock();
CComPtr<IUnknown> sp = m_vec.GetAt(nConnectionIndex);
pT->Unlock();

<수정후>
CComPtr<IUnknown> sp;
sp.Attach (GetInterfaceAt(nConnectionIndex));
5. 이제 Fire_이벤트이름 함수는 다른 COM 스레드(다른 apartment)에서 호출될 수 있습니다.

아직 끝이 아닙니다. :(
위와 같은 코드를 사용하기 위해서 ATL 프로젝트는 반드시 multi-threaded apartment(MTA) 옵션으로 작성되어야 합니다. 이 옵션은 ATL 프로젝트를 생성할 때 꼭 체크해야 하죠.

그리고 어떤 작업 스레드 내에서 Fire_이벤트이름 함수를 호출할 때 일종의 락을 걸어준다는 기분으로 다음과 같은 코드를 사용해야 합니다.
DWORD WINAPI someWorkerThread(LPVOID lpParameter)
{
   // MTA 작업 스레드를 위한 COM 초기화
   HRESULT hRes = CoInitializeEx(NULL, COINIT_MULTITHREADED);

   //...작업...

   // 이벤트 발생!
   Fire_이벤트이름();

   // COM 해제
   ::CoUninitialize();
}
이제 VB에서 작성한 이벤트를 받아봅니다.
잘되네요 :)
신고
Posted by gongdo
TAG , , ,


티스토리 툴바