如何正确P/Invoke CFDictionaryGetValue?macOS C#窗口列表获取崩溃
问题原因及解决方案
核心错误点
崩溃的直接原因是**CFDictionaryGetValue的第二个参数类型不匹配**:
- 原生
CFDictionaryGetValue要求第二个参数是CFStringRef(CoreFoundation字符串对象的指针),但你直接传递了C#的string类型。托管字符串和CoreFoundation字符串的内存布局完全不同,原生函数收到无效指针后直接触发崩溃,这种原生层面的错误无法被.NET的try/catch捕获。
修正步骤与完整代码
1. 补充CoreFoundation字符串相关的P/Invoke声明
需要添加创建CFString、转换为托管字符串、释放对象的方法:
public static class Native { public static uint kCGWindowListOptionOnScreenOnly = 1; public static uint kCGNullWindowID = 0; // CoreFoundation UTF-8编码常量 public static uint kCFStringEncodingUTF8 = 0x08000100; [DllImport("/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics")] public static extern IntPtr CGWindowListCopyWindowInfo(uint options, uint winid); [DllImport("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation")] public static extern int CFArrayGetCount(IntPtr theArrayRef); [DllImport("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation")] public static extern IntPtr CFArrayGetValueAtIndex(IntPtr theArrayRef, int index); [DllImport("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation")] public static extern IntPtr CFDictionaryGetValue(IntPtr dictionaryRef, IntPtr key); // 从C#字符串创建CFStringRef [DllImport("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation")] public static extern IntPtr CFStringCreateWithCString(IntPtr allocator, string cStr, uint encoding); // 将CFStringRef转为C字符串指针 [DllImport("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation")] public static extern IntPtr CFStringGetCStringPtr(IntPtr theString, uint encoding); // 释放CoreFoundation对象,避免内存泄漏 [DllImport("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation")] public static extern void CFRelease(IntPtr cf); }
2. 修正调用逻辑
先将C#键字符串转为CFStringRef再传入字典查询,最后转换回托管字符串并释放CF对象:
static void test() { IntPtr windowList = Native.CGWindowListCopyWindowInfo(Native.kCGWindowListOptionOnScreenOnly, Native.kCGNullWindowID); try { int count = Native.CFArrayGetCount(windowList); for (int idx = 0; idx < count; idx++) { IntPtr windowInfo = Native.CFArrayGetValueAtIndex(windowList, idx); // 创建CFString类型的字典键 IntPtr keyRef = Native.CFStringCreateWithCString(IntPtr.Zero, "kCGWindowOwnerName", Native.kCFStringEncodingUTF8); try { IntPtr ownerNameRef = Native.CFDictionaryGetValue(windowInfo, keyRef); if (ownerNameRef != IntPtr.Zero) { // 将CFString转为C#字符串 IntPtr cStrPtr = Native.CFStringGetCStringPtr(ownerNameRef, Native.kCFStringEncodingUTF8); if (cStrPtr != IntPtr.Zero) { string ownerName = System.Runtime.InteropServices.Marshal.PtrToStringUTF8(cStrPtr); Console.WriteLine($"窗口拥有者: {ownerName}"); } } } finally { // 释放创建的CFString对象 Native.CFRelease(keyRef); } } } finally { // 释放窗口列表的CFArray对象 Native.CFRelease(windowList); } }
额外注意事项
- 引用计数管理:所有通过
Copy或Create开头的CoreFoundation API返回的对象,必须调用CFRelease释放,否则会造成内存泄漏。 - 库路径优化:直接引用框架主文件(如
CoreGraphics.framework/CoreGraphics)比BridgeSupport路径更可靠,BridgeSupport主要用于脚本语言绑定,不是原生P/Invoke的最佳选择。 - 空指针判断:添加对返回指针是否为
IntPtr.Zero的检查,避免无效内存访问。
内容的提问来源于stack exchange,提问作者GuruGurra
相关产品推荐
相关产品推荐

