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.
String Theory
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 ?
Wrong.
This will compile but will not work:
var url: CFStringRef; begin : url := CFStringCreateWithPascalString(NIL, @ParamStr(2)[1], 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.
Have you considered something like this?
uses System.Mac.CFUtils
var
url: CFStringRef;
begin
url := TCFString.Create(ParamStr(2)).Value;
end.
Do you look at unit System.Mac.CFUtils?
// This unit provides helper records for dealing with Core Foundation’s
// CFPropertyList types, including CFArray, CFBoolean, CFDate, CFData,
// CFDictionary, CFNumber, and CFString.
specially:
TCFString = record
Value: CFStringRef;
public
// Conversion from Delphi string to CoreFoundation CFString
constructor Create(const AValue: string);
…
end;
Joylon – there are some significant misunderstandings in this piece. For a start, Objective-C hasn’t come into it at all, and for a second, you’ve missed the obvious when it comes to constructing a CFString. You also show an odd lack of understanding about why there is a CFStringRef type but not a CFString one (think how the Delphi and Turbo Pascal object models differ). I’ll blog the details later, all in the spirit of comradely understanding of course…
As an aside, is the original Apple demo available without an Apple developer ID? When I try to download it in Xcode, I fail because I don’t have one.
Chris, I think the misunderstandings are primarily sitting with you.
The lack of Objective-C isn’t a really significant point. At no point do I say that the post is *about* Objective-C. The starting point is an Objective-C program, that is all. In fact, I make specific mention of the fact that the posts in the series will not be focussing on Objective-C syntax or grammar at all.
If you missed that part of the post I wonder what other parts you missed (or are deliberately ignoring in order to construct points of criticism).
Similarly, I take great care to call out the difference between a CFStringRef and the CFString is references, identifying the distinction between the value type and the reference type. i.e. yes exactly the same situation that we had in the TP object model where we would commonly find:
type
PFoo = ^TFoo;
TFoo = object
:
end;
or, in terms relevant to this post:
type
CFStringRef = ^CFString;
CFString = object
:
end;
except that in this case CFString is not a TP or even a Delphi object but some data structure maintained over The Great Divide in the CoreFoundation runtime.
As far as getting the sample code, if you follow the first link in the post, to the Apple web site, you can download the sample code from there without a Developer ID. Did you try that ?
It’s curious that it doesn’t work from within Xcode but who has Xcode but doesn’t have a Developer ID ? More to the point, who can afford to pay for Delphi and has an interest in OS X/iOS development but can’t afford the $99 for an Apple Developer ID ?
Oh per-lease. You title the post ‘Porting the Objective-C CFFTPSample’, yet the clue is in the name – it is a Core Foundation example, and Core Foundation is a *straight C* API, not Objective-C. As for CFString, Apple have (rightly IMO) decided their pseudo-object types are opaque types. This is little different to the Windows API, in which an HDC (for example) is an opaque pointer to a never directly-seen DC. In other words, it isn’t Delphi that is hiding the CFString type!
That said, it’s good that the web links for the demos still work, thanks. I wonder when Apple will close that ‘loophole’…? Anyhow, after opening the original demo up, what do I see but the tedious world of the ‘Core’ APIs in pristine C. Use of it is almost exactly the same in Delphi, despite your insinuations to the contrary!
Lastly, the Core Foundation string type uses UTF-16, just like Delphi – you appear to have been misled either by the Apple demo and/or the FMX source (which, appallingly, does roundtrip conversions all the time – whether by ignorance or the need for FPC compatibility I’m not quite sure). The constructor function you want to use is CFStringCreateWithCharacters, or if you are sure the API won’t want to retain the object beyond the scope of the source Delphi string, CFStringCreateWithCharactersNoCopy. Generally, I’ve found it safer just to use CFStringCreateWithCharacters though, as the Apple frameworks can retain a string (causing access violations later on) when they wouldn’t seem to need to.
CoreFoundation is a framework, not a language. The fact that the sample in question is not making use of the “Objective” parts of the language being used doesn’t alter the fact that it was the “Objective-C compiler” involved.
Do you similarly attack people who post “Delphi” articles whose code is strictly speaking merely “Pascal” and not even “ObjectPascal” ?
You are, I think, picking nits for the sake of it.
If you are going to criticise please limit yourself to criticising the things I said, not the things you *invented* that I meant.
Similarly I never said that Delphi was “hiding” the CFString type, I only mentioned that it was not declared and that observation was intended as a reinforcement of the suggestion that we don’t ever need to deal with it directly.
Something that – ironically – the very presence of a “TCFString” type actually makes more confusing, not less.
And yes, thank you for summarising in that final paragraph the entire point of my post. However, the intention of my post was not simply to force-feed some facts into people without requiring any understanding on their part, but to shine a light on a learning process in the hope that by laying bare the steps toward a piece of learning we might learn more than just “To do X, disengage brain and follow steps 1, 2 and 3”.
Just as telling someone to just cast a String to a PChar without explaining why and how it works will only increase the chances of possession of this fact without the supporting knowledge will lead to further problems and confusion further down the track.
Core Foundation, like all the Core XXX APIs, is a straight C API. It’s that simple. Really…
@Giel and @Radek – see follow up post (coming shortly). 🙂
Hmm, I’d just call the CF API directly – System.Mac.CFUtils is a bit half-arsed in my view. That said, if you are going to claim supporting certain OS types at the compiler level, a la BSTR/WideString, is fundamentally more convenient than doing so at the library level, I will agree with you…
This is why I like the FPC approach, and its “Objective Pascal” syntax mode switch. See http://wiki.freepascal.org/FPC_PasCocoa
The Delphi way (i.e. using interfaces and on-the-fly-created-marshallers) sounds heavy, but matches the default object pascal syntax.
This was a choice made by Embarcadero, perhaps due to the difficulty of making the compiler evolve (there is no syntax switch built-in, alike FPC which support even multiple syntax flavors in the same project).
But as a matter of taste, the FPC approach sounds more natural. No such “plumbing” when creating a NSString.
Er, that is mostly irrelevant, because Joylon is not calling an Objective-C API. For sure, an NSString is ‘toll free bridged’ with CFString, but toll free bridging doesn’t get you that much in practice, as Apple’s own example code shows.
@Chris, yes it’s a straight ‘C’ API. As is the Windows API.
Thus all Delphi examples that only exercise the Windows API and do not use VCL classes or Delphi specific language extensions to Pascal are therefore not “Delphi” samples, but … what ? Pascal samples ? ‘C” samples ? What ?
For crying out loud. If you just want the brownie points for knowing that Objective-C is ‘C with knobs on” and this particular sample doesn’t have any of those particular knobs then congratulations. You get the prize. Well done you.
But since, as you point out as if it is some sort of “error” on my part, the subject of the post is NOT the original language of the sample but rather the hurdles to be overcome and the hoops to be jumped through when trying to recreate it in Delphi it really is entirely pointless to get so worked up at the fact that I used “Objective-C” in the post title, or that learning “Objective-C” was what brought me to this point.
All that matters is that I am porting a sample written in what is perhaps technically speaking only a subset of the Objective-C language into a form which is in turn exercising only a subset of the Delphi language. So this is also not a Delphi exercise, right ?
Which begs the question, why is someone with a blog called “DELPHI Haven” even reading an article that is so utterly irrelevant to them in the first place ?
Or at the very least, why are they banging on about C vs Objective-C but really couldn’t seem to care less when it comes to Pascal vs Delphi ?