'3. Implementation/COM & ActiveX'에 해당되는 글 19건

  1. 2011.08.16 C++ 에서 IDispatch 인터페이스를 효율적으로 호출하는 방법
  2. 2011.07.28 [펌] ATL ActiveX 만들기
  3. 2011.07.21 "서버 작업 중" 전환 / 다시시도 대화상자 관련
  4. 2009.10.22 Dynamic creation and placement of ActiveX controls
  5. 2009.06.12 __declspec(uuid()), __uuidof(), __declspec(property) 1
  6. 2009.03.09 COM Error 정보 반환
  7. 2009.03.05 형식 라이브러리를 통해 VB에서 C DLL을 보다 쉽게 사용할 수 있도록 하는 방법
  8. 2009.03.05 Processing (Reading and Writing) an XML Document Using XmlLite (IStream 활용) 1
  9. 2008.10.26 FinalConstruct & FinalRelease
  10. 2008.10.11 자동화와 이중 인터페이스
  11. 2008.10.10 MFC Automation 에서 예외 정보 던지기
  12. 2008.10.09 _com_error 클래스와 HRESULT 에러 처리
  13. 2008.10.09 _com_ptr_t 스마트 포인터 클래스
  14. 2008.10.09 __declspec(uuid()) , __uuidof() 와 __declspec(property)
  15. 2008.10.03 In-Process server에서 CoCreateInstacne() 호출 과정
  16. 2008.10.03 DllMain 함수
  17. 2008.10.03 애트리뷰트 기반 프로그래밍에서 애트리뷰트 매커니즘
  18. 2008.10.03 ATL Smart Pointer Class (스마트 포인터 클래스)
  19. 2008.08.18 USES_CONVERSION : ATL and MFC String Conversion Macros
2011. 8. 16. 23:02

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

원제는 "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]);

2011. 7. 28. 03:36

[펌] ATL ActiveX 만들기

2011. 7. 21. 06:07

"서버 작업 중" 전환 / 다시시도 대화상자 관련

ActiveX 로 작업하다 보면 아래 그림과 같은 대화상자를 직면하게 되는 경우가 종종 있습니다. 

"서버 작업 중 - 다른 프로그램이 사용 중이므로 이 작업을 완료할 수 없습니다. [전환]을 선택하여 사용 중인 프로그램을 활성화하여 문제를 해결하십시오"
"Component Request Pending - This action cannot be completed because the other application is busy. Choose 'Switch To' to activate the busy application and correct the problem"



 이는 사용하고자 하는 ActiveX 컨트롤이 다음과 같은 이유에서 발생할 수 있습니다.

  1. ActiveX 컨트롤이 초기화되기 전에 클라이언트 애플리케이션이 이를 사용하려고 시도할 때
  2. 시스템이 느리거나 동시에 많은 애플리케이션이 동작 중이어서 ActiveX 컨트롤이 응답하지 못하는 경우
  3. ActiveX 컨트롤의 설계에 문제가 있어서 응답이 늦도록 제작되어 있을 때

이것과 관련하여 Microsoft 에서는 아래 코드를 이용하여 응답을 기다리는 시간을 길게 설정하면 된다고 하지만 막상 해보니 위 대화상자가 나타나는 시간만 길어질 뿐 별 효과를 얻지 못했습니다.

BOOL CMyApp::InitInstance() {
     // ...
     AfxOleInit();
     COleMessageFilter * pFilter = AfxOleGetMessageFilter();
     pFilter->SetMessagePendingDelay(5000);
     // ...
}
이 방법 보다는 ActiveX 구현 자체를 신경써서 작성해야 합니다. ActiveX 가 로딩되는 때 필요한 작업을 최소화 하고, 서버와 통신하는 등의 작업은 로딩이 다 된 후에 시작하도록 합니다. 또한 TCP 통신과 같이 어떤 연결을 잡고 있는 경우에는 언제든지 바로 disconnect 가 가능하도록 코드를 작성하는 것이 최선입니다. 바로 이 대화상자가 보일 수 있는 원인들(위에 제시된 3가지)을 능동적으로 의식해서 제거해 주는게 최선이라는 말입니다.

참고:  http://support.microsoft.com/kb/602164/ko
2009. 10. 22. 06:54

Dynamic creation and placement of ActiveX controls

progid 를 이용하여 동적으로 ActiveX 를 생성하고 보여주는 방법을 설명한다.


출처 : http://www.codeguru.com/cpp/com-tech/activex/controls/article.php/c5537/
2009. 6. 12. 22:04

__declspec(uuid()), __uuidof(), __declspec(property)

__declspec(uuid()) 와 __uuidof()

Visual C++ 는 __declspec 의 uuid 확장 속성을 사용하여 다음과 같이 COM 객체나 인터페이스에 GUID 를 지정할 수 있게 한다.

 struct __declspec(uuid("4A6AAD5E-41C8-47a6-A5FA-53CBC8506424"))
/* LIBID */ __HelloServerLib;
struct __declspec(uuid("37DB7E59-075D-4c79-A9DC-1D25CADF647A"))
/* CLSID */ Hello;
struct __declspec(uuid("12188C03-A0B6-4c30-B8FE-8B9E39C6D495"))
/* interface */ IHello;


이와 같이 __declspec(uuid()) 를 사용하여 지정된 GUID 값을 꺼내오기 위해 __uuidof() 라는 예약어를 지원한다.

__uuidof(Hello)
__uuidof(IHello)


__declspec(property)

