ReadMe 1.7.0

multi input_bindgen_file
support bitflags crate
support tuple struct
support unit struct
new csharp_disable_emit_dll_name option
GroupedNativeMethods code generator
This commit is contained in:
neuecc 2023-04-17 20:01:20 +09:00
parent 11c68c818d
commit e4b25c3d73
8 changed files with 255 additions and 77 deletions

View File

@ -141,13 +141,19 @@ using System.Runtime.InteropServices;
static string ConvertMethodName(string typeName, string methodName, string removePrefix, string removeSuffix, bool removeUntilTypeName, bool fixMethodName) static string ConvertMethodName(string typeName, string methodName, string removePrefix, string removeSuffix, bool removeUntilTypeName, bool fixMethodName)
{ {
if (!fixMethodName) return methodName;
if (removeUntilTypeName) if (removeUntilTypeName)
{ {
var match = methodName.IndexOf(typeName); var match = methodName.IndexOf(typeName);
if (match != -1) if (match != -1)
{ {
methodName = methodName.Substring(match + typeName.Length); var substringMethodName = methodName.Substring(match + typeName.Length);
goto FINAL; if (substringMethodName.Trim(' ', '_') != "")
{
methodName = substringMethodName;
goto FINAL;
}
} }
} }
@ -163,18 +169,15 @@ using System.Runtime.InteropServices;
methodName = Regex.Replace(methodName, $"{Regex.Escape(removeSuffix)}$", ""); methodName = Regex.Replace(methodName, $"{Regex.Escape(removeSuffix)}$", "");
} }
methodName = methodName.Trim('_'); methodName = methodName.Trim('_', ' ');
if (fixMethodName) var split = methodName.Split('_');
methodName = string.Concat(split.Select(x =>
{ {
var split = methodName.Split('_'); if (x.Length == 0) return x;
methodName = string.Concat(split.Select(x => if (x.Length == 1) return char.ToUpper(x[0]).ToString();
{ return char.ToUpper(x[0]) + x.Substring(1);
if (x.Length == 0) return x; }));
if (x.Length == 1) return char.ToUpper(x[0]).ToString();
return char.ToUpper(x[0]) + x.Substring(1);
}));
}
return methodName; return methodName;
} }

164
README.md
View File

