从 C# 访问 C 全局变量 'errno'

Posted

技术标签:

【中文标题】从 C# 访问 C 全局变量 \'errno\'【英文标题】:Access C global variable 'errno' from C#从 C# 访问 C 全局变量 'errno' 【发布时间】:2011-01-29 22:46:53 【问题描述】:

P/Invoking 时是否可以访问 C# 中的“errno”变量?这类似于 Win32 GetLastError()。

【问题讨论】:

【参考方案1】:

我很确定有办法,但这可能是个坏主意。您如何保证运行时在其影响errno 的内部处理期间没有调用某些 CRT 函数?

出于同样的原因,您也不应该直接调用GetLastErrorDllImportAttribute 提供了一个SetLastError 属性,因此运行时知道立即捕获最后一个错误并将其存储在托管代码可以使用Marshal.GetLastWin32Error 读取的位置。

我认为在这种情况下您可以做的最强大的事情是创建一个 C DLL,它既可以执行实际的 C 工作,又可以捕获 errno。 (请注意,仅在 errno 捕获周围编写一个包装器仍然存在上述问题。)

【讨论】:

是的,我同意这一点。将 C 包装器设置为目标函数可能会更好,该目标函数又会从中返回 errno 的值。【参考方案2】:

是的,这是可能的——GetLastError 正是这样做的。但是,正如 binarycoder 指出的那样,您不应该直接执行此操作 - 而是在您的 DllImport 上设置 SetLastError 以自动执行和缓存(并避免多线程问题或运行时调用的函数修改 errno 值) - 然后,在调用 P/Invoked 函数时,检查它的返回状态,如果它显示错误条件 - 抛出 Win32Exception,它会自动读取最后一个错误的值。是的,即使在 Linux 上的 Mono 上也是如此。

【讨论】:

【参考方案3】:

解决方案是在DllImport 上使用SetLastError。这将使运行时保存最后一个错误,以便可以从Marshal.GetLastWin32Error 访问它。

直接调用GetLastError有两个问题:

运行时可能会在 PInvoke 返回后的某个时间执行,然后您才能获得最后一个错误 多个 .NET 线程可以驻留在同一个本机线程上。这可能导致 2 个 .NET 线程执行 PInvokes,本机库不知道更好,然后会覆盖最后一个错误。所以 .NET 中的线程 A 得到线程 B 的最后一个错误(可能)。

【讨论】:

你能举例说明在DllImport上使用SetLastError的语法吗? @CraigMcQueen [DllImport(LIB, CallingConvention=CallingConvention.Cdecl, SetLastError=true)] 但这仅适用于 dotnet-sdk-5.0 但不适用于 Mono ☹【参考方案4】:

是的,这是可能的。 起初,errno 似乎很神奇。 但是所有的魔法都是基于欺骗的,errno 也是如此。 errno 不是变量,它是预处理器定义的!

来自FreeBSD man-page:

extern int* __error();
#define errno (* __error())

查看我的 KDE-Neon Linux 上的源代码:

extern int *__errno_location (void) __THROW __attribute_const__;
# define errno (*__errno_location ())

(/usr/include/errno.h)

所以你可以像这样在 C# 中得到它: (注意,我假设它在 Linux 上的工作原理相同,我还没有测试我是否正确处理了指针)

