BEEPER.CPP
/* 
 * BEEPER.CPP 
 * Beeper Object #6 with Property Pages Chapter 16 
 * 
 * Implementation of the CBeeper class which supports property 
 * pages through ISpecifyPropertyPages and data binding through 
 * IPropertyNotifySink.  See also CONNPT.CPP for connection 
 * point business. 
 * 
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved 
 * 
 * Kraig Brockschmidt, Microsoft 
 * Internet  :  kraigb@microsoft.com 
 * Compuserve:  >INTERNET:kraigb@microsoft.com 
 */ 
 
 
#include "beeper.h" 
 
extern HINSTANCE g_hInst; 
 
 
/* 
 * CBeeper::CBeeper 
 * CBeeper::~CBeeper 
 * 
 * Parameters (Constructor): 
 *  pUnkOuter       LPUNKNOWN of a controlling unknown. 
 *  pfnDestroy      PFNDESTROYED to call when an object 
 *                  is destroyed. 
 */ 
 
CBeeper::CBeeper(LPUNKNOWN pUnkOuter, PFNDESTROYED pfnDestroy) 
    { 
    m_cRef=0; 
    m_pUnkOuter=pUnkOuter; 
    m_pfnDestroy=pfnDestroy; 
 
    m_lSound=0; 
    m_pImpIDispatch=NULL; 
    //CHPATER16MOD 
    m_pImpISpecifyPP=NULL; 
    m_pImpIConnPtCont=NULL; 
 
    //Connection point 
    m_pIPropNotifySink=NULL; 
    m_pConnPt=NULL; 
    //End CHAPTER16MOD 
    return; 
    } 
 
 
CBeeper::~CBeeper(void) 
    { 
    //CHAPTER16MOD 
    DeleteInterfaceImp(m_pImpIConnPtCont); 
    DeleteInterfaceImp(m_pImpISpecifyPP ); 
 
    ReleaseInterface(m_pConnPt); 
 
    //End CHAPTER16MOD 
    DeleteInterfaceImp(m_pImpIDispatch); 
    return; 
    } 
 
 
 
/* 
 * CBeeper::Init 
 * 
 * Purpose: 
 *  Performs any intiailization of a CBeeper that's prone to failure 
 *  that we also use internally before exposing the object outside. 
 * 
 * Parameters: 
 *  None 
 * 
 * Return Value: 
 *  BOOL            TRUE if the function is successful, 
 *                  FALSE otherwise. 
 */ 
 
BOOL CBeeper::Init(void) 
    { 
    LPUNKNOWN       pIUnknown=this; 
 
    if (NULL!=m_pUnkOuter) 
        pIUnknown=m_pUnkOuter; 
 
    m_pImpIDispatch=new CImpIDispatch(this, pIUnknown); 
 
    if (NULL==m_pImpIDispatch) 
        return FALSE; 
 
    //CHAPTER16MOD 
    m_pImpISpecifyPP=new CImpISpecifyPP(this, pIUnknown); 
 
    if (NULL==m_pImpISpecifyPP) 
        return FALSE; 
 
    m_pImpIConnPtCont=new CImpIConnPtCont(this, pIUnknown); 
 
    if (NULL==m_pImpIConnPtCont) 
        return FALSE; 
 
    m_pConnPt=new CConnectionPoint(this); 
 
    if (NULL==m_pConnPt) 
        return FALSE; 
 
    m_pConnPt->AddRef();    //Reversed in destructor 
    //End CHAPTER16MOD 
 
    return TRUE; 
    } 
 
 
 
 
/* 
 * CBeeper::QueryInterface 
 * CBeeper::AddRef 
 * CBeeper::Release 
 */ 
 
STDMETHODIMP CBeeper::QueryInterface(REFIID riid, PPVOID ppv) 
    { 
    *ppv=NULL; 
 
    if (IID_IUnknown==riid || IID_IBeeper==riid) 
        *ppv=this; 
 
    if (IID_IDispatch==riid || DIID_DIBeeper==riid) 
        *ppv=m_pImpIDispatch; 
 
    //CHAPTER16MOD 
    if (IID_ISpecifyPropertyPages==riid) 
        *ppv=m_pImpISpecifyPP; 
 
    if (IID_IConnectionPointContainer==riid) 
        *ppv=m_pImpIConnPtCont; 
    //End CHAPTER16MOD 
 
    if (NULL!=*ppv) 
        { 
        ((LPUNKNOWN)*ppv)->AddRef(); 
        return NOERROR; 
        } 
 
    return ResultFromScode(E_NOINTERFACE); 
    } 
 
 