@ -26,7 +26,7 @@ Install on `Cargo.toml` as `build-dependencies` and set up `bindgen::Builder` on
```toml ```toml
[build-dependencies] [build-dependencies]
csbindgen = "1.6.0" csbindgen = "1.7.0"
``` ```
### Rust to C#. ### Rust to C#.
@ -163,6 +163,7 @@ csbindgen::Builder::default()
.csharp_entry_point_prefix("") // optional, default: "" .csharp_entry_point_prefix("") // optional, default: ""
.csharp_method_prefix("") // optional, default: "" .csharp_method_prefix("") // optional, default: ""
.csharp_use_function_pointer(true) // optional, default: true .csharp_use_function_pointer(true) // optional, default: true
.csharp_disable_emit_dll_name(false) // optional, default: false
.csharp_dll_name_if("UNITY_IOS && !UNITY_EDITOR", "__Internal") // optional, default: "" .csharp_dll_name_if("UNITY_IOS && !UNITY_EDITOR", "__Internal") // optional, default: ""
.generate_csharp_file("../dotnet-sandbox/NativeMethods.cs") // required .generate_csharp_file("../dotnet-sandbox/NativeMethods.cs") // required
.unwrap(); .unwrap();
@ -192,7 +193,9 @@ namespace {csharp_namespace}
`csharp_dll_name_if` is optional. If specified, `#if` allows two DllName to be specified, which is useful if the name must be `__Internal` at iOS build. `csharp_dll_name_if` is optional. If specified, `#if` allows two DllName to be specified, which is useful if the name must be `__Internal` at iOS build.
`input_extern_file` allows mulitple call, if you need to add dependent struct, use this. `csharp_disable_emit_dll_name` is optional, if set to true then don't emit `const string __DllName`. It is useful for generate same class-name from different builder.
`input_extern_file` and `input_bindgen_file` allow mulitple call, if you need to add dependent struct, use this.
```rust ```rust
csbindgen::Builder::default() csbindgen::Builder::default()
@ -341,6 +344,56 @@ internal static unsafe partial class NativeMethods
If Unity, configure Platform settings in each native library's inspector. If Unity, configure Platform settings in each native library's inspector.
Grouping Extension Methods
---
In an object-oriented style, it is common to create methods that take a pointer to a state (this) as their first argument. With csbindgen, you can group these methods using extension methods by specifying a Source Generator on the C# side.
Install csbindgen from NuGet, and specify [GroupedNativeMethods] for the partial class of the generated extension methods.
> PM> Install-Package [csbindgen](https://www.nuget.org/packages/csbindgen)
```csharp
// create new file and write same type-name with same namespace
namespace CsBindgen
{
// append `GroupedNativeMethods` attribute
[GroupedNativeMethods()]
internal static unsafe partial class NativeMethods
{
}
}
```
```csharp
// original methods
[DllImport(__DllName, EntryPoint = "counter_context_insert", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void counter_context_insert(counter_context* context, int value);
// generated methods
public static void Insert(this ref global::CsBindgen.counter_context @context, int @value)
// ----
counter_context* context = NativeMethods.create_counter_context();
// standard style
NativeMethods.counter_context_insert(context, 10);
// generated style
context->Insert(10);
```
`GroupedNativeMethods` has four configuration parameters.
```csharp
public GroupedNativeMethodsAttribute(
string removePrefix = "",
string removeSuffix = "",
bool removeUntilTypeName = true,
bool fixMethodName = true)
```
`removeUntilTypeName` will remove until find type-name in method-name. For example `foo_counter_context_insert(countext_context* foo)` -> `Insert`. As a result, it is recommended to use a naming convention where the same type name is placed immediately before the verb.
Type Marshalling Type Marshalling
--- ---
@ -383,6 +436,7 @@ Rust types will map these C# types.
| `#[repr(C)]Struct` | `[StructLayout(LayoutKind.Sequential)]Struct` | | `#[repr(C)]Struct` | `[StructLayout(LayoutKind.Sequential)]Struct` |
| `#[repr(C)]Union` | `[StructLayout(LayoutKind.Explicit)]Struct` | | `#[repr(C)]Union` | `[StructLayout(LayoutKind.Explicit)]Struct` |
| `#[repr(u*/i*)]Enum` | `Enum` | | `#[repr(u*/i*)]Enum` | `Enum` |
| [bitflags!](https://crates.io/crates/bitflags) | `[Flags]Enum` |
| `extern "C" fn` | `delegate* unmanaged[Cdecl]<>` or `Func<>/Action<>` | | `extern "C" fn` | `delegate* unmanaged[Cdecl]<>` or `Func<>/Action<>` |
| `Option<extern "C" fn>` | `delegate* unmanaged[Cdecl]<>` or `Func<>/Action<>` | | `Option<extern "C" fn>` | `delegate* unmanaged[Cdecl]<>` or `Func<>/Action<>` |
| `*mut T` | `T*` | | `*mut T` | `T*` |
@ -442,6 +496,62 @@ internal unsafe partial struct MyVector3
} }
``` ```
Also supports tuple struct, it will generate `Item*` fields in C#.
```
#[repr(C)]
pub struct MyIntVec3(i32, i32, i32);
```
```csharp
[StructLayout(LayoutKind.Sequential)]
internal unsafe partial struct MyIntVec3
{
public int Item1;
public int Item2;
public int Item3;
}
```
It also supports unit struct, but there is no C# struct that is synonymous with Rust's unit struct (0 byte), so it cannot be materialized. Instead of using void*, it is recommended to use typed pointers.
```
// 0-byte
#[repr(C)]
pub struct CounterContext;
```
```csharp
// 1-byte
[StructLayout(LayoutKind.Sequential)]
internal unsafe partial struct CounterContext
{
}
```
```rust
// recommend to use as pointer, in C#, holds CounterContext*
#[no_mangle]
pub extern "C" fn create_counter_context() -> *mut CounterContext {
let ctx = Box::new(InternalCounterContext {
set: HashSet::new(),
});
Box::into_raw(ctx) as *mut CounterContext
}
#[no_mangle]
pub unsafe extern "C" fn counter_context_insert(context: *mut CounterContext, value: i32) {
let mut counter = Box::from_raw(context as *mut InternalCounterContext);
counter.set.insert(value);
Box::into_raw(counter);
}
#[no_mangle]
pub unsafe extern "C" fn destroy_counter_context(context: *mut CounterContext) {
_ = Box::from_raw(context as *mut InternalCounterContext);
}
```
### Union ### Union
`Union` will generate `[FieldOffset(0)]` struct. `Union` will generate `[FieldOffset(0)]` struct.
@ -492,6 +602,34 @@ internal enum ByteTest : byte
} }
``` ```
### bitflags Enum
csbindgen supports [bitflags](https://crates.io/crates/bitflags) crate.
```rust
bitflags! {
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct EnumFlags: u32 {
const A = 0b00000001;
const B = 0b00000010;
const C = 0b00000100;
const ABC = Self::A.bits() | Self::B.bits() | Self::C.bits();
}
}
```
```csharp
[Flags]
internal enum EnumFlags : uint
{
A = 0b00000001,
B = 0b00000010,
C = 0b00000100,
ABC = A | B | C,
}
```
### Function ### Function
You can receive, return function to/from C#. You can receive, return function to/from C#.
@ -591,40 +729,42 @@ NativeMethods.delete_context(context);
You can also pass memory allocated by C# to Rust (use `fixed` or `GCHandle.Alloc(Pinned)`). The important thing is that memory allocated in Rust must release in Rust and memory allocated in C# must release in C#. You can also pass memory allocated by C# to Rust (use `fixed` or `GCHandle.Alloc(Pinned)`). The important thing is that memory allocated in Rust must release in Rust and memory allocated in C# must release in C#.
If you want to pass a non FFI Safe struct, cast it to `*mut c_void`. Then C# will treat it as a `void*`. csbindgen does not support Opaque Type. If you want to pass a non FFI Safe struct, cast it to `*mut c_void`. Then C# will treat it as a `void*`. csbindgen does not support Opaque Type. Additionally, by returning a unit struct instead of `c_void`, you can create a typed handler.
```rust ```rust
#[no_mangle] #[no_mangle]
pub extern "C" fn create_counter_context() -> *mut c_void { pub extern "C" fn create_counter_context() -> *mut CounterContext {
let ctx = Box::new(CounterContext { let ctx = Box::new(InternalCounterContext {
set: HashSet::new(), set: HashSet::new(),
}); });
Box::into_raw(ctx) as *mut c_void Box::into_raw(ctx) as *mut c_void
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn insert_counter_context(context: *mut c_void, value: i32) { pub unsafe extern "C" fn insert_counter_context(context: *mut CounterContext, value: i32) {
let mut counter = Box::from_raw(context as *mut CounterContext); let mut counter = Box::from_raw(context as *mut InternalCounterContext);
counter.set.insert(value); counter.set.insert(value);
Box::into_raw(counter); Box::into_raw(counter);
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn delete_counter_context(context: *mut c_void) { pub unsafe extern "C" fn delete_counter_context(context: *mut CounterContext) {
let counter = Box::from_raw(context as *mut CounterContext); let counter = Box::from_raw(context as *mut InternalCounterContext);
for value in counter.set.iter() { for value in counter.set.iter() {
println!("counter value: {}", value) println!("counter value: {}", value)
} }
} }
#[repr(C)] #[repr(C)]
pub struct CounterContext { pub struct CounterContext;
// no repr(C)
pub struct InternalCounterContext {
pub set: HashSet<i32>, pub set: HashSet<i32>,
} }
``` ```
```csharp ```csharp
// in C#, ctx = void*
var ctx = NativeMethods.create_counter_context(); var ctx = NativeMethods.create_counter_context();
NativeMethods.insert_counter_context(ctx, 10); NativeMethods.insert_counter_context(ctx, 10);
@ -633,6 +773,8 @@ NativeMethods.insert_counter_context(ctx, 20);
NativeMethods.delete_counter_context(ctx); NativeMethods.delete_counter_context(ctx);
``` ```
In this case, recommed to use with [Grouping Extension Methods](#grouping-extension-methods).
If you want to pass null-pointer, in rust side, convert to Option by `as_ref()`. If you want to pass null-pointer, in rust side, convert to Option by `as_ref()`.
```rust ```rust

View File

@ -14,6 +14,7 @@ path = "src/lib.rs"
[dependencies] [dependencies]
csbindgen = { path = "../csbindgen" } csbindgen = { path = "../csbindgen" }
bitflags = "2.1.0"
# physx-sys = "0.11.0" # physx-sys = "0.11.0"
[build-dependencies] [build-dependencies]

View File

@ -65,7 +65,6 @@ fn main() -> Result<(), Box<dyn Error>> {
.generate_to_file("src/lz4_ffi.rs", "../dotnet-sandbox/lz4_bindgen.cs") .generate_to_file("src/lz4_ffi.rs", "../dotnet-sandbox/lz4_bindgen.cs")
.unwrap(); .unwrap();
// csbindgen::Builder::default() // csbindgen::Builder::default()
// .input_bindgen_file("src/sqlite3.rs") // .input_bindgen_file("src/sqlite3.rs")
// .method_filter(|x| x.starts_with("sqlite3_")) // .method_filter(|x| x.starts_with("sqlite3_"))

View File

@ -1,6 +1,6 @@
use std::{ use std::{
collections::HashSet, collections::HashSet,
ffi::{c_char, c_long, c_ulong, c_void, CString}, ffi::{c_char, c_long, c_ulong, CString},
}; };
#[allow(dead_code)] #[allow(dead_code)]
@ -66,8 +66,6 @@ mod lz4_ffi;
// println!("{:?}", hoge); // println!("{:?}", hoge);
// } // }
// #[no_mangle] // #[no_mangle]
// pub extern "C" fn string_char(str: char) { // pub extern "C" fn string_char(str: char) {
// println!("{}", str); // println!("{}", str);
@ -100,17 +98,26 @@ pub struct JPH_ContactManifold {
pub extern "C" fn JPH_PruneContactPoints( pub extern "C" fn JPH_PruneContactPoints(
ioContactPointsOn1: *mut JPH_ContactPoints, ioContactPointsOn1: *mut JPH_ContactPoints,
ioContactPointsOn2: *mut JPH_ContactManifold, ioContactPointsOn2: *mut JPH_ContactManifold,
) ) {
{
todo!(); todo!();
} }
use bitflags::bitflags;
bitflags! {
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct EnumFlags: u32 {
const A = 0b00000001;
const B = 0b00000010;
const C = 0b00000100;
const ABC = Self::A.bits() | Self::B.bits() | Self::C.bits();
}
}
/// my comment! /// my comment!
#[no_mangle] #[no_mangle]
pub extern "C" fn comment_one() { extern "C" fn comment_one(_flags: EnumFlags) {}
}
/// Multiline Comments /// Multiline Comments
/// # GOTO /// # GOTO
@ -121,22 +128,16 @@ pub extern "C" fn comment_one() {
/// TO /// TO
/// ///
/// ZZZ /// ZZZ
pub extern "C" fn long_jpn_comment() { pub extern "C" fn long_jpn_comment() {}
}
#[repr(C)] #[repr(C)]
pub struct my_int_vec3(i32,i32,i32); pub struct my_int_vec3(i32, i32, i32);
pub extern "C" fn use_vec3(_v3: my_int_vec3) {
}
pub extern "C" fn use_vec3(_v3: my_int_vec3) {}
#[repr(C)] #[repr(C)]
pub struct NfcCard { pub struct NfcCard {
pub delegate: unsafe extern "C" fn(ByteArray) -> ByteArray pub delegate: unsafe extern "C" fn(ByteArray) -> ByteArray,
} }
#[no_mangle] #[no_mangle]
@ -150,11 +151,11 @@ pub struct ByteArray {
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct event { pub struct event {
pub a: i32 pub a: i32,
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn event(event: event ) { pub extern "C" fn event(event: event) {
println!("{:?}", event); println!("{:?}", event);
} }
@ -283,27 +284,30 @@ pub extern "C" fn my_add(x: i32, y: i32) -> i32 {
x + y x + y
} }
#[repr(C)]
pub struct CounterContext;
#[no_mangle] #[no_mangle]
pub extern "C" fn create_counter_context() -> *mut c_void { pub extern "C" fn create_counter_context() -> *mut CounterContext {
let ctx = Box::new(CounterContext { let ctx = Box::new(InternalCounterContext {
set: HashSet::new(), set: HashSet::new(),
}); });
Box::into_raw(ctx) as *mut c_void Box::into_raw(ctx) as *mut CounterContext
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn insert_counter_context(context: *mut c_void, value: i32) { pub unsafe extern "C" fn counter_context_insert(context: *mut CounterContext, value: i32) {
let mut counter = Box::from_raw(context as *mut CounterContext); let mut counter = Box::from_raw(context as *mut InternalCounterContext);
counter.set.insert(value); counter.set.insert(value);
Box::into_raw(counter); Box::into_raw(counter);
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn delete_counter_context(context: *mut c_void) { pub unsafe extern "C" fn destroy_counter_context(context: *mut CounterContext) {
let counter = Box::from_raw(context as *mut CounterContext); _ = Box::from_raw(context as *mut InternalCounterContext);
for value in counter.set.iter() { // for value in counter.set.iter() {
println!("counter value: {}", value) // println!("counter value: {}", value)
} // }
} }
#[no_mangle] #[no_mangle]
@ -329,8 +333,8 @@ pub struct MyVector3 {
pub z: f32, pub z: f32,
} }
#[repr(C)] // not repr(C)
pub struct CounterContext { pub struct InternalCounterContext {
pub set: HashSet<i32>, pub set: HashSet<i32>,
} }
@ -472,7 +476,6 @@ fn build_test() {
// .generate_csharp_file("dotnet-sandbox/NativeMethods.cs") // .generate_csharp_file("dotnet-sandbox/NativeMethods.cs")
// .unwrap(); // .unwrap();
csbindgen::Builder::new() csbindgen::Builder::new()
.input_bindgen_file("csbindgen-tests/src/physx/physx_generated.rs") .input_bindgen_file("csbindgen-tests/src/physx/physx_generated.rs")
.input_bindgen_file("csbindgen-tests/src/physx/x86_64-pc-windows-msvc/structgen.rs") .input_bindgen_file("csbindgen-tests/src/physx/x86_64-pc-windows-msvc/structgen.rs")
@ -567,7 +570,6 @@ pub struct CallbackTable {
pub foobar: extern "C" fn(i: i32) -> i32, pub foobar: extern "C" fn(i: i32) -> i32,
} }
// fn run_physix(){ // fn run_physix(){
// unsafe { // unsafe {
// let foundation = physx_create_foundation(); // let foundation = physx_create_foundation();
@ -580,8 +582,6 @@ pub struct CallbackTable {
// z: 0.0, // z: 0.0,
// }; // };
// let dispatcher = phys_PxDefaultCpuDispatcherCreate( // let dispatcher = phys_PxDefaultCpuDispatcherCreate(
// 1, // 1,
// null_mut(), // null_mut(),

View File

@ -1,6 +1,6 @@
use crate::{alias_map::AliasMap, builder::BindgenOptions, field_map::FieldMap, type_meta::*}; use crate::{alias_map::AliasMap, builder::BindgenOptions, field_map::FieldMap, type_meta::*};
use regex::Regex; use regex::Regex;
use std::{collections::HashSet}; use std::collections::HashSet;
use syn::{ForeignItem, Item, Pat, ReturnType}; use syn::{ForeignItem, Item, Pat, ReturnType};
enum FnItem { enum FnItem {
@ -164,6 +164,14 @@ pub fn collect_struct(ast: &syn::File, result: &mut Vec<RustStruct>) {
fields, fields,
is_union: false, is_union: false,
}); });
} else if let syn::Fields::Unit = &t.fields {
let struct_name = t.ident.to_string();
let fields: Vec<FieldMember> = Vec::new();
result.push(RustStruct {
struct_name,
fields,
is_union: false,
});
} }
} }
} }
@ -249,7 +257,7 @@ pub fn collect_enum(ast: &syn::File, result: &mut Vec<RustEnum>) {
let token_string = t.mac.tokens.to_string(); let token_string = t.mac.tokens.to_string();
let match1 = Regex::new("pub struct ([^ ]+) : ([^ ]+)") let match1 = Regex::new("struct ([^ ]+) : ([^ ]+)")
.unwrap() .unwrap()
.captures(token_string.as_str()) .captures(token_string.as_str())
.unwrap(); .unwrap();
@ -269,6 +277,7 @@ pub fn collect_enum(ast: &syn::File, result: &mut Vec<RustEnum>) {
.as_str() .as_str()
.to_string() .to_string()
.replace("Self :: ", "") .replace("Self :: ", "")
.replace(" . bits ()", "")
.replace(" . bits", "") .replace(" . bits", "")
.trim() .trim()
.to_string(), .to_string(),

View File

@ -18,7 +18,7 @@ namespace CsBindgen
/// <summary>my comment!</summary> /// <summary>my comment!</summary>
[DllImport(__DllName, EntryPoint = "comment_one", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] [DllImport(__DllName, EntryPoint = "comment_one", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void comment_one(); public static extern void comment_one(EnumFlags _flags);
/// <summary>Multiline Comments # GOTO Here Foo Bar TO ZZZ</summary> /// <summary>Multiline Comments # GOTO Here Foo Bar TO ZZZ</summary>
[DllImport(__DllName, EntryPoint = "long_jpn_comment", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] [DllImport(__DllName, EntryPoint = "long_jpn_comment", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
@ -91,13 +91,13 @@ namespace CsBindgen
public static extern int my_add(int x, int y); public static extern int my_add(int x, int y);
[DllImport(__DllName, EntryPoint = "create_counter_context", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] [DllImport(__DllName, EntryPoint = "create_counter_context", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void* create_counter_context(); public static extern counter_context* create_counter_context();
[DllImport(__DllName, EntryPoint = "insert_counter_context", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] [DllImport(__DllName, EntryPoint = "counter_context_insert", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void insert_counter_context(void* context, int value); public static extern void counter_context_insert(counter_context* context, int value);
[DllImport(__DllName, EntryPoint = "delete_counter_context", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] [DllImport(__DllName, EntryPoint = "destroy_counter_context", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void delete_counter_context(void* context); public static extern void destroy_counter_context(counter_context* context);
[DllImport(__DllName, EntryPoint = "pass_vector3", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] [DllImport(__DllName, EntryPoint = "pass_vector3", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void pass_vector3(MyVector3 v3); public static extern void pass_vector3(MyVector3 v3);
@ -186,6 +186,11 @@ namespace CsBindgen
public int a; public int a;
} }
[StructLayout(LayoutKind.Sequential)]
internal unsafe partial struct counter_context
{
}
[StructLayout(LayoutKind.Explicit)] [StructLayout(LayoutKind.Explicit)]
internal unsafe partial struct MyUnion internal unsafe partial struct MyUnion
{ {
@ -225,6 +230,15 @@ namespace CsBindgen
} }
[Flags]
internal enum EnumFlags : uint
{
A = 0b00000001,
B = 0b00000010,
C = 0b00000100,
ABC = A | B | C,
}
internal enum IntEnumTest : sbyte internal enum IntEnumTest : sbyte
{ {
A = 1, A = 1,

View File

@ -23,8 +23,18 @@ unsafe
var ctx = NativeMethods.create_context(); var handler = NativeMethods.create_counter_context();
ctx->DeleteContext2();
handler->Insert(10);
handler->Insert(20);
handler->Insert(30);
NativeMethods.destroy_counter_context(handler);
//ctx->DeleteContext2();