easytier-ffi csharp 操作简单说明

实际上easytier-ffi 示例代码中有一个关于csharp 操作的,只是是基于早期的DllImport 模式,实际上微软只是比较建议大家尽可能使用LibraryImport模式,一个是辅助进行代码生成,一个是内部进行了一些调用的安全处理(内部使用了一些unsafe操作,构建的时候注意开启unsafe)

参考定义

  • EasyTierFFIV2.cs
public static partial  class EasyTierFFIV2
{
    // 导入 DLL 函数
    private const string DllName = "easytier_ffi.dylib";

    [LibraryImport(DllName, StringMarshalling = StringMarshalling.Utf16)]
    private static partial  int parse_config([MarshalAs(UnmanagedType.LPStr)] string cfgStr);

    [LibraryImport(DllName, StringMarshalling = StringMarshalling.Utf16)]
    private static partial  int run_network_instance([MarshalAs(UnmanagedType.LPStr)] string cfgStr);

    [LibraryImport(DllName, StringMarshalling = StringMarshalling.Utf16)]
    private static partial int retain_network_instance(IntPtr instNames, int length);

    [LibraryImport(DllName, StringMarshalling = StringMarshalling.Utf16)]
    private static partial  int collect_network_infos(IntPtr infos, int maxLength);

    [LibraryImport(DllName, StringMarshalling = StringMarshalling.Utf16)]
    private static partial void get_error_msg(out IntPtr errorMsg);

    [LibraryImport(DllName, StringMarshalling = StringMarshalling.Utf16)]
    private static partial  void free_string(IntPtr str);

    // 定义 KeyValuePair 结构体
    [StructLayout(LayoutKind.Sequential)]
    public struct KeyValuePair
    {
        public IntPtr Key;
        public IntPtr Value;
    }

    // 解析配置
    public static void ParseConfig(string config)
    {
        if (string.IsNullOrEmpty(config))
        {
            throw new ArgumentException("Configuration string cannot be null or empty.");
        }

        int result = parse_config(config);
        if (result < 0)
        {
            throw new Exception(GetErrorMessage());
        }
    }

    // 启动网络实例
    public static void RunNetworkInstance(string config)
    {
        if (string.IsNullOrEmpty(config))
        {
            throw new ArgumentException("Configuration string cannot be null or empty.");
        }

        int result = run_network_instance(config);
        if (result < 0)
        {
            throw new Exception(GetErrorMessage());
        }
    }

    // 保留网络实例
    public static void RetainNetworkInstances(string[] instanceNames)
    {
        IntPtr[] namePointers = null;
        IntPtr namesPtr = IntPtr.Zero;

        try
        {
            if (instanceNames != null && instanceNames.Length > 0)
            {
                namePointers = new IntPtr[instanceNames.Length];
                for (int i = 0; i < instanceNames.Length; i++)
                {
                    if (string.IsNullOrEmpty(instanceNames[i]))
                    {
                        throw new ArgumentException("Instance name cannot be null or empty.");
                    }
                    namePointers[i] = Marshal.StringToHGlobalAnsi(instanceNames[i]);
                }

                namesPtr = Marshal.AllocHGlobal(Marshal.SizeOf<IntPtr>() * namePointers.Length);
                Marshal.Copy(namePointers, 0, namesPtr, namePointers.Length);
            }

            int result = retain_network_instance(namesPtr, instanceNames?.Length ?? 0);
            if (result < 0)
            {
                throw new Exception(GetErrorMessage());
            }
        }
        finally
        {
            if (namePointers != null)
            {
                foreach (var ptr in namePointers)
                {
                    if (ptr != IntPtr.Zero)
                    {
                        Marshal.FreeHGlobal(ptr);
                    }
                }
            }

            if (namesPtr != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(namesPtr);
            }
        }
    }

    // 收集网络信息
    public static KeyValuePair<string, string>[] CollectNetworkInfos(int maxLength)
    {
        IntPtr buffer = Marshal.AllocHGlobal(Marshal.SizeOf<KeyValuePair>() * maxLength);
        try
        {
            int count = collect_network_infos(buffer, maxLength);
            if (count < 0)
            {
                throw new Exception(GetErrorMessage());
            }

            var result = new KeyValuePair<string, string>[count];
            for (int i = 0; i < count; i++)
            {
                var kv = Marshal.PtrToStructure<KeyValuePair>(buffer + i * Marshal.SizeOf<KeyValuePair>());
                string key = Marshal.PtrToStringAnsi(kv.Key);
                string value = Marshal.PtrToStringAnsi(kv.Value);

                // 释放由 FFI 分配的字符串内存
                free_string(kv.Key);
                free_string(kv.Value);

                result[i] = new KeyValuePair<string, string>(key, value);
            }

            return result;
        }
        finally
        {
            Marshal.FreeHGlobal(buffer);
        }
    }

    // 获取错误信息
    private static string GetErrorMessage()
    {
        get_error_msg(out IntPtr errorMsgPtr);
        if (errorMsgPtr == IntPtr.Zero)
        {
            return "Unknown error";
        }

        string errorMsg = Marshal.PtrToStringAnsi(errorMsgPtr);
        free_string(errorMsgPtr); // 释放错误信息字符串
        return errorMsg;
    }
}
  • 部分生成的代码

构建配置

csproj 文件添加如下代码

<AllowUnsafeBlocks>true</AllowUnsafeBlocks>

说明

基于LibraryImport是推荐的玩法,以上是一个简单调整, 目前测试是可以的

参考资料

https://github.com/EasyTier/EasyTier/blob/main/easytier-contrib/easytier-ffi/examples/csharp.cs

https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.libraryimportattribute?view=net-9.0

https://learn.microsoft.com/en-us/dotnet/standard/native-interop/pinvoke-source-generation

posted on 2025-08-16 08:27  荣锋亮  阅读(20)  评论(0)    收藏  举报

导航