कैसे रोकें () मुख्य धागा NSApp रन () इवेंट लूप? (सी ++ में एम्बेडेड)

Dec 21 2020

यहां एक न्यूनतम प्रजनन उदाहरण है, यदि पढ़ने के लिए बहुत लंबा है, तो समस्या के साथ अगले अनुभाग पर जाएं, और यदि आवश्यक हो तो कोड का पता लगाएं।

न्यूनतम उदाहरण:

मान लीजिए कि एक साधारण C ++ कमांड लाइन है:

main.cpp

#include <iostream>
#include "Wrapper.h"
int main()
{
    Wrapper wrapper;
    wrapper.run();
    std::cout << "Exiting" << std::endl;
}

ऑब्जेक्टिव-सी रैपर हेडर: Wrapper.h

struct OCWrapper;
class Wrapper
{
public:
    Wrapper() noexcept;
    virtual ~Wrapper() noexcept;
    void run();
private:
    OCWrapper* impl=nullptr;
};

और यह कार्यान्वयन: Wrapper.mm

#import "Wrapper.h"
#import "MyOCApp.h"

struct OCWrapper
{
    MyOCApp* wrapped=nullptr;
};

Wrapper::Wrapper() noexcept: impl(new OCWrapper)
{
    impl->wrapped = [[ MyOCApp alloc] init];
}

Wrapper::~Wrapper() noexcept
{
    [impl->wrapped release];
    delete impl;
}

void Wrapper::run()
{
    [impl->wrapped run];
}

और अंत में दिलचस्प हिस्सा, Objective-C, MyOCApp.h में:

#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>

@interface MyOCApp: NSObject
@end

@implementation MyOCApp
- (id)init 
{
    [[NSNotificationCenter defaultCenter] addObserver:self
                selector:@selector(applicationDidFinishLaunching:)
                name:NSApplicationDidFinishLaunchingNotification object:nil];
    
    return self;
}

- (void)run
{
    [self performSelector:@selector(shutdown:) withObject:nil afterDelay: 2];
    //CFRunLoopRun();
    
    [NSApplication sharedApplication];
    [NSApp run];
}

- (void) shutdown:(NSNotification *) notif
{
    NSLog(@"Stopping");
    //CFRunLoopStop(CFRunLoopGetCurrent());
    [NSApp stop:self];
}

- (void) applicationDidFinishLaunching:(NSNotification *) notif
{
    NSLog(@"Application ready");
}
@end

CMakeLists.txt

cmake_minimum_required (VERSION 3.10.0)
cmake_policy( SET CMP0076 NEW)

set(CMAKE_CXX_STANDARD 17)

project(ocapp)

add_executable(${PROJECT_NAME}) find_library(APP_KIT AppKit) find_library(CORE_FOUNDATION CoreFoundation) target_link_libraries( ${PROJECT_NAME} ${APP_KIT} ${CORE_FOUNDATION} )

target_sources( ${PROJECT_NAME} PRIVATE "main.cpp" "Wrapper.mm" PUBLIC "Wrapper.h" "MyOCApp.h" )

परियोजना निम्नलिखित आदेशों के साथ बनाई जा सकती है:

$ cmake -G Xcode।
$ ओपन ओप्पा। xcodeproj

समस्या:

उपयोग करते समय [NSApp run]और [NSApp stop:self], मैं इवेंट लूप को रोकने में असमर्थ हूं, इसलिए यह अनिश्चित काल तक चलता रहता है।

आवेदन समाप्त करना
बंद करना शुरू किया
.....
मार डाला: 9

उपयोग करते समय CFRunLoopRun()और CFRunLoopStop(CFRunLoopGetCurrent()), यह सही ढंग से शुरू / बंद होता है, लेकिन applicationDidFinishLaunchingकभी ट्रिगर नहीं होता है।

रोकना
समाप्त करना

प्रश्न:

ऐसा क्यों है? और दोनों फीचर कैसे काम करते हैं?

जवाब

2 Kamil.S Jan 06 2021 at 03:56

समस्या संलग्न कोड में नहीं है। आपका मौजूदा संस्करण