STDMETHODIMP_(ULONG) CBeeper::AddRef(void) 
    { 
    return ++m_cRef; 
    } 
 
 
STDMETHODIMP_(ULONG) CBeeper::Release(void) 
    { 
    if (0L!=--m_cRef) 
        return m_cRef; 
 
    if (NULL!=m_pfnDestroy) 
        (*m_pfnDestroy)(); 
 
    delete this; 
    return 0L; 
    } 
 
 
 
 
//IBeeper interface functions 
 
/* 
 * CBeeper::get_Sound 
 * CBeeper::put_Sound 
 * 
 * Purpose: 
 *  Functions called from DispInvoke to handle the Sound property. 
 * 
 * Parameters (Set only): 
 *  lSound          long, new sound to save after validation 
 * 
 * Return Value: (Get only): 
 *  long            Current sound. 
 */ 
 
STDMETHODIMP_(long) CBeeper::get_Sound(void) 
    { 
    return m_lSound; 
    } 
 
 
STDMETHODIMP_(void) CBeeper::put_Sound(long lSound) 
    { 
    if (MB_OK!=lSound && MB_ICONEXCLAMATION!=lSound 
        && MB_ICONQUESTION!=lSound && MB_ICONHAND!=lSound 
        && MB_ICONASTERISK!=lSound) 
        { 
        /* 
         * Since we cannot return a value to ITypeInfo::Invoke to 
         * indicate an exception condition, we'll use 
         * m_pImpIDispatch->Exception to raise them.  Before 
         * CImpIDispatch::Invoke calls ITypeInfo::Invoke, it clears 
         * the exception.  CImpIDispatch::Exception sets an 
         * exception, so when ITypeInfo::Invoke returns, our Invoke 
         * can store exception information as necessary. 
         */ 
        m_pImpIDispatch->Exception(EXCEPTION_INVALIDSOUND); 
        return; 
        } 
 
    //CHAPTER16MOD 
    /* 
     * We marked the "Sound" property as bindable and as 
     * requestedit in our type information, so we have to 
     * call any sink's OnRequestEdit first, and if that 
     * succeeds with S_OK then we have to call OnChanged. 
     */ 
    if (NULL!=m_pIPropNotifySink) 
        { 
        //If we didn't get permission, stop now 
        if (NOERROR!=m_pIPropNotifySink->OnRequestEdit(PROPERTY_SOUND)) 
            return; 
        } 
    //End CHAPTER16MOD 
 
    m_lSound=lSound; 
 
    //CHAPTER16MOD 
    if (NULL!=m_pIPropNotifySink) 
        m_pIPropNotifySink->OnChanged(PROPERTY_SOUND); 
    //End CHAPTER16MOD 
 
    return; 
    } 
 
 
/* 
 * CBeeper::Beep 
 * 
 * Purpose: 
 *  Function called from DispInvoke to invoke the Beep method. 
 * 
 * Return Value: 
 *  long           The sound played. 
 */ 
 
STDMETHODIMP_(long) CBeeper::Beep(void) 
    { 
    MessageBeep((UINT)m_lSound); 
    return m_lSound; 
    } 
 
 
 
//CHAPTER16MOD 
//ISpecifyPropertyPages interface implementation 
 
/* 
 * CImpISpecifyPP::CImpISpecifyPP 
 * CImpISpecifyPP::~CImpISpecifyPP 
 * 
 * Parameters (Constructor): 
 *  pObj            PCBeeper of the object we're in. 
 *  pUnkOuter       LPUNKNOWN to which we delegate. 
 */ 
 
CImpISpecifyPP::CImpISpecifyPP(PCBeeper pObj, LPUNKNOWN pUnkOuter) 
    { 
    m_cRef=0; 
    m_pObj=pObj; 
    m_pUnkOuter=pUnkOuter; 
    return; 
    } 
 
CImpISpecifyPP::~CImpISpecifyPP(void) 
    { 
    return; 
    } 
 
 
/* 
 * CImpISpecifyPP::QueryInterface 
 * CImpISpecifyPP::AddRef 
 * CImpISpecifyPP::Release 
 */ 
 
