How to marshal code to the original thread

F

Frank Rizzo

I have a class that handles events from another class. Unfortunately
these events arrive on a different thread. How can I marshal the code
execution to the thread on which the caller class was created.

'example

public class MyClass
{
private ClassWithEvents cls = new ClassWithEvents();
public MyClass()
{
cls.Hangup += new HangupEventHandler(cls_Hangup);
}


private void cls_Hangup(object sender, HangupEvent e)
{
// this happens on a different thread
// it needs to be marshalled to the original thread.
}
}
 
S

Stoitcho Goutsev \(100\)

Frank,

You can't unless the thread you want to marshal to is an UI thread. If it is
you can use Control.Invoke or Control.BeginInvoke for some of the controls
created in the thread.
 
N

Nicholas Paldino [.NET/C# MVP]

Stoitcho,

Well, that's not completely true. You could do it on a non UI thread,
but it would have to be a custom mechanism, and the thread would have to be
processing messages of some sort in a loop (like windows messages do).
 
F

Frank Rizzo

Nicholas said:
Stoitcho,

Well, that's not completely true. You could do it on a non UI thread,
but it would have to be a custom mechanism, and the thread would have to be
processing messages of some sort in a loop (like windows messages do).
I am open to that. Would you have some type of an example?

Regards
 
F

Frank Rizzo

Nicholas said:
Frank,

Is this for COM interop, by chance?

Sadly, yes. I am creating a VB6-friendly wrapper around work done by
Asterisk .NET
(http://www.gotdotnet.com/codegallery/codegallery.aspx?id=7948963b-7a3b-4303-9118-da800f4f84c3)
and Asterisk Java (http://asterisk-java.sourceforge.net/) fellas. So,
the events work great while in .NET code however, when I pass on an
event to VB6 code, it doesn't work so well, because the event was
originally fired on a different thread.

Regards
 
N

Nicholas Paldino [.NET/C# MVP]

Frank,

There is a better way to go about this.

What you want to do is marshal the interface pointer to the global
interface table. This will give you a cookie value (an unsigned int) which
you can store where you need to.

Then, in the event that is fired on another thread, you can get the
cookie, access the global interface table, and get a correctly marshaled
interface pointer to the VB object. Then, you can call your VB object
normally, and then release the interface from the table.
 
W

Willy Denoyette [MVP]

Not sure who's talking about calling a COM object, as far as I understand
it, the client is a VB6 application which calls into a .NET class through
COM interop and is called back from another thread as the original callers
thread.

Willy.

message | Frank,
|
| There is a better way to go about this.
|
| What you want to do is marshal the interface pointer to the global
| interface table. This will give you a cookie value (an unsigned int)
which
| you can store where you need to.
|
| Then, in the event that is fired on another thread, you can get the
| cookie, access the global interface table, and get a correctly marshaled
| interface pointer to the VB object. Then, you can call your VB object
| normally, and then release the interface from the table.
|
| --
| - Nicholas Paldino [.NET/C# MVP]
| - (e-mail address removed)
|
| | > Nicholas Paldino [.NET/C# MVP] wrote:
| >> Frank,
| >>
| >> Is this for COM interop, by chance?
| >
| > Sadly, yes. I am creating a VB6-friendly wrapper around work done by
| > Asterisk .NET
| >
(http://www.gotdotnet.com/codegallery/codegallery.aspx?id=7948963b-7a3b-4303-9118-da800f4f84c3)
| > and Asterisk Java (http://asterisk-java.sourceforge.net/) fellas. So,
the
| > events work great while in .NET code however, when I pass on an event to
| > VB6 code, it doesn't work so well, because the event was originally
fired
| > on a different thread.
| >
| > Regards
|
|
 
N

Nicholas Paldino [.NET/C# MVP]

My understanding is that there is a VB6 wrapper. It could be a COM
object (that is how I would do it) which makes the calls into the .NET
objects, passing itself (or another object) to be called when the event is
fired.
 
W

Willy Denoyette [MVP]

| Nicholas Paldino [.NET/C# MVP] wrote:
| > Frank,
| >
| > Is this for COM interop, by chance?
|
| Sadly, yes. I am creating a VB6-friendly wrapper around work done by
| Asterisk .NET
|
(http://www.gotdotnet.com/codegallery/codegallery.aspx?id=7948963b-7a3b-4303-9118-da800f4f84c3)
| and Asterisk Java (http://asterisk-java.sourceforge.net/) fellas. So,
| the events work great while in .NET code however, when I pass on an
| event to VB6 code, it doesn't work so well, because the event was
| originally fired on a different thread.
|
| Regards

Ok, before starting an endless discussion, we should get some questions
answered.
1. Is the client a VB6 application (exe)?
2. Is this VB6 application calling into Asterix .NET through COM interop?
3. Is your "VB6 friendly wrapper" the component exposed to the VB6 client?
4. Does asterix .NET communicates with the asterix server through
asynchronous sockets, that is, does it means that the messages received
arrive on a thread from the IOC pool and do these events get sinked from the
same thread?
5. Does asterix .NET communicates with the asterix server through
synchronous sockets, that is, does it means that the messages received
arrive on the same thread and do these events get sinked from this thread?

Willy.
 
F

Frank Rizzo

Nicholas said:
My understanding is that there is a VB6 wrapper. It could be a COM
object (that is how I would do it) which makes the calls into the .NET
objects, passing itself (or another object) to be called when the event is
fired.
No. It is exactly as Willy said in the parent post.
 
F

Frank Rizzo

Nicholas said:
My understanding is that there is a VB6 wrapper. It could be a COM
object (that is how I would do it) which makes the calls into the .NET
objects, passing itself (or another object) to be called when the event is
fired.
Basically here is the architecture. I have an existing VB6 client which
needs to access a .NET library that is too complicated for the Interop
to handle. Given that VB6 client only needs to access a limited subset
of the .NET library, I am writing a wrapper in .NET that will be
VB6-friendly (e.g. ComVisible, non-parametrised constructors, etc...).
The .NET library pretty much communicates by asynchronously shooting
down events. The idea of the wrapper is to catch these events and raise
them into the VB6 client. Unfortunately, the events are generated on a
different thread than the VB6 client. Thus when the events are fired,
VB6 app is totally confused and eventually comes back with a timeout
error. I am looking to somehow marshal the events coming from the .NET
library into .NET wrapper onto the original thread so that I can shoot
them down safely into the VB6 client.

Regards
 
F

Frank Rizzo

Willy said:
| Nicholas Paldino [.NET/C# MVP] wrote:
| > Frank,
| >
| > Is this for COM interop, by chance?
|
| Sadly, yes. I am creating a VB6-friendly wrapper around work done by
| Asterisk .NET
|
(http://www.gotdotnet.com/codegallery/codegallery.aspx?id=7948963b-7a3b-4303-9118-da800f4f84c3)
| and Asterisk Java (http://asterisk-java.sourceforge.net/) fellas. So,
| the events work great while in .NET code however, when I pass on an
| event to VB6 code, it doesn't work so well, because the event was
| originally fired on a different thread.
|
| Regards

Ok, before starting an endless discussion, we should get some questions
answered.
1. Is the client a VB6 application (exe)? yes
2. Is this VB6 application calling into Asterix .NET through COM interop?
Yes, it does so via the VB6 friendly wrapper (written in .NET) that in
turn calls teh actual Asterisk.NET library. The reason for writing a
wrapper around the Asterisk.NET library is because it's too complicated
for Interop and VB6 to tackle (e.g. parametrised constructors, etc...)
3. Is your "VB6 friendly wrapper" the component exposed to the VB6 client? Yes
4. Does asterix .NET communicates with the asterix server through
asynchronous sockets, that is, does it means that the messages received
arrive on a thread from the IOC pool and do these events get sinked from the
same thread?

It operates on asynchronous sockets, but these events are not sinked
anywhere. They are simply raised on whatever thread the code happens to
be on.
 
N

Nicholas Paldino [.NET/C# MVP]

Frank,

I would create an object in VB6 which you can pass to the .NET app.
This object would have methods which will fire the events to your main app
when called.

Then, I would make this instance available to your .NET code, using the
mechanism I stated before with the global interface table.
 
F

Frank Rizzo

Nicholas said:
Frank,

I would create an object in VB6 which you can pass to the .NET app.
This object would have methods which will fire the events to your main app
when called.

Then, I would make this instance available to your .NET code, using the
mechanism I stated before with the global interface table.

Do you have some kind of a sample for implementing this mechanism? I
don't fully understand it. When you speak of the global interface
table, are you talking about the COM construct?
 
N

Nicholas Paldino [.NET/C# MVP]

Frank,

It would require some interop, to say the least.

Basically, you have your VB object which has methods which corresponds
to the event being fired.

Then, you need to pass that to your .NET code.

In your .NET code, before you kick off the operation, you would register
the interface with the global interface table. In .NET, you can do this by
creating a COM object with the CLSID of CLSID_StdGlobalInterfaceTable (which
is defined in objidl.h, I believe).

You would also have to define the IGlobalInterfaceTable in .NET code.
You can then cast your object to this interface, and register your interface
on the GIT.

Then, in your event handler, you would take the cookie that is returned
from the call to RegisterInterfaceInGlobal and call the
GetInterfaceFromGlobal method, passing that cookie in. This will return an
interface pointer that you can then call the methods on, which will fire
your events. It should do this in the appropriate apartment.
 
W

Willy Denoyette [MVP]

| Nicholas Paldino [.NET/C# MVP] wrote:
| > Frank,
| >
| > I would create an object in VB6 which you can pass to the .NET app.
| > This object would have methods which will fire the events to your main
app
| > when called.
| >
| > Then, I would make this instance available to your .NET code, using
the
| > mechanism I stated before with the global interface table.
|
| Do you have some kind of a sample for implementing this mechanism? I
| don't fully understand it. When you speak of the global interface
| table, are you talking about the COM construct?

No need to do this at the managed side,the interface marshaling is done for
you by the CLR and this is not the issue either.
Point is that you want to sink an event on a COM connection point interface
(that's what I meant in my other reply ), note that sinking an event is the
same as calling an interface method (a VB6 method), the problem here is that
you call this method from a thread running in the MTA, no problem the
runtime knows the interface has an incompatible apartment and will
automatically marshal the call by creating a proxy/stub pair. So this should
work.
Now in another reply you said it doesn't work because VB is getting confused
(I didn't know that this was possible ;-)), and you are getting time-outs at
the VB6 side, and that's something that should get cleared up first, what
exactly do you mean with this?
Is it the call from VB6 into your friendly wrapper that is timed-out???
Do you have your "friendly wrapper code" available (I mean can you post it
here)?
Is the wrapper correctly registerd with it's typelib (regasm /tlb ...).

Willy.
 
F

Frank Rizzo

Willy said:
| Nicholas Paldino [.NET/C# MVP] wrote:
| > Frank,
| >
| > I would create an object in VB6 which you can pass to the .NET app.
| > This object would have methods which will fire the events to your main
app
| > when called.
| >
| > Then, I would make this instance available to your .NET code, using
the
| > mechanism I stated before with the global interface table.
|
| Do you have some kind of a sample for implementing this mechanism? I
| don't fully understand it. When you speak of the global interface
| table, are you talking about the COM construct?

No need to do this at the managed side,the interface marshaling is done for
you by the CLR and this is not the issue either.
Point is that you want to sink an event on a COM connection point interface
(that's what I meant in my other reply ), note that sinking an event is the
same as calling an interface method (a VB6 method), the problem here is that
you call this method from a thread running in the MTA, no problem the
runtime knows the interface has an incompatible apartment and will
automatically marshal the call by creating a proxy/stub pair. So this should
work.
Now in another reply you said it doesn't work because VB is getting confused
(I didn't know that this was possible ;-)), and you are getting time-outs at
the VB6 side, and that's something that should get cleared up first, what
exactly do you mean with this?
Is it the call from VB6 into your friendly wrapper that is timed-out???
Do you have your "friendly wrapper code" available (I mean can you post it
here)?

The entire thing is too long to repro here (and I don't have the code
right here), but I'll give you a quick sample:

namespace wrapper
{
//wrapper code
[ComVisible]
public class AsteriskWrapper
{
public delegate void DialEvent(string Status);
public event DialEvent Dial;

Asterisk.Net.Manager mgr;
public AsteriskWrapper {} //for VB6 benefit
public bool Initialize()
{
//instantiate the object
mgr = Asterisk.Net.Manager("MyServer", 5038);
//hook up an event
mgr.Dial += new DialEventHandler(mgr_Dial);
return true;
}

public bool MakeCall(string FromExt, string ToExt)
{
//this call will cause the mgr_Dial to fire
mgr.InitiateCall(FromExt, ToExt);
return true;
}

private void mgr_Dial(object sender, Event.DialEvent e)
{
//fire an event
if (Dial != null)
Dial(e.Status);
}
}
}


' here is the vb6 code
private withevents mobjAsterisk as MyWrapper.AsteriskWrapper
public sub btnDoSomething()
set mobjAsterisk = MyWrapper.AsteriskWrapper

o.Initialize
o.MakeCall"201, "202"
end sub

private sub mobjAsterisk_Dial(Status as string)
'it never gets here
end sub



Is the wrapper correctly registerd with it's typelib (regasm /tlb ...).

Yes, per this article:
http://www.vbrad.com/pf.asp?p=source/src_real_interop.htm
 
S

Stoitcho Goutsev \(100\)

Yes, custom solution are always possible. At the very end the message pump
has a pretty simple concept. The think is that there is no solution out of
the box.


--

Stoitcho Goutsev (100)

Nicholas Paldino said:
Stoitcho,

Well, that's not completely true. You could do it on a non UI thread,
but it would have to be a custom mechanism, and the thread would have to
be processing messages of some sort in a loop (like windows messages do).
 
W

Willy Denoyette [MVP]

Frank,

I didn't spend time on the code you posted, I just included here a complete
(non-sense) sample (CS and a VB6 code) to illustrate how I think you could
implement your wrapper and to prove you can fire events from abitrary CLR
threads to VB6 COM clients using connectionpoint interfaces.

Just compile the code and register with COM, do this from the command line
using
csc /t:library cowboy.cs
regasm /tlb /codebase cowboy.dll
when done create a new VB6 project and add a reference to the typelib
produced by regasm

Note that if you change the filename you will have to change the VB6 type
accordingly! (Cowboy.Cowboy)

The VB6 client creates an instance of Cowboy and calls a method Shoot with
as argument an int value (shots).
To simulate your Asterix callback arriving on a different thread, the Shoot
method creates a new thread and initializes it to enter the MTA (or STA
doesn't matter) and calls it's procedure (starts the thread).
The threads procedure checks the value of 'shots', if it's > 2 then an event
is fired with an arg. value 1, if 'shots' is < 2 an event is fired with a
value 0.
Note that I could have used some asynchronous socket code instead of this
silly sample, but this is not really relevant.

// cowboy.cs
// pay attention to the class decoration, this is important for
connectionpoint interface support.
using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
namespace Willys
{
[Guid(Cowboy.interfaceId)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface ICowboy
{
int Shoot(int number);
}
[Guid(Cowboy.eventsId)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ICowboyEvents
{
[DispId(1)] void Touched(int level);
}
// Delegate for the event
public delegate void TouchedEventHandler(int level);

[Guid(Cowboy.classId)]
[ComSourceInterfaces(typeof(ICowboyEvents))]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("Eventing.Cowboy")]
[ComVisible(true)]
public class Cowboy : ICowboy
{
// UUID's of class, interface and event
const string classId = "1a6cdc6e-9123-494c-92c1-d1fad8f48f27";
public const string interfaceId =
"59f20cb8-d7fa-41c1-83a2-50fba18f1f7d";
public const string eventsId = "74a49bed-5475-415e-8d9e-b889271040cc";

public event TouchedEventHandler Touched;
public Cowboy()
{}
public int Shoot(int shots)
{
// do some stuff ....
Thread t1 = new Thread(new ParameterizedThreadStart(DoWork));
t1.SetApartmentState(ApartmentState.MTA);
t1.Start(shots);
t1.Join();
return shots;
}
void DoWork(object o)
{
Touched += new TouchedEventHandler(OnTouched);
int n = (int)o;
if (Touched != null)
{
if (n <= 2)
Touched(0);
else
Touched(1);
Touched -= new TouchedEventHandler(OnTouched);
}
}
// Implemented by the COM client
void OnTouched(int level)
{}
}
}


[VB6]
'create a form with a single button
Private WithEvents myCowboy As Cowboy.Cowboy
Private Sub Command1_Click()
Set myCowboy = New Cowboy.Cowboy
Dim n As Long
n = myCowboy.Shoot(31)
End Sub

Private Sub myCowboy_Touched(ByVal level As Long)
MsgBox (level)
End Sub

Hope it's of any help.

Willy.



| Willy Denoyette [MVP] wrote:
| > | > | Nicholas Paldino [.NET/C# MVP] wrote:
| > | > Frank,
| > | >
| > | > I would create an object in VB6 which you can pass to the .NET
app.
| > | > This object would have methods which will fire the events to your
main
| > app
| > | > when called.
| > | >
| > | > Then, I would make this instance available to your .NET code,
using
| > the
| > | > mechanism I stated before with the global interface table.
| > |
| > | Do you have some kind of a sample for implementing this mechanism? I
| > | don't fully understand it. When you speak of the global interface
| > | table, are you talking about the COM construct?
| >
| > No need to do this at the managed side,the interface marshaling is done
for
| > you by the CLR and this is not the issue either.
| > Point is that you want to sink an event on a COM connection point
interface
| > (that's what I meant in my other reply ), note that sinking an event is
the
| > same as calling an interface method (a VB6 method), the problem here is
that
| > you call this method from a thread running in the MTA, no problem the
| > runtime knows the interface has an incompatible apartment and will
| > automatically marshal the call by creating a proxy/stub pair. So this
should
| > work.
| > Now in another reply you said it doesn't work because VB is getting
confused
| > (I didn't know that this was possible ;-)), and you are getting
time-outs at
| > the VB6 side, and that's something that should get cleared up first,
what
| > exactly do you mean with this?
| > Is it the call from VB6 into your friendly wrapper that is timed-out???
| > Do you have your "friendly wrapper code" available (I mean can you post
it
| > here)?
|
| The entire thing is too long to repro here (and I don't have the code
| right here), but I'll give you a quick sample:
|
| namespace wrapper
| {
| //wrapper code
| [ComVisible]
| public class AsteriskWrapper
| {
| public delegate void DialEvent(string Status);
| public event DialEvent Dial;
|
| Asterisk.Net.Manager mgr;
| public AsteriskWrapper {} //for VB6 benefit
| public bool Initialize()
| {
| //instantiate the object
| mgr = Asterisk.Net.Manager("MyServer", 5038);
| //hook up an event
| mgr.Dial += new DialEventHandler(mgr_Dial);
| return true;
| }
|
| public bool MakeCall(string FromExt, string ToExt)
| {
| //this call will cause the mgr_Dial to fire
| mgr.InitiateCall(FromExt, ToExt);
| return true;
| }
|
| private void mgr_Dial(object sender, Event.DialEvent e)
| {
| //fire an event
| if (Dial != null)
| Dial(e.Status);
| }
| }
| }
|
|
| ' here is the vb6 code
| private withevents mobjAsterisk as MyWrapper.AsteriskWrapper
| public sub btnDoSomething()
| set mobjAsterisk = MyWrapper.AsteriskWrapper
|
| o.Initialize
| o.MakeCall"201, "202"
| end sub
|
| private sub mobjAsterisk_Dial(Status as string)
| 'it never gets here
| end sub
|
|
|
|
| > Is the wrapper correctly registerd with it's typelib (regasm /tlb ...).
|
| Yes, per this article:
| http://www.vbrad.com/pf.asp?p=source/src_real_interop.htm
|
|
| >
| > Willy.
| >
| >
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top