How to generate HTML Help with Doc-O-Matic Express


XMLDoc in-source documentation style is becoming popular among Delphi open-source projects. There is also very good Delphi Documentation Guidelines document which describes what XML tags should be used for in-source XMLDoc-style documenting comments and how to use these tags. Unfortunately currently there are no documentation generators for Delphi in-source comments that fully support these guidelines.
So we must limit ourselves to the set of XML tags supported by the already available documentation generators. In fact I have found the only documentation generator that supports XMLDoc-style for Delphi in-source comments – that is Doc-O-Matic. There is also free (for non-commercial use) edition – Doc-O-Matic Express that can be downloaded from the official site. Using Doc-O-Matic Express one can generate documentation in several help formats; in this article we will see how to generate compiled html help (*.chm file).
For the demonstration purposes I downloaded Collections package by Alex Ciobanu; the project recently started to use XMLDoc-style in-source documenting comments.

Here is step-by-step instruction how to generate compiled html help using Doc-O-Matic Express 7.0:
Create the following directory structure:

Copy the “Collections” package files to the ‘Source’ subdirectory.

Run the Doc-O-Matic Express Configuration utility. Create a new Doc-O-Matic project and add all *.pas files from Source subdirectory to the project:

On the “General Options” panel set the project’s title (and other information if you like):

On the “Comment Finding” panel check “Use triple comments only” checkbox:

Now switch to HTML Help Tab; on the “HTML Help” panel set the name of *.chm file to be generated. The path to help compiler should already be set if help compiler is installed in your system; most probable it is installed already; if not download and install “HTML Help Workshop” from Microsoft – it is free:

On the “Help Project Option” check “Create binary TOC” checkbox:

Save the DoM project in DoM700 subdirectory under the name “Collections” and exit Doc-O-Matic Express Configuration utility.

We have created DoM project with necessary settings. Now we should use command-line documentation generator to create compiled HTML help file. To perform the compilation in a single click create a batch file collections.bat with a single text line like this:

“c:\Program Files\Doc-O-Matic 7 Express\domexpress.exe” -all c:\Work\Collections\DoM700\Collections.dox-express

The -all switch means to compile all configurations; we actually need only HTML Help configuration, but I have not found how to compile it alone, so I compile them all.

After executing collections.bat we obtain a very attractive help file Collections.chm; here is a screenshot:



Since the introduction of operator overloading in Delphi one can easily define custom data types that support arithmetic operations. For example it is quite easy to implement arithmetic of complex numbers like this:

  PComplex = ^TComplex;
  TComplex = record
    Re, Im: Extended;

    class operator Implicit(const A: Extended): TComplex;

    class operator Add(const A, B: TComplex): TksComplex;
    class operator Subtract(const A, B: TComplex): TComplex;
    class operator Multiply(const A, B: TComplex): TComplex;

Note that TComplex is a static type with fixed data size (Re, Im fields) and is normally allocated on stack. The problems arise when you need to implement arithmetic on dynamic data allocated on heap.
If you are about to implement big integer (TInteger) arithmetic you have two alternatives.
First, you can stick to static data types; that means that you should limit maximum number value in the implementation.
The second alternative is to use dynamic type to allocate the numbers, so there is no need to introduce artificial limits on maximum number value.

The first approach is obvious, the second is more interesting.
You can’t use GetMem/FreeMem to allocate/deallocate the numbers on heap because memory deallocation should be performed automatically by the compiler, and you just cannot instruct the compiler to call FreeMem for temporary TInteger value while evaluating arithmetic expression like

A:= (B + C) * D;

Consequently, the dynamic type to hold the number data should be lifetime-managed type – and the dynamic array seems to be the most probable candidate:

  TInteger = record
    FData: array of LongWord;

The above implementation forbids “in-place” operations on TInteger variables – the reason is a side effect of TInteger assignment. Dynamic arrays do not support copy-on-write, so the following assertion will fail:

  A, B: TInteger;

  A:= 1;
  B:= A;

After B:= A both A.FData and B.FData reference the same data, so that after Inc(A) we have B = 2.

Now we have 3 alternatives:

  • we can avoid completely the in-place operations on TInteger;
  • we can use undocumented format of dynamic array to implement TInteger “copy-on-write” by hacking dynamic array’s reference count before calling in-place operations;
  • we can use interfaces.

(Well, there are lifetime-managed data types in Delphi that support “copy-on-write” semantic – strings, but they can’t help us since we need some low-level hacking to manipulate the binary data in string format and “copy-on-write” will not work as we need).

Let us try the following:

  PIntegerData = ^TIntegerData;
  TIntegerData = record
    Size: LongInt;   // number of allocated longwords in Data
    Data: array[0..0] of LongWord;

  IInteger = interface
    function GetData: PIntegerData;
    function GetRefCount: Integer;

  TIntegerObject = class(TInterfacedObject, IInteger)
    FData: PIntegerData;
    constructor Create(Limbs: Integer);
    destructor Destroy; override;
    function GetData: PIntegerData;
    function GetRefCount: Integer;

  TInteger = record
    FInteger: IInteger;

The implementation based on interfaces is the most “correct”. It does not use any dirty hacking or undocumented features to get reference count and implement “copy-on-write”. The bad thing is that the above implementation has the worst performance. For example, to allocate a single TInteger value you need to allocate two memory blocks on heap – first for a TIntegerObject and second for TIntegerData referenced by the TIntegerObject.

All in all, a TInteger implementation based on dynamic arrays (with or without refcount hacking) looks most optimal today.