namespace MonoReplacement



    class MonoSux
    

        private const string LIBC = "libc";


        // [System.CLSCompliant(false)]
        [System.Flags]
        public enum AccessModes
            :int 
        
            R_OK = 1,
            W_OK = 2,
            X_OK = 4,
            F_OK = 8
        

        public enum Errno
            :int 
        
            EPERM = 1,
            ENOENT = 2,
            ESRCH = 3,
            EINTR = 4,
            EIO = 5,
            ENXIO = 6,
            E2BIG = 7,
            ENOEXEC = 8,
            EBADF = 9,
            ECHILD = 10,
            EAGAIN = 11,
            EWOULDBLOCK = 11,
            ENOMEM = 12,
            EACCES = 13,
            EFAULT = 14,
            ENOTBLK = 15,
            EBUSY = 16,
            EEXIST = 17,
            EXDEV = 18,
            ENODEV = 19,
            ENOTDIR = 20,
            EISDIR = 21,
            EINVAL = 22,
            ENFILE = 23,
            EMFILE = 24,
            ENOTTY = 25,
            ETXTBSY = 26,
            EFBIG = 27,
            ENOSPC = 28,
            ESPIPE = 29,
            EROFS = 30,
            EMLINK = 31,
            EPIPE = 32,
            EDOM = 33,
            ERANGE = 34,
            EDEADLK = 35,
            EDEADLOCK = 35,
            ENAMETOOLONG = 36,
            ENOLCK = 37,
            ENOSYS = 38,
            ENOTEMPTY = 39,
            ELOOP = 40,
            ENOMSG = 42,
            EIDRM = 43,
            ECHRNG = 44,
            EL2NSYNC = 45,
            EL3HLT = 46,
            EL3RST = 47,
            ELNRNG = 48,
            EUNATCH = 49,
            ENOCSI = 50,
            EL2HLT = 51,
            EBADE = 52,
            EBADR = 53,
            EXFULL = 54,
            ENOANO = 55,
            EBADRQC = 56,
            EBADSLT = 57,
            EBFONT = 59,
            ENOSTR = 60,
            ENODATA = 61,
            ETIME = 62,
            ENOSR = 63,
            ENONET = 64,
            ENOPKG = 65,
            EREMOTE = 66,
            ENOLINK = 67,
            EADV = 68,
            ESRMNT = 69,
            ECOMM = 70,
            EPROTO = 71,
            EMULTIHOP = 72,
            EDOTDOT = 73,
            EBADMSG = 74,
            EOVERFLOW = 75,
            ENOTUNIQ = 76,
            EBADFD = 77,
            EREMCHG = 78,
            ELIBACC = 79,
            ELIBBAD = 80,
            ELIBSCN = 81,
            ELIBMAX = 82,
            ELIBEXEC = 83,
            EILSEQ = 84,
            ERESTART = 85,
            ESTRPIPE = 86,
            EUSERS = 87,
            ENOTSOCK = 88,
            EDESTADDRREQ = 89,
            EMSGSIZE = 90,
            EPROTOTYPE = 91,
            ENOPROTOOPT = 92,
            EPROTONOSUPPORT = 93,
            ESOCKTNOSUPPORT = 94,
            EOPNOTSUPP = 95,
            EPFNOSUPPORT = 96,
            EAFNOSUPPORT = 97,
            EADDRINUSE = 98,
            EADDRNOTAVAIL = 99,
            ENETDOWN = 100,
            ENETUNREACH = 101,
            ENETRESET = 102,
            ECONNABORTED = 103,
            ECONNRESET = 104,
            ENOBUFS = 105,
            EISCONN = 106,
            ENOTCONN = 107,
            ESHUTDOWN = 108,
            ETOOMANYREFS = 109,
            ETIMEDOUT = 110,
            ECONNREFUSED = 111,
            EHOSTDOWN = 112,
            EHOSTUNREACH = 113,
            EALREADY = 114,
            EINPROGRESS = 115,
            ESTALE = 116,
            EUCLEAN = 117,
            ENOTNAM = 118,
            ENAVAIL = 119,
            EISNAM = 120,
            EREMOTEIO = 121,
            EDQUOT = 122,
            ENOMEDIUM = 123,
            EMEDIUMTYPE = 124,
            ECANCELED = 125,
            ENOKEY = 126,
            EKEYEXPIRED = 127,
            EKEYREVOKED = 128,
            EKEYREJECTED = 129,
            EOWNERDEAD = 130,
            ENOTRECOVERABLE = 131,
            EPROCLIM = 1067,
            EBADRPC = 1072,
            ERPCMISMATCH = 1073,
            EPROGUNAVAIL = 1074,
            EPROGMISMATCH = 1075,
            EPROCUNAVAIL = 1076,
            EFTYPE = 1079,
            EAUTH = 1080,
            ENEEDAUTH = 1081,
            EPWROFF = 1082,
            EDEVERR = 1083,
            EBADEXEC = 1085,
            EBADARCH = 1086,
            ESHLIBVERS = 1087,
            EBADMACHO = 1088,
            ENOATTR = 1093,
            ENOPOLICY = 1103
        


        // int access(const char *pathname, int mode);
        // https://linux.die.net/man/2/access
        [System.Security.SuppressUnmanagedCodeSecurity]
        [System.Runtime.InteropServices.DllImport(LIBC, CallingConvention = System.Runtime.InteropServices.CallingConvention.Cdecl, EntryPoint = "access", SetLastError=true)]
