131122 C++ COM <-> .NET interop
22 Nov 2013- COM 서버 프로젝트 만들기
- COM 서버에 BO 클래스 추가
- BO 메소드 추가
- BO 구현
- 이벤트 인터페이스에 이벤트함수 정의
- 이벤트 프록시 클래스에서 커넥션 붙은 객체들로 이벤트를 날려줄수 있는 Fire 코드를 만들자
- BO클래스의 문자열 결합 연산이 완료되면 이 이벤트를 발생시켜준다.
- COM 서버 빌드, 등록
- COM 서버의 타입라이브러리로 닷넷용 랩퍼 어셈블리 생성
- COM서버와 통신할 닷넷 응용프로그램 생성
- 닷넷 COM 래퍼클래스를 참조 어셈블리로 추가
- 닷넷 응용프로그램에서 COM 서버 모듈을 프록시 어셈블리를 통해서 사용
- COM과 닷넷과 연동할일이 있어서 dynamic 사용할려다가 좀 조사해보니 tlbimp 유틸리티가 있는것을 지금에야 알았다.
- 이 작업하다가 삽질한것도 있고, 혹시 다른사람들에게도 도움이 될까 정리좀 했다.
COM 서버 프로젝트 만들기
- 프로젝트타입 : ATL 프로젝트
- 이름 : MyComServer
COM 서버에 BO 클래스 추가
- 로직은 간단하게 문자열을 결합하는 기능으로 한다.
- 프로젝트에 클래스추가
- 클래스타입 : ATL Simple Object
- 이름 : MyComBO
- 속성
- Thread Model : Apartment
- Interface : Dual
- Support : Connection Point
- 나중에 COM 서버가 이벤트를 뿌릴려면 Connection Point를 구현해야 한다.
BO 메소드 추가
- 문자열결합 함수를 정의한다.
- ATL 위자드가 만들어주는 자동완성 기능을 이용하자.
- IMyComBO에 대고 메소드 추가 클릭
-
- 메소드
- 메소드명 : StrConcat
- 입력파라미터
- in BSTR bstrA
- in BSTR bstrB
- 입력파라미터
- 출력파라미터
- out retval BSTR* pbstrOut
- 메소드명 : StrConcat
- 메소드
BO 구현
- 문자열결합함수 내부를 구현한다.
- 아래와 같이 코딩한다. 첫코딩이다. --;;
STDMETHODIMP CMyComBO::StrConcat(BSTR bstrA, BSTR bstrB, BSTR* pbstrOut)
{
// TODO: Add your implementation code here
CString strA = bstrA;
CString strB = bstrB;
CString strOut = strA + strB;
*pbstrOut = strOut.AllocSysString();
return S_OK;
}
- 여기까지 구현했으면 COM서버가 동기호출에 대한 기능을 수행할 수 있다
- 이후에는 비동기 이벤트 사용하는 부분이다.
- 문자열 결합이 오래걸리는 작업이라고 생각하고 비동기로 만들어 보기로 한다.
이벤트 인터페이스에 이벤트함수 정의
- 이벤트 인터페이스는 BO클래스 정의할때 Connection Point 체크해서 생성되었지만, 이벤트 함수는 아직 없는 상태이다.
- 역시 ATL 위자드의 도움을 받아 자동완성 코드로 만들자.
-
- 함수명 : StrConcatCompleted ( 문자열 결합이 비동기적으로 완료되었다는 이벤트로 생각 )
- 입력파라미터 :
- in BSTR result
- 이벤트 프록시코드를 BO 클래스에서 사용할 수 있게 상속으로 붙인다.
- 역시 ATL 위자드의 도움을 받아 자동완성 코드로 만들자
- in BSTR result
이벤트 프록시 클래스에서 커넥션 붙은 객체들로 이벤트를 날려줄수 있는 Fire 코드를 만들자
- 이건 ATL 자동완성 될법도 하지만 안된다.
- 프록시 코드에 코딩을 좀 하자
class CProxy_IMyComBOEvents : public IConnectionPointImpl<T, &__uuidof( _IMyComBOEvents ), CComDynamicUnkArray>
{
public:
// WARNING: This class may be regenerated by the wizard
HRESULT Fire_Event(BSTR message)
{
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] = message;
avarParams[0].vt = VT_BSTR;
DISPPARAMS params = { avarParams, NULL, 1, 0 };
hr = pConnection->Invoke(1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, ¶ms, NULL, NULL, NULL);
}
}
return hr;
}
public:
};
- 코드보면 대강 이해되겠지만
- 커넥션 붙은 객체들한테 IDispatch 를 호출해 주는 과정이며, 1번 메소드 즉 사전에 정의한 StrConcat을 호출하게 되는 코드다.
BO클래스의 문자열 결합 연산이 완료되면 이 이벤트를 발생시켜준다.
- BO클래스에서 이 이벤트에 대한 프록시 코드를 사용할 수 있게 해 두었기 때문에 이 프록시 코드를 이용할 것이다.
- 프록시 코드를 구현해둔것을 호출하도록 코드를 추가한다.
STDMETHODIMP CMyComBO::StrConcat(BSTR bstrA, BSTR bstrB, BSTR* pbstrOut)
{
// TODO: Add your implementation code here
CString strA = bstrA;
CString strB = bstrB;
CString strOut = strA + strB;
*pbstrOut = strOut.AllocSysString();
CProxy_IMyComBOEvents<CMyComBO>::Fire_Event(*pbstrOut);
return S_OK;
}
COM 서버 빌드, 등록
- 이렇게 COM 서버를 원하는대로 끝까지 개발했다.
- COM 서버를 빌드, 등록한다.
- 등록은 빌드하면 자동으로 regsvr32로 된다.
- 지금까지가 어려웠고 앞으로는 좀 쉽다.
COM 서버의 타입라이브러리로 닷넷용 랩퍼 어셈블리 생성
- tlbimp 툴을 사용한다.
- MyComServer.tlb을 분석해서 COM과 닷넷응용프로그램 사이의 interop 코드가 담겨 있는 프록시코드를 자동생성해 준다.
"c:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools\tlbimp.exe" "MyComServer\Debug\MyComServer.tlb" /out:MyComServerDotNet.dll
pause
COM서버와 통신할 닷넷 응용프로그램 생성
- 간단하게 닷넷 콘솔 응용프로그램으로 생성한다.
- 프로젝트명 : MyComServerConsummer
- 프로젝트타입 : 닷넷 콘솔 응용프로그램
닷넷 COM 래퍼클래스를 참조 어셈블리로 추가
- MyComServerDotNet.dll 를 참조추가한다.
- 이때 주의할 점!!!
- MyComServerDotNet.dll 을 Embede Interop Types 에서 빼줘야 한다.
- ( 아… 삽질 많았다. )
- 이때 주의할 점!!!
닷넷 응용프로그램에서 COM 서버 모듈을 프록시 어셈블리를 통해서 사용
- 프록시 어셈블리 덕분에 닷넷에서 COM 사용할때 코드도 간결해지고 정적타입을 사용하니 생산성도 좋아진다. ㅠㅠ
class Program
{
static void Main(string[] args)
{
var myCom = new MyComBOClass();
myCom.StrConcatCompleted += _myCom_StrConcatCompleted;
var result = myCom.StrConcat("hello", "world");
Console.WriteLine("myCom.StrConcat result : " + result);
Console.ReadLine();
}
static void _myCom_StrConcatCompleted(string result)
{
Console.WriteLine("_myCom_StrConcatCompleted result : " + result);
}
}