Hi folks,
I'm having an interop problem where my managed component is reentered from
COM on the wrong thread, at which point things seem to go downhill very
rapidly. I'm successfully making very heavy use of COM interop elsewhere in
my C# code, but I'm having trouble finding a way around this particular
problem. This is VS .Net 2003.
The scenario involves calling an apartment-threaded COM object from managed
code, passing it a pointer to a specific COM interface to be used for
callback notifications. The COM object creates a worker thread and marshals
the callback notification interface pointer into it (via
CoMarshalInterThreadInterfaceInStream/CoGetInterfaceAndReleaseStream). The
worker thread calls through the marshaled interface pointer periodically, to
perform various notifications.
In rough pseudo-idl:
[ obect, local, oleautomation,uuid(...) ]
interface ILoaderNotify : IUnknown {
HRESULT __stdcall ReportPercentComplete(...);
}
[ object, local, oleautomation,uuid(...) ]
interface ILoader : IUnknown {
HRESULT __stdcall StartLoad(...,ILoaderNotify *Notify);
}
[ uuid(...) ]
coclass Loader {
[default] interface ILoader;
}
All of this works fine in a VB6 environment. Even though the worker thread
is in the MTA, it has the marshaled pointers to the callback interface, so
calling through it means that the original caller correctly gets the
notifications in its STA.
Implements ILoaderNotify
private sub DoLoad(...)
dim obj as Loader
set obj = new Loader
obj.StartLoad ...,Me
end sub
private sub ILoaderNotify_ReportPercentComplete(...)
' Gets called on the same thread as DoLoad()
end sub
Unforutnately in C#, the callback is getting called directly by the worker
thread.
internal class Loader : ILoaderNotify {
private void DoLoad() {
Loader obj = new Loader();
obj.StartLoad(...,this);
}
void ILoaderNotify.ReportPercentComplete(...) {
//
// This guy is getting called on a different thread than
DoLoad().
// The ApartmentState property indicates MTA.
//
}
}
I am wondering what I am doing wrong -- I'm sure there is something I am
missing but I'm having a hell of a time figuring out what. Changing the
worker thread to be in an STA doesn't seem to change anything. Interestingly
(?) Marshal.IsComObject(this) returns false. The docs say that this is
supposed to return true for any class directly or indirectly derived from
COM.
Does anyone have any ideas, or can anyone tell me what I am doing wrong?
Thanks very much!
-- Ted
Christian Fröschlin - 12 Sep 2003 08:31 GMT
> Does anyone have any ideas
Maybe you can live with being called on the wrong thread, if you
adapt the code in the ReportPercentComplete method. I assume you
use this to update some GUI element, so you might wish to use
Control.BeginInvoke or similiar.
Ted Miller - 12 Sep 2003 09:08 GMT
I truly wish that were the case, but what I posted was vastly simplified --
in reality, that callback routine in turn calls off into additional COM
objects and performs other complex stuff. I start getting exception 80020008
and other random behavior where it makes no sense, just like what happens
when you violate COM's threading rules. Sigh.
Thanks for the post, though!
> > Does anyone have any ideas
>
> Maybe you can live with being called on the wrong thread, if you
> adapt the code in the ReportPercentComplete method. I assume you
> use this to update some GUI element, so you might wish to use
> Control.BeginInvoke or similiar.
Christian Fröschlin - 12 Sep 2003 09:57 GMT
> I truly wish that were the case, but what I posted was vastly simplified --
> in reality, that callback routine in turn calls off into additional COM
> objects and performs other complex stuff.
I am no expert on multi-threading, but I suppose there must be a way
to dispatch a call to be made on a specific thread. In this case, you
could allow the callback to occur on the wrong thread, and simply put
code in it to call the real method on the correct thread. It should
be possible since Control.Invoke does it, but I don't know how this
is implemented. Maybe you can use SendMessage API or similiar.
As a silly workaround, you could try to make your function
member of a dummy control, and use Control.Invoke anyway ;)
Ted Miller - 12 Sep 2003 19:55 GMT
Following up on your suggestion, I created a dummy control and tried to use
its Invoke method from within the callback.
Something must really be confusing the clr because the method in the dummy
control never winds up getting called. Also, from within the callback
routine, the InvokeRequired property of the dummy control is still set to
false -- even though the control was created in a different thread. As Kelly
Bundy once said, Something is rotten in the state of Denver!
Thanks again,
Ted
> > I truly wish that were the case, but what I posted was vastly simplified --
> > in reality, that callback routine in turn calls off into additional COM
[quoted text clipped - 9 lines]
> As a silly workaround, you could try to make your function
> member of a dummy control, and use Control.Invoke anyway ;)