在 Linux 上从 .NET Core 中的 C# 获取 uname 发布字段
Get uname release field from C# in .NET Core on Linux
我正在尝试在 Ubuntu 18.04 运行 的 .NET Core 2.2 运行 中的 C# 中获取 uname -r
的输出。
我写这篇文章时考虑到了性能,所以一直在尝试使用 P/Invoke 来实现它。
uname(2)
文档表明我需要传递一个具有相关大小字段的结构。在尝试了很多变化之后,我想出了:
[StructLayout(LayoutKind.Sequential)]
unsafe internal struct Utsname
{
public fixed byte sysname[65];
public fixed byte nodename[65];
public fixed byte release[65];
public fixed byte version[65];
public fixed byte machine[65];
}
public static class Main
{
[DllImport("libc.so.6", CallingConvention = CallingConvention.Cdecl)]
internal static extern int uname(ref Utsname buf);
public static void Main(string[] args)
{
byte[] bs = new byte[65];
unsafe
{
var buf = new utsname();
uname(ref buf);
Marshal.Copy((IntPtr)buf.release, bs, 0, 65);
}
Console.WriteLine(Encoding.UTF8.GetString(bs));
}
}
这似乎可行,但将其移动到包装函数中,例如:
public static class Main
{
...
public static string GetUnameRelease()
{
var bs = new List<byte>();
unsafe
{
var buf = new utsname();
uname(ref buf);
int i = 0;
byte* p = buf.release;
while (i < 65 && *p != 0)
{
bs.Add(*p);
p++;
i++;
}
}
return Encoding.UTF8.GetString(bs.ToArray());
}
public static void Main(string[] args)
{
Console.WriteLine(GetUnameRelease());
}
}
似乎导致它失败。我只是不确定我做错了什么。它默默地失败了,大概是由于段错误,尽管我不确定 where/how 是否能找到它的踪迹。
我尝试过的其他结构编组方法
我还尝试了一些其他方法来取回结构。
最简单的似乎是具有固定长度值的 string
字段(但我认为这失败了,因为调用者需要分配可变字段供被调用者设置):
internal struct Utsname
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)]
public string sysname;
...
}
或者一个简单的 byte
数组:
internal struct Utsname
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 65)]
public byte[] sysname;
...
}
在这种情况下,我假设问题与将托管数组传递给调用时的 In/Out 调用约定有关。
我也尝试使用 out
而不是 ref
来简化 P/Invoke,但我的印象是 uname()
希望调用者在打电话。
我也尝试使用 [In]
和 [Out]
属性,但不确定默认值是什么或使用它们会如何改变。
编写外部 C 库来包装调用
我还写了一个小的 C 库来包装调用,使调用约定更容易处理:
#include <string.h>
#include <stdlib.h>
#include <sys/utsname.h>
char *get_uname_release()
{
struct utsname buf;
uname(&buf);
size_t len = strlen(buf.release);
char *release = malloc(len * sizeof(char));
strcpy(release, buf.release);
return release;
}
我用 gcc -shared -o libget_uname.so -fPIC get_uname.c
编译了它并将它放在主要托管 DLL 旁边。
调用它更容易,只需:
public static class Main
{
...
[DllImport("libget_uname.so", EntryPoint = "uname_get_release", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
internal static extern string GetUnameRelease();
}
我每次使用它似乎都有效。
但我反对在代码中包含本机库,如果可以直接 P/Invoke 代替的话。
改用 Process
调用
另一个显而易见的简单选择就是将 uname
coreutil 作为子进程调用:
public static class Main
{
...
public static string GetUnameRelease()
{
var unameProc = new Process()
{
StartInfo = new ProcessStartInfo()
{
FileName = "uname",
Arguments = "-r",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
unameProc.Start();
unameProc.WaitForExit();
return unameProc.StandardOutput.ReadToEnd();
}
}
但我希望避免子进程的开销...也许在 Linux 上还不错,值得做?
但我现在已经花了一段时间研究 PInvoke,所以我想知道它是否可行。
问题
所以我的问题是:
- 从 C#
uname
获取 release
字段的最佳(最可靠)方法是什么?
- 我如何 P/Invoke libc 中的
uname()
系统调用可靠地取回 utsname
结构?
将代码移至函数时它不起作用的原因是您的结构不包含 domainname
成员,因此当您调用 uname
时,它会破坏内存之外的内存你为你的结构分配了。
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
unsafe internal struct Utsname
{
public fixed byte sysname[65];
public fixed byte nodename[65];
public fixed byte release[65];
public fixed byte version[65];
public fixed byte machine[65];
public fixed byte domainname[65];
}
public static class Program
{
[DllImport("libc.so.6", CallingConvention = CallingConvention.Cdecl)]
internal static extern int uname(ref Utsname buf);
public static void Main(string[] args)
{
Console.WriteLine(GetUnameRelease());
}
static unsafe string GetUnameRelease()
{
Utsname buf;
uname(ref buf);
return Marshal.PtrToStringAnsi((IntPtr)buf.release);
}
}
这是一个不需要不安全代码的版本:
public class Utsname
{
public string SysName; // char[65]
public string NodeName; // char[65]
public string Release; // char[65]
public string Version; // char[65]
public string Machine; // char[65]
public string DomainName; // char[65]
public void Print()
{
System.Console.Write("SysName:\t");
System.Console.WriteLine(this.SysName);
System.Console.Write("NodeName:\t");
System.Console.WriteLine(this.NodeName);
System.Console.Write("Release:\t");
System.Console.WriteLine(this.Release);
System.Console.Write("Version:\t");
System.Console.WriteLine(this.Version);
System.Console.Write("Machine:\t");
System.Console.WriteLine(this.Machine);
System.Console.Write("DomainName:\t");
System.Console.WriteLine(this.DomainName);
Mono.Unix.Native.Utsname buf;
Mono.Unix.Native.Syscall.uname(out buf);
System.Console.WriteLine(buf.sysname);
System.Console.WriteLine(buf.nodename);
System.Console.WriteLine(buf.release);
System.Console.WriteLine(buf.version);
System.Console.WriteLine(buf.machine);
System.Console.WriteLine(buf.domainname);
}
}
[System.Runtime.InteropServices.DllImport("libc", EntryPoint = "uname", CallingConvention = System.Runtime.InteropServices.CallingConvention.Cdecl)]
private static extern int uname_syscall(System.IntPtr buf);
// https://github.com/jpobst/Pinta/blob/master/Pinta.Core/Managers/SystemManager.cs
private static Utsname Uname()
{
Utsname uts = null;
System.IntPtr buf = System.IntPtr.Zero;
buf = System.Runtime.InteropServices.Marshal.AllocHGlobal(8192);
// This is a hacktastic way of getting sysname from uname ()
if (uname_syscall(buf) == 0)
{
uts = new Utsname();
uts.SysName = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(buf);
long bufVal = buf.ToInt64();
uts.NodeName = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(new System.IntPtr(bufVal + 1 * 65));
uts.Release = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(new System.IntPtr(bufVal + 2 * 65));
uts.Version = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(new System.IntPtr(bufVal + 3 * 65));
uts.Machine = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(new System.IntPtr(bufVal + 4 * 65));
uts.DomainName = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(new System.IntPtr(bufVal + 5 * 65));
if (buf != System.IntPtr.Zero)
System.Runtime.InteropServices.Marshal.FreeHGlobal(buf);
} // End if (uname_syscall(buf) == 0)
return uts;
} // End Function Uname
版本不需要 unsafe 代码,也不需要 Mono:
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace UnameTest
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct Utsname
{
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 65)]
public byte[] sysname;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 65)]
public byte[] nodename;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 65)]
public byte[] release;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 65)]
public byte[] version;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 65)]
public byte[] machine;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 65)]
public byte[] domainname;
}
public static class Uts
{
[DllImport("libc", EntryPoint = "uname", CallingConvention = CallingConvention.Cdecl)]
internal static extern int uname(ref Utsname buf);
public static void PrintUtsname()
{
Utsname buf = new Utsname();
uname(ref buf);
Console.WriteLine($"Utsname:");
Console.WriteLine($"---------------------------------");
Console.WriteLine($"sysname: {GetString(buf.sysname)}");
Console.WriteLine($"nodename: {GetString(buf.nodename)}");
Console.WriteLine($"release: {GetString(buf.release)}");
Console.WriteLine($"version: {GetString(buf.version)}");
Console.WriteLine($"machine: {GetString(buf.machine)}");
Console.WriteLine($"domainname: {GetString(buf.domainname)}");
}
private static string GetString(in byte[] data)
{
var pos = Array.IndexOf<byte>(data, 0);
return Encoding.ASCII.GetString(data, 0, (pos < 0) ? data.Length : pos);
}
}
}
我正在尝试在 Ubuntu 18.04 运行 的 .NET Core 2.2 运行 中的 C# 中获取 uname -r
的输出。
我写这篇文章时考虑到了性能,所以一直在尝试使用 P/Invoke 来实现它。
uname(2)
文档表明我需要传递一个具有相关大小字段的结构。在尝试了很多变化之后,我想出了:
[StructLayout(LayoutKind.Sequential)]
unsafe internal struct Utsname
{
public fixed byte sysname[65];
public fixed byte nodename[65];
public fixed byte release[65];
public fixed byte version[65];
public fixed byte machine[65];
}
public static class Main
{
[DllImport("libc.so.6", CallingConvention = CallingConvention.Cdecl)]
internal static extern int uname(ref Utsname buf);
public static void Main(string[] args)
{
byte[] bs = new byte[65];
unsafe
{
var buf = new utsname();
uname(ref buf);
Marshal.Copy((IntPtr)buf.release, bs, 0, 65);
}
Console.WriteLine(Encoding.UTF8.GetString(bs));
}
}
这似乎可行,但将其移动到包装函数中,例如:
public static class Main
{
...
public static string GetUnameRelease()
{
var bs = new List<byte>();
unsafe
{
var buf = new utsname();
uname(ref buf);
int i = 0;
byte* p = buf.release;
while (i < 65 && *p != 0)
{
bs.Add(*p);
p++;
i++;
}
}
return Encoding.UTF8.GetString(bs.ToArray());
}
public static void Main(string[] args)
{
Console.WriteLine(GetUnameRelease());
}
}
似乎导致它失败。我只是不确定我做错了什么。它默默地失败了,大概是由于段错误,尽管我不确定 where/how 是否能找到它的踪迹。
我尝试过的其他结构编组方法
我还尝试了一些其他方法来取回结构。
最简单的似乎是具有固定长度值的 string
字段(但我认为这失败了,因为调用者需要分配可变字段供被调用者设置):
internal struct Utsname
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)]
public string sysname;
...
}
或者一个简单的 byte
数组:
internal struct Utsname
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 65)]
public byte[] sysname;
...
}
在这种情况下,我假设问题与将托管数组传递给调用时的 In/Out 调用约定有关。
我也尝试使用 out
而不是 ref
来简化 P/Invoke,但我的印象是 uname()
希望调用者在打电话。
我也尝试使用 [In]
和 [Out]
属性,但不确定默认值是什么或使用它们会如何改变。
编写外部 C 库来包装调用
我还写了一个小的 C 库来包装调用,使调用约定更容易处理:
#include <string.h>
#include <stdlib.h>
#include <sys/utsname.h>
char *get_uname_release()
{
struct utsname buf;
uname(&buf);
size_t len = strlen(buf.release);
char *release = malloc(len * sizeof(char));
strcpy(release, buf.release);
return release;
}
我用 gcc -shared -o libget_uname.so -fPIC get_uname.c
编译了它并将它放在主要托管 DLL 旁边。
调用它更容易,只需:
public static class Main
{
...
[DllImport("libget_uname.so", EntryPoint = "uname_get_release", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
internal static extern string GetUnameRelease();
}
我每次使用它似乎都有效。
但我反对在代码中包含本机库,如果可以直接 P/Invoke 代替的话。
改用 Process
调用
另一个显而易见的简单选择就是将 uname
coreutil 作为子进程调用:
public static class Main
{
...
public static string GetUnameRelease()
{
var unameProc = new Process()
{
StartInfo = new ProcessStartInfo()
{
FileName = "uname",
Arguments = "-r",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
unameProc.Start();
unameProc.WaitForExit();
return unameProc.StandardOutput.ReadToEnd();
}
}
但我希望避免子进程的开销...也许在 Linux 上还不错,值得做?
但我现在已经花了一段时间研究 PInvoke,所以我想知道它是否可行。
问题
所以我的问题是:
- 从 C#
uname
获取release
字段的最佳(最可靠)方法是什么? - 我如何 P/Invoke libc 中的
uname()
系统调用可靠地取回utsname
结构?
将代码移至函数时它不起作用的原因是您的结构不包含 domainname
成员,因此当您调用 uname
时,它会破坏内存之外的内存你为你的结构分配了。
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
unsafe internal struct Utsname
{
public fixed byte sysname[65];
public fixed byte nodename[65];
public fixed byte release[65];
public fixed byte version[65];
public fixed byte machine[65];
public fixed byte domainname[65];
}
public static class Program
{
[DllImport("libc.so.6", CallingConvention = CallingConvention.Cdecl)]
internal static extern int uname(ref Utsname buf);
public static void Main(string[] args)
{
Console.WriteLine(GetUnameRelease());
}
static unsafe string GetUnameRelease()
{
Utsname buf;
uname(ref buf);
return Marshal.PtrToStringAnsi((IntPtr)buf.release);
}
}
这是一个不需要不安全代码的版本:
public class Utsname
{
public string SysName; // char[65]
public string NodeName; // char[65]
public string Release; // char[65]
public string Version; // char[65]
public string Machine; // char[65]
public string DomainName; // char[65]
public void Print()
{
System.Console.Write("SysName:\t");
System.Console.WriteLine(this.SysName);
System.Console.Write("NodeName:\t");
System.Console.WriteLine(this.NodeName);
System.Console.Write("Release:\t");
System.Console.WriteLine(this.Release);
System.Console.Write("Version:\t");
System.Console.WriteLine(this.Version);
System.Console.Write("Machine:\t");
System.Console.WriteLine(this.Machine);
System.Console.Write("DomainName:\t");
System.Console.WriteLine(this.DomainName);
Mono.Unix.Native.Utsname buf;
Mono.Unix.Native.Syscall.uname(out buf);
System.Console.WriteLine(buf.sysname);
System.Console.WriteLine(buf.nodename);
System.Console.WriteLine(buf.release);
System.Console.WriteLine(buf.version);
System.Console.WriteLine(buf.machine);
System.Console.WriteLine(buf.domainname);
}
}
[System.Runtime.InteropServices.DllImport("libc", EntryPoint = "uname", CallingConvention = System.Runtime.InteropServices.CallingConvention.Cdecl)]
private static extern int uname_syscall(System.IntPtr buf);
// https://github.com/jpobst/Pinta/blob/master/Pinta.Core/Managers/SystemManager.cs
private static Utsname Uname()
{
Utsname uts = null;
System.IntPtr buf = System.IntPtr.Zero;
buf = System.Runtime.InteropServices.Marshal.AllocHGlobal(8192);
// This is a hacktastic way of getting sysname from uname ()
if (uname_syscall(buf) == 0)
{
uts = new Utsname();
uts.SysName = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(buf);
long bufVal = buf.ToInt64();
uts.NodeName = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(new System.IntPtr(bufVal + 1 * 65));
uts.Release = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(new System.IntPtr(bufVal + 2 * 65));
uts.Version = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(new System.IntPtr(bufVal + 3 * 65));
uts.Machine = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(new System.IntPtr(bufVal + 4 * 65));
uts.DomainName = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(new System.IntPtr(bufVal + 5 * 65));
if (buf != System.IntPtr.Zero)
System.Runtime.InteropServices.Marshal.FreeHGlobal(buf);
} // End if (uname_syscall(buf) == 0)
return uts;
} // End Function Uname
版本不需要 unsafe 代码,也不需要 Mono:
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace UnameTest
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct Utsname
{
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 65)]
public byte[] sysname;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 65)]
public byte[] nodename;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 65)]
public byte[] release;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 65)]
public byte[] version;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 65)]
public byte[] machine;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 65)]
public byte[] domainname;
}
public static class Uts
{
[DllImport("libc", EntryPoint = "uname", CallingConvention = CallingConvention.Cdecl)]
internal static extern int uname(ref Utsname buf);
public static void PrintUtsname()
{
Utsname buf = new Utsname();
uname(ref buf);
Console.WriteLine($"Utsname:");
Console.WriteLine($"---------------------------------");
Console.WriteLine($"sysname: {GetString(buf.sysname)}");
Console.WriteLine($"nodename: {GetString(buf.nodename)}");
Console.WriteLine($"release: {GetString(buf.release)}");
Console.WriteLine($"version: {GetString(buf.version)}");
Console.WriteLine($"machine: {GetString(buf.machine)}");
Console.WriteLine($"domainname: {GetString(buf.domainname)}");
}
private static string GetString(in byte[] data)
{
var pos = Array.IndexOf<byte>(data, 0);
return Encoding.ASCII.GetString(data, 0, (pos < 0) ? data.Length : pos);
}
}
}