Pascal Header Translations

By: Charles Calvert

Abstract: This article is designed to explain the Borland specification for the generation of Pascal translations of C/C++ Header files.

Pascal Header Translation Specification

Version 1.01

This article is designed to explain the Borland specification for the generation of Pascal translations of C/C++ Header files. I've tailored the article for Project JEDI engineers who are hoping to create header translations that can be used by Borland on its web site or on the Delphi product CD itself.

This article is divided into two sections:

  • The first part describes why Delphi R&D developed this particular specification.
  • The second part lists a set of 8 rules of the road that must be followed if you want to conform to our specification.

The Pascal files discussed in this article are properly known as Interface units, but to avoid confusion with the COM interface type, I will usually refer to them simply as "header translations".

A further document will be presented later to outline the legal issues and obligations for all participants.

Understanding the Specification

When reading this article, you need to keep five things in mind:

  • You are aiming to perform a task commonly taken on by Delphi R&D. In particular, you are creating files that at least theoretically can be shipped as part of Delphi itself. As a result, you have to follow the protocols and conventions followed by the Delphi team. You may believe, or perhaps even in your heart of hearts be certain, that there are better rules or guidelines than the ones defined in this article. That is an interesting point of view, but it is not relevant to the current situation. In this case you simply must follow the conventions that all Borland engineers follow because you are producing files that will, at least potentially, be a small part of the Borland product line. To follow any other course of action will be to abandon any hope of having your header translations become a standard accepted by Borland.
  • The header translations you create must be compatible with C/C++ Builder. In particular, C++ Builder will potentially be used to convert your header translations back into C/C++, producing an .hpp file for use in C++Builder projects. It turns out that it is a non-trivial task to convert a Pascal file into a C/C++ header file, particular if types already defined in C/C++ header files are redefined in a Pascal header translation. As a result, some very stringent guidelines have to be followed. One of the primary purposes of this article is to explain and define those guidelines.
  • Two tools provided by Borland, called wpar.exe and pp.exe, can aid in the creation of header translations. These tools will be discussed in more depth later in this article. When creating header translation files, you may decide to use a header translation utility. These tools are a great start, but you will have to go through your files manually in order to bring them into conformity with Borland's standards. Here is a link to one such tool called HeaderConvert. If you would like other tools of this sort to be listed here, send mail to Borland JEDI support.
  • Submitters are responsible for verifying that what they submit is valid and correct. We may not have the resources to check up on everything that is submitted. We'll do code examinations and spot checks to determine accept/reject status, but that's all we can commit to. It's in a submitter's best interest to make sure their submissions are clean. A rash of bug reports against a particular submitter's works will radically lower the likelihood of our accepting further works from that submitter.
  • If your aim is to appear on the Delphi Product CD, you should concentrate on Microsoft header files and avoid obscure header files that may not be of interest to the whole community. Be particularly on the look out for files that may represent some kind of copyright protection. Subjects of particular interest to the Delphi team include MAPI, TAPI, and BackOffice related files. We still want to encourage work outside the main stream, but it is likely to appear on the Borland or JEDI web sites, and not on the product CD. Finally, when creating your files, work through Project JEDI to ensure that you are not duplicating work already undertaken by someone else.

In the next two sub sections of this article I'm going to give you some background on key tools and conventions used by Borland engineers when creating header translations. In the process, I will address two main topics:

  • How Borland engineers save time by creating an intermediate file called a PAR file, that can help you save work when wrestling with the ANSI and Unicode versions of a function or type declaration.
  • How Borland engineers create header translations that are compatible with C++Builder.

Some of these matters will be covered in more depth in the third section of this article. At this time, I only want to give you enough information so that you will understand the relevance and reasoning behind some of the rules laid out in the second section of this article.

PAR Files

Header translators should be creating not .PAS files but .PAR files. PAR files contain most of what you see in a PAS file, but they omit the ANSI and WideChar versions of a declaration. There is a utility called wpar.exe that will translate PAR files into PAS files, and in the process automatically generate the correct WideChar and ANSI declarations.