#if USE_LPUTF8Str
        internal static extern int access([System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPUTF8Str)] string path, AccessModes mode);
#else
        internal static extern int access([System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(libACL.Unix.FileNameMarshaler))] string path, AccessModes mode);
#endif


        // char *strerror(int errnum);
        // https://man7.org/linux/man-pages/man3/strerror.3.html
        [System.Security.SuppressUnmanagedCodeSecurity]
        [System.Runtime.InteropServices.DllImport(LIBC, CallingConvention = System.Runtime.InteropServices.CallingConvention.Cdecl, EntryPoint = "strerror")]
        internal static extern string strerror(Errno errnum);


        [System.Security.SuppressUnmanagedCodeSecurity]
        [System.Runtime.InteropServices.DllImport(LIBC, EntryPoint = "__errno_location", CallingConvention = System.Runtime.InteropServices.CallingConvention.Cdecl)]
        internal static extern System.IntPtr __errno_location();
        




        /// <summary>
        /// access() checks whether the calling process can access the file pathname. If pathname is a symbolic link, it is dereferenced.
        /// </summary>
        /// <param name="pathmame"></param>
        /// <param name="mode"></param>
        /// <returns>On success (all requested permissions granted), zero is returned. On error (at least one bit in mode asked for a permission that is denied, or some other error occurred), -1 is returned, and errno is set appropriately.</returns>
        public static bool Access(string pathmame, AccessModes mode)
        
            int ret = access(pathmame, mode);

            if (ret == -1)
            
                // return null;
                System.Console.Error.WriteLine("Error on Access");

                // https://github.com/mono/mono/blob/master/mcs/class/Mono.Posix/Mono.Unix.Native/Stdlib.cs
                // https://***.com/questions/2485648/access-c-global-variable-errno-from-c-sharp
                // Errno errno = error();
                System.IntPtr errorptr = __errno_location();
                Errno errno = (Errno)System.Runtime.InteropServices.Marshal.ReadInt32(errorptr);

                string message = strerror(errno);

                // throw ACLManagerException(Glib::locale_to_utf8(strerror(errno)));
                throw new System.InvalidOperationException(message);
             // End if (ret == -1) 

            return ret == 0;
         // End Function Access 


    



注意: 正如 Jason Kresowaty 所提到的,以这种方式获取 errorno 是一个坏主意,因为 CLR 可能会同时调用函数。 但是根据developers.redhat.com,你也可以在Linux上使用Marshal.GetLastWin32Error。

例子:

[DllImport("libc", SetLastError = true))]
public static extern int kill(int pid, int sig);

SetLastError 表示函数使用 errno 来指示发生了什么 错误的。 Marshal.GetLastWin32Error 可用于检索errno。

这也是Tmds.LibC 所做的(这里,属性errorno 替换了神奇的预处理器定义):

using System.Runtime.InteropServices;

namespace Tmds.Linux

    public static partial class LibC
    
        public static unsafe int errno
            // use the value captured by DllImport
            => Marshal.GetLastWin32Error();
    

因此,为了在 C# 中模拟这种行为,我们使用属性来代替:

public static Errno errno

    get
    
        // How would you guarantee that the runtime has not called some CRT function 
        // during its internal processing that has affected the errno?

        // For the same reason, you should not call GetLastError directly either. 
        // The DllImportAttribute provides a SetLastError property so the runtime knows 
        // to immediately capture the last error and store it in a place that the managed code 
        // can read using Marshal.GetLastWin32Error.

        // this work on Linux !
        // Marshal.GetLastWin32Error can be used to retrieve errno.
        return (Errno)System.Runtime.InteropServices.Marshal.GetLastWin32Error();
    


string message = strerror(errorno);

【讨论】:

以上是关于从 C# 访问 C 全局变量 'errno'的主要内容,如果未能解决你的问题,请参考以下文章

从 C 中的另一个文件访问全局静态变量

如何在 C# 中使用全局变量?

XLua 入门之 C# 访问 Lua

仅在目标 C 中本地更新全局变量值

C语言全局变量(c文件和h文件中的全局变量静态全局变量)使用注意事项

C语言全局变量(c文件和h文件中的全局变量静态全局变量)使用注意事项