31
votes

I'm working on Delphi XE2 application targetting Mac OS and Windows. And I want to have integration into context menu. For windows this is simple task. But for Mac OS I dont know how to do this.

I've read Providing a Service documentation and tried similar code in Delphi but with no luck.

Look at simple code for Finder integration trials.

App.dpr

program App;
uses
   SysUtils,
{$IFDEF MACOS}
  AppKit, CocoaTypes, CoreFoundation,
  CoreServices, Foundation, Mach, ObjCRuntime,
  ObjectiveC, OCMarshal, OpenGL, QuartzCore, Security,
  SystemConfiguration,
{$ENDIF}
  MessageProvider;
{$IFDEF MACOS}
var
  app: NSApplication;
  provider: TMessageProvider;
{$ENDIF}

begin
  Application.Initialize;

{$IFDEF MACOS}
  provider := TMessageProvider.Create();

  app := TNSApplication.Alloc();
  app.setServicesProvider(provider);
{$ENDIF}

  Application.CreateForm(TFormOSVersion, FormOSVersion);
  Application.Run;
end.

MessageProvider.pas

unit MessageProvider;

interface

uses
  FMX.Dialogs
{$IFDEF MACOS}
  , AppKit, CocoaTypes, CoreFoundation,
  CoreServices, Foundation, Mach, ObjCRuntime,
  ObjectiveC, OCMarshal, OpenGL, QuartzCore, Security,
  SystemConfiguration
{$ENDIF}
  ;

type
  TMessageProvider = class
  public
    procedure simpleMessage(var userData: string; var error: string);
  end;

implementation

procedure TMessageProvider.simpleMessage(var userData: string; var error: string);
begin
  ShowMessage('Simple message from service.');
  error := '';
end;

end.

Added configuration to info.plist

<key>NSServices</key>
<array>
  <dict>
     <key>NSKeyEquivalent</key>
     <dict>
         <key>default</key>
         <string>e</string>
     </dict>
     <key>NSMenuItem</key>
     <dict>
         <key>default</key>
         <string>App/Message</string>
     </dict>
     <key>NSMessage</key>
     <string>simpleMesage</string>
     <key>NSPortName</key>
     <string>App</string>            
  </dict>
</array>

When run this on Mac OS application hungs and sometimes crashes with 'Bus error' exception.

Can anybody help with this problem?

Or maybe Delphi XE2 doesnt support this kind of functionality?

2
Related question assuming you're using Cocoa+ObjectiveC, could be adapted, using DelphiXE2/Firemonkey ability to invoke cocoa/objectiveC message-based APIs: stackoverflow.com/questions/9420361/… -- I would be tempted to write the whole making-services bit using Cocoa/ObjectiveC and find a way to then simply invoke that native objectiveC shared library from your delphi app.Warren P
I think if it is doable you will find it in the free pascal documentation or forums since XE2 uses free pascal for OSX. And since free pascal has been on OSX much long I am sure it will have more than the Delphi forums.adrianj98

2 Answers

2
votes

Finally, I returned to this project and successfully registered service provider and handled service request.

First of all I tried to use NSRegisterServicesProvider method, but there is no such method in Macapi sources, so I searched for applicationDidFinishLaunching delegate. Using it I registered my service provider:

procedure TApplicationDelegate.applicationDidFinishLaunching(Notification: Pointer);
var
  autoReleasePool: NSAutoreleasePool;
  app: NSApplication;
  provider: TMessageProvider;
begin
  autoReleasePool := TNSAutoreleasePool.Create;
  try
    autoReleasePool.init();

    app := TNSApplication.Wrap(TNSApplication.OCClass.sharedApplication);

    provider := TMessageProvider.Create();
    app.setServicesProvider(provider.ObjId);
  finally
    autoReleasePool.release();
  end;
end;

Also I have created interface for service provider (I think it is required for ObjectiveC-Delphi bridge work):

IMessageProvider = interface(IObjectiveC)['{1EA9319A-8F99-4445-B435-48D5E73876FA}']
    procedure simpleMessage(pBoard: Pointer; userData: Pointer; error: PPointer); cdecl;
end;

and inherited TMessageProvider from this interface and TOCLocal class.

After this my app can react to service request from context menu.

I've shared sources of my project. Here they are.

1
votes

I see two potential problems

  1. You are allocating your own NSApplication object. I doubt that this is correct - doesn't Delphi create one internally also? And even if it doesn't, you'd probably need to enter the NSApplication's run method at some point to make it actually capable of handling messages.

  2. Service providers must be registeres in the applicationDidFinishLaunching: delegate method. You attempt to register it immediatly after creating your NSApplication instance.

I think you can avoid both problems if you use NSRegisterServicesProvider(id provider, NSString *portName) to register your service provide, instead of using NSApplication's setServicesProvider:.