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

Ken*_*enD 7 c# macos .net-core

在 Windows 上获取 .Net Framework 中打开的窗口列表相对容易。如何在 macOS 上的 .Net Core/.Net 5 或更高版本中执行相同操作?

为了澄清这一点,我正在寻找一种方法来检索任何正在运行的应用程序/进程拥有的所有打开的窗口的列表。我没有太多 macOS 开发经验 - 我是一名 Windows 开发人员 - 但我尝试使用此答案NSApplication建议的方法。

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

 static void Main(string[] args)
    {
        NSApplication.Init();
    }
Run Code Online (Sandbox Code Playgroud)

我得到输出

Xamarin.Mac:dlopen错误:dlsym(RTLD_DEFAULT,mono_get_runtime_build_info):找不到符号

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

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

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

Ste*_*cht 7

在您引用的链接中,有一个重要说明:

...由于 Xamarin.Mac.dll 不在 .NET Core 运行时下运行,它仅在 Mono 运行时运行。

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

没有通过 NSApplication 的系统范围列表

如果您想读取打开窗口的系统范围列表,则与 NSApplication.shared.windows 的链接答案是不正确的。它只能用于确定发出调用的应用程序的所有当前现有窗口,请参阅Apple 的文档

替代解决方案

尽管如此,有多种方法可以访问 macOS 中的窗口信息。其中之一可能是一个小型的非托管 C-lib,它通过 CoreFoundation 和 CoreGraphics 获取必要的信息,并通过 Platform Invoke (P/Invoke) 将其返回到 C#。

本机代码

下面是 C-Lib 的示例代码,用于确定并返回窗口所有者的名称。

Windows列表库.h

extern char const **windowList(void);
extern void freeWindowList(char const **list);
Run Code Online (Sandbox Code Playgroud)

该库的接口仅包含两个函数。调用的第一个函数windowList返回一个包含窗口所有者名称的列表。列表的最后一个元素必须为 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);
}
Run Code Online (Sandbox Code Playgroud)

CGWindowListCopyWindowInfo是获取窗口信息的实际函数。它返回包含详细信息的字典列表。我们从中提取kCGWindowOwnerName. CFStringRef函数将其转换为动态分配的 UTF-8 字符串copyUTF8String

CGWindowListCopyWindowInfo按照惯例,包含单词copy(或)的调用create必须在使用 with 后释放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);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

该函数仅返回所有者名称,例如Finder、Xcode、Safari等。如果有多个窗口,所有者也会被多次返回等。具体应该确定的逻辑可能需要根据根据您的要求。然而,上面的代码至少应该展示一种可能的方法来实现这一点。

截屏

演示