Pass string from C# to Rust using FFI - c#

I try to pass a string as a function argument to a Rust library (cdylib) as described in the Rust FFI Omnibus.
I tried to however omit the libc dependency, because I think it should not be necessary anymore.
I am using Rust 1.50.0 and .net 5.0.103.
From the the documentation it seems to me as if the CStr::from_ptr() function constructs a CStr from the pointer by reading all bytes until the null-termination. And that C# strings are automatically marshalled to C compatible strings (and are therefore null-terminated). My problem however is, that I do not get the full string that I supply as the function argument, instead I only get the first character as the string.
This is my lib.rs:
use std::os::raw::c_char;
use std::ffi::CStr;
#[no_mangle]
pub extern fn print_string(text_pointer: *const c_char) {
unsafe {
let text: String = CStr::from_ptr(text_pointer).to_str().expect("Can not read string argument.").to_string();
println!("{}", text);
}
}
and my Cargo.toml:
[package]
name = "mylib"
version = "0.1.0"
authors = ["FrankenApps"]
edition = "2018"
[lib]
crate-type = ["cdylib"]
And this is my C# code:
using System;
using System.Runtime.InteropServices;
namespace dotnet
{
class Program
{
[DllImport("mylib.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern void print_string(string text);
static void Main(string[] args)
{
print_string("Hello World.");
}
}
}
In this case the output when I run the program is:
H
When I run the linked sample, I get an error:
thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: Utf8Error { valid_up_to: 1, error_len: Some(1) }', src\lib.rs:12:32
However when I only use ASCII characters and modify the code like that:
Rust:
use libc::c_char;
use std::ffi::CStr;
#[no_mangle]
pub extern "C" fn how_many_characters(s: *const c_char) -> u32 {
let c_str = unsafe {
assert!(!s.is_null());
CStr::from_ptr(s)
};
let r_str = c_str.to_str().unwrap();
println!("{}", r_str.to_string());
r_str.chars().count() as u32
}
C#
using System;
using System.Runtime.InteropServices;
class StringArguments
{
[DllImport("mylib", EntryPoint="how_many_characters")]
public static extern uint HowManyCharacters(string s);
static public void Main()
{
var count = StringArguments.HowManyCharacters("Hello World.");
Console.WriteLine(count);
}
}
I do get the desired output:
Hello World.
12
My question is what did I do wrong in my own sample, where I tried to not use libc? Is there any difference between c_char in libc and the standard library, that makes them behave differently?
My guess is that I missed something simple, because I do expect this to work...

Since .NET 4.7 you can use MarshalAs(UnmanagedType.LPUTF8Str) so the following should work fine:
using System.Runtime.InteropServices;
namespace dotnet
{
class Program
{
[DllImport("mylib.dll")]
public static extern void print_string([MarshalAs(UnmanagedType.LPUTF8Str)] string utf8Text);
static void Main(string[] args)
{
print_string("göes to élevên");
}
}
}

You need to use CharSet = CharSet.Ansi which does seem to be the default.
When I replace
[DllImport("mylib.dll", CharSet = CharSet.Unicode, SetLastError = true)]
with
[DllImport("mylib.dll", CharSet = CharSet.Ansi, SetLastError = true)]
I do get the output:
Hello World.
Still it would have been nice, if unicode strings could be supported somehow.
Edit
I figured out how to use UTF-8 strings. I did not change anything in the rust implementation, but instead of Marshalling the string automatically in C#, a UTF-8 encoded byte array is used as the function parameter in C# like that:
using System;
using System.Runtime.InteropServices;
namespace dotnet
{
class Program
{
[DllImport("mylib.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern void print_string(byte[] utf8Text);
static void Main(string[] args)
{
print_string(Encoding.UTF8.GetBytes("göes to élevên"));
}
}
}
This works perfectly and prints:
göes to élevên

Related

Failed to import DLL loaded by .NET Core 3.0

I have a .NET Core 3 project which uses a wrapper library that uses Mosquitto to talk with a MQTT Broker.
The wrapper is defined as:
public static class MosquittoWrapper
{
public const string MosquittoDll = "mosq";
[StructLayout(LayoutKind.Sequential)]
public struct mosquitto_message
{
public int mid;
public string topic;
public byte[] payload;
public int payloadlen;
public int qos;
[MarshalAs(UnmanagedType.U1)]
public bool retain;
}
public delegate void ConnectCallbackDelegate(int result);
public delegate void MessageCallbackDelegate(mosquitto_message mesage);
[DllImport(MosquittoDll, CallingConvention = CallingConvention.Cdecl)]
public static extern int mosq_init();
[DllImport(MosquittoDll, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
internal static extern int mosq_set_tls_psk(string psk, string identity, string? ciphers);
[DllImport(MosquittoDll, CallingConvention = CallingConvention.Cdecl)]
internal static extern void mosq_set_callback(ConnectCallbackDelegate? connect_callback, MessageCallbackDelegate? message_callback);
[DllImport(MosquittoDll, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
internal static extern int mosq_connect(string host, int port, int keepalive);
[DllImport(MosquittoDll, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
internal static extern int mosq_subscribe_topic(string topic);
[DllImport(MosquittoDll, CallingConvention = CallingConvention.Cdecl)]
internal static extern void mosq_runloop(int timeout, int max_packets, int sleep_on_reconnect);
[DllImport(MosquittoDll, CallingConvention = CallingConvention.Cdecl)]
internal static extern int mosq_destroy();
}
And the header is defined as:
#pragma once
#ifndef MOSQUITTO_H_
#define MOSQUITTO_H_
#include <mosquitto.h>
#ifdef _WIN32
# ifdef LIBRARY_EXPORTS
# define LIBRARY_API __declspec(dllexport)
# else
# define LIBRARY_API __declspec(dllimport)
# endif
#elif
# define LIBRARY_API
#endif
typedef void (*CONNECT_CALLBACK)(int);
typedef void (*MESSAGE_CALLBACK)(const struct mosquitto_message*);
// return value 0 represents success. Or ENOMEM, EINVAL when error.
LIBRARY_API int mosq_init();
LIBRARY_API int mosq_set_tls_psk(char* psk, char* identity, char* ciphers);
LIBRARY_API void mosq_set_callback(CONNECT_CALLBACK connect_callback, MESSAGE_CALLBACK message_callback);
LIBRARY_API int mosq_connect(char* host, int port, int keepalive);
LIBRARY_API int mosq_subscribe_topic(char* topic);
LIBRARY_API void mosq_runloop(int timeout, int max_packets, int sleep_on_reconnect);
LIBRARY_API int mosq_destroy();
#endif // MOSQUITTO_H_
The issue is when I tried to start the project, .NET Core throws an exception. System.DllNotFoundException: 'Unable to load DLL 'mosq' or one of its dependencies: The specified module could not be found. (0x8007007E)'
This doesn't make sense at all. The dll and its dependencies are copied to the folder which loads mosq dll. Moreover, the running process writes symbol loaded about related dlls explicitly. I also tried to toss the dll around different folders but it's of no use. What could go wrong?
DLL import in dotnet core works the same as it did in .net framework.
Go to https://mosquitto.org/ and download (source / dll). Here I grab the windows binary "mosquitto.dll".
Generate a C# program with a wrapper for the above dll (API: https://mosquitto.org/api/files/mosquitto-h.html ).
Here I'll just use the init function as a proof of concept
using System;
using System.Runtime.InteropServices;
namespace aac
{
class Program
{
static void Main(string[] args)
{
// Just test the init function
MosquittoWrapper.mosquitto_lib_init();
Console.WriteLine("success");
}
}
public static class MosquittoWrapper
{
// Same name as DLL
public const string MosquittoDll = "mosquitto";
// https://mosquitto.org/api/files/mosquitto-h.html#mosquitto_lib_init
[DllImport(MosquittoDll, CallingConvention = CallingConvention.Cdecl)]
public static extern int mosquitto_lib_init();
}
}
Then build the project
PS C:\Users\bburns\code\scratch\aac\aac> dotnet build
Microsoft (R) Build Engine version 16.3.0+0f4c62fea for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.
Restore completed in 20.94 ms for C:\Users\bburns\code\scratch\aac\aac\aac.csproj.
aac -> C:\Users\bburns\code\scratch\aac\aac\bin\Debug\netcoreapp3.0\aac.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:01.61
Then place the mosquitto.dll in the same folder as the executing assembly
bin\Debug\netcoreapp3.0
Run the program:
PS C:\Users\bburns\code\scratch\aac\aac> dotnet run
success
Note that if there's a name mis-match or the dll can't be found you'll generate a DllFileNotFoundException.
public const string MosquittoDll = "mosquittoz";
build and run
PS C:\Users\bburns\code\scratch\aac\aac> dotnet run
Unhandled exception. System.DllNotFoundException: Unable to load DLL 'mosquittoz' or one of its dependencies: The specified module could not be found. (0x8007007E)
at aac.MosquittoWrapper.mosquitto_lib_init()
at aac.Program.Main(String[] args) in C:\Users\bburns\code\scratch\aac\aac\Program.cs:line 10

correctly convert C++ long to C# int

I'm currently working on a .NET Framework 4.7.2 application using a business logic library written in unmanaged C++. I need to use unmanaged C++.
I need to use the logic from the C++ project, unfortunately I cannot correctly convert the input or output parameters of my program.
When I input 42, and simply want to return that value, I get 17582022 as a result. Which should actually be 42.
My C++ code looks like that:
MYCore header file:
#ifdef MYCORE_EXPORTS
#define MYCORE_API __declspec(dllexport)
#endif
#pragma once
#include <string>
using namespace std;
extern "C"
{
class MYCORE_API TestClass
{
private:
string name;
public:
TestClass(char*);
long Iterate(long &n);
};
MYCORE_API TestClass* TestClass_Create(char* name);
}
MYCore source file:
#include "stdafx.h"
#include "MYCore.h"
TestClass::TestClass(char* n)
{
name = n;
}
long TestClass::Iterate(long &n)
{
return n;
}
extern "C"
{
MYCORE_API TestClass * TestClass_Create(char* name)
{
return new TestClass(name);
}
}
I'm using a .NET 4.7.2 Framework Interface project to export the C++ library functionality:
namespace MYCore.Interface
{
public static class MYProxy
{
private const string coreDLL = "my.core.dll";
[DllImport(coreDLL, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr TestClass_Create(string name);
[DllImport(coreDLL, EntryPoint = "?Iterate#TestClass##XXXXX#X", CallingConvention = CallingConvention.ThisCall)]
public static extern int Iterate(int n);
}
}
In my actual application I further import the dll and use the logic like that:
public static void Initialize()
{
var test = MYProxy.WrapperIterator_Create("test");
var result = MYProxy.Iterate(42); // as a result I'm getting sth. like 17582022 instead of 42
}
Do you know how to correctly convert an int input from C# to C++ and vice versa?
Thank you!
What you're doing in C# does not work in C++ either:
auto result = Iterate(42l);
results in the compiler error
Cannot convert argument 1 from 'long' to 'long &'
I see two solutions:
a) Change the C++ code
long TestClass::Iterate(long n)
(without the reference)
b) Change the C# code
static extern int Iterate(ref int n);
(pass a reference) and call it like
int n = 42;
Console.WriteLine(Iterate(ref n));
The problem is actually called "Marshal an unmanaged C++ Class to C#".
In my Proxy class I created a method to call an actual instance method:
[DllImport(coreDLL, EntryPoint = "?Iterate#TestClass##XXX#X", CallingConvention = CallingConvention.ThisCall)]
public static extern int CallIterate(IntPtr instance, int n);
and the method in my C++ looks like that:
MYCORE_API int CallIterate(TestClass * instance, int n)
{
if (instance!= NULL)
{
return instance->Iterate(n);
}
}
For further reading on how to marshal unmanaged C++ classes, I can suggest the following article:
https://www.codeproject.com/Articles/18032/How-to-Marshal-a-C-Class
My solution works fine now. Thanks for all the good input!

How to free the memory of a string returned from Rust in C#?

I have these two functions exposed from Rust
extern crate libc;
use std::mem;
use std::ffi::{CString, CStr};
use libc::c_char;
pub static FFI_LIB_VERSION: &'static str = env!("CARGO_PKG_VERSION"); // '
#[no_mangle]
pub extern "C" fn rustffi_get_version() -> *const c_char {
let s = CString::new(FFI_LIB_VERSION).unwrap();
let p = s.as_ptr();
mem::forget(s);
p as *const _
}
#[no_mangle]
pub extern "C" fn rustffi_get_version_free(s: *mut c_char) {
unsafe {
if s.is_null() {
return;
}
let c_str: &CStr = CStr::from_ptr(s);
let bytes_len: usize = c_str.to_bytes_with_nul().len();
let temp_vec: Vec<c_char> = Vec::from_raw_parts(s, bytes_len, bytes_len);
}
}
fn main() {}
They are imported by C# as below
namespace rustFfiLibrary
{
public class RustFfiApi
{
[DllImport("rustffilib.dll", EntryPoint = "rustffi_get_version")]
public static extern string rustffi_get_version();
[DllImport("rustffilib.dll", EntryPoint = "rustffi_get_version_free")]
public static extern void rustffi_get_version_free(string s);
}
}
The memory of the string returned from rustffi_get_version is not managed by Rust anymore as mem::forget has been called. In C#, I want to call the get version function, get the string, and then pass it back to Rust for memory deallocation like below.
public class RustService
{
public static string GetVersion()
{
string temp = RustFfiApi.rustffi_get_version();
string ver = (string)temp.Clone();
RustFfiApi.rustffi_get_version_free(temp);
return ver ;
}
}
But the C# program crashes when it runs rustffi_get_version_free(temp). How to free the forgotten string memory in C#? What should be passed back to Rust for deallocation?
Instead of defining string as the argument in the C# extern, I changed it to pointer.
[DllImport("rustffilib.dll", EntryPoint = "rustffi_get_version")]
public static extern System.IntPtr rustffi_get_version();
[DllImport("rustffilib.dll", EntryPoint = "rustffi_get_version_free")]
public static extern void rustffi_get_version_free(System.IntPtr s);
public static string GetVersion()
{
System.IntPtr tempPointer = RustFfiApi.rustffi_get_version();
string tempString = Marshal.PtrToStringAnsi(tempPointer);
string ver = (string)tempString.Clone();
RustFfiApi.rustffi_get_version_free(tempPointer);
return ver ;
}
The IntPtr from rustffi_get_version can be successfully converted to a C# managed string type. tempString and ver are good.
When rustffi_get_version_free(tempPointer) runs, it throws an exception saying stack unbalanced:
A call to PInvoke function 'rustFfiLibrary!rustFfiLibrary.RustFfiApi::rustffi_get_version_free' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature.
sizeof(IntPtr) and sizeof(char *) are both 4 on my system. Plus, IntPtr works for return value; why doesn't it work as an input parameter?
extern "C" fn in Rust means the function uses the C calling convention.
C# expects P/Invoke functions to use the stdcall calling convention by default.
You can tell C# to use the C calling convention:
[DllImport("rustffilib.dll", CallingConvention = CallingConvention.Cdecl)]
Alternatively, you could use extern "stdcall" fn on the Rust side.

C# PInvoke flushall not giving any output, return value is 2

I was trying to follow a simple tutorial on C# PInvoke, and created the following program that should output the Test string.
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int puts(string c);
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern int _flushall();
public static void Main()
{
puts("Test");
int x = _flushall();
Console.ReadKey();
}
When I run the program, I don't see any output in the Console window, or the Output window in Visual Studio.
The return value from the _flushall call is 2. I have not yet been able to find a good reference on msvcrt.dll to see what functions/entry points are available, and what the return values mean.

PInvoke DLL in C#

I want to pass a structure to C function and I write the following code.
When I run it, the first function - Foo1 is working and then function Foo gets an exception. Can you help me to understand what the problem is?...
The C code:
typedef struct
{
int Size;
//char *Array;
}TTest;
__declspec(dllexport) void Foo(void *Test);
__declspec(dllexport) int Foo1();
void Foo(void *Test)
{
TTest *X = (TTest *)Test;
int i = X->Size;
/*for(int i=0;i<Test->Size;Test++)
{
Test->Array[i] = 127;
}*/
}
int Foo1()
{
return 10;
}
The C# code:
using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
[StructLayout(LayoutKind.Sequential)]
public class TTest
{
public int Size;
}
class Program
{
[DllImport(#"C:\.net course\unmanaged1\unmanaged3\Debug\unmanaged3.dll", CharSet = CharSet.Auto)]
public static extern void Foo(
[MarshalAs(UnmanagedType.LPStruct)]
TTest lplf // characteristics
);
[DllImport(#"C:\.net course\unmanaged1\unmanaged3\Debug\unmanaged3.dll", CharSet = CharSet.Auto)]
public static extern int Foo1();
static void Main(string[] args)
{
TTest Test = new TTest();
Test.Size = 25;
int XX = Program.Foo1();
Program.Foo(Test);
}
}
}
To the downvoters: This answer solves two issues: the immediate issue of the calling convention/the MarhsalAs attribute, and the issue he will soon find where his TTest parameter won't work if he takes my suggestion of turning TTest into a struct.
Your native code is asking for a void*, which in C# is an IntPtr. First you should define TTest as a struct and not a class. Second, you should change the declaration of Foo to:
[DllImport(#"C:\.net course\unmanaged1\unmanaged3\Debug\unmanaged3.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
public static extern void Foo(IntPtr lplf);
And third, you should pin the TTest using the fixed keyword and pass it's pointer to Foo. If you're using a class, you can use Marhsal.StructureToPtr to get an IntPtr from your TTest.
This provides the same functionality on both sides, where a pointer to any type can be passed in. You can also write overloads with all the class types that you want to use since they all equate to void* on the native side. With a struct, your parameters would be prepended with a ref.
What I'm curious about is why your native code wants a void* instead of a TTest* when the first thing you do in the unmanaged code is cast to a TTest*. If you switched the parameter to a TTest*, then providing identical functionality becomes simpler. You declaration would become:
[DllImport(#"C:\.net course\unmanaged1\unmanaged3\Debug\unmanaged3.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
public static extern void Foo(ref TTest lplf);
And you would call the function as Program.Foo(ref Test);
If you're using the class, the ref isn't necessary as classes are reference types.
You are using C call so you need to specify CallingConvention.Cdecl
[DllImport(#"C:\.net course\unmanaged1\unmanaged3\Debug\unmanaged3.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
By default its stdcall in C# pinvoke as i remember; You can also do change C code instead and leave your C# code as is like in below
__declspec(dllexport) void __stdcall Foo(void *Test);
But for me best is to both declare __cdecl (or stdcall) in your C export and CallingConvention.Cdecl (or stdcall) in your C# code to keep convenience. You can check https://learn.microsoft.com/en-gb/cpp/cpp/argument-passing-and-naming-conventions?view=vs-2017 and https://learn.microsoft.com/en-gb/dotnet/api/system.runtime.interopservices.callingconvention?view=netframework-4.7.2 for further info

Categories