Monday, September 27, 2004

Client dataset to .NET dataset

CC #21053: A great Delphi 8 project with source code written by Petr Vones. It can load a client dataset XML datapacket into a .NET dataset.

Sunday, September 26, 2004

Multicast events

I like multicast events in Microsoft .NET. Basically, a single event property (using Delphi terms) can be linked to multiple event handlers (as opposed to standard Delphi's events which can only be linked to a single event handler at most). In Delphi 7, a multicast technique is already used internally in TApplicationEvents, AppEvnts unit. In this case, the result is that you can have multiple TApplicationEvents components in your project (perhaps on different forms), each of which can receive and handle events coming from the global Application component.

It seems that some research is being done of multicast events support for future versions of Delphi for Win32.

In one of my projects, I've been experimenting with my own implementation (just for my own, non-visual classes, without any designtime support). It seems to be a success and I believe it helped me write cleaner, more modularized code. Writing it was fun, too.

Wednesday, September 22, 2004

Undocumented Delphi IDE command line switch

Another newsgroup nugget from Allen Bauer on b.p.d.n-t:

<quote>
One thing you can do with all versions of Delphi from at least D5, is use the "-r" command-line switch. This allows you to specify the root registry key to use when loading the IDE. For instance in D7, you could export the HKCU\Software\Borland\Delphi key to a file. Then rename the "Delphi" key to something else, like "SafeMode". Then re-import the exported key to recreate the HKCU\..\Delphi key. Finally, when you run the Delphi32 application, just pass the "-rSafeMode" switch to use that key.
</quote>

I think this feature can be very useful.

According to another post in that thread, there seem to be more undocumented switches: -s, -k, -q, -x, -y.
I have no idea what those are for...

Monday, September 20, 2004

Just a part of it, please (part 2)

For performance reasons, I have rewritten the ISAPI extension DLL (see my previous post) which simulates HTTP 1.1 GET with range specified.
The DLL now uses the IIS function HSE_REQ_TRANSMIT_FILE which is asynchronous and performance-wise optimized for sending files to clients.
Note: It helps to specify the FILE_FLAG_SEQUENTIAL_SCAN flag when opening the file. For asynchronous I/O operations, the FILE_FLAG_OVERLAPPED flag has to be specified.
For this purpose, I decided not to use Delphi's Web Application framework and wrote a plain ISAPI DLL instead.
Another modification that helped was not to use HTTP GET with parameters encoded directly in the URL. Instead, I now use HTTP POST with parameters encoded in binary POST data. DataSnap uses the same approach and it seems to be the most efficient one.
Unfortunately, our client applications are currently designed so that they send a lot of HTTP requests returning small packets of data. In such scenarios, it's better to specify HSE_IO_NODELAY flag in your HSE_REQ_TRANSMIT_FILE calls. Note: You can also use this flag with the WriteClient function (although it's not mentioned directly in its documentation).
This flag turns off the TCP nagling optimization (enabled by default) which, in case of small packets with Keep-alive connections, actually degrades performance because of the 200 ms timeout.
My subjective ;-) feeling is that the DLL's performance is now approximately the same as that of the standard HTTP 1.1 GET with range specified. I don't have enough statistical data yet to prove or disprove that speculation.

I have also finished a very simple ISAPI Filter DLL, whose only purpose is to be able to define some URL aliases. Again, some people have weird network configurations where their proxies forbid usage of URLs containing the .dll extension. The ISAPI filter modifies the incoming headers before IIS processes the request, so it can translate a URL like http://hostname/httpsrvr to http://hostname/scripts/httpsrvr.dll. Currently, it reads the alias definitions from the registry every time it's called by IIS, so the aliases are configurable without having to restart IIS. So far, this doesn't seem to adversely affect the performance.

CC #22334

Today I posted two performance counter libraries for instrumenting DataSnap appservers (one for sockets, one for HTTP transport) to CodeCentral.

Visual Poetry

Written in Delphi. Cool!

Saturday, September 18, 2004

CC #22332

Today I posted an example of creating a custom performance counter library to CodeCentral. I'm preparing my DataSnap performance libraries for publishing as well.

Saturday, September 04, 2004

YAPP

I'm using YAPP (Yet Another Pretty Printer ;-)) written by John Kaster to format my Delphi source code for HTML. The current version can highlight the following languages:
  • C++
  • C#
  • Delphi (of course!) ;-)
  • Java
  • SQL
  • Delphi forms (.dfm)
  • OMG IDL
  • Microsoft IDL
  • Xbase

It can produce highlighted output in HTML or RTF.
Quite useful. Thanks, John!

Just a part of it, please

A few days ago, I had to write an ISAPI extension DLL which would return partial content from a local file stored on the web server. This was meant as a workaround for some proxy configurations which (so I heard) strip some HTTP headers and thus make it impossible for our client code to simply use standard HTTP 1.1 GET requests with the Range header specified. (And before you ask, no, it was not acceptable to simply ask them to change their network configuration.)
So my ISAPI DLL should accept parameters like file name, start position and length in its URL and return only the requested bytes to the client (after translating the given file path to the actual local file path on the server).
The initial problem with Delphi's TWebResponse implementation (or so it seemed at first) was that it had no support for sending just a given number of bytes from a stream. The methods for sending response to the client are:


TWebResponse = class(TObject)
...
public
...
procedure SendResponse; virtual; abstract;
procedure SendRedirect(const URI: string); virtual; abstract;
procedure SendStream(AStream: TStream); virtual; abstract;
...
end;

The problem with TISAPIResponse.SendStream (TWebResponse is an abstract class, TISAPIResponse is the concrete descendant used in ISAPI applications) is that it will send the whole stream (or the rest of it from its current position) in 8K chunks until it reaches the end of the stream. So what are my options?

  1. Create a memory stream, copy the requested bytes to it and use it as the response content stream. Not a very good idea, if the requested range is large. This was my first, quick and dirty implementation, though.

  2. Write my own TWebResponse descendant and plug it into the framework - while perhaps possible, it might be a bit of work.

  3. Modify the VCL source code ;-) I don't like doing this unless for a very good reason.

  4. Anything else? Actually, yes.

My solution was to write a new TStream descendant:


type
TPartialFileStream = class(TStream)
private
FFileStream: TFileStream;
FSize: Int64;
FStartPos: Int64;
public
constructor Create(const FileName: string; AStartPos, ASize: Int64);
destructor Destroy; override;
function Read(var Buffer; Count: Longint): Longint; override;
function Write(const Buffer; Count: Longint): Longint; override;
function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; override;
end;

{ TPartialFileStream public }

constructor TPartialFileStream.Create(const FileName: string; AStartPos, ASize: Int64);
var
FileSize: Int64;
begin
inherited Create;
FFileStream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyNone);
FileSize := FFileStream.Size;
if (ASize < 0) or (ASize > FileSize) or (AStartPos < 0) or (AStartPos > FileSize) or
(FFileStream.Seek(AStartPos, soBeginning) <> AStartPos) then
raise EReadError.CreateRes(@SReadError);
FStartPos := AStartPos;
if ASize = 0 then
FSize := FileSize - FStartPos
else
FSize := ASize;
end;

destructor TPartialFileStream.Destroy;
begin
FFileStream.Free;
inherited Destroy;
end;

function TPartialFileStream.Read(var Buffer; Count: Longint): Longint;
begin
if FFileStream.Position + Count > FStartPos + FSize then
Count := FStartPos + FSize - FFileStream.Position;
if Count > 0 then
Result := FFileStream.Read(Buffer, Count)
else
Result := 0;
end;

function TPartialFileStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;
var
NewPos: Int64;
begin
case Origin of
soBeginning:
Result := FFileStream.Seek(FStartPos + Offset, soBeginning) - FStartPos;
soCurrent:
begin
NewPos := FFileStream.Position + Offset - FStartPos;
if NewPos < 0 then
NewPos := 0;
if NewPos > FSize then
NewPos := FSize;
Result := FFileStream.Seek(FStartPos + NewPos, soBeginning) - FStartPos;
end;
soEnd:
Result := FFileStream.Seek(FStartPos + FSize + Offset, soBeginning) - FStartPos;
else
Result := -1;
end;
end;

function TPartialFileStream.Write(const Buffer; Count: Longint): Longint;
begin
Result := 0; // this stream is read-only
end;

It uses TFileStream internally (and therefore accesses the file directly) but only "publishes" the specified part of the file. The code for sending the response is now very simple:


Stream := TPartialFileStream.Create(FileName, StartPos, Len);
Response.StatusCode := HTTP_STATUS_OK;
Response.ContentType := 'application/octet-stream';
Response.ContentStream := Stream;
Response.SendResponse;

Unicode Identifiers

News from Danny Thorpe about Unicode identifiers.

My personal and purely subjective note:
I just hope I will never have to read source code containing non-English (e.g. Chinese) identifiers.
I'm not a native English speaker but I shudder even when I think about source code containing identifiers in my own native language. Yuck! Am I too old already? ;-)

GExperts 1.2 released

Friday, September 03, 2004

Building packages from the command line

Allen Bauer has posted this little newsgroup nugget on b.p.d.n-t:

<quote>
The IDE supports a very little know feature where you can continue to control these options while in the IDE, yet allow the command-line be able to also control the options. In a package file, all the options listed there are propagated to all the contained units. This is different than a normal .dpr program/library file. In order to do this, all you need to do is replace the '$' with a ' ' (that's a <space>). Then when you open the dpk in the IDE, you can toggle those options as much as you like, but when you compile on the command-line, you can still control them.

This is how we build all our packages for the product itself. All of the Borland built packages have DEBUGINFO, STACKFRAMES, ASSERTIONS, OPTIMIZATION, LOCALSYMBOLS setup this way because like you, we wan't to be able to control those options from the command-line. The developers will typically do a complete "debug" build, whereas the integration team will not.

$DEFINES will sort of work the same way, in that if you replace the '$' with a ' ', the IDE will still recognize the defines and apply them, but any other modifications to the dpk source will re-insert the '$' character.
</quote>

Great to know. Thanks, Allen!

Thursday, September 02, 2004

PerfMon ready



My DataSnap appserver is now instrumented. In fact, my performance counter libraries are generic and will work with all DataSnap servers installed on the computer. DataSnap class IDs are read from the registry and a separate performance object instance is created per each class ID.
I have created two performance counter DLLs, one for web (httpsrvr.dll) and another for socket (scktsrvr.exe) transport.
I'm looking forward to watching the graphs later this month; some serious load is expected on our website when the users are back from their vacations.

Links to the source code:
Custom Performance Counters
Performance counter libraries for DataSnap appservers