Watch, Follow, &
Connect with Us

Jim Tierney

CodeRage 4 Samples

I’ve uploaded the sample DataSnap projects used in my CodeRage 4 sessions.  

Samples from "DataSnap Tooling" (Wednesday, September 9 at 7 pm PDT) are available here: http://cc.embarcadero.com/Item/27221.

Projects Descriptions Screen shots
EchoStringServer Simple console style DataSnap server with TCP/IP and HTTP support. EchoStringServer.jpg
DelphiEchoStringClient Simple client to call the "EchoString" DataSnap server method. May be used with EchoStringServer or with your own server created with one of the DataSnap server wizards.. DelphiEchoStringClient.jpg
SampleProxyGenerator Standalone VCL application to generate a Datasnap client proxy in Delphi or C++.  Generates code just like the proxy generator integrated with TSQLConnection. SampleProxyGenerator.jpg
PrismEchoStringClient Simple client to call the "EchoString" DataSnap server method. Works like DelphiEchoStringClient except using DBX/ADO.NET rather than native DBX. PrismEchoStringClient.jpg
SamplePrismProxyGenerator Standalone Winform application to generate a DataSnap client proxy in Prism.  Generates code just like the proxy generator integrated with the Prism Server Explorer. SamplePrismProxyGenerator.jpg

 

Samples from "DataSnap Server Method Table Parameters" (Thursday, September 10 at 6 pm) are available here: http://cc.embarcadero.com/Item/27222.

Projects Descriptions Screen shots
DataSnapTablesSampleServer DataSnap server with server methods declared with different types and directions of table parameters (TDataSet, TDBXReader, TParams, out, var, return and in). DataSnapTablesSampleServer.jpg
DataSnapTablesListViewClient DataSnap client that can call a server method to get a table then display the table in a ListView.   The server method must pass a table back to the client using var or out parameters, or result.  Use with DataSnapTablesSampleServer. DataSnapTablesListViewClient.jpg
DataSnapTablesLoggingServer DataSnap server that logs activity during a server method call (e.g.; enter server method, read table, exit server method).

DataSnapTablesLoggingServer.jpg
DataSnapTablesLoggingClient DataSnap client that logs activity during a server method call (e.g.; call server method, read table, return from server method call). Use with DataSnapTablesLoggingServer. DataSnapTablesLoggingClient.jpg
DataSnapTablesCDSClient DataSnap client with components to populate a TClientDataSet from a server method result table, and display in a grid. Components include TSQLConnection, TSQLServerMethod, TDataSetProvider, TClientDataSet, TDataSource and TDBGrid.

DataSnapTablesCDSClient.jpg
DataSnapTablesProxyClient DataSnap client that calls some of the methods in DataSnapTablesLoggingServer using a client proxy class generated using the TSQLConnection "Generate DataSnap client classes" command.

DataSnapTablesProxyClient.jpg
Posted by Jim Tierney on September 8th, 2009 under DataSnap, Delphi, Delphi Prism, Oxygene | 3 Comments »


DataSnap 2010 HTTP support with an ISAPI dll

With DataSnap in Delphi 2010, you can develop DataSnap clients and server that communicate over HTTP.  One way to implement the server is as an ISAPI dll.  I’ve been given permission to show this feature pre-release.  See  http://www.embarcadero.com/rad-studio-2010 for for more information about the upcoming release.

Click these links to skip ahead Configure IIS, Connect with Data Explorer, Ancestors and DataModules, ISAPI debugging

In the File/New… dialog, double click “DataSnap WebBroker Application”

 

Check “ISAPI/NSAPI Dynamic link Library”,  “Add Server Methods Class” and “Include sample methods”.

Select TPersistent as the ancestor class for the server methods.  I will say more about the ancestor choices later in this post.

Click OK to create a new project.

 

The new project has two units.  ServerMethodsUnit implements the server methods for this DataSnap server.  There is a single server method called EchoString with a string parameter (see my previous posts to learn more about server method parameter types).

The WebModule contains the components that make this ISAPI dll function as a DataSnap 2010 HTTP server. 

 

DSServer1 and DSServerClass are the same components used in stand alone TCP/IP DataSnap servers.   DSHTTPWebDispatcher is new to  DataSnap 2010.  It is responsible for managing ISAPI/WebBroker HTTP requests and responses on behalf of the DataSnap Server. 

Both DSHTTPWebDispatcher1 and DSServerClass1 components have their “Server” property set to “DSServer1”.  The DSServerClass1 component calls an event handler to get the type of the server methods class:

The wizard has generated a complete ISAPI application so no changes are required to the project source code, however I do need to configure IIS.  

Configure IIS

Here is how I configure IIS 6.0 on my Vista x32 system.

If you haven’t enabled IIS on your system then use “Programs and features”/”Turn Windows features on or off” to install.

Start Internet Information Services (IIS) Manager.

Expand Connections nodes, right click on “Default Web Site”, choose “Add Virtual Directory”.

Enter an alias of “DSProject” an a physical path of "c:\DSProject".

ISAPI must be enabled for this virtual directory.  Select the “DSProjects” Connections node.  Double click the “Handler Mappings” icon.  Click Actions/“Edit Handler Permissions…”.  Check “Execute”.

Directory browsing is convenient but not required.  Enable it for this example.  Select “DSProjects” Connections node.  Double click the “Directory Browsing” icon.  Click Actions/”Enable”.

