BEEPER.CPP
上传用户:bangxh
上传日期:2007-01-31
资源大小:42235k
文件大小:21k
源码类别:

Windows编程

开发平台:

Visual C++

  1. /*
  2.  * BEEPER.CPP
  3.  * Beeper Automation Object #1 Chapter 14
  4.  *
  5.  * Implementation of the CBeeper class which demonstrate a fully
  6.  * custom IDispatch implementation that only supports mapping of
  7.  * names to IDs through IDispatch::GetIDsOfNames.
  8.  *
  9.  * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
  10.  *
  11.  * Kraig Brockschmidt, Microsoft
  12.  * Internet  :  kraigb@microsoft.com
  13.  * Compuserve:  >INTERNET:kraigb@microsoft.com
  14.  */
  15. #include "beeper.h"
  16. extern HINSTANCE g_hInst;
  17. /*
  18.  * CBeeper::CBeeper
  19.  * CBeeper::~CBeeper
  20.  *
  21.  * Parameters (Constructor):
  22.  *  pUnkOuter       LPUNKNOWN of a controlling unknown.
  23.  *  pfnDestroy      PFNDESTROYED to call when an object
  24.  *                  is destroyed.
  25.  */
  26. CBeeper::CBeeper(LPUNKNOWN pUnkOuter, PFNDESTROYED pfnDestroy)
  27.     {
  28.     m_cRef=0;
  29.     m_pUnkOuter=pUnkOuter;
  30.     m_pfnDestroy=pfnDestroy;
  31.     m_lSound=0;
  32.     m_pImpIDispatch=NULL;
  33.     return;
  34.     }
  35. CBeeper::~CBeeper(void)
  36.     {
  37.     if (NULL==m_pszScratch)
  38.         free(m_pszScratch);
  39.     DeleteInterfaceImp(m_pImpIDispatch);
  40.     return;
  41.     }
  42. /*
  43.  * CBeeper::Init
  44.  *
  45.  * Purpose:
  46.  *  Performs any intiailization of a CBeeper that's prone to failure
  47.  *  that we also use internally before exposing the object outside.
  48.  *
  49.  * Parameters:
  50.  *  None
  51.  *
  52.  * Return Value:
  53.  *  BOOL            TRUE if the function is successful,
  54.  *                  FALSE otherwise.
  55.  */
  56. BOOL CBeeper::Init(void)
  57.     {
  58.     LPUNKNOWN       pIUnknown=this;
  59.     if (NULL!=m_pUnkOuter)
  60.         pIUnknown=m_pUnkOuter;
  61.     m_pImpIDispatch=new CImpIDispatch(this, pIUnknown);
  62.     if (NULL==m_pImpIDispatch)
  63.         return FALSE;
  64.     //Pre-allocate scratch space for IDispatch::GetIDsOfNames
  65.     m_pszScratch=(LPTSTR)malloc(256*sizeof(TCHAR));
  66.     if (NULL==m_pszScratch)
  67.         return FALSE;
  68.     return TRUE;
  69.     }
  70. /*
  71.  * CBeeper::QueryInterface
  72.  * CBeeper::AddRef
  73.  * CBeeper::Release
  74.  *
  75.  * Purpose:
  76.  *  IUnknown members for CBeeper object.
  77.  */
  78. STDMETHODIMP CBeeper::QueryInterface(REFIID riid, PPVOID ppv)
  79.     {
  80.     *ppv=NULL;
  81.     /*
  82.      * The only calls for IUnknown are either in a nonaggregated
  83.      * case or when created in an aggregation, so in either case
  84.      * always return our IUnknown for IID_IUnknown.
  85.      */
  86.     if (IID_IUnknown==riid)
  87.         *ppv=this;
  88.     /*
  89.      * QueryInterface must respond not only to IID_IDispatch for
  90.      * the primary automation interface, but also to the DIID of the
  91.      * dispinterface itself, which in our case is DIID_DIBeeper.
  92.      */
  93.     if (IID_IDispatch==riid || DIID_DIBeeper==riid)
  94.         *ppv=m_pImpIDispatch;
  95.     //AddRef any interface we'll return.
  96.     if (NULL!=*ppv)
  97.         {
  98.         ((LPUNKNOWN)*ppv)->AddRef();
  99.         return NOERROR;
  100.         }
  101.     return ResultFromScode(E_NOINTERFACE);
  102.     }
  103. STDMETHODIMP_(ULONG) CBeeper::AddRef(void)
  104.     {
  105.     return ++m_cRef;
  106.     }
  107. STDMETHODIMP_(ULONG) CBeeper::Release(void)
  108.     {
  109.     if (0L!=--m_cRef)
  110.         return m_cRef;
  111.     //Inform the server about destruction so it can handle shutdown
  112.     if (NULL!=m_pfnDestroy)
  113.         (*m_pfnDestroy)();
  114.     delete this;
  115.     return 0L;
  116.     }
  117. //IDispatch interface implementation
  118. /*
  119.  * CImpIDispatch::CImpIDispatch
  120.  * CImpIDispatch::~CImpIDispatch
  121.  *
  122.  * Parameters (Constructor):
  123.  *  pObj            PCBeeper of the object we're in.
  124.  *  pUnkOuter       LPUNKNOWN to which we delegate.
  125.  */
  126. CImpIDispatch::CImpIDispatch(PCBeeper pObj, LPUNKNOWN pUnkOuter)
  127.     {
  128.     m_cRef=0;
  129.     m_pObj=pObj;
  130.     m_pUnkOuter=pUnkOuter;
  131.     return;
  132.     }
  133. CImpIDispatch::~CImpIDispatch(void)
  134.     {
  135.     return;
  136.     }
  137. /*
  138.  * CImpIDispatch::QueryInterface
  139.  * CImpIDispatch::AddRef
  140.  * CImpIDispatch::Release
  141.  *
  142.  * Purpose:
  143.  *  IUnknown members for CImpIDispatch object.
  144.  */
  145. STDMETHODIMP CImpIDispatch::QueryInterface(REFIID riid, PPVOID ppv)
  146.     {
  147.     return m_pUnkOuter->QueryInterface(riid, ppv);
  148.     }
  149. STDMETHODIMP_(ULONG) CImpIDispatch::AddRef(void)
  150.     {
  151.     ++m_cRef;
  152.     return m_pUnkOuter->AddRef();
  153.     }
  154. STDMETHODIMP_(ULONG) CImpIDispatch::Release(void)
  155.     {
  156.     --m_cRef;
  157.     return m_pUnkOuter->Release();
  158.     }
  159. /*
  160.  * CImpIDispatch::GetTypeInfoCount
  161.  *
  162.  * Purpose:
  163.  *  Returns the number of type information (ITypeInfo) interfaces
  164.  *  that the object provides (0 or 1).
  165.  *
  166.  * Parameters:
  167.  *  pctInfo         UINT * to the location to receive
  168.  *                  the count of interfaces.
  169.  *
  170.  * Return Value:
  171.  *  HRESULT         NOERROR or a general error code.
  172.  */
  173. STDMETHODIMP CImpIDispatch::GetTypeInfoCount(UINT *pctInfo)
  174.     {
  175.     //We don't implement GetTypeInfo, so return 0
  176.     *pctInfo=0;
  177.     return NOERROR;
  178.     }
  179. /*
  180.  * CImpIDispatch::GetTypeInfo
  181.  *
  182.  * Purpose:
  183.  *  Retrieves type information for the automation interface.
  184.  *
  185.  * Parameters:
  186.  *  itinfo          UINT reserved.  Must be zero.
  187.  *  lcid            LCID providing the locale for the type
  188.  *                  information.  If the object does not support
  189.  *                  localization, this is ignored.
  190.  *  pptinfo         ITypeInfo ** in which to store the ITypeInfo
  191.  *                  interface for the object.
  192.  *
  193.  * Return Value:
  194.  *  HRESULT         NOERROR or a general error code.
  195.  */
  196. STDMETHODIMP CImpIDispatch::GetTypeInfo(UINT itinfo, LCID lcid
  197.     , ITypeInfo **pptInfo)
  198.     {
  199.     /*
  200.      * Since we returned zero from GetTypeInfoCount, this function
  201.      * should not be called.  If it is, be sure to NULL the pptInfo
  202.      * pointer according to normal out-parameter rules.
  203.      */
  204.     *pptInfo=NULL;
  205.     return ResultFromScode(E_NOTIMPL);
  206.     }
  207. /*
  208.  * CImpIDispatch::GetIDsOfNames
  209.  *
  210.  * Purpose:
  211.  *  Converts text names into DISPIDs to pass to Invoke
  212.  *
  213.  * Parameters:
  214.  *  riid            REFIID reserved.  Must be IID_NULL.
  215.  *  rgszNames       OLECHAR ** pointing to the array of names to be
  216.  *                  mapped.
  217.  *  cNames          UINT number of names to be mapped.
  218.  *  lcid            LCID of the locale.
  219.  *  rgDispID        DISPID * caller allocated array containing IDs
  220.  *                  corresponging to those names in rgszNames.
  221.  *
  222.  * Return Value:
  223.  *  HRESULT         NOERROR or a general error code.
  224.  */
  225. STDMETHODIMP CImpIDispatch::GetIDsOfNames(REFIID riid
  226.     , OLECHAR **rgszNames, UINT cNames, LCID lcid, DISPID *rgDispID)
  227.     {
  228.     HRESULT     hr;
  229.     int         i;
  230.     int         idsMin;
  231.     LPTSTR      psz;
  232.     if (IID_NULL!=riid)
  233.         return ResultFromScode(DISP_E_UNKNOWNINTERFACE);
  234.     /*
  235.      * This function will support English and German languages,
  236.      * where in English we have a "Sound" property and "Beep"
  237.      * method; in German these are "Ton" and "Peip" This particular
  238.      * coding will handle either language identically, so a
  239.      * controller using even both languages simultaneously can work
  240.      * with this same automation object.
  241.      *
  242.      * To check the passed LCID, we use the PRIMARYLANGID macro
  243.      * to check for LANG_ENGLISH and LANG_GERMAN, which means we
  244.      * support any dialect of english (US, UK, AUS, CAN, NZ, EIRE)
  245.      * and german (GER, SWISS, AUS).  The high byte of an LCID
  246.      * specifies the sub-language, and the macro here strips that
  247.      * differentiation.
  248.      *
  249.      * Note that LANG_NEUTRAL is considered here to be English.
  250.      */
  251.     //Set up idsMin to the right stringtable in our resources
  252.     switch (PRIMARYLANGID(lcid))
  253.         {
  254.         case LANG_NEUTRAL:
  255.         case LANG_ENGLISH:
  256.             idsMin=IDS_0_NAMESMIN;
  257.             break;
  258.         case LANG_GERMAN:
  259.             idsMin=IDS_7_NAMESMIN;
  260.             break;
  261.         default:
  262.             return ResultFromScode(DISP_E_UNKNOWNLCID);
  263.         }
  264.     /*
  265.      * The index in this loop happens to correspond to the DISPIDs
  266.      * for each element which also matches the stringtable entry
  267.      * ordering, where i+idsMin is the string to compare.  If we
  268.      * find a match, i is the DISPID to return.
  269.      */
  270.     rgDispID[0]=DISPID_UNKNOWN;
  271.     hr=ResultFromScode(DISP_E_UNKNOWNNAME);
  272.     psz=m_pObj->m_pszScratch;
  273.     for (i=0; i < CNAMES; i++)
  274.         {
  275.         /*
  276.          * If we had more than one name per method or property,
  277.          * we'd need to loop over the cNames parameter as well.
  278.          */
  279.         LoadString(g_hInst, idsMin+i, psz, 256);
  280.        #ifdef WIN32ANSI
  281.         char        szTemp[80];
  282.         WideCharToMultiByte(CP_ACP, 0, rgszNames[0], -1
  283.             , szTemp, 80, NULL, NULL);
  284.         if (0==lstrcmpi(psz, szTemp))
  285.        #else
  286.         if (0==lstrcmpi(psz, rgszNames[0]))
  287.        #endif
  288.             {
  289.             //Found a match, return the DISPID
  290.             rgDispID[0]=i;
  291.             hr=NOERROR;
  292.             break;
  293.             }
  294.         }
  295.     return hr;
  296.     }
  297. /*
  298.  * CImpIDispatch::Invoke
  299.  *
  300.  * Purpose:
  301.  *  Calls a method in the dispatch interface or manipulates a
  302.  *  property.
  303.  *
  304.  * Parameters:
  305.  *  dispID          DISPID of the method or property of interest.
  306.  *  riid            REFIID reserved, must be IID_NULL.
  307.  *  lcid            LCID of the locale.
  308.  *  wFlags          USHORT describing the context of the invocation.
  309.  *  pDispParams     DISPPARAMS * to the array of arguments.
  310.  *  pVarResult      VARIANT * in which to store the result.  Is
  311.  *                  NULL if the caller is not interested.
  312.  *  pExcepInfo      EXCEPINFO * to exception information.
  313.  *  puArgErr        UINT * in which to store the index of an
  314.  *                  invalid parameter if DISP_E_TYPEMISMATCH
  315.  *                  is returned.
  316.  *
  317.  * Return Value:
  318.  *  HRESULT         NOERROR or a general error code.
  319.  */
  320. STDMETHODIMP CImpIDispatch::Invoke(DISPID dispID, REFIID riid
  321.     , LCID lcid, unsigned short wFlags, DISPPARAMS *pDispParams
  322.     , VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
  323.     {
  324.     HRESULT     hr;
  325.     //riid is supposed to be IID_NULL always
  326.     if (IID_NULL!=riid)
  327.         return ResultFromScode(DISP_E_UNKNOWNINTERFACE);
  328.     /*
  329.      * There is nothing locale-sensitive in any of our properties
  330.      * or methods.  Some automation objects may have currency,
  331.      * date/time, or string values in properties or methods which
  332.      * would be sensitive to lcid; be sure the handle them properly.
  333.      */
  334.     /*
  335.      * Process the invoked member or property.  For members,
  336.      * call whatever functions are necessary to carry out the
  337.      * action.  For properties, either return the value you have
  338.      * or change it according to wFlags.
  339.      *
  340.      * This object supports one property and one method:
  341.      *  ID 0    "Sound" property, a long that must be one of
  342.      *          MB_OK, MB_ICONHAND, MB_ICONQUESTION,
  343.      *          MB_ICONEXCLAMATION, and MB_ICONASTERISK.
  344.      *  ID 1    "Beep" method, no parameters, return value of type
  345.      *          long which is the sound that was played.
  346.      *
  347.      * Note that the IDs are assigned in the implementation of
  348.      * IDispatch::GetIDsOfNames.
  349.      */
  350.     switch (dispID)
  351.         {
  352.         case PROPERTY_SOUND:
  353.             /*
  354.              * Some controllers might not be able to differentiate
  355.              * between a property get and a function call, so we
  356.              * have to handle both as a property get here.
  357.              */
  358.             if (DISPATCH_PROPERTYGET & wFlags
  359.                 || DISPATCH_METHOD & wFlags)
  360.                 {
  361.                 //Make sure we have a place for the result
  362.                 if (NULL==pVarResult)
  363.                     return ResultFromScode(E_INVALIDARG);
  364.                 VariantInit(pVarResult);
  365.                 V_VT(pVarResult)=VT_I4;
  366.                 V_I4(pVarResult)=m_pObj->m_lSound;
  367.                 return NOERROR;
  368.                 }
  369.             else
  370.                 {
  371.                 //DISPATCH_PROPERTYPUT
  372.                 long        lSound;
  373.                 int         c;
  374.                 VARIANT     vt;
  375.                 //Validate parameter count
  376.                 if (1!=pDispParams->cArgs)
  377.                     return ResultFromScode(DISP_E_BADPARAMCOUNT);
  378.                 //Check that we have a named DISPID_PROPERTYPUT
  379.                 c=pDispParams->cNamedArgs;
  380.                 if (1!=c || (1==c && DISPID_PROPERTYPUT
  381.                     !=pDispParams->rgdispidNamedArgs[0]))
  382.                     return ResultFromScode(DISP_E_PARAMNOTOPTIONAL);
  383.                 /*
  384.                  * Try to coerce the new property value into a
  385.                  * type VT_I4.  VariantChangeType will do this for
  386.                  * us and return an appropriate error code if the
  387.                  * type cannot be coerced.  On error we store 0
  388.                  * (first parameter) into puArgErr.
  389.                  *
  390.                  * We could also use DispGetParam here to do the
  391.                  * same thing:
  392.                  *   DispGetParam(pDispParams, 0, VT_I4
  393.                  *       , &vtNew, puArgErr);
  394.                  */
  395.                 VariantInit(&vt);
  396.                 hr=VariantChangeType(&vt, &pDispParams->rgvarg[0]
  397.                     , 0, VT_I4);
  398.                 if (FAILED(hr))
  399.                     {
  400.                     if (NULL!=puArgErr)
  401.                         *puArgErr=0;
  402.                     return hr;
  403.                     }
  404.                 //With the right type, now check the right value
  405.                 lSound=vt.lVal;
  406.                 if (MB_OK!=lSound && MB_ICONEXCLAMATION!=lSound
  407.                     && MB_ICONQUESTION!=lSound && MB_ICONHAND!=lSound
  408.                     && MB_ICONASTERISK!=lSound)
  409.                     {
  410.                     if (NULL==pExcepInfo)
  411.                         return ResultFromScode(E_INVALIDARG);
  412.                     /*
  413.                      * This is the right place for an exception--
  414.                      * the best we can tell the caller with a
  415.                      * return value is something like E_INVALIDARG.
  416.                      * But that doesn't at all indiate the problem.
  417.                      * So we use EXCEPTION_INVALIDSOUND and the
  418.                      * FillException callback to fill the EXCEPINFO.
  419.                      *
  420.                      * Note:  DispTest and Visual Basic 3 don't
  421.                      * support deferred filling of the EXCEPINFO
  422.                      * structure; Visual Basic 4 does.  Even if you
  423.                      * don't use deferred filling, a separate
  424.                      * function is still useful as you can just call
  425.                      * it here to fill the structure immediately.
  426.                      *
  427.                      * Deferred fill-in code would appear:
  428.                      *
  429.                      *   INITEXCEPINFO(*pExcepInfo);
  430.                      *   pExcepInfo->scode
  431.                      *       =(SCODE)MAKELONG(EXCEPTION_INVALIDSOUND
  432.                      *       , PRIMARYLANGID(lcid));
  433.                      *   pExcepInfo->pfnDeferredFillIn=FillException;
  434.                      */
  435.                     /*
  436.                      * So we can make a localized exception, we'll
  437.                      * store the language ID and our exception code
  438.                      * into the scode field; in FillException we move
  439.                      * the code into wCode and clear scode.  Otherwise
  440.                      * there's no way to tell FillException about
  441.                      * the locale.
  442.                      */
  443.                     pExcepInfo->scode
  444.                         =(SCODE)MAKELONG(EXCEPTION_INVALIDSOUND
  445.                         , PRIMARYLANGID(lcid));
  446.                     FillException(pExcepInfo);
  447.                     return ResultFromScode(DISP_E_EXCEPTION);
  448.                     }
  449.                 //Everything checks out:  save the new value
  450.                 m_pObj->m_lSound=lSound;
  451.                 }
  452.             break;
  453.         case METHOD_BEEP:
  454.             if (!(DISPATCH_METHOD & wFlags))
  455.                 return ResultFromScode(DISP_E_MEMBERNOTFOUND);
  456.             if (0!=pDispParams->cArgs)
  457.                 return ResultFromScode(DISP_E_BADPARAMCOUNT);
  458.             MessageBeep((UINT)m_pObj->m_lSound);
  459.             //The result of this method is the sound we played
  460.             if (NULL!=pVarResult)
  461.                 {
  462.                 VariantInit(pVarResult);
  463.                 V_VT(pVarResult)=VT_I4;
  464.                 V_I4(pVarResult)=m_pObj->m_lSound;
  465.                 }
  466.             break;
  467.         default:
  468.             return ResultFromScode(DISP_E_MEMBERNOTFOUND);
  469.         }
  470.     return NOERROR;
  471.     }
  472. /*
  473.  * FillException
  474.  *
  475.  * Purpose:
  476.  *  Callback function pointed to in IDispatch::Invoke that fills
  477.  *  an EXCEPINFO structure based on the code stored inside
  478.  *  Invoke.  This is a nice mechanism to keep all the management
  479.  *  of error code strings and help IDs centralized in one place,
  480.  *  even across many different automation objects within the same
  481.  *  application.  It also keeps Invoke cleaner.
  482.  *
  483.  * Parameters:
  484.  *  pExcepInfo      EXCEPINFO * to fill.
  485.  *
  486.  * Return Value:
  487.  *  HRESULT         NOERROR if successful, error code otherwise.
  488.  */
  489. HRESULT STDAPICALLTYPE FillException(EXCEPINFO *pExcepInfo)
  490.     {
  491.     SCODE       scode;
  492.     LANGID      langID;
  493.     USHORT      wCode;
  494.     HRESULT     hr;
  495.     LPTSTR      psz;
  496.     LPOLESTR    pszHelp;
  497.     UINT        idsSource;
  498.     UINT        idsException;
  499.     if (NULL==pExcepInfo)
  500.         return ResultFromScode(E_INVALIDARG);
  501.     /*
  502.      * Parts of our implementation that raise exceptions put the
  503.      * WORD exception code in the loword of scode and the LANGID
  504.      * in the hiword.
  505.      */
  506.     scode=pExcepInfo->scode;
  507.     langID=HIWORD(scode);
  508.     wCode=LOWORD(scode);
  509.     //Allocate BSTRs for source and description strings
  510.     psz=(LPTSTR)malloc(1024*sizeof(TCHAR));
  511.     if (NULL==psz)
  512.         return ResultFromScode(E_OUTOFMEMORY);
  513.     hr=NOERROR;
  514.     switch (wCode)
  515.         {
  516.         case EXCEPTION_INVALIDSOUND:
  517.             //Fill in unused information, macro in inole.h
  518.             INITEXCEPINFO(*pExcepInfo);
  519.             pExcepInfo->wCode=wCode;
  520.             /*
  521.              * DispTest and Visual Basic 3 ignore the help file and
  522.              * context ID.  A complete controller such as Visual
  523.              * Basic 4 checks if these fields are set, and if so,
  524.              * displays a Help button in a message box.  If Help
  525.              * is pressed, the controller calls WinHelp with this
  526.              * filename and context ID for complete integration.
  527.              *
  528.              * The sources for beeper.hlp are in
  529.              * inolechap14beephelp along with the actual help
  530.              * file.  For this sample I assume it's on C drive.
  531.              * Normally you'll want to read your own HELPDIR
  532.              * registry entry from under TypeLib and prepend that
  533.              * to the name of the help file, but since this sample
  534.              * doesn't have a type library, that entry doesn't
  535.              * exist so I just hard-code it.
  536.              */
  537.             pExcepInfo->dwHelpContext=HID_SOUND_PROPERTY_LIMITATIONS;
  538.             //Set defaults
  539.             pszHelp=OLETEXT("c:\inole\chap14\beephelp\beep0000.hlp");
  540.             idsSource=IDS_0_EXCEPTIONSOURCE;
  541.             idsException=IDS_0_EXCEPTIONINVALIDSOUND;
  542.             //Get the localized source and exception strings
  543.             switch (langID)
  544.                 {
  545.                 case LANG_GERMAN:
  546.                     idsSource=IDS_7_EXCEPTIONSOURCE;
  547.                     idsException=IDS_7_EXCEPTIONINVALIDSOUND;
  548.                     pszHelp=OLETEXT("c:\inole\chap14\beephelp\beep0007.hlp");
  549.                     break;
  550.                 case LANG_ENGLISH:
  551.                 case LANG_NEUTRAL:
  552.                 default:
  553.                     break;
  554.                 }
  555.             break;
  556.         default:
  557.             hr=ResultFromScode(E_FAIL);
  558.         }
  559.     if (SUCCEEDED(hr))
  560.         {
  561.         pExcepInfo->bstrHelpFile=SysAllocString(pszHelp);
  562.        #ifdef WIN32ANSI
  563.         OLECHAR     szTemp[256];
  564.         LoadString(g_hInst, idsSource, psz, 256);
  565.         MultiByteToWideChar(CP_ACP, 0, psz, -1, szTemp, 256);
  566.         pExcepInfo->bstrSource=SysAllocString(szTemp);
  567.         LoadString(g_hInst, idsException, psz, 256);
  568.         MultiByteToWideChar(CP_ACP, 0, psz, -1, szTemp, 256);
  569.         pExcepInfo->bstrDescription=SysAllocString(szTemp);
  570.        #else
  571.         LoadString(g_hInst, idsSource, psz, 1024);
  572.         pExcepInfo->bstrSource=SysAllocString(psz);
  573.         LoadString(g_hInst, idsException, psz, 1024);
  574.         pExcepInfo->bstrDescription=SysAllocString(psz);
  575.        #endif
  576.         }
  577.     free(psz);
  578.     return hr;
  579.     }