Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-devel / 2008-01

Creating a Cocoa Application

7 messages · 2 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Felipe Monteiro de Carvalho

Hello,

I'm again, trying to do some slow progress on the task of writting a cocoa application in pascal. We have objc runtime headers translated, but there are no examples explaining how to use them. In the past I posted to Apple mailling lists without sucess, so maybe someone could help me "reverse engineer" a simple example and go from there.

Attached one can find a trivial objective-c application (main.m) and it's corresponding assembler code (main.asm).

It should be noted that any code written in objective-c should be possible to describe in a procedural way using calls to the objective-c runtime library.

So, here is how the app starts:

#import <Cocoa/Cocoa.h>

int main(int argc, char *argv[])
{

    // Any ideas what is this? It will first call NSAutoreleasePool

alloc method and then the init method??

    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    // The next 2 should be simple procedures AFAIK, no not much
problem with them.
    NSApplicationLoad();
	/* uses a default "OK" button and no alternate buttons */
    NSRunAlertPanel(@"This is the title", @"This is the message", nil,
nil, nil);
    // Call release method from object pool
    [pool release];
    return 0;
}

I read the corresponding assembler code, but it's *very* confusing.

The order in which CALL's are made is:

        // NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
	call	L_objc_getClass$stub
	call	L_objc_msgSend$stub
	call	L_objc_msgSend$stub
        // The 2 simple procedures afaik
	call	L_NSApplicationLoad$stub
	call	L_NSRunAlertPanel$stub
        // [pool release];
	call	L_objc_msgSend$stub

So, getting which procedures are called where isn't that hard, but I couldn't understand what is filled on the parameters. Any ideas?

By the way, what does the instruction "leal" do?? I don't remember anything similar from intel intructions tables:
leal L_OBJC_SELECTOR_REFERENCES_0-"L00000000001$pb"(%ebx), %eax

thanks,

Felipe Monteiro de Carvalho

Reply by email

Jonas Maebe
to Felipe Monteiro de Carvalho
Felipe Monteiro de Carvalho wrote 18 lines
I read the corresponding assembler code, but it's *very* confusing. The order in which CALL's are made is: // NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; call L_objc_getClass$stub call L_objc_msgSend$stub call L_objc_msgSend$stub // The 2 simple procedures afaik call L_NSApplicationLoad$stub call L_NSRunAlertPanel$stub // [pool release]; call L_objc_msgSend$stub So, getting which procedures are called where isn't that hard, but I couldn't understand what is filled on the parameters. Any ideas?

As the API explains, objc_getClass has one parameter: a char* pointing to the class name. In this case, the class name is "NSAutoreleasePool", and the first parameter is indeed simply a pointer to that (null terminated) string.

objc_msgSend wants as first parameter the target (in this case: the result of objc_getClass), then the selector for the method to call (a selector is simply a char** pointing to the method name here, but for forward compatibility it is safer to call
sel_registerName(fullyQualifiedMethodName) and use its result; optimizations can always be done later)

etc.

By the way, what does the instruction "leal" do?? I don't remember anything similar from intel intructions tables: leal L_OBJC_SELECTOR_REFERENCES_0-"L00000000001$pb"(%ebx), %eax

lea means "load effective address". The "l" suffix in AT&T assembler syntax means "long", which always corresponds to "32 bit value".

The construct above is simple PIC. Just search for the symbol before the "-" to find out what it references.

Jonas

Reply by email

Felipe Monteiro de Carvalho
to Jonas Maebe

thanks, ok, I think I'm almost getting it to work.

I translated enougth headers to call NSApplicationLoad and NSRunAlertPanel, but I got stuck at the @"string" syntax. I tryed to just define PNSString as a generic pointer and pass a PChar to it, but it doesn't work, the application crashes with messages that the autorelease pool isn't installed:

2008-01-06 19:01:46.396 cocoamsgbox[1752] *** _NSAutoreleaseNoPool(): Object 0x318310 of class NSCFString autoreleased with no pool in place
- just leaking

Here is my current code:

program cocoamsgbox;
{$mode objfpc}{$H+}
uses
  objc, appkit;
const
  Str_NSAutoreleasePool = 'NSAutoreleasePool';
  Str_alloc = 'alloc';
  Str_init = 'init';
  Str_release = 'release';
var
  { classes }
  NSAutoreleasePoolId: id;
  { objects }
  pool: id;
