Volleynerd Knowledge Base

Friday, September 19, 2003


VS 7.x and COM object Events

Weird stuff - new ActiveX Controls that we're building aren't hooking up events correctly with IE. Works with VB, but not with IE/script.

200839 - HOWTO: Enable ActiveX Control Event Handling on a Web Page
(see below heading for info about dealing with EXE COM server)

So turns out it's a change in the way the wizard generates code for the ATL objects. The above article is not quite right when they say the IE browser QI's for either IProvideClassInfo2 or IProvideClassInfo. In fact, when testing this solution, I can only get it working with IProvideClassInfo.

So...to make ATL based objects with connection points get hooked up correctly to script based clients (IE in particular):

Derive from IProvideClassInfo2Impl like this:
public IProvideClassInfo2Impl<&CLSID_dantestobj, &DIID__IdantestobjEvents, &LIBID_dantestLib>


Then add the following line to the interface map:
COM_INTERFACE_ENTRY(IProvideClassInfo)


Receive Events in IE from COM EXE server


Mark found this google info here
Google Search: Event from out of process COM server

(note: see info from 3rd post down that gets chopped off due to length. full text here):
http://groups.google.com/groups?q=Event+from+out+of+process+COM+server&hl=en&lr=&ie=UTF-8&oe=UTF-8&selm=%23uW82o5z%23GA.258%40cppssbbsa05&rnum=3

(copied below in case can't find it)
I have had the same problem. Basically IE5 does not sink events from out of

process servers. I have some help on this with detailed explanations of
what's going wrong but unfortunately have not managed to get the code
example yet.

Here is the email that someone at Microsoft sent in a support case (not to
me). At present I am trying to get the code but I object to paying Microsoft
for a support incident to fix a bug that they have already fixed for someone
else. Why haven't they published the problem and solution somewhere?

If you get anywhere let me know and I'll do likewise

Pete Edwards

---------------------- Microsoft
info ----------------------------------------------------------------------
It is a known issue with IE4 that ActiveX objects that live in a local
server cannot be instantiated using the OBJECT tag. This is due to the fact
that in IE4, the scripting engine essentially calls CoGetClassObject with
only the INPROC context flags (CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER
) when it sees an OBJECT tag ( note: This behavior has changed for IE5,
which now adds the CLSCTX_LOCAL_SERVER flag to the list ). In order to
overcome this limitation I tried a very simple fix. Since IE is looking for
an inproc server, I decided to give it one. I registered a DLL as the
inproc server for my object ( Let's call it CLSID_ObjInLocalServer ). The
DLL's implementation of DllGetClassObject looks like this:

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
if( InlineIsEqualGUID(CLSID_ObjInLocalServer) )
{
return CoGetClassObject(rclsid, CLSCTX_LOCAL_SERVER, NULL,
riid, ppv);
} return CLASS_E_CLASSNOTAVAILABLE;
}


When IE calls CoGetClassObject( CLSCTX_INPROC_SERVER ... ), COM looks in the
registry and sees that for CLSID_ObjInLocalServer my DLL is registered as
the in-proc server. The DLL is loaded and DllGetClassObject is called. The
DLL then goes and gets the class object from the local server, which is
where the object really lives. Because COM is super-smart, everything works
like magic. New objects that are created using the
ClassFactory::CreateInstance have their interfaces marshalled by the
registered stardard marshallers that COM provides (many of which reside in
oleaut32.dll ). As long as you don't need to sink events from this object
in IE, the above works just fine. However, if you do want events, there is
a problem. It has to do with the registered marshaller for the ITypeInfo
interface ( this code is in oleaut32.dll ). The problem is that the proxy's
implementation for ITypeInfo::GetIDsOfNames simply returns E_NOTIMPL based
on the philosophy that TypeInfo IDs should be local... IE takes that
E_NOTIMPL and decides not to procede with setting up the event sink. Here is
how IE sets up scripting events:
1.) Queries ObjInLocalServer for IProvideClassInfo
2.) Gets the Class info (an ITypeInfo*) from
IProvideClassInfo::GetClassInfo( )
3.) Calls a bunch of methods on pCI to find the GUID for the default event
source
4.) Queries ObjInLocalServer for IConnectionPointContainer, and asks for the
default event source

