"Lines" Sample
The Lines sample is an ActiveX component
application that implements collections. This sample file allows a
collection of lines to be drawn on a pane using Automation. This sample
implements the following features:
-
Dual
interfaces that allow access to automation properties and methods through
VTBL binding and IDispatch.
-
Rich error information for VTBL-binding
controllers implemented by ISupportErrorInfo and IErrorInfo.
-
Two collections.
-
Active object
registration using RegisterActiveObject
and RevokeActiveObject.
-
Correct shutdown behavior.
-
A registration file that contains
Lines.Application as the ProgID.
-
Initial invisibility.
The following routine initializes OLE, and then
creates an instance of the Lines Application object (Main.cpp):
BOOL InitInstance (HINSTANCE hinst)
{
HRESULT hr;
// Intialize OLE.
hr = OleInitialize(NULL);
if (FAILED(hr))
return FALSE;
// Create an instance of the Lines Application object. The object is
// created with refcount 0.
hr = CApplication::Create(hinst, &g_pApplication);
if (FAILED(hr))
return FALSE;
return TRUE;
}
Initializing the Active Object
The following function creates and registers
the application's class factory, and then registers the Lines Application
object as the active object (Main.cpp):
BOOL ProcessCmdLine(LPSTR lpCmdLine, 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(lpCmdLine, "-Automation") != NULL
|| _fstrstr(lpCmdLine, "/Automation") != NULL)
{
pcf = new CApplicationCF;
if (!pcf)
goto error;
pcf->AddRef();
hr = CoRegisterClassObject(CLSID_Lines, pcf,
CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE,
pdwRegisterCF);
if (hr != NOERROR)
goto error;
pcf->Release();
}
else // Show window if started as stand-alone.
g_pApplication->ShowWindow(nCmdShow );
// Register Lines Application object in the running object table
// (ROT). Use weak registration so that the ROT releases its reference
// when all external references are released.
RegisterActiveObject(g_pApplication, CLSID_Lines, ACTIVEOBJECT_WEAK,
pdwRegisterActiveObject);
return TRUE;
error:
if (pcf)
pcf->Release();
return FALSE;
}
Registering the Active Object
The sample application exposes the class
factory for the Lines application, CApplicationCF, if the command
line contains
the /Automation switch. The switch
indicates that the application was started for programmatic access,
and therefore OLE needs to register the class
factory and create an instance of the Application object.
OLE applies this switch if it appears on the
command line or in the application's registration file. OLE also supports
the /Embedding switch, which
indicates that an application has been started by a container application.
You should register the class factory for the
Application object only if the application is launched with the /Automation switch.
When /Automation is not specified, the
application has been started for some reason other than programmatic access
through Automation. If the class factory is
registered under these circumstances, and a user later requests a new
instance
of the Application object, Automation will
return the existing instance instead of creating a new one.
The sample calls CoRegisterClassObject
to register the class factory as the active object. The CLSCTX_LOCAL_SERVER
flag means
the code that creates and manages
Application objects will run in a separate process space.
Because the Application object's class factory
is exposed, the call specifies the REGCLS_SINGLEUSE flag.
When a multiple-document interface (MDI)
application starts, it typically registers
the class factory for its Document
object, specifying REGCLS_MULTIPLEUSE.
This flag, defined in the REGCLS enumeration,
allows the existing application instance to be used later,
when instances of the document objects need to
be created. Each new Application object, however,
requires a new instance of the application to
be launched, and should therefore specify REGCLS_SINGLEUSE.
If the application registered its class factory
using REGCLS_MULTIPLEUSE, then the next CreateObject call that tries
to create the application will get an existing copy.
In the following example, the macro defines a
CLSID for Lines (Tlb.h):
DEFINE_GUID(CLSID_Lines,0x3C591B21,0x1F13,0x101B,0xB8,0x26,0x00,0xDD,0x01,0x10,0x3D,0xE1);
When the MIDL compiler to MkTypLib creates the
optional header file (Tlb.h), it inserts DEFINE_GUID macros for each
library, interface, and each class in an application.
Creating the Lines Registration File
The registration file provides information
about the application, the Application object, classes of objects, type
libraries,
and interfaces. Entries for objects and
interfaces start with the constant HKEY_CLASSES_ROOT,
which represents the root key of the entire
registration database. Entries for type libraries start with
HKEY_TYPELIB_ROOT.
After the constant, each entry supplies
specific information about an object, type library, or interface.
Use the following steps to create the
registration file:
-
Copy the file Lines.reg.
-
Rename and edit this file, adding entries for
the application.
Registering the Lines Application Files
The Lines sample uses the following entries to
register its Application object (Lines.Application) and its type library
with the system (Lines.reg):
REGEDIT
; Registration information for the Lines Application object. Version
; independent registration.
HKEY_CLASSES_ROOT\Lines.Application = Lines
HKEY_CLASSES_ROOT\Lines.Application\Clsid = {3C591B21-1F13-101B-B826-00DD01103DE1}
; Version 1.0 registration
HKEY_CLASSES_ROOT\Lines.Application.1 = Lines 1.0
HKEY_CLASSES_ROOT\Lines.Application.1\Clsid = {3C591B21-1F13-101B-B826-00DD01103DE1}
HKEY_CLASSES_ROOT\CLSID\{3C591B21-1F13-101B-B826-00DD01103DE1} = Lines 1.0
HKEY_CLASSES_ROOT\CLSID\{3C591B21-1F13-101B-B826-00DD01103DE1}\ProgID = Lines.Application.1
HKEY_CLASSES_ROOT\CLSID\{3C591B21-1F13-101B-B826-00DD01103DE1}\VersionIndependentProgID = Lines.Application
HKEY_CLASSES_ROOT\CLSID\{3C591B21-1F13-101B-B826-00DD01103DE1}\LocalServer32 = lines.exe /Automation
HKEY_CLASSES_ROOT\CLSID\{3C591B21-1F13-101B-B826-00DD01103DE1}\TypeLib = {3C591B20-1F13-101B-B826-00DD01103DE1}
HKEY_CLASSES_ROOT\CLSID\{3C591B21-1F13-101B-B826-00DD01103DE1}\Programmable
; Type library registration information.
HKEY_CLASSES_ROOT\TypeLib\{3C591B20-1F13-101B-B826-00DD01103DE1}
HKEY_CLASSES_ROOT\TypeLib\{3C591B20-1F13-101B-B826-00DD01103DE1}\1.0 = Lines 1.0 Type Library
HKEY_CLASSES_ROOT\TypeLib\{3C591B20-1F13-101B-B826-00DD01103DE1}\1.0\HELPDIR =
;English
HKEY_CLASSES_ROOT\TypeLib\{3C591B20-1F13-101B-B826-00DD01103DE1}\1.0\9\win32 = lines.tlb
; Interface registration. All interfaces that support VTBL binding
; must be registered as follows. RegisterTypeLib will do this
; automatically.
; LIBID_Lines = {3C591B20-1F13-101B-B826-00DD01103DE1}
; IID_IPoint = {3C591B25-1F13-101B-B826-00DD01103DE1}
HKEY_CLASSES_ROOT\Interface\{3C591B25-1F13-101B-B826-00DD01103DE1} = IPoint
HKEY_CLASSES_ROOT\Interface\{3C591B25-1F13-101B-B826-00DD01103DE1}\TypeLib = {3C591B20-1F13-101B-B826-00DD01103DE1}
HKEY_CLASSES_ROOT\Interface\{3C591B25-1F13-101B-B826-00DD01103DE1}\ProxyStubClsid32 = {00020424-0000-0000-C000-000000000046}
; IID_ILine = {3C591B24-1F13-101B-B826-00DD01103DE1}
HKEY_CLASSES_ROOT\Interface\{3C591B24-1F13-101B-B826-00DD01103DE1} = ILine
HKEY_CLASSES_ROOT\Interface\{3C591B24-1F13-101B-B826-00DD01103DE1}\TypeLib = {3C591B20-1F13-101B-B826-00DD01103DE1}
HKEY_CLASSES_ROOT\Interface\{3C591B24-1F13-101B-B826-00DD01103DE1}\ProxyStubClsid32 = {00020424-0000-0000-C000-000000000046}
; IID_ILines = {3C591B26-1F13-101B-B826-00DD01103DE1}
HKEY_CLASSES_ROOT\Interface\{3C591B26-1F13-101B-B826-00DD01103DE1} = ILines
HKEY_CLASSES_ROOT\Interface\{3C591B26-1F13-101B-B826-00DD01103DE1}\TypeLib = {3C591B20-1F13-101B-B826-00DD01103DE1}
HKEY_CLASSES_ROOT\Interface\{3C591B26-1F13-101B-B826-00DD01103DE1}\ProxyStubClsid32 = {00020424-0000-0000-C000-000000000046}
; IID_IPoints = {3C591B27-1F13-101B-B826-00DD01103DE1}
HKEY_CLASSES_ROOT\Interface\{3C591B27-1F13-101B-B826-00DD01103DE1} = IPoints
HKEY_CLASSES_ROOT\Interface\{3C591B27-1F13-101B-B826-00DD01103DE1}\TypeLib = {3C591B20-1F13-101B-B826-00DD01103DE1}
HKEY_CLASSES_ROOT\Interface\{3C591B27-1F13-101B-B826-00DD01103DE1}\ProxyStubClsid32 = {00020424-0000-0000-C000-000000000046}
; IID_IPane = {3C591B23-1F13-101B-B826-00DD01103DE1}
HKEY_CLASSES_ROOT\Interface\{3C591B23-1F13-101B-B826-00DD01103DE1} = IPane
HKEY_CLASSES_ROOT\Interface\{3C591B23-1F13-101B-B826-00DD01103DE1}\TypeLib = {3C591B20-1F13-101B-B826-00DD01103DE1}
HKEY_CLASSES_ROOT\Interface\{3C591B23-1F13-101B-B826-00DD01103DE1}\ProxyStubClsid32 = {00020424-0000-0000-C000-000000000046}
; IID_IApplication = {3C591B22-1F13-101B-B826-00DD01103DE1}
HKEY_CLASSES_ROOT\Interface\{3C591B22-1F13-101B-B826-00DD01103DE1} = IApplication
HKEY_CLASSES_ROOT\Interface\{3C591B22-1F13-101B-B826-00DD01103DE1}\TypeLib = {3C591B20-1F13-101B-B826-00DD01103DE1}
HKEY_CLASSES_ROOT\Interface\{3C591B22-1F13-101B-B826-00DD01103DE1}\ProxySt
Creating the IUnknown Interface for the Lines
Application
The IUnknown interface for the Lines
object looks like this (Lines.cpp):
STDMETHODIMP
CLine::QueryInterface(REFIID iid, void ** ppv)
{
*ppv = NULL;
if (iid == IID_IUnknown)
*ppv = (void *)(IUnknown *)this;
else if (iid == IDispatch)
*ppv = (void *)(IDispatch *)this;
else if (iid == IID_ILine)
*ppv = (void *)(ILine *)this;
else if (iid == IID_ISupportErrorInfo)
*ppv = &m_SupportErrorInfo;
else return E_NOINTERFACE;
AddRef();
return NOERROR;
}
STDMETHODIMP_(ULONG)
CLine::AddRef(void)
{
return ++m_cRef;
}
STDMETHODIMP_(ULONG)
CLine::Release(void)
{
if(--m_cRef == 0)
{
delete this;
return 0;
}
return m_cRef;
}
Creating the IDispatch Interface for the Lines
Application
The following sections
explain how to implement IDispatch by using
CreateStdDispatch and
DispInvoke.
Implementing IDispatch by Calling
CreateStdDispatch
The simplest way to
implement the IDispatch interface is to call
CreateStdDispatch.
This approach works
for ActiveX objects that return only the
standard dispatch exception codes, support a single national language, and
do not support dual interfaces.
CreateStdDispatch
returns a pointer to the created IDispatch interface. It takes three
pointers as input:
a pointer to the object's IUnknown interface, a pointer to the object to expose,
and a pointer to the type information for the
object. The following example implements IDispatch for
an object named CCalc by calling CreateStdDispatch on the loaded type information:
CCalc *
CCalc::Create()
{
HRESULT hresult;
CCalc * pcalc;
ITypeLib * ptlib;
ITypeInfo * ptinfo;
IUnknown * punkStdDisp;
ptlib = NULL;
ptinfo = NULL;
// Some error handling code omitted.
if ((pcalc = new CCalc()) == NULL)
return NULL;
pcalc->AddRef();
// Load the type library from the information in the registry.
if ((hresult = LoadRegTypeLib(LIBID_DspCalc2, 1, 0, 0x0409, &ptlib))
!= NOERROR){
goto LError0;
}
if ((hresult = ptlib->GetTypeInfoOfGuid(IID_ICalculator, &ptinfo))
!= NOERROR){
goto LError0;
}
// Create an aggregate with an instance of the default
// implementation of IDispatch that is initialized with our
// TypeInfo.
//
hresult = CreateStdDispatch(
pcalc, // Controlling unknown.
&(pcalc->m_arith), // VTBL pointer to dispatch on.
ptinfo,
&punkStdDisp);
Implementing IDispatch by Delegating
Another way to implement
IDispatch is to use the dispatch functions
DispInvoke and
DispGetIDsOfNames.
These functions give you the option of
supporting multiple national languages and creating
application-specific exceptions that are passed
back to ActiveX clients.
The Lines sample
implements IDispatch::GetIDsOfNames
and IDispatch::Invoke
using these functions (Lines.cpp):
STDMETHODIMP
CLines::GetIDsOfNames(
REFIID riid,
char ** rgszNames,
UINT cNames,
LCID lcid,
DISPID * rgdispid)
{
return DispGetIDsOfNames(m_ptinfo, rgszNames, cNames, rgdispid);
}
STDMETHODIMP
CLines::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);
}
}
The Lines object implements the IID_ILine
dual interface for VTBL binding.
It also implements the IID_ISupportErrorInfo
interface so that it can return rich, contextual error information through
VTBLs.
Implementing the Class Factory for the Lines
Application
The Lines sample implements a class factory for
its Application object, as follows (Appcf.cpp):
STDMETHODIMP
CApplicationCF::CreateInstance(IUnknown * punkOuter,
REFIID riid,
void ** ppv)
{
HRESULT hr;
*ppv = NULL;
// This implementation doesn't allow aggregation.
if (punkOuter)
return CLASS_E_NOAGGREGATION;
// This is REGCLS_SINGLEUSE class factory, so CreateInstance will be
// called at most once. An application object has a REGCLS_SINGLEUSE
// class factory. The global application object has already been
// created when CreateInstance is called. A REGCLS_MULTIPLEUSE class
// factory's CreateInstance would be called multiple times and would
// create a new object each time. An MDI application would have a
// REGCLS_MULTIPLEUSE class factory for its document objects.
hr = g_pApplication->QueryInterface(riid, ppv);
if (FAILED(hr))
{
g_pApplication->Quit();
return hr;
}
return NOERROR;
}
STDMETHODIMP
CApplicationCF::LockServer(BOOL fLock)
{
CoLockObjectExternal(g_pApplication, fLock, TRUE);
return NOERROR;
}
The object's class factory must also implement
an IUnknown interface. For example (Appcf.cpp):
STDMETHODIMP
CApplicationCF::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)
CApplicationCF::AddRef(void)
{
return ++m_cRef;
}
STDMETHODIMP_(ULONG)
CApplicationCF::Release(void)
{
if(--m_cRef == 0)
{
delete this;
return 0;
}
return m_cRef;
}
Setting Up the VTBL Interface
The Lines sample supports VTBL binding as well
as the IDispatch interface.
By
supporting this
dual
interface, the sample allows ActiveX clients both the flexibility
of the IDispatch interface and the speed
of VTBLs. Controllers that know the names
of the members can compile directly against the
function pointers in the VTBL. Controllers that do not have this information
can use IDispatch at run time.
To have a dual interface, an interface
must:
-
Declare all of its members to return an
HRESULT, and pass their actual return values as the last parameter.
-
Have only
Automation-compatible parameters and return types, as described in Chapter
7, "Conversion
and Manipulation Functions."
-
Specify the dual attribute on the
interface description in the .odl file.
-
Initialize the VTBLs with the appropriate
member function pointers.
In the Lines sample, the interfaces IPoint,
IPoints, ILine, ILines, IPane, and IApplication
are
all dual interfaces. The IPoint
interface defines functions that get and put the values of the X and
Y properties, as follows (Point.cpp):
STDMETHODIMP
CPoint::get_x(int * pnX)
{
*pnX = m_nX;
return NOERROR;
}
STDMETHODIMP
CPoint::put_x(int nX)
{
m_nX = nX;
return NOERROR;
}
STDMETHODIMP
CPoint::get_y(int * pnY)
{
*pnY = m_nY;
return NOERROR;
}
STDMETHODIMP
CPoint::put_y(int nY)
{
m_nY = nY;
return NOERROR;
}
The get_x and get_y accessor
functions pass their return values in the last parameter, pnX and pnY, and return an HRESULT as the function value.
In the .odl file, the interface is described as
follows (Lines.odl):
[
uuid(3C591B25-1F13-101B-B826-00DD01103DE1), // IID_IPoint.
helpstring("Point object."),
oleautomation,
dual
]
interface IPoint : IDispatch
{
[propget, helpstring("Returns and sets x coordinate.")]
HRESULT x([out, retval] int* retval);
[propput, helpstring("Returns and sets x coordinate.")]
HRESULT x([in] int Value);
[propget, helpstring("Returns and sets y coordinate.")]
HRESULT y([out, retval] int* retval);
[propput, helpstring("Returns and sets y coordinate.")]
HRESULT y([in] int Value);
}
The attributes
oleautomation
and dual
indicate that the interface supports both IDispatch
and VTBL binding.
All of the member functions are declared with
HRESULT return values.
The Get accessor
functions, indicated by the propget
attribute, return their value in the last parameter. This parameter has
the out
and retval
attributes.
In the Lines sample, the Application object
exposes the following method (App.cpp):
STDMETHODIMP
CApplication::CreatePoint(IPoint ** ppPoint)
{
CPoint * ppoint = NULL;
HRESULT hr;
// Create new item and QueryInterface for IDispatch.
hr = CPoint::Create(&ppoint);
if (FAILED(hr))
{hr = RaiseException(IDS_OutOfMemory); goto error;}
hr = ppoint->QueryInterface(IID_IDispatch, (void **)ppPoint);
if (FAILED(hr))
{hr = RaiseException(IDS_Unexpected); goto error;}
return NOERROR;
error:
if (ppoint)
delete ppoint;
return hr;
}
The CreatePoint
method creates a new point and returns a pointer to it in the parameter
pPoint.
In the Lines sample, the CLine object exposes
the Color property. This property is implemented by the following
accessor functions (Lines.cpp):
STDMETHODIMP
CLine::get_Color(long * plColorref)
{
*plColorref = m_colorref;
return NOERROR;
}
STDMETHODIMP
CLine::put_Color(long lColorref)
{
m_colorref = (COLORREF)lColorref;
return NOERROR;
}
Implementing the Value Property
In the Lines sample,
ILines.Item,
IPoints.Item, and
IApplication.Name are
the
Value
properties of the objects ILines,
IPoints, and
IApplication,
respectively. The ILines.Item
object is described as follows:
interface ILines : IDispatch
{
.
.// Some descriptions omitted for brevity.
.
[propget, id(0), helpstring(
"Given an integer index, returns one of the lines in the collection")]
HRESULT Item([in] long Index,[out, retval] ILine** retval);
.
}
Using this property, a user can refer to the
fourth line in the collection as ILines(4).Item or simply as ILines(4).
For more information on
recommended objects, properties, and methods, see Chapter 4, "Standard
Objects and Naming Guidelines."
Restricting Access to Objects
Automation provides several ways of restricting
access to objects.
The simplest approach is not to document the
properties and methods you do not want users to see.
Alternatively, you can
prevent a property or method from appearing in type library browsers by
specifying the hidden
attribute in the .odl file.
The
restricted
attribute goes one step further, preventing user calls from binding to
the property or method,
as well as hiding it from type browsers. For
example, the following restricts access to the _NewEnum property of
the ILines object:
[propget, restricted, id(DISPID_NEWENUM)] // Must be propget.
HRESULT _NewEnum( [out, retval] IUnknown** retval);
Restricted properties and methods can be
invoked by ActiveX clients, but are not visible to the user who may
be using a language such as Visual Basic. In
addition, they cannot be bound to by user calls.
Creating Collection Objects
A collection object contains a group of exposed
objects of the same type and can iterate over them.
Collection objects do not need an IClassFactory implementation, because they are accessed from elements
that have their own class factories.
For example, the Lines sample has a collection
object named CLines that iterates over a group of Line objects.
The following routine creates and initializes
the CLines collection object (Lines.cpp):
STDMETHODIMP
CLines::Create(ULONG lMaxSize, long lLBound, CPane * pPane,
CLines ** ppLines)
{
HRESULT hr;
CLines * pLines = NULL;
SAFEARRAYBOUND sabound[1];
*ppLines = NULL;
// Create new collection.
pLines = new CLines();
if (pLines == NULL)
goto error;
pLines->m_cMax = lMaxSize;
pLines->m_cElements = 0;
pLines->m_lLBound = lLBound;
pLines->m_pPane = pPane;
// Load type information for the Lines collection from type library.
hr = LoadTypeInfo(&pLines->m_ptinfo, IID_ILines);
if (FAILED(hr))
goto error;
// Create a safe array of variants used to implement the collection.
sabound[0].cElements = lMaxSize;
sabound[0].lLbound = lLBound;
pLines->m_psa = SafeArrayCreate(VT_VARIANT, 1, sabound);
if (pLines->m_psa == NULL)
{
hr = E_OUTOFMEMORY;
goto error;
}
*ppLines = pLines;
return NOERROR;
error:
if (pLines == NULL)
return E_OUTOFMEMORY;
if (pLines->m_ptinfo)
pLines->m_ptinfo->Release();
if (pLines->m_psa)
SafeArrayDestroy(pLines->m_psa);
pLines->m_psa = NULL;
pLines->m_ptinfo = NULL;
delete pLines;
return hr;
}
The parameters to CLines::Create specify
the maximum number of lines that the collection can contain,
the lower bound of the indexes of the
collection, and a pointer to a pane, which contains the lines and points in
the sample.
Implementing the IEnumVARIANT Interface for the
Lines Application
In the Lines sample, CEnumVariant
implements the Next, Skip, Reset, and Clone
member functions (Enumvar.cpp):
STDMETHODIMP
CEnumVariant::Next(ULONG cElements, VARIANT * pvar,
ULONG * pcElementFetched)
{
HRESULT hr;
ULONG l;
long l1;
ULONG l2;
if (pcElementFetched != NULL)
*pcElementFetched = 0;
// Retrieve the elements of the next cElements.
for (l1=m_lCurrent, l2=0; l1<(long)(m_lLBound+m_cElements) &&
l2<cElements; l1++, l2++)
{
hr = SafeArrayGetElement(m_psa, &l1, &pvar[l2]);
if (FAILED(hr))
goto error;
}
// Set count of elements retrieved.
if (pcElementFetched != NULL)
*pcElementFetched = l2;
m_lCurrent = l1;
return (l2 < cElements) ? S_FALSE : NOERROR;
error:
for (l=0; l<cElements; l++)
VariantClear(&pvar[l]);
return hr;
}
STDMETHODIMP
CEnumVariant::Skip(ULONG cElements)
{
m_lCurrent += cElements;
if (m_lCurrent > (long)(m_lLBound+m_cElements))
{
m_lCurrent = m_lLBound+m_cElements;
return S_FALSE;
}
else return NOERROR;
}
STDMETHODIMP
CEnumVariant::Reset()
{
m_lCurrent = m_lLBound;
return NOERROR;
}
STDMETHODIMP
CEnumVariant::Clone(IEnumVARIANT ** ppenum)
{
CEnumVariant * penum = NULL;
HRESULT hr;
*ppenum = NULL;
hr = CEnumVariant::Create(m_psa, m_cElements, &penum);
if (FAILED(hr))
goto error;
penum->AddRef();
penum->m_lCurrent = m_lCurrent;
*ppenum = penum;
return NOERROR;
error:
if (penum)
penum->Release();
return hr;
}
Implementing the _NewEnum Property for the
Lines Application
The Lines sample contains two collections,
Lines and Points, and implements a _NewEnum property for each.
Both are restricted properties, available to
ActiveX clients, but invisible to users of scripting or macro languages
supported by ActiveX clients. The property
returns an enumerator (IEnumVARIANT) for the items in the collection.
The following code implements the _NewEnum
property for the Lines collection (Lines.cpp):
STDMETHODIMP
CLines::get__NewEnum(IUnknown ** ppunkEnum)
{
CEnumVariant * penum = NULL;;
HRESULT hr;
*ppunkEnum = NULL;
// Create a new enumerator for items currently in the collection and
// QueryInterface for IUnknown.
hr = CEnumVariant::Create(m_psa, m_cElements, &penum);
if (FAILED(hr))
{hr = RaiseException(IDS_OutOfMemory); goto error;}
hr = penum->QueryInterface(IID_IUnknown, (VOID **)ppunkEnum);
if (FAILED(hr))
{hr = RaiseException(IDS_Unexpected); goto error;}
return NOERROR;
error:
if (penum)
delete penum;
return hr;
}
Returning Errors
The Lines sample defines an exception handler
that fills the EXCEPINFO structure and signals IDispatch to return
DISP_E_EXCEPTION (App.cpp):
STDMETHODIMP
HRESULT RaiseException (int nID, Refguid rguid)
{
extern HRESULT g_scodes[];
TCHAR szError[STR_LEN];
ICreateErrorInfo *pcerrinfo;
IErrorInfo *perrinfo;
HRESULT hr;
BSTR bstrDescription = NULL;
if (LoadString(g_pApplication->m_hinst, nID, szError, sizeof(szError)));
bstrDescription = SysAllocString(TO_OLE_STRING(szError));
// Set ErrInfo object so that VTBL binding controllers 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(&pcerrinfo);
if (SUCCEEDED(hr))
{
pcerrinfo->SetGUID(rguid);
pcerrinfo->SetSource(g_pApplication->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]);
}
Properties and methods in
the Lines 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.
Passing Exceptions Through VTBLs
The Lines sample also
provides rich error information for members invoked through VTBLs. Because
VTBL-bound calls bypass the IDispatch interface, they cannot return
exceptions through IDispatch. Instead, they must use the error
handling interfaces in Automation. The RaiseException function shown
in the example calls CreateErrorInfo
to create an error object, then fills the object's data fields with
information about the error. When all of the information has been
successfully recorded, it calls SetErrorInfo
to associate the error object with the current thread of execution.
ActiveX objects similar to the collection
object (CApplication), which use the error interfaces, must also implement
the ISupportErrorInfo interface. This interface identifies the object
as supporting the error interfaces, and ensures that error information can
be propagated correctly up the call chain. The following example shows how
the Lines sample implements this interface (Errinfo.cpp):
STDMETHODIMP
CSupportErrorInfo::CSupportErrorInfo(IUnknown * punkObject,
REFIID riid)
{
m_punkObject = punkObject;
m_iid = riid;
}
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;
}
ISupportErrorInfo
has the QueryInterface, AddRef, and Release methods
inherited from the IUnknown interface, along with the InterfaceSupportsErrorInfo method. ActiveX clients call
InterfaceSupportsErrorInfo to check whether the ActiveX object supports
the IErrorInfo interface, so they can access the error object. For
details, see Chapter 11, "Error Handling Interfaces."
Releasing OLE on Exit
The following code revokes an active Lines
object, revokes the Lines class, and then uninitializes OLE (Main.cpp):
void Uninitialize(DWORD dwRegisterCF, DWORD dwRegisterActiveObject)
{
if (dwRegisterCF != 0)
CoRevokeClassObject(dwRegisterCF);
if (dwRegisterActiveObject != 0)
RevokeActiveObject(dwRegisterActiveObject, NULL);
OleUninitialize();
}
Writing an Object Description Script
An object description script is essentially an
annotated header file, written in ODL. The following example shows a portion
of Lines.odl, the object description script for the Lines sample.
[
uuid(3C591B20-1F13-101B-B826-00DD01103DE1), // LIBID_Lines.
helpstring("Lines 1.0 Type Library"),
lcid(0x09),
version(1.0)
]
library Lines
{
importlib("stdole.tlb");
#define DISPID_NEWENUM -4
.
.
.
The preceding entry describes the type library
(Lines.tlb) created by the sample. The items in square brackets are
attributes, which provide additional information about the library. In the
example, the attributes give the library's UUID, a Help string, an LCID, and
a version number.
The importlib directive is similar to
the C or C++ #include directive. It allows access to the type
descriptions in the file Stdole.tlb from the Lines library. However, it does
not copy those types into the Lines.tlb. To use Lines.tlb, both the
Lines.tlb and Stdole.tlb files must be available.
By default, .odl files are preprocessed with
the C preprocessor, so the #include and #define directives can
be used.
The object description script continues with
information on the objects in the type library:
[
uuid(3C591B25-1F13-101B-B826-00DD01103DE1), // IID_Ipoint.
helpstring("Point object."),
oleautomation,
dual
]
interface IPoint : IDispatch
{
[propget, helpstring("Returns and sets x coordinate.")]
HRESULT x( [out, retval] int* retval);
[propput, helpstring("Returns and sets x coordinate.")]
HRESULT x([in] int Value);
[propget, helpstring("Returns and sets y coordinate.")]
HRESULT y( [out, retval] int* retval);
[propput, helpstring("Returns and sets y coordinate.")]
HRESULT y([in] int Value);
}
// .
// Additional definitions omitted for brevity.
// .
}
This entry describes the
IPoint interface. The interface has the attributes
oleautomation
and dual,
indicating that the types of all its properties and methods are compatible
with Automation, and that it supports binding through both IDispatch and VTBLs. The
IPoint interface has two pairs of property accessor
functions, which set and return the X and Y properties.
The Value parameter of both the
X and Y properties has the
in attribute.
These parameters supply a value and are read-only. Conversely, the retval
parameter of each property has the out
and retval
attributes, indicating that it returns the value of the property.
Because IPoint
supports VTBL binding and rich error information, its properties return
HRESULTs and pass their function return values through retval parameters. For more information, see Chapter 8, "Type
Libraries and the Object Description Language."
[
uuid(3C591B21-1F13-101B-B826-00DD01103DE1), // CLSID_Lines.
helpstring("Lines Class"),
appobject
]
coclass Lines
{
[default] interface IApplication;
interface IDispatch;
}
} |