Utilisation de chaînes Unicode dans DllImport avec une DLL écrite en Rust

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.

Code DLL de rouille

#![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) } 

Code du programme de la console C #

 [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.