Almost done configuring IIS.  Click on the root Connection node.  Double click the “ISAPI and CGI Restrictions” icon.  Click Actions/”Edit Feature Setting…”.  I have “Allow unspecified ISAPI modules” checked.  If you don’t check this then you will need to add to the list of ISAPI dlls recognized by IIS, after you have built your project. 

Now go back to Delphi 2010 and set the output directory to the virtual directory physical path c:\DSProject. 

Build the project.

Browse to http://localhost/DSProject and, if you have enabled directory browsing, you should see the ISAPI dll.  Click it.  You should see a page like this:

The following a wizard generated line of code provide the default content:

 

At this point it looks like our ISAPI dll is properly configured.  Now I can use the Data Explorer to test connectivity.

Connect with Data Explorer

I prefer to use the stand alone DataExplore.exe when testing connections rather than the RAD studio Data Explorer view, just in case there is a connection problem that causes the client to become unresponsive. 

Start DataExplorer.exe.  Right click the DataSnap node, choose add connection.  Enter “NewConnection”.  Click OK.

Expand the DataSnap node, right click “New Connection” and select “Modify Connection”.

Set Protocol to “http”, Host to “localhost”, Port to “80” and Path to “DsProject/Project1.dll”.

Click “Test Connection”.   Click OK. Click OK. 

 

Expand the NewConnection/Procedures node to see that EchoString is a server method.

To test, right click on the “EchoString” node and choose "View Parameters". Select the "Value" parameter and enter some text for the "Value" property. Right click and choose "Execute" (or click the the  image button in the upper left of the view parameters window).  

At this point I’ve created a server, configured it to run under IIS, and connected to it with DataExplorer.   The new DataSnap 2010 HTTP client features will need to be covered in another post.

I have two additional topics to cover.  First is this ancestor issue I mentioned near the beginning of this post.  Finally, I will add a few words about debugging.

Ancestors and DataModules

This example used TPersistent as the ancestor of our server method class.   The other choices, in the wizard, are TDataModule and TDSServerModule.  Use TDataModule when you want to use components in your server methods.  Use TDSServerModule when you want the equivalent of TRemoteDataModule.

When using TDataModule or TDSServerModule, there is an important requirement that only affects WebBroker applications.  Be sure that the there is only one module created during startup.  Use Project/View Source… to see this code.  The only module created here should be the WebModule. 

The project source should look like this:

Something like this will result in an exception with the message “Only one data module per application”.

ISAPI Debugging

In case your ISAPI dll isn’t working properly because of this or other issues, here is how I configure my project for debugging.

In project options, set Host application to the location of w3wp.exe and the Parameters to “-debug”.

You will need to stop IIS before debugging.  Use the IIS manager or [c:/]net stop w3svc.

When you run the application, you should see a prompt like this:

When you stop debugging, w3wp.exe will be shut down.   When you are done working on your project, restore IIS with [c:/]net start w3svc.

Posted by Jim Tierney on August 20th, 2009 under Data Explorer, DataSnap, Delphi, WebBroker | 8 Comments »


Delphi Prism and DataSnap Server Method Stream Parameters

This post is about using Delphi Prism to pass streams to/from DataSnap server methods.  The sample client that goes along with this post is available here: http://cc.embarcadero.com/item/26874.

This client has the same functionality as the Delphi client described in this post: DataSnap Server Method Stream Parameters.  Here is a screen shot of the two clients running against the same Delphi sample server.

Download the Delphi sample server (and Delphi sample client) here: http://cc.codegear.com/item/26854.   For introductory information about the difference between Delphi and Delphi Prism DataSnap clients see this post: Call DataSnap Server Methods with Delphi Prism.

Stream Parameter Types

This table shows the parameter types and return types in the stream samples. The columns under “Server Parameter” describe the parameter or return type in the Delphi DataSnap servers methods. The “Delphi Type” column shows the corresponding type used in the Delphi client. The “Delphi Prism Type” column shows type used in the Delphi Prism client.

Server Parameter Client Parameters
Type (default) var out Result Delphi Type Delphi Prism Type
TStream

(in)

TStream System.IO.Stream
TDBXStreamValue

(in/out)

 

 

 

TStream System.IO.Stream

Code Samples

The following code samples show the differences between calling server methods with DbExpress in Delphi compared to ADO.NET in Delphi Prism.

TStream Server Method
function TTestStream.Echo(I: TStream): TStream;
Delphi/DbExpress
function TTestStreamClient.Echo(I: TStream): TStream;
begin
  if FEchoCommand = nil then
  begin
    FEchoCommand := FDBXConnection.CreateCommand;
    FEchoCommand.CommandType := TDBXCommandTypes.DSServerMethod;
    FEchoCommand.Text := 'TTestStream.Echo';
    FEchoCommand.Prepare;
  end;
  FEchoCommand.Parameters[0].Value.SetStream(I, FInstanceOwner);
  FEchoCommand.ExecuteUpdate;
  Result := FEchoCommand.Parameters[1].Value.GetStream(FInstanceOwner);
end;
Delphi Prism/ADO.NET
function TTestStreamClient.Echo(I: System.IO.Stream): System.IO.Stream;
begin
  if FEchoCommand = nil then
  begin
    FEchoCommand := FDBXConnection.CreateCommand as TAdoDbxCommand;
    FEchoCommand.CommandType := System.Data.CommandType.StoredProcedure;
    FEchoCommand.CommandText := 'TTestStream.Echo';
    FEchoCommand.Prepare;
  end;
  FEchoCommand.Parameters[0].Value := I;
  FEchoCommand.ExecuteNonQuery;
  Result := FEchoCommand.Parameters[1].Value as System.IO.Stream;