[NSApplication sharedApplication];
[NSApp run];

तथा

[NSApp stop:self];

सही है।

अपराधी आपका है CMakeLists.txt। आपके द्वारा शामिल एक निष्पादन योग्य बाइनरी बनाता है। यह कंसोल ऐप के लिए ठीक है, लेकिन यह AppName.app फ़ोल्डर और अन्य फ़ाइलों के गुच्छा से युक्त एक मान्य MacOS ऐप नहीं है। चूंकि आप एक MacOS ऐप के उचित मचान के बिना AppKit एपीआई का उपयोग कर रहे हैं, यह काम नहीं करता है।

एक न्यूनतम ठीक अपने में CMakeLists.txtहै:

add_executable(
    ${PROJECT_NAME}
    MACOSX_BUNDLE
)

अब आपके पास Xcode में एक सही App का लक्ष्य होगा। आप CMakeLists.txtइंटरनेट पर MacOS ऐप्स के लिए उपयुक्त के अधिक उन्नत उदाहरण देख सकते हैं ।

अपडेट
तो मैंने इसकी और जांच की और बाहर निकलने की दिनचर्या का निरीक्षण किया
-[NSApplication run]( +[NSApp run]यह संगतता के लिए एक पर्यायवाची शब्द है लेकिन वास्तविक कार्यान्वयन है -[NSApplication run])। हम इस तरह lldb के माध्यम से एक प्रतीकात्मक ब्रेकपॉइंट सेट कर सकते हैं: b "-[NSApplication run]"ब्याज का स्निपेट (X86-64 के लिए) है:

->  0x7fff4f5f96ff <+1074>: add    rsp, 0x98
    0x7fff4f5f9706 <+1081>: pop    rbx
    0x7fff4f5f9707 <+1082>: pop    r12
    0x7fff4f5f9709 <+1084>: pop    r13
    0x7fff4f5f970b <+1086>: pop    r14
    0x7fff4f5f970d <+1088>: pop    r15
    0x7fff4f5f970f <+1090>: pop    rbp
    0x7fff4f5f9710 <+1091>: ret  

हम सत्यापित कर सकते हैं कि एक ब्रेकपॉइंट जहां तीर बिंदु केवल बंडल किए गए संस्करण में मारा जाता है, लेकिन "नग्न" निष्पादन योग्य संस्करण में नहीं। आगे के शोध के बाद मुझे यह उत्तर मिलाhttps://stackoverflow.com/a/48064763/5329717जो बहुत मददगार है। @Remko द्वारा प्रमुख उद्धरण:

ऐसा लगता है कि UI लूप स्टॉप अनुरोध केवल एक UI ईवेंट के बाद संसाधित किया जाता है (इसलिए मुख्य लूप इवेंट के बाद ही नहीं)।

और वास्तव में ऐसा ही है। यदि "नग्न" निष्पादन योग्य संस्करण में हम जोड़ते हैं

- (void) shutdown:(NSNotification *) notif
{
    NSLog(@"Stopping");
    //CFRunLoopStop(CFRunLoopGetCurrent());
    [NSApp stop:self];
    [NSApp abortModal]; //this is used for generating a UI NSEvent
}

हमें वांछित व्यवहार मिलता है और ऐप सामान्य रूप से समाप्त हो जाता है। तो आपका "नग्न" ऐप वैरिएंट एक सही MacOS ऐप नहीं है, इसलिए यह UI इवेंट्स को प्राप्त नहीं करता है (इसके रनओल सही तरीके से काम करता है)।

दूसरी ओर मैकओएस ऐप बंडल का उचित Info.plistहोना आदि मैकओएस के लिए ऐप विंडो, डॉक आइकन आदि को सेटअप करने के लिए आवश्यक है।

लंबे समय में मैं आपको या तो शुद्ध कंसोल ऐप के लिए जाने की सलाह देता हूं, अगर आपको ऐपकिट की ज़रूरत नहीं है या किताब द्वारा चीजें नहीं करनी हैं। अन्यथा तुम ऐसी विसंगतियों में दौड़ोगे।