将 .NET 字符串传递给 UTF-8 字符指针和大小 C 函数

问题描述 投票:0回答:3

我正在尝试与如下所示的 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
以获得更好的性能?

c# .net f# pinvoke
3个回答
0
投票

有很多方法可以做到这一点:

看起来你可以使用

Utf8StringMarshaller.ConvertToUnmanaged
但这也有你不知道字节数的问题。它可能遵循空字节终止约定。

您可以使用 for 循环自己找到空字节以确定长度或调用

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
是一种更快但更保守的分配缓冲区的方法,无需对字符串进行两次编码。


0
投票

.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
获取指向数组一部分的指针,或者按原样传递整个数组。


0
投票

我最近也需要与 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
.

我很想知道这种方法是否有任何缺点或陷阱。

© www.soinside.com 2019 - 2024. All rights reserved.