Skip to content

Commit f2d3c6e

Browse files
committed
Added example of writing to a C# ILogger from Rust
1 parent a5ea753 commit f2d3c6e

4 files changed

Lines changed: 118 additions & 9 deletions

File tree

CSharpDemo/CSharpDemo.csproj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,11 @@
88
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
99
</PropertyGroup>
1010

11+
<ItemGroup>
12+
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.3" />
13+
<PackageReference Include="Serilog" Version="4.2.0" />
14+
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.0" />
15+
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
16+
</ItemGroup>
17+
1118
</Project>

CSharpDemo/Program.cs

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
using System.IO.MemoryMappedFiles;
22
using System.Runtime.InteropServices;
3+
using Microsoft.Extensions.Logging;
4+
using Serilog;
5+
using ILogger = Serilog.ILogger;
36

47
class Program
58
{
@@ -18,18 +21,63 @@ public class MyClass
1821
public float value;
1922
}
2023

21-
[DllImport("librust_test.dylib")]
24+
private const string LIB_NAME = "librust_test.dylib";
25+
26+
[DllImport(LIB_NAME)]
2227
private static extern void write_to_memory(IntPtr ptr, int size);
2328

24-
[DllImport("librust_test.dylib", CallingConvention = CallingConvention.Cdecl)]
29+
[DllImport(LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
2530
private static extern void read_structs(IntPtr ptr, int count);
2631

2732

28-
[DllImport("librust_test.dylib", CallingConvention = CallingConvention.Cdecl)]
33+
[DllImport(LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
2934
private static extern void read_class(IntPtr ptr);
3035

36+
// Delegate that matches Rust's function signature
37+
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
38+
public delegate void LogCallback(IntPtr message);
39+
40+
// Import Rust functions
41+
[DllImport(LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
42+
private static extern void set_info_logger(LogCallback callback);
43+
44+
// Import Rust functions
45+
[DllImport(LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
46+
private static extern void set_err_logger(LogCallback callback);
47+
3148
static unsafe void Main()
3249
{
50+
Log.Logger = new LoggerConfiguration()
51+
.WriteTo.Console() // Output logs to console
52+
.MinimumLevel.Debug() // Set minimum log level
53+
.CreateLogger();
54+
55+
using var loggerFactory = LoggerFactory.Create(builder =>
56+
{
57+
builder.AddSerilog(); // Use Serilog
58+
});
59+
60+
var logger = loggerFactory.CreateLogger<Program>();
61+
62+
logger.LogInformation("[{ctx}]: Program starting...", "CSharp");
63+
64+
// Define the C# function that will be called from Rust
65+
void LogInfoCallback(IntPtr messagePtr)
66+
{
67+
var message = Marshal.PtrToStringAnsi(messagePtr);
68+
logger.LogInformation("[{ctx}]: {msg}", "RUST", message);
69+
}
70+
71+
void LogErrorCallback(IntPtr messagePtr)
72+
{
73+
var message = Marshal.PtrToStringAnsi(messagePtr);
74+
logger.LogError("[{ctx}]: {msg}", "RUST", message);
75+
}
76+
77+
// Pass the delegate function pointer to Rust
78+
set_info_logger(LogInfoCallback);
79+
set_err_logger(LogErrorCallback);
80+
3381
var size = 1024; // 1 KB memory-mapped file
3482
using var mmf = MemoryMappedFile.CreateFromFile("file.txt", FileMode.OpenOrCreate, null, size);
3583
using var accessor = mmf.CreateViewAccessor();
@@ -39,11 +87,12 @@ static unsafe void Main()
3987

4088
try
4189
{
42-
Console.WriteLine("Passing pointer to Rust...");
90+
logger.LogInformation("[{ctx}]: Passing pointer of ViewAccessor to Rust...", "CSharp");
4391
write_to_memory((IntPtr)ptr, size);
4492
}
4593
finally
4694
{
95+
logger.LogInformation("[{ctx}]: Releasing ViewAccessor pointer.", "CSharp");
4796
accessor.SafeMemoryMappedViewHandle.ReleasePointer();
4897
}
4998

@@ -57,6 +106,7 @@ static unsafe void Main()
57106
var span = CollectionsMarshal.AsSpan(structs);
58107
ref var searchSpace = ref MemoryMarshal.GetReference(span);
59108

109+
logger.LogInformation("[{ctx}]: Passing pointer of first span element to Rust...", "CSharp");
60110
fixed (MyStruct* readPtr = &searchSpace)
61111
{
62112
read_structs((IntPtr)readPtr, structs.Count);
@@ -70,11 +120,15 @@ static unsafe void Main()
70120

71121
try
72122
{
123+
logger.LogInformation("[{ctx}]: Passing pointer of Pinned Heap object to Rust...", "CSharp");
73124
read_class(ptr3);
74125
}
75126
finally
76127
{
128+
logger.LogInformation("[{ctx}]: Freed pinned handle.", "CSharp");
77129
handle.Free(); // Release the handle after Rust is done
78130
}
131+
132+
logger.LogInformation("[{ctx}]: Program ending...", "CSharp");
79133
}
80134
}

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Some examples I found useful calling Rust from CSharp
1414
1. Writing to a memory mapped file by getting a `SafeMemoryMappedViewHandle` and passing its pointer to Rust.
1515
2. Passing a List of structs to Rust by using `CollectionMarshall` to read as a span and pinning the reference to the first element using `MemoryMarshal.GetReference`
1616
3. Passing a heap allocated class to Rust by using `GCHandle.Alloc(obj, GCHandleType.Pinned)` to acquire a handler to a fixed handler and using `AddrOfPinnedObject()` to retrieve the pointer to that object's memory location.
17+
4. Writing to a C# ILogger using delegate function stored as global function pointer in Rust
1718

1819

1920
# License

rust_test/src/lib.rs

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,55 @@
1+
use std::ffi::{c_char, CString};
2+
3+
// Define a type for the logging function pointer
4+
type LogCallback = extern "C" fn(*const c_char);
5+
6+
// Store the logging function globally (Unsafe, must be set before use)
7+
static mut INFOLOGGER: Option<LogCallback> = None;
8+
static mut ERRLOGGER: Option<LogCallback> = None;
9+
10+
// Function to set the logger from C#
11+
#[unsafe(no_mangle)]
12+
pub extern "C" fn set_info_logger(callback: LogCallback) {
13+
unsafe {
14+
INFOLOGGER = Some(callback);
15+
}
16+
}
17+
18+
#[unsafe(no_mangle)]
19+
pub extern "C" fn set_err_logger(callback: LogCallback) {
20+
unsafe {
21+
ERRLOGGER = Some(callback);
22+
}
23+
}
24+
25+
pub fn log_info_to_csharp(message: &str) {
26+
let c_message = CString::new(message).unwrap();
27+
28+
unsafe {
29+
if let Some(logger) = INFOLOGGER {
30+
logger(c_message.as_ptr());
31+
}
32+
}
33+
}
34+
35+
pub fn log_err_to_csharp(message: &str) {
36+
let c_message = CString::new(message).unwrap();
37+
38+
unsafe {
39+
if let Some(logger) = ERRLOGGER {
40+
logger(c_message.as_ptr());
41+
}
42+
}
43+
}
44+
145
#[unsafe(no_mangle)]
246
pub extern "C" fn write_to_memory(ptr: *mut u8, size: i32) {
347
if ptr.is_null() {
48+
log_err_to_csharp("Null ptr recieved from C#");
449
return;
550
}
651

52+
log_info_to_csharp("Getting slice to memory mapped file...");
753
let slice = unsafe { std::slice::from_raw_parts_mut(ptr, size as usize) };
854
slice.fill(b' ');
955

@@ -14,9 +60,10 @@ pub extern "C" fn write_to_memory(ptr: *mut u8, size: i32) {
1460
slice[i] = 0;
1561
}
1662

63+
log_info_to_csharp("Copying message to slice...");
1764
slice[..len].copy_from_slice(&message[..len]);
1865

19-
println!("Rust wrote to memory-mapped file!");
66+
log_info_to_csharp("Rust wrote to memory-mapped file!");
2067
}
2168

2269
#[repr(C)]
@@ -29,14 +76,15 @@ pub struct MyStruct {
2976
#[unsafe(no_mangle)]
3077
pub extern "C" fn read_structs(ptr: *const MyStruct, count: i32) {
3178
if ptr.is_null() || count <= 0 {
79+
log_err_to_csharp("Null ptr recieved from C#");
3280
return;
3381
}
3482

3583
// Convert raw pointer into a slice
3684
let structs = unsafe { std::slice::from_raw_parts(ptr, count as usize) };
3785

3886
for s in structs {
39-
println!("Rust received: id = {}, value = {}", s.id, s.value);
87+
log_info_to_csharp(&format!("Rust received: id = {}, value = {}", s.id, s.value))
4088
}
4189
}
4290

@@ -50,12 +98,11 @@ pub struct MyClass {
5098
#[unsafe(no_mangle)]
5199
pub extern "C" fn read_class(ptr: *const MyClass) {
52100
if ptr.is_null() {
53-
println!("Received null pointer!");
101+
log_err_to_csharp("Null ptr recieved from C#");
54102
return;
55103
}
56104

57105
// Dereference the pointer to read the class data
58106
let obj = unsafe { &*ptr };
59-
60-
println!("Rust received: id = {}, value = {}", obj.id, obj.value);
107+
log_info_to_csharp(&format!("Rust received: id = {}, value = {} from the C# class.", obj.id, obj.value));
61108
}

0 commit comments

Comments
 (0)