Быстрый рост памяти, вызванный вызовом Go Objective-C

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.

Пожалуйста, помогите диагностировать проблему и подскажите, как исправить код.

Я не могу запустить эту программу как есть. Нужны ли для этого другие зависимости? CGImageDestinationFinalize failed for output type 'public.tiff'

coxley 16.03.2024 18:59
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
1
88
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Во-первых, здесь есть небольшая ошибка: исправление утечки памяти приведет к сбоям, поэтому, чтобы ее исправить, сначала удалите вызов [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). Если вы не вызывали что-то с этими именами, вы не должны освобождать объект.

Если вы хотите, чтобы объект жил за пределами текущего пула автоматического выпуска, вы должны сохранить его (или создать его с помощью одного из слов выше). И если вас вызывают из контекста, который не создает пул автовыпуска, вам придется его создать и опустошить.

Другие вопросы по теме

Как при импорте заголовка в быстрый файл определить условные макросы для разных целей?
Xcode 15: приложение выходит из строя при настройке пользовательской панели навигации для контроллера навигации
Используйте NSPredicate со Swift, но не можете получить тот же результат с кодом, написанным Objective-C
Xcode выдает ошибку компиляции, даже если использовать #available для более старой версии Xcode
Анализатор Xcode показывает утечку для CGPathRef, когда он находится в методе экземпляра класса?
Как я могу хранить объекты Swift/Objective-C с автоматическим подсчетом ссылок на карте C++, не вызывая утечек памяти при их удалении с карты?
Проблема Cocoapods (AFNetworking) при архивировании проекта
Лучший способ создания экземпляра класса, следующего протоколу
Записать текст Unicode в файл в режиме «wb», используя C и Objective C?
Получение ошибок компиляции при попытке добавить сканер QR-кода в проект Qt