Need to Automate Your Java, Visual C++, and VS.NET Builds?
Try the Programmers' Canvas Toolkit
Help!
Post your feedback and COM+ questions in the
COM+ forum.
Great Books on COM and ATL
  
  
 
Introduction
COM (and its successor, COM+) is one of the most successful object technologies in the
relatively brief history of the computer
industry. If you hear someone flippantly mention that there's nothing to
COM programming, they're either a genius or they have never written a COM object
in C++. COM works, as long as you do your
homework first. Otherwise you're in for a difficult learning
experience.
These pages represent an attempt to chronicle the not-so-obvious facts of
COM programming that we have discovered over the years. All of these
points have been proven by experience and verified by experts. However, if
you discover some untruths here, please email
us.
Free COM Tools
-
Install
ComUtils
-
Run the command dgregall for instructions:
Usage:
1) The directory to process. Also processes subdirectories.
2) Optional - Pass in the name of the directory that the files
must be found in (e.g., Debug). Pass in "." as the first
parameter to process the current directory.
Switches:
[-v] Verbose mode -- display all dialog boxes from
regsvr32
-
dgreglib.exe registers type libraries and may be used
-
The source to dgreglib.exe is in the
CVS module pctk/reglib.
-
dgregall.pl is the source for dgregall.exe
and is in the CVS module
RegAll
Revised on: 2001-04-16
Revised on: Feb 13 2001
The ASSERT, ASSERTE, ATLASSERT,
etc. macros can easily be overused. These macros are actually
removed from release builds, so they provide no value whatsoever in
these types of builds.
Do not use ASSERT macros to test error return
codes. The macros' only purpose is to assert something which should ALWAYS
must true and CAN NEVER be false. Since
failures can occur at run-time, ASSERTs should never be used to test for them.
Otherwise release builds will not know when a failure occurred.
An appropriate use of ASSERT is to test input parameters in internal methods
or functions. For example, a method can test that a passed-in pointer is
not NULL. However, it is
considered bad form to ASSERT on COM method parameters and properties.
Once you ship your COM object, you have no idea who
will call it. Your customers will experiment with the object and will
undoubtedly send bad data to it. Remember: ASSERTs are removed from
release builds. Your object should catch bad data before
it does something bad with it, such as crash the process. Crashing a customer's processes is never acceptable, even if they give you bogus
inputs (within reason).
Here is how code should test input parameters:
if (!pVal) return E_POINTER;
COM Strings
Revised on: 2000-09-23
See the Entire Page Dedicated to COM Strings
Store all IDL Files in the Same Directory
Revised on: 2000-09-25
- If IDL files import other IDL files, you should put all the files in the
same directory
- Import statements in IDL files should refer to "naked" IDL file
names
- For an explanation, see here
- Go to the project settings and choose "all configurations"
- Click the Resources tab. In the include directories enter the
relative path to the IDL file directory.
- Click the C+ tab. Select Preprocessor from the drop-down.
- Set the "include directories" to include the IDL directory
- Click on the IDL file in the project settings window
- Click on the MIDL tab
- Output the TLB file to IDL file directory
- Output the header file to the current directory
- Set the Additional include directories to the IDL file directory


