P/Invoking 时是否可以访问 C# 中的“errno”变量?这类似于 Win32 GetLastError()。
我相当确定有办法,但这可能是一个坏主意。您如何保证运行时在其内部处理期间没有调用某些影响
errno
的 CRT 函数?
出于同样的原因,您也不应该直接致电
GetLastError
。 DllImportAttribute
提供了 SetLastError
属性,以便运行时知道立即捕获最后一个错误并将其存储在托管代码可以使用 Marshal.GetLastWin32Error
读取的位置。
我认为在这种情况下,您可以做的最可靠的事情是创建一个 C DLL,它既执行实际的 C 工作,又执行捕获
errno
。 (请注意,仅在 errno
捕获周围编写包装器仍然会出现上述问题。)
解决方案是在
SetLastError
上使用 DllImport
。这将使运行时保存最后一个错误,以便可以从Marshal.GetLastWin32Error
访问它。
直接调用
GetLastError
有两个问题:
是的,有可能 -
GetLastError
正是这样做的。然而,正如binarycoder指出的,你不应该直接这样做 - 相反,在你的SetLastError
上设置DllImport
来自动执行和缓存(并避免多线程问题或运行时调用的函数修改errno
值) - 然后,在调用 P/Invoked 函数时,检查它的返回状态,如果它显示错误条件 - 抛出 Win32Exception
,它会自动读取最后一个错误的值。是的,即使在 Linux 上的 Mono 上也是如此。
是的,这是可能的。
乍一看,errno 似乎很神奇。
但所有的魔法都是基于欺骗,errno 也是如此。
errno 不是变量,它是预处理器定义!
来自 FreeBSD 手册页:
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://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
}
}
注:
正如 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);
我相信你可以
throw new Win32Exception()
。它的无参数构造函数将获取最后一个错误代码,并为您获取消息字符串。
它适用于 Windows、Linux 和 macOS(尽管名称令人困惑)。