The Hello sample is an Automation application with one object. It has these characteristics:
This sample has been simplified for demonstration purposes. It has the following limitations:
The sections that follow demonstrate how the Hello sample exposes a simple class. The code is abridged to illustrate the essential parts. For a complete listing, see the source code in the OLE Programmer's Reference in the Win32 SDK.
When the Hello application starts, it initializes OLE and then creates the object to be exposed through Automation. For example (Main.cpp):
BOOL InitInstance (HINSTANCE hinst)
{
HRESULT hr;
TCHAR ach[STR_LEN];
// Intialize OLE.
hr = OleInitialize(NULL);
if (FAILED(hr))
return FALSE;
// Create an instance of the Hello Application object. The object is
// created with refcount 0.
LoadString(hinst, IDS_HelloMessage, ach, sizeof(ach));
hr = CHello::Create(hinst, ach, &g_phello);
if (FAILED(hr))
return FALSE;
return TRUE;
}
This function calls OleInitialize to initialize OLE. It loads the string ach
with the initial Hello message, obtained from the string table through the constant IDS_HelloMessage
. Then it calls CHello::Create
to create a single, global instance of the application object, passing it the initial Hello message and receiving a value for g_phello
, a pointer to the instance. If the function is successful, it returns a value of True.
After Hello creates an instance of the object, it exposes and registers the class factory (if necessary) and registers the active object (Main.cpp):
BOOL ProcessCmdLine(LPSTR pCmdLine,
LPDWORD pdwRegisterCF,
LPDWORD pdwRegisterActiveObject,
int nCmdShow)
{
LPCLASSFACTORY pcf = NULL;
HRESULT hr;
*pdwRegisterCF = 0;
*pdwRegisterActiveObject = 0;
// Expose class factory for application object if command line
// contains the /Automation switch.
if (_fstrstr(pCmdLine, "-Automation") != NULL
|| _fstrstr(pCmdLine, "/Automation") != NULL)
{
pcf = new CHelloCF;
if (!pcf)
goto error;
pcf->AddRef();
hr = CoRegisterClassObject(CLSID_Hello, pcf,
CLSCTX_LOCAL_SERVER,
REGCLS_SINGLEUSE,
pdwRegisterCF);
if (hr != NOERROR)
goto error;
pcf->Release();
}
else g_phello->ShowWindow(nCmdShow); //Show if started stand-alone.
RegisterActiveObject(g_phello, CLSID_Hello, ACTIVEOBJECT_WEAK,
pdwRegisterActiveObject);
return TRUE;
error:
if (!pcf)
pcf->Release();
return FALSE;
}
The sample first checks the command line for the /Automation switch. This switch indicates that the application should be started for programmatic access, so that ActiveX clients can create additional instances of the application's class. In this case, the class factory must be created and registered. If the switch is present, the Hello sample creates a new CHelloCF
object and calls its AddRef method, thereby creating the class factory.
Next, the sample calls CoRegisterClassObject to register the class factory. It passes the object's CLSID (CLSID_Hello
), a pointer to the CHelloCF
object (pcf
), and two constants (CLSCTX_LOCAL_SERVER and REGCLS_SINGLEUSE) that govern the class factory's use.
pdwRegisterCF
must later be used to revoke the class factory.
The example specifies weak registration (ACTIVEOBJECT_WEAK
), which means that OLE will release the object when all external connections to it have disappeared. You should always give ActiveX objects weak registration. For more information, see "RegisterActiveObject" in Chapter 5, "Dispatch Interface and API Functions."
The OLE Programmer's Reference provides more information on the functions OleInitialize and CoRegisterClassObject. Inside OLE, Second Edition, published by Microsoft Press, provides more information about verifying application entries in the registration database.
Finally, the sample registers the Hello application object in the running object table (ROT). Registering an active object allows ActiveX clients to retrieve an object that is already running, rather than create a new instance of the object. Use weak registration (ACTIVEOBJECT_WEAK) so that the running object table releases its reference when all external references are released. If strong registration is used (the default), the running object table will not release the reference until RevokeActiveObject is called. For more information, refer to Chapter 5, "Dispatch Interface and API Functions."
The following sample shows the registration entries for the Hello object.
REGEDIT
; Registration information for Automation Hello 2.0 Application.
; Version independent registration. Points to Version 2.0.
HKEY_CLASSES_ROOT\Hello.Application = Automation Hello Application
HKEY_CLASSES_ROOT\Hello.Application\Clsid = {F37C8061-4AD5-101B-B826-00DD01103DE1}
; Version 2.0 registration.
HKEY_CLASSES_ROOT\Hello.Application.2 = Automation Hello 2.0 Application
HKEY_CLASSES_ROOT\Hello.Application.2\Clsid = {F37C8061-4AD5-101B-B826-00DD01103DE1}
The IDispatch interface provides access to and information about an object. The interface requires the member functions GetTypeInfoCount, GetTypeInfo, GetIdsOfNames, and Invoke. The Hello sample implements IDispatch as follows (Hello.cpp):
STDMETHODIMP
CHello::GetTypeInfoCount(UINT FAR* pctinfo)
{
*pctinfo = 1;
return NOERROR;
}
STDMETHODIMP
CHello::GetTypeInfo(
UINT itinfo,
LCID lcid,
ITypeInfo FAR* FAR* pptinfo)
{
*pptinfo = NULL;
if(itinfo != 0)
return ResultFromScode(DISP_E_BADINDEX);
m_ptinfo->AddRef();
*pptinfo = m_ptinfo;
return NOERROR;
}
STDMETHODIMP
CHello::GetIDsOfNames(
REFIID riid,
OLECHAR FAR* FAR* rgszNames,
UINT cNames,
LCID lcid,
DISPID FAR* rgdispid)
{
return DispGetIDsOfNames(m_ptinfo, rgszNames, cNames, rgdispid);
}
STDMETHODIMP
CHello::Invoke(
DISPID dispidMember,
REFIID riid,
LCID lcid,
WORD wFlags,
DISPPARAMS FAR* pdispparams,
VARIANT FAR* pvarResult,
EXCEPINFO FAR* pexcepinfo,
UINT FAR* puArgErr)
{
{
return DispInvoke(
this, m_ptinfo,
dispidMember, wFlags, pdispparams,
pvarResult, pexcepinfo, puArgErr);
}
}
Automation includes two functions, DispGetIdsOfNames and DispInvoke, which provide standard implementations for IDispatch::GetIDsOfNames, and IDispatch::Invoke. The Hello sample uses these two functions to simplify the code.
Every OLE object must implement the IUnknown interface, which allows controllers to query the object to find out what interfaces it supports. IUnknown has three member functions: QueryInterface, AddRef, and Release. The Hello sample implements these functions for the CHello object as follows (Hello.cpp):
STDMETHODIMP
CHello::QueryInterface(REFIID iid, void FAR* FAR* ppv)
{
*ppv = NULL;
if (iid == IID_IUnknown || iid == IID_IDispatch || iid == IID_IHello
*ppv = this;
else if (iid == IID_ISupportErrorInfo)
*ppv = &m_SupportErrorInfo;
else return ResultFromScode(E_NOINTERFACE);
AddRef();
return NOERROR;
}
STDMETHODIMP_(ULONG)
CHello::AddRef(void)
{
return ++m_cRef;
}
STDMETHODIMP_(ULONG)
CHello::Release(void)
{
if (--m_cRef == 0)
{
delete this;
return 0;
}
return m_cRef;
}
A class factory is a class that is capable of creating instances of another class. The Hello sample implements a single class factory named CHelloCF
, as follows (HelloCf.cpp):
CHelloCF::CHelloCF(void)
{
m_cRef = 0;
}
STDMETHODIMP
CHelloCF::QueryInterface(REFIID iid, void FAR* FAR* ppv)
{
*ppv = NULL;
if (iid == IID_IUnknown || iid == IID_IClassFactory)
*ppv = this;
else
return ResultFromScode(E_NOINTERFACE);
AddRef();
return NOERROR;
}
STDMETHODIMP_(ULONG)
CHelloCF::AddRef(void)
{
return ++m_cRef;
}
STDMETHODIMP_(ULONG)
CHelloCF::Release(void)
{
if (--m_cRef == 0)
{
delete this;
return 0;
}
return m_cRef;
}
STDMETHODIMP
CHelloCF::CreateInstance(IUnknown FAR* punkOuter,
REFIID riid,
void FAR* FAR* ppv)
{
HRESULT hr;
*ppv = NULL;
// This implementation doesn't allow aggregation.
if (punkOuter)
return ResultFromScode(CLASS_E_NOAGGREGATION);
hr = g_phello->QueryInterface(riid, ppv);
if (FAILED(hr))
{
g_phello->Quit();
return hr;
}
return NOERROR;
}
STDMETHODIMP
CHelloCF::LockServer(BOOL fLock)
{
CoLockObjectExternal(g_phello, fLock, TRUE);
return NOERROR;
}
The function CHelloCF::CHelloCF is a C++ constructor function. By default, the constructor function initializes the object's VTBLs; CHelloCF::CHelloCF also initializes the reference count for the class.
The class factory supports six member functions. QueryInterface, AddRef, and Release are the required IUnknown members, and CreateInstance and LockServer are the required IClassFactory members.
In addition to the IDispatch interface, the Hello sample supports VTBL binding. When a member is invoked, objects that support a VTBL interface return an HRESULT instead of a value, and pass their return value as the last parameter. Objects may also accept a LCID parameter, which allows them to parse strings correctly for the local language. The following example shows how the Visible property is implemented (Hello.cpp):
STDMETHODIMP
CHello::put_Visible(BOOL bVisible)
{
ShowWindow(bVisible ? SW_SHOW : SW_HIDE);
return NOERROR;
}
STDMETHODIMP
CHello::get_Visible(BOOL FAR* pbool)
{
*pbool = m_bVisible;
return NOERROR;
}
Additional information must be specified in the .odl file to create a dual interface, as shown in "Creating Type Information" later in this chapter.
The following lines from the Hello.reg file register the interface for VTBL binding. In the example, ProxyStubClsid refers to the proxy and stub implementation of IDispatch.
HKEY_CLASSES_ROOT\Interface\{F37C8062-4AD5-101B-B826-00DD01103DE1} = IHello
HKEY_CLASSES_ROOT\Interface\{F37C806 2-4AD5-101B-B826-00DD01103DE1}\TypeLib = {F37C8060-4AD5-101B-B826-00DD01103DE1}
HKEY_CLASSES_ROOT\Interface\{F37C8062-4AD5-101B-B826-00DD01103DE1}\ProxyStubClsid32 = {00020424-0000-0000-C000-000000000046}
The Hello sample includes an exception handler that passes exceptions through IDispatch::Invoke, and supports rich error information through VTBLs (Hello.cpp):
STDMETHODIMP
CHello::RaiseException(int nID)
{
extern return value g_scodes[];
char szError[STR_LEN];
ICreateErrorInfo *pcerrinfo;
IErrorInfo *perrinfo;
HRESULT hr;
BSTR bstrDescription = NULL;
if (LoadString(g_phello->m_hinst,nID, szError, sizeof(szError)))
bstrDescription = SysAllocString(TO_OLE_STRING(szError));
// Set ErrorInfo object so that VTBL binding controller can get
// rich error information. If the controller is using IDispatch
// to access properties or methods, DispInvoke will fill the
// EXCEPINFO structure using the values specified in the ErrorInfo
// object, and Dispinvoke will return DISP_E_EXCEPTION. The
// property or method must return a failure return value for DispInvoke
// to do this.
hr = CreateErrorInfo(hr))
{
pcerrinfo->SetGUID(rguid);
pcerrinfo->SetSource(g_phello->m_bstrProgID);
if (bstrDescription)
pcerrinfo->SetDescription(bstrDescription);
hr = pcerrinfo->QueryInterface(IID_IErrorInfo,
(LPVOID FAR*) &perrinfo);
if (succeeded(hr))
{
SetErrorInfo(0,perrinfo);
perrinfo->Release();
}
if (bstrDescription)
SysFreeString(bstrDescription);
return ResultFromScode(g_scodes[nID-1001]);
}
The member functions of the Hello sample call this routine when an exception occurs. RaiseException sets the system's error object so that controller applications that call through VTBLs can retrieve rich error information. Controllers that call through IDispatch::Invoke will be returned with this error information by DispInvoke through the EXCEPINFO structure.
Hello also implements the ISupportErrorInfo interface, which allows ActiveX clients to query whether an error object will be available (Hello.cpp):
CSupportErrorInfo::CSupportErrorInfo(IUnknown FAR* punkObject,
REFIID riid)
{
m_punkObject = punkObject;
m_iid = riid;
}
STDMETHODIMP
CSupportErrorInfo::QueryInterface(REFIID iid, void FAR* FAR* ppv)
{
return m_punkObject->QueryInterface(iid, ppv);
}
STDMETHODIMP_(ULONG)
CSupportErrorInfo::AddRef(void)
{
return m_punkObject->AddRef();
}
STDMETHODIMP_(ULONG)
CSupportErrorInfo::Release(void)
{
return m_punkObject->Release();
}
STDMETHODIMP
CSupportErrorInfo::InterfaceSupportsErrorInfo(REFIID riid)
{
return (riid == m_iid) ? NOERROR : ResultFromScode(S_FALSE);
}
When the Hello application ends, it revokes the class factory and the active object, and uninitializes OLE. For example (Main.cpp):
void Uninitialize(DWORD dwRegisterCF, DWORD dwRegisterActiveObject)
{
if (dwRegisterCF != 0)
CoRevokeClassObject(dwRegisterCF);
if (dwRegisterActiveObject != 0)
RevokeActiveObject(dwRegisterActiveObject, NULL);
OleUninitialize();
}
Type information for the Hello sample is described in ODL. The MIDL compiler and MkTypLib utility use the .odl file to create a type library (Hellotl.tlb) and a header file (Hellotl.h).
The following example shows the description for the Hello type library, interface, and Application object (Hello.odl):
[
uuid(F37C8060-4AD5-101B-B826-00DD01103DE1), // LIBID_Hello.
helpstring("Hello 2.0 Type Library"),
lcid(0x009),
version(2.0)
]
library Hello
{
importlib("stdole32.tlb");
[
uuid(F37C8062-4AD5-101B-B826-00DD01103DE1), // IID_Ihello.
helpstring("Application object for the Hello application."),
oleautomation,
dual
]
interface IHello : IDispatch
{
[propget, helpstring("Returns the application of the object.")]
HRESULT Application([out, retval] IHello** retval);
[propget,
helpstring("Returns the full name of the application.")]
HRESULT FullName([out, retval] BSTR* retval);
[propget, id(0),
helpstring("Returns the name of the application.")]
HRESULT Name([out, retval] BSTR* retval);
[propget, helpstring("Returns the parent of the object.")]
HRESULT Parent([out, retval] IHello** retval);
[propput]
HRESULT Visible([in] boolean VisibleFlag);
[propget,
helpstring
("Sets or returns whether the main window is visible.")]
HRESULT Visible([out, retval] boolean* retval);
[helpstring("Exits the application.")]
HRESULT Quit();
[propput,
helpstring("Sets or returns the hello message to be used.")]
HRESULT HelloMessage([in] BSTR Message);
[propget]
HRESULT HelloMessage([out, retval] BSTR *retval);
[helpstring("Say Hello using HelloMessage.")]
HRESULT SayHello();
}
[
uuid(F37C8061-4AD5-101B-B826-00DD01103DE1), // CLSID_Hello.
helpstring("Hello Class"),
appobject
]
coclass Hello
{
[default] interface IHello;
interface IDispatch;
}
}
The items enclosed by square brackets are attributes, which provide further information about the objects in the file. The oleautomation and dual attributes, for example, indicate that the IHello interface supports both IDispatch and VTBL binding. The appobject attribute indicates that Hello is the Application object.
For more information about attributes, refer to Chapter 8, "Type Libraries and the Object Description Language."
The system registration database lists all the OLE objects in the system. OLE uses this database to locate objects and determine their capabilities. The registration file registers the application, the type library, and the exposed classes of the sample (Hello.reg):
REGEDIT
; Registration information for Automation Hello 2.0 Application.
; Version independent registration. Points to Version 2.0.
HKEY_CLASSES_ROOT\Hello.Application = Hello 2.0 Application
HKEY_CLASSES_ROOT\Hello.Application\Clsid = {F37C8061-4AD5-101B-B826-00DD01103DE1}
; Version 2.0 registration
HKEY_CLASSES_ROOT\Hello.Application.2 = Hello 2.0 Application
HKEY_CLASSES_ROOT\Hello.Application.2\Clsid = {F37C8061-4AD5-101B-B826-00DD01103DE1}
HKEY_CLASSES_ROOT\CLSID\{F37C8061-4AD5-101B-B826-00DD01103DE1} = Hello 2.0 Application
HKEY_CLASSES_ROOT\CLSID\{F37C8061-4AD5-101B-B826-00DD01103DE1}\ProgID = Hello.Application.2
HKEY_CLASSES_ROOT\CLSID\{F37C8061-4AD5-101B-B826-00DD01103DE1}\VersionIndependentProgID = Hello.Application
HKEY_CLASSES_ROOT\CLSID\{F37C8061-4AD5-101B-B826-00DD01103DE1}\LocalServer = hello.exe /Automation
HKEY_CLASSES_ROOT\CLSID\{F37C8061-4AD5-101B-B826-00DD01103DE1}\TypeLib = {F37C8061-4AD5-101B-B826-00DD01103DE1}
HKEY_CLASSES_ROOT\CLSID\{F37C8061-4AD5-101B-B826-00DD01103DE1}\Programmable
; Type library registration information
HKEY_CLASSES_ROOT\TypeLib\{F37C8060-4AD5-101B-B826-00DD01103DE1}
HKEY_CLASSES_ROOT\TypeLib\{F37C8060-4AD5-101B-B826-00DD01103DE1}\2.0 = Hello 2.0 Type Library
HKEY_CLASSES_ROOT\TypeLib\{F37C8060-4AD5-101B-B826-00DD01103DE1}\2.0\HELPDIR =
; English
HKEY_CLASSES_ROOT\TypeLib\{F37C8060-4AD5-101B-B826-00DD01103DE1}\2.0\9\win32 = hello.tlb
;Interface registration. All interfaces that support vtable binding ;must be registered as follows. RegisterTypeLib & LoadTypeLib will do ;this automatically.
; IID_IHello = {F37C8062-4AD5-101B-B826-00DD01103DE1}
; LIBID_Hello = {F37C8060-4AD5-101B-B826-00DD01103DE1}
HKEY_CLASSES_ROOT\Interface\{F37C8062-4AD5-101B-B826-00DD01103DE1} = IHello
HKEY_CLASSES_ROOT\Interface\{F37C8062-4AD5-101B-B826-00DD01103DE1}\TypeLib = {F37C8060-4AD5-101B-B826-00DD01103DE1}
HKEY_CLASSES_ROOT\Interface\{F37C8062-4AD5-101B-B826-00DD01103DE1}\ProxyStubClsid32 = {00020424-0000-0000-C000-000000000046}
To merge an object's registration information with the system registry, the object should expose the DLLRegisterServer API, as described in the OLE Programmer's Reference. DLLRegisterServer should call RegisterTypeLib to register the type library and the interfaces supported by the application. This only applies to in-process servers. Out-of-process servers such as the Hello sample do not export DLLRegisterServer.