J’essaie d’appeler une DLL écrite en Rust à partir d’un programme C #. La DLL a deux fonctions simples qui prennent des piqûres (de manière différente) et impriment à la console.
#![crate_type = "lib"] extern crate libc; use libc::{c_char}; use std::ffi::CStr; #[no_mangle] pub extern fn printc(s: *const c_char){ let c_str : &CStr = unsafe { assert!(!s.is_null()); CStr::from_ptr(s) }; println!("{:?}", c_str.to_bytes().len()); //prints "1" if unicode let r_str = std::str::from_utf8(c_str.to_bytes()).unwrap(); println!("{:?}", r_str); } #[no_mangle] pub extern fn print2(ssortingng: Ssortingng) { println!("{:?}", ssortingng) }
[DllImport("lib.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] static extern void print2(ref ssortingng str); [DllImport("lib.dll", CallingConvention = CallingConvention.Cdecl)] static extern void printc(ssortingng str); static void Main(ssortingng[] args) { try { var graw = "yeyeye"; printc(graw); print2(ref graw); } catch (Exception ex) { Console.WriteLine("calamity!, {0}", ex.Message); } Console.ReadLine(); }
Pour la fonction print2
, il continue à imprimer des déchets à l’écran jusqu’à ce qu’il cause une AccessViolationException
La deuxième fonction printc
imprime la chaîne, mais uniquement si CharSet.Unicode
n’est pas défini. S’il est défini, il imprimera uniquement le premier caractère, d’où le println!("{:?}", c_str.to_bytes().len());
imprimera 1
.
Je crois que la fonction Cstr::from_ptr
ne supporte pas Unicode, c’est pourquoi elle ne renvoie que le premier caractère de la chaîne.
Une idée sur la manière de passer une chaîne Unicode en tant que parameters aux DLL Rust? Est-il possible de simplifier les choses comme dans la fonction print2
?
Si vous consultez la documentation sur CharSet
, vous verrez que CharSet.Unicode
indique à .NET de CharSet.Unicode
chaînes au CharSet.Unicode
UTF-16 ( c’est -à- dire deux octets par sharepoint code). Ainsi, .NET essaie de transmettre à printc
ce qui devrait être un *const u16
, pas un *const libc::c_char
. Lorsque CStr
va calculer la longueur de la chaîne, il voit ce qui suit:
b"y\0e\0y\0e\0y\0e\0"
C’est-à-dire qu’il voit une unité de code, puis un octet nul, donc il s’arrête; c’est pourquoi il est dit que la longueur est “1”.
Rust ne prend pas en charge les chaînes UTF-16 de manière standard, mais si vous travaillez sous Windows, il existe certaines méthodes de conversion: recherchez dans la documentation OsStrExt
et OsSsortingngExt
. Notez que vous devez utiliser les docs installés avec le compilateur. ceux en ligne ne l’incluront pas.
Malheureusement, rien ne permet de traiter directement avec les chaînes UTF-16 à zéro terminal. Vous devrez écrire du code non sécurisé pour transformer un *const u16
en un &[u16]
que vous pourrez transmettre à OsSsortingngExt::from_wide
.
Maintenant, Rust utilise Unicode, mais UTF-8. Malheureusement, il n’existe aucun moyen direct d’amener .NET à rassembler une chaîne au format UTF-8. L’utilisation de tout autre encodage semblerait perdre des informations. Vous devez donc traiter explicitement UTF-16 du côté Rust ou traiter explicitement de l’UTF-8 du côté C #.
Il est beaucoup plus simple de ré-encoder la chaîne au format UTF-8 en C #. Vous pouvez exploiter le fait que .NET organisera un tableau en tant que pointeur brut sur le premier élément (tout comme C) et transmettra une chaîne UTF-8 terminée par un caractère null.
Premièrement, une méthode statique permettant de prendre une chaîne .NET et de générer une chaîne UTF-8 stockée dans un tableau d’octets:
byte[] NullTerminatedUTF8bytes(ssortingng str) { return Encoding.GetBytes(str + "\0"); }
Puis déclarez la signature de la fonction Rust comme ceci:
[DllImport(dllname, CallingConvention = CallingConvention.Cdecl)] static extern void printc([In] byte[] str);
Enfin, appelez ça comme ça:
printc(NullTerminatedUTF8bytes(str));
Pour les points bonus, vous pouvez retravailler printc
pour prendre à la place un *const u8
et un u32
, en passant la chaîne ré-encodée plus sa longueur; alors vous n’avez pas besoin du terminateur null et pouvez reconstruire la chaîne à l’aide de la fonction std::slice::from_raw_parts
(mais cela commence à aller au-delà de la question initiale).
Quant à print2
, celui-ci est simplement inutilisable. .NET ne connaît rien du type Ssortingng
de Rust et n’est en aucun cas compatible avec les chaînes .NET. Plus que cela, Ssortingng
n’a même pas de structure garantie, aussi, l’obligation de la lier en toute sécurité est plus ou moins impossible.
Tout cela est une façon très longue de dire: n’utilisez jamais Ssortingng
, ni aucun autre type non conforme à FFI, dans des fonctions multilingues , jamais . Si votre intention ici était de passer une chaîne “possédée” dans Rust … Je ne sais pas s’il est même possible de le faire de concert avec .NET.
De plus : “FFI-safe” dans Rust se résume essentiellement à: est un type intégré de taille fixe ( c’est -à- dire qu’il ne usize
pas usize
/ isize
), ou un type défini par l’utilisateur auquel est attaché #[repr(C)]
. Malheureusement, le type “FFI-safe” d’un type n’est pas inclus dans la documentation.