__declspec(property) 확장 속성은 클래스의  '비정적 가상 데이터 멤버(non-static virtual data member)'에 사용할 수 있다. 이 속성이 지정되고 이 변수에 포인터 멤버 선택 연산자('->')를 사용하여 참조할 때 컴파일러는 이들에 대하여 대응되는 함수 호출로 변경한다.

 __declspec(property(get=Getname, put=Putname) _bstr_t name;

위의 __declspec(property) 확장 속성은 포인터 멤버 선택 연산자('->')와 함께 name 식별자가
rvalue 로 사용될 때 get 함수로 지정된 Getname 함수를 호출하고,
lvalue로 사용될 때 put 함수로 지정된 Putname 함수를 호출하게 된다.

wchar * name;
name = (wchar *)pIHello->name;

Visual C++ 는 다음과 같이 호출한다.

 name = (wchar*)pIHello->Getname();

이러한 예는 MSXML2의 스마트 포인터를 사용할 때 자주 나온다. 아래는 그 코드

 MSXML2::IXMLDOMNodePtr spNode;
spNode->text = _T("Hello");
CString strText = (LPCTSTR) spNode->text;

참고 : Component Development with Visual C++ & ATL



2009. 3. 9. 14:00

COM Error 정보 반환

에러정보 리턴

 

l       자동화 객체에서는 IErrorInfo 객체를 생성하여 여기에 발생한 에러에 대한 자세한 정보를 제공

l       자동화 객체는 ISupportErrorInfo 인터페이스를 지원해야 함

 

에러정보 리턴

    에러 정보를 반환하는 방법은 아래와 같다.

l       CreateErrorInfo SetErrorInfo 함수 이용

STDMETHODIMP CAddBack::AddEnd(short newval)

{

    AFX_MANAGE_STATE(AfxGetStaticModuleState());

 

    if(newval < 1) {

        CComPtr<ICreateErrorInfo> spCreateErrInfo;

        CComQIPtr<IErrorInfo, &IID_IErrorInfo> spErrInfo;

 

        HRESULT hr = ::CreateErrorInfo(&spCreateErrInfo);

        if(SUCCEEDED(hr))

        {

            spCreateErrInfo->SetSource(OLESTR("AddBack.AddBack.1"));

            spCreateErrInfo->SetDescription(OLESTR("0 보다큰값을입력하십시오!"));

            spErrInfo = spCreateErrInfo;

            ::SetErrorInfo(0, spErrInfo);

        }

        return E_INVALIDARG;

    }

 

    return S_OK;

}

 

l       CComCoClass::Error 함수 이용

STDMETHODIMP CAddBack::AddEnd2(short newval)

{

    AFX_MANAGE_STATE(AfxGetStaticModuleState());

 

    if(newval < 1)

    {

        return Error("0보다큰값을입력하십시오!", IID_IAddBack, E_INVALIDARG);

    }

 

    return S_OK;

}

 

l       AtlReportError 함수 이용

STDMETHODIMP CAddBack::AddEnd3(short newval)

{

    AFX_MANAGE_STATE(AfxGetStaticModuleState());

 

    if(newval < 1)

    {

        return AtlReportError(GetObjectCLSID(), L"0보다큰값을입력하시오!", IID_IAddBack, E_INVALIDARG);

    }

 

    return S_OK;

}

 

 

 

에러 정보 받기

l       IDispatch::InvokeEXCEPTINFO 인자 사용

l       직접 에러 정보 객체를 사용

1.       에러 발생시 ISupportErrorInfo 인터페이스를 구하여 InterfaceSupportsErrorInfo 함수를 호출 -> IErrorInfo를 지원하는지 여부 확인

2. GetErrorInfo를 호출하여 IErrorInfo 인터페이스를 구한 후 IErrorInfo 인터페이스의 메서드를 호출하여 에러정보를 추출

GetSource, GetDescription, GetGUID, GetHelpFile, GetHelpContext 등 이용

 

출처 : SDS 멀티 캠퍼스 COM & ATL 과정 강좌

2009. 3. 5. 04:50

형식 라이브러리를 통해 VB에서 C DLL을 보다 쉽게 사용할 수 있도록 하는 방법

아래는 VC++로 작성된 COM 개체의 메소드를 작성할 때, Visual Basic에서 보다 쉽게 사용할 수 있도록 enum 형을 제공하는 방법에 대한 것이다.

------------------------------------------------

형식 라이브러리는 자동화에 사용되는 복합 문서 파일(.tlb 파일)입니다. 여기에는 자동화 서버에서 해당 클라이언트에 제공하는 형식, 개체, 모듈 및 인터페이스에 대한 중요한 정보가 들어 있습니다. 다행히도 형식 라이브러리를 사용하기 위해서는 서버가 자동화 서버가 아니어도 됩니다. 사실 대부분의 C DLL은 자동화 서버가 아닙니다. 필요한 작업은 C DLL이 형식 라이브러리에서 함수를 모듈 멤버로 선언하는 것뿐입니다. Visual Basic과 같은 자동화 클라이언트는 이 정보를 읽고 마치 이 정보가 개체인 것처럼 이 정보에 바인딩할 수 있습니다. Visual Basic이 자동으로 모든 작업을 수행하므로 Declare 문을 사용하거나 힘들게 상수를 기억하지 않아도 됩니다.

DLL의 형식 라이브러리를 만들면 여러 가지 이점이 있습니다. 그 중에서 가장 중요한 것은 형식 안정성이 향상된다는 것입니다. 뿐만 아니라 Visual Basic이 초기 바인딩을 사용하여 함수에 자동으로 바인딩하므로 성능이 향상된다는 이점도 있습니다. 반면에 Declare 문은 모두 런타임에 바인딩됩니다. 또한 Visual Basic 프로그래머에 DLL이 표시되는 방법을 더 잘 제어할 수 있습니다. 형식 라이브러리를 사용하면 함수 및 매개 변수에 열거형과 UDT(사용자 정의 형식) 같은 유용한 추가 기능뿐 아니라 Visual Basic 이름을 제공할 수 있습니다.

현재 형식 라이브러리는 IDL(인터페이스 정의 언어)이나 ODL(개체 설명 언어)로 작성된 스크립트를 사용하여 만듭니다. 이러한 스크립트는 Visual Studio와 함께 제공되는 MkTypLib.EXE나 MIDL.EXE를 통해 컴파일됩니다. 프로젝트를 컴파일할 때 DLL 프로젝트와 연결하는 모든 ODL 파일은 MIDL을 통해 자동으로 컴파일되므로 Visual C++에서는 형식 라이브러리를 만드는 작업 중 일부 작업만 수행합니다.
 

단계별 예제 - DLL 및 형식 라이브러리 만들기

  1. Visual C++ 5.0을 열고 파일|새로 만들기를 선택합니다. 프로젝트 탭에서 "Win32 동적 연결 라이브러리"를 선택하고 프로젝트 이름을 "TLBSamp"로 지정합니다.
  2. 파일|새로 만들기를 다시 선택합니다. 파일 탭에서 "C++ 소스 파일"을 선택하고 파일 이름을 "TLBSamp.c"로 지정한 다음 확인을 누릅니다.
  3. 2단계를 다시 반복하고 이번에는 파일 형식으로 "텍스트 파일"을 선택합니다. 파일 이름을 각각 "TLBSamp.def"와 "TLBSamp.odl"로 지정합니다.
  4. 그런 다음 TLBSamp.c에 다음 코드를 추가합니다.
     

    #include <windows.h>

     

    // MyDll_ReverseString -- Reverses the characters of a given string

    void __stdcall MyDll_ReverseString(LPSTR lpString)

    {

        _strrev(lpString);

    }

     

    // MyDLL_Rotate -- Returns bit rotation of 32-bit integer value

    int __stdcall MyDll_Rotate(int nVal, int nDirect, short iNumBits)

    {

        int nRet = 0;

     

        if((iNumBits < 1) || (iNumBits > 31))

            return nRet;

     

        switch(nDirect)

        {

        case 0:

            // Rotate nVal left by iNumBits

            nRet = (((nVal) << (iNumBits)) |

                ((nVal) >> (32-(iNumBits))));

            break;

        case 1:

            // Rotate nVal right by iNumBits

            nRet = (((nVal) >> (iNumBits)) |

                ((nVal) << (32-(iNumBits))));

            break;

        }

     

        return nRet;

    }


  5. 함수를 내보낼 수 있게 하려면 TLBSamp.def에 다음 코드를 추가합니다.
          LIBRARY TLBSamp
          DESCRIPTION 'Microsoft KB Sample DLL'
          EXPORTS
            MyDll_ReverseString
            MyDll_Rotate
    
    						
  6. TLBSamp.odl에 다음 코드를 추가하여 형식 라이브러리에서 함수를 선언합니다.
          
     

    // This is the type library for TLBSamp.dll

    [

        // Use GUIDGEN.EXE to create the UUID that uniquely identifies

        // this library on the user's system. NOTE: This must be done!!

        uuid(F1B9E420-F306-11d1-996A-92FF02C40D32),

        // This helpstring defines how the library will appear in the

        // References dialog of VB.

        helpstring("KB Sample: Make your C DLL More Accessible"),

        // Assume standard English locale.

        lcid(0x0409),

        // Assign a version number to keep track of changes.

        version(1.0)

    ]

    library TLBSample

    {

     

        // Define an Enumeration to use in one of our functions.

        typedef enum tagRotateDirection

        {

            tlbRotateLeft=0,

            tlbRotateRight=1

        }RotateDirection;

     

        // Now define the module that will "declare" your C functions.

        [

            helpstring("Sample functions exported by TLibSamp.dll"),

            version(1.0),

            // Give the name of your DLL here.

            dllname("TLBSamp.dll")

        ]

        module MyDllFunctions

        {

     

            [

                // Add a description for your function that the developer can

                // read in the VB Object Browser.

                helpstring("Returns the reverse of a given string."),

                // Specify the actual DLL entry point for the function. Notice

                // the entry field is like the Alias keyword in a VB Declare

                // statement -- it allows you to specify a more friendly name

                // for your exported functions.

                entry("MyDll_ReverseString")

            ]

            // The [in], [out], and [in, out] keywords tell the Automation

            // client which direction parameters need to be passed. Some

            // calls can be optimized if a function only needs a parameter

            // to be passed one-way.

            void __stdcall ReverseString([in, out] LPSTR sMyString);

     

            [

                helpstring("Rotates a Long value in the given direction."),

                entry("MyDll_Rotate")

            ]

            // Besides specifying more friendly names, you can specify a more

            // friendly type for a parameter. Notice the Direction parameter

            // has been declared with our enumeration. This gives the VB

            // developer easy access to our constant values.

            int __stdcall BitRotate([in] int Value,

                [in] RotateDirection Direction,

                [in] short Bits);

     

        } // End of Module

    }; // End of Library


  7. 빌드 메뉴에서 "모두 다시 빌드"를 선택하여 DLL과 형식 라이브러리를 컴파일합니다. 컴파일이 완료되면 새 DLL(TLBSamp.dll)을 Visual Basic 디렉터리로 복사하여 테스트합니다.
참고: 편의상 형식 라이브러리를 DLL에 리소스로 포함할 수 있습니다. 이렇게 하면 별도의 TLB 파일을 Visual Basic 개발자에게 배포하지 않아도 됩니다.

라이브러리를 리소스로 추가하려면 다음과 같이 하십시오.
  1. 파일|새로 만들기를 선택합니다. 파일 탭에서 "텍스트 파일"을 선택하고 파일 이름을 "TLBSamp.rc"로 지정한 다음 확인을 누릅니다.
  2. 텍스트 창이 나타나면 다음 줄을 추가합니다.

    1 typelib TLBSamp.tlb
  3. 파일을 저장하고 DLL을 다시 컴파일합니다. 컴파일이 완료되면 새 DLL(TLBSamp.dll)을 Visual Basic 디렉터리로 복사하여 테스트하고 이전 파일을 덮어쓸 것인지 묻는 메시지가 나타나면 파일을 덮어씁니다.
 

단계별 예제 - Visual Basic 테스트 응용 프로그램

  1. DLL과 형식 라이브러리를 테스트하려면 Visual Basic 5.0을 열고 표준 프로젝트를 새로 만듭니다. 기본적으로 Form1이 만들어집니다.
  2. 프로젝트 메뉴에서 참조를 선택하여 참조 대화 상자를 불러온 다음 찾아보기를 눌러 새 형식 라이브러리(또는 라이브러리를 리소스로 추가한 경우 DLL)를 찾습니다. 새 형식 라이브러리를 찾은 다음 확인을 누릅니다. Visual Basic은 사용자가 라이브러리를 처음 참조할 때 해당 라이브러리를 자동으로 등록합니다. 참조 목록에서 라이브러리("KB 예제: C DLL을 보다 쉽게 사용할 수 있도록 하기)가 선택되어 있는지 확인한 다음 대화 상자를 닫습니다.
  3. F2 키를 눌러 개체 브라우저를 불러옵니다. 라이브러리(TLBSamp)가 Visual Basic 프로젝트에 추가되었으며, 이제 함수를 네이티브 Visual Basic 함수처럼 호출할 수 있습니다. 개발자가 BitRotate 함수에 Direction 매개 변수를 입력하면 열거형 목록이 드롭다운됩니다.
  4. Form1에 CommandButton을 추가하고 단추의 Click 이벤트에 다음 코드를 추가합니다.
          
     

    ' VBScript source code

    Private Sub Command1_Click()

        Dim n1 As Long, n2 As Long, nTmp As Long

        Dim sTest As String, sMsg As String

     

        sTest = "Hello World!"

        n1 = 100

     

        ReverseString sTest

        sMsg = sTest & " | "

        ReverseString sTest

        sMsg = sMsg & sTest & vbCrLf

     

        nTmp = BitRotate(n1, tlbRotateLeft, 2)

        n2 = BitRotate(nTmp, tlbRotateRight, 2)

        sMsg = sMsg & Str$(n1) & " : " & Str$(nTmp) & " : " & Str$(n2)

     

        MsgBox sMsg

    End Sub


  5. 이제 F5 키를 눌러 IDE에서 vb5allB 프로젝트를 실행합니다.

    참고: 오류 메시지가 나타나면 Visual Basic이 DLL을 찾을 수 없기 때문일 수 있습니다. 테스트 응용 프로그램을 실행하기 전에 Visual Basic 디렉터리나 시스템 경로에 DLL을 복사했는지 확인하십시오

출처 : http://support.microsoft.com/kb/189133

2009. 3. 5. 03:38

Processing (Reading and Writing) an XML Document Using XmlLite (IStream 활용)


This example shows how to process an XML document and create a derivative XML document from it.

This example works with Visual Studio 2005.

C/C++ Source File (XmlLiteReaderWriter.cpp)

 //-----------------------------------------------------------------------
// This file is part of the Windows SDK Code Samples.
//
// Copyright (C) Microsoft Corporation.  All rights reserved.
//
// This source code is intended only as a supplement to Microsoft
// Development Tools and/or on-line documentation.  See these other
// materials for detailed information regarding Microsoft code samples.
//
// THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//-----------------------------------------------------------------------

#include "stdafx.h"
#include <atlbase.h>
#include "xmllite.h"
#include <strsafe.h>

//implement filestream that derives from IStream
class FileStream : public IStream
{
    FileStream(HANDLE hFile)
    {
        _refcount = 1;
        _hFile = hFile;
    }

    ~FileStream()
    {
        if (_hFile != INVALID_HANDLE_VALUE)
        {
            ::CloseHandle(_hFile);
        }
    }

public:
    HRESULT static OpenFile(LPCWSTR pName, IStream ** ppStream, bool fWrite)
    {
        HANDLE hFile = ::CreateFileW(pName, fWrite ? GENERIC_WRITE : GENERIC_READ, FILE_SHARE_READ,
            NULL, fWrite ? CREATE_ALWAYS : OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

        if (hFile == INVALID_HANDLE_VALUE)
            return HRESULT_FROM_WIN32(GetLastError());
       
        *ppStream = new FileStream(hFile);
       
        if(*ppStream == NULL)
            CloseHandle(hFile);
           
        return S_OK;
    }

    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void ** ppvObject)
    {
        if (iid == __uuidof(IUnknown)
            || iid == __uuidof(IStream)
            || iid == __uuidof(ISequentialStream))
        {
            *ppvObject = static_cast<IStream*>(this);
            AddRef();
            return S_OK;
        } else
            return E_NOINTERFACE;
    }

    virtual ULONG STDMETHODCALLTYPE AddRef(void)
    {
        return (ULONG)InterlockedIncrement(&_refcount);
    }

    virtual ULONG STDMETHODCALLTYPE Release(void)
    {
        ULONG res = (ULONG) InterlockedDecrement(&_refcount);
        if (res == 0)
            delete this;
        return res;
    }

    // ISequentialStream Interface
public:
    virtual HRESULT STDMETHODCALLTYPE Read(void* pv, ULONG cb, ULONG* pcbRead)
    {
        BOOL rc = ReadFile(_hFile, pv, cb, pcbRead, NULL);
        return (rc) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
    }

    virtual HRESULT STDMETHODCALLTYPE Write(void const* pv, ULONG cb, ULONG* pcbWritten)
    {
        BOOL rc = WriteFile(_hFile, pv, cb, pcbWritten, NULL);
        return rc ? S_OK : HRESULT_FROM_WIN32(GetLastError());
    }

    // IStream Interface
public:
    virtual HRESULT STDMETHODCALLTYPE SetSize(ULARGE_INTEGER)
    {
        return E_NOTIMPL;  
    }
   
    virtual HRESULT STDMETHODCALLTYPE CopyTo(IStream*, ULARGE_INTEGER, ULARGE_INTEGER*,
        ULARGE_INTEGER*)
    {
        return E_NOTIMPL;  
    }
   
    virtual HRESULT STDMETHODCALLTYPE Commit(DWORD)                                     
    {
        return E_NOTIMPL;  
    }
   
    virtual HRESULT STDMETHODCALLTYPE Revert(void)                                      
    {
        return E_NOTIMPL;  
    }
   
    virtual HRESULT STDMETHODCALLTYPE LockRegion(ULARGE_INTEGER, ULARGE_INTEGER, DWORD)             
    {
        return E_NOTIMPL;  
    }
   
    virtual HRESULT STDMETHODCALLTYPE UnlockRegion(ULARGE_INTEGER, ULARGE_INTEGER, DWORD)           
    {
        return E_NOTIMPL;  
    }
   
    virtual HRESULT STDMETHODCALLTYPE Clone(IStream **)                                 
    {
        return E_NOTIMPL;  
    }

    virtual HRESULT STDMETHODCALLTYPE Seek(LARGE_INTEGER liDistanceToMove, DWORD dwOrigin,
        ULARGE_INTEGER* lpNewFilePointer)
    {
        DWORD dwMoveMethod;

        switch(dwOrigin)
        {
        case STREAM_SEEK_SET:
            dwMoveMethod = FILE_BEGIN;
            break;
        case STREAM_SEEK_CUR:
            dwMoveMethod = FILE_CURRENT;
            break;
        case STREAM_SEEK_END:
            dwMoveMethod = FILE_END;
            break;
        default:  
            return STG_E_INVALIDFUNCTION;
            break;
        }

        if (SetFilePointerEx(_hFile, liDistanceToMove, (PLARGE_INTEGER) lpNewFilePointer,
                             dwMoveMethod) == 0)
            return HRESULT_FROM_WIN32(GetLastError());
        return S_OK;
    }

    virtual HRESULT STDMETHODCALLTYPE Stat(STATSTG* pStatstg, DWORD grfStatFlag)
    {
        if (GetFileSizeEx(_hFile, (PLARGE_INTEGER) &pStatstg->cbSize) == 0)
            return HRESULT_FROM_WIN32(GetLastError());
        return S_OK;
    }

private:
    HANDLE _hFile;
    LONG _refcount;
};

int _tmain(int argc, WCHAR* argv[])
{
    HRESULT hr;
    CComPtr<IStream> pFileStream;
    CComPtr<IStream> pOutFileStream;
    CComPtr<IXmlReader> pReader;
    CComPtr<IXmlWriter> pWriter;
    CComPtr<IXmlReaderInput> pReaderInput;
    CComPtr<IXmlWriterOutput> pWriterOutput;

    if (argc != 3)
    {
        wprintf( L"Usage: XmlReaderWriter.exe name-of-input-file name-of-output-file\n");
        return 0;
    }

    //Open read-only input stream
    if (FAILED(hr = FileStream::OpenFile(argv[1], &pFileStream, FALSE)))
    {
        wprintf(L"Error creating file reader, error is %08.8lx", hr);
        return -1;
    }

    //Open writeable output stream
    if (FAILED(hr = FileStream::OpenFile(argv[2], &pOutFileStream, TRUE)))
    {
        wprintf(L"Error creating file writer, error is %08.8lx", hr);
        return -1;
    }

    if (FAILED(hr = CreateXmlReader(__uuidof(IXmlReader), (void**) &pReader, NULL)))
    {
        wprintf(L"Error creating xml reader, error is %08.8lx", hr);
        return -1;
    }

    pReader->SetProperty(XmlReaderProperty_DtdProcessing, DtdProcessing_Prohibit);

    if (FAILED(hr = CreateXmlReaderInputWithEncodingCodePage(pFileStream, NULL, 65001, FALSE,
                                                             L"c:\temp", &pReaderInput)))
    {
        wprintf(L"Error creating xml reader with encoding code page, error is %08.8lx", hr);
        return -1;
    }

    if (FAILED(hr = CreateXmlWriter(__uuidof(IXmlWriter),(void**) &pWriter, NULL)))
    {
        wprintf(L"Error creating xml writer, error is %08.8lx", hr);
        return -1;
    }

    if (FAILED(hr = CreateXmlWriterOutputWithEncodingCodePage(pOutFileStream, NULL, 65001,
                                                              &pWriterOutput)))
    {
        wprintf(L"Error creating xml reader with encoding code page, error is %08.8lx", hr);
        return -1;
    }

    if (FAILED(hr = pReader->SetInput(pReaderInput)))
    {
        wprintf(L"Error setting input for reader, error is %08.8lx", hr);
        return -1;
    }

    if (FAILED(hr = pWriter->SetOutput(pWriterOutput)))
    {
        wprintf(L"Error setting output for writer, error is %08.8lx", hr);
        return -1;
    }

    if (FAILED(hr = pWriter->SetProperty(XmlWriterProperty_Indent, TRUE)))
    {
        wprintf(L"Error setting indent property in writer, error is %08.8lx", hr);
        return -1;
    }

    XmlNodeType nodeType;
    const WCHAR* pQName;
    const WCHAR* pValue;
    double originalPrice = 0.0;
    double newPrice = 0.0;
    WCHAR priceString[100];
    bool inPrice = FALSE;

    /*
     * This quick start reads an XML of the form
     * <books>
     *   <book name="name of book1">
     *    <price>price-of-book1</price>
     *   </book>
     *   <book name="name of book2">
     *    <price>price-of-book2</price>
     *   </book>
     *  </books>
     *
     * and applies a 25% discount to the price of the book. It also adds originalPrice and discount
     * as two attributes to the element price. If the price is empty or an empty element, it does
     * not make any changes. So the transformed XML will be:
     *
     * <books>
     *   <book name="name of book1">
     *    <price originalPrice="price-of-book1" discount="25%">
     *           discounted-price-of-book1
     *       </price>
     *   </book>
     *   <book name="name of book2">
     *    <price originalPrice="price-of-book2" discount="25%">
     *           discounted-price-of-book2
     *       </price>
     *   </book>
     *  </books>
     *
     */

    //read until there are no more nodes
    while (S_OK == (hr = pReader->Read(&nodeType)))
    {

        // WriteNode will move the reader to the next node, so inside the While Read loop, it is
        // essential that we use WriteNodeShallow, which will not move the reader.
        // If not, a node will be skipped every time you use WriteNode in the while loop.

        switch (nodeType)
        {
        case XmlNodeType_Element:
            {
                if (pReader->IsEmptyElement())
                {
                    if (FAILED(hr = pWriter->WriteNodeShallow(pReader, FALSE)))
                    {
                        wprintf(L"Error writing WriteNodeShallow, error is %08.8lx", hr);
                        return -1;
                    }
                }
                else
                {
                    // if you are not interested in the length it may be faster to use
                    // NULL for the length parameter
                    if (FAILED(hr = pReader->GetQualifiedName(&pQName, NULL)))
                    {
                        wprintf(L"Error reading element name, error is %08.8lx", hr);
                        return -1;
                    }
                   
                    //if the element is price, then discount price by 25%  
                    if (wcscmp(pQName, L"price") == 0)
                    {
                        inPrice = TRUE;
                        if (FAILED(hr = pWriter->WriteNodeShallow(pReader, FALSE)))
                        {
                            wprintf(L"Error writing WriteNodeShallow, error is %08.8lx", hr);
                            return -1;
                        }


                    }
                    else
                    {
                        inPrice = FALSE;
                        if (FAILED(hr = pWriter->WriteNodeShallow(pReader, FALSE)))
                        {
                            wprintf(L"Error writing WriteNodeShallow, error is %08.8lx", hr);
                            return -1;
                        }

                    }
                }
            }
            break;
        case XmlNodeType_Text:
            if (inPrice == TRUE)
            {
                if (FAILED(hr = pReader->GetValue(&pValue, NULL)))
                {
                    wprintf(L"Error reading value, error is %08.8lx", hr);
                    return -1;
                }
               
                //apply discount to the node
                originalPrice = _wtof(pValue);
                newPrice = originalPrice * 0.75;

                if (FAILED(hr = StringCbPrintfW(priceString, sizeof (priceString), L"%f",
                                                newPrice)))
                {
                    wprintf(L"Error using StringCchPrintfW, error is %08.8lx", hr);
                    return -1;
                }

                //write attributes if any
                if (FAILED(hr = pWriter->WriteAttributeString(NULL, L"originalPrice", NULL,
                                                              pValue)))
                {
                    wprintf(L"Error writing WriteAttributeString, error is %08.8lx", hr);
                    return -1;
                }

                if (FAILED(hr = pWriter->WriteAttributeString(NULL, L"discount", NULL, L"25%")))
                {
                    wprintf(L"Error writing WriteAttributeString, error is %08.8lx", hr);
                    return -1;
                }
                if (FAILED(hr = pWriter->WriteString(priceString)))
                {
                    wprintf(L"Error writing WriteString, error is %08.8lx", hr);
                    return -1;
                }
                inPrice = FALSE;
            }
            else
            {
                if (FAILED(hr = pWriter->WriteNodeShallow(pReader, FALSE)))
                {
                    wprintf(L"Error writing WriteNodeShallow, error is %08.8lx", hr);
                    return -1;
                }
            }

            break;
        case XmlNodeType_EndElement:
            if (FAILED(hr = pReader->GetQualifiedName(&pQName, NULL)))
            {
                wprintf(L"Error reading element name, error is %08.8lx", hr);
                return -1;
            }
           
            if (wcscmp(pQName, L"price") == 0)  //if the end element is price, then reset inPrice
                inPrice = FALSE;
           
            if (FAILED(hr = pWriter->WriteFullEndElement()))
            {
                wprintf(L"Error writing WriteFullEndElement, error is %08.8lx", hr);
                return -1;
            }
            break;
        default:
            {
                if (FAILED(hr = pWriter->WriteNodeShallow(pReader, FALSE)))
                {
                    wprintf(L"Error writing WriteNodeShallow, error is %08.8lx", hr);
                    return -1;
                }
            }
            break;
        }

    }
    return 0;
}



This is the XML source file that the Process (Read and Write) an XML Document Using XmlLite example uses.

XML Source File (books.xml)

 <books>
    <book name="My First Book">
     <price>100.00</price>
  </book>
    <book name="A Less Expensive Book">
     <price>25.00</price>
  </book>
</books>

From : MSDN (ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.WIN32COM.v10.en/xmllite/html/30bbde83-de36-44ab-b479-dc91a134f3fd.htm)

2008. 10. 26. 09:23

FinalConstruct & FinalRelease


CComObjectRootEx::FinalConstruct

 

객체의 초기화를 수행한다.

 

클래스 생성자 대신 FinalConstruct에서 초기화를 수행할 시의 장점은 다음과 같다.

 

l  생성자에서는 상태 코드를 반환할 수 없지만, FinalConstruct 에서는 HRESULT 값을 반환값으로 사용할 수 있다. ATL에서 제공하는 표준 클래스 팩토리가 클래스의 객체를 생성할 때 이 반환값은 상세한 에러 정보를 제공할 수 있도록 COM 클라이언트에게 전파된다.

l  가상 함수 매커니즘에서는 클래스의 생성자에서 순수 가상 함수를 호출할 수 없다. 클래스의 생성자에서 가상 함수를 호출하는 것은 상속 계층에서 생성 되는 시점에 정의되어 있는 함수만을 호출한다. ( 순수 가상 함수에 대한 호출은 링크 에러를 초래한다.

 

당신의 클래스는 상속 계층에서 최하위 파생 클래스(the most derived class)가 아닐 때 어떤 기능을 지원하기 위해 ATL에서 제공된 파생 클래스라고 기대한다. 초기화가 그 클래스(ATL)에서 제공하는 기능을 사용할 좋은 기회이지만 ( 이것은 당신의 클래스 객체가 다른 객체에 집합(aggregate)될 때는 반드시 그러하다 ) , 당신의 클래스 생성자는 최하위 파생 클래스의 기능에 접근할 수 있는 방법이 없다. 당신 클래스의 생성 코드는 최하위 파생 클래스가 완전히 생성되기 전에 실행된다.

 

그러나, FinalConstruct 는 최하위 파생 클래스가 완전히 생성된 후에 즉시 호출되기 때문에 가상함수를 호출할 수 있고 ATL에서 제공하는 참조-카운트 구현을 사용할 수 있다.

 

예제

일반적으로 모든 집합된 객체 (aggregated object)를 생성하기 위해서는 CComObjectRootEx를 상속한 클래스 내에서 이 메소드를 재정의한다.

class CMyAggObject : public CComObjectRootEx< ... >

{

   DECLARE_GET_CONTROLLING_UNKNOWN( )

   HRESULT FinalConstruct( )

   {

      return CoCreateInstance(CLSID_SomeServer,

               GetControllingUnknown(), CLSCTX_ALL,

               IID_ISomeServer, &m_pSomeServer);

   }

   ...

};

 

만일 생성이 실패한다면, 에러를 반환할 수 있다. 또한 생성되는 동안 만일 내부 집합 객체(internal aggregated object)가 참조 카운트를 증가시키고 그러고 나서 참조 카운트를 0으로 감소시킬 때 외부 객체 (outer object)가 삭제되는 것을 보호하기 위해 DECLARE_PROTECT_FINAL_CONSTRUCT 매크로를 사용할 수 있다.

 

아래는 집합체를 생성하는 일반적인 방법이다.

l  클래스 객체에 IUnknown 포인터를 추가하고 생성자에서 NULL 로 초기화 한다.

l  집합체(aggregate)를 생성하기 위해 FinalConstruct를 재정의 한다.

l  당신이 정의한 IUnknown 포인터를 COM_INTERFACE_ENTRY_AGGREGATE 매크로의 파라미터로 사용한다.

l  IUnknown 포인터를 해제 하기 위해 FinalRelease를 재정한다.

 

CComObjectRootEx::FinalRelease

 

객체의 클린업을 수행하기 위해 파생 클래스에서 이 메소드를 재정의 할 수 있다.

 

기본적으로, CComObjectRootEx::FinalRelease 는 아무것도 하지 않는다.

 

FinalRelease에서 클린업을 수행하는 것은 클래스의 소멸자에 클린업 코드를 추가하는 것보다 선호된다 왜냐하면 그 객체는 FinalRelease 가 호출되는 시점에도 여전히 완전히 생성되어 있기 때문이다. 이것은 이것은 최하위 파생클래스에서 제공하는 메소드에 대한 안전한 접근을 가능하게 한다. 이것은 삭제되기 전에 모든 집합 객체(any aggregated objects)를 해제하는 경우 특히 중요하다.

 

참고 : MSDN 2005

2008. 10. 11. 01:14

자동화와 이중 인터페이스


IDispatch 인터페이스

 

interface IDispatch : public IUnknown

{

public:

virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(UINT * pctinfo) = 0;

virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(

UINT iTInfo,

LCID lcid,

ITypeInfo ** ppTInfo) = 0;

virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(

REFID riid,

LPOLESTR * rgszNames,

UINT cNames,

LCID lcid,

DISPID * rgDispId) = 0;

virtual HRESULT STDMETHODCALLTYPE Invoke(

DISPID dispidMember,

REFID riid,

LCID lcid,

WORD wFlags,

DISPPARAMS * pDispParams,

VARIANT * pVarResult,

EXCEPTINFO * pExcepInfo,

UINT *puArgErr) = 0;

}

 

Visual Basic 자동화 컨트롤러 애플리케이션 코드에서 어떻게 이들 메서드를 사용하는지 알아 보자.

 

dim obj As Object

 

set obj = CreateObject(“AddBack.AddBack.1”)

obj.Prop = propValue

obj.Method

 

set obj = CreateObject(“AddBack.AddBack.1”)

à CLSIDFromProgID 함수 호출

“AddBack.AddBack.1” -> CLSID 로 변환

à CoCreateInstance 함수 호출

자동화 객체 인스턴스 생성

IDispatch 인터페이스 포인터를 obj에 저장

 

obj.Prop = propValue

à GetIDsOfNames 메서드 호출

PropDISPID 값 구함

à Invoke 함수 호출 (DISPID를 매개변수로)

 

obj.Method

à GetIDsOfNames 메서드 호출

Method DISPID 값 구함

à Invoke 함수 호출 (DISPID를 매개변수로)

 

자동화 객체

IDispatch 인터페이스를 통하여 메서드나 속성을 노출시키기는 하지만 직접 기능을 노출시키는 것은 아님

à 대신, Invoke 함수를 통하여 자동화 컨트롤러에게 메서드와 속성을 노출시킴

 

GetIDsOfNames 함수로 구한 DISPID를 매개변수로 Invoke 함수를 호출한다. 이때 Invoke 함수는 switch 문을 사용하여 매개변수로 넘어온 DISPID에 대응되는 case 문 코드 블록을 실행하게 구현할 수도 있다.

 

디스패치 인터페이스 (dispatch interface, dispinterface)

IDispatch::Invoke 함수에 의하여 구현되는 함수 포인터 배열 dispatch interface 또는 dispinterface 라고 한다.

 

구조를 그림으로 나타내면 다음과 같다.


 

dispinterface COM 인터페이스가 아니다. 디스패치 인터페이스는 COM 인터페이스와 달리 가상 함수 테이블의 처음 세 개의 요소에 QueryInterface, AddRef, Release 함수 포인터가 저장되지 않는다. 그러나 물론, COM 인터페이스를 사용하여 dispinterface를 구현할 수도 있다.

 

이중 인터페이스 (Dual Interface)

자동화 객체는 IDispatch 인터페이스 외에도 이와 동일한 역할을 하는 커스텀 인터페이스를 함께 제공함으로써, 일반적인 COM 인터페이스에 접근할 수 있는 언어에서도 효율적으로 서비스를 사용할 수 있게 된다.

 

이중인터페이스?

IDispatch::Invoke 함수를 통하여 사용할 수 있는 모든 함수를 인터페이스 테이블 즉, 가상 함수 테이블(vtable)을 통해서도 직접 사용할 수 있게 하도록 구현된다.

 

이중 인터페이스의 구조를 그림으로 나타내면 다음과 같다.

 


 

이중 인터페이스는

l  C++ 프로그램이 가상 함수 테이블을 통하여 직접 함수를 호출함으로써 더욱 빨리 실행함 수 있게 함과 동시에,

l  매크로 언어에서는 IDispatch 인터페이스의 Invoke 함수를 통하여 함수를 호출할 수 있게 하는 효율성을 제공한다.


참고 : 전병선의 Component Development with Visual C++ & ATL

 

2008. 10. 10. 17:03

MFC Automation 에서 예외 정보 던지기


MFC Automation 에서 예외와 함께 에러 정보를 던지고자 하는 경우 간단하게 아래의 문장을 사용하면 된다.

 void CErroInfoTestDlgAutoProxy::Test(void)
{
 AFX_MANAGE_STATE(AfxGetAppModuleState());

 // TODO: 여기에 디스패치 처리기를 추가합니다

 AfxThrowOleDispatchException(1001, "Type Mismatch in Parameter. Pass a string array by reference");

}


VB에서 다음과 같이 사용하면 에러 정보를 받을 수 있음

 Sub test()
    On Error Resume Next
    Dim a As Object
   
    Set a = CreateObject("ErroInfoTest.Application")
    a.test
    If Err Then
        MsgBox Err.Description
    End If
End Sub

급한대로 방법만 생각함. CException을 상속하여 할수도 있을것임.

중요한건 과연 예외를 던져야만 하는가? 에 대한 신중한 판단을 내리는게 우선!
2008. 10. 9. 23:49

_com_error 클래스와 HRESULT 에러 처리


#import 지시어가 생성하는 .TLI 파일의 메서드 구현 코드는 다음 예와 같이 HRESULT 값을 검사하여 실패한 경우에 _com_issue_error 또는 _com_issue_errorex 함수를 호출한다.

 

inline HRESULT IHello::sayHello(unsigned short * name, unsigned short ** message) {

HRESULT _hr = raw_sayHello(name, message);

if(FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));

return _hr;

}

 

l  _com_issue_error

단순히 HRESULT 값을 매개변수로 취하여 _com_error 데이터형의 예외를 던짐

l  _com_issue_errorex

HRESULT 값과 함께 인터페이스 포인터와 IID를 매개변수로 취하여 해당 인터페이스가 IErrorInfo 인터페이스를 지원하면 IErrorInfo 객체를 구하여 HRESULT와 함께 IErrorInfo 객체 정보를 포함시켜 _com_error 데이터형의 예외를 던짐

 

예외 처리

_com_ptr_t 스마트 포인터 클래스를 통하여 COM 객체를 사용할 때 발생하는 에러를 처리할 수 있다.

 

try {

IHelloPtr pIHello(__uuidof(Hello));

// ….

pIHello->sayHello(name, &message);

// …

}

catch (_com_error & e) {

cout << e.ErrorMessage << endl;

}

 

_com_error 예외 타입 클래스 멤버

 

l  ErrorMessage : 에러를 설명하는 const TCHAR * 리턴

l  Error : HRESULT , 즉 에러 코드값 리턴

l  Source : IErrorInfo::GetSource 멤버 함수의 호출 결과(_bstr_t) , 에러 소스 리턴

l  Description : IErrorInfo::GetDescription 멤버 함수의 호출 결과(_bstr_t) 리턴

만일 IErrorInfo가 저장되어 있지 않은 경우 빈 _bstr_t를 리턴하며, IErrorInfo 멤버 함수를 호출하는 동안 발생한 실패는 무시된다.

 

예외의 발생

 

다음 예제와 같이 _error_issue_error 함수를 사용

 

IHelloPtr {

HRESULT hr = p.CreateInstance(__uuidof(Hello));

if(FAILED(hr))

_com_issue_error(hr);

}

catch (_com_error & e) {

cout << e.ErrorMessage() << endl;

}

 

참고 : 전병선의 Component Development with Visual C++ & ATL

 

2008. 10. 9. 23:30

_com_ptr_t 스마트 포인터 클래스


_com_ptr_t 스마트 포인터 클래스

           스마트 포인터 기능을 갖고 있는 템플릿 클래스

COMIP.H 헤더 파일에 정의

 

_COM_SMARTPTR_TYPEDEF 매크로를 이용하여 Ptr이 붙는 _com_ptr_t 클래스를 정의

 

_COM_SMARTPTR_TYPEDEF(IHello, __uuidof(IHello));

 

위의 매크로는 다음과 같이 확장 된다.

 

typedef _com_prt_t<_com_IID<IHello, __uuidof(IHello> > IHelloPtr;

 

_com_ptr_t 스마트 포인턴 클래스를 사용하여 COM 객체의 인스턴스 생성 방법

 

1.     생성자 이용

 

struct __declspec(uuid(“6B399539-6141-47ec-BC63-1DEFEFA78290”)) Hello;

 

IHelloPtr pIHello(__uuidof(Hello));

 

또는

 

extern “C” const GUID __declspec(selectany) CLSID_Hello =

{ 0xcb2c32ac, 0xe2fc, 0x4db0, { 0x95, 0x4b, 0x21, 0x50, 0x99, 0xb, 0xda, 0x91 } };

 

IHelloPtr pIHello(CLSID_IHello);

 

또는

 

IHelloPtr pIHello(“HelloServer.Hello.1”);  // prog id

 

2.     CreateInstance 함수 이용

 

HRESULT hr;

IHelloPtr pIHello;

 

hr = pIHello.CreateInstance(__uuidof(Hello));

if (SUCCEEDED(hr))

 

또는

 

hr = pIHello.CreateInstance(CLSID_Hello);

if(SUCCEEDED(hr))

 

hr = pIHello.CreateInsance(“HelloServer.Hello.1”);

if(SUCCEEDED(hr))

 

명시적으로 인터페이스 포인터를 해제하고자 할 경우 다음과 같이 할 수 있다.

 

IHelloPtr pIHello(__uuidof(Hello));

IGoodbyePtr pIGoodbye = pIHello;

 

pIGoodbye = 0;

pIHello = 0;

 

또는

 

pIGoodbye.Release();

pIHello.Release();

 

 참고 : 전병선의 Component Development with Visual C++ & ATL

2008. 10. 9. 23:09

__declspec(uuid()) , __uuidof() 와 __declspec(property)


__declspec(uuid()) __uuidof()

 

__declspecuuid 확장 속성을 사용하여 다음과 같이 COM 객체나 인터페이스에 GUID를 지정할 수 있게 한다.

 

struct __declspec(uuid(“5D5350AE-F8DF-41f1-9395-F9388C402E28”))

/* LIBID */ __HelloServerLib;

struct __declspec(uuid(“71F20FB8-0594-47c7-A99A-3200E110642D”))

/* CLSID */ Hello;

strcut __declspec(uuid(“38EA6580-0C0D-4f14-AC85-E3CBE5103CFB”))

/* interface */ IHello;

 

이와 같이 __declspec(uuid())를 사용하여 지정된 GUID 값을 꺼내오기 위해 __uuidof를 사용할 수 있다.

 

__uuidof(IHello)

 

__declspec(property)

 

클래스의 비정적 가상 데이터 멤버(non static virtual data member)’에 사용할 수 있다. 이 속성이 지정되고 이 변수에 포인터 멤버 선택 연산자(->)를 사용하여 참조할 때 컴파일러는 이들에 대하여 대응되는 함수 호출로 변경한다.

 

__declspec(property(get=Getname,put=Putname())

_bstr_t name;

 

위의 __declspec(property) 확장 속성은 포인터 멤버 선택 연산자(->)와 함께 name 식별자가 rvalue로 사용될 때 get 함수로 지정된 Getname 함수를 호출하고, name 식별자가 lvalue로 사용될 때 put 함수로 지정된 Putname 함수를 호출하게 한다.

 

따라서, 다음과 같이 name 데이터 멤버에서 값을 읽는 코드가 작성될 때,

 

wchar_t * name;

name = (wchar_t*)pIHello->name

 

Visual C++ 컴파일러는 다음과 같이 get 함수로 지정된 Getname 함수를 호출한다.

 

name = (wchar_t*)pIHello->Getname();

 
참고 : 전병선의 Component Development with Visual C++ & ATL

2008. 10. 3. 23:55

In-Process server에서 CoCreateInstacne() 호출 과정


클라이언트 애플리케이션에서 COM 객체의 인스턴스를 생성하기 위해 CoCreateInstance() 함수를 호출하면 내부적으로는 CoGetClassObject() 함수를 호출한다. 이때 CoGetClassObject() 함수는 레지스트리에서 해당 COM 객체의 정보를 찾아 CoLoadLibrary() API 함수를 호출함으로써 해당 COM 객체를 구현한 인-프로세스 서버 DLL을 메모리에 로드한다. 다음에 CoGetClassObject() 함수는 메모리에 로드한 인-프로세스 서버 DLL로부터 DllGetClassObject 익스포트 함수를 호출하고 생성하고자 하는 COM 객체의 CLSID를 넘겨준다.

 

이때 인-프로세스 서버 DLL에 구현된 DllGetClassObject() 함수에서는 인수로 넘어온 CLSID 값에 대응되는 클래스 팩토리 COM 객체의 인스턴스를 생성하고 클래스 팩토리 COM 객체의 IClassFactory 인터페이스 포인터를 구하여 CoGetClassObject() 함수에 리턴해 준다. 다시 CoGetClassObject() 함수는 CoCreateInstance() 함수에게 IClassFactory 인터페이스 포인터를 넘겨주게 되고 CoCreateInstane() 함수는 이 인터페이스 포인터를 통해 IClassFactory 인터페이스의 CreateInstance() 메서드를 호출함으로써 COM 객체의 인스턴스를 생성하게 된다.

 

결국 클라이언트 애플리케이션에서 COM 객체의 인스턴스를 생성하기 위해 CoCreateInstance() 함수를 호출하면 COM 라이브러리는 인-프로세스 서버 COM 컴포넌트 DLL을 메모리에 로드한 후 이 DLLDllGetClassObject() 익스포트 함수를 호출하는 것이다.

참고 : 전병선의 Component Development with Visual C++ & ATL

2008. 10. 3. 12:47

DllMain 함수


Dll이 처음 메모리에 로드되면 DllMain()이라고 하는 진입점(entry-point) 함수가 호출된다.

DllMain() 함수는 다음과 같은 구조를 갖는다.

 

BOOL APIENTRY DllMain( HINSTANCE hModule, DWORD dwReason, LPVOID lpReserved)

{

switch(dwReason){

case DLL_PROCESS_ATTACH:

// DLL 이 프로세스의 주소 영역에 매핑됨

// DLL 초기화 코드

break;

case DLL_THREAD_ATTACH:

// 스레드가 생성됨

break;

case DLL_THREAD_DETACH:

// 스레드가 종료됨

break;

case DLL_PROCESS_DETACH:

// DLL 이 프로세스의 주소 영역에서 매핑이 해제됨

// DLL 종료 처리 코드

break;

}

}

 

흥미로운 것은 DLL을 로드한 프로세스가 새로운 스레드를 생성할 때도 DllMain() 함수가 호출된다. 이 경우에는 dwReason 인수에 DLL_THREAD_ATTACH 값이 전달된다. 마찬가지로 DLL을 로드한 프로세스가 생성된 스레드를 소멸시킬 때도 DllMain() 함수가 호출되면 이 경우에는 DLL_THREAD_DETACH 값이 dwReason 인수에 전달된다.

참고 : 전병선의 Component Development with Visual C++ & ATL

2008. 10. 3. 12:37

애트리뷰트 기반 프로그래밍에서 애트리뷰트 매커니즘


애트리뷰트 매커니즘

 

컴파일러가 애트리뷰트를 만나면,

1.     파싱을 하고 구문 분석

2.     애트리뷰트 공급자(attribute provider) 호출

3.     컴파일 시에 코드를 삽입하거나 수정


 

 

/Fx 옵션으로 소스 코드를 컴파일함으로써 애트리뷰트에 대한 애트리뷰트 공급자가 생성한 코드를 볼 수 있다.

 

/Fx 옵션으로 HelloServer.cpp 소스 코드를 컴파일 할 때 helloserver.mrg.cpp 파일이 생성되고, 이 파일에서 애튜리뷰트 공급자가 생성한 코드를 볼 수 있다.

참고 : 전병선의 Component Development with Visual C++ & ATL
2008. 10. 3. 12:15

ATL Smart Pointer Class (스마트 포인터 클래스)

ATL에서 Visual C++_com_ptr_t 스마트 포인터 템플릿 클래스에 해당되는 클래스로 CComPtrCComQIPtr 템플릿 클래스를 제공.

 

l  자동적으로 AddRefRelease 호출

l  인터페이스 포인터 변수와 이들 클래스의 인스턴스를 구별없이 사용

l  CComQIPtr은 추가로 QueryInterface를 통하여 자동적으로 인터페이스를 질의하는 기능 제공

l  _com_ptr_t와는 달리 COM 객체의 인스턴스를 생성하는 일은 하지 못함

 

1.    CComPtr

 

다음의 생성자를 가짐

template < class T >

class CComPtr

 

CComPtr::CComPtr();

CComPtr::CComPtr(T * lp);

CComPtr::CComPtr(const CComPtr<T>& lp);

 

사용 예

void Foo(IUnknown * p)

{

HRSULT hr;

 

// 생성자 사용 예

CComPtr<IUnknown> pUnk1;

CComPtr<IUnknown> pUnk2(p);

CComPtr<IUnknown> pUnk3(pUnk2);

 

// ->& 연산자 사용 예

CComPtr<IHello> pHello1;

hr = pUnk->QueryInterface(IID_IHello, (LPVOID*)&pHello1);

if(FAILED(hr)) return;

 

// 대입 연산자 사용 예

IHello * pHello;

CComPtr<IHello> pHello2;

 

pHello2 = pHello1;

 

CComPtr<IHello> pHello3;

hr = p->QueryInterface(IID_IHello, (LPVOID*)&pHello;

if(FAILED(hr) return;

 

CComPtr<IHello> pHello4(pHello3);

pHello4 = pHello1;

pHello4 = pHello;

}

 

사용시 주의해야 할 점

n  &연산자를 사용할 때는 해당 CComPtr 클래스 객체의 멤버 포인터 p가 반드시 NULL이어야 한다.

n  -> 연산자를 사용할 때는 해당 CComPtr 클래스 객체의 멤버 포인터가 반드시 NULL이 아니어야 한다.

n  -> 연산자를 사용할 때 Release 메서드를 호출하는 경우

 

pUnk->Relase();

 

// 위 코드는 실제로는 다음 코드와 같다.

 

pUnk.p->Release();

 

이 경우 CComPtr 클래스의 소멸자에서 다시 한번 Release 메서드가 호출되므로 에러가 된다.

 

Release 메서드를 명시적으로 호출하고 한다면 -> 연산자 대신에 반드시 점(.) 연산자를 사용해야 한다.

 

pUnk.Release();

 

위 코드의 경우 CComPtr 클래스의 Release 멤버함수에서 멤버 포인터 p Release 메서드를 호출하고 NULL값을 지정하므로 소멸자에서 두번 Release 메서드를 호출하지 않게 된다.

n  CComPtr 템플릿 클래스는 서로 다른 인터페이스 포인터나 인터페이스 포인터를 캡슐화 하는 CComPtr 클래스 객체를 수용하지 못한다.

 

CComPtr<IUnknown> pUnk;

CComPtr<IHello> pIHello;

// 에러!!! ? 인터페이스가 다름.

CComPtr<IGoodbye> pIGoodbye(pUnk);

pIHello = pUnk;

 

CComPtr의 이러한 문제점을 보완하기 위해 CComQIPtr 템플릿 클래스를 제공

 

 

2.    CComQIPtr

 

CComQIPtr 클래스는 템플릿 매개변수로 인터페이스에 대한 GUID 포인터를 추가로 받아들이는 것을 제외하고는 CComPtr 클래스와 유사하다.

 

CComQIPtr 클래스는 IUnknown 인터페이스 포인터를 매개변수로 받아들이는 생성자와 대입 연산자 멤버 함수를 제공함으로써, 다른 인터페이스 포인터나 다른 인터페이스 포인터를 캡슐화하는 CComPtr 또는 CComQIPtr 클래스 객체가 매개 변수로 넘어올 때 QueryInterface 메서드를 호출해준다.

 

CComQIPtr::CComQIPtr(IUnknown * lp);

T* CComQIPtr::operator=(IUnknown*lp);

 

CComQIPtr 클래스의 사용 예

 

void Foo(IUnknown * p)

{

CComQIPtr<IHello, &IID_IHello> pIHello(p);

CComQIPtr<IGoodbye, &IID_IGoodbye> pIGoodbye1(pIHello);

CComQIPtr<IGoodbye, &IID_IGoodbye> pIGoodbye2;

pIGoodbye2 = pIHello;

 

CComQIPtr<IGoodbye, &IID_IGoodbye> pIGoodbye3(pIGoodbye1);

CComQIPtr<IGoodbye, &IID_IGoodbye> pIGoodbye4;

pIGoodbye4 = pIGoodbye3; // Addref 메서드만 호출

}

 

CComPtr 템플릿 클래스는 이제 필요없게 되는 걸까?

n  ATL 버전과의 호환성 때문이라도 남아 있어야 한다

n  CComQIPtr 템플릿 클래스가 IUnknown 인터페이스는 수용하지 않는다

 

CComQIPtr<IUnknown, &IID_IUnknown> pUnk;

 

위 코드에 대해서 컴파일러는 컴파일 에러 메시지를 보여주게 된다. 따라서, IUnknown 인터페이스를 수용하는 템플릿 클래스 객체를 사용하기 위해서는 다음과 같이 CComPtr 템플릿 클래스를 사용해야 한다.

 

CComPtr<IUnknown> pUnk;

 

출처 : 전병선의 Component Development with Visual C++ & ATL

 

2008. 8. 18. 22:38

USES_CONVERSION : ATL and MFC String Conversion Macros

USES_CONVERSION 은 ATL 3.0의 매크로로 다음과 같이 문자열 변환을 이용할 경우 미리 선언해 두는 매크로이다. (Visual C++ 6.0)

void func( LPSTR lpsz )
{
   USES_CONVERSION;
   ...
   LPWSTR x = A2W(lpsz)
   // Do something with x
   ...
}


ATL 7.0부터는 USES_CONVERSION 이라는 매크로를 사용할 필요가 없다. (.NET 이후)

아래 표는 ATL 3.0 과 ATL 7.0 의 비교하는 것이다.

 

Old ATL 3.0 Conversion Macros

New ATL 7.0 Conversion Classes

Allocates memory on the stack.

Uses stack memory for small strings. Uses the heap if the stack is not large enough.

The string is freed when the function is exited.

The string is freed when the variable goes out of scope.

Cannot be used in exception handlers.

Can be used in exception handlers.

Not suitable for use in loops. Memory use grows until the function is exited.

Supports use in loops. Loop scope ensures that memory is freed on each iteration.

Not good for large strings. Stack space is limited.

No problems with large strings. Strings will be allocated on the heap.

Usually require USES_CONVERSION to be defined.

Never require USES_CONVERSION to be defined.

Meaning of OLE depends on definition of OLE2ANSI.

OLE is always equivalent to W.

 
참고로 변환을 하드 코딩하는 API로 WideCharToMultiByte 와 MultiByteToWideChar 라는 변환 함수를 제공한다.

참고 : http://msdn.microsoft.com/ko-kr/library/87zae4a3(VS.80).aspx