Skip to content

Commit

Permalink
improve unity callback support(generate custom delegate)
Browse files Browse the repository at this point in the history
  • Loading branch information
neuecc committed Mar 8, 2023
1 parent 756bc8e commit e016568
Show file tree
Hide file tree
Showing 13 changed files with 666 additions and 318 deletions.
45 changes: 33 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Install on `Cargo.toml` as `build-dependencies` and set up `bindgen::Builder` on

```toml
[build-dependencies]
csbindgen = "1.1.0"
csbindgen = "1.2.0"
```

### Rust to C#.
Expand Down Expand Up @@ -57,7 +57,7 @@ namespace CsBindgen
{
const string __DllName = "nativelib";

[DllImport(__DllName, EntryPoint = "my_add", CallingConvention = CallingConvention.Cdecl)]
[DllImport(__DllName, EntryPoint = "my_add", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int my_add(int x, int y);
}
}
Expand Down Expand Up @@ -116,7 +116,7 @@ namespace CsBindgen
{
const string __DllName = "liblz4";

[DllImport(__DllName, EntryPoint = "csbindgen_LZ4_compress_default", CallingConvention = CallingConvention.Cdecl)]
[DllImport(__DllName, EntryPoint = "csbindgen_LZ4_compress_default", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int LZ4_compress_default(byte* src, byte* dst, int srcSize, int dstCapacity);
}
}
Expand Down Expand Up @@ -178,7 +178,7 @@ namespace {csharp_namespace}
#endif
}

[DllImport(__DllName, EntryPoint = "{csharp_entry_point_prefix}LZ4_versionNumber", CallingConvention = CallingConvention.Cdecl)]
[DllImport(__DllName, EntryPoint = "{csharp_entry_point_prefix}LZ4_versionNumber", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int {csharp_method_prefix}LZ4_versionNumber();
}
```
Expand All @@ -191,7 +191,7 @@ namespace {csharp_namespace}

```csharp
// true(default) generates delegate*
[DllImport(__DllName, EntryPoint = "callback_test", CallingConvention = CallingConvention.Cdecl)]
[DllImport(__DllName, EntryPoint = "callback_test", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int callback_test(delegate* unmanaged[Cdecl]<int, int> cb);

// You can define like this callback method.
Expand All @@ -203,12 +203,15 @@ callback_test(&Method);

// ---
// false will generates Action/Func, it is useful for Unity
[DllImport(__DllName, EntryPoint = "callback_test", CallingConvention = CallingConvention.Cdecl)]
public static extern int callback_test(Func<int, int> cb);
// false will generates {method_name}_{parameter_name}_delegate, it is useful for Unity
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int callback_test_cb_delegate(int a);

[DllImport(__DllName, EntryPoint = "callback_test", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int callback_test(callback_test_cb_delegate cb);

// Unity can define callback method as MonoPInvokeCallback
[MonoPInvokeCallback(typeof(Func<int, int>))]
[MonoPInvokeCallback(typeof(NativeMethods.callback_test_cb_delegate))]
static int Method(int x) => x * x;

// And use it.
Expand Down Expand Up @@ -496,10 +499,10 @@ extern "C" fn sum(x:i32, y:i32) -> i32 {
In default, csbindgen generates `extern "C" fn` as `delegate* unmanaged[Cdecl]<>`.

```csharp
[DllImport(__DllName, EntryPoint = "csharp_to_rust", CallingConvention = CallingConvention.Cdecl)]
[DllImport(__DllName, EntryPoint = "csharp_to_rust", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void csharp_to_rust(delegate* unmanaged[Cdecl]<int, int, int> cb);

[DllImport(__DllName, EntryPoint = "rust_to_csharp", CallingConvention = CallingConvention.Cdecl)]
[DllImport(__DllName, EntryPoint = "rust_to_csharp", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern delegate* unmanaged[Cdecl]<int, int, int> rust_to_csharp();
```

Expand Down Expand Up @@ -613,6 +616,24 @@ NativeMethods.insert_counter_context(ctx, 20);
NativeMethods.delete_counter_context(ctx);
```

If you want to pass null-pointer, in rust side, convert to Option by `as_ref()`.

```rust
#[no_mangle]
pub unsafe extern "C" fn null_pointer_test(p: *const u8) {
let ptr = unsafe { p.as_ref() };
match ptr {
Some(p2) => print!("pointer address: {}", *p2),
None => println!("null pointer!"),
};
}
```

```csharp
// in C#, invoke by null.
NativeMethods.null_pointer_test(null);
```

### String and Array(Span)

Rust's String, Array(Vec) and C#'s String, Array is different thing. Since it cannot be shared, pass it with a pointer and handle it with slice(Span) or materialize it if necessary.
Expand Down Expand Up @@ -843,7 +864,7 @@ pub unsafe extern "C" fn csharp_to_rust_bytes(bytes: *const u8, len: i32) {
```

```csharp
var str = "foobarbaz:あいうえお"; // JPN(Unicode)
var str = "foobarbaz:あいうえお"; // ENG:JPN(Unicode, testing for UTF16)
fixed (char* p = str)
{
NativeMethods.csharp_to_rust_string((ushort*)p, str.Length);
Expand Down
2 changes: 1 addition & 1 deletion csbindgen/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "csbindgen"
version = "1.1.0"
version = "1.2.0"
edition = "2021"
authors = [
"Yoshifumi Kawai <ils@neue.cc>",
Expand Down
53 changes: 49 additions & 4 deletions csbindgen/src/emitter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,20 +108,59 @@ pub fn emit_csharp(
let mut method_list_string = String::new();
for item in methods {
let method_name = &item.method_name;

if let Some(x) = &item.return_type {
if let Some(delegate_method) = build_method_delegate_if_required(
x,
options,
aliases,
method_name,
&"return".to_string(),
) {
method_list_string.push_str(
format!(" [UnmanagedFunctionPointer(CallingConvention.Cdecl)]\n")
.as_str(),
);
method_list_string
.push_str(format!(" public {delegate_method};\n\n").as_str());
}
}

for p in item.parameters.iter() {
if let Some(delegate_method) = build_method_delegate_if_required(
&p.rust_type,
options,
aliases,
method_name,
&p.name,
) {
method_list_string.push_str(
format!(" [UnmanagedFunctionPointer(CallingConvention.Cdecl)]\n")
.as_str(),
);
method_list_string
.push_str(format!(" public {delegate_method};\n\n").as_str());
}
}

let entry_point = match options.csharp_entry_point_prefix.as_str() {
"" => format!("{method_prefix}{method_name}"),
x => format!("{x}{method_name}"),
};
let return_type = match &item.return_type {
Some(x) => x.to_csharp_string(options, aliases, false),
Some(x) => {
x.to_csharp_string(options, aliases, false, method_name, &"return".to_string())
}
None => "void".to_string(),
};

let parameters = item
.parameters
.iter()
.map(|p| {
let mut type_name = p.rust_type.to_csharp_string(options, aliases, false);
let mut type_name =
p.rust_type
.to_csharp_string(options, aliases, false, method_name, &p.name);
if type_name == "bool" {
type_name = "[MarshalAs(UnmanagedType.U1)] bool".to_string();
}
Expand All @@ -137,7 +176,7 @@ pub fn emit_csharp(
}

method_list_string.push_str_ln(
format!(" [DllImport(__DllName, EntryPoint = \"{entry_point}\", CallingConvention = CallingConvention.Cdecl)]").as_str(),
format!(" [DllImport(__DllName, EntryPoint = \"{entry_point}\", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]").as_str(),
);
if return_type == "bool" {
method_list_string.push_str_ln(" [return: MarshalAs(UnmanagedType.U1)]");
Expand Down Expand Up @@ -167,7 +206,13 @@ pub fn emit_csharp(
structs_string.push_str_ln(" [FieldOffset(0)]");
}

let type_name = field.rust_type.to_csharp_string(options, aliases, true);
let type_name = field.rust_type.to_csharp_string(
options,
aliases,
true,
&"".to_string(),
&"".to_string(),
);
let attr = if type_name == "bool" {
"[MarshalAs(UnmanagedType.U1)] ".to_string()
} else {
Expand Down
126 changes: 93 additions & 33 deletions csbindgen/src/type_meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ pub enum PointerType {
ConstPointerPointer,
MutPointerPointer,
ConstMutPointerPointer,
MutConstPointerPointer
MutConstPointerPointer,
}

#[derive(Clone, Debug)]
Expand All @@ -105,8 +105,8 @@ impl RustType {
MutPointer => sb.push_str("*mut"),
ConstPointerPointer => sb.push_str("*const *const"),
MutPointerPointer => sb.push_str("*mut *mut"),
ConstMutPointerPointer =>sb.push_str("*const *mut"),
MutConstPointerPointer =>sb.push_str("*mut *const"),
ConstMutPointerPointer => sb.push_str("*const *mut"),
MutConstPointerPointer => sb.push_str("*mut *const"),
};
}

Expand Down Expand Up @@ -181,6 +181,8 @@ impl RustType {
options: &BindgenOptions,
alias_map: &AliasMap,
emit_from_struct: bool,
method_name: &String,
parameter_name: &String,
) -> String {
fn convert_type_name(type_name: &str) -> &str {
let name = match type_name {
Expand Down Expand Up @@ -256,52 +258,41 @@ impl RustType {
options,
alias_map,
emit_from_struct,
method_name,
parameter_name,
));
sb.push_str(", ");
}
match return_type {
Some(x) => {
sb.push_str(&x.to_csharp_string(options, alias_map, emit_from_struct));
sb.push_str(&x.to_csharp_string(
options,
alias_map,
emit_from_struct,
method_name,
parameter_name,
));
}
None => {
sb.push_str("void");
}
};
sb.push('>');
} else {
if return_type.is_some() {
sb.push_str("Func<")
} else {
sb.push_str("Action<")
}

let joined_param = parameters
.iter()
.map(|p| {
p.rust_type
.to_csharp_string(options, alias_map, emit_from_struct)
})
.collect::<Vec<_>>()
.join(", ");

sb.push_str(joined_param.as_str());
match return_type {
Some(x) => {
if !parameters.is_empty() {
sb.push_str(", ");
}
sb.push_str(&x.to_csharp_string(options, alias_map, emit_from_struct));
}
None => {}
};
sb.push('>');
sb.push_str(build_method_delegate_name(method_name, parameter_name).as_str());
}
}
TypeKind::Option(inner) => {
// function pointer can not annotate ? so emit inner only
sb.push_str(
inner
.to_csharp_string(options, alias_map, emit_from_struct)
.to_csharp_string(
options,
alias_map,
emit_from_struct,
method_name,
parameter_name,
)
.as_str(),
);
}
Expand All @@ -315,7 +306,10 @@ impl RustType {
MutPointer | ConstPointer => {
sb.push('*');
}
MutPointerPointer | ConstPointerPointer | MutConstPointerPointer | ConstMutPointerPointer => {
MutPointerPointer
| ConstPointerPointer
| MutConstPointerPointer
| ConstMutPointerPointer => {
sb.push_str("**");
}
}
Expand All @@ -328,7 +322,10 @@ impl RustType {
MutPointer | ConstPointer => {
sb.push('*');
}
MutPointerPointer | ConstPointerPointer | MutConstPointerPointer | ConstMutPointerPointer => {
MutPointerPointer
| ConstPointerPointer
| MutConstPointerPointer
| ConstMutPointerPointer => {
sb.push_str("**");
}
}
Expand All @@ -340,6 +337,69 @@ impl RustType {
}
}

pub fn build_method_delegate_if_required(
me: &RustType,
options: &BindgenOptions,
alias_map: &AliasMap,
method_name: &String,
parameter_name: &String,
) -> Option<String> {
let emit_from_struct = false;

match &me.type_kind {
TypeKind::Function(parameters, return_type) => {
if emit_from_struct && !options.csharp_use_function_pointer {
None
} else if options.csharp_use_function_pointer {
None
} else {
let return_type_name = match return_type {
Some(x) => x.to_csharp_string(
options,
alias_map,
emit_from_struct,
method_name,
parameter_name,
),
None => "void".to_string(),
};

let joined_param = parameters
.iter()
.map(|p| {
let cs = p.rust_type.to_csharp_string(
options,
alias_map,
emit_from_struct,
method_name,
parameter_name,
);
format!("{} {}", cs, p.escape_name())
})
.collect::<Vec<_>>()
.join(", ");

let delegate_name = build_method_delegate_name(method_name, parameter_name);
let delegate_code =
format!("delegate {return_type_name} {delegate_name}({joined_param})");
Some(delegate_code)
}
}
TypeKind::Option(inner) => build_method_delegate_if_required(
inner,
options,
alias_map,
method_name,
parameter_name,
),
_ => None,
}
}

pub fn build_method_delegate_name(method_name: &String, parameter_name: &String) -> String {
format!("{method_name}_{parameter_name}_delegate")
}

impl std::fmt::Display for RustType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_rust_string(""))
Expand Down
Loading

0 comments on commit e016568

Please sign in to comment.