/*
* AUTOCLI2.CPP
* Automation Client with Property Pages Chapter 16
*
* Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
*
* Kraig Brockschmidt, Microsoft
* Internet : kraigb@microsoft.com
* Compuserve: >INTERNET:kraigb@microsoft.com
*/
#define INITGUIDS
#include "autocli2.h"
/*
* WinMain
*
* Purpose:
* Main entry point of application.
*/
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hInstPrev
, LPSTR pszCmdLine, int nCmdShow)
{
MSG msg;
PCApp pApp;
SETMESSAGEQUEUE;
pApp=new CApp(hInst, hInstPrev, nCmdShow);
if (NULL==pApp)
return -1;
if (pApp->Init())
{
while (GetMessage(&msg, NULL, 0,0 ))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
delete pApp;
return msg.wParam;
}
/*
* AutoClientWndProc
*
* Purpose:
* Window class procedure. Standard callback.
*/
LRESULT APIENTRY AutoClientWndProc(HWND hWnd, UINT iMsg
, WPARAM wParam, LPARAM lParam)
{
PCApp pApp;
WORD wID;
DISPID dispID, dispIDParam;
DISPPARAMS dp;
VARIANTARG va;
EXCEPINFO exInfo;
UINT uErr;
HRESULT hr;
TCHAR szMsg[80];
pApp=(PCApp)GetWindowLong(hWnd, AUTOCLI2WL_STRUCTURE);
switch (iMsg)
{
case WM_NCCREATE:
pApp=(PCApp)(((LPCREATESTRUCT)lParam)->lpCreateParams);
SetWindowLong(hWnd, AUTOCLI2WL_STRUCTURE, (LONG)pApp);
return (DefWindowProc(hWnd, iMsg, wParam, lParam));
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_COMMAND:
wID=LOWORD(wParam);
switch (wID)
{
case IDM_GETSOUND:
//Find the dispID we need
hr=pApp->NameToID(OLETEXT("Sound"), &dispID);
if (FAILED(hr))
break;
//Get the property
SETNOPARAMS(dp);
hr=pApp->Invoke(dispID, DISPATCH_PROPERTYGET
, &dp, &va, &exInfo, NULL);
if (SUCCEEDED(hr))
{
wsprintf(szMsg, TEXT("Current 'Sound' is 0x%lX")
, va.lVal);
}
else
{
wsprintf(szMsg
, TEXT("Get 'Sound' failed with 0x%lX")
, hr);
}
pApp->Message(szMsg);
break;
case IDM_SETSOUNDDEFAULT:
case IDM_SETSOUNDHAND:
case IDM_SETSOUNDQUESTION:
case IDM_SETSOUNDEXCLAMATION:
case IDM_SETSOUNDASTERISK:
case IDM_SETSOUNDBOGUS:
//Find the dispID we need
hr=pApp->NameToID(OLETEXT("Sound"), &dispID);
if (FAILED(hr))
break;
/*
* Call IDispatch::Invoke passing wID which
* is a sound identifier (IDM_SETSOUNDDEFAULT
* has to set the sound to zero). The "bogus"
* sound should cause an exception.
*/
//Initialize arguments
VariantInit(&va);
va.vt=VT_I4;
va.lVal=(IDM_SETSOUNDDEFAULT==wID)
? 0L : (long)(wID);
/*
* Passing a named DISPID_PROPERTYPUT
* is required when setting properties.
*/
dispIDParam=DISPID_PROPERTYPUT;
SETDISPPARAMS(dp, 1, &va, 1, &dispIDParam);
hr=pApp->Invoke(dispID, DISPATCH_PROPERTYPUT
, &dp, NULL, &exInfo, NULL);
/*
* Success messages come through
* IPropertyNotifySink::OnChanged.
*/
if (FAILED(hr))
{
wsprintf(szMsg
, TEXT("Set 'Sound' failed with 0x%lX"), hr);
}
break;
case IDM_BEEP:
if (NULL==pApp->m_pIDispatch)
break;
hr=pApp->NameToID(OLETEXT("Beep"), &dispID);
if (FAILED(hr))
break;
SETNOPARAMS(dp);
hr=pApp->Invoke(dispID, DISPATCH_METHOD, &dp
, &va, &exInfo, &uErr);
/*
* va will have the sound played as the return
* value of the Beep method.
*/
if (SUCCEEDED(hr))
{
wsprintf(szMsg, TEXT("'Beep' played 0x%lX")
, va.lVal);
}
else
{
wsprintf(szMsg
, TEXT("'Beep' failed with 0x%lX"), hr);
}
pApp->Message(szMsg);
break;
//CHAPTER16MOD
case IDM_PROPERTIES:
pApp->ShowProperties();
break;
case IDM_ENFORCEREADONLY:
pApp->m_fReadOnly=!pApp->m_fReadOnly;
CheckMenuItem(GetMenu(hWnd), wID
, pApp->m_fReadOnly
? MF_CHECKED : MF_UNCHECKED);
break;
//End CHAPTER16MOD
case IDM_EXIT:
PostMessage(hWnd, WM_CLOSE, 0, 0L);
break;
}
break;
default:
return (DefWindowProc(hWnd, iMsg, wParam, lParam));
}
return 0L;
}
/*
* CApp::CApp
* CApp::~CApp
*
* Constructor Parameters: (from WinMain)
* hInst HINSTANCE of the application.
* hInstPrev HINSTANCE of a previous instance.
* nCmdShow UINT specifying how to show the app window.
*
*/
CApp::CApp(HINSTANCE hInst, HINSTANCE hInstPrev
, UINT nCmdShow)
{
m_hInst=hInst;
m_hInstPrev=hInstPrev;
m_nCmdShow=nCmdShow;
m_hWnd=NULL;
m_fInitialized=FALSE;
m_lcid=GetUserDefaultLCID();
m_szHelpDir[0]=(TCHAR)0;
m_pIDispatch=NULL;
//CHAPTER16MOD
m_fReadOnly=FALSE;
m_pSink=NULL;
m_pIConnPt=NULL;
m_dwConn=0L;
//End CHAPTER16MOD
return;
}
CApp::~CApp(void)
{
//CHAPTER16MOD
if (0!=m_dwConn && NULL!=m_pIConnPt)
{
m_pIConnPt->Unadvise(m_dwConn);
ReleaseInterface(m_pIConnPt);
}
ReleaseInterface(m_pSink);
//End CHAPTER16MOD
if (NULL!=m_pIDispatch)
{
m_pIDispatch->Release();
m_pIDispatch=NULL;
}
if (IsWindow(m_hWnd))
DestroyWindow(m_hWnd);
if (m_fInitialized)
CoUninitialize();
return;
}
/*
* CApp::Init
*
* Purpose:
* Initializes an CApp object by registering window classes,
* creating the main window, and doing anything else prone to
* failure such as calling CoInitialize. If this function fails
* the caller should insure that the destructor is called.
*
* Parameters:
* None
*
* Return Value:
* BOOL TRUE if successful, FALSE otherwise.
*/
BOOL CApp::Init(void)
{
WNDCLASS wc;
HRESULT hr;
CHECKVER_OLE;
if (FAILED(CoInitialize(NULL)))
return FALSE;
m_fInitialized=TRUE;
if (!m_hInstPrev)
{
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = AutoClientWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = CBWNDEXTRA;
wc.hInstance = m_hInst;
wc.hIcon = LoadIcon(m_hInst, TEXT("Icon"));
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU);
wc.lpszClassName = TEXT("AUTOCLI");
if (!RegisterClass(&wc))
return FALSE;
}
m_hWnd=CreateWindow(TEXT("AUTOCLI")
, TEXT("Property Page Client"), WS_OVERLAPPEDWINDOW
, 35, 35, 450, 250, NULL, NULL, m_hInst, this);
if (NULL==m_hWnd)
return FALSE;
//Create the beeper object we want to manipulate.
hr=CoCreateInstance(CLSID_Beeper, NULL, CLSCTX_INPROC_SERVER
, IID_IDispatch, (PPVOID)&m_pIDispatch);
if (FAILED(hr))
{
Message(TEXT("Failed to create object--terminating"), MB_OK);
return FALSE;
}
//Try to get the help directory for the object's type information
HelpDirFromCLSID(CLSID_Beeper, m_szHelpDir);
//CHAPTER16MOD
/*
* Try to hook up our IPropertyNotifySink to the object.
* On failure, we'll just gray out "Enforce Read-Only" on
* the menu, but otherwise we continue to run.
*/
m_pSink=new CPropertyNotifySink(this);
if (NULL!=m_pSink)
{
IConnectionPointContainer *pICPC;
HRESULT hr;
m_pSink->AddRef(); //Release destroys it
hr=m_pIDispatch->QueryInterface(IID_IConnectionPointContainer
, (void **)&pICPC);
if (SUCCEEDED(hr))
{
if (SUCCEEDED(pICPC->FindConnectionPoint
(IID_IPropertyNotifySink, &m_pIConnPt)))
{
/*
* This is the only way m_dwConn is set to non-
* zero. We hold onto m_pIConnPt until we close.
*/
m_pIConnPt->Advise(m_pSink, &m_dwConn);
}
pICPC->Release();
}
}
if (0==m_dwConn)
{
EnableMenuItem(GetMenu(m_hWnd), IDM_ENFORCEREADONLY
, MF_DISABLED | MF_GRAYED | MF_BYCOMMAND);
}
//End CHAPTER16MOD
ShowWindow(m_hWnd, m_nCmdShow);
UpdateWindow(m_hWnd);
return TRUE;
}
/*
* CApp::NameToID
*
* Purpose:
* Calls IDispatch::GetIDsOfNames for a single name to determine
* the DISPID to pass to IDispatch::Invoke.
*
* Parameters:
* pszName OLECHAR * to the name to map.
* pDispID DISPID * in which to store the dispID.
*
* Return Value:
* HRESULT Return value of GetIDsOfNames
*/
HRESULT CApp::NameToID(OLECHAR *pszName, DISPID *pDispID)
{
HRESULT hr;
TCHAR szMsg[80];
hr=m_pIDispatch->GetIDsOfNames(IID_NULL, &pszName, 1
, m_lcid, pDispID);
if (FAILED(hr))
{
wsprintf(szMsg
, TEXT("GetIDsOfNames on '%s' failed with 0x%lX")
, pszName, hr);
Message(szMsg);
}
return hr;
}
/*
* CApp::Invoke
*
* Purpose:
* Calls IDispatch::Invoke using the interface pointer we hold
* and using some default parameters. All the other parameters
* to this function are passed straight to Invoke
*
* Return Value:
* HRESULT Return value of Invoke. If DISP_E_EXCEPTION,
* this function generates the appropriate
* message box.
*/
HRESULT CApp::Invoke(DISPID dispID, WORD wFlags, DISPPARAMS *pdp
, VARIANT *pva, EXCEPINFO *pExInfo, UINT *puErr)
{
HRESULT hr;
LPTSTR pszMsg=NULL;
LPTSTR pszFmt=NULL;
UINT uRet;
UINT uStyle;
TCHAR szSource[80];
if (NULL==m_pIDispatch)
return ResultFromScode(E_POINTER);
hr=m_pIDispatch->Invoke(dispID, IID_NULL, m_lcid, wFlags
, pdp, pva, pExInfo, puErr);
if (DISP_E_EXCEPTION!=GetScode(hr))
return hr;
//If we're given a deferred filling function, fill now.
if (NULL!=pExInfo->pfnDeferredFillIn)
(*pExInfo->pfnDeferredFillIn)(pExInfo);
/*
* To handle the exception, display a message box with the
* controller's name in the caption and a message:
*
* "Error <code> in <source>: <description>"
*
* where <error> is the exception code in pExInfo->wCode or
* pExInfo->scode, <source> is the value of the ProdID
* in pExInfo->bstrSource and <description> is in
* pExInfo->bstrDescription.
*
* For simplicity, we assume that if description is set, so is
* source.
*
* To be complete, if pExInfo->bstrHelpFile is non-NULL,
* display a Help button. If Help is pressed, launch WinHelp
* with that filename and pExInfo->dwHelpContext.
*/
//Go get the real source name from the ProgID
lstrcpy(szSource, TEXT("Unknown"));
if (NULL!=pExInfo->bstrSource)
{
LONG lRet;
//If this doesn't work, we'll have "Unknown" anyway
#ifdef WIN32ANSI
char szTemp[80];
WideCharToMultiByte(CP_ACP, 0, pExInfo->bstrSource, -1
, szTemp, 80, NULL, NULL);
RegQueryValue(HKEY_CLASSES_ROOT, szTemp, szSource, &lRet);
#else
RegQueryValue(HKEY_CLASSES_ROOT, pExInfo->bstrSource
, szSource, &lRet);
#endif
SysFreeString(pExInfo->bstrSource);
}
if (NULL!=pExInfo->bstrDescription)
{
pszFmt=(LPTSTR)malloc(CCHSTRINGMAX*sizeof(TCHAR));
#ifdef WIN32ANSI
UINT cch;
char *pszDesc;
cch=wcslen(pExInfo->bstrDescription);
pszDesc=(LPSTR)malloc(cch);
WideCharToMultiByte(CP_ACP, 0, pExInfo->bstrDescription, -1
, pszDesc, cch, NULL, NULL);
pszMsg=(LPTSTR)malloc(CCHSTRINGMAX+lstrlen(szSource)+cch);
#else
pszMsg=(LPTSTR)malloc((CCHSTRINGMAX+lstrlen(szSource)
+lstrlen(pExInfo->bstrDescription))*sizeof(TCHAR));
#endif
if (0==pExInfo->wCode)
{
//Formatting for SCODE errors
LoadString(m_hInst, IDS_MESSAGEEXCEPTIONSCODE, pszFmt
, CCHSTRINGMAX);
wsprintf(pszMsg, pszFmt, (long)pExInfo->scode
, (LPTSTR)szSource
#ifdef WIN32ANSI
, pszDesc);
#else
, (LPTSTR)pExInfo->bstrDescription);
#endif
}
else
{
//Formatting for wCode errors
LoadString(m_hInst, IDS_MESSAGEEXCEPTION, pszFmt
, CCHSTRINGMAX);
wsprintf(pszMsg, pszFmt, (UINT)pExInfo->wCode
, (LPTSTR)szSource
#ifdef WIN32ANSI
, pszDesc);
#else
, (LPTSTR)pExInfo->bstrDescription);
#endif
}
free(pszFmt);
}
else
{
pszMsg=(LPTSTR)malloc(CCHSTRINGMAX*sizeof(TCHAR));
LoadString(m_hInst, IDS_MESSAGEUNKNOWNEXCEPTION, pszMsg
, CCHSTRINGMAX);
}
/*
* In Windows 95 there is an MB_HELP style that we use in the
* exception message if pExInfo->bstrHelpFile is non-NULL. For
* Windows NT 3.5 and Windows 3.1x, we'll just use a Cancel
* button to demonstrate since making a Help button is too much
* effort for this sample (requires a custom dialog box and code
* to resize the dialog based on the length of the description
* string which MessageBox does automatically...)
*/
uStyle=MB_OK | MB_ICONEXCLAMATION;
#ifdef MB_HELP
uStyle |=(NULL!=pExInfo->bstrHelpFile) ? MB_HELP : 0;
#else
uStyle |=(NULL!=pExInfo->bstrHelpFile) ? MB_OKCANCEL : 0;
#endif
uRet=Message(pszMsg, uStyle);
if (NULL!=pszMsg)
free(pszMsg);
#ifdef MB_HELP
if (IDHELP==uRet)
#else
if (IDCANCEL==uRet)
#endif
{
TCHAR szHelp[512];
/*
* If we read a HELPDIR, prepend it to the file. Otherwise
* just use the string we got since that's all we have.
*/
if ((TCHAR)0!=m_szHelpDir[0])
{
#ifdef WIN32ANSI
char szTemp[256];
WideCharToMultiByte(CP_ACP, 0, pExInfo->bstrHelpFile
, -1, szTemp, 256, NULL, NULL);
wsprintf(szHelp, TEXT("%s\\%s"), m_szHelpDir, szTemp);
#else
wsprintf(szHelp, TEXT("%s\\%s"), m_szHelpDir
, pExInfo->bstrHelpFile);
#endif
}
else
#ifdef WIN32ANSI
WideCharToMultiByte(CP_ACP, 0, pExInfo->bstrHelpFile
, -1, szHelp, 512, NULL, NULL);
#else
lstrcpy(szHelp, pExInfo->bstrHelpFile);
#endif
WinHelp(NULL, szHelp, HELP_CONTEXT, pExInfo->dwHelpContext);
}
//We're responsible for cleaning up the strings.
SysFreeString(pExInfo->bstrDescription);
SysFreeString(pExInfo->bstrHelpFile);
return ResultFromScode(DISP_E_EXCEPTION);
}
/*
* CApp::Message (overloaded)
*
* Purpose:
* Scribbles a message onto the client area of the window
* or displays the message in a message box if a message
* box style is given.
*
* Parameters:
* pszMsg LPTSTR to the message string.
* uStyle (message box only) UINT style bits
*
* Return Value:
* UINT Return value of MessageBox (MessageBox version
* only)
*/
void CApp::Message(LPTSTR pszMsg)
{
HDC hDC;
RECT rc;
hDC=GetDC(m_hWnd);
GetClientRect(m_hWnd, &rc);
SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));
/*
* We'll just be sloppy and clear the whole window as
* well as write the string with one ExtTextOut call.
* No word wrapping here...
*/
ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rc, pszMsg
, lstrlen(pszMsg), NULL);
ReleaseDC(m_hWnd, hDC);
return;
}
UINT CApp::Message(LPTSTR pszMsg, UINT uStyle)
{
return MessageBox(m_hWnd, pszMsg, TEXT("Automation Client")
, uStyle);
}
/*
* HelpDirFromCLSID
*
* Purpose:
* Given a CLSID, looks up the TypeLib entry in the registry then
* extracts the HELPDIR entry for that type information, storing
* the path in pszPath.
*
* Parameters:
* clsID CLSID of the object we're looking up.
* pszPath LPTSTR buffer in which to store the directory.
*
* Return Value:
* None
*/
void HelpDirFromCLSID(CLSID clsID, LPTSTR pszPath)
{
TCHAR szCLSID[80];
TCHAR szKey[512];
UINT cch;
long lRet;
if (NULL==pszPath)
return;
*pszPath=0;
cch=sizeof(szCLSID)/sizeof(TCHAR);
StringFromGUID2(clsID, szCLSID, cch);
wsprintf(szKey, TEXT("CLSID\\%s\\TypeLib"), szCLSID);
//Get LIBID from under CLSID
if (ERROR_SUCCESS==RegQueryValue(HKEY_CLASSES_ROOT, szKey
, szCLSID, &lRet))
{
//Get HELPDIR from under TypeLib
wsprintf(szKey, TEXT("TypeLib\\%s\\HELPDIR"), szCLSID);
RegQueryValue(HKEY_CLASSES_ROOT, szKey, pszPath, &lRet);
}
return;
}
/*
* CApp::ShowProperties
*
* Purpose:
* Invokes the standard property page frame using the
* property pages specified by the object we maintain
* (only a single one).
*/
void CApp::ShowProperties(void)
{
ISpecifyPropertyPages *pISPP;
CAUUID caGUID;
HRESULT hr;
if (FAILED(m_pIDispatch->QueryInterface
(IID_ISpecifyPropertyPages, (void **)&pISPP)))
{
Message(TEXT("Object has no property pages"));
return;
}
hr=pISPP->GetPages(&caGUID);
pISPP->Release();
if (FAILED(hr))
{
Message(TEXT("Failed to retrieve property page GUIDs"));
return;
}
hr=OleCreatePropertyFrame(m_hWnd, 10, 10, OLETEXT("Beeper")
, 1, (IUnknown **)&m_pIDispatch, caGUID.cElems
, caGUID.pElems, m_lcid, 0L, NULL);
if (FAILED(hr))
Message(TEXT("OleCreatePropertyFrame failed"));
//Free the GUIDs
CoTaskMemFree((void *)caGUID.pElems);
return;
}
/***
*** IPropertyNotifySink Object
***/
/*
* CPropertyNotifySink::CPropertyNotifySink
* CPropertyNotifySink::~CPropertyNotifySink
*
* Constructor Parameters:
* pApp PCApp of the application.
*/
CPropertyNotifySink::CPropertyNotifySink(PCApp pApp)
{
m_cRef=0;
m_pApp=pApp;
return;
}
CPropertyNotifySink::~CPropertyNotifySink(void)
{
return;
}
/*
* CPropertyNotifySink::QueryInterface
* CPropertyNotifySink::AddRef
* CPropertyNotifySink::Release
*/
STDMETHODIMP CPropertyNotifySink::QueryInterface(REFIID riid
, LPVOID *ppv)
{
*ppv=NULL;
if (IID_IUnknown==riid || IID_IPropertyNotifySink==riid)
*ppv=this;
if (NULL!=*ppv)
{
((LPUNKNOWN)*ppv)->AddRef();
return NOERROR;
}
return ResultFromScode(E_NOINTERFACE);
}
STDMETHODIMP_(ULONG) CPropertyNotifySink::AddRef(void)
{
return ++m_cRef;
}
STDMETHODIMP_(ULONG) CPropertyNotifySink::Release(void)
{
if (0!=--m_cRef)
return m_cRef;
delete this;
return 0;
}
/*
* CPropertyNotifySink::OnChanged
*
* Purpose:
* Notifies this sink that the property identified with dispID
* has changed in whatever object this sink is connected to.
*
* Parameters:
* dispID DISPID of the property that changed,
* which can be DISPID_UNKNOWN for unspecified
* changes to multiple properties.
*
* Return Value:
* HRESULT NOERROR always.
*/
STDMETHODIMP CPropertyNotifySink::OnChanged(DISPID dispID)
{
TCHAR szTemp[200];
wsprintf(szTemp
, TEXT("OnChanged notification received for DISPID=%lu")
, dispID);
m_pApp->Message(szTemp);
return NOERROR;
}
/*
* CPropertyNotifySink::OnRequestEdit
*
* Purpose:
* Notifies this sink that the property identified with dispID
* is about to change and that the sink can prevent the change
* if desired. This can be used to enforce read-only states or
* to save prior states before the change occurs.
*
* Parameters:
* dispID DISPID of the property that is changing
* which can be DISPID_UNKNOWN for multiple
* properties.
*
* Return Value:
* HRESULT NOERROR if the property can change, S_FALSE
* if it cannot change, error code otherwise.
*/
STDMETHODIMP CPropertyNotifySink::OnRequestEdit(DISPID dispID)
{
TCHAR szTemp[200];
wsprintf(szTemp
, TEXT("OnRequestEdit received for DISPID=%lu"), dispID);
m_pApp->Message(szTemp);
return ResultFromScode(m_pApp->m_fReadOnly ? S_FALSE : S_OK);
}