Remoting是NET平台下比較成熟高效的分布式技術,我們習慣采用傳統的遠端調用的方式使用Remoting。在用戶端所在的Application Domain,我們通過Proxy(Transparent Proxy)遠端地跨Application Domain調用一個方法。當來自Client端的調用請求通過Proxy到達Server端所在的Application Domain後,Remoting Infrastructure在Server 端激活(Activate)相應的遠端對象(一個繼承子System.MarshalByRefObject類對象)——這裡僅僅以服務端激活對象(Server Activated Object——SAO),然後再Server端執行相應的操作後把Result傳遞給Proxy,并最終到達Client。這是一種典型的Request/Response的調用方式。
這次不是講用C#如果實作,而是用Delphi prism 實作。
PRINCE.Contract:Class Library Project,定義遠端對象(Remote Object)和Callback對象的Contract(Interface)。實際上,站在Server端的角度上看,Callback的操作是在Client端的Application Domain中執行的,是以從本質上講, Callback對象是Server端的遠端對象。
之是以定義這樣一個Contract Project,其目的主要有以下幾點:
1. 如果沒有把遠端對象的Interface,對已某一個需要調用這個遠端對象的Client來說,它必須引用遠端對象本身。從安全的角度考慮,Server向Client過多暴露了操作的實作邏輯。如果我們把遠端操作的Contract提取出來,Client隻要引用這個Interface就可以了。
2. 一般來說,遠端對象的Contract相對時靜态的(static),而業務邏輯的實作則是經常 變化的。因為Client隻需要了解的是遠端對象的Contract,所在無論Server端對遠端對象的實作作了多大的變動,對不回對Client産生任何影響。
PRINCE.Service:Class Library Project,定義遠端對象本身。由于遠端對象必須實作上邊定義的Contract。是以需要引用PRINCE.Contract。
PRINCE.Hosting:Console Application Project,以Self-Host的方式Host Remoting。引用PRINCE.Service, PRINCE.Contract。
PRINCE.Client:Console Application Project,引用PRINCE.Contract。

