我正在尝试与如下所示的 C API 互操作:
bool next(state* state, const uint8_t* input, size_t input_length, result* result);
input
应该是指向 UTF-8 字节数组的指针,input_length
是字节数。
我应该如何将 .NET
string
对象传递给此 API?
我已经确定了
Marshal.ZeroFreeCoTaskMemUTF8
,但这不会返回字节数。这是最好的方法吗?
此外,过程
next
不会修改input
中的任何字节。是否可以在不从 .NET next
复制所有输入数据的情况下执行 string
以获得更好的性能?
有很多方法可以做到这一点:
Utf8StringMarshaller.ConvertToUnmanaged
但这也有你不知道字节数的问题。它可能遵循空字节终止约定。
UTF8Encoding.GetByteCount
.
或者,如果您的字符串很短,您可以在堆栈上分配它们:
open System
open System.Text
open Microsoft.FSharp.NativeInterop
let stackString () =
let x = "UTF 8 String on the stack"
let utf8 = UTF8Encoding()
let maxCount = utf8.GetMaxByteCount(x.Length)
let ptr = NativePtr.stackalloc<byte> maxCount
let span = Span(NativePtr.toVoidPtr ptr, maxCount)
let length = utf8.GetBytes(x, span)
// Make your call here. Below code demonstrates one way to read bytes back to a string
let x2 = utf8.GetString(ptr, length)
x, x2, length, maxCount
GetMaxByteCount
是一种更快但更保守的分配缓冲区的方法,无需对字符串进行两次编码。
.NET 中的字符串存储为 UTF16,因此始终需要向/从 UTF8 进行复制。
如果你想使用指针那么你需要手动分配缓冲区并传入它。
[DllImport("YourDll", CallingConvention = CallingConvention.CDecl)]
bool next([In] in State state, IntPtr input, IntPtr input_length, [Out] out result result);
您可以使用
Marshal.StringToCoTaskMemUTF8
分配缓冲区并将字符串复制到其中。不要忘记在finally
.中释放内存
var value = IntPtr.Zero;
try
{
value = Marshal.StringToCoTaskMemUTF8(YourString);
if(!next(in someState, value, (IntPtr)Encoding.UTF8.GetByteCount(YourString), out var result))
throw new SomeExceptionHere();
// etc
}
finally
{
Marshal.FreeCoTaskMem(value);
}
更好的选择是将其作为
byte[]
数组传递。
[DllImport("YourDll", CallingConvention = CallingConvention.CDecl)]
bool next([In] in State state, byte[] input, IntPtr input_length, [Out] out result result);
var byteString = Encoding.UTF8.GetBytes(YourString);
if(!next(in someState, value, (IntPtr)byteString.Length, out var result))
throw new SomeExceptionHere();
// etc
如果你想防止复制,那么你首先需要将字符串存储为 UTF8 字节数组。然后,您可以使用
fixed
获取指向数组一部分的指针,或者按原样传递整个数组。
我最近也需要与 F# 进行 C 互操作,最终使用了
fixed
关键字:
let getByteArrayPointerWithLength (s : string) =
let byteArray =
s
|> Seq.map byte
|> Seq.toArray
use p_byteArray = fixed &byteArray.[0]
p_byteArray, byteArray.Length
这个函数有签名
string -> nativeptr<byte> * int
.
我很想知道这种方法是否有任何缺点或陷阱。