The problem is that when the object and IE live in separate processes ( as
they always do in this situation ), the ITypeInfo* returned by the
GetClassInfo method must be marshalled.

The object that implements the ITypeInfo in this case is one returned by a
call to LoadRegTypeLib. This returns a pointer to an object created in
oleaut32.dll. Then COM goes through it's marshalling steps. In this case,
the proxy implementation of the object has a problem in that it just returns
E_NOTIMPL for GetIDsOfNames.... So what to do?

Well, clearly the problem is that we don't own the object given to us by
LoadRegTypeLib... but we do know the parameters that we pass to it...

So I created another object called CMarshalableTI. This object exposes
IMarshal, ITypeInfo, and a custom interface called IMarshalableTI. The
custom interface is used only for initialization and exposes a single
method:

interface IMarshalableTI : IUnknown
{
[helpstring("method Create")]
HRESULT Create(
[in] REFIID clsid,
[in] REFIID iidLib,
[in] LCID lcid,
[in] WORD
dwMajorVer,
[in] WORD dwMinorVer
);
};


This object lives in a new DLL called mslablti ( stands for Marshalable Type
Info )...

In ObjLocalServer, I change my IProvideClassInfo::GetClassInfo to look like
this:


STDMETHOD(GetClassInfo)(ITypeInfo** pptinfo)
{
CComPtr spmti;
HRESULT hr = CoCreateInstance(CLSID_MarshalableTI, NULL,
CLSCTX_INPROC, IID_IMarshalableTI, reinterpret_cast(&spmti));
if( SUCCEEDED( hr ) )
{
if( SUCCEEDED( hr =
spmti->Create(CLSID_ObjInLocalServer, LIBID_LOCALAUTOMATIONLib,
LANG_NEUTRAL, 1, 0) ) )
{
hr = spmti->QueryInterface(IID_ITypeInfo,
reinterpret_cast(pptinfo));
}
}

return hr;
}


Now the object that exposes ITypeInfo is mine, and if it is mine, then I can
control it.

So COM takes the ITypeInfo and tries to marshall it. It queries the object
for IMarshal, which I have provided. Really only 4 of the IMarshal methods
are of any interest here. Before I get to them, here is the gist of what I
do. Since the TypeInfo is really just some static gunk that we get from the
OS, the object providing ITypeInfo* really doesn't have any state. So we
can have a local version of the TypeInfo for IE, and another copy of it in
our local server. When COM asks me to marshal the ITypeInfo interface, I
simply stick the LIBID, the LocaleID, the object CLSID and the typelib
version information in a stream, and re-query for the TypeInfo using
LoadRegTypeLib on the other side...

GetMarshalSizeMax- In this function, I tell COM how much space I need to
store the guids and the version information
GetUnmarshalClass- In this function, I give COM a CLSID of a registered
component that knows how to UnMarshal this interface
MarshalInterface- Here I simply stick the parameters to LoadRegTypeLib into
a stream
UnmarshalInterface- I read the parameters from the given stream and call
LoadRegTypeLib to get an ITypeInfo*.

Now I have an ITypeInfo* on the local side that will provide the same
information as the other one that lives in the local server. IE gets the
information that it wants, and my events are sinked correctly!

The nice thing is that the solution is general, and can be used anywhere
that the OS ITypeInfo marshalling falls short. In particular this is useful
for ActiveX objects that live in local servers that try to send event
notifications to IE.


---------------------- End of Microsoft
info ---------------------------------------------------------------



Comments: Post a Comment

Home