Search code examples
multithreadingcocoauser-interfacemacosobjective-c++

Can't Find Cocoa GUI Thread in C++ Application w/ Objective-C++ GUI


I have a multi-posix-threaded Linux C++ application without a GUI that I want to be able to use the occasional Cocoa control in, namely the file upload/download dialogs and the alert.

I'm far from an expert with Cocoa, but was able to build a few Objective-C++ test/demo apps that worked as intended.

Now that I've integrated the Cocoa code into my application, it seems that I'm having trouble posting things to the main GUI thread. Maybe I didn't do what I needed to do to create one, I'm really not sure. Here's what I have in my .mm file:

#ifdef MACOS
@interface CocoaInterface : NSObject
{
}
- (id) init;
- (void) ShowFileUploadDialog;
- (void) ShowFileDownloadDialog;
@end

@implementation CocoaInterface
- (id) init
{
    cout << "Creating NSAutoreleasePool" << endl;
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    cout << "Creating NSApplication" << endl;
    NSApplication* app = [[NSApplication alloc] init];
    cout << "Calling NSApplication::finishLaunching" << endl;
    [app finishLaunching];
    [super init];
    return self;
}

- (void) ShowFileUploadDialog
{
    cout << "Entering ShowFileUploadDialog" << endl;
    if ([NSThread isMainThread])
    {
        // Show file dialog
        cout << "Calling NSRunAlertPanel" << endl;
        NSRunAlertPanel(@"This is a test", @"Does it work?", @"Yes", @"No", @"");
    }
    else 
    {
        //NSRunAlertPanel(@"This is a test", @"Does it work?", @"Yes", @"No", @"");
        cout << "Redirecting ShowFileUploadDialog call to main thread." << endl;
        [self performSelectorOnMainThread:@selector(ShowFileUploadDialog) withObject:nil waitUntilDone:YES];
    }
}

- (void) ShowFileDownloadDialog
{
    cout << "Entering ShowFileDownloadDialog" << endl;
    if ([NSThread isMainThread])
    {
        // Show file dialog
        cout << "Calling NSRunAlertPanel" << endl;
        NSRunAlertPanel(@"This is a test", @"Does it work?", @"Yes", @"No", @"");
    }
    else
    {
        //NSRunAlertPanel(@"This is a test", @"Does it work?", @"Yes", @"No", @"");
        cout << "Redirecting ShowFileDownloadDialog call to main thread." << endl;
        [self performSelectorOnMainThread:@selector(ShowFileDownloadDialog) withObject:nil waitUntilDone:YES];
    }
}
@end 
#endif

I call this from the code I have in the various threads I have processing incoming network messages:

cout << "Creating CocoaInterface." << endl;
CocoaInterface* interface = [[CocoaInterface alloc] init];
cout << "Calling CocoaInterface::ShowFileDownloadDialog." << endl;
[interface ShowFileDownloadDialog];

This hangs on trying to perform the selector -- as if it can never actually find the main thread. A backtrace in GDB shows me waiting on a semaphore forever.

When I uncomment the NSRunAlertPanel call before the performSelectorOnMainThread call, I get a white block in the shape of the dialog, but it doesn't fully draw or process any messages, presumably because it's not on the main GUI thread.

It seems that I don't have a proper GUI thread or just can't get to it from where I am. I suspect that I've missed something in initialization. Any suggestions?


Solution

  • It turns out what I needed was a combination of these two things:

    [NSApplication sharedApplication];
    [NSApp run];
    

    I've been spoiled by other GUI APIs such as GTK and wxWidgets so that calling

    [NSApp terminate: nil];
    

    has caught me off guard by unexpectedly destroying the entire application via an exit(0) call and the rest of the main() never executes, but that's apparently the intended behavior and a topic for another day.