MacOS 12.6.8 Apple M1 Про
версия go go1.20.7 дарвин/arm64
Вызов функции GetWindowImage приводит к быстрому расширению памяти.
Код:
package main
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Cocoa -framework ApplicationServices
#import <Cocoa/Cocoa.h>
#import <ApplicationServices/ApplicationServices.h>
CGWindowID GetWindowNumber(const char *title) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *windowTitle = [NSString stringWithUTF8String:title];
CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
CGWindowID windowNumber = 0;
for (NSDictionary *entry in (__bridge NSArray *)windowList) {
NSString *winTitle = [entry objectForKey:(id)kCGWindowName];
// NSLog(@"Window Title: %@", winTitle);
if ([winTitle isEqualToString:windowTitle]) {
windowNumber = (CGWindowID)[[entry objectForKey:(id)kCGWindowNumber] integerValue];
break;
}
}
CFRelease(windowList);
[pool drain];
return windowNumber;
}
CFDataRef CaptureWindowImageData(CGWindowID windowNumber) {
CGWindowID windowList[] = {windowNumber};
CFArrayRef windowArray = CFArrayCreate(NULL, (const void **)windowList, 1, NULL);
CGImageRef image = CGWindowListCreateImageFromArray(CGRectNull, windowArray, kCGWindowImageDefault);
NSImage *nsImage = [[NSImage alloc] initWithCGImage:image size:NSZeroSize];
CFRelease(windowArray);
CGImageRelease(image);
NSBitmapImageRep *rep = [NSBitmapImageRep imageRepWithData:[nsImage TIFFRepresentation]];
NSDictionary *props = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:1.0] forKey:NSImageCompressionFactor];
CFDataRef imageData = (CFDataRef)[rep representationUsingType:NSBitmapImageFileTypePNG properties:props];
[nsImage release];
[rep release];
return imageData;
}
*/
import "C"
import (
"bytes"
"fmt"
"image"
"image/png"
"unsafe"
)
func GetWindowNumber(name string) uint {
cTitle := C.CString(name)
defer C.free(unsafe.Pointer(cTitle))
return uint(C.GetWindowNumber(cTitle))
}
func GetWindowImage(windowNumber uint) (image.Image, error) {
imageData := C.CaptureWindowImageData(C.uint(windowNumber))
if imageData == C.CFDataRef(unsafe.Pointer(nil)) {
return nil, fmt.Errorf("failed to get window image data for %d", windowNumber)
}
defer C.CFRelease(C.CFTypeRef(imageData))
dataLength := C.CFDataGetLength(imageData)
dataPtr := C.CFDataGetBytePtr(imageData)
data := C.GoBytes(unsafe.Pointer(dataPtr), C.int(dataLength))
return png.Decode(bytes.NewReader(data))
}
func main() {
winId := GetWindowNumber("Clock")
fmt.Println(winId)
for i := 0; i < 100; i++ {
_, err := GetWindowImage(winId)
if err != nil {
panic(err)
}
}
}
В настоящее время я не могу определить, в чем заключается проблема, будь то Objective-C или C.
Пожалуйста, помогите диагностировать проблему и подскажите, как исправить код.





Во-первых, здесь есть небольшая ошибка: исправление утечки памяти приведет к сбоям, поэтому, чтобы ее исправить, сначала удалите вызов [rep release]. Вы должны звонить release только тогда, когда вы вступили в (разделенную) собственность:
Вы становитесь владельцем объекта, если создаете его с помощью метода, имя которого начинается с «alloc» или «new» или содержит «copy» (например, alloc, newObject или mutableCopy), или если вы отправляете ему сообщение сохранения. Вы несете ответственность за отказ от владения принадлежащими вам объектами с помощью выпуска или автоматического выпуска. В любой другой раз, когда вы получаете объект, вы не должны его отдавать.
Подробности см. в Основные правила управления памятью.
Итак, в CaptureWindowImageData удалите строку [rep release].
Если это исправлено, ваша проблема заключается в том, что вы создаете большие объекты в пуле автоматического выпуска и никогда не опустошаете пул. Когда вас вызывают из контекста, отличного от ObjC, вы несете ответственность за управление пулом. Поскольку в настоящее время вы возвращаете автоматически освобожденный объект, нужно немного потанцевать, чтобы удержать его. Я не создавал это, но оно должно работать:
// Rename this to Create... rather than Capture... It now returns a
// retained object, so needs to have the word Create in its name.
// https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFMemoryMgmt/Concepts/Ownership.html#//apple_ref/doc/uid/20001148-103029
CFDataRef CreateWindowImageData(CGWindowID windowNumber) {
// Create your pool
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
CGWindowID windowList[] = {windowNumber};
// ...
[nsImage release];
// [rep release]; <---- delete this; you don't own this object
// Hold onto your imageData
CFRetain(imageData);
// Drain the rest of the pool
[pool drain];
return imageData;
}
При этом ваш код Go должен быть правильным (за исключением исправления имени функции). Он уже правильно освобождает объект.
Core Foundation и Foundation имеют чрезвычайно согласованные соглашения об именах для обозначения управления памятью. Изучив правила, вы сразу поймете, когда вызывать релиз, а когда нет.
Для ObjC (основной фонд) найдите «выделить», «новый», «копировать» или «сохранить» и/или C (основной фундамент) найдите «Создать», «Копировать» или «Сохранить». Все это означает, что вы должны вызвать Release (или CFRelease). Если вы не вызывали что-то с этими именами, вы не должны освобождать объект.
Если вы хотите, чтобы объект жил за пределами текущего пула автоматического выпуска, вы должны сохранить его (или создать его с помощью одного из слов выше). И если вас вызывают из контекста, который не создает пул автовыпуска, вам придется его создать и опустошить.
Я не могу запустить эту программу как есть. Нужны ли для этого другие зависимости?
CGImageDestinationFinalize failed for output type 'public.tiff'