mirror of https://github.com/mono/CppSharp.git
2 changed files with 457 additions and 0 deletions
@ -0,0 +1,455 @@ |
|||||||
|
# Getting started |
||||||
|
|
||||||
|
From an higher level overview, CppSharp will take a bunch of user-provided C/C++ |
||||||
|
headers and generate either C++/CLI or C# code that can be compiled into a |
||||||
|
regular .NET assembly. |
||||||
|
|
||||||
|
Since there are no binary releases yet, the project needs to be compiled from |
||||||
|
source first. |
||||||
|
|
||||||
|
## Compiling on Windows/Visual Studio |
||||||
|
|
||||||
|
1. Clone CppSharp to `<CppSharp>` |
||||||
|
2. Clone LLVM to `<CppSharp>\deps\llvm` |
||||||
|
3. Clone Clang to `<CppSharp>\deps\llvm\tools\clang` (see: |
||||||
|
[http://clang.llvm.org/get_started.html](http://clang.llvm.org/get_started.html)) |
||||||
|
4. Run CMake in `<CppSharp>\deps\llvm` and compile solution in *RelWithDebInfo* mode |
||||||
|
5. Run `GenerateProjects.bat` in <CppSharp>\build |
||||||
|
6. Build generated solution in *Release*. |
||||||
|
|
||||||
|
Building in *Release* is recommended because else the Clang parser will be |
||||||
|
excruciatingly slow. |
||||||
|
|
||||||
|
## Generating bindings |
||||||
|
|
||||||
|
Suppose we have the following declarations in a file named `Sample.h` and we |
||||||
|
want to bind it to .NET. |
||||||
|
|
||||||
|
```csharp |
||||||
|
class Foo |
||||||
|
{ |
||||||
|
public: |
||||||
|
|
||||||
|
int a; |
||||||
|
float b; |
||||||
|
}; |
||||||
|
|
||||||
|
int FooAdd(Foo* foo); |
||||||
|
``` |
||||||
|
|
||||||
|
The easiest way to get started with CppSharp is to create a new class and |
||||||
|
implement the `ILibrary` interface. |
||||||
|
|
||||||
|
Each implemented method will be called by the generator during different |
||||||
|
parts of the binding process. |
||||||
|
|
||||||
|
```csharp |
||||||
|
public interface ILibrary |
||||||
|
{ |
||||||
|
/// Setup the driver options here. |
||||||
|
void Setup(Driver driver); |
||||||
|
|
||||||
|
/// Setup your passes here. |
||||||
|
void SetupPasses(Driver driver, PassBuilder passes); |
||||||
|
|
||||||
|
/// Do transformations that should happen before passes are processed. |
||||||
|
void Preprocess(Driver driver, Library lib); |
||||||
|
|
||||||
|
/// Do transformations that should happen after passes are processed. |
||||||
|
void Postprocess(Driver driver, Library lib); |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Then you just need to call the `ConsoleDriver.Run` static method with your |
||||||
|
an instance of your class to start the generation process. Like this: |
||||||
|
|
||||||
|
```csharp |
||||||
|
ConsoleDriver.Run(new SampleLibrary()); |
||||||
|
``` |
||||||
|
|
||||||
|
Now let's drill through each of the interface methods in more detail: |
||||||
|
|
||||||
|
1. `void Setup(Driver driver)` |
||||||
|
|
||||||
|
This is the first method called and here you should setup all the options needed |
||||||
|
for Clang to correctly parse your code. You can get at the options through a |
||||||
|
property in driver object. The essential ones are: |
||||||
|
|
||||||
|
**Parsing** |
||||||
|
|
||||||
|
- Defines |
||||||
|
- Include directories |
||||||
|
- Headers |
||||||
|
- Libraries |
||||||
|
- Library directories |
||||||
|
|
||||||
|
**Generator** |
||||||
|
|
||||||
|
- Output language (C# or C++/CLI) |
||||||
|
- Output namespace |
||||||
|
- Output directory |
||||||
|
|
||||||
|
|
||||||
|
Here's how the setup method could be implemented for binding the sample code |
||||||
|
above: |
||||||
|
|
||||||
|
```csharp |
||||||
|
void Setup(Driver driver) |
||||||
|
{ |
||||||
|
var options = driver.Options; |
||||||
|
options.GeneratorKind = LanguageGeneratorKind.CSharp; |
||||||
|
options.LibraryName = "Sample"; |
||||||
|
options.Headers.Add("Sample.h"); |
||||||
|
options.Libraries.Add("Sample.lib"); |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Pretty simple! We just need to tell the name of our library, what headers to |
||||||
|
process and the path to the compiled library containing the exported symbols |
||||||
|
of the declarations. And of course, what kind of output language we want. |
||||||
|
|
||||||
|
This is enough to get the generator outputting some bindings: |
||||||
|
|
||||||
|
```csharp |
||||||
|
public unsafe partial class Foo : IDisposable |
||||||
|
{ |
||||||
|
[StructLayout(LayoutKind.Explicit, Size = 8)] |
||||||
|
public struct Internal |
||||||
|
{ |
||||||
|
[FieldOffset(0)] |
||||||
|
public int a; |
||||||
|
|
||||||
|
[FieldOffset(4)] |
||||||
|
public float b; |
||||||
|
|
||||||
|
[SuppressUnmanagedCodeSecurity] |
||||||
|
[DllImport("Sample.Native", CallingConvention = CallingConvention.ThisCall, |
||||||
|
EntryPoint="??0Foo@@QAE@XZ")] |
||||||
|
public static extern System.IntPtr Foo0(System.IntPtr instance); |
||||||
|
} |
||||||
|
|
||||||
|
public System.IntPtr _Instance { get; protected set; } |
||||||
|
|
||||||
|
internal Foo(Foo.Internal* native) |
||||||
|
: this(new System.IntPtr(native)) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
internal Foo(Foo.Internal native) |
||||||
|
: this(&native) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
internal Foo(System.IntPtr native) |
||||||
|
{ |
||||||
|
_Instance = native; |
||||||
|
} |
||||||
|
|
||||||
|
public Foo() |
||||||
|
{ |
||||||
|
_Instance = Marshal.AllocHGlobal(8); |
||||||
|
Internal.Foo0(_Instance); |
||||||
|
} |
||||||
|
|
||||||
|
public void Dispose() |
||||||
|
{ |
||||||
|
Dispose(disposing: true); |
||||||
|
GC.SuppressFinalize(this); |
||||||
|
} |
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing) |
||||||
|
{ |
||||||
|
Marshal.FreeHGlobal(_Instance); |
||||||
|
} |
||||||
|
|
||||||
|
public int a |
||||||
|
{ |
||||||
|
get |
||||||
|
{ |
||||||
|
var _ptr = (Internal*)_Instance.ToPointer(); |
||||||
|
return _ptr->a; |
||||||
|
} |
||||||
|
|
||||||
|
set |
||||||
|
{ |
||||||
|
var _ptr = (Internal*)_Instance.ToPointer(); |
||||||
|
_ptr->a = value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public float b |
||||||
|
{ |
||||||
|
get |
||||||
|
{ |
||||||
|
var _ptr = (Internal*)_Instance.ToPointer(); |
||||||
|
return _ptr->b; |
||||||
|
} |
||||||
|
|
||||||
|
set |
||||||
|
{ |
||||||
|
var _ptr = (Internal*)_Instance.ToPointer(); |
||||||
|
_ptr->b = value; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public partial class SampleSample |
||||||
|
{ |
||||||
|
public struct Internal |
||||||
|
{ |
||||||
|
[SuppressUnmanagedCodeSecurity] |
||||||
|
[DllImport("Sample.Native", CallingConvention = CallingConvention.Cdecl, |
||||||
|
EntryPoint="?FooAdd@@YAHPAVFoo@@@Z")] |
||||||
|
public static extern int FooAdd0(System.IntPtr foo); |
||||||
|
} |
||||||
|
|
||||||
|
public static int FooAdd(Foo foo) |
||||||
|
{ |
||||||
|
var arg0 = foo._Instance; |
||||||
|
var ret = Internal.FooAdd0(arg0); |
||||||
|
return ret; |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
The generator creates one managed class corresponding to each native class. |
||||||
|
It also creates one class per file to hold all the free functions in the |
||||||
|
native code, since free functions are not supported under C#. By default, |
||||||
|
it will use the library name followed by the file name, hence `SampleSample`. |
||||||
|
|
||||||
|
Each bound class will have an `Internal` nested type which is used for interop |
||||||
|
with the native code. It holds the native P/Invoke declarations and has the |
||||||
|
same size as the native class, as it will be used to pass an instance of the |
||||||
|
object whenever value-type semantics are expected in the native code. |
||||||
|
|
||||||
|
Alright, now that we know how to generate barebones bindings, let's see how |
||||||
|
we can clean them up. |
||||||
|
|
||||||
|
## Cleaning up after you |
||||||
|
|
||||||
|
CppSharp provides plenty of support for customization of the generated code. |
||||||
|
|
||||||
|
A couple of things could be improved in the generated code above: |
||||||
|
|
||||||
|
- Property names do not follow the .NET naming convention |
||||||
|
- `FooAdd` function could be an instance method instead of a static method |
||||||
|
|
||||||
|
The main mechanism provided to customize the code are passes. |
||||||
|
|
||||||
|
A pass is a simple tree [visitor](https://en.wikipedia.org/wiki/Visitor_pattern) |
||||||
|
whose methods get called for each declaration that was parsed from the headers |
||||||
|
(or translation units). |
||||||
|
|
||||||
|
CppSharp already comes with a collection of useful built-in passes and we will |
||||||
|
now see how to use them to fix the flaws enumerated above. |
||||||
|
|
||||||
|
2. `void SetupPasses(Driver driver, PassBuilder passes)` |
||||||
|
|
||||||
|
New passes are added to the generator by using the API provided by `PassBuilder`. |
||||||
|
|
||||||
|
```csharp |
||||||
|
void SetupPasses(Driver driver, PassBuilder passes) |
||||||
|
{ |
||||||
|
passes.RenameDeclsUpperCase(RenameTargets.Any); |
||||||
|
passes.FunctionToInstanceMethod(); |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Re-generate the bindings and voila: |
||||||
|
|
||||||
|
```csharp |
||||||
|
public unsafe partial class Foo : IDisposable |
||||||
|
{ |
||||||
|
[StructLayout(LayoutKind.Explicit, Size = 8)] |
||||||
|
public struct Internal |
||||||
|
{ |
||||||
|
[FieldOffset(0)] |
||||||
|
public int a; |
||||||
|
|
||||||
|
[FieldOffset(4)] |
||||||
|
public float b; |
||||||
|
|
||||||
|
[SuppressUnmanagedCodeSecurity] |
||||||
|
[DllImport("Sample.Native", CallingConvention = CallingConvention.ThisCall, |
||||||
|
EntryPoint="??0Foo@@QAE@XZ")] |
||||||
|
public static extern System.IntPtr Foo0(System.IntPtr instance); |
||||||
|
|
||||||
|
[SuppressUnmanagedCodeSecurity] |
||||||
|
[DllImport("Sample.Native", CallingConvention = CallingConvention.Cdecl, |
||||||
|
EntryPoint="?FooAdd@@YAHPAVFoo@@@Z")] |
||||||
|
public static extern int Add0(System.IntPtr instance); |
||||||
|
} |
||||||
|
|
||||||
|
public System.IntPtr _Instance { get; protected set; } |
||||||
|
|
||||||
|
internal Foo(Foo.Internal* native) |
||||||
|
: this(new System.IntPtr(native)) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
internal Foo(Foo.Internal native) |
||||||
|
: this(&native) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
internal Foo(System.IntPtr native) |
||||||
|
{ |
||||||
|
_Instance = native; |
||||||
|
} |
||||||
|
|
||||||
|
public Foo() |
||||||
|
{ |
||||||
|
_Instance = Marshal.AllocHGlobal(8); |
||||||
|
Internal.Foo0(_Instance); |
||||||
|
} |
||||||
|
|
||||||
|
public void Dispose() |
||||||
|
{ |
||||||
|
Dispose(disposing: true); |
||||||
|
GC.SuppressFinalize(this); |
||||||
|
} |
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing) |
||||||
|
{ |
||||||
|
Marshal.FreeHGlobal(_Instance); |
||||||
|
} |
||||||
|
|
||||||
|
public int A |
||||||
|
{ |
||||||
|
get |
||||||
|
{ |
||||||
|
var _ptr = (Internal*)_Instance.ToPointer(); |
||||||
|
return _ptr->a; |
||||||
|
} |
||||||
|
|
||||||
|
set |
||||||
|
{ |
||||||
|
var _ptr = (Internal*)_Instance.ToPointer(); |
||||||
|
_ptr->a = value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public float B |
||||||
|
{ |
||||||
|
get |
||||||
|
{ |
||||||
|
var _ptr = (Internal*)_Instance.ToPointer(); |
||||||
|
return _ptr->b; |
||||||
|
} |
||||||
|
|
||||||
|
set |
||||||
|
{ |
||||||
|
var _ptr = (Internal*)_Instance.ToPointer(); |
||||||
|
_ptr->b = value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public int Add() |
||||||
|
{ |
||||||
|
var ret = Internal.Add0(_Instance); |
||||||
|
return ret; |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
## Custom processing |
||||||
|
|
||||||
|
Now that the bindings are looking good from a .NET perspective, let's see how |
||||||
|
we can achieve more advanced things by using the remaining overloads in the |
||||||
|
interface. |
||||||
|
|
||||||
|
3. `void Preprocess(Driver driver, Library lib);` |
||||||
|
4. `void Postprocess(Driver driver, Library lib);` |
||||||
|
|
||||||
|
As their comments suggest, these get called either before or after the the |
||||||
|
passes we setup earlier are run and they allow you free reign to manipulate |
||||||
|
the declarations before the output generator starts processing them. |
||||||
|
|
||||||
|
Let's say we want to change the class to provide .NET value semantics, |
||||||
|
drop one field from the generated bindings and rename the `FooAdd` function. |
||||||
|
|
||||||
|
```csharp |
||||||
|
void Postprocess(Driver driver, Library lib) |
||||||
|
{ |
||||||
|
lib.SetClassAsValueType("Foo"); |
||||||
|
lib.SetNameOfFunction("FooAdd", "FooCalc"); |
||||||
|
lib.IgnoreClassField("Foo", "b"); |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Re-generate the bindings and this is what we get: |
||||||
|
|
||||||
|
```csharp |
||||||
|
public unsafe partial struct Foo |
||||||
|
{ |
||||||
|
[StructLayout(LayoutKind.Explicit, Size = 8)] |
||||||
|
public struct Internal |
||||||
|
{ |
||||||
|
[FieldOffset(0)] |
||||||
|
public int a; |
||||||
|
|
||||||
|
[SuppressUnmanagedCodeSecurity] |
||||||
|
[DllImport("Sample.Native", CallingConvention = CallingConvention.ThisCall, |
||||||
|
EntryPoint="??0Foo@@QAE@XZ")] |
||||||
|
public static extern System.IntPtr Foo0(System.IntPtr instance); |
||||||
|
|
||||||
|
[SuppressUnmanagedCodeSecurity] |
||||||
|
[DllImport("Sample.Native", CallingConvention = CallingConvention.Cdecl, |
||||||
|
EntryPoint="?FooAdd@@YAHPAVFoo@@@Z")] |
||||||
|
public static extern int Calc0(System.IntPtr instance); |
||||||
|
} |
||||||
|
|
||||||
|
internal Foo(Foo.Internal* native) |
||||||
|
: this(new System.IntPtr(native)) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
internal Foo(Foo.Internal native) |
||||||
|
: this(&native) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
internal Foo(System.IntPtr native) |
||||||
|
{ |
||||||
|
var _ptr = (Internal*)native.ToPointer(); |
||||||
|
A = _ptr->a; |
||||||
|
} |
||||||
|
|
||||||
|
internal Internal ToInternal() |
||||||
|
{ |
||||||
|
var _native = new Foo.Internal(); |
||||||
|
_native.a = A; |
||||||
|
return _native; |
||||||
|
} |
||||||
|
|
||||||
|
internal void FromInternal(Internal* native) |
||||||
|
{ |
||||||
|
var _ptr = native; |
||||||
|
A = _ptr->a; |
||||||
|
} |
||||||
|
|
||||||
|
public int A; |
||||||
|
|
||||||
|
public int Calc() |
||||||
|
{ |
||||||
|
var _instance = ToInternal(); |
||||||
|
var ret = Internal.Calc0(new System.IntPtr(&_instance)); |
||||||
|
FromInternal(&_instance); |
||||||
|
return ret; |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
This is just a very small example of what is available. Since the entire AST |
||||||
|
is accessible, pretty much every customization one might need is possible. |
||||||
|
|
||||||
|
The methods we called are in fact just regular .NET extension methods |
||||||
|
provided by the library to be used as helpers for common operations. |
||||||
|
|
||||||
|
## Where to go from here |
||||||
|
|
||||||
|
Hopefully now you have a better idea of how the generator works and how to |
||||||
|
setup simple customizations to get the outputs better mapped to .NET. |
||||||
|
|
||||||
|
This barely touched the surface of what can be done with CppSharp, so please |
||||||
|
check out the user and developer reference manuals for more information. |
Loading…
Reference in new issue