Conversion Helper Package

Download

For the conversion of API headers from C or C++ to Delphi, I don’t rely on automated tools. These either produce wrong results, or no useful results at all. So I load the API header into the Delphi IDE, and manually convert all the code, often using search & replace and other editor features. But to make some awkward and repetitive tasks easier, I wrote this simple expert.

Conversion pack menu items

The old version used a very crude parser, but for this one I wrote a new C lexer and parser combination. They do not cover all aspects of the language, and take a few shortcuts, but they made the task of implementing new functionality much easier. They will not cope with every construct, but with most of them, which leaves only a few items to be converted manually.

I intend to add a few more menu items/features, but time will tell what and when.

Installation

I changed the package. Now there is a project for each version of Delphi I support (2006-2010), and a common directory for the shared units. Go to the directory for your version of Delphi (e.g. D2009) and open the convertpack.dproj, convertpack.bdsproj or convertpack.dpk file. In the project manager (or package manager), right click on the convertpack.bpl entry, and choose Install from the context menu that appears. After a few seconds, you should get a message that the file is installed.

In the zip file, a simple convertpack.ini is included. This is a copy of a recent convertpack.ini for conversions I have done myself. If you want, you can modify this with any text editor (including the Delphi IDE). If you copy this to the same directory as convertpack.bpl, it can serve as a start. If you don’t, the expert will create a new one, with a few commonly used defaults.

Features

At the moment, the expert implements these functions:

I will discuss each in short in the following paragraphs. Instead of using the menu items, you can also use the matching shortcuts directly. For repetetive tasks, this is probably easier.

Add JEDI Header

I try to make my conversions JEDI-compliant. One part of that is the JEDI header, with the legalese that comes with the MPL license. This item will add a compliant header to the top of the source file.

To make inserting strings in the header a little easier, a dialog pops up where you can enter the values in the header, like Unit name, Copyright holder, etc.

Insert $EXTERNALSYM

In C/C++, global symbols must be unique, otherwise the linker will report an error. To make the API header compatible with C++Builder, every global symbol which is also defined in the original header must be marked with an {$EXTERNALSYM SymbolName} directive. This will prevent that this symbol is stored in the generated object file, which could be used in a C++ program that uses the translation directly or indirectly. Instead, the symbol from the original library is used.

This menu item will add an $EXTERNALSYM directive for the symbol under the cursor, in the line above the symbol. Example: if you put the cursor somewhere on the URLMON_OPTION_USERAGENT identifier in the following code:

1
2
3
4
5
6
const
  // URLMON-specific defines for UrlMkSetSessionOption
  URLMON_OPTION_USERAGENT           = $10000001;
  URLMON_OPTION_USERAGENT_REFRESH   = $10000002;
  URLMON_OPTION_URL_ENCODING        = $10000004;
  URLMON_OPTION_USE_BINDSTRINGCREDS = $10000008;

the code will change to this:

1
2
3
4
5
6
7
const
  // URLMON-specific defines for UrlMkSetSessionOption
  {$EXTERNALSYM URLMON_OPTION_USERAGENT}
  URLMON_OPTION_USERAGENT           = $10000001;
  URLMON_OPTION_USERAGENT_REFRESH   = $10000002;
  URLMON_OPTION_URL_ENCODING        = $10000004;
  URLMON_OPTION_USE_BINDSTRINGCREDS = $10000008;

After this, the cursor will be on URLMON_OPTION_USERAGENT_REFRESH, so you can immediately repeat the action (for instance by pressing Ctrl+Alt+E) for that symbol, and so on, and so forth.

Swap Declarations

One of the most annoying parts in converting is the fact that declarations of parameters, fields and variables are backward (for a Delphi programmer). Turning such declarations around manually is very tedious, and error prone. This function will do this for you. The typedef and function declaration converters will convert entire structs, unions or function declarations, so this function may not be as necessary as it was in the old version. But sometimes, an unstandard construct requires manual corrections or a complete manual conversion.

