Writing Delphi console applications that call a Cocoa API

Just a quick tip for anyone struggling with calling a Cocoa (i.e., Objective-C) API in a Delphi console application: if you’re getting ‘class cannot be found’ errors on the simplest of calls, that’s because the relevant Cocoa framework hasn’t been loaded, and therefore, hasn’t had a chance to register its classes with your application. A quick way to fix this is to make a dummy call at the top of the DPR to one of the string ‘constant’ loading routines in the Macapi.Foundation unit, e.g. NSDefaultRunLoopMode:

uses 
  System.SysUtils, Macapi.Foundation;
begin
  NSDefaultRunLoopMode;
  //actually do stuff...

Once done, there is nevertheless a second thing you’ll need to do, which is to set up an Objective-C/Cocoa ‘auto-release pool’. This probably sounds more grand than in actually is – just create an instance of NSAutoRelease pool at the head of your code, and ‘drain’ it at its foot:

program CocoaConsoleTest;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils, Macapi.Foundation;
var
  Pool: NSAutoreleasePool;
  Host: NSHost;
begin
  NSDefaultRunLoopMode; //just something to ensure the Cocoa Foundation framework is loaded
  Pool := TNSAutoreleasePool.Create;
  try
    Host := TNSHost.Wrap(TNSHost.OCClass.currentHost);
    WriteLn('This computer is called ' + UTF8ToUnicodeString(Host.localizedName.UTF8String));
  finally
    Pool.drain;
  end;
end.
Advertisements

