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/standard/native-interop/pinvoke-source-generation