If the converter stalls on an unknown macro definition or keyword, you can make it known to the parser by entering it in the Macros section of the Options dialog.

You use it by placing the cursor somewhere on the first word in the declaration, and pressing the Ctrl+Alt+S shortcut.

Convert to $HPPEMIT

Sometimes, to make the converted unit usable with C++Builder, you must tell the Delphi compiler to put some extra information in the .hpp file it generates (in C++Builder only). You can do this by issuing $HPPEMIT directives in your code. Usually this is a part of the original header code. This menu item allows you to turn a source line into a $HPPEMIT directive.

One example is from the UrlMon.pas file. The original code (slightly reformatted to make it fit on this page) is:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
//-------------------------------------------------------------
// if compilation errors occur while attempting to access
// structs, unions, or enums define NO_WIN32_LEAN_AND_MEAN so
// that the appropriate windows headers are included.
//-------------------------------------------------------------
#if defined(NO_WIN32_LEAN_AND_MEAN)
#include "rpc.h"
#include "rpcndr.h"
#include "urlmon.h"
  #ifndef COM_NO_WINDOWS_H
  #include "windows.h"
  #include "ole2.h"
  #endif
#endif

This code must be included in the .hpp file generated for C++Builder, so it is put in $HPPEMIT directives:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{$HPPEMIT '//-------------------------------------------------------------'}
{$HPPEMIT '// if compilation errors occur while attempting to access'}
{$HPPEMIT '// structs, unions, or enums define NO_WIN32_LEAN_AND_MEAN so'}
{$HPPEMIT '// that the appropriate windows headers are included.'}
{$HPPEMIT '//-------------------------------------------------------------'}
{$HPPEMIT '#if defined(NO_WIN32_LEAN_AND_MEAN)'}
{$HPPEMIT '#include "rpc.h"'}
{$HPPEMIT '#include "rpcndr.h"'}
{$HPPEMIT '#include "urlmon.h"'}
{$HPPEMIT '  #ifndef COM_NO_WINDOWS_H'}
{$HPPEMIT '  #include "windows.h" '}
{$HPPEMIT '  #include "ole2.h"'}
{$HPPEMIT '  #endif'}
{$HPPEMIT '#endif'}

The use is simple. Put the cursor on the line to be “$HPPEMITted”, and press the shortcut (or select the menu item).

Convert Typedef

This converts typedefs of structs, unions, enums and simple types to their Delphi equivalent. Currently, it will not convert typedefs of function pointers yet.

The converter is invoked by placing the cursor on the first line of the typedef and invoking the shortcut Ctrl+Alt+T or by clicking the corresponding menu item.

Simple typedefs

Simple type definitions like

1
typedef ULONG_PTR HCRYPTPROV;

will be converted to

1
2
3
4
5
6
////typedef ULONG_PTR HCRYPTPROV;

  PHcryptprov = ^THcryptprov;
  {$EXTERNALSYM HCRYPTPROV}
  HCRYPTPROV = ULONG_PTR;
  THcryptprov = ULONG_PTR;

As you can see, the conversion automatically added an $EXTERNALSYM directive and created a "Delphified" version of the declarator (THcryptproc), and a pointer type for this (PHcryptprov). The original declaration was commented out by prefixing it with ////. If you don’t need the original C declaration, you can simply delete it, or turn this feature off in the Options dialog. I chose to use //// so these lines can easily be found and removed using a search and replace with regular expressions, in the Delphi IDE.

The code which turns the original declarator into a Delphi-style name is rather simple. It adds a T in front of the declarator, and "CamelCaps" the name depending on the underscores in it. So a name like CERT_STORE_PROV_INFO is turned into TCertStoreProvInfo and a pointer type is also generated by replacing the leading T with a P (giving PCertStoreProvInfo).

Structs and unions

Type definitions of structs and unions are compeletely converted, a Delphified name and pointer type are generated. An example:

