황현동 블로그 개발, 인생, 유우머

131122 C++ COM <-> .NET interop

Tags:




  • 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

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 위자드의 도움을 받아 자동완성 코드로 만들자

이벤트 프록시 클래스에서 커넥션 붙은 객체들로 이벤트를 날려줄수 있는 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, &params, 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);
    }
}