STDMETHODIMP CImpISpecifyPP::QueryInterface(REFIID riid 
    , LPVOID *ppv) 
    { 
    return m_pUnkOuter->QueryInterface(riid, ppv); 
    } 
 
STDMETHODIMP_(ULONG) CImpISpecifyPP::AddRef(void) 
    { 
    ++m_cRef; 
    return m_pUnkOuter->AddRef(); 
    } 
 
STDMETHODIMP_(ULONG) CImpISpecifyPP::Release(void) 
    { 
    --m_cRef; 
    return m_pUnkOuter->Release(); 
    } 
 
 
 
/* 
 * CImpISpecifyPP::GetPages 
 * 
 * Purpose: 
 *  Returns an array of GUIDs identifying the indivudual property 
 *  pages used for this object. 
 * 
 * Parameters: 
 *  pPages          CAUUID * pointing to a counted array of GUIDs. 
 *                  This function allocates the array elements 
 *                  and stores them in this structure. 
 */ 
 
STDMETHODIMP CImpISpecifyPP::GetPages(CAUUID *pPages) 
    { 
    IMalloc *pIMalloc; 
    GUID    *pGUID; 
 
    pPages->cElems=0; 
    pPages->pElems=NULL; 
 
    if (FAILED(CoGetMalloc(MEMCTX_TASK, &pIMalloc))) 
        return ResultFromScode(E_OUTOFMEMORY); 
 
    pGUID=(GUID *)pIMalloc->Alloc(CPROPPAGES*sizeof(GUID)); 
 
    if (NULL!=pGUID) 
        { 
        //Fill the array now that we allocated it. 
        pGUID[0]=CLSID_BeeperPropertyPage; 
 
        //Fill the structure and return. 
        pPages->cElems=CPROPPAGES; 
        pPages->pElems=pGUID; 
        } 
 
    pIMalloc->Release(); 
    return (NULL!=pGUID) ? NOERROR 
        : ResultFromScode(E_OUTOFMEMORY); 
    } 
//End CHAPTER16MOD 
 
 
 
 
 
//IDispatch interface implementation 
 
/* 
 * CImpIDispatch::CImpIDispatch 
 * CImpIDispatch::~CImpIDispatch 
 * 
 * Parameters (Constructor): 
 *  pObj            PCBeeper of the object we're in. 
 *  pUnkOuter       LPUNKNOWN to which we delegate. 
 */ 
 
CImpIDispatch::CImpIDispatch(PCBeeper pObj, LPUNKNOWN pUnkOuter) 
    { 
    m_cRef=0; 
    m_pObj=pObj; 
    m_pUnkOuter=pUnkOuter; 
 
    //These are created as needed in GetTypeInfo 
    m_pITINeutral=NULL; 
    m_pITIGerman=NULL; 
 
    return; 
    } 
 
CImpIDispatch::~CImpIDispatch(void) 
    { 
    ReleaseInterface(m_pITIGerman); 
    ReleaseInterface(m_pITINeutral); 
    return; 
    } 
 
 
 
/* 
 * CImpIDispatch::QueryInterface 
 * CImpIDispatch::AddRef 
 * CImpIDispatch::Release 
 * 
 * Purpose: 
 *  IUnknown members for CImpIDispatch object. 
 */ 
 
STDMETHODIMP CImpIDispatch::QueryInterface(REFIID riid, PPVOID ppv) 
    { 
    return m_pUnkOuter->QueryInterface(riid, ppv); 
    } 
 
 
STDMETHODIMP_(ULONG) CImpIDispatch::AddRef(void) 
    { 
    ++m_cRef; 
    return m_pUnkOuter->AddRef(); 
    } 
 
STDMETHODIMP_(ULONG) CImpIDispatch::Release(void) 
    { 
    --m_cRef; 
    return m_pUnkOuter->Release(); 
    } 
 
 
 
/* 
 * CImpIDispatch::GetTypeInfoCount 
 * 
 * Purpose: 
 *  Returns the number of type information (ITypeInfo) interfaces 
 *  that the object provides (0 or 1). 
 * 
 * Parameters: 
 *  pctInfo         UINT * to the location to receive 
 *                  the count of interfaces. 
 * 
 * Return Value: 
 *  HRESULT         NOERROR or a general error code. 
 */ 
 