end;

TDBXStreamValue Server Method
procedure TTestStreamValue.Swap(I: TDBXStreamValue; J: TDBXStreamValue);
Delphi/DbExpress
procedure TTestStreamValueClient.Swap(var I: TStream; var J: TStream);
begin
  if FSwapCommand = nil then
  begin
    FSwapCommand := FDBXConnection.CreateCommand;
    FSwapCommand.CommandType := TDBXCommandTypes.DSServerMethod;
    FSwapCommand.Text := 'TTestStreamValue.Swap';
    FSwapCommand.Prepare;
  end;
  if I = nil then
    FSwapCommand.Parameters[0].Value.SetNull
  else
    FSwapCommand.Parameters[0].Value.SetStream(I, FInstanceOwner);
  if J = nil then
    FSwapCommand.Parameters[1].Value.SetNull
  else
    FSwapCommand.Parameters[1].Value.SetStream(J,  FInstanceOwner);
  FSwapCommand.ExecuteUpdate;
  if FSwapCommand.Parameters[0].Value.IsNull then
    I := nil
  else
    I := FSwapCommand.Parameters[0].Value.GetStream(FInstanceOwner);
  if FSwapCommand.Parameters[1].Value.IsNull then
    J := nil
  else
    J := FSwapCommand.Parameters[1].Value.GetStream(FInstanceOwner);
end;
Delphi Prism/ADO.NET
procedure TTestStreamValueClient.Swap(var I: System.IO.Stream; var J: System.IO.Stream);
begin
  if FSwapCommand = nil then
  begin
    FSwapCommand := FDBXConnection.CreateCommand as TAdoDbxCommand;
    FSwapCommand.CommandType := System.Data.CommandType.StoredProcedure;
    FSwapCommand.CommandText := 'TTestStreamValue.Swap';
    FSwapCommand.Prepare;
  end;
  if I = nil then
    FSwapCommand.Parameters[0].Value := DbNull.Value
  else
    FSwapCommand.Parameters[0].Value := I;
  if J = nil then
    FSwapCommand.Parameters[1].Value := DbNull.Value
  else
    FSwapCommand.Parameters[1].Value := J;
  FSwapCommand.ExecuteNonQuery;
  if FSwapCommand.Parameters[0].Value = DBNull.Value then
    I := nil
  else
    I := FSwapCommand.Parameters[0].Value as System.IO.Stream;
  if FSwapCommand.Parameters[1].Value = DBNull.Value then
    J := nil
  else
    J := FSwapCommand.Parameters[1].Value as System.IO.Stream;
end;

That’s all for now.  A post on table parameter types is coming soon. 

Posted by Jim Tierney on April 25th, 2009 under DataSnap, Delphi Prism, Oxygene | 3 Comments »


DataSnap Server Method Stream Parameters

This is a continuation of my posts on DataSnap server method parameters and return types.  This post is about  TStream and TDBXStreamValue.    The sample client and server projects that go with this post can be downloaded here: http://cc.embarcadero.com/item/26854

See my earlier posts on “Basic” and “Basic DBXValue” types.   I’ve yet to post about TDBXConnection, TDBXConnectionValue, TDBXReader, TParams, and TDataSet.  However, TDataSet is demonstrated in the sample projects.

Basic Basic DBXValue Collection Connection
  • AnsiString
  • Boolean
  • Currency
  • TDateTime
  • TDBXDate
  • TDBXTime
  • Double
  • Int64
  • Integer
  • LongInt
  • OleVariant
  • Single
  • SmallInt
  • WideString
  • TDBXAnsiStringValue
  • TDBXAnsiCharsValue
  • TDBXBcdValue
  • TDBXBooleanValue
  • TDBXDateValue
  • TDBXDoubleValue
  • TDBXInt16Value
  • TDBXInt32Value
  • TDBXInt64Value
  • TDBXSingleValue
  • TDBXStringValue
  • TDBXTimeStampValue
  • TDBXTimeValue
  • TDBXWideCharsValue
  • TDBXWideStringValue
  • TDBXReader
  • TDataSet
  • TParams
  • TStream
Collection DBXValue
  • TDBXReaderValue
  • TDBXStreamValue
  • TDBXConnection
Connection DBXValue
  • TDBXConnectionValue

The following table summarizes differences between TStream and TDBXStreamValue types:

  Supports null values Declaration Accessing Values Proxy generator
Default parameter direction Other parameter directions function result type Get Set
TStream No

in

in/out: use var keyword out: use out keyword

Yes

lhs := AParameter AParameter := rhs

Yes

TDBXStreamValue

Yes

in/out

None

No

AParameter.IsNull lhs:=AParameter.GetStream(InstanceOwner) AParameter.SetNull AParameter.SetStream(rhs, InstanceOwner)

No

Supports null values

The TDBXStreamValue type has an IsNull property and a SetNull method.   Use this type instead of TStream a parameter value can be null/nil, in some cases.

Declaration

The var and out keywords can’t be used to specify the parameter direction of a TDBXStreamValue parameter.  The direction is always in/out. A TDBXStreamValue type can’t be used as a function result.

Proxy Generator

The RAD Studio 2007 client proxy generator does not work properly for server methods with TDBXStreamValue parameters. So you will need to hand code the client code or correct the generated proxy. The sample client has hand corrected proxy methods that look like this.