Revised on: 2000-09-28
When using type-library marshaling, it's an absolute requirement that the
type libraries be registered on the client and server machines. Sometimes
you want to register the type library for a server on a client without actually
installing the server components. Here's a free
type-library registering program. Run dgreglib.exe with the
path of a TLB file.
Don't Modify Error Object In Release()
Revised on: 2000-10-14
It's a very bad idea to clear or set the error info object in Release().
Corollary:
It's a very bad idea to clear or set the error info object in a
destructor. Note that it's sometimes not so easy to avoid doing this
because destructors may call methods on COM objects. Code that does this
should preserve the error object that was present when Release() was called.
Revised on: 2000-09-23
Helper classes that initialize and uninitialize
COM via constructors and destructors can be dangerous with regards to cleaning
up local variables of type CComPtr<> and similar smart
pointer classes in the thread's "main" function. In order to
avoid uninitializing COM before these local variables are destroyed, put the
function's body inside a nested block or put the code in a separate function.
LONG threadEntry(void *p)
{
// Initialize COM
ComInit foo;
{
// Do stuff here
}
return 0;
}
Common Service Errors
Revised on: 2000-12-08
Error 3: The system cannot find the path specified
If this happens when starting a service, it's probably because the system
can't find the service's executable as specified in the registry. Search
the registry for the name of the executable and make sure it's right. Make
sure the path is right in the registry.
The service's executable should be on a local drive, not on a network share
or a subst'd drive.
Revised on: 2000-11-27
RPC_S_SERVER_UNAVAILABLE
- Make sure the DNS suffix on the client and server machines are the same
- From the client, enter "ping foo.bar.com", in other words make
sure you can look up the machine using the machine's full name with the
DNS suffix
- Make sure the proxy/stub DLLs (or the TLBs)
are registered
RPC_E_DISCONNECTED_DNE / RPC_E_DISCONNECTED
- COM clients must ping remote servers every 6 minutes
- If a remote server doesn't hear from a client after 6 minutes it
terminates the connection
- The service that pings the remote server is called
rpcss
- There is only one
rpcss process per machine (it's a service)
rpcss can get "stuck" by bad processes that call
WaitForSingleObject in a single-threaded apartment (STA), or when other STAs
for one reason or another do not process the Windows event queue
- When
rpcss gets stuck no "pings" can be sent out
from the ENTIRE MACHINE
- Therefore this error might not be caused by your code! The problem
might be caused by another process, in which case the resolution is pretty
much hopeless. Microsoft technical support can help you identify the
"bad" process.
- One solution is to move your object to a machine where the "bad"
process isn't running
- Use SOAP :)
CO_E_SERVER_EXEC_FAILURE
- The user that's invoking the object doesn't have sufficient rights on
the machine.
- The user can instantiate the object and access its methods but the
object is attempting to access resources on the machine that the user
doesn't have sufficient rights to.
- This usually happens when invoking an object via a system service
- Solution is to supply a network user account when calling
CoCreateInstanceEx
- Another solution is to run DCOM on the server, double click on the
object, click Identification, and choose Identity. Select "this
User" and specify the user that has sufficient rights to the needed
resources.
Multiple Dispatch Interfaces on a Single Object
Revised on: 2000-09-24
"Dual" interfaces inherit from IDispatch. When a component
needs to implement two "dual" interfaces, which
IDispatch pointer should QueryInterface return?
Note that this is only a problem for automation languages such as VBScript.
Languages, for example C++, that can call QueryInterface do not suffer from the
multiple IDispatch problem.
There are several solutions to this problem but Victor Pavlov's
solution is best. The component author chooses the default
interface. Each interface contains an additional method QueryDispatch that
can only be invoked via IDispatch. QueryDispatch accepts
a string that distinguishes each IDispatch pointer. All of this work is
done by clever macros that are simple to incorporate into existing code.
List Implemented Interfaces in IDL
Revised on: 2000-11-09
The CoClass section of IDL files should list the custom interfaces that an
object implements. This allows OLEVIEW and similar programs to be able to
display the interface that a particular object implements.
An exception to this is ISupportErrorInfo, which is usually omitted from the
IDL. But it doesn't hurt.
This is an asinine recommendation. Only import IDL files when you
absolutely have to. Specifying IDispatch in IDL files works. Sure,
you have to do an extra QueryInterface and the interface isn't
self-describing. But here's what can happen when IDL files import other
IDL files:
- You're likely to run into issues about whether the interface is defined
inside or outside the Library block
- You will have compile-time name collisions and struct vs. class errors
v:\source\c\whois\whois\debugu\whois.tlh(171) : error C2374: 'IID_IFoo' : redefinition; multiple initialization
- Use the exclude #import directive to fix this
- IDL's import statement doesn't obey the project's include
directories. You have to enter the include directories via the
project's MIDL tab
- Dependencies between IDL files are just as bad a dependencies between
header files. It becomes a tangled mess that you will regret later.
- Using IDispatch is very flexible. Some day you might return an
object that supports a different interface. It's similar the
flexibility of VARIANTS. It allows you to incrementally add features
without changing interfaces.
Revised on: 2000-09-23
Do not rely on smart pointer destructors to call Release on interface
pointers that are stored in data members. Do not call Release() in
destructors either. Instead, override the FinalRelease
method and manually release the interfaces.
Failure to do this may result in crashes or memory leaks at shut-down.
This is because DllCanUnloadNow will return true before the
interfaces get released.
Revised on: 2000-09-23
Heavyweight logic should be put in FinalConstruct,
not in a constructor. Better yet, delay any "heavyweight"
processing until a "real" method is called. Methods called Init
or Initialize are considered bad form -- the object should be able to know when
to initialize itself, not the programmer.
Common Linker Errors
Revised on: 2000-09-23
Missing symbol *_Lib, CLSID_* etc...
Add a file to your project that doesn't include the precompiled headers
that contains one line, where foo.tlb is the TLB (or DLL or IDL) where the COM
interface, library, object, etc. resides:
#import "foo.tlb" named_guids
Turn an In-Process DLL into an
Out-of-Process Server
Revised on: 2000-09-23
If you already have an in-process COM object, it's not necessary
to create a separate COM project just because you want to be able to use it
across process or machine boundaries.
The following instructions will enable an in-process COM
object to be created out-of process as long as CoCreateInstance is called with
the CLSCTX_LOCAL_SERVER (that's right, local server!) context.
The Windows surrogate process dllhost.exe hosts the
object. The downside to this approach is that, when you look in Task
Manager, all you can see is dllhost.exe. If some other object might be
hosted by dllhost.exe, you have virtually no way of telling which process
contains your object. Some vendors distribute their own copy of dllhost.exe
that simply has a different name and set the registry entry DllSurrogate to
the full path of the renamed host executable. Note that most flavors of
Windows (98, XP, NT, 2000) have their own version of dllhost.exe,
so it's recommended that your installer copy %WINDIR%\system32\dllhost.exe
rather than shipping your own version.
-
Open the Visual C++ project file
-
Open the RGS file for your COM object
-
Inside ForceRemove{GUID}{ add:
val AppID = s '{APPGUID}'
-
Add the following at the bottom of the file before the last
closing paren:
NoRemove AppID
{
ForceRemove {APPGUID}= s 'My Object'
{
val DllSurrogate = s ''
}
}
-
Replace the {APPGUID} text above with the CLSID of the object, e.g., {1A9B826E-930F-4CF6-8279-B55C8F8E6108}. Or, you can generate a new GUID:
-
Run GUIDGEN.EXE
-
Click 4-Registry format
-
Click copy
-
Go back to the RGS file
-
Replace the GUID text with the
contents of the clipboard via Edit/Paste
If you don't have access to the project's source code, you can
alter the registry yourself. Note: HKCR is an alias for HKEY_CLASSES_ROOT
-
Create HKCR\AppID\{APPID GUID}...
-
Set the default value to any text you like
-
Create a string value DllSurrogate which is
empty
-
Under HKCR\CLSID\{CLSID}...
-
Create a string value AppID which contains the
APPID GUID
As you can see, it's easy to instantiate an object in another process
space. What's not so easy is to implement the singleton design
pattern. Such as, processes on machines A, B, and C all have a pointer to
the same instance running on machine D. Generally this is not a scalable
model but let's say you want to that. The best way to do so is to create
an executable that runs as a service. If you already have an
in-process DLL that does all the work, the executable could merely forward
method calls on to this object.
Also see here.
There is some information on surrogate sharing here.
Single-Instance Out-of-Process Servers
Revised on: 2001-03-09
By default, out-of-process servers host many objects in a single
process. Consider an example. Two client processes instantiate two
objects from the COM EXE server called MyServ.EXE. Although there are
objects instantiated, there will only be one MyServ.EXE in the process list.
Sometimes it is necessary to have one process per object. You would do
this to isolate processes for security or robustness reasons. For example,
if one instance crashes a process, it won't affect the other instances because
their processes will be intact. Another situation where this is helpful is
when the code uses a non thread-safe library and you want multiple instances to
be able to use this library simultaneously in separate logical threads.
To do this, you must create a COM EXE server -- you can not use the built-in
surrogate (dllhost.exe). When the class factory is registered using CoRegisterClassObject,
use REGCLS_SINGLEUSE for the 4th parameter (flags).
This technique uses many more resources per object so you should only use it
when you have a good reason to do so.
Free Non-Retval Output Parameters
Revised on: 2000-12-15
Output parameters that are not marked "retval", particularly VARIANTs, need to be
free'd; otherwise memory leaks will result.
VBScript doesn't free memory before calling methods.
For example:
STDMETHODIMP CURLUtilObject::GetNVPairs(BSTR in,
VARIANT *names, VARIANT *values)
{
if (!names) return E_POINTER;
if (!values) return E_POINTER;
VariantClear(names);
VariantClear(values);
}
If you write VBScript that calls GetNVPairs twice in a row with the same
variable for "values" or "names", a memory leak will result
unless GetNVPairs internally calls VariantClear.
Initialize Output Parameters to
NULL
Revised on: 2000-12-15
It's good practice to initialize output parameters (BSTR*,
LPVARIANT, LPDISPATCH etc.) immediately
when called. COM does not look at the return code when marshaling output
parameters. Even if a failure scode is returned, all output parameters are
passed back to the caller. If the parameters contain bogus or
uninitialized data, COM will crash.
See also "Free
Non-Retval Output Parameters" -- sometimes you also need to free the
memory, not just set the pointer to NULL.
Do not set output parameters to a value that needs to be
deallocated (e.g., a string) until you are certain that the
method can not return a failure SCODE.
If you were to, say, set an [out, retval] BSTR * and return
E_FAIL, the caller might not deallocate
the string. This is really, really, bad form on the part of the caller,
but it can happen.
Revised on: 2000-09-23
- I always create projects for IDL files. The project might
implement interfaces or it might just be a "stub"
project. Creating project files for IDLs enables you to use the
project wizards to add methods and properties.
- Do not name a CoClass the same as a library name -- the names need to be
different by more than just the case of their letters (e.g., Foo and FOO).
Otherwise you will get weird compiler errors.
- Type library names should be unique across all companies --
this is needed for Visual Basic support.
- A common convention is, when creating a project via the ATL project
wizard, to use the naming convention
XXLibName where XX is
a company-wide two-letter prefix. Naming the DLL in a special way
is not necessary -- you can modify the IDL file directly and provide
your own name.
- It's a good idea for the library name and the project name to be the
same, or have Lib attached to it
- E.g., the project that has the IDL file is DGFoo and builds DGFoo.DLL.
The library name is DGFOOLib. The ATL Wizard will do this
automatically.
- Names of enumerated values: (1) uppercase (2) all names within one
IDL file (and included IDL files, if any) must be unique.
- Use the following format as the object's description -- or
enter in the "Type" field in the ATL wizard:
<company name> <Class Name>
- ProgIDs should have the form (set this in the RGS file):
<company name>.<object name>
- The help string for type libraries should have the form (set this in the
IDL file):
<company name> <library name> Type
Library
- Beware of name collisions when an IDL file needs to include another IDL
file
- Such as, when implementing an interface that's defined in another IDL
file
- In IDL files, the names of interfaces, coclasses, enumerations, and on
and on may clash with other names
- Bottom line: in order to be safe, all identifiers should have a
prefix

Here is an example RGS file:
HKCR
{
devguy.SystemInfo.1 = s 'DevGuy SystemInfo Class'
{
CLSID = s '{F16F6FFC-129C-4736-9963-8E34F22C195B}'
}
devguy.SystemInfo = s 'DevGuy SystemInfo Class'
{
CLSID = s '{F16F6FFC-129C-4736-9963-8E34F22C195B}'
}
NoRemove CLSID
{
ForceRemove {F16F6FFC-129C-4736-9963-8E34F22C195B} = s
'DevGuy SystemInfo Class'
{
ProgID = s 'devguy.SystemInfo.1'
VersionIndependentProgID = s 'devguy.SystemInfo'
InprocServer32 = s '%MODULE%'
{
val ThreadingModel = s 'both'
}
}
}
}
Here is a sample IDL file:
import "oaidl.idl";
import "ocidl.idl";
[
object,
uuid(65D62D03-3F65-487E-BAA9-1F8F6EFD5A6C),
dual,
helpstring("IDGStringUtil Interface"),
pointer_default(unique)
]
interface IDGStringUtil : IDispatch
{
};
[
uuid(57B9B864-F875-472A-9887-9BB396234982),
version(1.0),
helpstring("StringUtil 1.0 Type Library")
]
library DGStringUtilLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");
[
uuid(22B4BAC9-E321-4674-8E8F-372B6256FBC3),
helpstring("DevGuy StringUtil Class")
]
coclass DGStringUtil
{
[default] interface IDGStringUtil;
};
};
Revised on: 2000-09-23
The following code demonstrates a memory leak:
CComBSTR str;
pFoo->get_Bar(&str);
pFoo->get_Baf(&str);
CComBSTR::operator & does not free the CComBSTR's string. To remedy this,
call str.Empty() before calling get_Baf().
The same thing can happen with strings stored in CComVariant. You must
call the Clear() method first.
Note that when creating a CComVariant with a BSTR,
you must make sure the BSTR doesn't get deallocated twice.
Use CComVariant(CComBSTR("hello").Detach()))
Revised on: 2000-09-23
- Also see the information regarding implementing
interfaces
- Implement
ISupportsErrorInfo
- All interfaces should be dual
- Use
CComPtr or similar smart pointers to avoid memory leaks especially in situations
when MFC throws exceptions.
- Interfaces and TLBs
- Multiple interfaces can go into one IDL/TLB file, but minimize this practice - logically
distinct interfaces should not appear in the same TLB
- In order to use the ATL wizard, one project may "own" the IDL so you can add
methods/properties easily - this is called the "master project"
- Projects that implement existing interfaces should import the TLB instead of
"#including" the MIDL-generated header files from the master project
- See MSDN section for a link regarding implementing existing
interfaces with ATL
- ATL Tips:
LIBCMT.lib (crt0.obj) : error LNK2001: unresolved external symbol _main
If you get this error when building the release build, simply click on the C/C++ tab in
Project->Settings and select Preprocessor from the Category drop-down box. Remove
the _ALT_MIN_CRT flag in the Preprocessor definitions box. By default, ATL release
builds use a tiny version of the C runtime library (CRT). However, this tiny CRT
does not include certain functions which your ATL project may use. Therefore, you
will get unresolved symbols. By removing this flag, you will tell the linker to link
in full-size version of the CRT. However, this only increases your DLL by ~30KB.
_ATL_STATIC_REGISTRY and _ATL_DLL
If one uses the VC++ wizard to build an ATL project, then try to build a
"MinSIZE" build, you may get the error:
C:\Program Files\Microsoft Visual Studio\VC98\ATL\INCLUDE\atlbase.h(4927) : error
C2259: 'CRegObject' : cannot instantiate abstract class due to the following members:
C:\Program Files\Microsoft Visual Studio\VC98\ATL\INCLUDE\statreg.h(233) : see declaration
of 'CRegObject' C:\Program Files\Microsoft Visual Studio\VC98\ATL\INCLUDE\atlbase.h(4927)
:
warning C4259: 'long __stdcall IUnknown::QueryInterface(const struct _GUID&,void ** )'
: pure virtual function was not defined
C:\Program Files\Microsoft Visual Studio\VC98\INCLUDE\unknwn.h(109) : see declaration of
'QueryInterface'
This turns out to be because of the way VC++ links with ATL. There are two ways; one,
by compiling in statically all the ATL base functions (QueryInterface, etc.) or by linking
dynamically with ATL.DLL The former is invoked by defining _ATL_STATIC_REGISTRY, which is
placed by the wizard into StdAfx.h. However, when the MinSize version of the Unicode or
non-Unicode release is setup, the wizard also places _ATL_DLL in the preprocessor
declarations. _ATL_STATIC_REGISTRY and ATL_DLL are not compatible, hence the error.
So, one can remove (or conditionally ifdef) the _ATL_STATIC_REGISTRY if one wants to
save 5 kB (size of the statically linked ATL functions) or remove _ATL_DLL and accept the
extra 5 KB. For further info, see MSDN: Article ID: Q166717 .
In the same build (after fixing the former) I also got an odd message: warning LNK4086: entrypoint "_wWinMainCRTStartup" is not
__stdcall with 12 bytes of arguments; image may not run. This turned out to be the
fact that "_wWinMainCRTStartup" waas defined. In fact, it doesn't cause a
problem, but one gets the warning because wWinMainCRTStartup should only be defined as the
entry point for an EXE, not a DLL. This warning only occurs with Release builds, not with
Debug builds - whether Unicode or no.... For further info see Article ID: Q166480
IUNKNOWN
If you want to call QI inside an ATL implementation, in the class declaration
declare
DECLARE_GET_CONTROLLING_UNKNOWN()
And use
GetControllingUnknown() to get the IUnknown pointer of the object.
E_CLASSNOTREG or E_INTERFACENOTIMPL
Problem:
When I try to CoCreate my object I get E_CLASSNOTREG. Or, I get
E_INTERFACENOTIMPL when I call QueryInterface.
Solution:
Look at the header file for your object, specifically at the
BEGIN_COM_MAP section. Make sure there is a COM_INTERFACE_ENTRY
item for the interface in question. For example, the following is a
COM map for an object that implements several interfaces.
BEGIN_COM_MAP(CDispenser)
COM_INTERFACE_ENTRY(IDGDispenser)
COM_INTERFACE_ENTRY(ISupportErrorInfo)
COM_INTERFACE_ENTRY(IConnectionPointContainer)
COM_INTERFACE_ENTRY_AGGREGATE(IID_IMarshal, m_pUnkMarshaler.p)
COM_INTERFACE_ENTRY2(IDispatch, IDGDispenser)
COM_INTERFACE_ENTRY(IXMLAcceptor)
END_COM_MAP()
Revised on: 2000-09-23
Use
#import <TLB filename> named_guids raw_interfaces_only
raw_native_types no_implementation
it's convenient, avoids build dependencies, and MIDL-generated files don't have to be
checked in if you use it (unless you need to...)
Do not use no_namespace unless you really mean it.
Namespaces are absolutely required when a project implements an interface that's
also #import'ed.
Using plain #import causes COM object wrappers to throw exceptions instead of returning
an HRESULT indicating the error. Use raw_interfaces_only to turn it off. See:
MSDN\vccore.chm::/html/_predir_the_.23.import_directive.htm
When importing non-dual COM objects, don't use
raw_interfaces_only. The importer will create a nice wrapper class
around IDispatch (see the generated .tlh file). Note that you should catch
_com_error.
If you get the linker error LNK1179, try removing named_guids.
Revised on: 2000-09-23
- TLB is a binary interface whereas IDL is a text interface that must be compiled.
TLB is the only unambiguous form of an interface.
- TLB files should be used as the interface between components. Do not include IDL
files to use a COM object. Use TLB instead.
- Do not check in TLB files unless they're from third party products -- TLBs
are generated by VC++ projects.
Revised on: 2000-09-23
The TLB or IDL file for third party products should be checked
into source code control. This makes it easier to maintain machines since
it is not always necessary to install an SDK because the TLB files are
sufficient.
In some situations the TLB file isn't available or the IDL can't be
compiled without having an SDK installed on a machine. This means you must use #import
"dllname". After you do that, you might be tempted to use the generated TLH file
directly. However, the TLH file is an artifact that is much more obscure than TLB files so
they shouldn't be #include'd explicitly.
Instead of checking in TLH files, do one of the following:
Generate the TLB and check it into a 3rd party directory - then #import the
TLB
Check in the DLL in a 3rd party directory - then #import the DLL
Make sure the DLL is installed somewhere in the PATH on all machines
that need to build the project. Don't
assume all machines install the DLL in the same directory; don't specify the full PATH of
the DLL
#1 or #2 is preferred for more "obscure" 3rd party products.
Revised on: 2000-09-23
A library's help string should follow the form:
<Company Name> <Control Name> Type Library
Such as "ACME Service Type Library".
In general, define interfaces inside the library definition. Do not do
this if you intend to implement the same interface multiple times. You
must define dispinterfaces inside library declarations (at least, that's
what I've found from my experience).
Automation Considerations and Datatypes
Revised on: 2002-03-12
This is the most
important section of this document. Failure to understand how
scripting languages utilize the myriad COM datatypes will yield unusable
interfaces. Pay special consideration to the "When should VARIANT be
used?" section below.
In general, all interfaces should inherit from IDispatch
and be dual and should be marked oleautomation
[
object,
uuid(DAC49A7E-239A-42be-BECC-FB22E253FEDA),
oleautomation,
dual,
helpstring(" Interface"),
pointer_default(unique),
oleautomation
]
interface IFoo : IDispatch
{
}
Input Parameters
- Simple input datatypes:
VARIANT, BSTR, long, short, float, double,
DATE, VARIANT_BOOL,
LPDISPATCH
- Use
VARIANT_BOOL, not BOOL. The C++ constants for boolean values are VARIANT_TRUE and
VARIANT_FALSE.
- Use
long instead of ULONG or DWORD - unsigned 32-bit integers are not compatible with VB
6.0 or earlier. int is the same as long.
[in] VARIANT values should be passed in as VARIANT,
not as VARIANT *
- Even if a method accepts a
VARIANT as opposed to a VARIANT
*, pointers can be passed indirectly via VT_BYREF|VT_VARIANT.
VBScript passes [in] VARIANTs in this fashion.
- Optional parameters: Must be
VARIANT or VARIANT
* - [in, optional] VARIANT v
Output Parameters
- Scripting languages generally don't allow multiple output parameters.
A method may only have one output parameter and must be marked "retval."
Using multiple output parameters requires the use of VARIANT arrays (Safearray).
- Pointer types for
[out]: VARIANT*, LPDISPATCH* (VBScript
does not support any other types)
- Use
[out] VARIANT*, not [out] VARIANT
- Methods that accept output
VARIANT*
values MUST call VariantClear on VARIANT *
parameters before changing the value of the VARIANT
- Pointer types for
[out,retval]: VARIANT*, BSTR*, long *,
short *, float *, double *, VARIANT *, LPDISPATCH
- VBScript only supports arrays (
SAFEARRAY) that contain VARIANTs
(input and output). VBScript passes [in] VARIANTSs as
VT_BYREF|VT_VARIANT and the pvarVal pointer
points to a VARIANT of type VT_BYREF|VT_VARIANT|VT_ARRAY
which means the SAFEARRAY is in the pparray
member. The members of the array are VT_VARIANT, not BYREF.
- Array indexes should start with 0
When should VARIANT be used?
- For any output parameter that is not a
retval ([out,retval])
and is not a LPDISPATCH, use
VARIANT * --
VBScript and ActivePerl can't handle it otherwise
- If your method accepts a NULL interface pointer, pass a
VARIANT.
JScript can not handle it otherwise.
- Use
VARIANT if you want to accept or return an array (SAFEARRAY) of
values
- Use
VARIANT if you want to "overload" a method name so that it
can accept a variety of arguments and datatypes
- Use
VARIANT if you want the parameter to have a default value.
Make the
parameter optional -- [in, optional] VARIANT v -- When
optional parameters are omitted, the variant's type is VT_ERROR.
Creating VB SAFEARRAYs in JScript
You can not pass JScript arrays to methods that take VB
SAFEARRAYs (SAFEARRAYs are passed as VARIANTs).
-
Create a Scripting.Dictionary object
-
Populate the dictionary by calling Add(key,value)
repeatedly where the key value is an incrementing integer
-
Call the Items() method on the dictionary object
which returns a Variant containing a VBArray
Reading VB SAFEARRAYs in JScript
Use the
VBArray object
An optional parameter is defined as:
[in, optional] VARIANT v
If an optional argument value is not given, the variant's type will be
VT_ERROR and its scode member is set to DISP_E_PARAMNOTFOUND
Returning Enumerators
There is a standard "enumerator" concept in COM that is used by VB's
foreach
statement. This is used when you want to return multiple objects instead of just
one. This is preferable to using VARIANT arrays (SAFEARRAY)
in many situations (however, VB's foreach statement supports
arrays too). This
requires that the object have some properties available via IDispatch.
See the information on ATL
enumerators.
Revised on: 2000-09-23
Use the Detach or CopyTo methods on CComBSTR.
CComBSTR bstr("Hi");
// pbstrOut is a BSTR *
*pbstrOut = bstr.Detach();
or
return m_bstrValue.CopyTo(pbstrOut);
Revised on: 2000-09-23
This is probably the most common mistake made by C++ programmers.
When a COM method takes a BSTR, you should send it a BSTR, not a
hard-coded string created by the L"" operator or the _T("") macro.
As with all things COM, this rule is deceptive. Passing a constant like L"foo" to such a method works only when the caller
and the COM object live in the same apartment. If you were to call the COM object from a
different apartment from which it was created (i.e., your work is split across threads),
COM would try to marshal the string across apartments. Because the string is not a
BSTR - its length is not
in the 4 bytes before the address of the passed-in string - the method would receive a
random substring or superstring of the value, or an exception would be thrown in the COM
runtime.
Here is how to create a hard-coded BSTR:
CComBStr(L"foo")
The _bstr_t class can be used as well.
Using CComVariant Properly
Revised on: 2001-04-11
It's very easy to misuse CComVariant, which is a pity since CComVariant
is supposed to make your life easier. The important thing to
remember is that CComVariant, like CComPtr, exists to
free resources for you when the object is destroyed. That can be a bad
thing if you're not careful. Like all other "smart pointers", CComVariant
can end up freeing the same resource (BSTR, array, interface
pointer, etc.) twice, which will result in memory corruption, leading to
unpredictable behavior.
Here are the common mistakes made with CComVariant
- Assigning a
CComBSTR to a CComVariant without
using Detach() on the CComBSTR:
CComVariant v;
v.vt = VT_BSTR;
v.bstrVal = CComBSTR(L"Hello"); // error -- call Detach() on the
string
- Assigning a
CComPtr to a CComVariant without
using Detach() on the CComPtr:
CComPtr<IUnknown> pFoo;
CComVariant v;
v.vt = VT_DISPATCH;
v.pdispVal = pFoo; // error -- call Detach() on the pointer
Revised on: 2000-09-23
- Creating COM instances on remote machines
- use
CoCreateInstanceEx which is defined in <objbase.h> may need to define
_WIN32_DCOM in order for the method to be properly prototyped
- Take advantage of the
MULTI_QI structure which is one of the parameters for
CoCreateInstanceEx; the structure allows a client to specify all of the interfaces wants
on the instantiated object which will save any round trips performed in order to do a
QueryInterface
- Always consider performance especially when you feel the urge to
marshal interface
pointers (
LPDISPATCH, LPUNKNOWN) because every time a property/method is accessed by the
remote machine on the marshalled interface pointer, a round trip is necessary.
Consider using "by value" semantics such as a VARIANT containing a
SAFEARRAY if you need to pass multiple values
- Security settings
- Use
DCOMCNFG.EXE to
enable DCOM on your computer as well as to modify security settings
- Use
CoInitializeSecurity in order for the client application to properly allow the
server process identity to call into the client process (ex. QueryInterface); if
CoInitializeSecurity is not called, then the default DCOM security settings will be
enforced which often does not allow most server process identities to call
back into the client process (see Q158508 for a thorough explanation)
CSecurityDescriptor sd;
sd.InitializeFromThreadToken();
CoInitializeSecurity( sd,
-1,
NULL,
NULL,
RPC_C_AUTHN_LEVEL_PKT,
RPC_C_IMP_LEVEL_IMPERSONATE,
NULL,
EOAC_NONE,
NULL);
Revised on: 2000-09-23
In order to read resources correctly, generally every COM method should start with
AFX_MANAGE_STATE(AfxGetStaticModuleState())
If the project is MFC-based. The ATL Wizards insert this code for you automatically.
Revised on: 2000-09-23
All threads should initialize COM by first calling CoInitialize or
CoInitializeEx(COINIT_MULTITHREADED) and
CoUninitialize() on exit.
This allows us to write utility code that uses COM but doesn't have to fret about
CoInit/CoUninit. Initializing COM when the caller is unaware of that fact can cause
really bad performance if COM isn't initialized - imagine calling this utility code in a
loop - so that's why we shouldn't do it. It is a convenient feature for utility code
to start up and shut down the COM runtime for you, but it's a bad feature - unexpected but
correct results.
Revised on: 2000-09-23
Instantiate COM objects using the version-independent ProgID, not CLSIDs. See the
function CLSIDFromProgID. This rule of thumb should not be
followed for objects that are created frequently.
Revised on: 2000-09-23
- Check input parameters in Debug and Release builds (in other words, all builds!) and
return E_INVALIDARG - don't use ASSERTs
Revised on: 2000-09-23
- Know the COM threading models and pick the right one for the job
- This is particularly an issue with client code - client code should choose the right
threading model when initializing COM - performance is highly dependent upon how
CoInitialize or CoInitializeEx are called.
Implementing Existing Interfaces With ATL
Revised on: 2000-09-27
Go to the ClassView tab for the project. Right click on the appropriate
object. Select "implement interface." After doing this,
change the #import statements -- you will usually need to change the paths to
the TLB files.
Or, if you want to do it manually...
The ATL new object wizard creates a COM object with its own interface.
If you want to implement an existing interface (instead of creating a new
interface), or if you want to add a new interface to an existing object, follow
these guidelines.
- Add the interface to the IDL file
import "oaidl.idl";
import "ocidl.idl";
import "../../XMLAcceptor/n_xmlacc/n_xmlacc.idl";
<<...skipping many lines...>>
coclass IPResolver
{
[default] interface IXMLAcceptor;
interface IIPResolver;
};
- Create a new file that imports the TLB of the implemented interface
#include "stdafx.h"
#import "source\tlb\n_xmlacc.tlb" no_namespace named_guids no_implementation raw_interfaces_only
- In the .H file, delete the line
COM_INTERFACE_ENTRY(IDispatch),
add a COM_INTERFACE_ENTRY2 entry for the default interface, and add a COM_INTERFACE_ENTRY
for the interface that is not the default interface:
BEGIN_COM_MAP(CIPResolver)
COM_INTERFACE_ENTRY2(IDispatch, IXMLAcceptor)
COM_INTERFACE_ENTRY(IIPResolver)
END_COM_MAP()
- Add the interface's methods to the .H and .CPP files
Problem:
IFoo first seen as struct; now seen as class; struct type redefinition
Solution:
This happens when a project #imports a type library and #includes the
same library's MIDL-generated files.
The #import directive probably uses no_namespace -- otherwise,
you should not be seeing this error message.
- Change stdafx.h to #import the TLB file using
no_namespace named_guids
raw_interfaces_only raw_native_types
- Remove all statements that #include the generated header and .C files
Problem:
I'm getting undefined symbols at link time, such as IID_Foo not found
Solution:
Create a file, such as "import.cpp" that #import's the type
library that contain the implemented interfaces using no_namespace
named_guids raw_interfaces_only raw_native_types
Problem:
I did that and now I'm getting errors like "was seen as 'struct';
now seen as 'class'"
Solution:
This happens when IDL files import other IDL files. The
solution is to have only one #import statement per file. Create
additional .cpp files, one for each type library imported.
Problem:
I'm still getting "was seen as 'struct';
now seen as 'class'" or struct type redefinition
Solution:
You're probably implementing an interface that a .h or .cpp file loads
via #import and the #import uses the keyword no_namespace. I don't
know the answer to that one except to use a namespace and use namespace
prefixes or "using namespaces." But then the compiler might complain that
LIBID_Foo isn't defined, and I don't have a solution for that.
Also, the IDL file needs to "import" the IDL file that contains
the problematic interface, otherwise the compiler will complain that
LIBID_Foo isn't defined.
Problem:
The compiler complains about an "import" statement in an IDL
file -- the path looks correct.
Solution:
This happens when an IDL file imports an IDL file that in turn imports
another IDL file (there are three IDL files in all). All import statements in the IDL files must either
use absolute paths (bad idea), have no path component, or must have the same number of
..\ 's
Lesson: put all IDL files in the same
directory
Implementing shared state with the dispenser paradigm
Revised on: 2002-04-09
Consider the scenario in which a global variable holds
an interface pointer to a COM object. Multiple threads have access
to this interface pointer.
Storing interface pointers to in-process COM objects in C++
global variables is problematic because they could be accessed by different
types of apartments. You must use the Global Instance Table (GIT) or CoMarshal to pass
interfaces to single-threaded apartment objects across apartment boundaries.
"Raw" pointers to objects that use the
"free" (or the mythological "rental") thread model can be
accessed by STAs without any problems, as long as the objects are instantiated
in-process.
The problem is getting a raw pointer to an interface when an
object is created via a single-threaded apartment (STA). The global
interface pointer should only be a "raw" pointer to the object, not a
pointer to a proxy. Otherwise other STAs and MTAs won't be able to use the
pointer.
Marking an object as "Both" is one
way to do this. So why not use "Both" and be done with it?
An object marked "Both" is multi-threaded and interface pointers to
these objects can be used in different threads -- even in different
apartments. Although this seems like the ideal solution, "Both"
doesn't provide the lifetime characteristics needed for global objects.
Consider the following scenario:
-
A thread living in an STA creates an object
-
The pointer to the object's interface is stored in a global
variable
-
The thread that created the STA calls
CoUninitialize
-
Some code attempts to use the interface pointer stored in
the global variable
This results in the error "the server has terminated"
or, if you call QueryInterface on the
pointer, "E_NOINTERFACE -- no such interface implemented." The apartment that created to object was
terminated. Creating objects via an STA and then terminating the apartment
doesn't work if you want objects to live beyond the life of the STA.
"Both" doesn't solve this problem. "Both" causes the
object to be instantiated in the STA. When the STA is terminated, the
singleton dies with it.
There is another problem with this approach: every DLL has
its own global interface pointer. It is often desirable for all objects in
an entire process to share the same global state, regardless of whether the
objects live in the same DLL.
One way to implement singletons is via ATL's singleton class
factory. However, it isn't wise to implement objects
as ATL singletons. The next topic explains why.
Also refer to
COM
Singletons - A Dangerous Animal, By Richard Blewett.
A better solution is to first implement all objects as regular
objects. It's a good idea to be able to create two instances of an object
if you need to some day. Then, implement another object to
"dispense" interface pointers to these objects, as if they were
singletons. For example, the "dispenser" interface would have a
property whose datatype is IDispatch which provides access to a
"global" object.
This approach is a good start. However, it still doesn't
deal with the threading issues discussed above. The result can be strange
intermittent problems that apparently occur more often when objects are
instantiated by IIS (Microsoft Internet Information Server).
Here is a complete solution for managing
global in-process objects:
-
Declare an interface that "dispenses" global data, such as
interface pointers or strings
-
Implement this interface twice
-
One implementation implements the interface "for real." This
implementation can use the Both thread model, and must use critical sections to
make sure the object's data doesn't become corrupted.
-
The other implementation delegates to a static (global)
interface pointer to the "real" dispenser implementation
- This implementation uses a global critical section object to ensure that only one instance is
created (see example below)
- This object uses the Free thread model. Using the Free thread model
protects the dispenser from code that calls
CoUninitialize.
- This object ensures the multi-threaded apartment stays alive
forever. It starts a thread that calls
CoCreateInstanceEx(COINIT_MULTITHREADED) and waits indefinitely.
When the application needs to access global data, it creates a
delegate dispenser object. The application has no idea that it's not
talking to the real dispenser. When the application is finished with the
delegate, it calls Release on the interface pointer. The real
dispenser object creates objects and manages the interface pointers to them.
This solution avoids all marshaling issues without
resorting to the free-threaded marshaller, ATL's singleton class factory
implementation, CoMarshalInterfaceInStream, or the GIT. The
dispenser can even manage pointers to single-threaded objects without running
into any marshalling issues. Unfortunately, there is a performance
penalty to be paid for creating the delegate and destroying it repeatedly.
This is the price applications must pay to share global state.
The delegate never decreases the reference count of the "real"
object. Like ATL singletons, the object will never properly clean itself
up when the process dies, as described in the next topic. This also causes the module (DLL) in which these
objects reside to never be unloaded until the process dies, and this can result
in remote COM servers never terminating by themselves, even when there are no objects
instantiated in the server. All of these problems can be avoided by adding
a Stop method to the interface that calls Release on the
global interface pointer. Knowing exactly when to call Stop
is a tricky proposition.
On a related note, it is a good idea to provide a method on the
dispenser interface to cause the "real" dispenser object to release
its interface pointers and create new objects. In the real world,
long-lived objects may become unusable over time due to bugs or network
problems. Sometimes creating a new object is the only remedy.
The following code is an example of how a delegate object can
store a global interface
pointer to the "real" dispenser object. Note that AddRef
isn't called, and this solution only works because there is no Stop
method which would cause the "real" dispenser object to be released
and destroyed while a method is running. In this example, the progid of
the "real" dispenser object is devguy.GlobalsSingleton.
static CComAutoCriticalSection static_cs;
static IGlobals * static_pGlobals = NULL;
IGlobals *GetGlobals()
{
HRESULT hr;
for (int i = 0; i < 10; i++)
{
if (!static_pGlobals)
{
static_cs.Lock();
try
{
if (!static_pGlobals)
{
hr = CoCreateInstance(L"devguy.GlobalsSingleton",
__uuidof(IGlobals), (void**) &static_pGlobals, CLSCTX_INPROC), 0);
if (FAILED(hr)) throw hr;
}
}
catch(...){
ATL_TRACE(_T("Unable to create devguy.GlobalsSingleton"))
}
static_cs.Unlock();
}
else
break;
}
return static_pGlobals;
}
Revised on: 2002-04-09
There are several problems with singletons ala Design
Patterns. Singletons are inflexible because, by
definition, you can't instantiate more than one of them. You might be
convinced that a particular object is a singleton and six months later change
your mind. At that point reimplementing the object may not be an
option. Therefore, COM objects should almost never be implemented as
singletons. Instead, a secondary "dispenser" object should dole
out object instances. Dispensers can be implemented as ATL singletons (DECLARE_CLASS_FACTORY_SINGLETON).
However, ATL singletons can lead to bizarre behavior that is similar to objects that
implement the free-threaded marshaler. Please refer to
COM
Singletons - A Dangerous Animal, By Richard Blewett.
Singletons have other problems. Singletons' reference counts are always greater than zero, which means they
never get removed from memory. It's pointless to call Release
on a singleton. There is no way to free a singleton's resources via Release.
Singletons are only cleaned up when the process is terminated. This can be
a problem if the singletons occupy large amounts of memory. The
work-around is to provide a method that cleans up resources, via reference
counting or otherwise. But there never seems to be a good time to release
a singleton's resources.
By far, the worst behavior of singletons is that their destructors aren't called until all threads in a process have
terminated. Any
threads that the singleton might have created will not be around by the time the
singleton's destructor executes. This can lead to file corruption if the
singleton writes to files in background threads because the singleton is not
able to wait for the threads to finish pending operations. Other
side-effects can result in even worse consequences.
The destruction behavior of singletons can lead to crashes. Singletons get removed from memory
essentially at random. Consider the following scenario. The singleton
and the user of the singleton, another COM object, live in different DLLs.
When the process terminates, the singleton DLL gets unloaded first, leaving a
dangling interface pointer in the client object. When the client object
gets destroyed, it calls Release on the singleton's
interface. This results in a crash because the singleton's data and code
have been removed from the process's address space.
Lesson:
Never call Release on a singleton's interface in a destructor
Corollary:
Class data members should not use smart pointers to hold interface pointers to singletons.
Just use raw pointers to singletons and don't worry about releasing them.
A singleton "dispenser" object provides a false sense of
security. Just because a singleton dispenser holds onto an interface
pointer to a dispensed object doesn't mean that the object can't go away.
If the singleton dispenses Apartment-threaded objects, it's possible that the
calling code can terminate the apartment via CoUninitialize (or by the thread
terminating), causing the object to be "disconnected." The best
way around this problem is to implement the singleton as "Free" and
have the singleton manage one thread that keeps the multi-threaded apartment
alive. These two steps insulate your singleton from application code -- it
can't unload COM out from under it. This problem is also described in the above
section.
It is not possible to rely on the dispenser's destructor to properly clean
up the dispensed objects. Instead, client logic must control the lifetime
of the dispensed singletons. One way to do this is override Lock
and Unlock in CComModule. When the lock count
goes to zero, it's time to tell the dispenser to release all cached interface
pointers. Instead of using AddRef
and Release on the dispenser object to accomplish this objective,
add new methods like AddRefSingleton and ReleaseSingleton to
the dispenser's interface. Lock calls AddRefSingleton
when the reference count is one and Unlock calls ReleaseSingleton
when the lock count is zero. However, this can result in restarting the
dispenser too frequently, which negatively affects performance.
There just doesn't seem to be a good time to release global objects unless
the executable itself does it. Which, of course, is not possible if code is running
inside IIS, MTS, Microsoft Management Console, or a surrogate process.
Consider using COM exes or NT services the next time you want a
singleton. Although you pay in terms of network overhead and additional
installation tasks, there will be fewer development headaches and much less
chance of resource misuse that can lead to file corruption and worse
problems.
If you want to dedicate one singleton per process, you can
accomplish this with a "dispenser" COM singleton object that instantiates objects.
The dispenser is described above and also in the above
section. If you want multiple processes to share the same
singleton, use an NT service. If you want processes on different machines
to share the same singleton, use services. If you want each COM DLL to
have its own singleton instance, ATL singletons won't work for you -- you must
use C++ global variables, which are very problematic (see above
section).
Revised on: 2000-09-23
After building up several years of experience with COM's lousy
HRESULT/IErrorInfo scheme, I have come up some rules of thumb:
-
Always support IErrorInfo
-
All methods immediately call GetLastError(NULL, &pIErrInfo)
to elminiate any "residue" error objects
-
Assume any COM method you call will clear out the error
object, so hold on to error objects if you don't want to lose them
-
Don't bother looking at the Interface pointer, CLSID, PROGID,
etc. members of the IErrorInfo object -- treat every IErrorInfo as a valid
error object
-
Return E_FAIL unless you're certain the SCODE is
"local" for your interface. Interfaces should not return
SCODEs from other Interfaces unless you really know what you're doing
Like I said, these are my rules of thumb. Here's some more
information:
COM's error handling is flaky at best. There is a very
real chance of "ErrorInfo residue" which occurs when:
-
A method is called on an object that supports IErrorInfo
-
Method returns and error and sets the ErrorInfo object
-
Caller fails to call GetErrorInfo
-
A method is called on an object that doesn't support
IErrorInfo
-
Method returns a failure SCODE
-
Caller investigates the ErrorInfo object
-
Result: The ErrorInfo object pertains to the first method
call, not the second one, but the caller *thinks* it pertains to the second
call
There are some ways around ErrorInfo residue, such as calling GetErrorInfo
before calling any methods. This is undesirable as it has a performance
penalty; however, it's the most foolproof method. All other schemes rely
on the caller doing the right thing by calling GetErrorInfo to
clear the ErrorInfo object after making a method call. It's not uncommon
to find methods that clear ErrorInfo before performing any
work. This is a good solution but it's not foolproof. Consider a
DCOM scenario in which the client and server have been disconnected.
Error Codes (SCODES)
- The error code should be greater than or equal to 0x500 and less than
0xFFFF.
Error codes are specific to an interface.
- Different COM interfaces can use the same error code value - there is no master error code list
- HRESULTs should be defined only for errors that can be handled. Otherwise, the
methods will just return E_FAIL.
- Error codes are documented with each interface's main documentation
- Generate error codes with
MAKE_HRESULT(1, FACILITY_ITF, 0x500). This
results in 0x80040500
- When calling other COM methods, if a failure occurs,
E_FAIL should be returned instead of
returning the HRESULT from the last call
- This prevents ambiguity that arises from overlapping HRESULTs codes
Error Info
- All objects should implement
ISupportErrorInfo and return S_TRUE
- If an error can be handled, clear the error message via GetErrorInfo()
- If you don't do this, sometimes the error object gets mistaken as the error object for
the current error when instead it was set for some other error that occurred earlier
- An error object should be set on the current thread only when specific events
occur. These events will be determined according to end-user's ability to understand
the error message.
- When an error is encountered and an error object isn't needed, call
SetErrorInfo(NULL)
- This prevents using the error object from previous errors
- This doesn't include the times when method returns an HRESULT
indicating failure
- Because the method would have already set the ErrorInfo object
- But it may include times when code calls third-party code that doesn't
implement
ISupportsErrorInfo
Returning Error Information
- Implement the
ISupportErrorInfo interface.
- To create an instance of the generic error object, call the
CreateErrorInfo
function.
- To set its contents, use the
ICreateErrorInfo methods.
- To associate the error object with the current logical thread, call the
SetErrorInfo
function.
The following figure illustrates this procedure.