STDMETHODIMP CImpIDispatch::GetTypeInfoCount(UINT *pctInfo) 
    { 
    //We implement GetTypeInfo so return 1 
    *pctInfo=1; 
    return NOERROR; 
    } 
 
 
 
 
/* 
 * CImpIDispatch::GetTypeInfo 
 * 
 * Purpose: 
 *  Retrieves type information for the automation interface.  This 
 *  is used anywhere that the right ITypeInfo interface is needed 
 *  for whatever LCID is applicable.  Specifically, this is used 
 *  from within GetIDsOfNames and Invoke. 
 * 
 * Parameters: 
 *  itInfo          UINT reserved.  Must be zero. 
 *  lcid            LCID providing the locale for the type 
 *                  information.  If the object does not support 
 *                  localization, this is ignored. 
 *  ppITypeInfo     ITypeInfo ** in which to store the ITypeInfo 
 *                  interface for the object. 
 * 
 * Return Value: 
 *  HRESULT         NOERROR or a general error code. 
 */ 
 
STDMETHODIMP CImpIDispatch::GetTypeInfo(UINT itInfo, LCID lcid 
    , ITypeInfo **ppITypeInfo) 
    { 
    HRESULT     hr; 
    ITypeLib   *pITypeLib; 
    ITypeInfo **ppITI=NULL; 
 
    if (0!=itInfo) 
        return ResultFromScode(TYPE_E_ELEMENTNOTFOUND); 
 
    if (NULL==ppITypeInfo) 
        return ResultFromScode(E_POINTER); 
 
    *ppITypeInfo=NULL; 
 
    /* 
     * Since we returned one from GetTypeInfoCount, this function 
     * can be called for a specific locale.  We support English, 
     * German, and neutral (defaults to English) locales.  Anything 
     * else is an error. 
     * 
     * After this switch statement, ppITI will either be NULL or 
     * a valid pointer in it after.  If NULL, we know we need to 
     * load type information, retrieve the ITypeInfo we want, and 
     * then store it in *ppITI. 
     */ 
    switch (PRIMARYLANGID(lcid)) 
        { 
        case LANG_NEUTRAL: 
        case LANG_ENGLISH: 
            ppITI=&m_pITINeutral; 
            break; 
 
        case LANG_GERMAN: 
            ppITI=&m_pITIGerman; 
            break; 
 
        default: 
            return ResultFromScode(DISP_E_UNKNOWNLCID); 
        } 
 
    //Load a type lib if we don't have the information already. 
    if (NULL==*ppITI) 
        { 
        /* 
         * The type libraries are registered under 0 (neutral), 
         * 7 (German), and 9 (English) with no specific sub- 
         * language, which would make them 407 or 409 and such. 
         * If you are sensitive to sub-languages, then use the 
         * full LCID instead of just the LANGID as done here. 
         */ 
        hr=LoadRegTypeLib(LIBID_BeeperTypeLibrary, 1, 0 
            , PRIMARYLANGID(lcid), &pITypeLib); 
 
        /* 
         * If LoadRegTypeLib fails, try loading directly with 
         * LoadTypeLib, which will register the library for us. 
         * Note that there's no default case here because the 
         * prior switch will have filtered lcid already. 
         * 
         * NOTE:  You should prepend your DIR registry key to the 
         * .TLB name so you don't depend on it being it the PATH. 
         * This sample will be updated later to reflect this. 
         */ 
        if (FAILED(hr)) 
            { 
            switch (PRIMARYLANGID(lcid)) 
                { 
                case LANG_NEUTRAL: 
                case LANG_ENGLISH: 
                    hr=LoadTypeLib(OLETEXT("BEEP0000.TLB"), &pITypeLib); 
                    break; 
 
                case LANG_GERMAN: 
                    hr=LoadTypeLib(OLETEXT("BEEP0007.TLB"), &pITypeLib); 
                    break; 
                } 
            } 
 
        if (FAILED(hr)) 
            return hr; 
 
        //Got the type lib, get type info for the interface we want 
        hr=pITypeLib->GetTypeInfoOfGuid(IID_IBeeper, ppITI); 
        pITypeLib->Release(); 
 
        if (FAILED(hr)) 
            return hr; 
        } 
 
    /* 
     * Note:  the type library is still loaded since we have 
     * an ITypeInfo from it. 
     */ 
 
    (*ppITI)->AddRef(); 
    *ppITypeInfo=*ppITI; 
    return NOERROR; 
    } 
 
 
 
 
 
 
 
 
 
