开发者

Access C global variable 'errno' from C#

开发者 https://www.devze.com 2022-12-24 16:20 出处:网络
Is it possibl开发者_开发问答e to access the \"errno\" variable in C# when P/Invoking? This is similar to Win32 GetLastError().I\'m fairly sure that there is a way, but it probably is a bad idea. How w

Is it possibl开发者_开发问答e to access the "errno" variable in C# when P/Invoking? This is similar to Win32 GetLastError().


I'm fairly sure that there is a way, but it probably is a bad idea. 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.

I think the most robust thing you could do in this case is make a C DLL that performs both the actual C work and the capture of the errno. (Note that just writing a wrapper around the errno capture would still have the concerns mentioned above.)


Yes, it is possible - GetLastError does exactly that. However, as binarycoder pointed out, you should not do this directly - instead, set SetLastError on your DllImport to have this performed and cached automatically (and to avoid multithreading problems or runtime-invoked functions modifying the errno value) - then, on invoking the P/Invoked function, check it's return status, and if it's showing an error condition - throw Win32Exception, which reads the value of last error automatically. Yes, even on Mono on Linux.


The solution is to use SetLastError on DllImport. This will make the runtime save the last error so it can be accessed from Marshal.GetLastWin32Error.

There are two problems with calling GetLastError directly:

  • The runtime might do sometime after the PInvoke returns before you are able to get the last error
  • Multiple .NET threads can reside on the same native thread. This can result in 2 .NET threads doing PInvokes, native libraries not knowing any better, would then overwrite the last error. So thread A in .NET getting thread B's last error (potentially).


Yep, it is possible.
At first, errno seems like magic.
But all magic is based on deception, and so is errno.
errno is not a variable, it's a preprocessor-define !

From the FreeBSD man-page:

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

Looking into the sources on my KDE-Neon Linux:

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

(/usr/include/errno.h)

So you can get it in C# like this:
(caution, I assume it works the same on Linux, and I have not yet tested if I have handled the pointer correctly)

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://stackoverflow.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 


    }


}

Note:
As mentioned by Jason Kresowaty, getting the errorno this way is a bad idea, because the CLR might call functions in the meantime.
But according to developers.redhat.com, you can use Marshal.GetLastWin32Error on Linux, too.

Example:

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

SetLastError means the function uses errno to indicate what went wrong. Marshal.GetLastWin32Error can be used to retrieve errno.

This is also what Tmds.LibC does (here, the property errorno replaces the magic preprocessor-define):

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();
    }
}

So, to emulate this behaviour in C#, we use a property instead:

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);
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号