home *** CD-ROM | disk | FTP | other *** search
/ Tricks of the Windows Gam…ming Gurus (2nd Edition) / Disc2.iso / msdn_vcb / samples / vc98 / sdk / com / inole2 / chap14 / beeper1 / beeper.cpp next >
Encoding:
C/C++ Source or Header  |  1996-05-23  |  20.5 KB  |  711 lines

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