在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
的输出.
I'm trying to get the output of uname -r
in C# in .NET Core 2.2 running on Ubuntu 18.04.
我在编写此文件时会考虑到性能,因此一直尝试使用P/Invoke来实现它.
I'm writing this with performance in mind, so have been trying to use a P/Invoke to achieve it.
uname(2)
文档指示我需要传递带有相关大小字段的结构.在玩了很多变体之后,我想到了:
The uname(2)
docs indicate I need to pass a struct in with the relevant sized fields. After playing with a lot of variations, I came up with:
[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));
}
}
这似乎可行,但是将其移入包装函数,如:
This seems to work, but moving it into a wrapper function like:
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());
}
}
似乎导致它失败.我只是不确定自己在做什么错.它默默地失败了,大概是由于段错误造成的,尽管我不确定在哪里/如何找到它.
Seems to cause it to fail. I'm just not sure what I'm doing wrong. It fails silently, presumably due to a segfault, although I'm not sure where/how to get a trace of that.
我还尝试了其他几种方法来恢复该结构.
I also tried a few other ways to get the struct back.
最简单的方法似乎是具有固定长度值的string
字段(但我认为这样做会失败,因为调用方需要分配可变字段供被调用方设置):
The simplest seemed to be the string
fields with fixed-length values (but I assume this fails because the caller needs to allocate mutable fields for the callee to set):
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调用约定有关.
In this case, I assume the problem is something to do with the In/Out calling convention when passing a managed array into the call.
我也尝试使用out
而不是ref
来简化P/Invoke,但是我得到的印象是uname()
希望调用者在调用之前分配内存.
I tried using out
instead of ref
to simplify the P/Invoke as well, but I get the impression uname()
expects the caller to allocate the memory before the call.
我也尝试使用[In]
和[Out]
属性,但是不确定默认值是什么或使用它们会如何改变.
I also tried using the [In]
and [Out]
attributes, but not sure what the defaults are or how using them would change things.
我还编写了一个小的C库来包装调用,以使调用约定更易于处理:
I also wrote a small C library to wrap the call to make the calling convention easier to handle:
#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的旁边.
I compiled this with gcc -shared -o libget_uname.so -fPIC get_uname.c
and put it next to the main managed DLL.
只需:
public static class Main
{
...
[DllImport("libget_uname.so", EntryPoint = "uname_get_release", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
internal static extern string GetUnameRelease();
}
这似乎在我每次使用时都有效.
This seemed to work every time I used it.
但是我不建议在代码中包含本机库,如果可以直接P/Invoke代替的话.
But I'm averse to including a native library in code, if it might be possible to just P/Invoke directly instead.
另一个明显的简单选择是将uname
coreutil称为子进程:
The other obvious simple choice would just be to call the uname
coreutil as a subprocess:
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上还不错,值得一试?
But I was hoping to avoid the overhead of a subprocess... Perhaps it's not so bad on Linux and just worth doing?
但是我现在花了一段时间研究PInvoke,所以我想知道是否有可能.
But I've spent a while looking into the PInvoke now, so I would like to know if it's possible.
所以我的问题是:
- 从C#中从
uname
获取release
字段的最佳(最快可靠方式)是什么? - 我如何可靠地在libc中P/调用
uname()
syscall以获取utsname
结构?
- What's the best (fastest reliable) way to get the
release
field fromuname
from C#? - How would I P/Invoke the
uname()
syscall in libc reliably to get theutsname
struct back?
推荐答案
将代码移至函数时不起作用的原因是您的结构不包含domainname
成员,因此在调用
The reason it is not working when you move the code to a function is that your structure does not include the domainname
member, so when you call uname
it is clobbering memory beyond the memory you allocated for your structure.
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);
}
}
这篇关于在Linux上的.NET Core中从C#获取uname发布字段的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!