16 thoughts on “Writing Delphi console applications that call a Cocoa API

  1. Hi Chris

    I enjoy your posts.

    However I was wondering if you know how to do an example tabbar app using uiviewcontrollers all in delphi??? no firemonkey.

    thanks

    • Sorry, no – uiviewcontrollers is iOS isn’t it? I’ve only been looking at the purely Delphi side, which means OS X – the FPC part uses a completely different mechanism for Objective-C interop, for a start.

  2. Hey Chris:

    I don’t know if you can help, but I’m trying to access the Screens function of the NSScreenClass and I got your book but I wasn’t able to figure it out.

    TNSScreen is defined in MacAPI.AppKit.pas as

      TNSScreen = class(TOCGenericImport<NSScreenClass, NSScreen>)  end;
    

    So I’d expect to be able to access the NSScreenClass’s functions but alas no.

    var
       ScreenObj:NSScreen;
    begin
      ScreenObj:=TNSScreen.Alloc;
      ScreenObj.Screens;
    

    Won’t compile.

    Thanks in advance,

    Ken

    • If you look at Apple’s documentation for NSScreen, you’ll see the ‘screens’ method is listed with a plus sign in front of it. This means it is a class not an instance method, and as such, needs to be accessed via the OCClass member of TNSScreen:

      uses
        Macapi.CocoaTypes, Macapi.Foundation, Macapi.AppKit;
      
      procedure TForm1.Button1Click(Sender: TObject);
      var
        Screens: NSArray;
        I: NSUInteger;
        Screen: NSScreen;
        Msg: string;
        FrameRect: NSRect;
      begin
        Screens := TNSScreen.OCClass.screens; // <- HERE
        for I := 0 to Screens.count - 1 do
        begin
          Screen := TNSScreen.Wrap(Screens.objectAtIndex(I));
          FrameRect := Screen.frame;
          Msg := Msg + Format('Screen %d: (%g, %g) - (%g, %g)', [I + 1,
            FrameRect.origin.x, FrameRect.origin.y,
            FrameRect.size.width + FrameRect.origin.x,
            FrameRect.size.height + FrameRect.origin.y]) + sLineBreak;
        end;
        ShowMessage(Msg);
      end;
      
  3. Thank you so much — my problem isn’t so much looking at Apple’s documentation but rather UNDERSTANDING how it relates to Delphi! 🙂

    You are providing invaluable insight here and I highly recommend your books. Stupid question, perhaps, but how did you get so smart…? Meaning rather, what is your source for the kind of information you know as it certainly isn’t Embarcadero’s (sad excuse for) documentation.

    • ‘what is your source for the kind of information’

      Trawling through the source code and just attempting to use it really… Mind you, the Delphi to Objective-C bridge could really do with some official documentation by now – so far as I am aware, the few pages about it in my XE2 book is the only documentation it’s ever had!

      • I’m sure it is. They (Embarcadero) are dropping the ball in so many places with FireMonkey. I have a fascinating bug of the week — create a bitmap, load an image, do something with it, FREE the bitmap and then create a new one… and it will have the contents of the old bitmap that you freed! (Quality (cough-cough) Center Report # 117090)

        • Well, for me the new pixels being undefined wouldn’t be invalid as such – the VCL is different only because its TBitmap does an explicit FillRect call internally. On the other hand, the docs don’t say the new pixels are undefined, and given that, one would have thought it would be reasonable to assume FMX behaves like the VCL…

          • What’s inconsistent about it is the FIRST time a program creates a bitmap, it is completely empty, not filled with random noise. So it seems logical that subsequently created bitmaps would also be completely empty not refilled with old data. If you had to initialize the first one, then I could accept the behavior but it should be one or the other.

          • By ‘undefined’, I meant… undefined, and therefore, with no necessary consistency. That said, even the ‘first time around’ behaviour you describe doesn’t match the VCL. Try this – with a fresh VCL application, add a TPaintBox to the form and handle the paint box’s OnPaint event as such:

            procedure TForm1.PaintBox1Paint(Sender: TObject);
            var
              Bitmap: TBitmap;
            begin
              Bitmap := TBitmap.Create;
              try
                Bitmap.Canvas.Brush.Color := clRed;
                Bitmap.SetSize(PaintBox1.Width, PaintBox1.Height);
                PaintBox1.Canvas.Draw(0, 0, Bitmap);
              finally
                Bitmap.Free;
              end;
            end;
            

            Run the application; you should find the paint box filled with red. Now create a fresh FMX application, add a TPaintBox again, and handle its OnPaint event similarly:

            procedure TForm2.PaintBox1Paint(Sender: TObject; Canvas: TCanvas);
            var
              Bitmap: TBitmap;
              R: TRectF;
            begin
              Bitmap := TBitmap.Create(0, 0);
              try
                R := RectF(0, 0, PaintBox1.Width, PaintBox1.Height);
                Bitmap.Canvas.Fill.Color := TAlphaColors.Red;
                Bitmap.SetSize(Trunc(R.Width), Trunc(R.Height));
                Canvas.DrawBitmap(Bitmap, R, R, 1);
              finally
                Bitmap.Free;
              end;
            end;
            

            This time, nothing appears to gets drawn.

          • Hmmm, it seems like it’s actually a lot more complicated than just the initial definition of the pixels during the setsize. SetSize performs a canvas.EndScene so even if you explicitly draw to the bitmap following the SetSize and prior to the DrawBitmap, nothing appears.

            Why they didn’t just wrap ALL drawing functions in a beginscene/endscene wrapper baffles me. In what case would you would have code that tries to draw something and you’d want it to simply fail silently? Furthermore, since the BeginSceneCount is just an integer that gets incremented, the overhead for checking it and incrementing/decrementing it is effectively nil.

            Because of this and other issues, I’ve put together two helper classes that they to make working with bitmaps and canvases far more predictable and logical. I’ve sent them to you via email and you should feel free to post them to the world as you see fit.

            The Bitmap helper class makes use of your TClipboard object to support cutting/copying and pasting… without which I would personally be up the creek without a paddle.

            Cheers,

          • Yes, you’re quite right about the missing BeginScene/EndScene calls – I rather typed too soon.

            I’ve just updated the TClipboard code by the way (r.82) – can you let me know whether the corruption issue still happens please?

  4. Hi Chris

    Just came across your posts. What I’m looking for is simulate a key press on OS X. I imagine it is not so complicated if you know where to start which I don’t so perhaps you can give me a hint or two.

    Grega

    • Hi Grega

      Bit of a belated reply this, but you need to call the CGEventXXX functions from MacApi.CoreGraphics – I’m going to post an example (and a simple cross platform wrapper) soon.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s