The .NET remoting architecture is flexible and it provides a framework that can be customized and extended. Learn about object activation, distributed architectures, and the benefits of multi-tier application development. (From Delphi for .NET Developer's Guide by Xavier Pacheco (Sams, 2004, ISBN: 0672324431).
Contributed by Xavier Pacheco Rating: / 14 August 09, 2004
Remoting is the process through which applications communicate across certain boundaries. The simplest form of boundary is a process, whereas a more complex one (and more common) is a network.
COM, DCOM, CORBA are all remoting technologies that, despite the different names and the incompatibles, work following a similar pattern: A client packages the name and parameters of a request, sends it over the boundary to a predefined location, and waits for a response.
Remoting technologies allow you to enter the world of "distributed systems" and achieve a level of flexibility and scalability not possible otherwise.
Remoting Technologies Available Today
Forms of Remoting have been usable from Delphi for a long time.
You could have used the COM/DCOM or CORBA frameworks included since version 3, the support for SOAP WebServices introduced in version 6, or even just simple TCP/IP components such as Indy.
The following section lists some of the most common remoting technologies available in the IT industry.
Sockets
Sockets are the basis of all network communication and allow developers to access network resources as if they were streams.
Sockets give full control to developers when it comes to low-level features. Unfortunately, sockets are difficult to program and add unnecessary complexity to the development of distributed applications.
All the other technologies listed in this section are built on top of sockets and provide developers with abstraction layers that simplify communication.
RPC
RPC is both an abbreviation for Remote Procedure Call and a specification designed by the the Open Group (http://www.opengroup.org/).
RPC outlined many of the architectural pillars used in Remoting technologies such as DCOM, SOAP, and CORBA.
These include
proxies and stubs: code present on clients and servers whose purpose is to make remote calls look as if they were local
data marshalling: the process of packaging a stream of data containing a request name and its parameters
interface definition language (IDL): a type of document that lists the name and parameters of the procedures that remote clients can invoke
Java RMI enables programmers to create distributed Java to Java applications in which the methods of remote Java objects can be invoked from other Java virtual machines on the same or different computer.
It is the equivalent of .NET Remoting for the Java platform, but it is only accessible from Java environments.
CORBA is the abbreviation for Common Object Request Broker Architecture, and it is an open, vendor-independent specification defined by the Object Management Group (http://www.omg.org).
Companies such as Borland, Iona, PrismTech, and 2AB provide products that implement the specification.
CORBA is based on the IIOP protocol to transmit messages.
XML-RPC is a specification for an XML-based messaging protocol designed to work over HTTP.
The XML-RPC encoding is very simple to understand and use and provides support for most simple data types (integers, strings, and so on) and complex structures (objects).
Many XML-RPC implementations are available for the Linux, Unix, and Windows platforms.
The Distributed Component Object Model (DCOM) was the technology recommended by Microsoft for building distributed applications and, simply put, allows to access COM automation servers from remote computers.
DCOM was designed to work using different network transports (that is, TCP/IP and HTTP), to be cross platform, and it was based on the Open Group DCE-RPC specification.
Although the framework provides support for non-x86 architectures and has been ported to some Unix platforms, it has never been successful on non-Microsoft platforms.
Delphi supported DCOM, starting from version 3, with a framework that greatly simplified the development of DCOM servers and clients.
COM-Interop
COM-Interop is a technology that enables .NET and COM/DCOM applications to interoperate with each other. COM-Interop was covered in Chapter 16.
SOAP
SOAP is the acronym for Simple Object Access Protocol. The official definition found in the recent 1.2 specification says:
SOAP is a lightweight protocol intended for exchanging structured information in a decentralized, distributed environment. SOAP uses XML technologies to define an extensible messaging framework, which provides a message construct that can be exchanged over a variety of underlying protocols. The framework has been designed to be independent of any particular programming model and other implementation specific semantics.
SOAP is the foundation of Web Services (applications programmatically accessible by remote clients via HTTP) and is a de facto standard supported by many vendors including Microsoft, IBM, Borland, and Sun.
While relatively new, SOAP is the most accepted protocol for cross-platform communication. It is one of the best candidates to write systems accessible by applications written in any language, from any platform. Figure 29.1 illustrates this relationship.
Figure 29.1 SOAP cross-platform communication.
Microsoft .NET SDK provides a lot of infrastructure and prebuilt classes to create and consume Web Services that are fully accessible from Delphi.
You can build SOAP servers and clients in both Delphi 7 and Delphi for .NET, although the approach you have to use is very different.
.NET Remoting
.NET technologies use the term .NET Remoting to indicate two different things:
A form of remoting that uses binary messaging and is native to .NET
The foundation of classes that allow for any kind of remoting in .NET (either SOAP, binary, or user defined)
Throughout this chapter and Chapter 30, we will use the second definition.
The binary messaging protocol available in the .NET Remoting framework is Microsoft's official replacement for COM/DCOM. It is the most efficient way to make .NET applications communicate with each other, but, differently from SOAP, it is not meant for cross-platform communication because it only works between .NET applications.
We'll see how to create applications that use this form of remoting later in this chapter and in Chapter 30.
This chapter is from Delphi for .NET Developer's Guide, by Xavier Pacheco (Sams, 2004, ISBN: 0-672-32443-1). Check it out at your favorite bookstore today.
A system divided in two or more executable modules running on different machines, processes, or AppDomains is said to use a distributed architecture. Processes and AppDomains are mentioned because marshalling would still be required.
Although it could be argued that a single executable divided in several units also represents an example of a distributed architecture, we'll stick to examples in which some form of marshaling is required.
Distributed architecture had existed for a long time before .NET Remoting was created. These systems and the technologies used to build them have been the foundation for the design and implementation of what is now available in Delphi for .NET and the Microsoft .NET Framework. Each of these architectures makes use of one or multiple remoting technologies to perform remote communication.
Client/Server
This is the earliest and most well-known distributed architecture. Delphi applications using technologies such as dbGo, Interbase Express, or the Borland Database Engine/SQL Links are client/server.
This type of architecture is usually referred to as two-tier in which a client is responsible for the presentation of the data (that is, using data-aware controls such as TDBGrid) and a server for the retrieval and storage of it.
Although this architecture is simpler to understand and use than others, it has several problems that are critical in today's Internet enabled world:
It usually only works in local area network(s).
It cannot efficiently serve thousands of concurrent users.
It is not easy to update and maintain: Because each client requires a permanent connection to the database and most business logic is contained in the clients themselves, whenever a change is due, all clients need to be redeployed.
Figure 29.2 illustrates a client/server model.
Figure 29.2 Client-server model.
Peer-to-peer
Peer-to-peer indicates networks without a central server in which each computer acts as a client and a server at the same time. A Windows workgroup without a domain server is a peer-to-peer network, for instance.
This type of architecture generally only works in local area networks and depends on frequent UDP broadcasts to make each node aware of the status of the others. These broadcasts are normally not possible over the Internet. Figure 29.3 depicts the peer-to-peer network.
Figure 29.3 A peer-to-peer network.
Gnutella or Napster are forms of hybrid peer-to-peer systems in which each node communicates with a server to find out about the existence of other nodes and then initiates direct communication with each.
Although convenient for file sharing and chat programs, this architecture fails to provide benefits to regular, data-oriented applications.
Multitier
Multitier is the preferred type of distributed architecture today. Delphi applications built using technologies such as DataSnap or Windows DNA are multitier. You will use .NET Remoting to develop multitier systems in Delphi for .NET.
This type of architecture is commonly referred to as three-tier, where a client communicates to a process on a remote server and this, in turn, communicates with an RDBMS located on a different server. This is shown in Figure 29.4.
Figure 29.4 Multitier distributed model.
The key aspect of this type of architecture is the presence of the middle tier. Middle tiers are usually composed of two layers:
Data access—Responsible for querying and updating the underlying data source. It can perform data caching to improve performance and minimize access to the database. It can abstract the RDBMS engine dialect and provide a simple object-oriented API for the business layer.
Business—Normally composed by objects accessible from the clients through Remoting. These objects perform data validation, transformation, in-memory calculations, and so on.
Data access and business layers could reside in different application domains or simply be placed in one. In general, using one application domain makes things easier, although you might opt for a more marked division in a few cases.
An example could be providing a hot-swappable data access layer that works as a plug-in and can be replaced at runtime or during installation without the need to recompile the business layer.
The next section lists some of the most evident advantages of multitier systems.
This chapter is from Delphi for .NET Developer's Guide, by Xavier Pacheco (Sams, 2004, ISBN: 0-672-32443-1). Check it out at your favorite bookstore today.
A multitier design offers several advantages over two-tier client/server designs, although it is probably more complex to understand and implement.
Scalability and Fault Tolerance
The first and most prominent advantage of multitier architectures is scalability.
Client/server systems depend on a central database server and require permanent connections to be present. The more clients, the worse the server will perform because of the increased resource consumption (that is, network connections constantly open, frequent access to tables, cursors, and so on). Client/server systems aren't usually capable of handling more than a couple of hundred concurrent users in an efficient manner.
By having a middle tier composed of multiple servers (cluster), you will be able to divide the workload between them and only hit the database server when really needed. By implementing caching techniques, you could also minimize access to what is strictly necessary, increasing overall performance.
Figure 29.5 shows two (or more) clients accessing a cluster of two (or more) servers, which, in turn, are connected to a single database server.
Figure 29.5 Multitier cluster model.
In order to benefit from clustering, your clients will need to operate in a disconnected fashion and only connect to the middle-tier when they need to read data or send some updates. Regardless of the status of the remote machine—which could have become irresponsive or have been shut down for maintenance—this ensures that the client will continue to function by using another server.
From a server perspective, this approach is called stateless. The server only has a means to communicate with clients when they initiate a call. Once this is completed, the client ceases to exist for the server.
If you are familiar with Web development, you will find many analogies: Stateless design is almost mandatory when you need high scalability and have large numbers of concurrent clients.
We will explain how remote objects are instantiated later in the sections "Server Activation" and "Client Activation." In order to ensure that your system is highly scalable, you'll have to use one of the two Server Activation models (SingleCall or Singleton). If you need a more coupled, but less scalable and certainly not clusterable approach, you could instead decide to tie a client to a specific instance of an object residing on one server, using Client activation.
In client/server systems, business processing is mostly done on the client applications—sometimes with the help of stored procedures running on the database server.
Unfortunately, because stored procedures are more complex to write and debug than Delphi code, they are not used extensively—especially in companies that don't have a dedicated database administration staff.
If the number of clients is large, updating each desktop with a new copy of the client executable might be laborious and error prone.
In multitier systems, a big part (if not all) of business processing happens in the middle tier. The applications running on the middle tier are simple to write (Delphi code), and, once updated, all clients automatically benefit from fixes or enhancements without the need for redeployment.
Somebody might argue that having an executable placed on a shared network drive could be just as easy to update, but consider what would happen if a workstation is keeping the file locked or if you needed to access it from an office in another city.
Security
Because clients don't directly communicate with the database, it becomes much easier for administrators to enforce programmatic security into their applications and protect the database from unauthorized access.
The request made by clients can travel, encrypted, over SSL connections. Methods of business objects can return less data to clients based on session information: This would be transparent to the caller and changeable over time without the need to redeploy the clients.
This chapter is from Delphi for .NET Developer's Guide, by Xavier Pacheco (Sams, 2004, ISBN: 0-672-32443-1). Check it out at your favorite bookstore today.
The following sections provide an overview of the .NET remoting technology.
Architectural Overview
The .NET Remoting architecture is extremely flexible and provides developers with a framework that is easy to customize and extend.
This section provides you with a brief description of the most important elements of the framework and introduces a few concepts only present in .NET Remoting.
Application Domains
Application Domains are at the core of the remoting infrastructure and represent the boundaries for Interprocess Communication (IPC).
In classical Win32, Windows creates a new process when an executable is launched. Processes are the lowest level of isolation and cannot directly share memory between each other. Memory addresses are process relative: Pointers to memory in one process are meaningless to another.
Application Domains, or AppDomains, are to .NET what processes are to Win32. AppDomains provide a more granular level of separation and better security than Win32 processes.
You can run several application domains in a single process with the same level of isolation that would exist in separate processes, but without incurring the additional overhead of making cross-process calls or switching between processes. Figure 29.6 illustrates this concept.
.NET Remoting is necessary to make objects in one domain communicate with those hosted in another, regardless of the domains being in the same process or in processes running on different machines.
The System.Runtime.Remoting Namespace
In order to develop applications that use .NET Remoting, you will need to reference the namespace System.Runtime.Remoting in both your clients and servers.
Figure 29.6 Cross AppDomain communication using .NET Remoting.
The System.Runtime.Remoting namespace and those depending on it provide classes and interfaces that allow developers to create and configure distributed applications.
The RemotingConfiguration Class
The RemotingConfiguration class contains static class methods for interfacing with configuration settings and register objects so that they can be remotely invoked.
The following snippet of code registers the class TBankManager:
More about this method is explained in the section "Server Activation."
Remoting settings can be programmatically set or can be read from external configuration files.
The RemotingConfiguration.Configure() method allows developers to configure the Remoting infrastructure through the use of XML formatted configuration files.
The configuration files can contain information such as the networking protocol to use for remote communication, the TCP or HTTP port used, the message formatting type (SOAP or binary), and more.
This chapter is from Delphi for .NET Developer's Guide, by Xavier Pacheco (Sams, 2004, ISBN: 0-672-32443-1). Check it out at your favorite bookstore today.
The ChannelServices class provides static class methods to register remoting channels, and it's used in both clients and servers.
The following snippet of code shows how to create and register an HTTP communication channel listening on port 9088 for use on the server side:
var Channel : HttpChannel; begin Channel := HttpChannel.Create(9088); ChannelServices.RegisterChannel(Channel);
Clients do not specify a port number when registering channels. The URI information necessary to access remote objects is specified in the call to the Activator object at a later time, and this will also include a port number.
The following snippet of code shows how to create a client channel and acquire a reference to the remote service from the client application:
type TCalculator = class(MarshalByRefObject, ICalculator) private protected // ICalculator function Sum(A,B : integer): integer;
public end;
Remotable Objects
Objects intended to be accessed by different domains are called remotable objects. Two types of remotable objects exist in .NET: Marshal-By-Reference, and Marshal-By-Value.
Note - A third type, Context-bound is also available, but it won't be covered in this book because of the length and complexity of the topic. You can think of a .NET context as a subdivision of an application domain in which context-bound objects live. For more information about Contexts, refer to the .NET Framework SDK Documentation at "ms-help://MS.NETFrameworkSDKv1.1/cpguidenf/html/ cpconremotableobjects.htm".
Marshal-By-Reference Objects
Simply put, instances of this type can be seen as a collection of methods that can be invoked from remote clients.
Marshal-By-Reference objects are created on the server where they live for the duration of a method call (see"Single-call Instantiation"later) or are shared among different clients (see "Singleton Activation" later) for the duration of a lease (see "Leases and Sponsors" later).
In order to create a Marshal-By-Reference object, you need to make its class descend from MarshalByRefObject, which is defined in the System namespace.
The following is an example of a Marshal-By-Reference class:
type TCalculator = class(MarshalByRefObject, ICalculator) private protected // ICalculator function Sum(A,B : integer): integer;
public end;
The methods of this type of objects can have any kind of simple data type (that is, integers, strings, and so on) or object parameters(as long as they are Marshal-By-Value objects).
Marshal-By-Value Objects
Instances of this type cross application domain boundaries after being serialized in a transportable format.
When they reach the target domain, they are deserialized and a new instance of the original class is created in the target application domain.
In order to create a serializable object, you need to mark its class with the [Serializable] attribute like this:
The purpose of these types of objects is generally to share data between applications in a structured manner. They are usually used as input or output parameters of remote methods.
This chapter is from Delphi for .NET Developer's Guide, by Xavier Pacheco (Sams, 2004, ISBN: 0-672-32443-1). Check it out at your favorite bookstore today.
Before you can acquire a reference to a remote object running in a different application domain, the object needs to be created on the remote machine.
There are two ways to create an instance of a remotable object: using server activation and using client activation.
Server Activation
Server-activated objects are referred to as well-known because they are registered in the .NET remoting system and published at a specific and well-known endpoint or URI.
Well-known objects can be activated in two ways: singleton and single-call.
Singleton Activation
Singleton activated means only one instance of an object will be created and be accessible at any given time. If two clients request a reference to a singleton-mode–configured object, they will both receive the same reference and their calls will be serialized.
The following code shows how to register a singleton-mode–configured object:
Single-call instantiation is the most scalable activation mode because it best fits stateless systems. When you register an object as single-call, an instance will be created upon each client's request and, once the call is completed, it will be released for garbage collection.
The following code shows how to enable single-call instantiation:
Client-activated objects are activated on a per-client basis. Each client will have his own unique reference that can also remain active between method calls (stateful).
Although client-activation offers some advantages and is simpler to use than server-side instantiation, it is less scalable. It uses more resources on the server, ties clients to one server, and, because of this, doesn't work in Web farms.
To enable for this kind of activation, your server will have to include a call similar to the following:
You will see more in detail how to create client-activated objects in the section "Client Activation" in Chapter 30.
Leases and Sponsors
A remote object's lifetime is managed by lease-based garbage collection. Each application domain contains its own lease manager and tracks access to objects. If an object is not accessed for a certain amount of time, it's then handed to the garbage collector that destroys it.
Object lifetime management in .NET is radically different from how DCOM handles object lifetimes. In DCOM, a combination of reference counting and network pinging was used to determine when objects could be destroyed. This led to network congestion and other side effects that were less-than-optimal in large installations. .NET remoting was designed to resolve those problems.
Leases are used for server-activated singleton objects and client-activated objects. After the .NET Framework creates an instance of those, it calls the virtual method InitializeLifeTimeServices (inherited from MarshalByRefObject and possibly overridden), which returns an object that implements the interface ILease. This object will then be queried to determine if the lease on the object has expired and it can be destroyed.
Leases can be renewed by sponsors. You can define a class that acts as a sponsor by implementing the ISponsor interface. You then need to associate the sponsor to a lease by calling the method ILease.Register.
The .NET Framework defines the ClientSponsor class, which provides a default implementation for a lifetime sponsor class.
Proxies
Clients communicate with remote objects by using proxies. A proxy is an object that resides in the address space of the client and acts as a surrogate for the remote object.
In .NET, we have two types of proxies: transparent and real.
The transparent proxy is the object we directly acquire when writing code such as
After a method call is issued, the transparent proxy packages the name and parameters into a message object and hands this to the real proxy.
The real proxy then dispatches the message object to the .NET Framework, which will then use a remoting channel to deliver it.
Channels
Remoting channels transport messages across application domains. The .NET SDK provides two types of channels you can use: TCP/IP and HTTP.
You would use a TCP/IP channel mostly in local area networks or where communication has to be as fast as possible.
You would instead use the HTTP channel for Internet or, in general, firewall-friendly type of communication.
Both channels can be used independently of the messaging format. This means that you can decide to use SOAP or Binary formatting over either TCP/IP or HTTP.
This chapter is from Delphi for .NET Developer's Guide, by Xavier Pacheco (Sams, 2004, ISBN: 0-672-32443-1). Check it out at your favorite bookstore today.
Developing Delphi applications that make use of .NET remoting is a relatively easy process, and, if you used COM, you will find that it's conceptually very similar to developing OLE automation servers. Luckily, registration of remotable objects is much simpler. It can be done programmatically (RemotingConfiguration method calls) or declaratively (using an XML configuration file). You no longer have to deal with type libraries.
The following example, although simple, shows how to best structure .NET Remoting projects and contains a certain amount of real-world business logic.
The server of this example represents a bank service. It contains a list of bank accounts (two to be precise) and allows to query for the details of these (account number, name of the person associated with it, and its balance) and to do a money transfer.
Setting Up the Project
We'll start the development of our bank system by creating a project group and three empty projects (a Package, a Console Application, and a Windows Forms Application) that we will complete in the following sections.
Select File, New, Other. Click Other Files and select Project Group, as shown in Figure 29.7.
Figure 29.7 Setting up a project group.
Save the project group as BankExample.
It's very convenient to use project groups when developing distributed applications, especially at early stages. Project groups provide you with an immediate look at all the elements of your systems and allow you to switch between them more efficiently than with individual projects.
Add the Package to the group by clicking the New button inside the Project Manager panel and then selecting Package, as shown in Figure 29.8.
Figure 29.8 Selecting a Package.
Repeat the operation and add a Console Application and a Windows Forms Application to your project group.
Once done, save and name the projects as shown in Table 29.1.
Table 29.1 Project Names in the Project Group
Project Type
Project Name
Package
BankPackage.dll
Console Application
BankServer.exe
WinForms Client
BankClient.exe
This chapter is from Delphi for .NET Developer's Guide, by Xavier Pacheco (Sams, 2004, ISBN: 0-672-32443-1). Check it out at your favorite bookstore today.
In order for applications to make use of Delphi packages, the package itself and the applications using it must include a reference to the Borland.Delphi assembly.
This is automatically added for you by Delphi, but in case you removed the reference, simply go inside the Project Manager, right-click on the node Requires under the project BankPackage.dll and select Add Reference.
When the Add Reference dialog box appears, select the Borland.Delphi assembly, as shown in Figure 29.9.
Figure 29.9 Adding the Borland.Delphi reference.
Click OK, compile the package, and select the Server project.
The server application will need references to the Borland.Delphi assembly, the BankPackage.dll itself, and System.Runtime.Remoting.
Reopen the Add Reference dialog box and select the assemblies shown in Figure 29.10. You will have to browse to the directory containing BankPackage.dll to select it. Once done, you will see it in the Project References page of this dialog box. The .NET Assemblies page only contains GAC'ed assemblies.
Figure 29.10 Adding the Assembly reference to the BankServer project.
Once you're done adding these references, your project group should look like that shown in Figure 29.11.
Figure 29.11 The BankExample project group.
This chapter is from Delphi for .NET Developer's Guide, by Xavier Pacheco (Sams, 2004, ISBN: 0-672-32443-1). Check it out at your favorite bookstore today.
If you're familiar with COM, you'll be accustomed to creating type libraries. The type library is a standardized binary resource linked in your server's executable or DLL that Windows requires to enable for interprocess communication. The type library represents the contract between your server and clients: It tells clients what they can do and what complex data types they will be using, but it contains no implementation.
When you develop .NET Remoting servers, type libraries are not necessary anymore: .NET's metadata included inside the compiled package will serve this purpose. The BankPackage.dll we just created is a .NET assembly that will contain the shared interfaces and classes used by the server and the client application.
We've added a new unit to BankPackage.dll and saved it as BankShared.pas. This unit is shown in Listing 29.1.
In Listing 29.1, the IBankManager interface is a critical aspect of this unit (lines 24–32). As you can see, there isn't any class implementing it. This package is going to be shared between server and client, but the client doesn't need to know about (or contain) any implementation details. All it needs to know is what and how to call it.
The only class defined and implemented in this unit is TAccount (lines 6–22 and 38–47). In addition to this, the array TAccountNumberArray is also declared (line 25).
Because these two types are referenced by the methods of IBankManager, they need to be included in the shared assembly.
The interface IBankManager is implemented in the server project.
The other important thing to notice is the use of the attribute [Serializable] above the class TAccount (line 7). If TAccount was not marked as serializable, IBankManager.GetAccount would fail at runtime when called remotely.
TAccount is used as a Marshal-By-Value object. When the remote client calls GetAccount, a copy of the object is passed across application domain boundaries.
This chapter is from Delphi for .NET Developer's Guide, by Xavier Pacheco (Sams, 2004, ISBN: 0-672-32443-1). Check it out at your favorite bookstore today.
The project source file in which we will open an HTTP remoting channel and register the class that implements the IBankManager interface.
A unit that contains the class TBankManager. This class implements the IBankManager interface.
We've added a new unit to the server project and saved it as BankServer_Impl.pas. This unit is shown in Listing 29.2.
Listing 29.2 The BankServer_Impl.pas Unit
1: unit BankServer_Impl; 2: 3: interface 4: 5: uses 6: BankShared; 7: 8: type 9: TBankManager = class(MarshalByRefObject, IBankManager) 10: private 11: fAccount1, 12: fAccount2 : TAccount; 13: protected 14: // IBankManager 15: function GetAccountNumbers : TAccountNumberArray; 16: function GetAccount(const AccountNumber : integer) : TAccount; 17: procedure TransferMoney(const Origin, Destination : integer; 18: const Amount : double); 19: public 20: constructor Create; 21: end; 22: 23: implementation 24: 25: { TBankManager } 26: 27: constructor TBankManager.Create; 28: begin 29: inherited Create; 30: fAccount1 := TAccount.Create(1, 'John Smith', 1999); 31: fAccount2 := TAccount.Create(2, 'Jack Rockwell', 249); 32: end; 33: 34: function TBankManager.GetAccount(const AccountNumber: integer): TAccount; 35: begin 36: case AccountNumber of 37: 1 : result := fAccount1; 38: 2 : result := fAccount2; 39: else raise Exception.Create('Invalid account number!'); 40: end; 41: 42: Console.WriteLine('A client requested account '+result.Number.ToString+ 43: ' ('+result.Name+')'); 44: end; 45: 46: function TBankManager.GetAccountNumbers: TAccountNumberArray; 47: begin 48: SetLength(result, 2); 49: result[0] := fAccount1.Number; 50: result[1] := fAccount2.Number; 51: 52: Console.WriteLine('A client requested the list of accounts'); 53: end; 54: 55: procedure TBankManager.TransferMoney(const Origin, Destination: integer; 56: const Amount: double); 57: var origin_acct, destination_acct : TAccount; 58: begin 59: origin_acct := GetAccount(Origin); 60: destination_acct := GetAccount(Destination); 61: 62: if (origin_acct.Balance<Amount) or (Amount<0) 63: then raise Exception.Create('Insufficient funds or invalid amount specified'); 64: 65: destination_acct.Balance := destination_acct.Balance+Amount; 66: origin_acct.Balance := origin_acct.Balance-Amount; 67: 68: Console.WriteLine('Transferred ${0} from account {1} to account {2}', 69: Amount.ToString, origin_acct.Number.ToString, 70: destination_acct.Number.ToString); 71: end; 72: 73: end.
Find the code on the CD: \Code\Chapter 29\Ex01.
Pay attention to the declaration of TBankManager at line 9. MarshalByRefObject is the base class for objects that communicate across application domain boundaries by exchanging messages using a proxy.
The proxy is created the first time the object is accessed. Subsequent calls on the proxy are marshaled back to the object residing in the server application domain.
Classes must inherit from MarshalByRefObject when their instances need to be used across application domains, and their state doesn't need to be or cannot be copied.
In the project source file (see Listing 29.3 later), we've added the namespaces System.Runtime.Remoting, System.Runtime.Remoting.Channels, and System.Runtime.Remoting.Channels.HTTP. The project source file now contains
uses BankServer_Impl in 'BankServer_Impl.pas', System.Runtime.Remoting, System.Runtime.Remoting.Channels, System.Runtime.Remoting.Channels.HTTP;
We've also defined a variable of type HTTPChannel called Channel and initialized it as follows:
var def_HTTPPort : integer = 8099; Channel : HttpChannel; Begin [..] Channel := HttpChannel.Create(def_HTTPPort); ChannelServices.RegisterChannel(Channel);
Finally, we registered the type TBankManager by using the RemotingConfiguration class:
The complete source for the BankServer.dpr file is shown in Listing 29.3.
Listing 29.3 BankServer.dpr File
1: program BankServer; 2: 3: {$APPTYPE CONSOLE} 4: 5: {%DelphiDotNetAssemblyCompiler 'BankPackage.dll'} 6: {%DelphiDotNetAssemblyCompiler [..]} 7: {%DelphiDotNetAssemblyCompiler [..]} 8: 9: uses 10: BankServer_Impl in 'BankServer_Impl.pas', 11: System.Runtime.Remoting, 12: System.Runtime.Remoting.Channels, 13: System.Runtime.Remoting.Channels.HTTP; 14: 15: var def_HTTPPort : integer = 8099; 16: Channel : HttpChannel; 17: begin 18: Console.WriteLine('Initializing server...'); 19: 20: // Initializes the server to listen for HTTP requests on a specific port 21: Channel := HttpChannel.Create(def_HTTPPort); 22: ChannelServices.RegisterChannel(Channel); 23: Console.WriteLine('HTTP channel created. Listening on port '+ 24: System.Convert.ToString(def_HTTPPort)); 25: 26: // Registers the TBankManager service 27: RemotingConfiguration.RegisterWellKnownServiceType( 28: typeOf(TBankManager), 29: 'BankManager.soap', 30: WellKnownObjectMode.Singleton); 31: 32: // Starts accepting requests 33: Console.WriteLine('Waiting for requests...'); 34: Console.ReadLine; 35: end.
Find the code on the CD: \Code\Chapter 29\Ex01.
Lines 21–22 show how to make the server application create a remoting channel that listens for HTTP messages on port 8099. Lines 27–30 register the object TBankManager as a singleton ready to be remotely accessed.
Notice how you do not need to associate a class to a channel. If you have other remote classes, you would just need to register them as we did with TBankManager (lines 27–30). The HTTP channel that was previously created listens for any type of remoting message coming from clients and forwards it to the correct target object.
At this point, you can compile the server application and launch it outside the IDE. It will display the contents as shown in Figure 29.12.
Figure 29.12 Server application output.
This chapter is from Delphi for .NET Developer's Guide, by Xavier Pacheco (Sams, 2004, ISBN: 0-672-32443-1). Check it out at your favorite bookstore today.
In the client project, the main form's uses clause contains the following namespaces:
uses System.Drawing, System.Collections, System.ComponentModel, System.Windows.Forms, System.Data, // Remoting and Bank related BankShared, System.Runtime.Remoting, System.Runtime.Remoting.Channels, System.Runtime.Remoting.Channels.HTTP;
The form's OnLoad event handler contains the following code:
As you probably guessed, the preceding code creates an instance of the remote object BankManager, which we can now use from inside the client application.
It is now possible to test the remote methods. Figure 29.13 shows the form of the example application that does this at design-time.
Figure 29.13 Main form of Client application.
The Refresh button's Click event handler contains code shown in Listing 29.4.
Listing 29.4 Refresh Button's Click Event Handler
1. procedure TWinForm2.bRefresh_Click(sender: System.Object; 2. e: System.EventArgs); 3. var accountarray : TAccountNumberArray; 4. i : integer; 5. begin 6. accountarray := fBankManager.GetAccountNumbers; 7. cbBankAccounts.Items.Clear; 8. cbOrigin.Items.Clear; 9. cbDestination.Items.Clear; 10. 11. for i := 0 to High(accountarray) do begin 12. cbBankAccounts.Items.Add(accountarray[i].ToString); 13. cbOrigin.Items.Add(accountarray[i].ToString); 14. cbDestination.Items.Add(accountarray[i].ToString); 15. end; 16. cbBankAccounts.SelectedIndex := 0; 17. end;
Find the code on the CD: \Code\Chapter29\Ex01.
As you can see from the first line of this method, the use of the remote object pointed by fBankManager is not any different from using a local copy. We declared a variable of type TAccountNumberArray at line 3, and we use it at line 6.
Keep in mind that the similarities end at the code level, especially when it comes to performance.
When the code
accountarray := fBankManager.GetAccountNumbers;
is executed, the client application is creating a SOAP message, sending it to the server via HTTP and unpacking the response. This overhead is not obvious when making a single method call, but you will immediately see a big difference in performance as soon as you put that code inside a loop.
The Retrieve button's Click event handler contains the code shown in Listing 29.5.
At line 6, we grab the selected account number from the combo box cbBankAccounts and assign it to the integer variable acctnumber. We then pass acctnumber to the BankManager.GetAccount method (line 7), which will generate a remote call to the BankManager object running on the server. Finally (lines 8–10), we display the information contained in the account returned by the server.
Finally, the Transfer button's Click event handler contains
The client, when launched, displays the form shown in Figure 29.14.
Figure 29.14 Client application—remoting in action.
This chapter is from Delphi for .NET Developer's Guide, by Xavier Pacheco (Sams, 2004, ISBN: 0-672-32443-1). Check it out at your favorite bookstore today.