You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 20:12:53