如何从C#调用接收结构体指针参数的C库函数?
C#调用带结构体指针参数的C库函数
一、C#端结构体的正确定义
你原有的结构体定义存在几个问题,修正后的版本如下:
[StructLayout(LayoutKind.Sequential, Pack = 4)] // 64位系统可改为Pack=8,需与C库编译时的内存对齐规则一致 public struct ArgvArgcResult { public IntPtr Buffer; // 对应C的char* buffer,用IntPtr适配跨平台指针 public IntPtr Argv; // 对应C的char** argv,指针的指针同样用IntPtr表示 public uint Argc; // 对应C的uint argc,不要误用ulong }
- 为什么用
IntPtr而非char*?IntPtr是.NET专门用于表示非托管指针的类型,无需开启unsafe上下文,且适配32/64位系统;如果必须使用unsafe指针,也可以写成char*和char**,但需要在项目设置中允许unsafe代码。
二、DllImport的正确写法
C函数的结构体指针参数,在C#中有两种常用传递方式:
方式1:用ref关键字(推荐,无需unsafe)
[DllImport("libgetargv.dylib", CallingConvention = CallingConvention.Cdecl)] private static extern bool get_argv_and_argc_of_pid(nuint pid, ref ArgvArgcResult result);
nuint对应C的pid_t,适配macOS上的无符号PID类型;ref关键字会将结构体的地址传递给非托管函数,等价于C的struct ArgvArgcResult*。
方式2:用unsafe指针(需开启unsafe代码)
[DllImport("libgetargv.dylib", CallingConvention = CallingConvention.Cdecl)] private static extern bool get_argv_and_argc_of_pid(nuint pid, ArgvArgcResult* result);
使用此方式时,需要在项目属性中勾选“允许不安全代码”。
三、内存处理与字符串转换
1. 内存释放(必须做,避免泄漏)
C函数会为buffer和argv分配堆内存,必须调用C库提供的释放函数回收内存(如果C库没有提供,需要确认其内存分配方式,比如用malloc的话,可调用Marshal.FreeHGlobal,但更推荐由C库提供统一的释放接口)。
假设C库提供如下释放函数:
void free_argv_argc_result(struct ArgvArgcResult* result);
C#端需导入该函数:
[DllImport("libgetargv.dylib", CallingConvention = CallingConvention.Cdecl)] private static extern void free_argv_argc_result(ref ArgvArgcResult result);
2. 未知长度的字节数组转换
由于C字符串无固定编码,用byte[]存储是合理选择。可以通过遍历指针找到null终止符来确定字符串长度:
// 读取argv中的所有字符串字节数组 IntPtr[] argvPointers = new IntPtr[result.Argc]; Marshal.Copy(result.Argv, argvPointers, 0, (int)result.Argc); List<byte[]> argvBytesList = new List<byte[]>(); foreach (IntPtr ptr in argvPointers) { if (ptr == IntPtr.Zero) continue; // 计算字符串长度(直到遇到null终止符) int length = 0; while (Marshal.ReadByte(ptr, length) != 0) { length++; } byte[] bytes = new byte[length]; Marshal.Copy(ptr, bytes, 0, length); argvBytesList.Add(bytes); } byte[][] argvBytes = argvBytesList.ToArray();
如果需要读取整个buffer的内容,逻辑类似:
if (result.Buffer != IntPtr.Zero) { int bufferLength = 0; while (Marshal.ReadByte(result.Buffer, bufferLength) != 0) { bufferLength++; } byte[] bufferBytes = new byte[bufferLength]; Marshal.Copy(result.Buffer, bufferBytes, 0, bufferLength); // 处理bufferBytes... }
四、完整示例代码
using System; using System.Collections.Generic; using System.Runtime.InteropServices; class Program { [StructLayout(LayoutKind.Sequential, Pack = 4)] public struct ArgvArgcResult { public IntPtr Buffer; public IntPtr Argv; public uint Argc; } [DllImport("libgetargv.dylib", CallingConvention = CallingConvention.Cdecl)] private static extern bool get_argv_and_argc_of_pid(nuint pid, ref ArgvArgcResult result); [DllImport("libgetargv.dylib", CallingConvention = CallingConvention.Cdecl)] private static extern void free_argv_argc_result(ref ArgvArgcResult result); static void Main(string[] args) { nuint targetPid = 1234; // 替换为目标进程PID ArgvArgcResult result = new ArgvArgcResult(); try { bool success = get_argv_and_argc_of_pid(targetPid, ref result); if (!success) { Console.WriteLine("获取进程参数失败"); return; } // 解析argv参数 IntPtr[] argvPointers = new IntPtr[result.Argc]; Marshal.Copy(result.Argv, argvPointers, 0, (int)result.Argc); Console.WriteLine($"参数数量(Argc):{result.Argc}"); Console.WriteLine("参数字节数组:"); for (int i = 0; i < argvPointers.Length; i++) { IntPtr ptr = argvPointers[i]; if (ptr == IntPtr.Zero) continue; int length = 0; while (Marshal.ReadByte(ptr, length) != 0) { length++; } byte[] bytes = new byte[length]; Marshal.Copy(ptr, bytes, 0, length); Console.WriteLine($"索引 {i}:{BitConverter.ToString(bytes)}"); // 若确定编码为UTF-8,可转换为字符串: // string argStr = System.Text.Encoding.UTF8.GetString(bytes); // Console.WriteLine($"索引 {i}:{argStr}"); } // 解析buffer(可选) if (result.Buffer != IntPtr.Zero) { int bufferLength = 0; while (Marshal.ReadByte(result.Buffer, bufferLength) != 0) { bufferLength++; } byte[] bufferBytes = new byte[bufferLength]; Marshal.Copy(result.Buffer, bufferBytes, 0, bufferLength); Console.WriteLine($"Buffer字节数组:{BitConverter.ToString(bufferBytes)}"); } } finally { // 务必释放内存,防止泄漏 free_argv_argc_result(ref result); } } }
内容的提问来源于stack exchange,提问作者Camden Narzt
相关产品推荐
相关产品推荐

