|
|
|
date: Fri, 25 Jan 2008 12:17:05 -0800 (PST),
group: microsoft.public.dotnet.framework.remoting
back
Events stop working when interface assembly is signed (strong-named).
Our csharp Windows forms application uses .Net remoting as a way for
third party applications to integrate with us. This API includes
methods, properties and events that client applications can do to
share context with us. We've also provided a COM wrapper so that non-
dotnet apps can use it too.
Problem: When we recently attempted to sign the shared assembly and
access it from the GAC, certain events stopped working. Events that
have parameters derived from EventArgs, and are defined in the shared
interface assembly, were broken. Other events that simply passed
EventArgs still worked. Here is a derived event argument:
[Serializable]
public class LoginEventArgs : EventArgs
{
public string UserName;
public LoginEventArgs(string userName)
{
UserName = userName;
}
}
Our application (server) would fire the events, but they would never
get to the client, and there were no exceptions or errors or anything
to indicate a problem.
I created a simplified test program that duplicates the problem
without even having to include methods and properties, just two
events. One event uses an EventArgs parameter, and the other uses the
derived LoginEventArgs parameter. The one that uses the EventArgs
parameter works. Here is how the test solution it is layed out
(similar to all the remoting samples you have probably seen):
RemoteTest - solution
Interface - C# class libary containg the shared remoting interface
Server - Windows form app that fires the events
Client - Windows forms app that receives fired events
The Server app has a simple form containing a button that will fire
the UserLoggedIn event. When closing the form, it will fire the
Terminated event. NOTE: the "event wrapper" and ISynchronize.Invoke
method is used to get the events to the client in a thread-safe
manner.
The Client app has a simple form containing a "Connect" button, which
simply connects to Server's remoting port.
I broke down the problem one step further: I signed the shared
assembly, but did NOT install it in the GAC. I simply let it be copied
to each applications local directory. IT STILL DID NOT WORK. I then
unsigned the assembly, and sure enough, all events worked.
So, it would seem that signing an assembly somehow breaks the
reference to the shared interface for the Server and/or Client
application(s). Has anyone seen this behavior? Is there some kind of
security thing going on here that I don't know about? Any chance that
this is a problem with .Net serialization? Any advice is
appreciated...
The source code starts here...
-----------------------------------------------------
// The shared "Interface" assembly
using System;
using System.Runtime.Remoting.Messaging;
using System.ComponentModel;
namespace RemoteTest.Interface
{
//EventArg derivative for Login
[Serializable]
public class LoginEventArgs : EventArgs
{
public string UserName;
public LoginEventArgs(string userName)
{
UserName = userName;
}
}
// event delegates
public delegate void UserLoggedInHandler(object sender,
LoginEventArgs e);
public delegate void TerminatedHandler(object sender, EventArgs e);
// event wrappers needed for remoting
public class UserLoggedInEventWrapper : MarshalByRefObject
{
public event UserLoggedInHandler UserLoggedInArrivedLocally;
[OneWay]
public void LocallyHandleUserLoggedIn(object sender,
LoginEventArgs e)
{
foreach (UserLoggedInHandler singleCast in
UserLoggedInArrivedLocally.GetInvocationList())
{
ISynchronizeInvoke syncInvoke = singleCast.Target as
ISynchronizeInvoke;
try
{
if ((null != syncInvoke) &&
(syncInvoke.InvokeRequired))
syncInvoke.Invoke(singleCast, new object[] { sender,
e });
else
singleCast(sender, e);
}
catch { }
}
}
public override object InitializeLifetimeService()
{
return null;
}
}
[Serializable]
public class TerminatedEventWrapper : MarshalByRefObject
{
public event TerminatedHandler TerminatedArrivedLocally;
[OneWay]
public void LocallyHandleTerminated(object sender, EventArgs e)
{
// forward the message to the client
foreach (TerminatedHandler singleCast in
TerminatedArrivedLocally.GetInvocationList())
{
ISynchronizeInvoke syncInvoke = singleCast.Target as
ISynchronizeInvoke;
try
{
if ((null != syncInvoke) &&
(syncInvoke.InvokeRequired))
syncInvoke.Invoke(singleCast, new object[] { sender,
e });
else
singleCast(sender, e);
}
catch { }
}
}
public override object InitializeLifetimeService()
{
return null;
}
}
public interface IRemoteTest
{
event UserLoggedInHandler UserLoggedIn;
event TerminatedHandler Terminated;
}
}
-----------------------------------------------------
// The Server, which fires the events
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Text;
using System.Windows.Forms;
using RemoteTest.Interface;
namespace RemoteTest.Server
{
public partial class Form1 : Form, IRemoteTest
{
public event UserLoggedInHandler UserLoggedIn;
public event TerminatedHandler Terminated;
TcpChannel _tcpChannel = null;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
this.FormClosed += new
FormClosedEventHandler(Form1_FormClosed);
//initialilze Remoting for Slave
BinaryServerFormatterSinkProvider serverProv = new
BinaryServerFormatterSinkProvider();
serverProv.TypeFilterLevel =
System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
IDictionary props = new Hashtable();
props["port"] = 9090;
_tcpChannel = new TcpChannel(props, new
BinaryClientFormatterSinkProvider(), serverProv);
ChannelServices.RegisterChannel(_tcpChannel, true);
RemotingServices.Marshal(this, "RemoteTest.Interface",
typeof(IRemoteTest));
}
void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
if (null != Terminated)
Terminated("RemoteTest_Server", new EventArgs());
}
private void btnLogin_Click(object sender, EventArgs e)
{
if (null != UserLoggedIn)
UserLoggedIn("RemoteTest_Server", new
LoginEventArgs("george"));
}
}
}
------------------------------------------------------
// Client app - receives events
using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Text;
using System.Windows.Forms;
using RemoteTest.Interface;
namespace RemoteTest.Client
{
public partial class Form1 : Form
{
TcpChannel _channel = null;
IRemoteTest _remoteTest = null;
public Form1()
{
InitializeComponent();
}
private void btnConnect_Click(object sender, EventArgs e)
{
IDictionary props = new ListDictionary();
props["port"] = 0; // have the Remoting system pick a unique
listening port (required for callbacks)
props["name"] = String.Empty; // have the Remoting system
pick a unique name
_channel = new TcpChannel(props, null, null);
ChannelServices.RegisterChannel(_channel, true);
_remoteTest =
(IRemoteTest)Activator.GetObject(typeof(IRemoteTest), "tcp://localhost:
9090/RemoteTest.Interface");
//set up event handlers
UserLoggedInEventWrapper userLoggedIn_w = new
UserLoggedInEventWrapper();
userLoggedIn_w.UserLoggedInArrivedLocally += new
UserLoggedInHandler(UserLoggedIn);
_remoteTest.UserLoggedIn += new
UserLoggedInHandler(userLoggedIn_w.LocallyHandleUserLoggedIn);
TerminatedEventWrapper terminated_w = new
TerminatedEventWrapper();
terminated_w.TerminatedArrivedLocally += new
TerminatedHandler(Terminated);
_remoteTest.Terminated += new
TerminatedHandler(terminated_w.LocallyHandleTerminated);
}
//IRemoteTest Events
public void UserLoggedIn(object sender, LoginEventArgs e)
{
MessageBox.Show("UserLoggedIn: " + e.UserName);
}
public void Terminated(object sender, EventArgs e)
{
MessageBox.Show("Terminated");
}
}
}
date: Fri, 25 Jan 2008 12:17:05 -0800 (PST)
author: GeorgeK
|
|