/* 
 * CImpIDispatch::GetIDsOfNames 
 * 
 * Purpose: 
 *  Converts text names into DISPIDs to pass to Invoke 
 * 
 * Parameters: 
 *  riid            REFIID reserved.  Must be IID_NULL. 
 *  rgszNames       OLECHAR ** pointing to the array of names to be 
 *                  mapped. 
 *  cNames          UINT number of names to be mapped. 
 *  lcid            LCID of the locale. 
 *  rgDispID        DISPID * caller allocated array containing IDs 
 *                  corresponging to those names in rgszNames. 
 * 
 * Return Value: 
 *  HRESULT         NOERROR or a general error code. 
 */ 
 
STDMETHODIMP CImpIDispatch::GetIDsOfNames(REFIID riid 
    , OLECHAR **rgszNames, UINT cNames, LCID lcid, DISPID *rgDispID) 
    { 
    HRESULT     hr; 
    ITypeInfo  *pTI; 
 
    if (IID_NULL!=riid) 
        return ResultFromScode(DISP_E_UNKNOWNINTERFACE); 
 
    //Get the right ITypeInfo for lcid. 
    hr=GetTypeInfo(0, lcid, &pTI); 
 
    if (SUCCEEDED(hr)) 
        { 
        hr=DispGetIDsOfNames(pTI, rgszNames, cNames, rgDispID); 
        pTI->Release(); 
        } 
 
    return hr; 
    } 
 
 
 
/* 
 * CImpIDispatch::Invoke 
 * 
 * Purpose: 
 *  Calls a method in the dispatch interface or manipulates a 
 *  property. 
 * 
 * Parameters: 
 *  dispID          DISPID of the method or property of interest. 
 *  riid            REFIID reserved, must be IID_NULL. 
 *  lcid            LCID of the locale. 
 *  wFlags          USHORT describing the context of the invocation. 
 *  pDispParams     DISPPARAMS * to the array of arguments. 
 *  pVarResult      VARIANT * in which to store the result.  Is 
 *                  NULL if the caller is not interested. 
 *  pExcepInfo      EXCEPINFO * to exception information. 
 *  puArgErr        UINT * in which to store the index of an 
 *                  invalid parameter if DISP_E_TYPEMISMATCH 
 *                  is returned. 
 * 
 * Return Value: 
 *  HRESULT         NOERROR or a general error code. 
 */ 
 
STDMETHODIMP CImpIDispatch::Invoke(DISPID dispID, REFIID riid 
    , LCID lcid, unsigned short wFlags, DISPPARAMS *pDispParams 
    , VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr) 
    { 
    HRESULT     hr; 
    ITypeInfo  *pTI; 
 
    //riid is supposed to be IID_NULL always 
    if (IID_NULL!=riid) 
        return ResultFromScode(DISP_E_UNKNOWNINTERFACE); 
 
    //Get the ITypeInfo for lcid 
    hr=GetTypeInfo(0, lcid, &pTI); 
 
    if (FAILED(hr)) 
        return hr; 
 
    /* 
     * Clear exceptions.  Note that this mechanism only works 
     * for a single-thread, which is fine for the initial 32-bit 
     * release of OLE but not a long-term solution. 
     */ 
    m_wException=EXCEPTION_NONE; 
 
    //This is exactly what DispInvoke does--so skip the overhead. 
    hr=pTI->Invoke((IBeeper *)m_pObj, dispID, wFlags 
        , pDispParams, pVarResult, pExcepInfo, puArgErr); 
 
    /* 
     * Check if an exception was raised.  Again, this is not 
     * thread-safe. 
     */ 
    if (EXCEPTION_NONE!=m_wException) 
        { 
        pExcepInfo->scode 
            =(SCODE)MAKELONG(m_wException, PRIMARYLANGID(lcid)); 
        FillException(pExcepInfo); 
        hr=ResultFromScode(DISP_E_EXCEPTION); 
        } 
 
    pTI->Release(); 
    return hr; 
    } 
 
 
 
 