begin
//  NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  NSAutoreleasePoolId := objc_getClass(PChar(Str_NSAutoreleasePool));
  pool := objc_msgSend(NSAutoreleasePoolId,
sel_registerName(PChar(Str_alloc)), []);
  objc_msgSend(NSAutoreleasePoolId, sel_registerName(PChar(Str_init)), []);
  NSApplicationLoad();
  { uses a default "OK" button and no alternate buttons }
//    NSRunAlertPanel(@"This is the title", @"This is the message",
nil, nil, nil);
  NSRunAlertPanel(PChar('This is the title'), PChar('This is the
message'), nil, nil, nil, []);
//  [pool release];
  pool := objc_msgSend(NSAutoreleasePoolId,
sel_registerName(PChar(Str_release)), []);
end.

thanks,

Felipe Monteiro de Carvalho

Reply by email

Jonas Maebe
to Felipe Monteiro de Carvalho
Felipe Monteiro de Carvalho wrote 5 lines
I translated enougth headers to call NSApplicationLoad and NSRunAlertPanel, but I got stuck at the @"string" syntax. I tryed to just define PNSString as a generic pointer and pass a PChar to it, but it doesn't work, the application crashes with messages that the autorelease pool isn't installed:

@"string" is indeed a shorthand for a constant NSString. Compiler magic is required to construct these properly (similar to how you can't just fake a constant ansistring by using a pchar on a compiler which doesn't support ansistrings). E.g. @"This is the title" is translated into this:

.cstring
LC3:
.ascii "This is the title\0"
.section __DATA, __cfstring
.align 2
LC4:
.long ___CFConstantStringClassReference
.long 1992
.long LC3
.long 17
.text

Without this supportyou must create the NSString at run time from a regular constant string. Regarding when you need an autorelease pool and when not (and how to create it), see e.g. http://www.otierney.net/objective-c.html#autorelease

Jonas

Reply by email

Felipe Monteiro de Carvalho
to Jonas Maebe
Without this supportyou must create the NSString at run time from a regular constant string. Regarding when you need an autorelease pool and when not (and how to create it), see e.g. http://www.otierney.net/objective-c.html#autorelease

Ok, pretty excelent so far =) I can already show the status panel.

About the autorelease pool, I'm pretty sure I need one, and I think there is something wrong with my code which should create it.

//  NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  NSAutoreleasePoolId := objc_getClass(PChar(Str_NSAutoreleasePool));
  pool := objc_msgSend(NSAutoreleasePoolId,
sel_registerName(PChar(Str_alloc)), []);
  objc_msgSend(NSAutoreleasePoolId, sel_registerName(PChar(Str_init)), []);

On the second call to msgSend where should I declare that I am calling a method from a already created object? I mean, the typical "self" pointer is missing.

The first argument should be the "self" pointer according to the docs, but if I set it to pool instead of NSAutoreleasePoolID I get a crash. Also I can't consiliate it expecting a generic ID from the class with it being the "self" pointer.

My new code:

program cocoamsgbox;
{$mode objfpc}{$H+}
uses
  objc, FPCMacOSAll, appkit;
const
  Str_NSAutoreleasePool = 'NSAutoreleasePool';
  Str_alloc = 'alloc';
  Str_init = 'init';
  Str_release = 'release';
  Str_Panel_Title = 'This is the title';
  Str_Panel_Message = 'This is the message';
var
  { classes }
  NSAutoreleasePoolId: objc.id;
  { objects }
  pool: objc.id;
  { strings }
  CFTitle, CFMessage: CFStringRef;
begin
//  NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  NSAutoreleasePoolId := objc_getClass(PChar(Str_NSAutoreleasePool));
  pool := objc_msgSend(NSAutoreleasePoolId,
sel_registerName(PChar(Str_alloc)), []);
  objc_msgSend(NSAutoreleasePoolId, sel_registerName(PChar(Str_init)), []);
  NSApplicationLoad();
  { uses a default "OK" button and no alternate buttons }
//    NSRunAlertPanel(@"This is the title", @"This is the message",
nil, nil, nil);
  CFTitle := CFStringCreateWithCString(nil, PChar(Str_Panel_Title),
kCFStringEncodingUTF8);
  CFMessage := CFStringCreateWithCString(nil,
PChar(Str_Panel_Message), kCFStringEncodingUTF8);
  NSRunAlertPanel(CFTitle, CFMessage, nil, nil, nil, []);
  CFRelease(CFTitle);
  CFRelease(CFMessage);
//  [pool release];
  pool := objc_msgSend(NSAutoreleasePoolId,
sel_registerName(PChar(Str_release)), []);
end.

thanks,

Felipe Monteiro de Carvalho

Reply by email

