在C#中调用含缓冲区指针参数的Nvidia Omniverse C API遇崩溃
解决.NET Core调用Nvidia Omniverse omniClientWriteFileEx崩溃问题
你遇到的崩溃问题,核心出在C#与C API的类型封送不匹配、结构传递方式错误这两个关键点上,下面逐一分析你的方案问题,并给出正确实现:
方案1的致命错误
- 结构类型映射错误:
OmniClientContent的Buffer字段用Byte[]完全错误——C#数组无法直接映射到C的void*指针,CLR封送时会生成数组的包装对象,而API期望的是原始内存地址,直接导致内存访问越界崩溃。 - 笔误问题:代码中
Buffer = content明显是错误,应该指向读取到的data数组。
方案2的核心问题
- 变量名冲突:
var content = await dssHttpContent.ReadAsByteArrayAsync();与后续var content = new OmniClientContent重名,编译阶段就会报错。 - 结构传递方式错误:C API的
content参数是const OmniClientContent*(指针传递),而你用值传递结构,CLR会拷贝结构值后传递,API无法正确读取缓冲区信息。 - 内存管理隐患:分配的非托管内存
Marshal.AllocHGlobal未在合适时机释放,且如果API是异步处理,提前释放会直接导致崩溃。 - 字符集未指定:Omniverse的C API默认使用UTF-8字符串,未指定字符集会导致字符串封送错误。
正确实现步骤
1. 匹配C API的结构与函数定义
根据Omniverse的C API契约,先修正DllImport和结构定义:
// 注意指定UTF-8字符集,匹配C API的字符串编码 [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Utf8)] static extern ulong omniClientWriteFileEx( string url, ref OmniClientContent content, // 用ref对应C的指针传递 IntPtr userData, [MarshalAs(UnmanagedType.FunctionPtr)] OmniClientWriteCallback callback, string message); // 对应C的size_t类型,兼容32/64位平台 [StructLayout(LayoutKind.Sequential)] private struct OmniClientContent { public UIntPtr Size; public IntPtr Buffer; } // 定义回调委托(如果API是异步模式) [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void OmniClientWriteCallback( IntPtr userData, ulong result, ulong bytesWritten, ulong totalBytes);
2. 同步模式下的正确调用(API同步处理)
public async Task UploadUsingCApiAsync(string url, HttpContent httpContent) { var data = await httpContent.ReadAsByteArrayAsync().ConfigureAwait(false); IntPtr buffer = IntPtr.Zero; try { // 分配非托管内存 buffer = Marshal.AllocHGlobal(data.Length); Marshal.Copy(data, 0, buffer, data.Length); var content = new OmniClientContent { Size = (UIntPtr)data.Length, Buffer = buffer }; // 调用API,必须用ref传递结构 ulong result = omniClientWriteFileEx(url, ref content, IntPtr.Zero, null, string.Empty); // 检查API返回码,0通常表示成功 if (result != 0) { throw new InvalidOperationException($"OmniClient调用失败,错误码:{result}"); } } finally { // 同步调用完成后释放内存 if (buffer != IntPtr.Zero) { Marshal.FreeHGlobal(buffer); } } }
3. 异步模式下的正确调用(API异步处理)
如果omniClientWriteFileEx是异步执行(通过回调返回结果),需要在回调中释放内存:
public async Task<ulong> UploadUsingCApiAsync(string url, HttpContent httpContent) { var data = await httpContent.ReadAsByteArrayAsync().ConfigureAwait(false); IntPtr buffer = Marshal.AllocHGlobal(data.Length); Marshal.Copy(data, 0, buffer, data.Length); var tcs = new TaskCompletionSource<ulong>(); // 定义回调函数,在回调中释放内存并完成Task OmniClientWriteCallback callback = (userData, result, bytesWritten, totalBytes) => { try { if (result != 0) { tcs.SetException(new InvalidOperationException($"上传失败,错误码:{result}")); } else { tcs.SetResult(totalBytes); } } finally { Marshal.FreeHGlobal(buffer); } }; var content = new OmniClientContent { Size = (UIntPtr)data.Length, Buffer = buffer }; // 启动异步上传 ulong initResult = omniClientWriteFileEx(url, ref content, IntPtr.Zero, callback, string.Empty); if (initResult != 0) { Marshal.FreeHGlobal(buffer); throw new InvalidOperationException($"启动上传失败,错误码:{initResult}"); } // 等待异步完成 return await tcs.Task.ConfigureAwait(false); }
关键注意事项
- 结构传递方式:必须用
ref传递OmniClientContent,对应C API的指针参数,否则API无法正确读取缓冲区信息。 - size_t类型兼容:用
UIntPtr代替int,避免64位平台下缓冲区大小被截断。 - 字符集设置:
CharSet.Utf8必须指定,否则字符串封送会导致API解析错误。 - 异步内存管理:异步模式下,缓冲区必须在回调中释放,不能提前释放,否则API处理时会访问已释放的内存。
内容的提问来源于stack exchange,提问作者S7H
相关产品推荐
相关产品推荐

