PDA

View Full Version : NSApplicationMain: What happens to parameters?




ajbrehm
Mar 31, 2007, 02:43 PM
As it should Xcode sets up the main function in main.m as follows:


int main(int argc, char *argv[])
{
return NSApplicationMain(argc, (const char **) argv);
}


Perhaps this is a stupid question but the answer is really not obvious to me. How do I get the values in argc and argv from within my classes?

argc should be 1, at least, and argv[0] should be the parth to my program. And within main in main.m that is easily verified. But how do I read them later?

Also, how do I define a method that runs when the program starts? I can press a button and make a method do something, but how can I get a method to run without having the user do anything to create an event?



Eraserhead
Mar 31, 2007, 02:48 PM
Also, how do I define a method that runs when the program starts? I can press a button and make a method do something, but how can I get a method to run without having the user do anything to create an event?

You can put it in an init or an awakeFromNib method.

ajbrehm
Mar 31, 2007, 03:02 PM
You can put it in an init or an awakeFromNib method.

Thanks. That worked.

Any idea for the argc and argv parameter problem? What happened to them when in "return NSApplicationMain(argc, (const char **) argv);"?

Eraserhead
Mar 31, 2007, 03:16 PM
Any idea for the argc and argv parameter problem? What happened to them when in "return NSApplicationMain(argc, (const char **) argv);"?


It is used in Command Line applications, which accept arguments from the command line, and it is used in C which Obj-C is based on, which I guess is why they are there. If you make a command line App in Obj-C you modify main.m so can use them...

ajbrehm
Mar 31, 2007, 04:06 PM
It is used in Command Line applications, which accept arguments from the command line, and it is used in C which Obj-C is based on, which I guess is why they are there. If you make a command line App in Obj-C you modify main.m so can use them...

Yes, but how do I use them?

The first parameter contains the path to my binary, which is useful. The second should contain the path to the file (associated with my program) I double-clicked.

Eraserhead
Mar 31, 2007, 04:46 PM
Yes, but how do I use them?

The first parameter contains the path to my binary, which is useful. The second should contain the path to the file (associated with my program) I double-clicked.

No idea, can't you get these values in a different way?

wittegijt
Mar 31, 2007, 05:11 PM
If you know which command line options you want (e.g.: appname -path pathname), the easiest way is:
NSString *path = [[NSUserDefaults standardUserDefaults] stringForKey:@"path"];

If not, use the -(NSArray *)arguments method from NSProcessInfo.

Wittegijt.

ajbrehm
Mar 31, 2007, 07:09 PM
If you know which command line options you want (e.g.: appname -path pathname), the easiest way is:
NSString *path = [[NSUserDefaults standardUserDefaults] stringForKey:@"path"];

If not, use the -(NSArray *)arguments method from NSProcessInfo.

Wittegijt.


This is weird.

The first parameter is indeed the path to my binary. But the second parameter is not the file I double-clicked on. Instead I get the following:

"-psn_0_336986113"

When I do a "myprogram file.ext" in the command line I get the following two command line parameters:

1. path to my binary
2. "file.ext"

When I double-click on file.ext in the Finder (.ext files are associated with myprogram) I get the following two command line parameters:

1. path to my binary (as expected)
2. "-psn_0_336986113" (or some other number)

The second I did not expect. How do I get the path to the file I double-clicked? How does that work in Cocoa?

ajbrehm
Mar 31, 2007, 08:32 PM
If you know which command line options you want (e.g.: appname -path pathname), the easiest way is:
NSString *path = [[NSUserDefaults standardUserDefaults] stringForKey:@"path"];

If not, use the -(NSArray *)arguments method from NSProcessInfo.

Wittegijt.

I got my program to display everything it could read in [NSUserDefaults standardUserDefaults] but the path to the file I double-clicked to open was not in it.

If the path to my file is not the second command line parameter or in the defaults, where is it?

I tried finding out in the TextEdit example source but it was too well-hidden.

This is surprisingly complicated and the documentation does not even touch the subject. Yet all Cocoa applications out there can open files double-clicked in the Finder if associated with them.

robbieduncan
Apr 1, 2007, 03:35 AM
The filename is not on the command line. When your application is running your NSApplication delegate (if you have one) will be asked to open any files double clicked on via:

- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename

Note that this can get called over and over if you have multiple files to open. If you are using the document architecture this all get's handled automatically for you. If you are writing an application that can open documents you should probably be using the Document Based Application template in XCode.

You can see the description for this in the Delegate Methods section of the NSApplication documentation.

ajbrehm
Apr 1, 2007, 09:10 AM
The filename is not on the command line. When your application is running your NSApplication delegate (if you have one) will be asked to open any files double clicked on via:

- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename


