Memory corruption after using NSOpenPanel - macos

I got a problem that leads to my program reporting malloc corruption when I use NSOpenPanel. My code is mainly C (not using Xcode) and what I do is something like this:
main(..)
{
[NSApplication sharedApplication];
... create window etc, no problem
[NSApp run];
}
Later I call something like this
openFileDialog(..)
{
// tried to create NSAutoreleasePool and things here bit still breaks
NSOpenPanel* open = [NSOpenPanel openPanel];
int res = [open runModal]
...
}
After exiting the function (or a bit later) I will get
test (1948,0x7fff7d852960) malloc: * error for object 0x7ff19b879608:
incorrect checksum for freed object - object was probably modified after being freed.
* set a breakpoint in malloc_error_break to debug
Ideas?

Related

`NSStatusItem` created from `main()` doesn't show up on system status bar

I'm trying to make a simple macOS Cocoa application using NSStatusItem to create a clickable icon on the system status bar. However, when I launch my application, I get this warning and the icon doesn't show up:
2020-03-03 14:43:11.564 Mocha_bug_example[936:39572] CGSGetActiveMenuBarDrawingStyle((CGSConnectionID)[NSApp contextID], &sCachedMenuBarDrawingStyle) returned error 268435459 on line 46 in NSStatusBarMenuBarDrawingStyle _NSStatusBarGetCachedMenuBarDrawingStyle(void)
Here's a minimal reproducible example for my application:
#import <AppKit/AppKit.h>
NSStatusItem* statusItem;
int main (int argc, char* argv[]) {
statusItem = [NSStatusBar.systemStatusBar statusItemWithLength: -1];
statusItem.button.title = #"foobar";
statusItem.visible = YES;
[NSApplication.sharedApplication run];
return 0;
}
I compiled and ran the example like this:
MacBook-Air-5:Mocha ericreed$ clang -o Mocha_bug_example -framework AppKit -fobjc-arc Mocha_bug_example.m
MacBook-Air-5:Mocha ericreed$ ./Mocha_bug_example
2020-03-03 14:43:11.564 Mocha_bug_example[936:39572] CGSGetActiveMenuBarDrawingStyle((CGSConnectionID)[NSApp contextID], &sCachedMenuBarDrawingStyle) returned error 268435459 on line 46 in NSStatusBarMenuBarDrawingStyle _NSStatusBarGetCachedMenuBarDrawingStyle(void)
[Application hung until I pressed Ctrl+C]
^C
MacBook-Air-5:Mocha ericreed$
Note: disabling automatic reference counting and adding [statusItem release]; after calling run as this similar question suggested made no visible difference.
This is how to add status bar item to command line app mac osx cocoa
Adapting apodidae's answer to Swift. Just put this in the main.swift file:
let app = NSApplication()
let statusItem = NSStatusBar.system.statusItem(withLength: -1)
statusItem.button!.title = "Hello, world!"
app.run()
I don't understand the finer details of the NSReleasePool as apodidae included, but it works for me without that.
#import <Cocoa/Cocoa.h>
int main(){
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
NSApplication *application = [NSApplication sharedApplication];
NSStatusItem* statusItem;
statusItem = [NSStatusBar.systemStatusBar statusItemWithLength: -1];
statusItem.button.title = #"foobar";
statusItem.visible = YES;
[application run];
[pool drain];
return 0;
}
Save file with the name 'statusBar_SO.m'
Compile from Terminal:
clang statusBar_SO.m -framework Cocoa -o statusBar && ./statusBar
This is not the kind of thing you can do in main().
Except for extrememly unusual situations, you should never modify the main() that comes with the application template, and it must call NSApplicationMain():
int main(int argc, char *argv[])
{
// start the application
return NSApplicationMain(argc, (const char **) argv);
}
The Cocoa framework doesn't get initialized until you call NSApplicationMain() and is generally unusable until then.
This kind of setup should be done in applicationWillFinishLaunching or applicationDidFinishLaunching.
Update
The original poster is not using Xcode and is willing to brave the wilderness alone. ;)
This also implies that their application bundle will not have a main NIB file that would normally create and connect the application delegate object, main menu, and so forth.
There are intrepid individuals who have braved this territory and you can read about it in Creating a Cocoa application without NIB files.

