相关文章推荐
腼腆的小刀  ·  处理URL中的“robots.txt ...·  6 月前    · 
有爱心的爆米花  ·  将"\r\n "替换为"\n"·  1 年前    · 
稳重的打火机  ·  poi ...·  1 年前    · 

This article is inspired by Nishant S 's article entitled " Absolute beginner's introduction to remoting " :- ( http://www.codeproject.com/csharp/absoluteremoting.asp ) Nishant's article presents a very simple example use case of .NET Remoting. Having thoroughly enjoyed Nishant's article, I decided to provide to all readers a -slightly- more advanced example. Note that, like Nishant, my target audience remains the "beginners" level developers.

My example program is entitled "ProcessActivator". I provide a server program which exposes an object named " ProcessActivator ". This server object is an implementation of an interface named " IProcessActivator ". This implementation defines a single method named "Run" which, when invoked by a client, will cause the server to start up a process (on the machine in which the server is running). Without further ado, let's start examining our example code...

The Interface

Like Nishant, I have defined the interface " IProcessActivator " inside a C# DLL project. This DLL is what I like to refer to as an "empty interface dll", i.e. it only defines and exposes interface(s) but does not provide any implementation code. Later we will derive our C# remote server class from this same interface. This DLL file will also be copied to the client machine in order for the client to reference it in order to compile successfully.

Let's have a look at the " IProcessActivator " interface : public interface IProcessActivator bool Run( string strProgramName, string strArgumentString);

