Understanding Core Foundation

The native API on OS X is spread over several layers. Some of these are written in (and designed for) Objective-C, but others are just C-based APIs. An example of the latter is the POSIX layer, which is the API shared with other Unix and Unix-like operating systems such as Linux.

Roughly speaking, POSIX has a feel akin to the traditional Windows API, if not completely – lower case identifiers rule, and text encoding is UTF-8 rather than UTF-16 or ‘Ansi’. Beyond it stands the ‘Core’ APIs such as Core Foundation and Core Graphics. Unlike POSIX, these are Mac specific, and use a pseudo-object oriented approach within the bounds of a straight C interface. For example, instead of using C-style ‘strings’ (PAnsiChar or PWideChar in Delphi terms), the Core APIs have ‘CFString’ and ‘CFMutableString’ pseudo-object types. These act a bit like the ‘handle’ types you have in the Windows API, in which every window has an HWND, every graphical output destination (‘device context’) an HDC, and so on. Indeed, the analogy goes further, because just like you never see a ‘WND’ or ‘DC’ type in code (instead, you use HWND and HDC), so you never see CFString or CFMutableString: rather, you use CFStringRef and CFMutableStringRef. This can seem odd at first, but only because Delphi classes are reference not value types, meaning the ‘Ref’ thing is effectively implicit in pure Delphi code.

Where the analogy with Windows API handles falls down though is in how Core XXX ‘objects’ are reference counted. The reference counting model used is similar to the IUnknown/IInterface one, though not exactly – in particular, weak references are much more common when using the Apple API, i.e. where the reference count (in Apple-speak, ‘retain count’) hasn’t been incremented. Because of this, automatic reference counting in the way it implemented by the Delphi compiler for IUnknown/IInterface isn’t realistic, and instead, reference counting is all manual. When using Core Foundation objects, this means calling CFRelease and CFRetain as appropriate: every time you get a CF object from a function with ‘Create’ or ‘Copy’ in its name, you will need to call CFRelease to have it freed properly; conversely, receiving an object via a function without ‘Create’ or ‘Copy’ in its name may require calling CFRetain (then CFRelease later) to avoid the object being freed from under your feet.

Like Delphi, Core Foundation’s native string encoding is UTF-16. To convert from a Delphi to a CFString, call CFStringCreateWithCharacters.

uses Macapi.CoreFoundation;

function DelphiToCFString(const S: string): CFStringRef; inline;
begin
  Result := CFStringCreateWithCharacters(nil, PChar(S), Length(S));
end;

If you look at the FMX source, you may get the idea you need to go through a conversion to UTF-8 – you don’t however. Anyhow, to go the other way, call CFStringGetCharacters:

function CFToDelphiString(const CFStr: CFStringRef): string;
var
  Range: CFRange;
begin
  if CFStr = nil then Exit('');
  Range.location := 0;
  Range.length := CFStringGetLength(CFStr);
  SetLength(Result, Range.length);
  CFStringGetCharacters(CFStr, Range, PChar(Result));
end;

Since CFString is ‘toll-free bridged’ with the Cocoa/Objective-C equivalent, you can use the above routines to convert to and from NSString too (or more exactly, the NSString wrapper interface type):

uses Macapi.ObjectiveC, Macapi.Foundation;

function DelphiToNSString(const S: string): NString;
begin
  Result := TNSString.Wrap(DelphiToCFString(S));
end;

function NSToDelphiString(const S: NSString): string;
begin
  Result := CFToDelphiString((S as ILocalObject).GetObjectID);
end;

If truth be told, using the Core APIs is a little tedious – being able to pass in C-style ‘strings’ and ‘arrays’ directly would be so much simpler, but no, everything is done through pseudo-object wrappers. However, the tedium applies regardless of the language you are programming in – it isn’t a special Delphi ‘tax’.

Advertisements