function TTestStreamValueClient.Echo(var I: TStream): TStream;
begin
  if FEchoCommand = nil then
  begin
    FEchoCommand := FDBXConnection.CreateCommand;
    FEchoCommand.CommandType := TDBXCommandTypes.DSServerMethod;
    FEchoCommand.Text := 'TTestStreamValue.Echo';
    FEchoCommand.Prepare;
  end;
  if I = nil then
    FEchoCommand.Parameters[0].Value.SetNull
  else
    FEchoCommand.Parameters[0].Value.SetStream(I, FInstanceOwner);
  FEchoCommand.ExecuteUpdate;
  if FEchoCommand.Parameters[0].Value.IsNull then
    I := nil
  else
    I := FEchoCommand.Parameters[0].Value.GetStream(FInstanceOwner);
  Result := FEchoCommand.Parameters[1].Value.GetStream(FInstanceOwner);
end;

This client method works with a server method declared as follows:

function Echo(I: TDBXStreamValue): TStream;

Calling a server method with a TDBXStreamValue parameter is the same as calling a server method with an TStream parameter, except that you can use SetNull and IsNull methods to work with null values.

For example, compare the following server method declaration and client method implementation to the previous example:

function Echo(I: TStream): TStream; 
function TTestStreamClient.Echo(I: TStream): TStream;
begin
  if FEchoCommand = nil then
  begin
    FEchoCommand := FDBXConnection.CreateCommand;
    FEchoCommand.CommandType := TDBXCommandTypes.DSServerMethod;
    FEchoCommand.Text := 'TTestStream.Echo';
    FEchoCommand.Prepare;
  end;
  FEchoCommand.Parameters[0].Value.SetStream(I, FInstanceOwner);
  FEchoCommand.ExecuteUpdate;
  Result := FEchoCommand.Parameters[1].Value.GetStream(FInstanceOwner);
end;

Accessing Values

The GetStream and SetStream methods have an InstanceOwner parameter.  Passing True indicates that DBX owns the stream and will free it.  Passing False indicates that the caller owns the stream.  To control how the generated proxy classes call SetStream and GetStream, there is an AInstanceOwner parameter on the proxy class constructor:

constructor TTestStreamClient.Create(ADBXConnection: TDBXConnection; AInstanceOwner: Boolean);

The other constructor is equivalent to passing AInstanceOwner as True.

constructor TTestStreamClient.Create(ADBXConnection: TDBXConnection);

 

Sample Client And Server Applications

The sample server has a few simple server methods for passing values (including null).

  {$METHODINFO ON}
  TTestCollection = class(TComponent)
  strict protected
    procedure LogMessage(const AMessage: string);
  public
    // Server method to get the parameter type names
    procedure GetTypeNames(I: T; ADeclaredName, AActualName: TDBXStringValue);
  end;
  {$METHODINFO OFF}

  TTestStream = class(TTestCollection)
  public
    function Echo(I: TStream): TStream;
    procedure Copy(I: TStream; out J: TStream);
    procedure Swap(var I: TStream; var J: TStream);
  end;

  TTestStreamValue = class(TTestCollection)
  public
    function Echo(I: TDBXStreamValue): TStream;
    procedure Copy(I: TDBXStreamValue; J: TDBXStreamValue);
    procedure Swap(I: TDBXStreamValue; J: TDBXStreamValue);
    function IsNull(I: TDBXStreamValue): Boolean;
    procedure SetNull(I: TDBXStreamValue);
  end;

The sample client tests the server methods by calling them with sample values and verifying the results (e.g; comparing streams). TMemoryStream is used to pass short streams and TFileStream to pass long streams.  Here is a screen shot of the running server and client:

The following table shows the parameter types and return types demonstrated in the sample client and server:

Type Direction
(default) var out Result
TDBXStreamValue X (in/out)      
TStream X (in) X X X

 

Stream Usage

Check  “Log Stream Usage” to show TStream sizes, type names, creates, destroys, and reads.  The results from 4 different test cases are displayed after “Call Server Methods” is clicked. 

The screen shot below shows the result from a test case with longer streams.  I’ve added highlighting to illustrate these points:

  1. When the server reads a longer stream passed from the client, round trips are made to the client.  This makes it possible to send very large streams to the server without consuming lots of memory.
  2. When the client reads a longer stream passed from the server, round trips are made to the server.  This makes it possible to return very large streams to the client without consuming lots of memory.
  3. The TStream objects that represent longer client and server streams have a Size of –1, meaning the size is unknown.  Rely on the return value of TStream.Read to detect the end of the stream.  Also note that some DBX streams such as TDBXStreamReaderStream do not fully support Seek.

That’s all for now.

Posted by Jim Tierney on April 6th, 2009 under DataSnap, Delphi | Comment now »


Call DataSnap Server Methods with Delphi Prism

This post provides information about calling DataSnap server methods from Delphi Prism clients.

There are two new samples that go along with this post: http://cc.embarcadero.com/item/26806 and http://cc.embarcadero.com/item/26805

The first sample is a Delphi Prism client that demonstrates "Basic" types.  This client has nearly the same functionality as the Delphi client described in this post: DataSnap Server Methods Parameters.  The difference is that the Delphi Prism client isn’t able to call server methods with OleVariant parameter types.  The Delphi client is shown on the left and the Delphi Prism client on the right:

The second sample is a Delphi Prism client that demonstrates "Basic DBXValue" types.  This client has the same functionality as the Delphi client described in this post: DataSnap Server Method DBXValue Parameters.  The Delphi client is shown on the left and the Delphi Prism client on the right:

The Delphi Prism clients use the same sample servers as the Delphi clients.  Download the Delphi sample servers (and Delphi sample clients) here: http://cc.codegear.com/item/26702, http://cc.embarcadero.com/item/26734.

The following table summarizes the major differences between the Delphi and Delphi Prism sample clients:

Clients Connectivity Proxy Generator UI Framework Parameter Types Language/RTL
Framework Types
Delphi DbExpress

TDBXConnection, TDBXCommand

Yes

VCL

Delphi

Delphi/Delphi
Delphi Prism ADO.NET

TAdoDbxConnection, TAdoDbxCommand

No

WinForms

Oxygene/.NET

Oxygene/.NET

Connectivity

The Delphi Prism client uses the DataSnap ADO.NET provider rather than native DbExpress.  Here are some code samples to show differences between calling DataSnap server methods with DbExpress in Delphi vs. ADO.NET in Delphi Prism.

Delphi
  TTestIntegerClient = class
  private
    FDBXConnection: TDBXConnection;
    FInstanceOwner: Boolean;
    FEchoCommand: TDBXCommand;
    FCopyCommand: TDBXCommand;
    FSwapCommand: TDBXCommand;
  public
    constructor Create(ADBXConnection: TDBXConnection); overload;
    constructor Create(ADBXConnection: TDBXConnection; AInstanceOwner: Boolean); overload;
    destructor Destroy; override;
    function Echo(I: Integer): Integer;
    procedure Copy(I: Integer; out J: Integer);
    procedure Swap(var I: Integer; var J: Integer);
  end;
Delphi Prism
  TTestIntegerClient = class
  private
    FDBXConnection: TAdoDbxConnection;
    FInstanceOwner: Boolean;
    FEchoCommand: TAdoDbxCommand;
    FCopyCommand: TAdoDbxCommand;
    FSwapCommand: TAdoDbxCommand;
  protected
    procedure Dispose(ADisposing: Boolean); virtual;
  public
    constructor Create(ADBXConnection: TAdoDbxConnection);
    constructor Create(ADBXConnection: TAdoDbxConnection; AInstanceOwner: Boolean);
    procedure Dispose; virtual;
    function Echo(I: Integer): Integer;
    procedure Copy(I: Integer; out J: Integer);
    procedure Swap(var I: Integer; var J: Integer);
  end;

Delphi
procedure TTestIntegerClient.Swap(var I: Integer; var J: Integer);
begin
  if FSwapCommand = nil then
  begin
    FSwapCommand := FDBXConnection.CreateCommand;
    FSwapCommand.CommandType := TDBXCommandTypes.DSServerMethod;
    FSwapCommand.Text := 'TTestInteger.Swap';
    FSwapCommand.Prepare;
  end;
  FSwapCommand.Parameters[0].Value.SetInt32(I);
  FSwapCommand.Parameters[1].Value.SetInt32(J);
  FSwapCommand.ExecuteUpdate;
  I := FSwapCommand.Parameters[0].Value.GetInt32;
  J := FSwapCommand.Parameters[1].Value.GetInt32;
end;
Delphi Prism
procedure TTestIntegerClient.Swap(var I: Integer; var J: Integer);
begin
  if FSwapCommand = nil then
  begin
    FSwapCommand := FDBXConnection.CreateCommand as TAdoDbxCommand;
    FSwapCommand.CommandType := System.Data.CommandType.StoredProcedure;
    FSwapCommand.CommandText := 'TTestInteger.Swap';
    FSwapCommand.Prepare;
  end;
  FSwapCommand.Parameters[0].Value := I;
  FSwapCommand.Parameters[1].Value := J;
  FSwapCommand.ExecuteNonQuery;
  I := FSwapCommand.Parameters[0].Value as Integer;
  J := FSwapCommand.Parameters[1].Value as Integer;
end;

Proxy Generator

Delphi Prism does not have a DataSnap client proxy generator like the one in Delphi.  The preceding Delphi code sample was generated with the proxy generator; the Delphi Prism code sample was hand written.

UI Framework

The Delphi Prism sample clients are WinForm applications.  DataSnap clients may be implemented in other application types supported by Delphi Prism, such as ASP.NET. 

Parameter Types

The following table shows the parameter types and return types demonstrated in the “Basic” types samples.   The columns under “Server Parameter” describe the parameter or return type in the Delphi DataSnap server’s methods.  The “Delphi Type” column shows the corresponding type used in the Delphi client.  The “Delphi Prism Type” show the corresponding type used in the Delphi Prism client.

Server Parameter Client Parameters
Type (in) var out Result Delphi Type Delphi Prism Type
AnsiString X     X AnsiString String
Boolean X X X X Boolean Boolean
Currency X X X X Currency Decimal
TDateTime X X X X TDateTime DateTime
TDBXDate X X X X TDBXDate DateTime
TDBXTime X X X X TDBXTime DateTime
Double X X X X Double Double
Int64 X X X X Int64 Int64
Integer X X X X Integer Integer
LongInt X X X X LongInt LongInt
OleVariant X X X   OleVariant <Not supported>
Single X X X X Single Single
SmallInt X X X X SmallInt SmallInt
String X     X String String
WideString X     X WideString String

The following table shows the parameter types and return types demonstrated in the “Basic DBXValue” types samples:

Server Parameter Client Parameters
Type (in/out) var out Result Delphi Type Delphi Prism Type
TDBXAnsiCharsValue X       AnsiString string
TDBXAnsiStringValue X       AnsiString string
TDBXBcdValue X       TBcd Decimal
TDBXBooleanValue X       Boolean Boolean
TDBXDateValue X       TDBXDate DateTime
TDBXDoubleValue X       Double Double
TDBXInt16Value X       Int16 Int16
TDBXInt32Value X       Int32 Int32
TDBXInt64Value X       Int64 Int64
TDBXSingleValue X       Single Single
TDBXStringValue X       String String
TDBXTimeValue X       TDBXTime DateTime
TDBXTimeStampValue X       TSQLTimeStamp DateTime
TDBXWideCharsValue X       UnicodeString string
TDBXWideStringValue X       UnicodeString string

Language Compatability

Syntax

Delphi Prism projects uses the Oxygene language.

In these samples, there are a few syntax differences between Oxygene .pas files and Delphi .pas files including  keyword (e.g.; unit vs. namespace, var and out in method calls) and a delimiter (e.g.; Oxygene requires  ‘;’ and ‘@’ in places that Delphi doesn’t).  Diff the like named .pas files for details.

Delphi Prism has some project options that enable the Oxygene compiler to accept more Delphi language conventions.  Choose “Project/Project properties…”  The one I’ve checked for these samples is “Allow ‘Create’ constructor calls’.  This option enables compilation of the following line:

LTest := TTestBooleanClient.Create(DBXConnection)

Otherwise, the code would need new instead of Create:

LTest := new TTestBooleanClient.(DBXConnection).

Destroy vs. Dispose

In the Delphi Prism samples, Dispose is called in place of Destroy in the Delphi samples.   Calling Dispose doesn’t free the object but tells the object to release unmanaged resources (like a tcp/ip connection). 

SysUtils.Format vs. String.Format

In the Delphi Prism samples, String.Format is called in place of Format in the Delphi samples:

LogTestMessage(string.Format(’{0:s} {1:s}’, [FClassName + '.' + LMethodName, LDeclaredType]));

in place of

LogTestMessage(Format(’%0:s (%1:s)’, [FClassName + '.' + LMethodName, LDeclaredType]));

 

That’s all for now.  I (continue to) plan on covering collection types in my next post.

Posted by Jim Tierney on March 25th, 2009 under DataSnap, Delphi Prism, Oxygene | 3 Comments »


DataSnap Server Method DBXValue Parameters

This post provides more information about DataSnap server method parameters and return types. This is my second post on this subject, following DataSnap Server Method Parameters. The previous post was about "Basic" types. This post is about "Basic DBXValue" types.  The sample client and client and server projects that go with this post can be downloaded here: http://cc.embarcadero.com/item/26734

Basic Basic DBXValue Collection Connection
  • AnsiString
  • Boolean
  • Currency
  • TDateTime
  • TDBXDate
  • TDBXTime
  • Double
  • Int64
  • Integer
  • LongInt
  • OleVariant
  • Single
  • SmallInt
  • WideString
  • TDBXAnsiStringValue
  • TDBXAnsiCharsValue
  • TDBXBcdValue
  • TDBXBooleanValue
  • TDBXDateValue
  • TDBXDoubleValue
  • TDBXInt16Value
  • TDBXInt32Value
  • TDBXInt64Value
  • TDBXSingleValue
  • TDBXStringValue
  • TDBXTimeStampValue
  • TDBXTimeValue
  • TDBXWideCharsValue
  • TDBXWideStringValue
  • TDBXReader
  • TDataSet
  • TParams
  • TStream
Collection DBXValue
  • TDBXReaderValue
  • TDBXStreamValue
  • TDBXConnection
Connection DBXValue
  • TDBXConnectionValue

The following table summarizes differences between "Basic" and "Basic DBXValue" types:

  Supports null values Declaration Accessing Values Proxy generator
Default parameter direction Other parameter directions function result type Get Set
Basic No

in

in/out: use var keyword
out: use out keyword

Yes

lhs := AParameter AParameter := rhs

Yes

DBXValue

Yes

in/out

None

No

AParameter.IsNull
lhs:=AParameter.GetInt32,
lhs:=AParameter.GetString, etc.
AParameter.SetNull
AParameter.SetInt32(rhs),
AParameter.SetString(rhs), etc.

No

 

Supports null values

Support for null values is needed, for example, when a server method needs to pass values that come from a database column that allows NULL.    All DBXValue types have an IsNull property and a SetNull method.

Declaration

The var and out keywords can’t be used to specify the parameter direction of a DBXValue parameter.  The direction is always in/out.   A DBXValue type can’t be used as a function result.

Proxy Generator

The RAD Studio 2007 client proxy generator does not work properly for server methods with DBXValue parameters.  So you will need to hand code.   The sample client has code like this to call a server method:

// Non-generic version of TestSetNull
procedure TCallTestMethods.TestSetNullInt32(AConnection: TDBXConnection; I: Int32);
const
  LMethodName = 'TTestDBXInt32Value.SetNull';
var
  LIsNull: Boolean;
  LDBXCommand: TDBXCommand;
begin
  LDBXCommand := AConnection.CreateCommand;
  try
    try
      LDBXCommand.CommandType := TDBXCommandTypes.DSServerMethod;
      LDBXCommand.Text := LMethodName;
      LDBXCommand.Prepare;
      LDBXCommand.Parameters[0].Value.SetInt32(I);
      LDBXCommand.ExecuteUpdate;
      LIsNull := LDBXCommand.Parameters[0].Value.IsNull;
      LogTestResult(LMethodName, LIsNull);
    except
      on E: Exception do
      LogTestException(LMethodName, E);
    end;
  finally
    LDBXCommand.Free;
  end;