1
2
3
4
5
6
7
8
typedef struct _CERT_STORE_PROV_INFO {
    DWORD               cbSize;
    DWORD               cStoreProvFunc;
    void                **rgpvStoreProvFunc;
    HCERTSTOREPROV      hStoreProv;
    DWORD               dwStoreProvFlags;
    HCRYPTOIDFUNCADDR   hStoreProvFuncAddr2;
} CERT_STORE_PROV_INFO, *PCERT_STORE_PROV_INFO;

This results in:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
////typedef struct _CERT_STORE_PROV_INFO {
////    DWORD               cbSize;
////    DWORD               cStoreProvFunc;
////    void                **rgpvStoreProvFunc;
////    HCERTSTOREPROV      hStoreProv;
////    DWORD               dwStoreProvFlags;
////    HCRYPTOIDFUNCADDR   hStoreProvFuncAddr2;
////} CERT_STORE_PROV_INFO, *PCERT_STORE_PROV_INFO;

  PCertStoreProvInfo = ^TCertStoreProvInfo;
  {$EXTERNALSYM _CERT_STORE_PROV_INFO}
  _CERT_STORE_PROV_INFO = record
    cbSize: DWORD;
    cStoreProvFunc: DWORD;
    rgpvStoreProvFunc: PPointer;
    hStoreProv: HCERTSTOREPROV;
    dwStoreProvFlags: DWORD;
    hStoreProvFuncAddr2: HCRYPTOIDFUNCADDR;
  end;
  {$EXTERNALSYM CERT_STORE_PROV_INFO}
  CERT_STORE_PROV_INFO = _CERT_STORE_PROV_INFO;
  {$EXTERNALSYM PCERT_STORE_PROV_INFO}
  PCERT_STORE_PROV_INFO = ^_CERT_STORE_PROV_INFO;
  TCertStoreProvInfo = _CERT_STORE_PROV_INFO;

As you can see, this is a complete conversion of the typedef. Just like in the case of the simple typedef, a Delphified name and pointer type are generated, and each of the declarators (CERT_STORE_PROV_INFO and PCERT_STORE_PROV_INFO) got its own declaration and $EXTERNALSYM directive.

The double pointer rgpvStoreProvFunc is declared as PPointer because the converter knows that void* is Pointer, and every extra * simply adds an extra P prefix to the type name. The converter will not declare the resulting type, so this may be something you will have to do, manually.

Unions will not be fully converted to Delphi syntax. I simply use record syntax, but with the invalid "keyword" union instead of record. I will probably enhance this to turn them into real variant record definitions using case and make that optional. For now, you’ll have to do that manually.

Structs containing anonymous unions, i.e. unions that have no name, are possible in C, but not in Delphi. The parser currently chokes on them, but I plan to add a feature to have them named u, u2, u3, etc., automatically. But see my conversion article about this. Unions can be tricky, since they are not exactly the same as variant records in Delphi.

Enums

Like structs, enums are fully converted. The parser does, however, not evaluate the constant expression defining an enumerator. If an enumerator has a defined value (indicated by ‘=’ followed by a constant expression), then the parser merely parses up to the next ‘,’ or ‘}’, whichever comes first. It only converts C operators to their Delphi equivalent, and reformats the expression and eventual comments a bit. Usually this is good enough, but sometimes this requires some manual editing.

