获取 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.Mac
和 libxammac.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 包含详细信息的字典列表。从中我们提取 kCGWindowOwnerName
。 CFStringRef
被函数 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,所有者也会返回多次,等等。应该确定什么的确切逻辑可能必须根据您的要求进行更改。但是,上面的代码至少应显示一种可能的方法来完成此操作。
截图
在 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.Mac
和 libxammac.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 包含详细信息的字典列表。从中我们提取 kCGWindowOwnerName
。 CFStringRef
被函数 copyUTF8String
.
按照惯例,像 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,所有者也会返回多次,等等。应该确定什么的确切逻辑可能必须根据您的要求进行更改。但是,上面的代码至少应显示一种可能的方法来完成此操作。
截图