Addendum to Beginning ATL COM Programming
Chapter 6 of Beginning ATL COM Programming explained connection points and developed an example object (theEvents object) that generated events and a control
(the EventControl object) that responded to them. In that example the object and the
control were both created on a local machine, and since the Events object was created
using the same identity as the client it means that it has full permission to call methods on the
sink interface of the control.If the object and control are on different machines then you have a different situation. In general, when the source and sink of events are on different machines you have to use DCOMCNFG to mark the source server as being launchable by the account used by the client machine and also to specify that this account can access any objects (in particular the event source object) created by the server. Further, since the source generates events by calling methods on the sink object you will need to mark the sink object on the client machine as accessible by the account that the source server runs under on the remote host. (You do not need to allow the source object to launch the sink because the sink will always be running before the source has beens created).
In our example the first part is simple: on the server machine run DCOMCNFG, select the
Events Class and allow the client account to launch and access the server
(to allow anyone to do this give launch and access permissions to the EveryOne account).
However, it is not so simple for the control. The reason is that security is applied on a process,
not a DLL: an inproc server takes the security settings of the process that loads it. (Don't be fooled
by DCOMCNFG displaying the server as the Events Class, this is a misleading
bug in DCOMCNFG where it gives the name of the first coclass that it finds that is in this
server; security is applied on the entire server process and not just on this one coclass.)
In the case of, say, loading a control into IE, the security changes must be made on Internet Explorer
itself. This is a problem, because these security settings will be applied to all pages loaded into IE
and not just the one that hosts your control.
"Ahh!", you say, "there is a COM API function called CoInitializeSecurity() supplied
to change a process's security". Indeed there is, but this function should be called immediately after
CoInitializeEx() has been called and before any interfaces are marshaled. If you do not
call CoInitializeSecurity() at this point then COM will search through the registry for
the settings applied through DCOMCNFG or failing that it will use default values. An inproc server does
not call CoInitializeEx() and instead is launched into the apartment in the client that
called CoCreateInstanceEx() or, if it is incompatible with its threading model, an apartment
created by COM.
For more details on security issues, and the registry entries that DCOMCNFG will change, read Chapter 7 of Professional DCOM Programming.
The Middleman object
The solution to this problem is to use a middleman object. Such an object is implemented in a local
server and hence will have a separate security context to the control container and thus you can use
DCOMCNG to specify the accounts that can access objects created by the server. A middleman object that
catches events generated by a remote Events object can be configured to give access to the
remote account. If you want to turn of security completely, you can make the middleman object launchable
and accessible by EveryOne, so that any object can access the remote Events
object; and so that you can catch events generated by this object you can use CoInitializeSecurity()
to turn off authentication.
In the following picture you can see how this works. The MiddlemanSvr process serves
two COM classes, EventsMiddleman and EMMEvents. The EventControl
object will create an instance of the EventsMiddleman, and since this implements the
IEvents interface it mimics the remote Events object that the control really wants
to access. The remote object will generate events and so will need to be passed the IEventsSink
interface of a sink object. These sink objects are instances of the EMMEvents class.

The EventsMiddleMan object will create the Events object on a remote machine,
to do this it will need to know what that machine's name is. In this example I have decided to punt on
this issue and get the middleman object to read a registry value for the server name.
When the EventControl object has created the EventsMiddleMan object it will
then try to establish a connection. To do this it will QI() for the
IConnectionPointContainer interface and use this to get hold of the connection point for the
sink interface. The implementation of this interface in EventsMiddleMan just passes the
calls to the EMMEvents object. The reason is that this object will be catching the events
from the remote Events object and then generate the events for the control, hence it needs
to have access to all the connections.
The final point you should notice is the IEMMEvents interface on the sink object. This
has methods to initialize the sink object when it is first created (in particular to pass it an interface
pointer to the remote Events object so that it can make the connection) and to clean up the
sink object when the EventsMiddleMan object is released. This last action is important
because if the sink object has a connection to the remote object it must break the connection so that
the remote object can die release the reference on the sink object, so the sink object can die. If you
do not break the connection then the sink object and remote Events object will be immortal,
forever holding hands.
Both the MiddleManSvr and EventWatcher servers make calls to
CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_NONE, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);
Immediately after the call to CoInitialize() in _tWinMain(), this
ensures that the objects created by these servers can be accessed by anyone. However, it does not affect
who can launch the server. Since the MiddleManSvr server is run locally to the
EventControl object you should not have any other problems with security, but an
EventsMiddleMan object will try to create an Events object on another machine so
you must run DCOMCNFG on that remote machine and give launch access to EveryOne (or for the
specific accounts that you want to allow).
Testing the Objects
Compile the three projects, MiddleManSvr, EventWatcher and EventControl
and the proxy-stubs for all three. Copy EventWatcher.EXE, EventWatcherps.DLL
and EventControlps.DLL to the remo9te machine and register all three, then run
DCOMCNFG and select the Events Class and give launch permission to EveryOne.
Notice that by default this server is run under the identity of the launching client. This is not the
most efficient way to run the server in nterms of the security WinStations used, and indeed will not
work with connection points because NT4 does not allow the security context to be used to make outgoing
calls, so move to the Identity tab and change the Identity to a domain, or local account
on the server machine.
On the client machine you need to register MiddleManSvr and EventControl
and their proxy-stubs and EventWatcherps.DLL. Next you need to run RegEdit on
the client and create the following key:
HKEY_CURRENT_USER\Software\Wrox\Events
And then create a named value called Server; edit this value and type the name
of the remote host.
Now you are ready to test these objects. Run SetEvent.EXE on the remote server and
create the Test event as you did in Chapter 6 of Beginning ATL COM Programming.
You can now create the EventControl on the client machine (either in the test container or
in IE4) and when you change the event from Set to Reset and back you should see the corresponding change
in the control. The events that the Events object is generating are being transmitted over
the network to be caught by the EventsMiddleMan object which is then re-generating the events
and sending them to the EventControl object.
You'll find the code for the EventsMiddleMan object, and the updated
EventControl and EventWatcher objects
here.