Skip to content

Commit

Permalink
Csharp support (#45)
Browse files Browse the repository at this point in the history
* migrate C++/CLI support from https://github.com/iit-reacts/djinni to new project structure
* add documentation of C++/CLI support
* update integration tests to include testing for C++/CLI
* add additional commandline options that where missing in the iit-reacts implementation:
    * --cppcli-base-lib-include-prefix
    * --cppcli-include-cpp-prefix
* fix some IntelliJ IDEA Code Analysis warnings
* improve "generated code usage" documentation
* make sure the generated file list always uses unix paths, even on Windows. This is helpful as CMake (which is eventually the tool that will process the file) cannot handle Windows paths.
* add python support to Readme feature list.
  • Loading branch information
jothepro authored Apr 23, 2021
1 parent 8d0b533 commit a82dc9f
Show file tree
Hide file tree
Showing 42 changed files with 1,862 additions and 167 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@ Djinni generator parses an interface definition file and generates:
- C++ implementations of types (enums, records)
- Java implementations of types
- Objective-C implementations of types
- Python implementation of types
- C++/CLI implementation of types
- C++ code to convert between C++ and Java over JNI
- Objective-C++ code to convert between C++ and Objective-C.
- Objective-C++ code to convert between C++ and Objective-C
- Python and C code to convert between C++ and Python over CFFI
- C++/CLI code to convert between C++ and C#


## Installation
Expand Down
1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ lazy val djinni = (project in file("."))
libraryDependencies += "org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.2",
libraryDependencies += "org.yaml" % "snakeyaml" % "1.26",
libraryDependencies += "com.github.scopt" %% "scopt" % "3.7.1",
libraryDependencies += "commons-io" % "commons-io" % "2.8.0",
assemblyOutputPath in assembly := { file("target/bin") / (assemblyJarName in assembly).value },
assemblyJarName in assembly := s"${name.value}",
assemblyOption in assembly := (assemblyOption in assembly).value.copy(prependShellScript = Some(defaultUniversalScript(shebang = false))),
Expand Down
24 changes: 23 additions & 1 deletion docs/cli-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,16 @@ djinni \
| `--c-wrapper-base-lib-include-prefix <prefix>` | The C wrapper base support library's include path (default: `djinni/cwrapper/`). |
| `--py-import-prefix <import-prefix>` | The import prefix used within python generated files (default: "") |

### C++/CLI

| Argument | Description |
| -------- | ----------- |
| `--cppcli-out <out-folder>` | The output folder for C++/CLI files (Generator disabled if unspecified). |
| `--cppcli-namespace ...` | The namespace name to use for generated C++/CLI classes. |
| `--cppcli-include-cpp-prefix` | The prefix for #include of the main C++ header files from C++/CLI files. |
| `--cppcli-base-lib-include-prefix ...` | The C++/CLI base support library's include path (default: `djinni/cppcli/`). |


### Yaml Generation

| Argument | Description |
Expand Down Expand Up @@ -177,7 +187,7 @@ Possible values: `FooBar`, `fooBar`, `foo_bar`, `FOO_BAR`, `m_fooBar`.

#### Objective C

| Argument | Default |
| Argument | Default |
| -------- | -------- |
| `--ident-objc-enum` | `FooBar` |
| `--ident-objc-field` | `fooBar` |
Expand All @@ -200,6 +210,18 @@ Possible values: `FooBar`, `fooBar`, `foo_bar`, `FOO_BAR`, `m_fooBar`.
| `--ident-py-enum` | `Foo_Bar` |
| `--ident-py-const` | `FOO_BAR` |

#### C++/CLI

| Argument | Default |
| -------- | -------- |
| `--ident-cppcli-type` | `FooBar` |
| `--ident-cppcli-type-param` | `FooBar` |
| `--ident-cppcli-property` | `FooBar` |
| `--ident-cppcli-method` | `FooBar` |
| `--ident-cppcli-local` | `fooBar` |
| `--ident-cppcli-enum` | `FooBar` |
| `--ident-cppcli-const` | `FooBar` |
| `--ident-cppcli-file` | `FooBar` |

Example:

Expand Down
3 changes: 2 additions & 1 deletion docs/developer-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ sbt "run
--objc-out out/objc
--py-out out/python
--pycffi-out out/cffi
--c-wrapper-out out/cwrapper"
--c-wrapper-out out/cwrapper
--cppcli-out out/cppcli"
```

```bash
Expand Down
23 changes: 23 additions & 0 deletions docs/generated-code-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,26 @@ your C++ library through the CFFI bridge.
* External types defined in YAML are not yet supported.
* Use of non-nullable pointers is not yet supported.


## C++/CLI / C++ Project

C++/CLI is a technology by Microsoft that provides interoperability of C++ with Microsoft .NET languages such as C#.
It is only supported on Windows.

Djinni generates a shallow C++/CLI wrapper around the C++ interfaces. Once compiled to a shared library, the resulting `dll`
just needs to be added to your C# project as reference, and you can call your Djinni interfaces from C# like any other .NET library.

### Includes & Build target

The following code will be generated for each defined type:

| Type | C++ header | C++ source | C++/CLI header/sources |
|------------|------------------------|----------------------------|-------------------------------------|
| Enum/Flags | my\_enum.hpp | | MyEnum.hpp, MyEnum.cpp |
| Record | my\_record.hpp | my\_record.cpp | MyRecord.hpp, MyRecord.cpp |
| Interface | my\_interface.hpp | my\_interface.cpp (+) | MyInterface.hpp, MyInterface.cpp |

Add all generated files to your build target, and link against the [djinni-support-lib](https://github.com/cross-language-cpp/djinni-support-lib).

C++/CLI sources have to be compiled with MSVC and the [`/clr` (Common Language Runtime Compilation)](https://docs.microsoft.com/en-us/cpp/build/reference/clr-common-language-runtime-compilation?view=msvc-160) option.

61 changes: 34 additions & 27 deletions docs/idl.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ my_cpp_interface = interface +c {
const version: i32 = 1;
}
# This interface will be implemented in Java, ObjC and Python and can be called from C++.
my_client_interface = interface +j +o +p {
# This interface will be implemented in Java, ObjC, Python and C# and can be called from C++.
my_client_interface = interface +j +o +p +s {
log_string(str: string): bool;
}
```
Expand All @@ -78,16 +78,18 @@ The available data types for a record, argument, or return value are:
- Primitives (`i8`, `i16`, `i32`, `i64`, `f32`, `f64`).
- Strings (`string`)
- Binary (`binary`). This is implemented as `std::vector<uint8_t>` in C++, `byte[]` in Java,
`NSData` in Objective-C, and an object supporting the `buffer` interface in Python.
`NSData` in Objective-C, an object supporting the `buffer` interface in Python, and `System.Array<System.Byte>` in C#.
- Date (`date`). This is `chrono::system_clock::time_point` in C++, `Date` in Java,
`NSDate` in Objective-C, and `datetime.datetime` in Python.
`NSDate` in Objective-C, `datetime.datetime` in Python, and `System.DateTime` in C#.
- List (`list<type>`). This is `vector<T>` in C++, `ArrayList` in Java, `NSArray`
in Objective-C, and `List` in Python. Primitives in a list will be boxed in Java and Objective-C.
in Objective-C, `List` in Python, and `System.Collections.Generic.List` in C#.
Primitives in a list will be boxed in Java and Objective-C.
- Set (`set<type>`). This is `unordered_set<T>` in C++, `HashSet` in Java, `NSSet` in
Objective-C, and `Set` in Python. Primitives in a set will be boxed in Java and Objective-C.
Objective-C, `Set` in Python, and `System.Collections.Generic.HashSet` in C#.
Primitives in a set will be boxed in Java and Objective-C.
- Map (`map<typeA, typeB>`). This is `unordered_map<K, V>` in C++, `HashMap` in Java,
`NSDictionary` in Objective-C, and `Dictionary` in Python. Primitives in a map will be boxed in Java
and Objective-C.
`NSDictionary` in Objective-C, `Dictionary` in Python, and `System.Collections.Generic.Dictionary` in C#.
Primitives in a map will be boxed in Java and Objective-C.
- Enumerations / Flags
- Optionals (`optional<typeA>`). This is `std::experimental::optional<T>` in C++11, object /
boxed primitive reference in Java (which can be `null`), and object / NSNumber strong
Expand All @@ -96,14 +98,16 @@ The available data types for a record, argument, or return value are:
deep-copy the contents.

## Types

An IDL file can contain 4 kinds of declarations: enums, flags, records, and interfaces.

* [**Enums**](#enums) become C++ enum classes, Java enums, ObjC `NS_ENUM`s, or Python `IntEnum`s.
* [**Flags**](#flags) become C++ enum classes with convenient bit-oriented operators, Java enums with `EnumSet`, ObjC `NS_OPTIONS`, or Python `IntFlag`s.
* [**Enums**](#enums) become C++ enum classes, Java enums, ObjC `NS_ENUM`s, Python `IntEnum`s, or C# `System.Enum`s.
* [**Flags**](#flags) become C++ enum classes with convenient bit-oriented operators, Java enums with `EnumSet`,
ObjC `NS_OPTIONS`, Python `IntFlag`s, or C# `System.Enum`s with the [`[Flags]` Attribute](https://docs.microsoft.com/en-us/dotnet/api/system.flagsattribute?view=net-5.0).
* [**Records**](#records) are pure-data value objects.
* [**Interfaces**](#interfaces) are objects with defined methods to call (in C++, passed by `shared_ptr`). Djinni
produces code allowing an interface implemented in C++ to be transparently used from ObjC,
Java, or Python and vice versa.
Java, Python or C#, and vice versa.

### Enums

Expand All @@ -116,7 +120,7 @@ my_enum = enum {
```

Enums are translated to C++ `enum class`es with underlying type `int`, ObjC `NS_ENUM`s with
underlying type `NSInteger`, Java enums, and Python `IntEnum`s.
underlying type `NSInteger`, Java enums, Python `IntEnum`s, and C# `System.Enum`s.

### Flags

Expand All @@ -132,8 +136,8 @@ my_flags = flags {

Flags are translated to C++ `enum class`es with underlying type `unsigned` and a generated set of
overloaded bitwise operators for convenience, ObjC `NS_OPTIONS` with underlying type `NSUInteger`,
Java `EnumSet<>`, and Python `IntFlag`. Contrary to the above enums, the enumerants of flags
represent single bits instead of integral values.
Java `EnumSet<>`, Python `IntFlag`, and C# `System.Enum`s with the [`[Flags]` Attribute](https://docs.microsoft.com/en-us/dotnet/api/system.flagsattribute?view=net-5.0).
Contrary to the above enums, the enumerants of flags represent single bits instead of integral values.

In the above example the elements marked with `none` and `all` are given special meaning. In C++,
ObjC, and Python the `no_flags` option is generated with a value that has no bits set (i.e. `0`),
Expand Down Expand Up @@ -168,7 +172,7 @@ records (so a record cannot contain itself).
#### Extensions

To support extra fields and/or methods, a record can be "extended" in any language. To extend a
record in a language, you can add a `+c` (C++), `+j` (Java), `+o` (ObjC), or `+p` (Python) flag
record in a language, you can add a `+c` (C++), `+j` (Java), `+o` (ObjC), `+p` (Python), or `+s` (C#) flag
after the record tag. The generated type will have a `Base` suffix, and you should create a derived
type without the suffix that extends the record type.

Expand All @@ -186,7 +190,7 @@ another_record = record {

For record types, Haskell-style "deriving" declarations are supported to generate some common
methods. Djinni is capable of generating equality and order comparators, implemented as operator
overloading in C++ and standard comparison functions in Java, Objective-C, and Python.
overloading in C++ and standard comparison functions in Java, Objective-C, Python and C#.

!!! note

Expand All @@ -209,52 +213,55 @@ my_cpp_interface = interface +c {
const version: i32 = 1;
}
# This interface will be implemented in Java, ObjC, and Python and can be called from C++.
my_client_interface = interface +j +o +p {
# This interface will be implemented in Java, ObjC, Python and C# and can be called from C++.
my_client_interface = interface +j +o +p +s {
log_string(str: string): bool;
}
```

Interfaces are objects with defined methods to call (in C++, passed by `shared_ptr`). Djinni
produces code allowing an interface implemented in C++ to be transparently used from ObjC,
Java or Python and vice versa.
Java Python or C# and vice versa.

#### Special Methods for C++ Only
`+c` interfaces (implementable only in C++) can have methods flagged with the special keywords const and static which have special effects in C++:
`+c` interfaces (implementable only in C++) can have methods flagged with the special keywords const and static which
have special effects in C++:

special_methods = interface +c {
const accessor_method();
static factory_method();
}

- `const` methods will be declared as const in C++, though this cannot be enforced on callers in other languages, which lack this feature.
- `static` methods will become a static method of the C++ class, which can be called from other languages without an object. This is often useful for factory methods to act as a cross-language constructor.
- `static` methods will become a static method of the C++ class, which can be called from other languages without an object.
This is often useful for factory methods to act as a cross-language constructor.

#### Exception Handling
When an interface implemented in C++ throws a `std::exception`, it will be translated to a
`java.lang.RuntimeException` in Java, an `NSException` in Objective-C or a `RuntimeError` in Python.
`java.lang.RuntimeException` in Java, an `NSException` in Objective-C, a `RuntimeError` in Python,
or a `System.Exception` in C#.
The `what()` message will be translated as well.

#### Constants
Constants can be defined within interfaces and records. In Java, Python and C++ they are part of the
Constants can be defined within interfaces and records. In Java, Python, C# and C++ they are part of the
generated class; and in Objective-C, constant names are globals with the name of the
interface/record prefixed. Example:

```
record_with_const = record +c +j +o +p {
record_with_const = record +c +j +o +p +s {
const const_value: i32 = 8;
}
```

will be `RecordWithConst::CONST_VALUE` in C++, `RecordWithConst.CONST_VALUE` in Java,
`RecordWithConst.CONST_VALUE` in Python, and `RecordWithConstConstValue` in Objective-C.
`RecordWithConst.CONST_VALUE` in Python, `RecordWithConst.ConstValue` in C#, and `RecordWithConstConstValue` in Objective-C.

## Comments

```
# This is a comment
```

If comments are are placed **on top or inside a type definition**, they will be converted to
If comments are placed **on top or inside a type definition**, they will be converted to
**Javadoc / Doxygen compatible** comments in the generated Java/C++/Objective-C interfaces, or a
Python **docstring**.
Python **docstring**. Comments are currently not available in C#.
Loading

0 comments on commit a82dc9f

Please sign in to comment.