An example of a converted enum (I snipped a few values in the middle) follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
////typedef /* [public][helpstring] */
////enum __MIDL___MIDL_itf_danim_0134_0001
////    {   DAQUAL_AA_TEXT_ON               = 1L << 0,
////        DAQUAL_AA_TEXT_OFF              = 1L << 1,
////        // values snipped here...
////        DAQUAL_QUALITY_TRANSFORMS_ON    = 1L << 10,
////        DAQUAL_QUALITY_TRANSFORMS_OFF   = 1L << 11
////    }   DA_IMAGE_QUALITY_FLAGS;

  { [public][helpstring] }
  PDaImageQualityFlags = ^TDaImageQualityFlags;
  {$EXTERNALSYM __MIDL___MIDL_itf_danim_0134_0001}
  __MIDL___MIDL_itf_danim_0134_0001 = (
    DAQUAL_AA_TEXT_ON             = 1  shl 0,
    DAQUAL_AA_TEXT_OFF            = 1  shl 1,
    // values snipped here...
    DAQUAL_QUALITY_TRANSFORMS_ON  = 1  shl 10,
    DAQUAL_QUALITY_TRANSFORMS_OFF = 1  shl 11
  );
  {$EXTERNALSYM DA_IMAGE_QUALITY_FLAGS}
  DA_IMAGE_QUALITY_FLAGS = __MIDL___MIDL_itf_danim_0134_0001;
  TDaImageQualityFlags = __MIDL___MIDL_itf_danim_0134_0001;

Note that the enum above should actually be converted to a simple type representing a DWORD and a number of const declarations, since these numbers will apparently be used as bit flags. I plan to make this an option, for such enums.

You can also use the typedef converter to convert normal enum, struct or union declarations that are not typedefs, In other words, the keyword typedef can be missing.

Convert Function Declaration

Converting a function declarations is not so different from converting a struct declaration. In C, functions have return types and can have some extra specifiers like extern “C” or “C++”, const or volatile, pointers, __stdcall or __declspec, etc. The converter knows these things and converts them correctly (well, actually, it ignores most of them).

Macros, like WINAPI and STDAPI, but also obsolete ones like FAR can be recognized. The converter knows a few of them already, and you can enter more of them and their definitions in the Macros tab of the Options dialog.

Parameters are parsed by the same code as members of a struct, and produce a similar result. The converter will format the function declaration and add the correct calling convention and return type. It will also add an $EXTERNALSYM declaration.

The converter is invoked by putting the cursor on the first word to be converted and pressing the shortcut Ctrl+Alt+F.

An example of a converted function follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
////WINCRYPT32API
////BOOL
////WINAPI
////CryptEncodeObjectEx(
////    __in DWORD dwCertEncodingType,
////    __in LPCSTR lpszStructType,
////    __in const void *pvStructInfo,
////    __in DWORD dwFlags,
////    __in_opt PCRYPT_ENCODE_PARA pEncodePara,
////    __out_opt void *pvEncoded,
////    IN __out DWORD *pcbEncoded
////    );

{$EXTERNALSYM CryptEncodeObjectEx}
function CryptEncodeObjectEx(
  dwCertEncodingType: DWORD;
  lpszStructType: LPCSTR;
  pvStructInfo: Pointer;
  dwFlags: DWORD;
  pEncodePara: PCRYPT_ENCODE_PARA;
  pvEncoded: Pointer;
  var pcbEncoded: DWORD
): BOOL; winapi;

While the converter has a way to define macro definitions and their subsitutions, it currently won’t handle macros with parameters like __out_bcount_part_opt(*pcbFormat, *pcbFormat), as you can see in some API headers (I found this in wincrypt.h). So to convert a declaration like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
WINCRYPT32API
BOOL
WINAPI
CryptFormatObject(
    __in DWORD dwCertEncodingType,
    __in DWORD dwFormatType,
    __in DWORD dwFormatStrType,
    __in_opt void *pFormatStruct,
    __in_opt LPCSTR lpszStructType,
    __in_bcount(cbEncoded) const BYTE *pbEncoded,
    __in DWORD cbEncoded,
    __out_bcount_part_opt(*pcbFormat, *pcbFormat) __typefix(WCHAR *) void *pbFormat,
    __inout DWORD *pcbFormat
    );

you will currently have to remove the macros with parameters manually. They can usually be ignored anyway. (I plan to change this, so you can also specify macros which have parameters in the Options dialog. Since they will be ignored anyway, I’ll simply make the lexer or parser read over the parameter list).

