获取 macOS 上 .Net Core 运行 中所有打开 windows 的列表(通过 NSApplication?)

Get list of all open windows in .Net Core running on macOS (via NSApplication?)

在 Windows 上获取 .Net Framework 中打开的 windows 的列表是 relatively easy。我如何在 macOS 上的 .Net Core/.Net 5 或更高版本中执行相同的操作?

澄清一下,我正在寻找一种方法来检索任何 运行 application/process 拥有的所有打开的 windows 的列表。我没有太多 macOS 开发经验 - 我是一名 Windows 开发人员 - 但我已尝试使用 所建议的 NSApplication

我在 macOS Monterey (12.2) 上的 VS2022 中创建了一个 .Net 6.0 控制台应用程序,添加了对 Xamarin.Maclibxammac.dylib 的引用,如 here 所述 - 在Xamarin 而不是 .Net,但我没有看到任何其他选项来创建控制台应用程序。使用简单的代码:

 static void Main(string[] args)
    {
        NSApplication.Init();
    }

我得到输出

Xamarin.Mac: dlopen error: dlsym(RTLD_DEFAULT, mono_get_runtime_build_info): symbol not found

我不知道这是什么意思。我什至不确定这种方法有什么优点。

有谁知道是否可以从 .Net Core/6.0 应用程序中使用 NSApplication,如果可以,NSApplication 是否能让我读取系统范围的打开 windows?如果没有,还有其他方法可以实现吗?

这仅供我自己内部使用,在我自己的环境之外不需要以任何方式便携或稳定。

在您所指的link中,有一个重要说明:

... as Xamarin.Mac.dll does not run under the .NET Core runtime, it only runs with the Mono runtime.

因为您在 .net-core 下尝试 运行 Xamarin.Mac.dll,您会收到此 dlopen 错误。

否 System-wide 通过 NSApplication 列出

如果您想阅读 system-wide 个打开的 windows 列表,则 NSApplication.shared.windows 的链接答案是不正确的。它只能用于确定发出调用的应用程序当前存在的所有 windows,请参阅 Apple's documentation

备选方案

不过,有几种方法可以在 macOS 中访问 Window 信息。其中之一可能是小型非托管 C-lib,它通过 CoreFoundation 和 CoreGraphics 获取必要的信息,并通过 Platform Invoke (P/Invoke) returns 将其传送到 C#。

本地代码

这是 C-Lib 的示例代码,它确定 returns window 所有者的姓名。

WindowsListLib.h

extern char const **windowList(void);
extern void freeWindowList(char const **list);

图书馆的界面只有两个功能。第一个函数调用 windowList returns 一个包含 window 所有者姓名的列表。列表的最后一个元素必须为 NULL,以便您可以检测到列表在托管 C# 端的结束位置。由于字符串列表的内存是动态分配的,处理后必须使用freeWindowList函数释放相关内存。

WindowsListLib.c

#include "WindowListLib.h"
#include <CoreFoundation/CoreFoundation.h>
#include <CoreGraphics/CoreGraphics.h>

static void errorExit(char *msg) {
    fprintf(stderr, "%s\n", msg);
    exit(1);
}

static char *copyUTF8String(CFStringRef string) {
    CFIndex length = CFStringGetLength(string);
    CFIndex size = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
    char *buf = malloc(size);
    if(!buf) {
        errorExit("malloc failed");
    }
    if(!CFStringGetCString(string, buf, size, kCFStringEncodingUTF8)) {
        errorExit("copyUTF8String with utf8 encoding failed");
    }
    return buf;
}


char const **windowList(void) {
    CFArrayRef cfWindowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly,  kCGNullWindowID);
    CFIndex count = CFArrayGetCount(cfWindowList);
    char const **list = malloc(sizeof(char *) * (count + 1));
    if(!list) {
        errorExit("malloc failed");
    }
    list[count] = NULL;
    for(CFIndex i = 0; i < count; i++) {
        CFDictionaryRef windowInfo = CFArrayGetValueAtIndex(cfWindowList, i);
        CFStringRef name = CFDictionaryGetValue(windowInfo, kCGWindowOwnerName);
        if(name) {
            list[i] = copyUTF8String(name);
        } else {
            list[i] = strdup("unknown");
        }
    }
    CFRelease(cfWindowList);
    return list;
}

void freeWindowList(char const **list) {
    const char **ptr = list;
    while(*ptr++) {
        free((void *)*ptr);
    }
    free(list);
}

CGWindowListCopyWindowInfo是获取window信息的实际函数。它 returns 包含详细信息的字典列表。从中我们提取 kCGWindowOwnerNameCFStringRef 被函数 copyUTF8String.

转换为动态分配的 UTF-8 字符串

按照惯例,像 CGWindowListCopyWindowInfo 这样包含单词 copy(或 create)的调用必须在与 CFRelease 一起使用后释放,以避免造成内存泄漏。

C#代码

然后可以在 C# 端像这样调用整个过程:

using System.Runtime.InteropServices;

namespace WindowList
{
    public static class Program
    {
        [DllImport("WindowListLib", EntryPoint = "windowList")]
        private static extern IntPtr WindowList();

        [DllImport("WindowListLib", EntryPoint = "freeWindowList")]
        private static extern void FreeWindowList(IntPtr list);

        private static List<string> GetWindows()
        {
            var nativeWindowList = WindowList();
            var windows = new List<string>();
            var nativeWindowPtr = nativeWindowList;
            string? windowName;
            do
            {
                var strPtr = Marshal.ReadIntPtr(nativeWindowPtr);
                windowName = Marshal.PtrToStringUTF8(strPtr);
                if (windowName == null) continue;
                windows.Add(windowName);
                nativeWindowPtr += Marshal.SizeOf(typeof(IntPtr));
            } while (windowName != null);

            FreeWindowList(nativeWindowList);
            return windows;
        }


        static void Main()
        {
            foreach (var winName in GetWindows())
            {
                Console.WriteLine(winName);
            }
        }
    }
}

GetWindows 方法通过对 WindowList 的本机调用获取数据并将 C 字符串转换为托管字符串,然后通过对 FreeWindowList 的调用释放本机资源。

此函数returns只返回所有者名称,如Finder、Xcode、Safari等,如果有多个windows,所有者也会返回多次,等等。应该确定什么的确切逻辑可能必须根据您的要求进行更改。但是,上面的代码至少应显示一种可能的方法来完成此操作。

截图