在Linux上的.NET Core中从C#获取Uname发布字段

时间:2019-03-16 09:36:08

标签: c# .net-core posix pinvoke

我试图在运行于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());
    }
}

似乎导致它失败。我只是不确定自己在做什么错。它默默地失败了,大概是由于段错误导致的,尽管我不确定在哪里/如何找到它。

我尝试过的其他结构编组方法

我还尝试了其他几种方法来恢复结构。

最简单的似乎是具有固定长度值的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#中从release获取uname字段的最佳(最快可靠)方法是什么?
  • 我如何可靠地在libc中P /调用uname()的系统调用以恢复utsname结构?

3 个答案:

答案 0 :(得分:0)

将代码移至函数时,它不起作用的原因是您的结构不包含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);
        }
}

答案 1 :(得分:0)

这是不需要不安全代码的版本:

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

答案 2 :(得分:0)

不需要不安全代码或 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);
        }
    }
}