Dismiss Notice

REGISTRATION IS AFTER ADMIN ACTIVATION

DONATIONS WITH PAYPAL CLICK TO BUTTON

6 MONTHS VIP - 20$; 1 YEAR VIP - 30$; 2 YEARS VIP - 50$; GOLD USER FOR LIFE VIP - 150$

DONATIONS WITH Bitcoin Address:3NRs3CK3fhXifrNYxHEZKpETDd9vNLMsMD

Dismiss Notice
The registration is approved by the Administrator. It takes about 1 day to approve your registration
Dismiss Notice
For open hidden message no need write thanks, thank etc. Enough is click to like button on right side of thread.

Using OS X APIs directly from Delphi

Discussion in 'Delphi Android And IOS' started by AdminDF, May 28, 2014.

  1. AdminDF
    Online

    AdminDFAdminDF is a Verified Member DelphiFan Administrator Staff Member DF Staff

    The target last week was OS X. In this post, I’ll share a couple of things I learned about working with OS X in Delphi.

    Specifically, I’ll talk about using objects directly from the operating system, in order to get access to features that FireMonkey doesn’t directly provide.

    Note: This is obviously not cross-platform, since we’re going to be creating platform-specific code. If you’re going to use these techniques in a cross-platform application, you’ll need to rewrite this code for each platform, using $IFDEF to separate code for different platforms. In the case of OS X, you’ll want to enclose the platform-dependent code in {$IF DEFINED(MACOS)}..{$IFEND} pairs.

    Rule #1 – The object you have is not the object you want
    [Most of] The OS X API is object-oriented, which is cool. The platform API calls often require you to pass an object reference, known as an id in Objective-C parlance. Delphi is also object-oriented, and you can get those OS X objects as Delphi objects pretty easily (I’ll show you how a little below). But here’s the trick: The Delphi object is not the Objective-C id. That will make a little more sense below, but just remember that. If you have an object in Delphi, and you need to pass it to an OS X API, you need to get the id of the object.

    With that out of the way, let’s look at doing something common direct from the API: Making an HTTP request.

    Our quest: An HTTP request without using Indy
    The Indy networking components are included with Delphi XE6, and work in applications created with the FireMonkey framework. Indy is kind of polarizing through, and while many developers use it and are very successful, others prefer not to use it. I’m not making any judgement of Indy, or anyone’s preferences, but for this demo, let’s assume that we want to make an HTTP request without any dependencies other than the operating system.

    Step 1: How do we make an HTTP request in OS X?
    The first step is figuring out what the OS X APIs would be in the first place. A little web searching leads us to the API docs for NSURLConnection, a class specifically designed for this task.

    Looking at the API, we see this list of “tasks:”
    [​IMG]

    [font='Helvetica Neue', Helvetica, sans-serif]This is what we Delphi programmers would call the “methods” of the NSURLConnection class. The ones that are prefixed with a ‘-’ symbol are the “instance” methods, and the ones prefixed with a “+” symbol are the “class” methods.[/font]
    [font='Helvetica Neue', Helvetica, sans-serif]To keep this simple, we’ll send send a synchronous request, which means that we’re going to send our HTTP request and then wait for a response. (You shouldn’t really do this in your application’s main thread, because it will bHIDE-THANKS your UI. For real-world use, either use a synchronous request in a background thread, or use an asynchronous request). NSURLConnection has a “method” called
    [/font]
    Code:
    + sendSynchronousRequest:returningResponse:error:
    

    [​IMG]

    This gives us a little more information:

    It returns an NSData object
    It takes 3 parameters:
    an NSURLRequest, which is in object that represents the URL to load
    an reference to an NSURLResponse variable, which the method will use as an OUT parameter
    a reference to an NSError variabls, which the method will use as an OUT parameter. This can be NULL (or nil in Delphi terms)
    How do we call this method in Delphi?

    To call this class method, we need two things: First, we need a reference to the NSURLConnection class, and second, we need the parameters.

    So, here’s the next big idea:

    Getting an OS X class reference in Delphi
    Many of the OS X classes, including NSURLConnection, are defined in the Macapi.Foundation unit. Add Macapi.Foundation to your uses clause.

    If we search Macapi.Foundation.pas for NSURLConnection, we find three apparently useful types:

    NSURLConnectionClass
    NSURLConnection
    TNSURLConnection
    We know that we want the sendSynchronousRequest method, and that appears to be defined in NSURLConnectionClass, but NSURLConnectionClass is an interface, and we can’t call class methods on an interface, so how do we get a reference to that interface?

    The key here is TNSURLConnection. It’s the “bridge” between the Delphi interfaces that represent the class, and the underlying OS X classes. We can use TNSURLConnection.OCClass to get a reference to NSURLConnectionCass.

    IMPORTANT: This is a pattern throughout Delphi. If you want to call a class method on an ObjectiveC class, use T<WhateverTheClassNameIs>.OCClass.callTheMethod(...) (I’m pretty sure OCClass stands for Objective-C Class).

    So, we can call TNSURLConnection.OCClass.sendSynchronousRequest(???). We just need to fill in the ???

    The first parameter is an NSURLRequest. How do we get one of those?

    Getting an OS X Object instance in Delphi
    Most Objective-C classes don’t use a traditional constructor the way we’re used to in Delphi. Instead, they often have class methods that return an instance of the class. (If they do use a traditional constructor, it’s actually a pair of methods called alloc and init).

    NSURLRequest has a class method +(id)requestWithURL: that returns an NSURLRequest. We’ll use that as our constructor, using the same pattern as above:


    Code:
    //var URLRequest: NSURLRequest
    URLRequest := TNSURLRequest.OCClass.requestWithURL(???); //We'll figure out the 
    
    parameter below
    This looks good, but in fact, it’s wrong!

    Remember rule #1: The object you want isn’t the object you have. The requestWithURL method returns the id of the Objective-C class. We can’t call Delphi methods on this id. We need the Delphi interface that wraps around this id. Again, TNSURLRequest is the bridge. We can call the wrap method of TNSURLRequest to get the Delphi wrapper interface for the Objective-C id:

    URLRequest := TNSURLRequest.Wrap(TNSURLRequest.OCClass.requestWithURL(??)); //This works
    IMPORTANT:  This is the other side of the pattern. To get a Delphi interface for an Objective-C class, use T<WhateverTheClassNameIs>.Wrap(ObjC_id);

    Of course, now the rabbit hole goes deeper. We need that parameter to requestWithURL, which is an NSURL.

    (At this point, we start to get a little frustrated: I just want to give it a string with a URL and get back the data at that URL! Never fear; we’re almost there.)

    Looking at the API docs for NSURL, we find a glimmer of hope: NSURL has a class method +(id)URLWithString:. Hopefully that will let us create a url from our string and finally put this all together to get a result.

    And indeed, we can. First attempt:


    Code:
    //var URL: NSURL
    URL := TNSUrl.Wrap(TNSURL.OCClass.URLWithString('http://www.twodesk.com'));
    
    This almost works, except it doesn’t compile. There’s an error “There is no overloaded version of URLWithString that can be called with these arguments.”

    Remember rule #1 again. URLWithString takes an NSString, and we’re trying to pass it a Delphi string. The object we have is not the object we want. Thankfully, there’s a simple utility method to convert a Delphi string into an NSString object. It’s in the Macapi.Helpers unit, and it’s called StrToNSStr:

    URL := TNSURL.Wrap(TNSURL.OCClass.URLWithString(StrToNSStr('http://www.twodesk.com')));
    Finally! We have a URL object. Let’s put it all together:


    Code:
    var
      URL: NSURL;
      URLRequest: NSURLRequest;
      Data: NSData;
      Response: Pointer;
    begin
      URL := TNSURL.Wrap(TNSURL.OCClass.URLWithString(
        StrToNSStr('http://www.twodesk.com')));
      URLRequest := TNSURLRequest.Wrap(TNSURLRequest.OCClass.requestWithURL(URL));
      Data := TNSURLConnection.OCClass.sendSynchronousRequest(URLRequest, @Response,
          nil);
      //Do something with the data
    end;
    
    The last step is to do something with the data:

    Using NSData
    NSData is a general-purpose class for representing any generic data. Its two most important members are NSData.bytes, which is a pointer to the actual data, and NSData.length, which is the size of the data in bytes.

    In the case of an NSData object returned by TNSURLConnection in the example above, bytes is UTF-8 encoded text. This means we can directly access the bytes as a PAnsiChar in Delphi:


    Code:
    //var S: string
    S := PAnsiChar(Data.bytes);
    
    Put it all together
    I created a FireMonkey HD Desktop app, and put a TButton (Button1) and TMemo (Memo1) on the form. Then, I changed the target platform to OS X (If you don’t change the target platform, none of the platform-specific code will compile). Here’s the code for the Button1 click event:


    Code:
    procedure TForm1.Button1Click(Sender: TObject);
    var
      URL: NSURL;
      URLRequest: NSURLRequest;
      Data: NSData;
      Response: Pointer;
    begin
      URL := TNSURL.Wrap(TNSURL.OCClass.URLWithString(
        StrToNSStr('http://www.twodesk.com')));
      URLRequest := TNSURLRequest.Wrap(TNSURLRequest.OCClass.requestWithURL(URL));
      Data := TNSURLConnection.OCClass.sendSynchronousRequest(URLRequest, @Response,
          nil);
      Memo1.Lines.Text := PAnsiChar(Data.bytes);
    end;
    

    Recap
    To get direct access to the static methods of an Objective-C class in Delphi: T<ObjCClassName>.OCClass.<StaticMethodName>

    To get a Delphi interface to an instance of an Objective-C class: T<ObjCClassName>.Wrap(<ObjC_id>)

    (We didn’t use this in the code above, but for the sake of completeness) To get the Objective-C id from a Delphi interface: (<DelphiInterface as ILocalObject).GetObjectID

    To convert a Delphi string into an NSStringObject: StrToNSStr(<DelphiString>) //In the Macapi.Helpers unit
     

Share This Page

Laws
Our website, law, laws, copyright and aims to be respectful of individual rights. Our site, as defined in the law of 5651 serves as a provider of space. According to the law, contrary to the law of site management has no obligation to monitor content. Therefore, our site has adopted the principle of fit and remove. Works are subject to copyright laws and is shared in a manner that violated their legal rights, or professional associations, rights holders who, adsdelphi@gmail.com I can reach us at e-mail address. Complaints considered to be infringing on the examination of the content will be removed from our site.
Donate Help To Us and Be VIP
DONATIONS WITH PAYPAL CLICK TO BUTTON
6 MONTHS VIP - 20$; 1 YEAR VIP - 30$; 2 YEARS VIP - 50$; GOLD USER FOR LIFE VIP - 150$
Social Block