Tuesday, May 29, 2007

Stream Me a River

Recently, I have uploaded new open source unit to my web pages - GpStreams. It contains a mix of TStream descendants, enhancers and helpers I have written over the last few years.

Stream Window

TGpStreamWindow is a TStream descendant which provides a window into another stream.

Sometimes you want to pass just a part of a stream to some method. For example, you have a stream containing a header and data and you want to pass it to a method that expects only data. If you can't change the method in question to ignore the header (maybe it's not a method you have written), then you would normally have to copy the data part to another stream and pass the copy to that method.

Alternatively, you can wrap the stream into TGpStreamWindow and set limits to exclude the header, then pass the TGpStreamWindow instance to the method. TGpStreamWindow doesn't copy data but overrides Read, Write, and Seek methods.

Relevant part of the interface:

  TGpStreamWindow = class(TStream)
public
constructor Create(baseStream: TStream); overload;
constructor Create(baseStream: TStream; firstPos, lastPos: int64); overload;
procedure SetWindow(firstPos, lastPos: int64);
property FirstPos: int64 read srFirstPos;
property LastPos: int64 read srLastPos;
end; { TGpStreamWindow }

Example:

procedure DoSomethingWithData(dataStream: TStream);
begin
//...
end;

procedure ProcessStream(headerAndData: TStream);
var
dataStream: TGpStreamWindow;
begin
// first 512 bytes contain the header - ignore!
dataStream := TGpStreamWindow.Create(headerAndData, 513, headerAndData.Size - 1);
try
DoSomethingWithData(dataStream);
finally FreeAndNil(dataStream); end;
end;

Streamed Memory Buffer


TGpFixedMemoryStream is a TStream descendant which provides  streamed access to a memory buffer. In a way, it is similar to the TStringStream except that the underlying data is stored in a constant-size memory buffer.


Interface:

  TGpFixedMemoryStream = class(TStream)
public
constructor Create; overload;
constructor Create(const data; size: integer); overload;
constructor Create(const data: string); overload;
procedure SetBuffer(const data; size: integer);
property Memory: pointer read fmsBuffer;
end; { TGpFixedMemoryStream }

Stream enhancers


TGpStreamEnhancer class contains various small helpers for the TStream class. Because of the technology used (class helpers), it can only be used in D2005 and newer. Also because of the technology used, TGpStreamEnhancer extends functionality of TStream and all descendant classes.


TGpStreamEnhancer contains:



  • Big endian (Motorola) readers/writers - Methods to read/write numbers in Motorola (big endian) format.
  • Little endian (Intel) readers/writers - Methods to read/write numbers in Intel (little endian) format.
  • Tagged readers/writers - Methods to read/write tagged data.
  • Text file emulator - Write(string) and Writeln.
  • Other helpers - Save/load stream to/from file, format stream as string or hex string, empty the stream.

Stream Wrappers


Two stream wrappers are included. Both are implemented with interfaces and use the fact that Delphi automatically destroy interfaces when they go out of scope to achieve some 'automagic' functionality.


AutoDestroyStream automatically destroys a stream, when the wrapper goes out of scope.


For example, ProcessStream example above could be rewritten with AutoDestroyStream to:

procedure ProcessStream(headerAndData: TStream);
begin
// first 512 bytes contain the header - ignore!
DoSomethingWithData(AutoDestroyStream(
TGpStreamWindow.Create(headerAndData, 513, headerAndData.Size)).Stream);
end;

KeepStreamPosition wrapper automatically resets stream position to the original value when wrapper is destroyed.


Other Utilities


Few 'unclassified' utilities are also included in the GpStreams unit.


SafeCreateFileStream is a wrapper around TFileStream.Create that maps exception into function result.


DestroyFileStreamAndDeleteFile destroys TFileStream and deletes the file that was used as a stream source.



Technorati tags: , , , ,

Monday, May 28, 2007

New GpLists and other updates

It's been quite some time since I last updated my Delphi freeware stuff so here's the list of latest changes:

DSiWin32 1.24

GpLists 1.26

  • Added TGpReal class.
  • Added TGpCountedIntegerList class.
  • Added TGpCountedInt64List class.
  • Added CustomSort method to the TGpIntegerList and TGpInt64List classes.
  • Added EqualTo method to the TGpIntegerList class.
  • Added ValuesIdx to the TGpObjectMap class.
  • Compiles with Delphi 6 again. Big thanks to Tao Lin and Erik Berry for reporting, diagnosing and fixing the problem.
  • Older articles: Sir! Do you need a list? Cheap, just for you!

GpHugeFile 5.01a

  • Fixed a bug where internal and external file pointer got out of sync in buffered write mode.
  • Exposed underlying file's handle as a Handle property from the TGpHugeFileStream class.
  • Added support for asynchronous writing.
  • Older articles: GpHugeFile 4.0

GpStructuredStorage 1.12a

GpSync 1.20

  • New class TGpCircularBuffer.

GpStuff 1.05

  • Newly published on the web, contains some small utilities. See the source.

GpStreams 1.13

  • Collection of stream helpers, stream descendants and stream utilities. I'm preparing a more detailed description which should be published soon.

Previous updates: Bulk update

Technorati tags: , , ,

Friday, May 11, 2007

Case .. else raise, again

Yesterday I used a really bad example which got most of my readers thinking about how most of the code could be optimized away. As that was not the idea behing my post, I decided to give a better example. This one is straight from the production code:

case mafWorkerOwner.PostToThread(MSG_AF_SCHEDULE_VIDEO, clonedBuffer) of
mqpOK:
Result := ClearError;
mqpTimeout:
Result := SetError(ERR_MXFFILTER_THREAD_TIMEOUT,
'Timeout while sending message to the worker thread');
mqpQueueFull:
Result := SetError(ERR_MXFFILTER_QUEUE_FULL,
'Cannot send message to the worker thread - queue full');
else raise Exception.Create('TMXFAsyncFilter.ScheduleVideo: Unexpected PostToThread result');
end; //case PostToThread

Am I making more sense now?


Technorati tags: , ,

Thursday, May 10, 2007

Case .. else raise

I believe in safe programming. In my book that means that my programs should fail whenever I do something wrong. Even better - they should automatically detect my future stupidities and warn me when I make them.

For example, take this completely made-up example:

type
TEnumeration = (enOne, enTwo, enThree);

function DoSomethingWith(enum: TEnumeration): integer;
begin
case enum of
enOne: Result := 1;
enTwo: Result := 2;
enThree: Result := 3;
end;
end;

Pretty typical, huh? We'll, I won't write it like that. Never.


Let's say that in the future TEnumeration gets extended to include enFour. Now you have to hunt all places where it is used and adapt the code. Maybe you'll miss one or two, maybe not. I usually do :(


At least in fragments like the one above, a little planning can save you hours of debugging. By adding just one line, you can be automatically reminded that you failed to update the code at this very place:

type
TEnumeration = (enOne, enTwo, enThree);

function DoSomethingWith(enum: TEnumeration): integer;
begin
case enum of
enOne: Result := 1;
enTwo: Result := 2;
enThree: Result := 3;
else raise Exception.Create('DoSomethingWith: Unexpected value');
end;
end;

(For commenters: Yes, I'm aware that this is simply a precondition check.)


[

Technorati tags: , ,
]