OS X main.m running a server style program

I writing my first OS X command line program which is a server style program. It's job is to process various information and respond to other events.
I have the following code in my main.m
int main(int argc, const char * argv[]) {
#autoreleasepool {
PIPieman *pieman = [[[PIPieman alloc] init] autorelease];
[pieman start];
NSRunLoop *loop = [NSRunLoop currentRunLoop];
while (!pieman.finished && [loop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
}
return 0;
}
I got this code from various documents and the basic idea is that once pieman.finished is set to YES, that the program then exits.
The problem I have is that the flag is being set by code inside pieman, but the run loop is not being triggered, so the program does not exit. I've been looking for ways to trigger the run loop and there seem to be a variety, but none that feel like a good solution. For example I could reduce the beforeDate: to a few seconds to cause periodic triggering of the run loop.
My preference would be that something triggers the run loop on the change of the finished boolean value.
Any suggestions?
You need to tell the run loop to return from runMode:beforeDate:. The NSRunLoop class doesn't define a message to do that, but NSRunLoop is built on CFRunLoop, and the CFRunLoopStop function does what you need.
#implementation PIPieman
...
- (void)setFinished:(BOOL)finished {
_finished = finished;
CFRunLoopStop(CFRunLoopGetMain());
}

stringByReplacingOccurenceOfString fails

I'm trying to make a Mac OS X application that asks the user for a directory. I'm using an NSOpenPanel that gets triggered when the user presses a "Browse" button.
The problem is, [NSOpenPanel filenames] was deprecated, so now I'm using the URLs function. I want to parse out the stuff that's url related to just get a normal file path. So I tried fileName = [fileName stringByReplacingOccurrencesOfString:#"%%20" withString:#" "];, but that gave me an error:
-[NSURL stringByReplacingOccurrencesOfString:withString:]: unrecognized selector sent to instance 0x100521fa0
Here's the entire method:
- (void) browse:(id)sender
{
int i; // Loop counter.
// Create the File Open Dialog class.
NSOpenPanel* openDlg = [NSOpenPanel openPanel];
// Enable the selection of files in the dialog.
[openDlg setCanChooseFiles:NO];
// Enable the selection of directories in the dialog.
[openDlg setCanChooseDirectories:YES];
// Display the dialog. If the OK button was pressed,
// process the files.
if ( [openDlg runModal] == NSOKButton )
{
// Get an array containing the full filenames of all
// files and directories selected.
NSArray* files = [openDlg URLs];
// Loop through all the files and process them.
for( i = 0; i < [files count]; i++ )
{
NSString* fileName = (NSString*)[files objectAtIndex:i];
NSLog(#"%#", fileName);
// Do something with the filename.
fileName = [fileName stringByReplacingOccurrencesOfString:#"%%20" withString:#" "];
NSLog(#"%#", fileName);
NSLog(#"Foo");
[oldJarLocation setStringValue:fileName];
[self preparePopUpButton];
}
}
}
Interestingly enough, "Foo" never gets outputted to that console. It's like the method aborts at the stringByReplacigOccurencesOfString line.
If I remove that one line, the app will run and fill my text box with the string, just in URL form, which I don't want.
Your problem is that the NSArray returned by [NSOpenPanel URLs] contains NSURL objects, not NSString objects. You're doing the following cast:
NSString* fileName = (NSString*)[files objectAtIndex:i];
Since NSArray returns an id, there isn't any compile-time checking to make sure your cast makes sense, but you do get a runtime error when you try to send an NSString selector to what is actually an NSURL.
You could convert the NSURL objects to NSString and use your code mostly as-is, but there's no need for you to handle the URL decoding yourself. NSURL already has a method for retrieving the path portion which also undoes percent-encoding: path.
NSString *filePath = [yourUrl path];
Even if your code was dealing with just a percent-encoded NSString, theres stringByReplacingPercentEscapesUsingEncoding:.

Sheets and long running tasks

I need to run a complex (ie long) task after the user clicks on a button.
The button opens a sheet and the long running operation is started using dispatch_async and other Grand Central Dispatch stuff.
I've written the code and it works fine but I need help to understand if I've done everything correctly or if I've ignored (due to my ignorance) any potential problem.
The user clicks the button and opens sheet, the block contains the long task (in this example it only runs a for(;;) loop
The block contains also the logic to close the sheet when task completes.
-(IBAction)openPanel:(id)sender {
[NSApp beginSheet:panel
modalForWindow:[self window]
modalDelegate:nil
didEndSelector:NULL
contextInfo:nil];
void (^progressBlock)(void);
progressBlock = ^{
running = YES; // this is a instance variable
for (int i = 0; running && i < 1000000; i++) {
[label setStringValue:[NSString stringWithFormat:#"Step %d", i]];
[label setNeedsDisplay: YES];
}
running = NO;
[NSApp endSheet:panel];
[panel orderOut:sender];
};
//Finally, run the block on a different thread.
dispatch_queue_t queue = dispatch_get_global_queue(0,0);
dispatch_async(queue,progressBlock);
}
The panel contains a Stop button that allows user to stop the task before its completion
-(IBAction)closePanel:(id)sender {
running = NO;
[NSApp endSheet:panel];
[panel orderOut:sender];
}
This code has a potential problem where it sets value of the status text. Basically all objects in AppKit are only allowed to be called from the main thread and can break in weird ways if they're not. You're calling the setStringValue: and setNeedsDisplay: methods on the label from whatever thread the global queue is running on. To fix this you should write the loop like so:
for (int i = 0; running && i < 1000000; i++) {
dispatch_async(dispatch_get_main_queue(), ^{
[label setStringValue:[NSString stringWithFormat:#"Step %d", i]];
[label setNeedsDisplay: YES];
});
}
This will set the label text from the main thread as AppKit expects.

Getting URL From beginSheetModalForWindow:

I'm using an OpenPanel to get a file path URL. This works:
[oPanel beginSheetModalForWindow:theWindow completionHandler:^(NSInteger returnCode)
{
NSURL *pathToFile = nil;
if (returnCode == NSOKButton)
pathToFile = [[oPanel URLs] objectAtIndex:0];
}];
This doesn't, resulting in an 'assignment of read-only variable' error:
NSURL *pathToFile = nil;
[oPanel beginSheetModalForWindow:theWindow completionHandler:^(NSInteger returnCode)
{
if (returnCode == NSOKButton)
pathToFile = [[oPanel URLs] objectAtIndex:0];
}];
return pathToFile;
In general, any attempt to extract pathToFile from the context of oPanel has failed. This isn't such a big deal for small situations, but as my code grows, I'm forced to stuff everything -- XML parsing, core data, etc -- inside an inappropriate region. What can I do to extract pathToFile?
Thanks.
This doesn't, resulting in an 'assignment of read-only variable' error:
NSURL *pathToFile = nil;
[oPanel beginSheetModalForWindow:theWindow completionHandler:^(NSInteger returnCode)
{
if (returnCode == NSOKButton)
pathToFile = [[oPanel URLs] objectAtIndex:0];
}];
return pathToFile;
Yes, because you're trying to assign to the copy of the pathToFile variable that gets made when the block is created. You're not assigning to the original pathToFile variable that you declared outside the block.
You could use the __block keyword to let the block assign to this variable, but I don't think this will help because beginSheetModalForWindow:completionHandler: doesn't block. (The documentation doesn't mention this, but there's no reason for the method to block, and you can verify with logging that it doesn't.) The message returns immediately, while the panel is still running.
So, you're trying to have your completion-handler block assign to a local variable, but your method in which you declared the local variable will probably have returned by the time block runs, so it won't be able to work with the value that the block left will leave in the variable.
Whatever you do with pathToFile should be either in the block itself, or in a method (taking an NSURL * argument) that the block can call.
you can also runModal after you begin the sheet you just need to make sure you end the sheet later. This way you don't have to bend to apple's will, it isn't deprecated and it should still work perfectly.
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
[openPanel beginSheetModalForWindow:window completionHandler:nil];
NSInteger result = [openPanel runModal];
NSURL *url = nil;
if (result == NSFileHandlingPanelOKButton)
{
url = [openPanel URL];
}
[NSApp endSheet:openPanel];
It seems a little bit like black magic coding but it does work.

Resources