从Object-C迁移到.NET7:如何在macOS用C#获取文件所有者信息?
解决.NET 7跨平台获取文件所有者和组的问题
问题原因
直接PInvoke调用Objective-C的NSFileManager无效,因为Objective-C的类和方法基于消息发送机制实现,并非C风格的导出函数,无法通过DllImport直接调用。而FileInfo.GetAccessControl仅支持Windows系统,无法在macOS/Linux上使用。
推荐解决方案:调用Unix标准C API
通过调用macOS/Linux系统标准C库(libc.dylib/libc.so)中的stat、getpwuid、getgrgid函数,获取文件的UID/GID后转换为对应的用户名和组名,这是跨平台且稳定的实现方式:
using System; using System.Runtime.InteropServices; using System.ComponentModel; public static class UnixFileInfoHelper { [StructLayout(LayoutKind.Sequential)] private struct stat { public long st_dev; public uint st_mode; public long st_nlink; public uint st_ino; public uint st_uid; public uint st_gid; public long st_rdev; public long st_size; public long st_atime; public long st_mtime; public long st_ctime; public long st_blksize; public long st_blocks; } [DllImport("libc", SetLastError = true)] private static extern int stat(string path, ref stat buf); [DllImport("libc")] private static extern IntPtr getpwuid(uint uid); [DllImport("libc")] private static extern IntPtr getgrgid(uint gid); [StructLayout(LayoutKind.Sequential)] private struct passwd { public IntPtr pw_name; } [StructLayout(LayoutKind.Sequential)] private struct group { public IntPtr gr_name; } public static (string Owner, string Group) GetFileOwnerAndGroup(string filePath) { var statBuffer = new stat(); if (stat(filePath, ref statBuffer) != 0) { throw new Win32Exception(Marshal.GetLastWin32Error()); } // 解析用户名 var pwPtr = getpwuid(statBuffer.st_uid); if (pwPtr == IntPtr.Zero) { throw new InvalidOperationException($"无法解析UID {statBuffer.st_uid} 对应的用户名"); } var passwdData = Marshal.PtrToStructure<passwd>(pwPtr); string owner = Marshal.PtrToStringAnsi(passwdData.pw_name) ?? statBuffer.st_uid.ToString(); // 解析组名 var grPtr = getgrgid(statBuffer.st_gid); if (grPtr == IntPtr.Zero) { throw new InvalidOperationException($"无法解析GID {statBuffer.st_gid} 对应的组名"); } var groupData = Marshal.PtrToStructure<group>(grPtr); string group = Marshal.PtrToStringAnsi(groupData.gr_name) ?? statBuffer.st_gid.ToString(); return (owner, group); } }
使用示例
try { var (owner, group) = UnixFileInfoHelper.GetFileOwnerAndGroup("/path/to/your/file"); Console.WriteLine($"所有者:{owner},组:{group}"); } catch (Exception ex) { Console.WriteLine($"获取文件信息失败:{ex.Message}"); }
备选方案:调用Objective-C运行时API(不推荐)
如果一定要沿用Objective-C的逻辑,需要通过Objective-C运行时的objc_msgSend发送消息调用API,但需要手动管理内存,复杂度较高:
using System; using System.Runtime.InteropServices; public static class ObjCFileInfoHelper { [DllImport("/usr/lib/libobjc.dylib")] private static extern IntPtr objc_getClass(string className); [DllImport("/usr/lib/libobjc.dylib")] private static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector); [DllImport("/usr/lib/libobjc.dylib")] private static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector, IntPtr arg1, int arg2); [DllImport("/usr/lib/libobjc.dylib")] private static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector, IntPtr arg); [DllImport("/usr/lib/libobjc.dylib")] private static extern IntPtr sel_registerName(string selectorName); [DllImport("/usr/lib/libobjc.dylib")] private static extern void objc_release(IntPtr obj); public static (string Owner, string Group) GetFileOwnerAndGroup(string filePath) { // 创建NSString路径 IntPtr nsStringClass = objc_getClass("NSString"); IntPtr stringWithUTF8Sel = sel_registerName("stringWithUTF8String:"); IntPtr nsFilePath = objc_msgSend(nsStringClass, stringWithUTF8Sel, filePath); // 获取NSFileManager实例 IntPtr fileManagerClass = objc_getClass("NSFileManager"); IntPtr sharedInstanceSel = sel_registerName("sharedInstance"); IntPtr fileManager = objc_msgSend(fileManagerClass, sharedInstanceSel); // 获取文件属性字典 IntPtr getAttributesSel = sel_registerName("fileAttributesAtPath:traverseLink:"); IntPtr attributesDict = objc_msgSend(fileManager, getAttributesSel, nsFilePath, 1); // 获取所有者名称 IntPtr ownerKey = objc_msgSend(nsStringClass, stringWithUTF8Sel, "NSFileOwnerAccountName"); IntPtr ownerNsString = objc_msgSend(attributesDict, sel_registerName("objectForKey:"), ownerKey); IntPtr ownerCStr = objc_msgSend(ownerNsString, sel_registerName("UTF8String")); string owner = Marshal.PtrToStringAnsi(ownerCStr); // 获取组名称 IntPtr groupKey = objc_msgSend(nsStringClass, stringWithUTF8Sel, "NSFileGroupOwnerAccountName"); IntPtr groupNsString = objc_msgSend(attributesDict, sel_registerName("objectForKey:"), groupKey); IntPtr groupCStr = objc_msgSend(groupNsString, sel_registerName("UTF8String")); string group = Marshal.PtrToStringAnsi(groupCStr); // 释放Objective-C对象,避免内存泄漏 objc_release(nsFilePath); objc_release(ownerKey); objc_release(ownerNsString); objc_release(groupKey); objc_release(groupNsString); // 注意:NSFileManager的sharedInstance是单例,不需要release return (owner, group); } }
注意事项
- 该方案仅适用于macOS,无法在Linux上运行
- 必须严格管理Objective-C对象的内存,否则会导致内存泄漏
- 需要处理Objective-C方法调用可能返回的
nil情况
内容的提问来源于stack exchange,提问作者Akino
相关产品推荐
相关产品推荐