end;

Calling a server method with a TDBXInt32Value parameter is the same as calling a server method with an Int32 parameter, except that you can use SetNull and IsNull methods to work with null values.  

Accessing Values

The following table shows the methods that are used to access a non-null value from the different DBXValue types   A mismatched call, such as GetBcd on TDBXStringValue, will raise an exception. 

Get method Set method Type Use with
GetAnsiString SetAnsiString AnsiString TDBXAnsiCharsValue, TDBXAnsiStringValue
GetBcd SetBcd TBcd TDBXBcdValue
GetBoolean SetBoolean Boolean TDBXBooleanValue
GetDate SetDate TDBXDate TDBXDateValue
GetDouble SetDouble Double TDBXDoubleValue
GetInt16 SetInt16 Int16 TDBXInt16Value
GetInt32 SetInt32 Int32 TDBXInt32Value
GetInt64 SetInt64 Int64 TDBXInt64Value
GetSingle SetSingle Single TDBXSingleValue
SetString GetString string TDBXStringValue, TDBXWideCharsValue, TDBXWideStringValue
GetTime SetTime TDBXTime TDBXTimeValue
GetTimeStamp SetTimeStamp TSQLTimeStamp TDBXTimeStampValue
GetWideString SetWideString UnicodeString (same as string) TDBXStringValue, TDBXWideCharsValue, TDBXWideStringValue

 

String Value Types

Of the five DBXValue types that represent strings, you need only use TDBXAnsCharsValue to pass AnsiString values and TDBXWideCharsValue to pass UnicodeString/string values. The following table shows that these two types are always passed to server methods even if the method is declared with one of the other string types.

Declared Type Actual Type
TDBXAnsiCharsValue TDBXAnsiCharsValue
TDBXAnsiStringValue TDBXAnsiCharsValue
TDBXStringValue TDBXWideCharsValue
TDBXWideCharsValue TDBXWideCharsValue
TDBXWideStringValue TDBXWideCharsValue

Sample Client And Server Applications

The sample server has a few simple server methods for passing values (including null), and two informational server methods.  In order to support various DBXValue types, I’ve implemented a generic server method base class:

{$METHODINFO ON}
  TTestBasicDBXValueType = class(TComponent)
  strict protected
    function EqualValues(I: TValue; J: TValue): Boolean; virtual;
    function GetValue(I: T): TValue; virtual; abstract;
    procedure SetValue(I: T; Value: TValue); virtual; abstract;
  public
    // Server methods to pass values (including null)
    function IsEqual(I: T; J: T): Boolean;
    function Echo(I: T): TValue;
    procedure Swap(I: T; J: T);
    function IsNull(I: T): Boolean;
    procedure SetNull(I: T);
    // Server method to get the DBXValue parameter type names
    procedure GetTypeNames(I: T; ADeclaredName, AActualName: TDBXStringValue);
    // Server method to try DBXValue methods like GetInt32 and SetInt32
    function TryGetAndSet(I: T): TDataSet;
  end;
{$METHODINFO OFF}

Then I’ve used TTestBasicDBXValueType to declare and implement server methods classes for a variety of DBXValue types, such as TDBXInt32Value, TTestDBXDateValue , etc.

  TTestDBXInt32Value = class(TTestBasicDBXValueType)
  strict protected
    function GetValue(I: TDBXInt32Value): Int32; override;
    procedure SetValue(I: TDBXInt32Value; Value: Int32); override;
  end;

  TTestDBXDateValue = class(TTestBasicDBXValueType)
  strict protected
    function GetValue(I: TDBXDateValue): TDBXDate; override;
    procedure SetValue(I: TDBXDateValue; Value: TDBXDate); override;
  end;

Consult the sample server to see how I’ve implemented the "plumbing" to make these classes callable by a DataSnap client. As in the “Basic” types sample server, the implementation is unconventional because the TDSServerClass component is not used.

The sample client tests the generated methods by calling them with sample values and verifying the results. Consult the sample for implementation details. Here is screen shot of the running server and client:

The following table shows the parameter types and return types demonstrated in the sample client and server:

Type (in/out) var out Result
TDBXAnsiCharsValue X      
TDBXAnsiStringValue X      
TDBXBcdValue X      
TDBXBooleanValue X      
TDBXDateValue X      
TDBXDoubleValue X      
TDBXInt16Value X      
TDBXInt32Value X      
TDBXInt64Value X      
TDBXSingleValue X      
TDBXStringValue X      
TDBXTimeValue X      
TDBXTimeStampValue X      
TDBXWideCharsValue X      
TDBXWideStringValue X      

That’s all for now.  I plan on covering the collection types in my next post.

Posted by Jim Tierney on March 12th, 2009 under DataSnap, Delphi | 4 Comments »


DataSnap Server Methods Parameters

Delphi 2009 introduced support for DataSnap server methods. If you are not familiar with this feature, here are two articles that describe server methods: DataSnap 2009 Overview , Getting Started with Delphi DataSnap 2009.

DataSnap server methods support a variety of parameter and return types. The following list shows the types grouped into my own categories. 

This post is about "Basic" types.  The sample client and server projects that go with this post can be downloaded here: http://cc.codegear.com/item/26702

