[Previous][Up][Next] (#fcl-res)

How to implement a new resource writer

Remark: This chapter assumes you have some experience in using this library.

We'll see how to implement a writer for a new resource file format. A resource writer is a descendant of TAbstractResourceWriter, and it's usually implemented in a unit named namewriter, where name is file format name.

Suppose we must write a writer for file format foo; we could start with a unit like this:

unit foowriter;

{$MODE OBJFPC} {$H+}

interface

uses
  Classes, SysUtils, resource;

type
  TFooResourceWriter = class (TAbstractResourceWriter)
  protected
    function GetExtensions : string; override;
    function GetDescription : string; override;
    procedure Write(aResources : TResources; aStream : TStream); override;
  public
    constructor Create; override;
  end;

implementation

function TFooResourceWriter.GetExtensions: string;
begin

end;

function TFooResourceWriter.GetDescription: string;
begin

end;

procedure TFooResourceWriter.Write(aResources: TResources; aStream: TStream);
begin

end;

constructor TFooResourceWriter.Create;
begin

end;

initialization
  TResources.RegisterWriter('.foo',TFooResourceWriter);

end.

Note that in the initialization section, TFooResourceWriter is registered for extension .foo.

We must implement abstract methods of TAbstractResourceWriter. Let's start with the simpler ones, GetExtensions and GetDescription.

function TFooResourceWriter.GetExtensions: string;
begin
  Result:='.foo';
end;

function TFooResourceWriter.GetDescription: string;
begin
  Result:='FOO resource writer';
end;

Now let's see Write. This method must write all resources in the TResources object to the stream.

Suppose that our foo format is very simple:

Our Write method could be something like this:

procedure TFooResourceWriter.Write(aResources: TResources; aStream: TStream);
var i : integer;
begin
  WriteFileHeader(aStream,aResources);
  for i:=0 to aResources.Count-1 do
    WriteResource(aStream,aResources[i]);
end;

So we must implement WriteFileHeader, which writes the 16-byte file header, and WriteResource, which writes a single resource with its header.

Let's start from the first one:

procedure TFooResourceWriter.WriteFileHeader(aStream: TStream; aResources: TResources);
var signature : array[0..3] of char;
    rescount : longword;
    padding : qword;
begin
  signature:='FOO*';
  rescount:=aResources.Count;
  padding:=0;

  aStream.WriteBuffer(signature[0],4);
  aStream.WriteBuffer(rescount,sizeof(rescount));
  aStream.WriteBuffer(padding,sizeof(padding));
end;

This code simply writes the file header as defined in foo format. Note that if we are running on a big endian system we should swap bytes before writing, e.g. calling SwapEndian function, but for simplicity this is omitted.

Now WriteResource comes. This method could be like this:

procedure TFooResourceWriter.WriteResource(aStream: TStream; aResource: TAbstractResource);
var aType : longword;
    aName : longword;
    aLangID : longword;
    aDataSize : longword;
begin
  aType:=aResource._Type.ID;
  aName:=aResource.Name.ID;
  aLangID:=aResource.LangID;
  aDataSize:=aResource.RawData.Size;

  //write resource header
  aStream.WriteBuffer(aType,sizeof(aType));
  aStream.WriteBuffer(aName,sizeof(aName));
  aStream.WriteBuffer(aLangID,sizeof(aLangID));
  aStream.WriteBuffer(aDataSize,sizeof(aDataSize));

  //write resource data
  aResource.RawData.Position:=0;
  aStream.CopyFrom(aResource.RawData,aResource.RawData.Size);

  //align data so that it ends on a 4-byte boundary
  Align4Bytes(aStream);
end;

We write the 16-byte resource header, and then resource data. Note that if resources have been loaded from a stream and the user didn't modify resource data, we are copying data directly from the original stream.

Align4Bytes is a private method (not shown for simplicity) that writes some padding bytes to the stream if needed, since FOO file format specifies that resource data must be padded to end on a 4 byte boundary. Again, we didn't consider endianess for simplicity. And finally, note that foo format only supports IDs for types and names, so if one of them is a name (that is, a string) this code might cause exceptions to be raised.

Note: More complex file formats store resources in a tree hierarchy; since TResources internally stores resources in this way too, a writer can choose to acquire a reference to the internal tree used by the TResources object (see TAbstractResourceWriter.GetTree) and use it directly. For these file formats resources can be written faster, since there is no overhead involved in building a separate resource tree in the writer.

That's all. Now you should be able to create a real resource writer.


Documentation generated on: Jun 22 2020