When developing web-embedded software applications, COM technology is often used to provide an interface to which other modules can connect. These interfaces are universal and can be used in different programming environments and even across different processes and machines. Moreover, COM objects can fire events which can be captured by another COM object or in a regular C++ program. I have noticed that knowledge about COM is not easily conceived by just reading about it, but rather by practical experience. The objective of this document is to provide a practical guide to COM by showing code examples and by explaining how to set up ATL projects. Only a few topics related to COM will be addressed. Still, I hope this gives enough hints how to create your own COM objects and how to use/extend existing COM objects. This guide has been written for Microsoft Visual C++ 6.0 (SP5).
There are a some tools which are very useful when working with COM. The most commonly used tools are "OLEview.exe" and "GUIDgen.exe", which can be found in "C:\Program Files\Microsoft Visual Studio\Common\Tools" . The latest version of these tools can be found in "C:\Program Files\Microsoft SDK\Bin" after installing the Microsoft Platform SDK.
In this section a few COM examples will be shown by using the color picker from the Common Controls. First we show how this would typically be done in C++ using the Win32 application programming interface (API).
#include <windows.h> #pragma comment(lib, "comdlg32.lib") #include <commdlg.h> static COLORREF acrCustClr[16]; // array of custom colors ZeroMemory(acrCustClr, 16*sizeof(COLORREF)); CHOOSECOLOR cc; ZeroMemory(&cc, sizeof(CHOOSECOLOR)); cc.lStructSize = sizeof(cc); cc.lpCustColors = (LPDWORD)acrCustClr; if (TRUE==::ChooseColor(&cc)) COLORREF col = cc.rgbResult;
Note that in this code example it is necessary to link statically with the common control library. In the following we achieve the same thing in different environments using COM.
Dim obj Set obj = WScript.CreateObject("MSComDlg.CommonDialog") obj.ShowColor WScript.Echo(obj.Color) Set obj = Nothing
var obj = WScript.CreateObject("MSComDlg.CommonDialog"); obj.ShowColor(); WScript.Echo(obj.Color);
<HTML> <BODY onLoad="obj.ShowColor(); alert(obj.Color);"> <OBJECT id="obj" classid="CLSID:F9043C85-F6F2-101A-A3C9-08002B2F49FB"> </OBJECT> </BODY> </HTML>
#include <atlbase.h> extern CComModule _Module; #include <atlcom.h> ::CoInitialize(NULL); { CComPtr<IUnknown> spUnk; spUnk.CoCreateInstance(L"MSComDlg.CommonDialog"); CComPtr<IDispatch> spDisp = CComQIPtr<IDispatch, &IID_IDispatch>(spUnk); CComDispatchDriver spDrv(spDisp); spDrv.Invoke0(L"ShowColor"); CComVariant vOut; spDrv.GetPropertyByName(L"Color", &vOut); COLORREF col = vOut.ulVal; } ::CoUninitialize();
The examples as shown above all use late binding. COM technology enables the developer to create objects by ProgID or by CLSID and interface with them without neeeding a header file or library for that particular type. The drawback is that it will create some overhead, but the major advantage is that provides a very flexible mechanism for developing component-based applications.
There are a couple of convenient classes available. The most commonly used are:
atlbase.h | |
---|---|
CComPtr | wrapper class for COM pointers (does also reference count) |
CComQIPtr | same as CComPtr but also provides interface checking |
CComBSTR | wrapper class for UNICODE strings (wide character) |
CComVariant | wrapper class for the VARIANT container |
atlcom.h | |
CComDispatchDriver | wrapper class for the IDispatch interface |
Note that for some types the class ComVariant only stores a pointer to the data, therefore the following expression is wrong:
ComVariant vIn(CComBSTR(L"abc").m_str); // wrong!
because vIn.bstrVal will point to data, which has been rendered invalid by the destructor of CComBSTR.
COM objects have an hierarchy of intefaces. The top-level interface is called IUnknown. In order to call methods on a COM object in the case of late binding we need to query the COM object for the IDispatch interface. This interface is derived from IUnknown and it allows access to properties and methods. The wrapper class CComDispatchDriver can be constructed from a pointer to an IDispatch interface. Next we can get and set properties in the following way:
CComDispatchDriver spDrv(spDisp); // spDisp is of type IDispatch // set property CComVariant vIn(75.f); spDrv.PutPropertyByName(L"Value", &vIn); // get property CComVariant vOut; spDrv.GetPropertyByName(L"Value", &vOut); float f = vOut.fltVal;
Calling methods of a COM object is done in a very similar way, expect for the fact that special care should be taken with the function arguments. Note that in the case of more than two arguments one should use InvokeN and pass the parameters in reversed order.
CComDispatchDriver spDrv(spDisp); // spDisp is of type IDispatch // call method without parameters spDrv.Invoke0(L"MyFunc0"); // call method with 2 parameters CComVariant vIn1(L"a"); CComVariant vIn2(false); spDrv.Invoke2(L"MyFunc1", &vIn1, &vIn2); // call method with 3 parameters (in reversed order!) const int NUM_PARAMS = 3; CComVariant vParams[NUM_PARAMS] = {4.5f, 3.1f, L"a"}; spDrv.InvokeN(L"MyFunc2", vParams, NUM_PARAMS);
Get the type information from a type library:
#include <atlbase.h> #import <comdlg32.ocx> raw_interfaces_only, named_guids, no_namespace ::CoInitialize(NULL); { CComPtr<ICommonDialog> spDlg; HRESULT hr = spDlg.CoCreateInstance(L"MSComDlg.CommonDialog"); if (SUCCEEDED(hr) && spDlg) { hr = spDlg->ShowColor(); OLE_COLOR col; hr = spDlg->get_Color(&col); } } ::CoUninitialize();
For some very common objects, the SDK provides header files in which the type information is defined:
#include <atlbase.h> #include <msxml2.h> ::CoInitialize(NULL) { CComPtr<IXMLDOMDocument2> spDoc; HRESULT hr = spDoc.CoCreateInstance(L"Msxml2.DOMDocument"); if (SUCCEEDED(hr) && spDoc) { VARIANT_BOOL bSucceeded; hr = spDoc->loadXML(L"<NODE>some text</NODE>", &bSucceeded); } } ::CoUninitialize();
There are some utility functions to convert between the ProgID, CLSID and string representation of a GUID.
#include <atlbase.h> #include <exdisp.h> { BSTR lpsz = L"{8856F961-340A-11D0-A96B-00C04FD705A2}"; CLSID c; HRESULT hr = ::CLSIDFromString(lpsz, &c); }
{ CLSID c; HRESULT hr = ::CLSIDFromProgID(L"Shell.Explorer", &c); CComBSTR b(c); // has StringFromCLSID() in CTOR ::MessageBoxW(NULL, b.m_str, L"", 0); }
{ CLSID c = CLSID_WebBrowser; BSTR lpsz = NULL; HRESULT hr = ::ProgIDFromCLSID(c, &lpsz); ::MessageBoxW(NULL, lpsz, L"", 0); ::CoTaskMemFree(lpsz); }
{ GUID g; HRESULT hr = ::CoCreateGuid(&g); BSTR lpsz = NULL; HRESULT hr = ::StringFromCLSID(g, &lpsz); ::MessageBoxW(NULL, lpsz, L"", 0); ::CoTaskMemFree(lpsz); }
To capture events from an ActiveX control one employs the technique of event binding. In this example we use the Microsoft Slider Control. This control supports event firing through its dispinterface (ISliderEvents). We can capture the "Change" event in the following way.
<HTML> <BODY> <SCRIPT type="text/javascript" for="slider" event="Change()"> alert(slider.Value); </SCRIPT> <OBJECT id="slider" classid="CLSID:F08DF954-8592-11D1-B16A-00C0F0283628"> </OBJECT> </BODY> </HTML>
Further on in this guide we will demonstrate how this would typically be done in C++.
One typically uses the "ATL COM AppWizard" in DevStudio to create an ActiveX control. These are the steps necessary to create such a control from scratch:
In "ClassView" you will see that a new class has been added representing the control. By right-clicking on the interface symbols you can add methods and properties to the control. Add a method called "SomeMethod" without any parameters for the sake of simplicity.
A HTML file will be generated by the wizard containing an OBJECT tag with the GUID for the ActiveX control. You can test the method you have just added in the following way:
<HTML> <BODY onLoad="MyCtrl.SomeMethod();"> <OBJECT id="MyCtrl" classid="CLSID:E7E1DA55-D255-46DD-923D-1D67947C98DF"> </OBJECT> </BODY> </HTML>
Note that when one tries to call a method of the ActiveX control via JScript, the following warning will be given:" An ActiveX control on this page might be unsafe to interact with other parts of the page. Do you want to allow this intercation? ". In order to prevent this warning from appearing, just edit the control's header file.
Add the IObjectSafetyImpl interface to the multiple inheritance list:
public IObjectSafetyImpl<CMyCtrl, INTERFACESAFE_FOR_UNTRUSTED_CALLER | INTERFACESAFE_FOR_UNTRUSTED_DATA>
and update the COM map as follows:
COM_INTERFACE_ENTRY(IObjectSafety)
It is convenient to initialize an ActiveX control via the PARAM tag inside the OBJECT tag in HTML. To this end properties first need to be added to the ActiveX control for both get and put operations.
<OBJECT id="MyCtrl" classid="CLSID:34D67F3C-E79E-4574-809C-4BD8D12DBBC3"> <PARAM name="ShowListView" value="yes"> <PARAM name="TextColor" value="255"> <PARAM name="SetBkColor" value="16711680"> </OBJECT>
The IPersistPropertyBagImpl interface must be added to the multiple inheritance list:
public IPersistPropertyBagImpl<CMyCtrl>
and update the COM map:
COM_INTERFACE_ENTRY(IPersistPropertyBag)
The property map must be filled in the following way:
BEGIN_PROP_MAP(CMyCtrl) PROP_ENTRY("ShowListView", 7, CLSID_NULL) PROP_ENTRY("TextColor", 8, CLSID_NULL) PROP_ENTRY("SetBkColor", 9, CLSID_NULL) PROP_DATA_ENTRY("SomeValue", m_dwValue, VT_UI4) // no put/get methods required END_PROP_MAP() DWORD m_dwValue;
where the second parameter represents the dispid of the property as defined in the IDL file.
Since the ActiveX control is web-embedded we can acquire a handle to it via the web browser. Suppose we have another ActiveX control named CAnotherCtrl from which we want to acquire a handle to CMyCtrl.
#include <mshtml.h> LRESULT CAnotherCtrl::OnCreate(/*parameter list skipped*/) { CComPtr<IOleContainer> spContainer; this->m_spClientSite->GetContainer(&spContainer); CComPtr<IHTMLDocument2> spDoc = CComQIPtr<IHTMLDocument2>(spContainer); CComPtr<IHTMLElementCollection> spColl; spDoc->get_all(&spColl); CComPtr<IDispatch> spDisp; spColl->item(CComVariant("MyCtrl"), CComVariant((long)0), &spDisp); CComPtr<IHTMLObjectElement> spObj = CComQIPtr<IHTMLObjectElement>(spDisp); return S_OK; }
Another way of passing a handle is to use a JScript method, in which case you need to pass the path to the element, like e.g. SetProp(document.obj) which will be of type IDispatch.
#import <MyCtrl.dll> raw_interfaces_only, named_guids, no_namespace CComPtr<IDispatch> spDisp; // a handle to the ActiveX control CComPtr<IMyCtrl> spCtrl = CComQIPtr<IMyCtrl, &IID_IMyCtrl>(spDisp); if (spCtrl) spCtrl->SomeMethod();
CComPtr<IDispatch> spCtrl; // a handle to the ActiveX control CComDispatchDriver spDrv(spCtrl); spDrv.Invoke0(L"SomeMethod"));
First create an ActiveX control from scratch as described in Section 3. Next,
create an ActiveX control inside this control to act as server. In this example
we will create an instance of the Windows Media Player. In the following we
will describe how the client should be modified to add event sink support.
In the OnCreate method of the class CMyCtrl create an instance of the WMP and
subscribe to the server:
AtlAxWinInit(); RECT rc; this->GetClientRect(&rc); DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN; DWORD dwExStyle = WS_EX_CLIENTEDGE; CAxWindow win; win.Create(m_hWnd, rc, _T("WMPlayer.OCX"), dwStyle, dwExStyle); CComPtr<IWMPPlayer3> spPlayer; win.QueryControl(&spPlayer); this->DispEventAdvise(spPlayer); // subscribe
In the header file for MyCtrl add the following classes to the multiple inheritance list:
public IObjectSafetyImpl<CMyCtrl, INTERFACESAFE_FOR_UNTRUSTED_CALLER>, public IProvideClassInfo2Impl<&CLSID_MyCtrl, &IID_IMyCtrl, &LIBID_MYWMPLib>, public IDispEventImpl<MYID, CMyCtrl, &DIID__WMPOCXEvents, &LIBID_WMPLib, 1, 0>
Add the following entries to the COM map:
COM_INTERFACE_ENTRY(IObjectSafety) COM_INTERFACE_ENTRY(IProvideClassInfo) COM_INTERFACE_ENTRY(IProvideClassInfo2) COM_INTERFACE_ENTRY_IID(DIID__WMPOCXEvents, IDispatch)
Add the following import statement to header file StdAfx.h:
#import <wmp.dll> raw_interfaces_only, named_guids, no_namespace
Now specify which events the sink must capture and use OLEview.exe to find the correct DISPID:
BEGIN_SINK_MAP(CMyWMPCtrl) SINK_ENTRY_EX(MYID, DIID__WMPOCXEvents, 0x13ed, OnPlayStateChange) SINK_ENTRY_EX(MYID, DIID__WMPOCXEvents, 0x16aa, OnMediaChange) END_SINK_MAP()
where 0x13ed is the DISPID of the PlayStateChange event. Declare the callbacks as follows:
void STDMETHODCALLTYPE OnPlayStateChange(long NewState); void STDMETHODCALLTYPE OnMediaChange(IDispatch* Item);
Of course in OnDestroy the counter-part of DispEventAdvise (DispEventUnadvise) must be called.
The following steps detail the modifications necessary to an existing ActiveX control in order to support firing an event through a dispinterface. If you were creating a new ActiveX control from scratch, you would make it support connection points by choosing that option in the ATL Object Wizard. In this case you will duplicate the effects of choosing that option by adding that support manually.
[ uuid(AEB864F2-2CF9-45a8-A91E-FD67D5F103D9), helpstring("_IMyCtrlEvents Interface") ] dispinterface _IMyCtrlEvents { properties: methods: };
[ uuid(22D4EC6A-ACDC-4829-9C91-AD403FC4C1E1), helpstring("MyCtrl Class") ] coclass MyCtrl { [default] interface IMyCtrl; [default, source] dispinterface _IMyCtrlEvents; };
public IObjectSafetyImpl<CMyCtrl,INTERFACESAFE_FOR_UNTRUSTED_CALLER>, public CProxy_IMyCtrlEvents<CMyCtrl>, public IConnectionPointContainerImpl<CMyCtrl>, public IProvideClassInfo2Impl<&CLSID_MyCtrl, &IID_IMyCtrl, &LIBID_MYCTRLLib>
COM_INTERFACE_ENTRY(IObjectSafety) COM_INTERFACE_ENTRY_IMPL(IConnectionPointContainer) COM_INTERFACE_ENTRY(IProvideClassInfo) COM_INTERFACE_ENTRY(IProvideClassInfo2)
BEGIN_CONNECTION_POINT_MAP(CMyCtrl) CONNECTION_POINT_ENTRY(DIID__IMyCtrlEvents) END_CONNECTION_POINT_MAP()
CProxy_IMyCtrlEvents<CMyCtrl>::Fire_Finished();
Although I perfer to use the ATL library as little as possible, it has some very convenient window classes, which allow you to host ActiveX control much like is done in MFC. To this end one can add an instance of the template class CAxDialogImpl using the ATL Object Wizard. The wizard will add a dialog resource to your project.
class CMyDlg : public CAxDialogImpl<CMyDlg> { public: enum {IDD = IDD_MYDLG}; BEGIN_MSG_MAP(CMyDlg) MESSAGE_HANDLER(WM_INITDIALOG, OnInit) COMMAND_ID_HANDLER(IDOK, OnOK) END_MSG_MAP() LRESULT OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); LRESULT OnOK(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled); };
Next choose "Insert ActiveX Control" from the menu and select "Microsoft Web Browser"
LRESULT CMyDlg::OnInit(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { CComPtr<IUnknown> spUnk; GetDlgControl(IDC_IE, IID_IUnknown, reinterpret_cast<void**>(&spUnk)); CComPtr<IWebBrowser2> spBrowser = CComQIPtr<IWebBrowser2>(spUnk); spBrowser->Navigate(L"about:blank", NULL, NULL, NULL, NULL); return 1L; } LRESULT CMyDlg::OnOK(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) { EndDialog(wID); return 0L; }
For hosting ActiveX controls in a Win32 application we use the class CAxWindow from the ATL library. Add the following to your StdAfx.h header file:
#include <atlbase.h> extern CComModule _Module; #include <atlcom.h> #include <atlctl.h> #include <atlhost.h> //#pragma comment (lib, "atl.lib")
Now modify the WinMain function and create a windowed ActiveX control by ProgID or by CLSID:
#include "StdAfx.h" CComModule _Module; int APIENTRY WinMain(/*parameter list skipped*/) { _Module.Init(0, hInstance); AtlAxWinInit(); { RECT rc = {0, 0, 800, 600}; CAxWindow win; DWORD dwStyle = WS_CAPTION | WS_POPUP | WS_VISIBLE; win.Create(NULL, rc, _T("ShockwaveFlash.ShockwaveFlash"), dwStyle); CComPtr<IUnknown> spUnk; win.QueryControl(&spUnk); // late binding CComPtr<IDispatch> spDisp = CComQIPtr<IDispatch, &IID_IDispatch>(spUnk); CComDispatchDriver spDrv(spDisp); CComVariant vIn1((int)0); CComVariant vIn2(L"C:\\WINDOWS\\Help\\Tours\\mmTour\\intro.swf"); spDrv.Invoke2(L"LoadMovie", &vIn1, &vIn2); MSG msg; while (::GetMessage(&msg, NULL, 0, 0)) {::TranslateMessage(&msg); ::DispatchMessage(&msg);} } AtlAxWinTerm(); _Module.Term(); return 0; }
The control can be subclassed in the following way:
::SetWindowLong(win.m_hWnd, GWL_WNDPROC, (LPARAM)(WNDPROC)lpfn);
When an ActiveX has been created as described in Section "Creating a web-embedded ActiveX control", it doesn't yet have the appropriate interface to be embedded in a Win32 window or dialog. To this end the interface needs to be extended. Add the following classes to the multiple inheritance list:
public IDataObjectImpl<CMyCtrl>, public IProvideClassInfo2Impl<&CLSID_MyCtrl, &IID_IMyCtrl, &LIBID_MYWMPLib>
and update the COM map as follows:
COM_INTERFACE_ENTRY(IDataObject) COM_INTERFACE_ENTRY(IProvideClassInfo) COM_INTERFACE_ENTRY(IProvideClassInfo2)
The following template class is very handy when you want to sink events from an event server. This class has a CAxWindow data member, which is used for hosting the ActiveX control, and it is derived from CComPtr for direct access to methods of the COM interface. The IDispEventImpl interface allows for the actual sinking of events after you have subscribed to it.
template <class T, class Interface> class CAxControl : public CComPtr<Interface>, public IDispEventImpl<0, T> { public: HRESULT Create(HWND hWnd, RECT rc, const TCHAR* progid) { DWORD dwStyle = WS_CHILD | WS_VISIBLE; if (!m_Win.Create(hWnd, rc, progid, dwStyle)) return E_FAIL; HRESULT hr = m_Win.QueryControl(&(*this)); if (SUCCEEDED(hr)) hr = AtlGetObjectSourceInterface(*this, &m_libid, &m_iid, &m_wMajorVerNum, &m_wMinorVerNum); return hr; } HRESULT Subscribe() {return DispEventAdvise(*this, &m_iid);} HRESULT Unsubscribe() {return DispEventUnadvise(*this, &m_iid);} protected: CAxWindow m_Win; };
By adding entries to the sink map, you can specify by DISPID which events should be sinked:
#import <mscomctl.ocx> raw_interfaces_only, named_guids, no_namespace class MySlider : public CAxControl<MySlider, ISlider> { public: BEGIN_SINK_MAP(MySlider) SINK_ENTRY(0, 0x01, OnScroll) SINK_ENTRY(0, 0x02, OnChange) END_SINK_MAP() STDMETHOD(OnScroll)(); STDMETHOD(OnChange)(); }; STDMETHODIMP MySlider::OnScroll() { long val; (*this)->get_Value(&val); return S_OK; }
Sink the Internet Explorer for the event DISPID_NAVIGATECOMPLETE2 after initializing the browser with the URL "about:blank" and retrieve a handle to the HTML document via the method IWebBrowser2::get_Document().
#include <mshtml.h> CComPtr<IHTMLDocument2> spDoc; // handle to a HTML document TCHAR szHTMLText[] = _T("<HTML><HEAD><STYLE type=\"text/css\">") _T("BODY {margin: 0px; overflow: hidden}") _T("</STYLE></HEAD><BODY>") _T("<IMAGE src=\"file.jpg\" width=\"100\" height=\"100\">") _T("</BODY></HTML>"); size_t cchLength = _tcsclen(szHTMLText); HGLOBAL hHTMLText = ::GlobalAlloc(GPTR, cchLength+1); if (hHTMLText) { _tcscpy((TCHAR*)hHTMLText, szHTMLText); CComPtr<IStream> pStream; ::CreateStreamOnHGlobal(hHTMLText, FALSE, &pStream); CComPtr<IPersistStreamInit> pPrst = CComQIPtr<IPersistStreamInit>(spDoc); pPrst->InitNew(); pPrst->Load(pStream); // load in-memory HTML content into the document pPrst.Release(); pStream.Release(); ::GlobalFree(hHTMLText); } CComPtr<IPersistFile> spFile = CComQIPtr<IPersistFile>(spDoc); spFile->Save(L"C:\\test.htm", FALSE); // save content to file spFile.Release();
#import <flash.ocx> raw_interfaces_only, named_guids using namespace ShockwaveFlashObjects; RECT rc = {0,0,800,600}; CAxWindow win; win.Create(NULL, rc, _T("ShockwaveFlash.ShockwaveFlash"), WS_POPUP|WS_VISIBLE); CComPtr<IShockwaveFlash> spFlash; win.QueryControl(&spFlash); // early binding spFlash->put_Menu(VARIANT_FALSE); spFlash->LoadMovie(0, L"C:\\WINDOWS\\Help\\Tours\\mmTour\\intro.swf");
#include <atlbase.h> #include <urlmon.h> #pragma comment(lib, "urlmon.lib") CComPtr<IStream> spStrm; HRESULT hr = ::URLOpenBlockingStream(NULL, "file.jpg", &spStrm, 0, NULL); if (SUCCEEDED(hr) && spStrm) { CComPtr<IPicture> spPic; // no need to call CoCreateInstance() BOOL bKeep = FALSE; hr = ::OleLoadPicture(spStrm, 0, !bKeep, IID_IPicture, (void**)&spPic); spStrm.Release(); if (SUCCEEDED(hr) && spPic) { spPic->put_KeepOriginalFormat(bKeep); HBITMAP hBmp = NULL; hr = spPic->get_Handle((OLE_HANDLE*)&hBmp); if (SUCCEEDED(hr) && hBmp) { BITMAP bm; ZeroMemory(&bm, sizeof(BITMAP)); ::GetObject(hBmp, sizeof(BITMAP), &bm); } } }
Also supports WMP, BMP and GIF format.
#import <wmp.dll> raw_interfaces_only, named_guids, no_namespace RECT rc = {0,0,800,600}; CAxWindow win; win.Create(NULL, rc, _T("WMPlayer.OCX"), WS_POPUP | WS_VISIBLE); CComPtr<IWMPPlayer3> spPlayer; win.QueryControl(&spPlayer); // early binding spPlayer->put_uiMode(L"none"); spPlayer->put_fullScreen(VARIANT_FALSE); spPlayer->put_enableContextMenu(VARIANT_FALSE); spPlayer->put_stretchToFit(VARIANT_TRUE); spPlayer->put_URL(L"C:\\movie.wmv");
#include <atlbase.h> #include <shlobj.h> HINSTANCE hI = ::LoadLibrary("shell32.dll"); ::CoInitialize(NULL); { CComPtr<IProgressDialog> spDlg; spDlg.CoCreateInstance(CLSID_ProgressDialog); // early binding spDlg->SetTitle(L"My Slow Operation"); spDlg->SetAnimation(hI, 167); // 150 - 170 spDlg->StartProgressDialog(NULL, NULL, PROGDLG_NOTIME, NULL); spDlg->SetCancelMsg(L"Please wait...", NULL); spDlg->SetLine(1, L"Unzipping...", FALSE, NULL); for (int i=0; i<100; i++) { if (spDlg->HasUserCancelled()) break; spDlg->SetLine(2, L"I'm processing item n", FALSE, NULL); Sleep(50); spDlg->SetProgress(i, 100); } spDlg->StopProgressDialog(); } ::CoUninitialize(); ::FreeLibrary(hI);
#include <atlbase.h> #include <shlobj.h> #include <ntquery.h> CComPtr<IShellFolder> spRoot; HRESULT hr = ::SHGetDesktopFolder(&spRoot); wchar_t* fname = L"c:\\windows"; LPITEMIDLIST pidl = NULL; hr = spRoot->ParseDisplayName(NULL, NULL, fname, NULL, &pidl, NULL); CComPtr<IShellFolder2> spFolder; hr = spRoot->BindToObject(pidl, NULL, IID_IShellFolder2, (void**)&spFolder); CComPtr<IEnumIDList> spEnumIDList; hr = spFolder->EnumObjects(NULL, SHCONTF_NONFOLDERS, &spEnumIDList); do { CComPtr<IMalloc> spMalloc; ::SHGetMalloc(&spMalloc); const ULONG celt = 1; LPITEMIDLIST pidl = NULL; ULONG celtFetched; hr = spEnumIDList->Next(celt, &pidl, &celtFetched); if (FAILED(hr) || celtFetched==0) break; SHCOLUMNID scid = {PSGUID_STORAGE, PID_STG_NAME}; CComVariant vOut; spFolder->GetDetailsEx(pidl, &scid, &vOut); CComBSTR fname(vOut.bstrVal); spMalloc->Free(pidl); } while (SUCCEEDED(hr));