On the NZ DUG email list (yes, we still have those here) a question was recently posted asking for help with getting some FTP code working on OSX, using XE2. This coincided nicely with my reaching a point in my Objective-C learning where this sort of exercise was of interest to me also, so I decided to try porting the CFFTPSample from the Apple Developer reference materials as a learning exercise.
Whilst I am intent on using Objective-C for any actual OSX/iOS work, I was also curious to see just how difficult (or easy) XE2 made this task (on the off-chance that initial impressions might have been overly pessimistic) but also as an exercise in testing my level of knowledge with Objective-C gleaned so far.
Could I take some arbitrary Objective-C code and translate it to Pascal ?
Obviously syntax isn’t the biggest challenge here, rather understanding how the Objective-C language is “plumbed in” to the OSX/iOS environment, which isn’t something that the language teaching materials guides (that I have been using to this point) actually deal with.
This post is the first in a series of currently undetermined length (or frequency) in which I shall share this experience.
For those who can’t wait for the denoument and want to know whether the exercise confirmed or dispelled my concerns w.r.t using Delphi (in it’s current form) for OSX/iOS development, suffice to say I am continuing with my Objective-C learning.
If you are still interested in how and why I reaffirmed this decision, read on…
First Things First
So, the first thing obviously is to download the CFFTPSample code itself.
When viewing the developer reference materials from the XCode Documentation viewer application, the sample entry has an “Open Project” link. This prompts you for the location where you wish to place the sample code and then opens the project in XCode itself. Very slick.
Embarcadero – take some notes here will you ?
If you are viewing the sample page in a browser over the web then this XCode integration is replaced by a “Download Sample Code” facility which downloads a zip – not as slick, obviously, but just goes to show that just because you have documentation on a web site that doesn’t mean you can’t also offer the same thing in a way that takes advantage of tighter integration where/when available.
What it Does and Doesn’t Do
The CFFTPSample is a simple command line utility that supports a basic download/upload or directory listing facility to a URL. It respects system configured proxy settings where required and exercises the key aspects of an FTP client, so in that respects it is a useful, non-trivial example.
At the time of writing, I have so far only translated the directory listing functionality of the sample code, but that has already thrown up enough challenges, with attendant cranial bruising and wall damage, to make these posts worth writing before the exercise is strictly complete.
In the main()
First of all, I am not – at this stage – going to show the line-by-line translation of the code as it happened. Most of it is pretty mundane and straightforward stuff. I shall instead focus on those things that caused headaches or are non-obvious.
So things like the decoding of command line parameters passed to the utility I shall skip over. Obviously there was no literal translation from the Objective-C code using argc and argv etc, rather a re-write to use ParamCount and ParamStr().
However, one aspect of the parameter handling did cause some unexpected trouble.
As Delphi developers I think it can be easy to forget the pain that users of other lower level languages have to deal with when it comes to strings. The Delphi String type is – by and large – a hugely powerful and yet deceptively simple beast (things got a bit more complicated for some of us, thanks to the way that Unicode was approached, but even then this complication results primarily in pitfalls for the unwary who venture into perhaps rarer territory than most when it comes to string handling).
In particular we take for granted the fact that this power comes packaged in a neat little box which we can use directly with the runtime of the operating system for which it was, after-all, designed (i.e Windows) simply by the appropriate use of a type-cast. A String, with all it’s power, is also a simple PChar when required.
That’s fine and dandy for Windows, but things are not so straightforward when working with OS X or iOS.
In OS X/iOS (from now on just OS X, for brevity unless/except where the distinction is important and thus will be mentioned) we have to deal with strings in a multitude of forms. The most common (in the CFFTPSample at least) is the CFStringRef form.
This is actually a reference to a CFString object, the reference/value distinction being explicit in this case. CFString is the string, to which a CFStringRef is a reference. In our code (so far it seems) we always deal with a CFString through a CFStringRef reference (CFString isn’t even declared for us in the MacAPI.* units with XE2. More on what is, and what is NOT in those MacAPI.* units later).
Being a command-line sample, one of the first things I had to do in my port was to convert the various command-line parameters to the application into CFStringRef references. The CoreFoundation library provides a number of factory functions to facilitate precisely this.
Notice I said “functions”, not “constructors”.
Although CFString is referred to as an “object”, everything we do with it is via function calls, not methods (or messages if you are already adapted to the Objective-C way of thinking).
!! HERE BE DRAGONS !!
For the unwary Delphite these factory functions include a couple of traps.
First, there is the temptingly named CFStringCreateWithPascalString() function, which even allows us to specify the encoding using by that string! Brilliant! We can just pass in our Delphi Strings, telling OS X that they use UTF-16 and all will be well, right ?
This will compile but will not work:
var url: CFStringRef; begin : url := CFStringCreateWithPascalString(NIL, @ParamStr(2), kCFStringEncodingUTF16); : end.
Delphi being Pascal, this is a honey trap of the first order! A “String” in Delphi Pascal is NOT a “PascalString” as far as OS X thinks of them. At least not necessarily.
If you are still using SHORTSTRINGS in your Delphi code then yes, this is what is meant by “PascalString” in this context. i.e. a string with a leading length byte. Otherwise the String type in Delphi is actually a C string. That is, null terminated.
So, clearly the CFStringCreateWithCString() factory function is the most appropriate. Yes ? As we know from the convenience of being able to simply type-cast a String as a PChar on Windows, whenever we need to pass a Delphi String to a function expecting a C string, we just have to type-cast it, right?
Normally, yes. But in this case, no.
The documentation for CFStringCreateWithCString() tells us that the C string supplied to the function must use an 8-bit encoding. There is no option to specify the encoding with this function, and in XE2 a Delphi “String” is a UnicodeString, which uses UTF-16 – a 16-bit encoding.
So, before we can use CFStringCreateWithCString() to create a CFStringRef for our command line parameters we must first re-encode those parameter strings in an 8-bit encoding. UTF-8 would be the most obvious candidate:
var url: CFStringRef; url8: RawByteString; begin : url8 := UTF8Encode(ParamStr(2)); url := CFStringCreateWithCString(NIL, PANSIChar(url8), kCFStringEncodingUTF8); : end.
This is a bit laborious however, and fortunately there is yet another factory function which is slightly more convenient: CFStringCreateWithCharacters()
This takes a pointer to a WideChar buffer, although slightly less conveniently it also requires that we specify the length of that buffer – null termination is no help here.
var url: CFStringRef; begin : url := CFStringCreateWithCString(NIL, PWideChar(ParamStr(2)), Length(ParamStr(2))); : end.
Conclusions of the First Part, and Next Steps
So, even the simple process of getting our command-line parameters into a form usable by the real “meat” of our simple sample presented some unexpected hurdles and gave us (i.e. me) our first introduction to some of the differences to expect when coming from a language that has been extended specifically to support the underlying OS and runtime in the way that Delphi originally was for Windows, versus being cajoled into spitting out the right code at the back-end without being updated on the front-end.
Not that I think the “String” type necessarily would have been easily implemented as an OS X “native” type or in a type-compatible fashion as was possible with Delphi String and Windows *CHAR. Just that this provides a first brush with the level of “disconnect” experienced when trying to use Delphi for OS X development.
In the next installment in this series I shall look at another disconnect, in the form of gaping omissions in the XE2 exposures of CoreFoundation libraries available in OS X. Even basic OS facilities are not accessible using the provided MacAPI.* units in XE2.
Fortunately adding the required exposures is relatively straightforward, if cumbersome. And that is what we shall look at next time.