Skip to content

Commit a82dc9f

Browse files
authored
Csharp support (#45)
* 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.
1 parent 8d0b533 commit a82dc9f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1862
-167
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,12 @@ Djinni generator parses an interface definition file and generates:
1313
- C++ implementations of types (enums, records)
1414
- Java implementations of types
1515
- Objective-C implementations of types
16+
- Python implementation of types
17+
- C++/CLI implementation of types
1618
- C++ code to convert between C++ and Java over JNI
17-
- Objective-C++ code to convert between C++ and Objective-C.
19+
- Objective-C++ code to convert between C++ and Objective-C
20+
- Python and C code to convert between C++ and Python over CFFI
21+
- C++/CLI code to convert between C++ and C#
1822

1923

2024
## Installation

build.sbt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ lazy val djinni = (project in file("."))
1212
libraryDependencies += "org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.2",
1313
libraryDependencies += "org.yaml" % "snakeyaml" % "1.26",
1414
libraryDependencies += "com.github.scopt" %% "scopt" % "3.7.1",
15+
libraryDependencies += "commons-io" % "commons-io" % "2.8.0",
1516
assemblyOutputPath in assembly := { file("target/bin") / (assemblyJarName in assembly).value },
1617
assemblyJarName in assembly := s"${name.value}",
1718
assemblyOption in assembly := (assemblyOption in assembly).value.copy(prependShellScript = Some(defaultUniversalScript(shebang = false))),

docs/cli-usage.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,16 @@ djinni \
129129
| `--c-wrapper-base-lib-include-prefix <prefix>` | The C wrapper base support library's include path (default: `djinni/cwrapper/`). |
130130
| `--py-import-prefix <import-prefix>` | The import prefix used within python generated files (default: "") |
131131

132+
### C++/CLI
133+
134+
| Argument | Description |
135+
| -------- | ----------- |
136+
| `--cppcli-out <out-folder>` | The output folder for C++/CLI files (Generator disabled if unspecified). |
137+
| `--cppcli-namespace ...` | The namespace name to use for generated C++/CLI classes. |
138+
| `--cppcli-include-cpp-prefix` | The prefix for #include of the main C++ header files from C++/CLI files. |
139+
| `--cppcli-base-lib-include-prefix ...` | The C++/CLI base support library's include path (default: `djinni/cppcli/`). |
140+
141+
132142
### Yaml Generation
133143

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

178188
#### Objective C
179189

180-
| Argument | Default |
190+
| Argument | Default |
181191
| -------- | -------- |
182192
| `--ident-objc-enum` | `FooBar` |
183193
| `--ident-objc-field` | `fooBar` |
@@ -200,6 +210,18 @@ Possible values: `FooBar`, `fooBar`, `foo_bar`, `FOO_BAR`, `m_fooBar`.
200210
| `--ident-py-enum` | `Foo_Bar` |
201211
| `--ident-py-const` | `FOO_BAR` |
202212

213+
#### C++/CLI
214+
215+
| Argument | Default |
216+
| -------- | -------- |
217+
| `--ident-cppcli-type` | `FooBar` |
218+
| `--ident-cppcli-type-param` | `FooBar` |
219+
| `--ident-cppcli-property` | `FooBar` |
220+
| `--ident-cppcli-method` | `FooBar` |
221+
| `--ident-cppcli-local` | `fooBar` |
222+
| `--ident-cppcli-enum` | `FooBar` |
223+
| `--ident-cppcli-const` | `FooBar` |
224+
| `--ident-cppcli-file` | `FooBar` |
203225

204226
Example:
205227

docs/developer-guide.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ sbt "run
3838
--objc-out out/objc
3939
--py-out out/python
4040
--pycffi-out out/cffi
41-
--c-wrapper-out out/cwrapper"
41+
--c-wrapper-out out/cwrapper
42+
--cppcli-out out/cppcli"
4243
```
4344

4445
```bash

docs/generated-code-usage.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,26 @@ your C++ library through the CFFI bridge.
126126
* External types defined in YAML are not yet supported.
127127
* Use of non-nullable pointers is not yet supported.
128128

129+
130+
## C++/CLI / C++ Project
131+
132+
C++/CLI is a technology by Microsoft that provides interoperability of C++ with Microsoft .NET languages such as C#.
133+
It is only supported on Windows.
134+
135+
Djinni generates a shallow C++/CLI wrapper around the C++ interfaces. Once compiled to a shared library, the resulting `dll`
136+
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.
137+
138+
### Includes & Build target
139+
140+
The following code will be generated for each defined type:
141+
142+
| Type | C++ header | C++ source | C++/CLI header/sources |
143+
|------------|------------------------|----------------------------|-------------------------------------|
144+
| Enum/Flags | my\_enum.hpp | | MyEnum.hpp, MyEnum.cpp |
145+
| Record | my\_record.hpp | my\_record.cpp | MyRecord.hpp, MyRecord.cpp |
146+
| Interface | my\_interface.hpp | my\_interface.cpp (+) | MyInterface.hpp, MyInterface.cpp |
147+
148+
Add all generated files to your build target, and link against the [djinni-support-lib](https://github.com/cross-language-cpp/djinni-support-lib).
149+
150+
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.
151+

docs/idl.md

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ my_cpp_interface = interface +c {
5252
const version: i32 = 1;
5353
}
5454
55-
# This interface will be implemented in Java, ObjC and Python and can be called from C++.
56-
my_client_interface = interface +j +o +p {
55+
# This interface will be implemented in Java, ObjC, Python and C# and can be called from C++.
56+
my_client_interface = interface +j +o +p +s {
5757
log_string(str: string): bool;
5858
}
5959
```
@@ -78,16 +78,18 @@ The available data types for a record, argument, or return value are:
7878
- Primitives (`i8`, `i16`, `i32`, `i64`, `f32`, `f64`).
7979
- Strings (`string`)
8080
- Binary (`binary`). This is implemented as `std::vector<uint8_t>` in C++, `byte[]` in Java,
81-
`NSData` in Objective-C, and an object supporting the `buffer` interface in Python.
81+
`NSData` in Objective-C, an object supporting the `buffer` interface in Python, and `System.Array<System.Byte>` in C#.
8282
- Date (`date`). This is `chrono::system_clock::time_point` in C++, `Date` in Java,
83-
`NSDate` in Objective-C, and `datetime.datetime` in Python.
83+
`NSDate` in Objective-C, `datetime.datetime` in Python, and `System.DateTime` in C#.
8484
- List (`list<type>`). This is `vector<T>` in C++, `ArrayList` in Java, `NSArray`
85-
in Objective-C, and `List` in Python. Primitives in a list will be boxed in Java and Objective-C.
85+
in Objective-C, `List` in Python, and `System.Collections.Generic.List` in C#.
86+
Primitives in a list will be boxed in Java and Objective-C.
8687
- Set (`set<type>`). This is `unordered_set<T>` in C++, `HashSet` in Java, `NSSet` in
87-
Objective-C, and `Set` in Python. Primitives in a set will be boxed in Java and Objective-C.
88+
Objective-C, `Set` in Python, and `System.Collections.Generic.HashSet` in C#.
89+
Primitives in a set will be boxed in Java and Objective-C.
8890
- Map (`map<typeA, typeB>`). This is `unordered_map<K, V>` in C++, `HashMap` in Java,
89-
`NSDictionary` in Objective-C, and `Dictionary` in Python. Primitives in a map will be boxed in Java
90-
and Objective-C.
91+
`NSDictionary` in Objective-C, `Dictionary` in Python, and `System.Collections.Generic.Dictionary` in C#.
92+
Primitives in a map will be boxed in Java and Objective-C.
9193
- Enumerations / Flags
9294
- Optionals (`optional<typeA>`). This is `std::experimental::optional<T>` in C++11, object /
9395
boxed primitive reference in Java (which can be `null`), and object / NSNumber strong
@@ -96,14 +98,16 @@ The available data types for a record, argument, or return value are:
9698
deep-copy the contents.
9799

98100
## Types
101+
99102
An IDL file can contain 4 kinds of declarations: enums, flags, records, and interfaces.
100103

101-
* [**Enums**](#enums) become C++ enum classes, Java enums, ObjC `NS_ENUM`s, or Python `IntEnum`s.
102-
* [**Flags**](#flags) become C++ enum classes with convenient bit-oriented operators, Java enums with `EnumSet`, ObjC `NS_OPTIONS`, or Python `IntFlag`s.
104+
* [**Enums**](#enums) become C++ enum classes, Java enums, ObjC `NS_ENUM`s, Python `IntEnum`s, or C# `System.Enum`s.
105+
* [**Flags**](#flags) become C++ enum classes with convenient bit-oriented operators, Java enums with `EnumSet`,
106+
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).
103107
* [**Records**](#records) are pure-data value objects.
104108
* [**Interfaces**](#interfaces) are objects with defined methods to call (in C++, passed by `shared_ptr`). Djinni
105109
produces code allowing an interface implemented in C++ to be transparently used from ObjC,
106-
Java, or Python and vice versa.
110+
Java, Python or C#, and vice versa.
107111

108112
### Enums
109113

@@ -116,7 +120,7 @@ my_enum = enum {
116120
```
117121

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

121125
### Flags
122126

@@ -132,8 +136,8 @@ my_flags = flags {
132136

133137
Flags are translated to C++ `enum class`es with underlying type `unsigned` and a generated set of
134138
overloaded bitwise operators for convenience, ObjC `NS_OPTIONS` with underlying type `NSUInteger`,
135-
Java `EnumSet<>`, and Python `IntFlag`. Contrary to the above enums, the enumerants of flags
136-
represent single bits instead of integral values.
139+
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).
140+
Contrary to the above enums, the enumerants of flags represent single bits instead of integral values.
137141

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

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

@@ -186,7 +190,7 @@ another_record = record {
186190

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

191195
!!! note
192196

@@ -209,52 +213,55 @@ my_cpp_interface = interface +c {
209213
const version: i32 = 1;
210214
}
211215
212-
# This interface will be implemented in Java, ObjC, and Python and can be called from C++.
213-
my_client_interface = interface +j +o +p {
216+
# This interface will be implemented in Java, ObjC, Python and C# and can be called from C++.
217+
my_client_interface = interface +j +o +p +s {
214218
log_string(str: string): bool;
215219
}
216220
```
217221

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

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

225230
special_methods = interface +c {
226231
const accessor_method();
227232
static factory_method();
228233
}
229234

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

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

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

243250
```
244-
record_with_const = record +c +j +o +p {
251+
record_with_const = record +c +j +o +p +s {
245252
const const_value: i32 = 8;
246253
}
247254
```
248255

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

252259
## Comments
253260

254261
```
255262
# This is a comment
256263
```
257264

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

0 commit comments

Comments
 (0)