IDuplexCalculator.pas
namespace PRINCE.Contract;
interface
uses
System.Collections.Generic,
System.Linq,
System.Text;
type
IDuplexCalculator = public interface
procedure &Add(x,y:double;callback:ICalculatorCallback);
end;
implementation
end.
ICalculatorCallback.pas
namespace PRINCE.Contract;
interface
type
ICalculatorCallback = public interface
procedure ShowResult(x,y,resulted:Double );
end;
implementation
end.
Step 3 在PRINCE.Service定義遠端對象
DuplexCalculatorRemoting.pas
namespace PRINCE.Service;
interface
uses PRINCE.Contract;
type
DuplexCalculatorRemoting = public class(MarshalByRefObject, IDuplexCalculator)
private
protected
public
procedure &Add(x,y:double;callback:ICalculatorCallback);
end;
implementation
procedure DuplexCalculatorRemoting.&Add(x, y: Double; callback:ICalculatorCallback);
var
resulted:Double;
begin
Console.WriteLine('Invoke the method Add(' + x.ToString() + ',' + y.ToString() +')');
resulted := x + y;
callback.ShowResult(x,y,resulted);
end;
end.
Step 4 在PRINCE.Hosting Host遠端對象
App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.remoting>
<application name="Calculator">
<service>
<wellknown mode="SingleCall"
type="PRINCE.Service.DuplexCalculatorRemoting,PRINCE.Service"
objectUri="DuplexCalculator.soap" />
</service>
<channels>
<channel ref="http" port="8080">
<serverProviders>
<provider ref="wsdl" />
<formatter ref="binary" typeFilterLevel="Full" />
</serverProviders>
<clientProviders>
<formatter ref="binary" />
</clientProviders>
</channel>
</channels>
<channels>
<channel ref="tcp" port="8081">
<serverProviders>
<provider ref="wsdl" />
<formatter ref="binary" typeFilterLevel="Full" />
</serverProviders>
<clientProviders>
<formatter ref="binary" />
</clientProviders>
</channel>
</channels>
</application>
</system.runtime.remoting>
</configuration>
Program.pas
namespace Hosting;
interface
uses
System.Linq;
type
ConsoleApp = class
public
class method Main;
end;
implementation
class method ConsoleApp.Main;
begin
System.Runtime.Remoting.RemotingConfiguration.Configure(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile,false);
Console.WriteLine("Calculator service has begun to listen... ...");
Console.Read();
end;
end
.
這裡需要特别注意的有以下兩點:
1. 在定義Channel是需要指定一個雙向Channel(Bi-Directional Channel)。系統給我們定義一一系列的System-Defined Channel用于調用遠端對象。其中有一些隻能提供單向的通信——比如隻支援Client到Server的通信,而另一些可以提供雙向的通信——比如TCP Channel 和Http Channel.
2. 在ServerProvider Section,我們必須設定typeFilterLevel為Full。出于安全的考量,Remoting提供了兩個反序列化級别(Level)——Low & Full。Low是預設的,如果把typeFilterLevel設為Low,Remoting之會反序列化Remoting基本功能相關的對象。而設為Full則意味着Remoting會反序列化所有類型。如果你想知道那些類型是在Low Level下被限制,請參考http://msdn2.microsoft.com/en-us/library/5dxse167.aspx。
之是以要把typeFilterLevel為Full,是因為我們的遠端調用裡包含一Callback對象,它實際上是一個繼承System.MarshalByRefObject類對象(這個的對象将在Artech.DuplexRemoting.Client中定義)。而這個對象是不會再Low Level下被自動反序列化。
<channels>
<channel ref="http" port="8080">
<serverProviders>
<provider ref="wsdl" />
<formatter ref="binary" typeFilterLevel="Full" />
</serverProviders>
<clientProviders>
<formatter ref="binary" />
</clientProviders>
</channel>
</channels>
public interface IDuplexCalculator
{
void Add(double x, double y, ICalculatorCallback callback);
}
Step 4 在PRINCE.Client定義Callback對象和調用遠端對象
CalculatorCallbackHandler.pas
namespace PRINCE.Client;
interface
uses
System.Collections.Generic,
System.Linq,
System.Text,
PRINCE.Contract;
type
CalculatorCallbackHandler = public class(MarshalByRefObject, ICalculatorCallback)
private
protected
public
procedure ShowResult(x,y,resulted:Double );
end;
implementation
procedure CalculatorCallbackHandler.ShowResult(x, y, resulted: Double);
begin
Console.WriteLine('x + y = ' + resulted.ToString() + ' where x = ' + x.ToString() + ' and y = ' + y.ToString());
end;
end.
App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.remoting>
<application>
<channels>
<channel ref="http" port="0">
<clientProviders>
<formatter ref="binary" />
</clientProviders>
<serverProviders>
<formatter ref="binary" typeFilterLevel="Full" />
</serverProviders>
</channel>
</channels>
</application>
</system.runtime.remoting>
</configuration>
Program.pas
namespace PRINCE.Client;
interface
uses
System.Linq,
PRINCE.Contract;
type
ConsoleApp = class
private
class method InvocateDuplexCalculator(remoteAddress:String);
public
class method Main;
end;
implementation
class method ConsoleApp.Main;
begin
System.Runtime.Remoting.RemotingConfiguration.Configure(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile, false);
InvocateDuplexCalculator("http://localhost:8080/Calculator/DuplexCalculator.soap");
end;
class method ConsoleApp.InvocateDuplexCalculator(remoteAddress: String);
var
proxy:IDuplexCalculator;
begin
proxy := IDuplexCalculator(Activator.GetObject(typeof(IDuplexCalculator), remoteAddress));
proxy.Add(1,2,new CalculatorCallbackHandler());
Console.Read();
end;
end.
這裡有兩點需特别注意的:
1. 由于Server端時跨Application Domain遠端地調用運作Client Application Domain中的Callback對象(Callback的執行實際是在Client而不在Server),是以Callback對象應該是一個MarshalByRefObject對象。
2. 上面我們以經提及,對于Server端了來說Callback對象實際上是一個遠端對象(在Callback過程中Client端轉變成Server端,而Server端轉變成Client端)。Server端需要注冊一些Channel用于Client通路寄宿在Server端的遠端對象,同理,Server需要Callback一個寄宿在Client端Application Domain中的Callback對象,Client端需要注冊相應的Channel
3. 和Server端一樣,我們必須設定typeFilterLevel為Full。
到現在為止我們已經完成了所有的Program,我們來運作一下。