Felipe Monteiro de Carvalho
to Felipe Monteiro de Carvalho

I reread the docs and it seams that when calling alloc I should pass the class id as "self", then use it's result as "self" for init and then use the result from init to call subsequent methods.

Ok, this worked fine now =) Final version of the simple pascal cocoa app bellow.

This is excelent, because it means we can have cocoa bindings purely written in pascal.

program cocoamsgbox;
{$mode objfpc}{$H+}
uses
  objc, FPCMacOSAll, appkit;
const
  Str_NSAutoreleasePool = 'NSAutoreleasePool';
  Str_alloc = 'alloc';
  Str_init = 'init';
  Str_release = 'release';
  Str_Panel_Title = 'This is the title';
  Str_Panel_Message = 'This is the message';
var
  { classes }
  NSAutoreleasePoolId: objc.id;
  { objects }
  allocbuf, pool: objc.id;
  { strings }
  CFTitle, CFMessage: CFStringRef;
begin
//  NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  NSAutoreleasePoolId := objc_getClass(PChar(Str_NSAutoreleasePool));
  allocbuf := objc_msgSend(NSAutoreleasePoolId,
sel_registerName(PChar(Str_alloc)), []);
  pool := objc_msgSend(allocbuf, sel_registerName(PChar(Str_init)), []);
  NSApplicationLoad();
  CFTitle := CFStringCreateWithCString(nil, PChar(Str_Panel_Title),
kCFStringEncodingUTF8);
  CFMessage := CFStringCreateWithCString(nil,
PChar(Str_Panel_Message), kCFStringEncodingUTF8);
  { uses a default "OK" button and no alternate buttons }
  NSRunAlertPanel(CFTitle, CFMessage, nil, nil, nil, []);
  CFRelease(CFTitle);
  CFRelease(CFMessage);
//  [pool release];
  objc_msgSend(pool, sel_registerName(PChar(Str_release)), []);
end.

Reply by email

Felipe Monteiro de Carvalho
to Felipe Monteiro de Carvalho

A compilable version. I updated it to include in itself very limited appkit bindings so that one can run this example even without my partial cocoa bindings. It requires the objc headers, however. In fact I just made this the main objc headers example:

{
cocoamsgbox.pas

This example shows how to use the objective-c runtime headers to call initialization and finalization code for an objective-c class (in this case NSAutoreleasePool), and also shows a message box using minimal AppKit bindings to demonstrate that this can be used to build Cocoa applications.

Compilation of this example requires the following options: -k-framework -kcocoa -k-lobjc

This example project is released under public domain

AUTHORS: Felipe Monteiro de Carvalho
}
program cocoamsgbox;

{$mode objfpc}{$H+}
uses
  objc, ctypes, FPCMacOSAll;

{ Very limited appkit bindings, just to run this example independently of the Cocoa bindings }

{ From AppKit/NSApplication.inc }
function NSApplicationLoad(): CBOOL; cdecl; external;
{ from AppKit/NSPanel.inc }
function NSRunAlertPanel(title, msg, defaultButton, alternateButton,
otherButton: CFStringRef; others: array of const): cint; cdecl;
external;
const
  Str_NSAutoreleasePool = 'NSAutoreleasePool';
  Str_alloc = 'alloc';
  Str_init = 'init';
  Str_release = 'release';
  Str_Panel_Title = 'This is the title';
  Str_Panel_Message = 'This is the message';
var
  { classes }
  NSAutoreleasePoolId: objc.id;
  { objects }
  allocbuf, pool: objc.id;
  { strings }
  CFTitle, CFMessage: CFStringRef;
begin
  {  NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; }
  NSAutoreleasePoolId := objc_getClass(PChar(Str_NSAutoreleasePool));
  allocbuf := objc_msgSend(NSAutoreleasePoolId,
sel_registerName(PChar(Str_alloc)), []);
  pool := objc_msgSend(allocbuf, sel_registerName(PChar(Str_init)), []);
  NSApplicationLoad();
  CFTitle := CFStringCreateWithCString(nil, PChar(Str_Panel_Title),
kCFStringEncodingUTF8);
  CFMessage := CFStringCreateWithCString(nil,
PChar(Str_Panel_Message), kCFStringEncodingUTF8);
  { uses a default "OK" button and no alternate buttons }
  NSRunAlertPanel(CFTitle, CFMessage, nil, nil, nil, []);
  CFRelease(CFTitle);
  CFRelease(CFMessage);
  {  [pool release]; }
  objc_msgSend(pool, sel_registerName(PChar(Str_release)), []);
end.

Reply by email