Developing with Objective Pascal

Part 9: Parsing Objective C Header Files


Contents

Introduction
The Main Include File
The Parser Config File
Running the Parser
The Interface Unit
Creating a Patch File


Introduction

You can use Objective C classes in your Pascal app if you have a unit of Objective Pascal declarations that are equivalent to the ObjC header file declarations. Except for very simple ObjC classes, you'll want to use FPC's header file parser to assist in creating this Pascal interface unit. The latest version of the parser can be obtained from here:

iOS_6_Parsing_Status.html

The parser includes the parser input files and interface unit for the ASIHTTPRequest classes, so we'll use those files as examples and go over each file in some depth. The ASIHTTPRequest classes and header files to parse are available here:

http://allseeing-i.com/ASIHTTPRequest


The Main Include File

This is a Pascal include file (.inc) that serves both as a list of header files to parse and a list of nested include files, one per header file, that will be "included" in the interface unit.

The ASIHTTPRequest include file is Asihttp.inc and can be found in the parser's /uikit-skel/src/asihttp folder. You can create your own include file wherever you want and point the parser to it, as described below.

Here's what Asihttp.inc looks like:

{$include ASIHTTPRequest.inc}
{$include ASIHTTPRequestDelegate.inc}
{$include ASIProgressDelegate.inc}
{$include ASICacheDelegate.inc}
{$include ASIFormDataRequest.inc}
{$include ASINetworkQueue.inc}
{$include ASIDownloadCache.inc}
{$include ASIInputStream.inc}
{$include ASIDataDecompressor.inc}
{$include ASIDataCompressor.inc}
{$include ASIAuthenticationDialog.inc}
To save yourself some typing, you can open a Terminal window, change to where the header files are, and use the ls command with the -1 switch (numeric digit "one") to list the header files in a single column. You can then copy and paste this list into your .inc file, add {$include to each line, and change the file extensions from .h to .inc}. For example:

ls -1 *.h
ASIAuthenticationDialog.h
ASICacheDelegate.h
ASIDataCompressor.h
ASIDataDecompressor.h
ASIDownloadCache.h
ASIFormDataRequest.h
ASIHTTPRequest.h
ASIHTTPRequestConfig.h
ASIHTTPRequestDelegate.h
ASIInputStream.h
ASINetworkQueue.h
ASIProgressDelegate.h
This list will be in alphabetical order and you can start out keeping the .inc files in that order too, although you may need to change the order later if you have classes that reference other classes.

Note that there's no include file for ASIHTTPRequestConfig.h - this file does not have anything we need, so we've left it out. Some frameworks or classes will have a small .h file that just includes all the other .h files - you can leave out that file as well.


The Parser Config File

The other parser input file that you'll need to create is a small XML configuration file. This file has the same format as the parser's frameworks.xml file that defines the iOS and OS X system frameworks for the parser. Here's a blank template that you can use:

<?xml version="1.0" encoding="UTF-8" ?>
<frameworks>
  <framework>
    <name></name>
    <root></root>
    <headers></headers>
    <include_pattern></include_pattern>
    <header_pattern></header_pattern>
    <external_macro></external_macro>
    <ignore_lines>
    </ignore_lines>
    <ignore_types></ignore_types>
    <ignore_methods></ignore_methods>
    <replace_types>
    </replace_types>
  </framework>
</frameworks>
Note that even though we're defining these classes like a framework, they don't have to be in a framework. Actually, a framework is just a versioned library in a bundle (folder) with a .framework extension that contains the compiled library, headers and other files. From the point of view of the parser, there isn't much difference.

Here's the filled-in asihttp.xml file, with unused elements deleted (consult frameworks.xml for examples of how those elements are used):

<?xml version="1.0" encoding="UTF-8" ?>
<frameworks>
  <framework>
    <name>asihttp</name>
    <root>/asihttp/Asihttp.inc</root>
    <!-- Change the following path if ASI header files (.h) are located elsewhere --> 
    <headers>/Users/Shared/asi-http-request/Classes</headers>
    <include_pattern>{[$]+include (.*).inc}</include_pattern>
    <header_pattern>^(.*)\.h</header_pattern>
    <external_macro>extern</external_macro>
  </framework>
