3. Implementation/COM & ActiveX

C++ 에서 IDispatch 인터페이스를 효율적으로 호출하는 방법

SSKK 2011. 8. 16. 23:02
원제는 "C++에서 MFC나 #import를 사용하지 않고 Excel을 자동화하는 방법" 입니다. 하지만 이 예제는 Excel 이 아니어도 IDispatch 를 구현한 모든 인터페이스에 적용할 수 있는 아주 좋은 예제입니다. 

C++만을 사용하여 Excel을 자동화하는 간단한 Visual C++ 6.0 콘솔 응용 프로그램을 작성하려면 아래의 단계를 수행하십시오.
  1. Visual C++ 6.0을 시작하고 XlCpp라는 Win32 콘솔 응용 프로그램을 새로 만듭니다. "Hello, World!" 응용 프로그램을 선택하고 Finish를 누릅니다.
  2. 생성된 XlCpp.cpp를 열고 main() 함수 앞에 다음 코드를 추가합니다.
#include <ole2.h> // OLE2 Definitions

// AutoWrap() - Automation helper function...
HRESULT AutoWrap(int autoType, VARIANT *pvResult, IDispatch *pDisp, LPOLESTR ptName, int cArgs...) {
    // Begin variable-argument list...
    va_list marker;
    va_start(marker, cArgs);

    if(!pDisp) {
        MessageBox(NULL, "NULL IDispatch passed to AutoWrap()", "Error", 0x10010);
        _exit(0);
    }

    // Variables used...
    DISPPARAMS dp = { NULL, NULL, 0, 0 };
    DISPID dispidNamed = DISPID_PROPERTYPUT;
    DISPID dispID;
    HRESULT hr;
    char buf[200];
    char szName[200];

    
    // Convert down to ANSI
    WideCharToMultiByte(CP_ACP, 0, ptName, -1, szName, 256, NULL, NULL);
    
    // Get DISPID for name passed...
    hr = pDisp->GetIDsOfNames(IID_NULL, &ptName, 1, LOCALE_USER_DEFAULT, &dispID);
    if(FAILED(hr)) {
        sprintf(buf, "IDispatch::GetIDsOfNames(\"%s\") failed w/err 0x%08lx", szName, hr);
        MessageBox(NULL, buf, "AutoWrap()", 0x10010);
        _exit(0);
        return hr;
    }
    
    // Allocate memory for arguments...
    VARIANT *pArgs = new VARIANT[cArgs+1];
    // Extract arguments...
    for(int i=0; i<cArgs; i++) {
        pArgs[i] = va_arg(marker, VARIANT);
    }
    
    // Build DISPPARAMS
    dp.cArgs = cArgs;
    dp.rgvarg = pArgs;
    
    // Handle special-case for property-puts!
    if(autoType & DISPATCH_PROPERTYPUT) {
        dp.cNamedArgs = 1;
        dp.rgdispidNamedArgs = &dispidNamed;
    }
    
    // Make the call!
    hr = pDisp->Invoke(dispID, IID_NULL, LOCALE_SYSTEM_DEFAULT, autoType, &dp, pvResult, NULL, NULL);
    if(FAILED(hr)) {
        sprintf(buf, "IDispatch::Invoke(\"%s\"=%08lx) failed w/err 0x%08lx", szName, dispID, hr);
        MessageBox(NULL, buf, "AutoWrap()", 0x10010);
        _exit(0);
        return hr;
    }
    // End variable-argument section...
    va_end(marker);
    
    delete [] pArgs;
    
    return hr;
}


   // Initialize COM for this thread...
   CoInitialize(NULL);

   // Get CLSID for our server...
   CLSID clsid;
   HRESULT hr = CLSIDFromProgID(L"Excel.Application", &clsid);

   if(FAILED(hr)) {

      ::MessageBox(NULL, "CLSIDFromProgID() failed", "Error", 0x10010);
      return -1;
   }

   // Start server and get IDispatch...
   IDispatch *pXlApp;
   hr = CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER, IID_IDispatch, (void **)&pXlApp);
   if(FAILED(hr)) {
      ::MessageBox(NULL, "Excel not registered properly", "Error", 0x10010);
      return -2;
   }

   // Make it visible (i.e. app.visible = 1)
   {

      VARIANT x;
      x.vt = VT_I4;
      x.lVal = 1;
      AutoWrap(DISPATCH_PROPERTYPUT, NULL, pXlApp, L"Visible", 1, x);
   }

   // Get Workbooks collection
   IDispatch *pXlBooks;
   {
      VARIANT result;
      VariantInit(&result);
      AutoWrap(DISPATCH_PROPERTYGET, &result, pXlApp, L"Workbooks", 0);
      pXlBooks = result.pdispVal;
   }

   // Call Workbooks.Add() to get a new workbook...
   IDispatch *pXlBook;
   {
      VARIANT result;
      VariantInit(&result);
      AutoWrap(DISPATCH_PROPERTYGET, &result, pXlBooks, L"Add", 0);
      pXlBook = result.pdispVal;
   }

   // Create a 15x15 safearray of variants...
   VARIANT arr;
   arr.vt = VT_ARRAY | VT_VARIANT;
   {
      SAFEARRAYBOUND sab[2];
      sab[0].lLbound = 1; sab[0].cElements = 15;
      sab[1].lLbound = 1; sab[1].cElements = 15;
      arr.parray = SafeArrayCreate(VT_VARIANT, 2, sab);
   }

   // Fill safearray with some values...
   for(int i=1; i<=15; i++) {
      for(int j=1; j<=15; j++) {
         // Create entry value for (i,j)
         VARIANT tmp;
         tmp.vt = VT_I4;
         tmp.lVal = i*j;
         // Add to safearray...
         long indices[] = {i,j};
         SafeArrayPutElement(arr.parray, indices, (void *)&tmp);
      }
   }

   // Get ActiveSheet object
   IDispatch *pXlSheet;
   {
      VARIANT result;
      VariantInit(&result);
      AutoWrap(DISPATCH_PROPERTYGET, &result, pXlApp, L"ActiveSheet", 0);
      pXlSheet = result.pdispVal;
   }

   // Get Range object for the Range A1:O15...
   IDispatch *pXlRange;
   {
      VARIANT parm;
      parm.vt = VT_BSTR;
      parm.bstrVal = ::SysAllocString(L"A1:O15");

      VARIANT result;
      VariantInit(&result);
      AutoWrap(DISPATCH_PROPERTYGET, &result, pXlSheet, L"Range", 1, parm);
      VariantClear(&parm);

      pXlRange = result.pdispVal;
   }

   // Set range with our safearray...
   AutoWrap(DISPATCH_PROPERTYPUT, NULL, pXlRange, L"Value", 1, arr);

   // Wait for user...
   ::MessageBox(NULL, "All done.", "Notice", 0x10000);

   // Set .Saved property of workbook to TRUE so we aren't prompted
   // to save when we tell Excel to quit...
   {
      VARIANT x;
      x.vt = VT_I4;
      x.lVal = 1;
      AutoWrap(DISPATCH_PROPERTYPUT, NULL, pXlBook, L"Saved", 1, x);
   }

   // Tell Excel to quit (i.e. App.Quit)
   AutoWrap(DISPATCH_METHOD, NULL, pXlApp, L"Quit", 0);

   // Release references...
   pXlRange->Release();
   pXlSheet->Release();
   pXlBook->Release();
   pXlBooks->Release();
   pXlApp->Release();
   VariantClear(&arr);

   // Uninitialize COM for this thread...
   CoUninitialize();
			
AutoWrap() 함수는 직접 IDispatch를 사용하는 것과 관련된 대부분의 하위 수준 세부 사항을 단순화합니다. 사용자 자신의 구현에서 자유롭게 사용하십시오. 한 가지 주의할 점은 여러 개의 매개 변수를 전달할 때는 역순으로 전달해야 한다는 것입니다. 예를 들면 다음과 같습니다

    VARIANT parm[3];
    parm[0].vt = VT_I4; parm[0].lVal = 1;
    parm[1].vt = VT_I4; parm[1].lVal = 2;
    parm[2].vt = VT_I4; parm[2].lVal = 3;
    AutoWrap(DISPATCH_METHOD, NULL, pDisp, L"call", 3, parm[2], parm[1], parm[0]);