"Hello" Sample
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:
-
Has only one
object.
-
Uses only scalar
argument types. Automation also supports methods and properties that
accept arguments of complex types,
-
including
arrays, references to objects, and formatted data, but not structures.
-
Supports one
national language.
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 COM Programmer's Reference in the Platform SDK.
Initializing OLE
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.
Registering the
Active Object
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.
-
CLSCTX_LOCAL_SERVER indicates that the executable code for the object runs
in a separate process space from the controller.
-
REGCLS_SINGLEUSE allows only one ActiveX
client to use each instance of the class factory. The value returned
through
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 COM
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.
Registering the
Hello Application
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}
Implementing
IDispatch
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 * pctinfo)
{
*pctinfo = 1;
return NOERROR;
}
STDMETHODIMP
CHello::GetTypeInfo(
UINT itinfo,
LCID lcid,
ITypeInfo ** pptinfo)
{
*pptinfo = NULL;
if(itinfo != 0)
return DISP_E_BADINDEX;
m_ptinfo->AddRef();
*pptinfo = m_ptinfo;
return NOERROR;
}
STDMETHODIMP
CHello::GetIDsOfNames(
REFIID riid,
OLECHAR ** rgszNames,
UINT cNames,
LCID lcid,
DISPID * rgdispid)
{
return DispGetIDsOfNames(m_ptinfo, rgszNames, cNames, rgdispid);
}
STDMETHODIMP
CHello::Invoke(
DISPID dispidMember,
REFIID riid,
LCID lcid,
WORD wFlags,
DISPPARAMS * pdispparams,
VARIANT * pvarResult,
EXCEPINFO * pexcepinfo,
UINT * 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.
Implementing
IUnknown
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 ** ppv)
{
*ppv = NULL;
if (iid == IID_IUnknown)
*ppv = (void *)(IUnknown *)this;
else if (iid == IID_IDispatch)
*ppv = (void *)(IDispatch *)this;
else if (iid == IID_IHello)
*ppv = (void *)(IHello *)this;
else if (iid == IID_ISupportErrorInfo)
*ppv = &m_SupportErrorInfo;
else return 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;
}
Implementing
IClassFactory
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 ** ppv)
{
*ppv = NULL;
if (iid == IID_IUnkown)
*ppv = (void *)(IUnknown *)this;
else if (iid == IID_IClassFactory)
*ppv = (void *)(IClassFactory *)this;
else
return 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 * punkOuter,
REFIID riid,
void ** ppv)
{
HRESULT hr;
*ppv = NULL;
// This implementation doesn't allow aggregation.
if (punkOuter)
return 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.
Implementing VTBL
Binding
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 * 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.
Registering the
Interface for VTBL Binding
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}
Handling Errors
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 *) &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 * punkObject,
REFIID riid)
{
m_punkObject = punkObject;
m_iid = riid;
}
STDMETHODIMP
CSupportErrorInfo::QueryInterface(REFIID iid, void ** 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 : S_FALSE;
}
Releasing Objects
and OLE
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();
}
Creating Type
Information
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."
Creating the Hello
Registration File
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 COM 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.
|