/* 
 * CImpIDispatch::Exception 
 * 
 * Purpose: 
 *  Raises an exception for CImpIDispatch::Invoke from within 
 *  ITypeInfo::Invoke.  This simply sets the m_wException variable 
 *  to the exception code.  Invoke will pick this up and fill in 
 *  the exception structure as necessary. 
 * 
 * Parameters: 
 *  wException      WORD exception code. 
 */ 
 
void CImpIDispatch::Exception(WORD wException) 
    { 
    /* 
     * Note: calls to this function will be wrapped in a critical 
     * section on Win32 because this is always called from within 
     * DispInvoke which we wrapped above in Invoke. 
     */ 
    m_wException=wException; 
    return; 
    } 
 
 
 
 
/* 
 * FillException 
 * 
 * Purpose: 
 *  Callback function pointed to in IDispatch::Invoke that fills 
 *  an EXCEPINFO structure based on the code stored inside 
 *  Invoke.  This is a nice mechanism to keep all the management 
 *  of error code strings and help IDs centralized in one place, 
 *  even across many different automation objects within the same 
 *  application.  It also keeps Invoke cleaner. 
 * 
 * Parameters: 
 *  pExcepInfo      EXCEPINFO * to fill. 
 * 
 * Return Value: 
 *  HRESULT         NOERROR if successful, error code otherwise. 
 */ 
 
HRESULT STDAPICALLTYPE FillException(EXCEPINFO *pExcepInfo) 
    { 
    SCODE       scode; 
    LANGID      langID; 
    USHORT      wCode; 
    HRESULT     hr; 
    LPTSTR      psz; 
    LPOLESTR    pszHelp; 
    UINT        idsSource; 
    UINT        idsException; 
 
    if (NULL==pExcepInfo) 
        return ResultFromScode(E_INVALIDARG); 
 
    scode=pExcepInfo->scode; 
    langID=HIWORD(scode); 
    wCode=LOWORD(scode); 
 
    //Allocate BSTRs for source and description strings 
    psz=(LPTSTR)malloc(1024*sizeof(TCHAR)); 
 
    if (NULL==psz) 
        return ResultFromScode(E_OUTOFMEMORY); 
 
    hr=NOERROR; 
 
    switch (wCode) 
        { 
        case EXCEPTION_INVALIDSOUND: 
            //Fill in unused information, macro in inole.h 
            INITEXCEPINFO(*pExcepInfo); 
            pExcepInfo->wCode=wCode; 
 
            pExcepInfo->dwHelpContext=HID_SOUND_PROPERTY_LIMITATIONS; 
 
            //We registered a HELPDIR so we don't include paths 
            pszHelp=OLETEXT("beep0000.hlp"); 
            idsSource=IDS_0_EXCEPTIONSOURCE; 
            idsException=IDS_0_EXCEPTIONINVALIDSOUND; 
 
            //Get the localized source and exception strings 
            switch (langID) 
                { 
                case LANG_GERMAN: 
                    idsSource=IDS_7_EXCEPTIONSOURCE; 
                    idsException=IDS_7_EXCEPTIONINVALIDSOUND; 
                    pszHelp=OLETEXT("beep0007.hlp"); 
                    break; 
 
                case LANG_ENGLISH: 
                case LANG_NEUTRAL: 
                default: 
                    break; 
                } 
 
            break; 
 
        default: 
            hr=ResultFromScode(E_FAIL); 
        } 
 
    if (SUCCEEDED(hr)) 
        { 
        pExcepInfo->bstrHelpFile=SysAllocString(pszHelp); 
 
       #ifdef WIN32ANSI 
        OLECHAR     szTemp[256]; 
 
        LoadString(g_hInst, idsSource, psz, 256); 
        MultiByteToWideChar(CP_ACP, 0, psz, -1, szTemp, 256); 
        pExcepInfo->bstrSource=SysAllocString(szTemp); 
 
        LoadString(g_hInst, idsException, psz, 256); 
        MultiByteToWideChar(CP_ACP, 0, psz, -1, szTemp, 256); 
        pExcepInfo->bstrDescription=SysAllocString(szTemp); 
       #else 
        LoadString(g_hInst, idsSource, psz, 1024); 
        pExcepInfo->bstrSource=SysAllocString(psz); 
        LoadString(g_hInst, idsException, psz, 1024); 
        pExcepInfo->bstrDescription=SysAllocString(psz); 
       #endif 
        } 
 
    free(psz); 
    return hr; 
    }