问题
How is it possible to write Rust code like the C code below? This is my Rust code so far, without the option to marshal it:
pub struct PackChar {
id: u32,
val_str: String,
}
#[no_mangle]
pub extern "C" fn get_packs_char(size: u32) -> Vec<PackChar> {
let mut out_vec = Vec::new();
for i in 0..size {
let int_0 = '0' as u32;
let last_char_val = int_0 + i % (126 - int_0);
let last_char = char::from_u32(last_char_val).unwrap();
let buffer = format!("abcdefgHi{}", last_char);
let pack_char = PackChar {
id: i,
val_str: buffer,
};
out_vec.push(pack_char);
}
out_vec
}
The code above tries to reproduce the following C code which I am able to interoperate with as is.
void GetPacksChar(int size, PackChar** DpArrPnt)
{
int TmpStrSize = 10;
*DpArrPnt = (PackChar*)CoTaskMemAlloc( size * sizeof(PackChar));
PackChar* CurPackPnt = *DpArrPnt;
char dummyString[]= "abcdefgHij";
for (int i = 0; i < size; i++,CurPackPnt++)
{
dummyString[TmpStrSize-1] = '0' + i % (126 - '0');
CurPackPnt->IntVal = i;
CurPackPnt->buffer = strdup(dummyString);
}
}
This C code could be accessed via DLL import in C# like this:
[Dllimport("DllPath", CallingConvention = CallingConvention.Cdecl)]
public static extern void GetPacksChar(uint length, PackChar** ArrayStructs)
PackChar* MyPacksChar;
GetPacksChar(10, &MyPacksChar);
PackChar* CurrentPack = MyPacksChar;
var contLst = new List<PackChar>();
for (uint i = 0; i < ArrL; i++, CurrentPack++)
contlist.Add(new PackChar() {
IntVal = CurrentPack->IntVal, buffer = contLst->buffer
});
回答1:
Let's break this down into the various requirements that your Rust code needs to meet:
- The DLL needs to expose a function with the correct name
GetPacksChar
. This is because you declare it with the nameGetPacksChar
from C# and the names must match. - The function needs the correct calling convention, in this case
extern "C"
. This is because you declare the function asCallingConvention = CallingConvention.Cdecl
from C#, which matches theextern "C"
calling convention in Rust. - The function needs the correct signature, in this case taking the Rust equivalent of a
uint
and aPackChar**
and returning nothing. This matches the function signaturefn (u32, *mut *mut PackChar)
. - The declaration of
PackChar
needs to match between C# and Rust. I'll go over this below. - The function needs to the replicate the behavior of the original C function. I'll go over this below.
The easiest part will be declaring the function in Rust:
#[no_mangle]
pub extern "C" fn GetPacksChar(length: u32, array_ptr: *mut *mut PackChar) {}
Next we need to address PackChar
. Based on how it's used in the C# code, it looks like it should be declared:
#[repr(C)]
pub struct PackChar {
pub IntVal: i32,
pub buffer: *mut u8,
}
Breaking this down, #[repr(C)]
tells the Rust compiler to arrange PackChar
in memory the same way a C compiler would, which is important since you're telling C# that it's calling into C. IntVal
and buffer
are both used from C# and the original C version. IntVal
is declared as an int
in the C version, so we use i32
in the Rust version, and buffer
is treated as an array of bytes in C, so we use a *mut u8
in Rust.
Note that the definition of PackChar
in C# should match the declaration in C/Rust, so:
public struct PackChar {
public int IntVal;
public char* buffer;
}
Now all that's left is to reproduce the original behavior of the C function in Rust:
#[no_mangle]
pub extern "C" fn GetPacksChar(len: u32, array_ptr: *const *mut PackChar) {
static DUMMY_STR: &'static [u8] = b"abcdefgHij\0";
// Allocate space for an array of `len` `PackChar` objects.
let bytes_to_alloc = len * mem::size_of::<PackChar>();
*array_ptr = CoTaskMemAlloc(bytes_to_alloc) as *mut PackChar;
// Convert the raw array of `PackChar` objects into a Rust slice and
// initialize each element of the array.
let mut array = slice::from_raw_parts(len as usize, *array_ptr);
for (index, pack_char) in array.iter_mut().enumerate() {
pack_char.IntVal = index;
pack_char.buffer = strdup(DUMMY_STR as ptr);
pack_char.buffer[DUMMY_STR.len() - 1] = b'0' + index % (126 - b'0');
}
}
Important points from the above:
- We have to manually include the null terminating character (
\0
) inDUMMY_STR
because it's meant to be a C string. - We call
CoTaskMemAlloc()
andstrdup()
, which are both C functions.strdup()
is in the libc crate, and you can probably find in the ole32-sys crate. - The function is declared as
unsafe
because we have to do a number of unsafe things, like calling C functions and doingstr::from_raw_parts()
.
Hope that helps!
来源:https://stackoverflow.com/questions/33581363/how-to-return-a-vector-of-structs-from-rust-to-c