Basic DBXValue Collection Connection
  • AnsiString
  • Boolean
  • Currency
  • TDateTime
  • TDBXDate
  • TDBXTime
  • Double
  • Int64
  • Integer
  • LongInt
  • OleVariant
  • Single
  • SmallInt
  • WideString
  • TDBXAnsiStringValue
  • TDBXAnsiCharsValue
  • TDBXBcdValue
  • TDBXBooleanValue
  • TDBXConnectionValue
  • TDBXDateValue
  • TDBXDoubleValue
  • TDBXInt16Value
  • TDBXInt32Value
  • TDBXInt64Value
  • TDBXReaderValue
  • TDBXSingleValue
  • TDBXStreamValue
  • TDBXStringValue
  • TDBXTimeStampValue
  • TDBXTimeValue
  • TDBXWideCharsValue
  • TDBXWideStringValue
  • TDBXReader
  • TDataSet
  • TParams
  • TStream
  • TDBXConnection

Getting Started with Delphi DataSnap 2009 uses the following function as an example of a server method:

function TDSServerModule1.Echo(s: string): string;
begin
 Result := 'Delphi DataSnap 2009 is echoing ' + s + ' ....' + s;
end;

The proxy generator generates the following code to call this method:

function TDSServerModule1Client.Echo(s: string): string;
begin
  if FEchoCommand = nil then
   begin
     FEchoCommand := FDBXConnection.CreateCommand;
     FEchoCommand.CommandType := TDBXCommandTypes.DSServerMethod;
     FEchoCommand.Text := 'TDSServerModule1.Echo';
     FEchoCommand.Prepare;
   end;
   FEchoCommand.Parameters[0].Value.SetWideString(s);
   FEchoCommand.ExecuteUpdate;
   Result := FEchoCommand.Parameters[1].Value.GetWideString;
end;

I wanted my sample server and client to work like the sample in the article, with simple server methods and  generated proxy in the client.  In addition, I wanted to support various types so started with this generic server method implementation:

{$METHODINFO ON}
 TTestBasicType = class(TComponent)
     function Echo(I: T): T;
 end;
{$METHODINFO OFF}

 function TTestBasicType.Echo(I: T): T;
 begin
   Result := I;
 end;

The I used TTestBasicType used to declare and implement "Echo" methods for a variety of types, such as string, boolean, and double:

TTestString = class(TTestBasicType<string>))
end;

TTestBoolean = class(TTestBasicType<Boolean>)
end; 

TTestDouble = class(TTestBasicType<Double>))
end;

To test parameters directions, I expanded the generic class with var and out parmeters:

{$METHODINFO ON}
TTestBasicType = class(TComponent)
  function Echo(I: T): T;
  procedure Copy(I: T; out J: T);
  procedure Swap(var I: T; var J: T);
end;
{$METHODINFO OFF}

Consult the sample server to see how I’ve implemented the "plumbing" to make these classes callable by a DataSnap client. The implementation is unconventional because the TDSServerClass component is not used.

The sample VCL client is built starting with a TSQLConnection component. After setting the port and host name, I right clicked on the TSQLConnection and selected "Generate DataSnap client classes" to generate a client proxy:

In the generated code, there is a "Client" class for every one of the server classes. For example, TTestBooleanClient calls the TTestBoolean class on the server:

TTestBooleanClient = class
private
  FDBXConnection: TDBXConnection;
  FInstanceOwner: Boolean;
  FEchoCommand: TDBXCommand;
  FCopyCommand: TDBXCommand;
  FSwapCommand: TDBXCommand;
public
  constructor Create(ADBXConnection: TDBXConnection);overload;
  constructor Create(ADBXConnection: TDBXConnection; AInstanceOwner: Boolean); overload;
  destructor Destroy; override;
  function Echo(I: Boolean): Boolean;
  procedure Copy(I: Boolean; out J: Boolean);
  procedure Swap(var I: Boolean; var J: Boolean);
end;

Here is the implementation of TTestBooleanClient.Echo.  Consult the sample code to see the complete proxy implementation.  

function TTestBooleanClient.Echo(I: Boolean): Boolean;
begin
 if FEchoCommand = nil then
 begin FEchoCommand := FDBXConnection.CreateCommand;
   FEchoCommand.CommandType := TDBXCommandTypes.DSServerMethod;
   FEchoCommand.Text := 'TTestBoolean.Echo';
   FEchoCommand.Prepare;
 end; FEchoCommand.Parameters[0].Value.SetBoolean(I);
 FEchoCommand.ExecuteUpdate;
 Result := FEchoCommand.Parameters[1].Value.GetBoolean;
end;

The sample client tests the generated methods by calling them with sample values and verifying the results. Consult the sample for implementation details.

Here is screen shot of the running server and client:

 

The following table shows the parameter types and return types demonstrated in the sample client and server:

Type (in) var out Result
AnsiString X     X
Boolean X X X X
Currency X X X X
TDateTime X X X X
DBXDate X X X X
DBXTime X X X X
Double X X X X
Int64 X X X X
Integer X X X X
LongInt X X X X
OleVariant X X X  
Single X X X X
SmallInt X X X X
String X     X
WideString X     X

There are some Delphi types that you might expect to see in this list such as Byte and Cardinal. DataSnap currently doesn’t support these two nor LongWord, ShortInt, Word, TSQLTimeStamp, and TBcd. Support for var and out strings is coming (a workaround/alternative is to use TDBXStringValue in place of var String and TDBXAnsiStringValue in place of var AnsiString).

Thats all for now. I plan to cover more types in the future.

Posted by Jim Tierney on February 13th, 2009 under DataSnap, Delphi | 6 Comments »




Server Response from: BLOGS2