飘逸的烈马 · delphi 第一次接触与配置接口 - ...· 1 周前 · |
腼腆的小刀 · 处理URL中的“robots.txt ...· 6 月前 · |
有爱心的爆米花 · 将"\r\n "替换为"\n"· 1 年前 · |
骑白马的咖啡 · Lustre并行文件系统源码安装配置· 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...
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 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.
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
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
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 :
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.
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
,
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
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.
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 :-)
In this section, I'll discuss the second observation I made concerning the
Activator.GetObject()
method. Note the entire call to
Activator.GetObject()
:
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 :
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.
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.
public class ServiceManager : System.ServiceProcess.ServiceBase, MarshalByRefObject