6 thoughts on “Understanding Core Foundation

  1. This post contains a number of errors. Primarily in the fact that it doesn’t help us understand CoreFoundation at all. It merely picks out one very isolated type and passes a few comments about it. 😉

    You also incorrectly compare the various CoreFoundation Ref types to handles in the Windows API.

    HWND, HDC et al are – in the main – nothing at all like the ref types. A ref type, such as CFStringRef is a pointer, plain and simple (a pointer to what I confess I am not *entirely* sure at this stage). But a pointer it is. A handle on the other hand is an opaque reference – it might in some cases be a pointer but far more often than not it is merely an ID or index to some internal data structure that in turn may contain a further dereferenced reference to the ultimate entity involved.

    Also, if you look through the FMX source, as well as getting the idea that sometimes you need to convert to UTF-8, the far clearer idea is that the people writing FMX don’t themselves seem to know what they need to do. Sometimes they convert to UTF-8. Sometimes they don’t. One thing they never do is use their own TCFString pseudo-wrapper to convert Delphi Strings to CFStringRef’s.

    The clearest idea is that FMX is a bit of a dog’s breakfast. Though ironically not one that contains any of Embarcadero’s own dog food!

    If truth be told, the Windows API can be a little tedious. Being able to pass in strings directly as we can in Delphi makes things so much simpler even though in the Delphi RTL those strings are actually far more complex pseudo-objects that are much easier to work with than the C strings that the API expects.

    The tedium is far greater in other languages. Delphi provides a rebate on that tax which is one of the reasons it is such a GREAT language for developing Windows applications, even when needing to access the underlying Windows API directly.

    That is only as the result of a very wise decision taken in the implementation of the Delphi 2 compiler and the really quite sophisticated compiler support for the LongString type introduced with that revision of the compiler.

    Before that – in Delphi 1 and before that in Turbo Pascal – StrPas() and StrPCopy() were a fact of life for the Delphi developer on Windows.

    The most depressing part of all this is that as far as I can tell so far, CFString[Ref] itself looks like it could provide the perfect basis for the underlying implementation of the Delphi String type. Had Embarcadero been bothered to put that effort into this iteration of the compiler we could have had a language which would have had the same advantage over the “native” languages of the newly supported Mac platforms as it did over C/C++ on Windows.

    But they didn’t, and unless/until they come around to this way of thinking we’re stuck with the tax (relative to the “rebate” we enjoy when developing for Windows) AND a whole host of other impediments to writing truly “native” code for this platform.

    • The core points (boom boom) were that Core Foundation is a straight C not an Objective-C API, that it has a pseudo-OOP and reference counted structure, and that calling it from Delphi is little different to calling it from Objective-C (put another way: the Delphi to Objective-C bridge doesn’t come into it, and you call it from Delphi directly). Additionally, I went through how to convert to and from Delphi and CF strings, of which there seems to be a certain misunderstanding about. Seems a reasonable intro to CF to me…

      Anyhow, you should really forget about that CFUtils unit. Do you lay in bed at night troubled by TXmlIniFile too? Actually, don’t answer that… 😉

    • PS – the fact HWND, HDC et al are type aliased to an integer not a pointer type is arguably an error in the Delphi header translations that has never been corrected. That XE2 aliases them to a ‘pointer-sized’ integer specifically is a bit of a hack, conceptually speaking…

    • PPS: “The most depressing part of all this is that as far as I can tell so far, CFString[Ref] itself looks like it could provide the perfect basis for the underlying implementation of the Delphi String type.”

      Presumably you mean CFMutableString? Even still, I wouldn’t go that far, as it would mean a different implementation of the Delphi string type across different platforms, and consistency between ‘Delphis’ is a virtue in itself. That said, I still think a WideString-type thing would be good though, as I suggested on your blog – i.e., a dedicated string type that uses the CF API under the hood, and named appropriately (unlike WideString!). Apple’s slighly wonky reference counting norms are a potential issue though.

    • Jolyon,

      You start your reply by stating “This post contains a number of errors”, yet you fail to mention a single one, merely rebutting matters of opinion.

      I’ve observed in many Delphi blogs the same trend – it’s like there is some pissing contest going on. Is this an inside joke or something?

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