You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 10:55:04