Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/atilaneves/dpp

Directly include C headers in D source code
https://github.com/atilaneves/dpp

clang cpp cpp17 d dlang dlanguage libclang translation

Last synced: about 17 hours ago
JSON representation

Directly include C headers in D source code

Awesome Lists containing this project

README

        

d++ - #include C and C++ headers in D files
====================================================

[![CI](https://github.com/atilaneves/dpp/actions/workflows/d.yml/badge.svg)](https://github.com/atilaneves/dpp/actions/workflows/d.yml)
[![Coverage](https://codecov.io/gh/atilaneves/dpp/branch/master/graph/badge.svg)](https://codecov.io/gh/atilaneves/dpp)
[![Open on run.dlang.io](https://img.shields.io/badge/run.dlang.io-open-blue.svg)](https://run.dlang.io/is/JK0CAf)

Goal
----

To directly `#include` C and C++ headers in [D](https://dlang.org) files and have the same semantics and ease-of-use
as if the file had been `#included` from C or C++ themselves. Warts and all, meaning that C `enum` declarations
will pollute the global namespace, just as it does "back home".

This work was supported by [Symmetry Investments](http://symmetryinvestments.com/).

Example
-------

```c
// c.h
#ifndef C_H
#define C_H

#define FOO_ID(x) (x*3)

int twice(int i);

#endif
```

```c
// c.c
int twice(int i) { return i * 2; }
```

```d
// foo.dpp
#include "c.h"
void main() {
import std.stdio;
writeln(twice(FOO_ID(5))); // yes, it's using a C macro here!
}
```

At the shell:

```
$ gcc -c c.c
$ d++ foo.dpp c.o
$ ./foo
$ 30
```

[![Open on run.dlang.io](https://img.shields.io/badge/run.dlang.io-open-blue.svg)](https://run.dlang.io/is/WwpvhT)

C++ support
-----------

C++ support is currently limited. Including any header from the C++
standard library is unlikely to work. Simpler headers might, the
probability rising with how similar the C++ dialect used is to
C. Despite that, dpp currently does try to translate classes,
templates and operator overloading. It's unlikely to work on
production headers without judicious use of the `--ignore-cursor` and
`--ignore-namespace` command-line options. When using these, the user
can then define their own versions of problematic declarations such as
`std::vector`.

Limitations
-----------

* Only known to work on Linux with libclang versions 6 and up. It might work in different conditions.
* When used on multiple files, there might be problems with duplicate definitions depending on imports.
It is recommended to put all `#include`s in one `.dpp` file and import the resulting D module.
* Not currently able to translate Linux kernel headers.

Success stories
--------------

Known project headers whose translations produce D code that compiles:

* nanomsg/nn.h, nanomsg/pubsub.h
* curl/curl.h
* stdio.h, stdlib.h
* pthread.h
* julia.h
* xlsxwriter.h
* libvirt/libvirt.h, libvirt/virterror.h
* libzfs
* openssl/ssl.h
* imapfilter.h
* libetpan/libetpan.h
* Python.h

Compilation however doesn't guarantee they work as expected and YMMV. Please consult the examples.

Command-line arguments
----------------------

It is likely that the header or headers need `-I` flags to indicate paths to be searched,
both by this executable and by libclang itself. The `--include-path` option can be
used for that, once for each such path.

Use `-h` or `--help` to learn more.

Details
-------

`d++` is an executable that wraps a D compiler such as dmd (the default) so that D files with `#include`
directives can be compiled.

It takes a `.dpp` file and outputs a valid D file that can be compiled. The original can't since D
has no preprocessor, so the `.dpp` file is "quasi-D", or "D with #include directives".
The only supported C preprocessor directive is `#include`.

The input `.dpp` file may also use C preprocessor macros defined in the file(s) it `#include`s, just as a C/C++
program would (see the example above). It may not, however, define macros of its own.

`d++` goes through the input file line-by-line, and upon encountering an `#include` directive, parses
the file to be included with libclang, loops over the definitions of data structures and functions
therein and expands in-place the relevant D translations. e.g. if a header contains:

```c
uint16_t foo(uint32_t a);
```

The output file will contain:

```d
ushort foo(uint a);
```

d++ will also enclose each one of these original `#include` directives with either
`extern(C) {}` or `extern(C++) {}` depending on the header file name and/or command-line options.

As part of expanding the `#include`, and as well as translating declarations, d++ will also
insert text to define macros originally defined in the `#include`d translation unit so that these
macros can be used by the D program. The reason for this is that nearly every non-trivial
C API requires the preprocessor to use properly. It is possible to mimic this usage in D
with enums and CTFE, but the result is not guaranteed to be the same. The only way to use a
C or C++ API as it was intended is by leveraging the preprocessor.

Trivial literal macros however(e.g. `#define THE_ANSWER 42`) are translated as
D enums.

As a final pass before writing the output D file, d++ will run the C
preprocessor (currently the cpp binary installed on the system) on the
intermediary result of expanding all the `#include` directives so that
any used macros are expanded, and the result is a D file that can be compiled.

In this fashion a user can write code that's not-quite-D-but-nearly that can "natively"
call into a C/C++ API by `#include`ing the appropriate header(s).

Translation notes
----------------

### enum

For convenience, this declaration:

```c
enum Enum { foo, bar, baz }
```

Will generate this translation:

```d
enum Enum { foo, bar, baz }
enum foo = Enum.foo;
enum bar = Enum.bar;
enum baz = Enum.baz;
```

This is to mimic C semantics with regards to the global namespace whilst also allowing
one to, say, reflect on the enum type.

### Renaming enums

There is the ability to rename C enums. With the following C definition:

```c
enum FancyWidget { Widget_foo, Widget_bar }
```

Then adding this to your .dpp file after the `#include` directive:

```d
mixin dpp.EnumD!("Widget", // the name of the new D enum
FancyWidget, // the name of the original C enum
"Widget_"); // the prefix to cut out
```

will yield this translation:

```d
enum Widget { foo, bar }
```

### Names of structs, enums and unions

C has a different namespace for the aforementioned user-defined types. As such, this is legal C:

```c
struct foo { int i; };
extern int foo;
```

The D translations just use the short name for these aggregates, and if there is a name collision
with a variable or function, the latter two get renamed and have a `pragma(mangle)` added to
avoid linker failures:

```d
struct foo { int i; }
pragma(mangle, "foo") extern export __gshared int foo_;
```

### Functions or variables with a name that is a D keyword

Similary to name collisions with aggregates, they get an underscore
appended and a `pragma(mangle)` added so they link:

```c
void debug(const char* msg);
```

Becomes:

```d
pragma(mangle, "debug")
void debug_(const(char)*);
```

Build Instructions
------------------
```
dub install dpp
```

After the instructions for your OS (see below), you can use this commands to run dpp:

```
dub run dpp -- yoursourcefilenamehere.dpp
```

Note: for a reproducible and cross-platform build environment, you can run [setup-cpp](https://github.com/aminya/setup-cpp) with `--llvm=11.0.0`. This will set up LLVM 11.0.0 and the proper environment variables.

### Windows

Install [LLVM](https://github.com/llvm/llvm-project/releases/download/llvmorg-12.0.0/LLVM-12.0.0-win64.exe) into `C:\Program Files\LLVM\`, making sure to tick the "Add LLVM to the system PATH for all users" option.

If `libclang.lib` was not found, put the `lib` folder of the llvm directory on the PATH.

### Linux

If `libclang` is not installed, install `libclang-10-dev` with apt: `sudo apt-get install -y -qq libclang-10-dev`

If `libclang.so` was not found, link it using the following command (adjust the installation path and the llvm version):
```
sudo ln -s path_to_llvm/lib/libclang-12.so.1 /lib/x86_64-linux-gnu/libclang.so
```

### MacOS

If using an external LLVM installation, add these to your `~/.bash_profile`

```bash
LLVM_PATH="/usr/local/opt/llvm/" # or any other path
LLVM_VERSION="11.0.0"
export PATH="$LLVM_PATH:$PATH"
export SDKROOT=$(xcrun --sdk macosx --show-sdk-path)
export LD_LIBRARY_PATH="$LLVM_PATH/lib/:$LD_LIBRARY_PATH"
export DYLD_LIBRARY_PATH="$LLVM_PATH/lib/:$DYLD_LIBRARY_PATH"
export CPATH="$LLVM_PATH/lib/clang/$LLVM_VERSION/include/"
export LDFLAGS="-L$LLVM_PATH/lib"
export CPPFLAGS="-I$LLVM_PATH/include"
export CC="$LLVM_PATH/bin/clang"
export CXX="$LLVM_PATH/bin/clang++"
```

(adjust the clang version and the external llvm installation path.)

Then run `source ~/.bash_profile`

If `libclang.dylib` was not found, link it using the following command (adjust the installation path):
```
ln -s path_to_llvm/lib/libclang.dylib /usr/local/opt/llvm/lib/libclang.dylib
```