In my previous post on libclang for Delphi, I mentioned an obvious use case for libclang: to build a (better) C-to-Pascal header converter. I also suggested I may provide such a tool in the future.

Well, the future is here and I put my money where my mouth is. The result is Chet, a sort of acronym for C Header Translator (although Cheat would technically be a better name, but let’s not).

You can find Chet on my personal GitHub page in the Chet repository. The repository contains not only the source code, but also a ready-to-run 64-bit Windows executable if you prefer. The front page and readme of the repository also have a User Guide that helps to get the best conversion results.

Features

Some notable features are:

Translates C data types such as structs, union, enums, typedefs, procedural types and opaque types.

Translates C functions to Delphi procedures or functions.

Tries to translate #define declarations to constants where possible.

declarations to constants where possible. Generates a single .pas file for an entire directory of .h files. This reduces issues due to dependencies between header files.

file for an entire directory of files. This reduces issues due to dependencies between header files. Generates output for multiple platforms (Winsows, macOS, Linux, iOS and Android) if desired.

You can customize the Clang parsing process by supplying command line arguments to the compiler.

You can customize the output of some conversion operations.

Retains any Doxygen style documentation comments if desired, or converts them to XmlDoc comments or PasDoc comments.

Provides a GUI for configuring the conversion process, and allows you to save the conversion settings to a .chet project file for reuse.

Limitations

Chet has limitations as well though, including:

Chet only works with C header files, not with C++ header files.

All non-inlined functions in the header files are translated and assumed to be available in the static or dynamic library for the project. This does not have to be the case however. (Inlined functions are never translated since they are never exported by the library.)

Since Clang is used to parse the header files, this means that Clangs preprocessor is run as well to perform conditional parsing (guided by #ifdef directives). This is both good and bad. It is good because it improves conversion accuracy. But it can be bad because it uses the system that Chet runs on to determine some conditional paths. For example, because Chet runs on Windows, it will parse code in #ifdef _WIN32 sections but skip any code in sections for other platforms.

Even though, I was able to run Chet on a wide variety of real-life header files and found that many conversions would compile in Delphi without any manual edits. Some of the header files I tested include:

libclang: the header files for libclang itself. That would have saved me some trouble when I created the libclang for Delphi project.

Duktape: as could have been used by the Duktape for Delphi project.

ZeroMQ: as could have been used for our Scalable Backend article.

Angle: OpenGL ES for Windows.

FFMPEG: audio and video tools. Notoriously hard to convert because of FFMPEG’s use of incomprehensible macros and uncommon C constructs.

Speex and OPUS: audio codecs.

SDL: graphics, audio and input library.

However, don’t expect that you don’t need to edit the generated Pascal files. There will almost always be declarations that could not be converted or are better converted manually (especially when it comes to macros).

Sample Conversion

For a quick example, consider the following header file:

#define THE_ANSWER 42 #define FIFTY ((THE_ANSWER>>1) & 0x0F) * 10 typedef float Float32; enum Color { Red = 2, Green = 5, Blue = 7 }; struct Person { char name[10]; int age; }; typedef void (*MessageCallback)(char* message); int Add(int a, int b);

Chet converts it to this Pascal file (partially shown):

const THE_ANSWER = 42; FIFTY = ((THE_ANSWER shr 1) and $0F) * 10; type // Forward declarations PPerson = ^Person; Float32 = Single; Color = ( Red = 2, Green = 5, Blue = 7); Person = record name: array [0..9] of UTF8Char; age: Integer; end; MessageCallback = procedure(message: PUTF8Char); cdecl; function Add(a: Integer; b: Integer): Integer; cdecl; external LIB_SAMPLE name _PU + 'Add';

Requirements

Since Chet uses an actual compiler, you will need to have a (minimal) C development environment installed, as well as LLVM with Clang. Clang needs to be able to find the system headers for the development environment. These will usually be available if you have some version of Visual Studio with Visual C++ installed. The free (community) edition of Visual Studio suffices.

I am not entirely sure of these requirements. My development systems all have a version of Visual Studio installed, and libclang uses its header files. I don’t know what happens without Visual Studio though, since I haven’t been able to test on a clean machine. If you have a better answer, I would love to know!

You can run Chet first to check for any errors related to missing dependencies. If you get any dependency errors when running the translator, then you can download the dependencies here:

LLVM with Clang (Clang for Windows (64-bit) is recommended).

Visual Studio IDE (the free Community edition suffices; be sure to install C++ support).

If you want to compile Chet yourself, then you also need libclang for Delphi and make sure the Delphi IDE can find it (the Chet project will find it automatically if the Neslib.Clang directory is at the same level as the Chet directory).

Chet as a Library

Chet is mainly a GUI application that assists in performing header conversions. But you can also it as a library to integrate into your own tools. The two classes you need are:

TProject : a class that holds the conversion settings for a particular project. You can set the configuration manually or save and load them from a file.

: a class that holds the conversion settings for a particular project. You can set the configuration manually or save and load them from a file. THeaderTranslator : the class that performs the actual header translations. It gets its settings from a TProject and provides an OnMessage event to fire progress messages.

I tried to keep the code clean and easy. But unfortunately there are quite a few esoteric C constructs that require special handling, resulting in more if SpecialSituation then... code than I would prefer.

You Can Help

Chet is in no way perfect, but can already handle a wide variety of scenarios. If you find that Chet fails at a specific scenario, and the scenario is pretty common, then please post an issue on the Chet Issues page on GitHub (and include the problematic header file). Of course, your pull requests are welcome as well!