This interface defines a method named "Run". The specification for Run() is that it should cause the object (which implements IProcessActivator ) to startup a process (identified by the " strProgramName " parameter with arguments specified by " strArgumentString

You can find the source codes to this "empty interface dll" in the " IProcessActivator " folder (contained in the source codes zip file accompanying this article). There is a C# solution file (IProcessActivator.sln) located in this folder. The project settings must be such that this project file will be compiled into a DLL (albeit without any implementation). Once this DLL has been successfully compiled, our client and server programs can reference this DLL for compilation purposes.

The Remote Server Object

The remote server class that we create will be derived from " IProcessActivator " of course. This is so that our server class will implement this interface. Our remote server class must also be derived from the MarshalByRefObject class.

MarshalByRefObject is the base class for objects that can be created by a remote client and then transported across application domain boundaries to the remote client. Communications between the remote client and the MarshalByRefObject is achieved by exchanging messages using a proxy. It is the proxy that makes the object "marshalled by reference". By being "marshalled by reference", the remote server object becomes "stateful" to the client application.

The first time a (remote) client application accesses the MarshalByRefObject , the proxy is passed to the remote client application. The client uses this proxy to make method calls on the remote object. These method calls and their parameters are actually marshalled across application domain boundaries to the remote server object itself. Much of it is reminiscent of COM's way of invoking remote server function calls.

Let's look at our server code :

using System; using System.Diagnostics; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Tcp; using IProcessActivator; namespace ProcessActivator /// < summary > /// Summary description for ProcessActivator. /// < /summary > public class ProcessActivator : MarshalByRefObject, IProcessActivator.IProcessActivator public ProcessActivator() // TODO: Add constructor logic here public bool Run( string strProgramName, string strArgumentString) Process.Start(strProgramName, strArgumentString); return false ; public static void Main() TcpServerChannel channel = new TcpServerChannel( 9000 ); ChannelServices.RegisterChannel(channel); WellKnownServiceTypeEntry remObj = new WellKnownServiceTypeEntry typeof (ProcessActivator), " ProcessActivator" , WellKnownObjectMode.SingleCall RemotingConfiguration.RegisterWellKnownServiceType(remObj); Console.WriteLine( " Press [ENTER] to exit." ); Console.ReadLine();

The ProcessActivator class' implementation of the Run() method uses the "Process" class which provides access to local and remote processes and enables an application to start and stop local system processes.

The Process class is very useful for starting, stopping, controlling, and monitoring applications. Using the Process class, an application can also obtain a list of the processes (including system processes) that are currently running. Various information, including threads, loaded modules (.dll and .exe files), and performance data (e.g. memory usage) are also available.

This opens up a lot of potential enhancements for the IProcessActivator interface. We can enhance IProcessActivator to include methods (e.g. GetProcesses() ) that return an array of information on processes currently running on a machine.

The reader is encouraged to experiment with the attached source codes to achieve this.

The Main() function implements typical code for registering Remoting Objects. The typical code uses TcpServerChannel , ChannelServices , WellKnownServiceTypeEntry and RemotingConfiguration .

I must admit that when I first studied this code (which I copied from one of the many example codes available in MSDN), I was most puzzled by the lack of obvious connection between the call to ChannelServices.RegisterChannel() and the call to RemotingConfiguration.RegisterWellKnownServiceType() .

This was so until I further studied the ChannelServices documentation. My findings are analysed in the next section.

An Analysis Of Channel Services And RemotingConfiguration

Note the documentation for the ChannelServices class :

"(ChannelServices) Provides static methods to aid with remoting channel registration, resolution, and URL discovery."

And the documentation for the ChannelServices.RegisterChannel() method states :

"You cannot register two channels with the same name in an AppDomain. By default, the name of an HttpChannel is "http", and the name of a TcpChannel is "tcp". Therefore, if you want to register two channels of the same type, you must specify a different name for one of them through configuration properties."

Here is my take for the connection between ChannelServices and

RemotingConfiguration:

ChannelServices are intrinsically used in a server application for exposing Remoting Objects to clients.

The WellKnownServiceTypeEntry and the RemotingConfiguration classes are used to generically register an object TYPE on the server and to expose this type to all clients. The registration is done by the call to

RemotingConfiguration.RegisterWellKnownServiceType() function.

In order for clients to connect with this type of object, it must know the URL of the object. This URL will include the URI of the object. This is where the second parameter to the WellKnownServiceTypeEntry constructor comes in. It specifies the URI of the object.

In our example code, this second parameter is "ProcessActivator" and clients must know this piece of information. This is why it is "well-known" .

Note also that this well-known service type registration is possible on condition that the server application first create a channel from which clients can connect to. This is done by the call to ChannelServices.RegisterChannel() method.

Although there is no direct connection between ChannelServices and RemotingConfiguration, they are, nevertheless connected (albeit internally) by the .NET framework.

That the connection between ChannelServices and RemotingConfiguration is totally internal can be appreciated by the fact that a server can define and register as many types of channels as it wishes and these need not be of any concern when we call RemotingConfiguration.RegisterWellKnownServiceType().

As an example, we can code our Main() function as follows :

TcpServerChannel channel = new TcpServerChannel( 9000 ); HttpServerChannel http_channel = new HttpServerChannel( 8000 ); ChannelServices.RegisterChannel(channel); ChannelServices.RegisterChannel(http_channel); WellKnownServiceTypeEntry remObj = new WellKnownServiceTypeEntry typeof (ProcessActivator), " ProcessActivator" , WellKnownObjectMode.SingleCall RemotingConfiguration.RegisterWellKnownServiceType(remObj); Console.WriteLine( " Press [ENTER] to exit." ); Console.ReadLine();

The above code demonstrates the registering of a TcpServerChannel as well as a HttpServerChannel. With this enhancement, a client application can reach the Remoting object either via TCP/IP (using port number 9000) or via HTTP (using port number 8000). Either way, our WellKnownServiceTypeEntry object remains passive and need not be created nor registered with any indication of the specific channel type.

When we explore our client code next, we will present an example where the client makes good use of the multiply registered channels of the server and connects with the it via HTTP.

The Client Code

Let's now examine our client code :

using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Tcp; using IProcessActivator; namespace ProcessActivatorClient class ProcessActivatorClient /// < summary > /// The main entry point for the application. /// < /summary > [STAThread] static void Main(string[] args) // TODO: Add code to start application here TcpClientChannel channel = new TcpClientChannel(); ChannelServices.RegisterChannel(channel); IProcessActivator.IProcessActivator process_activator = (IProcessActivator.IProcessActivator)Activator.GetObject typeof (IProcessActivator.IProcessActivator), " tcp://localhost:9000/ProcessActivator" process_activator.Run( " IExplore.exe" , @" http://www.codeproject.com" );

Our client code also uses "typical" code to connect to the remote object. This typical code involves the use of the TcpClientChannel ,

ChannelServicesand the Activator classes.

Once again, the connection between ChannelServices and Activator seem to be implicit and handled internally by the .NET framework.

The recurring pattern seem to be that a client channel (used by the client to connect with the remote object server) must be registered by the

ChannelServicesclass before Activator can succeed.

Recall that our server code can register more than one type of channel. If we had registered both a TCP/IP channel as well as a HTTP channel, our client code can be done this way :

HttpClientChannel http_channel = new HttpClientChannel(); ChannelServices.RegisterChannel(http_channel); IProcessActivator.IProcessActivator process_activator = (IProcessActivator.IProcessActivator)Activator.GetObject typeof (IProcessActivator.IProcessActivator), " http://localhost:8000/ProcessActivator" process_activator.Run( " IExplore.exe" , @http: // www.codeproject.com);

In the course of my learning experience with .NET Remoting, I noted two important points about the Activator.GetObject() method. Both are significant findings. The first is a little odd (could signal a bug in .NET) but is not disastrous in my opinion. The second indicates similarities between .NET Remoting, COM and the Object-Oriented features of C++ in general which gives me a surge of confidence that the COM and C++ stuff I learnt will be re-usable in .NET.

The First Parameter To Activator.GetObject()

In this section, I'll discuss my first observation about the GetObject() method. The first parameter to GetObject() is documented in MSDN as :

"The type of the well-known object to which you want to connect."

In my client code, I used :

typeof(IProcessActivator.IProcessActivator)

When I first experimented with the client code, I had hoped that I can supply another interface type (provided of course that the Remoting Object implements that interface). To cut a long story short, I found that, in general, this parameter seem to make no difference whatsoever no matter what I supplied (for the first parameter) as the type I want from the Remoting Object. This seem to be the case as long as the supplied type is found in the Remoting server.

What does make a difference is the URI of the Remoting Object that I supply as part of the URL which is supplied as the second parameter. Here, the URI must be the well-known name of the actual object that you want the server to create and return.

The fact that the first parameter seemed insignificant may be some kind of design oversight by the Microsoft Engineers but I could well be wrong. If any readers out there understand the significance of the first parameter in the context I just mentioned, please share with us :-)

The Similarities Between Activator.GetObject(), COM's CoCreateInstance() and C++'s "new" Keyword

In this section, I'll discuss the second observation I made concerning the Activator.GetObject() method. Note the entire call to Activator.GetObject() :

IProcessActivator.IProcessActivator process_activator = (IProcessActivator.IProcessActivator)Activator.GetObject ( typeof (IProcessActivator.IProcessActivator), " " );

This piece of code indicates to the .NET Framework to create an object of "class" "ProcessActivator" and then return to me the IProcessActivator interface of that object.

This same style of object creation and interfacing return is evident in COM's CoCreateInstance() API. For example :

::CoCreateInstance(CLSID_MyClassID, NULL, CLSCTX_INPROC_SERVER , IID_IMyInterface, (LPVOID *)&pIMyInterface);

Here, we tell the COM sub-system to create a COM object with class ID CLSID_MyClassID and then to return to us a pointer to that COM object's IMyInterface implementation. Notice the same style of object creation and interface pointer return.

Finally, to present the idea solidly into the reader's mind, note C++'s style of object creation and interface pointer return in the following example code :

IMyInterface* pIMyInterface = new CMainObject();

The above code assumes, of course, that the C++ class CMainObject is derived from IMyInterface . Here, we instantiate a new instance of the CMainObject class and a pointer to the CMainObject 's IMyInterface vtable is returned to pIMyInterface .

Notice the same general approach used in object creation and interface pointer return in C++. It is the same as that in .NET Remoting and

This same trend of object creation is also evident in Visual Basic.

In Conclusion

I certainly hope that my article will be of benefit to other readers. I have attempted to present my own research work and share with all my findings.

I also hope that my example code will be of practical use to projects that require some remote controlling of machines. Such remote controlling is not uncommon and is particularly useful in automated systems.

Drop a message to me anytime you have a good suggestion on the example code or on Remoting in general.

Lim Bio Liong is a Specialist at a leading Software House in Singapore.
Bio has been in software development for over 10 years. He specialises in C/C++ programming and Windows software development.
Bio has also done device-driver development and enjoys low-level programming. Bio has recently picked up C# programming and has been researching in this area. Thanks for a great article, I'm trying to get remoting straight in my head and this helped a lot.
Question though, I have tried adding a new form to the server project, I want to invoke that form from the client, so far all I get is a form that egg times and does'nt respond??
How do I do it?
Cheers
Sign in · View Thread Hello,
Great article. Quick question: Since the form inherits from MarshalByRefObject, I would think it'd be easy to host a form on a server and instantiate and use it from a client application, but this doesn't seem to be the case. As soon as the form appears, it enters a "Not Responding" state, and does not update or respond to the user. I'm trying to create a multi-form application where the main window acts as a client from which you can instantiate other forms that are being hosted on the server. I hope I am explaining it well.
Anyway, the actual question:
Do you know if it's possible to do what I have explained above, and if it is, can you point me in the right direction?
Thank you for your time (and the article),
Tommy V
Sign in · View Thread It sounds like the web page style serving of windows forms, I think you would like to create an application which doesn't require updates (or patches, I don't know what your aim is). If so, you really should consider to apply a web application project to achieve such an approach. Otherwise, if I could correctly understand the problem you stated, It'll be a waste of time to try to create such a design while there is one already.
E. Guldogan
Sign in · View Thread When I use the server and client on the same machine, it works fine. But using the client from another machine, specify the serverIP, and receive an socket exception "No connection could be made because the target machine actively refused it". I tried to start alot of service related to the RPC and Remote access on the server machine but hopeless. Any thoughts?
Thanks alot,
LamNgo
Sign in · View Thread Hello Lam Ngo,
The error message is very familiar and I have experienced it before during development of some remoting project. I'm trying to recall the reason and outcome of the error message but I know that it has nothing to do with RPC or remote access.
I do recall discovering either a bug in program code or that the server program was not started up when the client wants to connect with it.
I'll email you again once I can remember the solution to this issue.
Thanks, Lam Ngo,
Sign in · View Thread Did you find a solution to the problem "No connection could be made because the target machine actively refused it", i am having the same problem. The code runs well on the same machine but does not work over my internal network. Will greatly appreciate any inputs you or anyone might have.
Thanks
Sign in · View Thread Found the solution, i was making a call to the local machine instead of the remote machine(IP address)
"typeof(IProcessActivator.IProcessActivator),"tcp://localhost:5999/ProcessActivator"
Solution:...
Pass the IP address of the remote machine to the client application
"typeof(IProcessActivator.IProcessActivator),"tcp://192.168.0.104:5999/ProcessActivator"
Sign in · View Thread
Another possible trouble may be a firewall prevention between your client and server machines. Try to use telnet to check. On the client machine on the command prompt type "telnet <ServerIP> <ServerPort>". If you see a whole black command prompt screen that waits, that means there is no problem about any firewall. Or If "...Could not open connection to the host..." is stated by telnet, you should check the firewall(s) on the line between (and on) your client and host machines.
E. Guldogan
Sign in · View Thread Thx for your article.
But I need to create 2 window services that have to communicate with each other.
I thought about using .NET Remoting but this is not possible in my mind Frown | :( :
public class ServiceManager : System.ServiceProcess.ServiceBase, MarshalByRefObject
Because MarshalByRefObject is not an Interface.
Please help me if you have a better solution.
Sign in · View Thread Hello MisterG,
First note that a Windows Service is an application, not an object. It would not make sense to derive "ServiceManager" from MarshalByRefObject.
However, you certainly can create 2 services that communicate with each other via .NET Remoting. Classes deriving from MarshalByRefObject can certainly be defined inside your Windows Service source codes and then exposed as usual via ChannelServices and RemotingConfiguration.
Regards,
Sign in · View Thread Put the server code in the class constructor of the windows service. I needed to place the client code into a service controller. The client code can remain the same, but I also placed the code in a client contructor. I initially got an error about "actively not blah blah" ( Frown | :( sorry I cant remember ). But I think that had to do with the windows firewall running on my computer. Now I am trying to figure out how to pass an object with state information about the service.
Sign in · View Thread Make your windows service both server and client. The client to communicate with the other's server.
WWW: http://hardywang.1accesshost.com
ICQ: 3359839
yours Hardy
Sign in · View Thread Hello Hardy, MisterG,
Yes, Hardy said it right there. Basically, MisterG, you would want to use your Service Applications as platforms for hosting your special .NET Remoting Objects.
The twist here is that these Remoting Objects are not generally exposed to other applications for usage. They serve as a communications mechanism between the 2 services.
Thanks, Hardy.
Regards,
Sign in · View Thread First of all, thanx for a great article on Remoting.
Second...there is one aspect of the client part that got me thinking, it even made me test if my theory was true. In the Main method for the client this code is executed:
TcpClientChannel channel = new TcpClientChannel();
ChannelServices.RegisterChannel(channel);
IProcessActivator.IProcessActivator process_activator =
(IProcessActivator.IProcessActivator)Activator.GetObject
typeof(IProcessActivator.IProcessActivator),
"tcp://localhost:9000/ProcessActivator"
process_activator.Run("IExplore.exe",
@"http://www.codeproject.com");
But what got me thinking is the first two lines, where you register a new TcpClientChannel. But it really doesn't supply any new information to the framework other than that you are going to use a TcpClientChannel at some point. But that information is also supplied to the framework when you call the Activator.GetObject() method. The parameters of that method both tells it what type of channel you are going to use, and what the name of the well known object is.
I tried to do the same thing in a project of my own and skipped the registration of the TcpClientChannel and it still worked. So what do i really use the TcpClientChannel for?
/Rouzbeh
Sign in · View Thread
Hello Rouzbeh,
Thanks very much for sharing the very interesting observation. I also tried running the client code without the TcpClientChannel registration and it indeed worked as you reported.
I really do not know whether this confirms that the TcpClientChannel registration can be ommitted in client code which connects to well-known objects via Activator.GetObject() since this convention seem to be in use in many example codes (including those found in MSDN). Personally, I have my doubts that this is so.
Let me do some research and I'll see if there can be some explanation. Let me know if you do come up with something too, Rouzbeh.
And thanks again, for the good catch !
Best Regards,
Sign in · View Thread Actually when the TcpClientChannel is registerd, it specifies on which port the client will be listening to receive the Return data if any.
As you know, port 0 means .net remoting selects a port for you.
So it is obvious that when client connects to well known objects on the server without Client port registration it works. I think, the problem would occur when you are try to return some data from the remote function back to client.
Regards,
Atul.
Sign in · View Thread I m not registering TcpClientChannel and just using activator.Getobject(). But my application is working fine without any prob.and its a big application doing all kind of operations.
So i still dont understand it really needs to register or not.
thanks,
Sign in · View Thread The IProcessActivator.IProcessActivator is confusing Dead | X| . I think that normally you have [namespace].[class]
Can you please clarify what the first part is and what the second part is and why you called them both the same thing? Confused | :confused:
Sign in · View Thread Hello,
IProcessActivator.IProcessActivator -is- in the [namespace].[class] format. The namespace is IProcessActivator and the interface name is also IProcessActivator.
Look at the IProcessActivator.cs file and you will see the following :
using System;
namespace IProcessActivator
/// <summary>
/// Summary description for interface IProcessActivator.
/// </summary>
public interface IProcessActivator
bool Run(string strProgramName, string strArgumentString);
Notice that the namespace name and the interface name are the same.
I must agree that this can potentially cause confusion even to the C# compiler. Notice in the server source codes :
using System;
using System.Diagnostics;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using IProcessActivator;
namespace ProcessActivator
/// <summary>
/// Summary description for ProcessActivator.
/// </summary>
public class ProcessActivator : MarshalByRefObject,
IProcessActivator.IProcessActivator
Notice that I also had to use "IProcessActivator.IProcessActivator" as part of the derivation for the class ProcessActivator despite the fact that I have already specified the I'm using the IProcessActivator namespace :
using IProcessActivator;
The same compilation problem is faced in the client code.
If I had coded the namespace for the IProcessActivator interface as IProcessActivatorNamespace, for example :
using System;
namespace IProcessActivatorInterface
/// <summary>
/// Summary description for interface IProcessActivator.
/// </summary>
public interface IProcessActivator
bool Run(string strProgramName, string strArgumentString);
... this problem would not have occurred. I would've been able to code the client and server codes using the "IProcessActivatorInterface" namespace and I would have no need to specify the fully qualified interface name "IProcessActivatorInterface.IProcessActivator".
Thanks for reporting this problem.
Best Regards,
It is basic programming procedure to start interfaces with I. The confusion is because you are starting the namespace with I as well. You should just drop the I off the namespace and only use it on the interface. Eek! | :eek:
Also, you shouldn't even need to specify using since it should all be in the same namespace. The only using should be for the remotable object.
Other than that, it seems like a decent article, except you shouldn't be reference Nish on here, except that it seems like you need his approval. Be your own person and don't use Nish as a way to grab hits.
Sign in · View Thread It is basic programming procedure to start interfaces with I. The confusion is because you are starting the namespace with I as well. You should just drop the I off the namespace and only use it on the interface. Eek! | :eek:
Also, you shouldn't even need to specify using since it should all be in the same namespace. The only using should be for the remotable object.
Other than that, it seems like a decent article, except you shouldn't be referencing Nish on here, except that it seems like you need his approval. Be your own person and don't use Nish as a way to grab hits.
Sign in · View Thread Thanks very much for the tips. Really appreciate them Smile | :)
I've also just posted an update to the code project webmaster with what I hope to be an improvement to this article. Hope it comes up soon.
Best Regards,
Sign in · View Thread Funnily enough I needed to write something almost exactly the same as this a month ago and my code pattern has turned out to mimic yours very closely. ( I am a .NET and C# learner so I like simple examples Big Grin | :-D )
It would be interesting for me to see some security layered upon this to protect remote access to the interface on the server side, perhaps by password, but then again I am a complete neophyte in that area too Blush | :O .
Cheers
Sign in · View Thread Hello Ohmigosh,
Very glad to know that my code is very similar to yours. It certainly shows that I'm on the right track Smile | :)
The idea about security is good. I'll see if I can come up with something along this line.
Thanks,
-Bio.
Sign in · View Thread Web01 2.8:2024-01-30:1