</frameworks>
It may still not be obvious what these elements should contain, so here's a brief explanation of each one:


Running the Parser

To see a list of parser switches, run the parser without any switches:

  php parser.php
Here's the command line for parsing the ASIHTTPRequest headers using the two files described above. Enter this all on one line:

  php parser.php -all -frameworks=^foundation,^uikit,asihttp -aux_config=asihttp.xml -root=`pwd`/uikit-skel/src
    -framework_path=/Developer/Platforms/iPhoneOS.Platform/Developer/SDKs/iPhoneOS5.0.sdk/System/Library/Frameworks
Since this is quite a bit to type, it makes sense to put it in a script. See the parse-ios.sh script for examples of how to do this.

Here are the switches used:


The Interface Unit

The easiest way to create the interface unit file is to make a copy of one of the interface units included with the parser and modify it. However, you may need to do some sleuthing to determine what all needs to go into this file, as described below for the sections of the Asihttp.pas file.

Linking

unit Asihttp;

{$mode delphi}
{$modeswitch objectivec1}
{$modeswitch cvar}
{$packrecords c}

interface

{$linkframework CFNetwork}
{$linkframework SystemConfiguration}
{$linkframework MobileCoreServices}

{$l libAsihttp.a}  //if have static library, use instead of .o files below}

(*
{$l ASIAuthenticationDialog.o}
{$l ASIDataCompressor.o}
{$l ASIDataDecompressor.o}
{$l ASIDownloadCache.o}
{$l ASIFormDataRequest.o}
{$l ASIHTTPRequest.o}
{$l ASIInputStream.o}
{$l ASINetworkQueue.o}
{$l Reachability.o}
*)

{$IFDEF CPUARM}
  {$linklib libgcc_s.1}
{$ENDIF}

Uses

uses
  ctypes,
  CFStream,
  CGBase,
  CFHTTPMessage, CFHTTPAuthentication,
  {SCNetworkReachability,}
  SecBase,
  zlib,
  iPhoneAll,
  AnonClassDefinitionsAsihttp;

Interface

The rest of the unit is the interface that your app will see and use. You can define additional types and constants here if necessary (see iPhoneAll.pas for better examples).

{$define INTERFACE}

type
  BytefPtr = pBytef;  //defined in zlib
//  sockaddr_inPtr = Pointer;  //only needed for Reachability.inc

{$define HEADER}
{$include asihttp/Asihttp.inc}
{$undef HEADER}

{$define TYPES}
{$include asihttp/Asihttp.inc}
{$undef TYPES}

{$define RECORDS}
{$include asihttp/Asihttp.inc}
{$undef RECORDS}

type
{$define FORWARD}
{$include asihttp/Asihttp.inc}
{$undef FORWARD}

{$define PROTOCOLS}
{$include asihttp/Asihttp.inc}
{$undef PROTOCOLS}

{$define CLASSES}
{$include asihttp/Asihttp.inc}
{$undef CLASSES}
 
{$define FUNCTIONS}
{$include asihttp/Asihttp.inc}
{$undef FUNCTIONS}

{$define EXTERNAL_SYMBOLS}
{$include asihttp/Asihttp.inc}
{$undef EXTERNAL_SYMBOLS}

{$define USER_PATCHES}
{$include asihttp/Asihttp.inc}
{$undef USER_PATCHES}

{$undef INTERFACE}
implementation
{$define IMPLEMENTATION}

{$define USER_PATCHES}
{$include asihttp/Asihttp.inc}
{$undef USER_PATCHES}

{$undef IMPLEMENTATION}
end.

Creating a Patch File

The parser isn't 100% accurate (and might never be), so you may need to edit one or more of the .inc files it creates in order for the interface unit to compile. If you'll never need to parse the header files again, you're done. But what if you will parse them again (for example, with an updated parser) or you want to provide your parser input files to someone else? In that case, you'll probably want to create a patch file, if only because it's usually easier than documenting or explaining what edits need to be made.

With the ASIDownloadCache.inc file that the parser creates, one edit needs to be made to eliminate a duplicate identifier error when compiling.


Copyright 2011 by Phil Hess.

macpgmr (at) fastermac (dot) net

First posted Nov. 12, 2011; last edited May 9, 2013.