You won’t have to remove macros like __in or __in_opt, if they were entered in the Macros section of the Options dialog already.

Convert #define

In C, #define is often used to declare true constants. The converter converts these kind of macros just as well as singe line macros with parameters. The text of the simple macros is treated like the defined value of an enumerator: the text is read up to the end of the line and operators are converted to their Delphi equivalents. Multi-line macros are not supported yet.

Macros with parameters are converted to simple function declarations, where each parameter gets the type DWORD and the return value is a DWORD too. The text of the macro is turned into a function body commented out by (* and *). An example of both follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// ALG_ID crackers
#define GET_ALG_CLASS(x)                (x & (7 << 13))
#define GET_ALG_TYPE(x)                 (x & (15 << 9))
#define GET_ALG_SID(x)                  (x & (511))

// Algorithm classes
// certenrolld_begin -- ALG_CLASS_*
#define ALG_CLASS_ANY                   (0)
#define ALG_CLASS_SIGNATURE             (1 << 13)
#define ALG_CLASS_MSG_ENCRYPT           (2 << 13)
#define ALG_CLASS_DATA_ENCRYPT          (3 << 13)

After invoking the converter a few times, this looks like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// ALG_ID crackers
{$EXTERNALSYM GET_ALG_CLASS}
function GET_ALG_CLASS(x: DWORD): DWORD;
(*
begin
  Result := (x and (7 shl 13));
end;
*)
{$EXTERNALSYM GET_ALG_TYPE}
function GET_ALG_TYPE(x: DWORD): DWORD;
(*
begin
  Result := (x and (15 shl 9));
end;
*)
{$EXTERNALSYM GET_ALG_SID}
function GET_ALG_SID(x: DWORD): DWORD;
(*
begin
  Result := (x and (511));
end;
*)

// Algorithm classes
// certenrolld_begin -- ALG_CLASS_*
  {$EXTERNALSYM ALG_CLASS_ANY}
  ALG_CLASS_ANY                       = (0);
  {$EXTERNALSYM ALG_CLASS_SIGNATURE}
  ALG_CLASS_SIGNATURE                 = (1 shl 13);
  {$EXTERNALSYM ALG_CLASS_MSG_ENCRYPT}
  ALG_CLASS_MSG_ENCRYPT               = (2 shl 13);
  {$EXTERNALSYM ALG_CLASS_DATA_ENCRYPT}
  ALG_CLASS_DATA_ENCRYPT              = (3 shl 13);

You invoke the functionality by placing the cursor on the line with the macro and pressing the shortcut Alt+Ctrl+D or selecting the corresponding menu item. The macro is converted, and the cursor is placed on the next line.

Conversion Pack Options

The dialog allows you to enter two lists for the Swap Declarations tool. One is a list of replacements for macros used in headers and the other a list of obsolete macros, like __RPC_FAR_, which have no meaning and can be ignored.

Legalese

The Conversion Helper Package (convertpack.zip) is freeware. All rights are reserved. Its code is provided as is, expressly without a warranty of any kind. You use it at your own risk.

I hope this code is useful to you. If you use some of it, please credit me. If you modify or improve the expert, please send me the modifications.

I may improve or enhance the expert myself, and I will try to post changes here. But this is not a promise. Please don’t request features.

Rudy Velthuis

Standard Disclaimer for External Links

These links are being provided as a convenience and for informational purposes only; they do not constitute an endorsement or an approval of any of the products, services or opinions of the corporation or organization or individual. I bear no responsibility for the accuracy, legality or content of the external site or for that of subsequent links. Contact the external site for answers to questions regarding its content.

Disclaimer and Copyright

The coding examples presented here are for illustration purposes only. The author takes no responsibility for end-user use. All content herein is copyrighted by Rudy Velthuis, and may not be reproduced in any form without the author's permission. Source code written by Rudy Velthuis presented as download is subject to the license in the files.

Back to top