To retrieve error information
- Check whether the returned value represents an error that the object is prepared to
handle.
- Call QueryInterface to get a pointer to the
ISupportErrorInfo interface.
Then, call InterfaceSupportsErrorInfo to verify that the error was raised by the
object that returned it and that the error object pertains to the current error, and not
to a previous call.
- To get a pointer to the error object, call the
GetErrorInfo function.
- To retrieve information from the error object, use the
IErrorInfo methods.
The following figure illustrates this procedure.

If the object is not prepared to handle the error, but needs to propagate the error
information further down the call chain, it should simply pass the return value to its
caller. Because the GetErrorInfo function clears the error information and passes
ownership of the error object to the caller, the function should be called only by the
object that handles the error.
Beware the Connection Point Implementation Wizard
Revised on: 2001-06-30
It's very easy to add a connection point to an ATL object. However,
there are problems with the generated code, which always seems to be the case
with Microsoft's wizards.
- If you haven't already done so, create a DISPINTERFACE for your event
[
uuid(A6AE2118-A509-4cd0-A48C-D9EB6742AE3D),
nonextensible,
helpstring("")
]
dispinterface _DIEvent
{
properties:
methods:
[id(1), helpstring("")] HRESULT OnEvent([in] long data);
};
- Go to the Class View in the Project Viewer
- Right-click on the object name that you want to add the connection point
to
- Select "implement connection point"
- Choose the event interface that the connection point invokes (this is the
outgoing interface)
- Open the *CP.h file
- Comment out the #import of the DLL or #import the TLB instead. The
DLL probably won't exist at build time.
- Advise, Unadvise, and Fire_ lock the object's critical section.
Therefore the following scenario will result in a deadlock
- Your object has Both or Free model
- The event sink has Apartment model
- Your object calls Lock()
- Your object calls Fire_Foo()
- The event sink object calls Unadvise()
The best way to avoid this is to make sure your object doesn't have its
critical section locked when calling Fire.
- The Fire_ methods will fail if an event sink calls Unadvise. Also,
the method returns a bogus SCODE if there are no event sinks registered with
the object. Here
is an example fix.
HRESULT Fire_Event(long data)
{
/* Copy the list of event sinks to a local
variable to eliminate issues with
Unadvise being called here */
CComDynamicUnkArray vec;
T* pT = static_cast<T*>(this);
pT->Lock();
if (!m_vec.GetSize())
{
pT->Unlock();
return S_OK;
}
vec = m_vec;
pT->Unlock();
CComVariant varResult;
int nConnectionIndex;
CComVariant* pvars = new CComVariant[1];
int nConnections = vec.GetSize();
for ( nConnectionIndex = 0;
nConnectionIndex < nConnections;
nConnectionIndex++)
{
CComPtr<IUnknown> sp;
sp = vec.GetAt(nConnectionIndex);
IDispatch* pDispatch =
reinterpret_cast<IDispatch*>(sp.p);
if (pDispatch != NULL)
{
VariantClear(&varResult);
pvars[0] = data;
DISPPARAMS disp = { pvars, NULL, 2, 0 };
pDispatch->Invoke(0x1, IID_NULL,
LOCALE_USER_DEFAULT,
DISPATCH_METHOD,
&disp, &varResult, NULL, NULL);
}
delete[] pvars;
return varResult.scode;
}
(c) 2001 DevGuy.com
|