For instance, in the PAR file, you would write the following:

    {#BEGIN} ... // Code omitted here for the sake clarity function GetPrintProcessorDirectory{$}(pName, pEnvironment: LPTSTR; Level: DWORD; pPrintProcessorInfo: Pointer; cbBuf: DWORD; var pcbNeeded: DWORD): BOOL; stdcall; {#END}

Take special note of the #BEGIN, #END and {$} symbols. After you ran the file through wpar.exe, you would get the following declarations:

    function GetPrintProcessorDirectoryA(pName, pEnvironment: PAnsiChar; Level: DWORD; pPrintProcessorInfo: Pointer; cbBuf: DWORD; var pcbNeeded: DWORD): BOOL; stdcall; function GetPrintProcessorDirectoryW(pName, pEnvironment: PWideChar; Level: DWORD; pPrintProcessorInfo: Pointer; cbBuf: DWORD; var pcbNeeded: DWORD): BOOL; stdcall; function GetPrintProcessorDirectory(pName, pEnvironment: PChar; Level: DWORD; pPrintProcessorInfo: Pointer; cbBuf: DWORD; var pcbNeeded: DWORD): BOOL; stdcall;

Clearly this is a tool that can save you considerable time and effort. It will also make it very easy to convert our header translation files over to Unicode if Microsoft releases versions of Windows that are based on Unicode.

EXTERNALSYM, HPPEMIT and NODEFINE

The header translation files that you create will need to be compatible with C++Builder. In particular, C++Builder can read in Pascal source files, and can automatically generate a header file with an .hpp extension. This can be a delicate process, and some rules need to be defined about how to proceed in certain cases.

It is wonderful that C++Builder can read in Pascal source files. However, it is perhaps a bit ironic that whatever translations you create are going to be duplicates of files that already exist in C++Builder. For instance, if you create a translation of WinSpool.h called WinSpool.pas, then the file you created will be mostly filled with duplications of information already available to C++Builder. In fact, if C++Builder simply converted your file back into C/C++, then there will now be two versions of each symbol you translated, one in the WinSpool.hpp and one in WinSpool.h. This can cause maddening confusion for C/C++ programmers who are trying to compile and link their programs. The EXTERNALSYM directive was designed to clear up this problem.

    I say that C++Builder converts Pascal source files to .hpp files. In fact, it is the Pascal compiler, dcc32.exe, that is called on to make the conversion. Dcc32.exe ships with C++Builder.

When you use EXTERNALSYM in a Pascal source file, you are telling C++Builder that it should not paste that particular symbol back into the HPP file that it is generating. That way, there will not be two versions of a particular symbol, one in the WinSpool.hpp and one in WinSpool.h.

Consider the following excerpt from a Pascal header translation:

    PRINTER_CONTROL_PAUSE = 1; {$EXTERNALSYM PRINTER_CONTROL_PAUSE}

The EXTERNALSYM statement shown here tells the C++Builder compiler not to insert the PRINTER_CONTROL_PAUSE identifier back into the HPP file it is generating. There is no need for this declaration since it is already included in the original header file from which the Pascal translation was made. So all the HPP file need do is include the original header file.

All of which brings us around to HPPEMIT, which is the symbol used to tell the C++Builder compiler that it needs to include a symbol which is not active in the Pascal source file. For example, the following text is found at the top of WinSpool.pas:

    (*$HPPEMIT '' *) (*$HPPEMIT '#include ' *) (*$HPPEMIT '' *)

The Pascal compiler will ignore this information. The C++Builder compiler, on the other hand, will explicitly insert #include into the top of the WinSpool.hpp file that it generates from WinSpool.pas.

Though you are unlikely to have reason to use it, I will also mention the NODEFINE directive. It instructs the compiler to create a private symbol. No definition will be output to the HPP file, but some information may be output to the OBJ file. This might be helpful when generating RTTI or debugging information, for example. It is the responsibility of the programmer to define this type, if it is required, using $HPPEMIT.

This directive is meant to be used when a type definition uses Pascal idioms that cannot be easily translated into C/C++. You might use this for Pascal code that has been translated from C/C++ and given a different name than the C/C++ code. The following example is from the WinSock unit.

    type {$NODEFINE PFDSet} PFDSet = ^TFDSet; {$NODEFINE TFDSet} TFDSet = record fd_count: u_int; fd_array: array[0..FD_SETSIZE-1] of TSocket; end;

Another issue that comes up involves name mangling incompatibilities. That subject, however, is fairly complex, and will be treated in a second document, and perhaps also in the Delphi 5 and C++Builder 5 compilers themselves.

When you combine the effect of the EXTERNALSYM, NODEFINE and HPPEMIT statements, you end up with a system that allows the C++Builder compiler to properly process Pascal Header translations. All of this means extra work for people who are creating the header translations, but the end result is a file that is truly useful to the entire Delphi community, the C++Builder community, and the Delphi development team itself.

Basic Rules of the Road

Now you know a little something about the special issues involved in creating a Pascal translation of a C/C++ header file. With this information in mind, it is possible for you to better understand the basic rules of the road you need to follow if you want to create a file that conforms to the Delphi team's standards.

I am going to present two versions of the rules of the road. The first version is very short and concise, and can be used as a reference. Immediately following my listing of the rules you will find a series of sub-sections which explicate these rules so that you can better understand how they work.

Here are the basic rules that must be followed:

  1. Maintain the original header's symbol name and casing.
  2. Mark all symbols that are actually defined in the original header as {$EXTERNALSYM }. This applies to types, records, objects, functions and procedures. Use HPPEMIT as necessary.
  3. Introduce T and P type aliases version for use in Delphi.
  4. Some functions take a parameter that is a pointer to a type. If the type can never be passed as nil, then it should be changed to a var parameter that is usually not a pointer.
  5. Macros should be converted to functions or procedures.
  6. There should be no IFDEFS in your Pascal translations.
  7. All COM interfaces should be native Delphi interfaces.
  8. Source-code formatting is important. No tabs should appear in a source file. Indent only two spaces for each indent level. Details for formatting will be laid out in a separate article.

Some of these rules are fairly easy to understand, but others require some explanation. In the next few subsections I will go through these rules one by one, adding a few paranthetical comments to help make their meaning as clear as possible.

Rule 1: Maintain Original Case and Spacing

You must maintain original header's symbol name and casing. For instance, if a C/C++ header defines a type as a WORD, then the Pascal header should list the type as a WORD, not as Word. The emphasis on case applies to both types and function declarations. Be careful not to define a type in your header that is already defined somewhere else. For instance, DWORD is declared in Windows.pas, so you should not redeclare it in your file unless Microsoft has redeclared it there with a value different than the one in Windows.h.

Remember: The Pascal compiler will output exactly what the programmer typed, each and every time the symbol is used. If you declare a symbol in a Pascal header translation, and get the case wrong, it won't matter much in Pascal programs, but it probably will cause big problems when processed by C++Builder. This is particularly true when it comes to the EXTERNALSYM issue, described in the next section.

There are also some useful cases where casing allows conflicts to be avoided. consider if the Pascal source is being translated and it uses a reserved word such as 'this' for a parameter name. It can be cased as 'This' and then the translation goes without a problem.

Rule 2: Mark Defined Symbols with EXTERNALSYM

Mark all symbols that are actually defined in the original header as {$EXTERNALSYM <Ident>}. This applies to types, records, objects, functions and procedures. As mentioned in the previous section, be particularly careful about case!

Consider this definition from Winspool.h:

#define PORT_TYPE_WRITE         0x0001 

It should appear in the Pascal file as follows:

    PORT_TYPE_WRITE = $0001; {$EXTERNALSYM PORT_TYPE_WRITE}

This ensures that the C++Builder process that translates the file back into C/C++ knows that this symbol is already defined in a header file and does not need to be re-declared. In fact, the process of re-declaring a symbol in two places can cause considerable confusion for C/C++ programmers, so it is important that we be both correct and polite in this regard.

It is best if you insert the EXTERNALSYM statement immediately after your declaration for the code. It is not an error to place it immediately before the Pascal declaration, or even in some remote corner of the file such as the end. However, the file will be processed faster if you place the EXTERMANSYM statement immediately after the Pascal declaration. Because of the need to maintain unified standards, you could risk having your header rejected by Borland if you failed to follow conventions on this matter.

EXTERNALSYM also helps with the translation of particular types that receive special treatment in Delphi. Consider the LPCTSTR type in this record from the Windows.par file:

    tagWNDCLASSEX{$} = packed record cbSize: UINT; style: UINT; lpfnWndProc: TFNWndProc; cbClsExtra: Integer; cbWndExtra: Integer; hInstance: HINST; hIcon: HICON; hCursor: HCURSOR; hbrBackground: HBRUSH; lpszMenuName: LPCTSTR; lpszClassName: LPCTSTR; hIconSm: HICON; end;

Here is what the same record looks like in Windows.pas:

    tagWNDCLASSEXA = packed record cbSize: UINT; style: UINT; lpfnWndProc: TFNWndProc; cbClsExtra: Integer; cbWndExtra: Integer; hInstance: HINST; hIcon: HICON; hCursor: HCURSOR; hbrBackground: HBRUSH; lpszMenuName: PAnsiChar; lpszClassName: PAnsiChar; hIconSm: HICON; end;

As you can see, lpszMenuName and lpszClassName are declared as PAnsiChar in Windows.pas. The conversion from LPCTSTR to PAnsiChar was taken care of automatically by wpar.exe. As a result, you need not concern yourself with the translation directly, but you should understand that it is happening.

If you look back a WNDCLASSEXA, you will see that it is declared as packed record. This directive effects the way the compiler aligns the data in the record when it is displayed in memory. The subject of properly aligning your data is complex enough that it will be treated in separate document, which is currently still in draft form.

Rule 3: Declare Both Static and Pointer Versions of a Type

You should introduce both T<Ident> and P<Ident> type alias versions for use in Delphi. Consider the following C/C++ struct:

    typedef struct _PORT_INFO_3A { DWORD dwStatus; LPSTR pszStatus; DWORD dwSeverity; } PORT_INFO_3A, *PPORT_INFO_3A, *LPPORT_INFO_3A;

In Pascal, you will need to come up with both a PPortInfo3 and a TPortInfo3 type alias.

This can be a bit more complicated than it might at first appear, in part because you need to take into account by ANSI and WideChar versions of a type, as discussed later in this article.

You should not, of course, create EXTERNALSYM statements for these T<Ident> and P<Ident> types. EXTERNAL SYM is only for types that are already declared in existing C/C++ headers.

Rule 4: var Parameters and Pointers

Some functions take a parameter that is a pointer to a type. If the type can never be passed as nil, then it should be changed to a var parameter and the type changed to the appropriate non-pointer type.

Consider the following C/C++ declaration from Winspool.h:

    BOOL WINAPI EnumPrintersA( DWORD Flags, LPSTR Name, DWORD Level, LPBYTE pPrinterEnum, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned );

Here is the Pascal translation of this type:

    function EnumPrintersA(Flags: DWORD; Name: PAnsiChar; Level: DWORD; pPrinterEnum: Pointer; cbBuf: DWORD; var pcbNeeded, pcReturned: DWORD): BOOL; stdcall;

Notice that the last two parameters are passed as var parameters of type DWORD, even though they are declared as pointers to a DWORD in the original C/C++ header. If it were legal to call EnumPrintersA as follows, then the last two parameters would have to be declared in Pascal as pointers:

EnumPrintersA( 0, "SomeName", 0, nil, 0, nil, nil); 

The problem is that in this evocation of the function, nil is being passed in the last two parameters. The compiler will obviously give a type mismatch if it is expecting a DWORD and gets a pointer value (nil) instead. Now, in this particular case, Microsoft does not allow you to pass nil in either of the last two parameters of this function. As a result, it is okay to declare the function as a DWORD passed by reference. Doing so will make the function easier for all Pascal programmers to call.

Needless to say, the act of deciding which parameters can be passed as nil and which can't is a very tedious job. There is simply no way for it to be done mechanically. It must be done by hand, probably with a copy of the Windows Help file open next to you. If the help file explicitly says that you can pass nil in a particular parameter, then you are not going to be able to declare it as a var parameter.

A related subject involves the possible use of function overloading in header translations. Though this subject shows considerable promise, for now this practice will be discouraged until additional experience with the subject leads to a either a complete rejection of the practice, or a series of clearly defined rules on how the technology can best be used. We will not reject the use of overloading out of hand, but if problems are found with your use of overloading, then the entire header could be rejected.

Rule 5: Translate Macros into Functions or Procedures

Delphi does not support macros. As a result, macros should be converted to functions or procedures. If necessary, they should be appropriately marked {$EXTERNALSYM}. Do not use the stdcall calling convention for macros.

Rule 6: No IFDEFs in a Pascal Header Translation.

There should be no IFDEFs in a Pascal header translation. There are no exceptions to this rule.

Since header translation files ship with Delphi, there is no need for alternate definitions to support previous products. Borland doesn't support using files from the current version of the product with previous versions of the product. If these files were distributed independently of the product, then yes, IFDEFs might be justifiable, but not for files that ship with the product.

IFDEFs are discouraged because they are only useful if you intend to rebuild the source. That defeats all of the productivity and performance advantages of precompiled DCUs and DCPs and makes for some nasty versioning headaches.

A simple Pascal pre-processor, called pp.exe, can be used to strip IFDEFs out of your header translation files. This way you can maintain a header file which is IFDEFed for different versions of the compiler. However, when you submit the header translation to Borland, you should run pp.exe on it so that it strips out all the IFDEFs, keeping only the code relevant to the current version of Delphi.

Rule 7: COM Interfaces Should be Native Delphi Types

All COM interfaces should be native Delphi interfaces. Delphi supports a native type called Interface, and you should use this type when creating Pascal header translations. For instance, here is the Delphi declaration for the IUnknown interface:

    IUnknown = interface ['{00000000-0000-0000-C000-000000000046}'] function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall; function _AddRef: Integer; stdcall; function _Release: Integer; stdcall; end;

As you can see, this type is declared not as a class, but as an interface.

As per rule 6, there can be no IFDEFs for pre D3 versions. Our Delphi header translations cannot support D1 and D2 versions or else they will no longer be compatible with C++Builder.

Rule 8: Formating your Code

Source-code formatting is very important to the Delphi team. Do not include any tabs in any of your source files. Indent only two spaces for each indent level.

You now know the key rules which must be followed when creating Pascal header translations. Computer programming by its nature must be goverened by very strict rules, and we have laid out a number of fairly stringent ones in this document. Nevertheless, the Delphi R&D team and others at Borland are excited about the possibilities inherent in this project. We are proud of our development community and enjoy working with them. Everyone believes that this will turn out to be a rewarding project for all involved, and we extend our sincerest thanks to all who decide to participate in this project.

Addendum

When translating SDK integer constants to Delphi syntax, maintain the hexidecimal or decimal notation used in the original SDK.

When declaring 4-byte hexidecimal constants which have the high bit set, you should use a typecast to prevent the Delphi compiler from promoting the unsigned hexidecimal value to Int64.

  Example (from windows.pas) 

const 
  GENERIC_WRITE  =  $40000000; 
  GENERIC_READ    =  DWORD($80000000); 

HRESULT constants: Any constant that will be used as an OLE / COM result code should be cast as an HRESULT.

Interface declarations: Always put the IID (GUID) of an interface in the Delphi interface declaration. This may require some digging, since the SDK C headers usually do not include the interface IIDs. Look for a corresponding .IDL files in the SDK which will contain the complete interface definition.

IID constants: Since the Delphi interface types already contain their IIDs and the compiler automatically extracts the IID when you pass an interface to a GUID parameter, the SDK IID_ constant identifiers only need to exist for SDK documentation compatibility. Instead of declaring the GUID data twice (once in the interface, again in the IID_ constant), just declare the IID_ constant as an alias to the actual interface. This has less maintenance risk and makes DCUs and EXEs a little smaller. It's also a lot easier to type.

  Example: 

const 
    IID_IPersistMoniker = IPersistMoniker; 

Server Response from: ETNASC04