This sounds a lot more difficult than on other platforms. :-(

That means my application has to consider two different possibilities regarding the file it is supposed to open. One for having been started from a command line and one for the Finder.

I'll try the above.

Thanks.

ajbrehm
Apr 1, 2007, 09:25 AM
You can see the description for this in the Delegate Methods section of the NSApplication documentation.


The documentation keeps referring to "delegates" as if everybody is supposed to know what they are. :-(

Also, openFile does not seem to return a string.

This really is surprisingly complicated. Usually (on Windows and UNIX) the path to the file double-clicked can be found in the second command line parameter. What does my program need to be told its process number for?

Thanks for your help, but somehow I am unable to make the step from


- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename


to getting the path for my file that I expected to find in argv[1].

In fact, none of the methods in NSApplication seem to return a string.

robbieduncan
Apr 1, 2007, 09:50 AM
delegation is a standard design pattern. It's not even really a Cocoa specific thing: .Net uses then a lot too, so yes you are supposed to know what they are. You keep referring to other platforms. Cocoa is not supposed to be cross platform. It's for writing Mac applications. If you want to write something cross platform it's probably easier to write a standard POSIX (or whatever) command line app and the wrap a true Cocoa GUI round it.

Anyway back on topic! Apple provide an amazing amount of very good documentation. Have you tried reading it? For example they explain delegates here (http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/CommunicatingWithObjects/chapter_6_section_4.html). If you've not read the entire Cocoa Fundamentals guide then do that now. That's not a Aprils fool joke: it's well after midday here.

The basic idea is that you create a custom object and set that as the application delegate. In that object you implement a method with the signature I provided previously. NSApplication will then automatically call your method passing the filename as the named arguement.

ajbrehm
Apr 1, 2007, 12:17 PM
delegation is a standard design pattern. It's not even really a Cocoa specific thing: .Net uses then a lot too, so yes you are supposed to know what they are. You keep referring to other platforms. Cocoa is not supposed to be cross platform. It's for writing Mac applications. If you want to write something cross platform it's probably easier to write a standard POSIX (or whatever) command line app and the wrap a true Cocoa GUI round it.

Anyway back on topic! Apple provide an amazing amount of very good documentation. Have you tried reading it? For example they explain delegates here (http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/CommunicatingWithObjects/chapter_6_section_4.html). If you've not read the entire Cocoa Fundamentals guide then do that now. That's not a Aprils fool joke: it's well after midday here.

The basic idea is that you create a custom object and set that as the application delegate. In that object you implement a method with the signature I provided previously. NSApplication will then automatically call your method passing the filename as the named arguement.

Wow!

How did you know that I never tried reading the documentation I was referring to? And how did you so clearly deduce that when I compared Cocoa to other platforms I was obviously referring to cross-platform development?


I have to thank the other two guys for helping me with the command line problem. It's good to see that people often explain something in simple terms rather than demonstrate how much more they know about the issue as such.

robbieduncan
Apr 1, 2007, 01:07 PM
I didn't say you hadn't read it. I asked if you had. You may have considered it to be a rhetorical question but it was not intended as one.

I simply thought that as you were asking a basic question about a core Cocoa concept that you might find some advantage in taking some time to read or re-read the basic core Cocoa conceptual documentation that is essential to understanding Cocoa.

ajbrehm
Apr 1, 2007, 01:17 PM
I didn't say you hadn't read it. I asked if you had. You may have considered it to be a rhetorical question but it was not intended as one.

I simply thought that as you were asking a basic question about a core Cocoa concept that you might find some advantage in taking some time to read or re-read the basic core Cocoa conceptual documentation that is essential to understanding Cocoa.

Ok. No harm, no foul; I am sure.

But seriously, I don't want to open the file, I only want to find out the path to it. Surely there must be an easier method like on other platforms?

Also, I'm not sure I have asked a question about a core Cocoa concept. The concept I am looking for is fairly universal and it works the same way on Windows and UNIX. I don't think it can be so much more complicated on a Mac, no?

robbieduncan
Apr 1, 2007, 01:23 PM
It would be the same if you wrote a pure C command line app. Once you decide to use Cocoa you are deciding to use that Framework and all that brings. You don't have to open the file. Just return NO from the delegate method.

ajbrehm
Apr 1, 2007, 02:36 PM
It would be the same if you wrote a pure C command line app. Once you decide to use Cocoa you are deciding to use that Framework and all that brings. You don't have to open the file. Just return NO from the delegate method.

So there really is no simple way to find out the path to the file double-clicked?

robbieduncan
Apr 1, 2007, 03:05 PM
I really don't see what is so difficult about doing it via the delegate. See the absolutely minimal example attached. This is a non-document based app that "opens" files *.test. If you drop any .test file on it's Dock icon or (once you've run the app once) double click in any .test file it'll tell you the path.

Edit: forgot to attach file :o

ajbrehm
Apr 1, 2007, 03:50 PM
I really don't see what is so difficult about doing it via the delegate. See the absolutely minimal example attached. This is a non-document based app that "opens" files *.test. If you drop any .test file on it's Dock icon or (once you've run the app once) double click in any .test file it'll tell you the path.

Thanks!

Weirdly enough it works on its own. But when I add the method to my program with the only change of the name of the text field, it doesn't do anything.

Since other methods can update the text field in question I assume the text field works. But


- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename


seems never to be called.

robbieduncan
Apr 1, 2007, 03:58 PM
Did you check out the nib file? You need to ensure that there is an instance of the new class created somewhere (I did it in the nib) and that the instance is set as the NSApplication delegate (again I did it in the nib)...

ajbrehm
Apr 1, 2007, 04:14 PM
Did you check out the nib file? You need to ensure that there is an instance of the new class created somewhere (I did it in the nib) and that the instance is set as the NSApplication delegate (again I did it in the nib)...

In your nib file I find an instance of the class AppDelegate. In my nib file is an instance of my class.

Does the class have to be called "AppDelegate"? I tried that, created the class, the files, and the instance in Interface Builder, but it still didn't do anything.

robbieduncan
Apr 1, 2007, 04:18 PM
The name is irrelevant. It obviously has to be an instance of the correct class (but the name is just a name). It then has to be connected as the NSApplication delegate. The FileOwner icon is the NSApplication (in this case, this is not always true). Do the normal to connect it as a delegate (ctrl-drag from FileOwner to the instance and choose delegate).

Edit it add: in case the start of that is not clear: the class can be called anything you like. The instance can also be renamed in Interface Builder without altering the class (in case you have two instances of the same class and you want to be able to tell them apart for instance).

ajbrehm
Apr 1, 2007, 04:21 PM
The name is irrelevant. It obviously has to be an instance of the correct class (but the name is just a name). It then has to be connected as the NSApplication delegate. The FileOwner icon is the NSApplication (in this case, this is not always true). Do the normal to connect it as a delegate (ctrl-drag from FileOwner to the instance and choose delegate).

Edit it add: in case the start of that is not clear: the class can be called anything you like. The instance can also be renamed in Interface Builder without altering the class (in case you have two instances of the same class and you want to be able to tell them apart for instance).

That worked! Thanks.

It's not obvious.

robbieduncan
Apr 1, 2007, 04:25 PM
Well you don't have to do it that way. You could have your instance set itself as the application delegate programatically when it wakes from the bib. Or do you mean the way you do the connection? As that's covered in the tutorials. You eventually think it's second nature!

ajbrehm
Apr 1, 2007, 04:41 PM
Well you don't have to do it that way. You could have your instance set itself as the application delegate programatically when it wakes from the bib. Or do you mean the way you do the connection? As that's covered in the tutorials. You eventually think it's second nature!

I might not use Cocoa often enough to develop a second nature for that. :-) I usually use Visual Studio.

What I am trying to do on the Mac is write a wrapper for .NET applications (using Mono). I did something similar for Darwin applications a few years ago. I was completely surprised by how Cocoa wanted me to handle files I double-click on.

ajbrehm
Apr 1, 2007, 05:24 PM
Well you don't have to do it that way. You could have your instance set itself as the application delegate programatically when it wakes from the bib. Or do you mean the way you do the connection? As that's covered in the tutorials. You eventually think it's second nature!

It works now, but only for the text field. When I try to assign the string filename to another NSString in the program, the program crashes.

Specifically, what works is:


[someTextField setStringValue:filename];
NSString *anotherString = filename;
someGlobalString = @"Hello, world";


And what doesn't work is:


someGlobalString = filename;


With "someGlobalString" being defined for the entire class.

Any ideas?

ajbrehm
Apr 1, 2007, 05:33 PM
I just noticed that I can write the string filename into a text field and then read it from there into a class variable.

I.e. this doesn't work:


someGlobalString = filename;


But this does:


[someTextField setStringValue:filename];
someGlobalString = [sometextField stringValue];

Nutter
Apr 1, 2007, 06:21 PM
This is most likely a memory management issue. The filename variable was autoreleased before it was passed to you, which means that it will automatically be deallocated at some point in the (near) future.

As you assign the filename to your global variable you should retain it, to indicate that you don't want it to be deallocated. Like so:

someGlobalString = [filename retain];

Having done this, you are now responsible for releasing the object when you have finished with it. In other words, if you want to change the string that your global variable points to you should first release this string. I've edited Robbie's project and am attaching a new version to show what I mean.

Note that unless you have a really good reason to use a global variable it's better to add an instance variable to your object and write an accessor method to access the path from other parts of your app.

Eraserhead
Apr 2, 2007, 06:05 AM
Rather than setting the pointer to the old string, it might be better to use newString=[NSString stringWithString:filename];

Remember with Cocoa some stuff will be worse than VS and some stuff better, and some stuff just different, you'll have to just get used to it ;).

robbieduncan
Apr 2, 2007, 06:14 AM
As that is not an alloc/init or copy call I would expect that to return an autoreleased string too. This will result in the same errors as before. Personally I have no issue with retaining the original string, but it you want to create a new string copy of it you'll have to retain it (or use it in the this run loop only)...

Nutter
Apr 2, 2007, 06:51 AM
... or use [filename copy], which is a more obvious way of creating a copy and gives you ownership of the object.

However, NSString (like other immutable Cocoa objects) implements -copyWithZone: to just call -retain, because a copy of an immutable object can never be any different from the original.

That's why I just retained the original string in my code, but yes, from a semantic point of view someGlobalString = [filename copy]; is more correct.