Developing with Objective Pascal

Part 10: Creating a Multi-Architecture Static Library (Pascal)


Contents

Introduction
Requirements
Creating the Library Project
Wrapping the Pascal Class
Wrapping the Pascal Methods and Properties
Wrapping Tips
Testing the Wrapper Class
Notes


Introduction

Part 8 describes how to compile Objective C classes into a static library that you can use in an Objective Pascal app for iOS. These notes describe how to create an Objective C wrapper for a Pascal class and compile it into a static library for use in an Objective C app for iOS.

When would it be appropriate to use Pascal in an Objective C app? Several typical scenarios suggest themselves:


Requirements


Creating the Library Project

Start by creating a project in Xcode 4 using the Objective Pascal project template for a Cocoa Touch Static Library. (If you don't see Cocoa Touch Static Library on the Objective Pascal tab for iOS, then you don't have the templates installed correctly.)

As an example, we'll wrap the Pascal TStringList class, so enter the following:

Once you've saved your project, take a look at what the template has created for you:


Wrapping the Pascal Class

Your wrapper class needs to declare and create an instance of your Pascal class. This object is what the Pascal code will operate on. (Note: This technique is sometimes referred to as object composition.) In our example, declare and create a TStringList object. Your code should look like this, where the additions you need to make are in bold. Note that you can edit the Pascal file in an external text editor such as TextWrangler that provides Pascal syntax highlighting.

unit PasStringListU;

{$modeswitch ObjectiveC1}

interface

uses
  iPhoneAll, Classes;
  
type
  PasStringList = objcclass(NSObject)
  private
    FStrList : TStringList;
  public
    function init : id; override;  
    procedure dealloc; override;
  end;
  

implementation

function PasStringList.init : id;
begin
  Result := inherited init;
  if Assigned(Result) then
    begin
    FStrList := TStringList.Create;
    end;
end;

procedure PasStringList.dealloc;
begin
  FStrList.Free;
  inherited dealloc;
end;

end.

Wrapping the Pascal Methods and Properties

For each method or property in your Pascal class that you want to be able to use in an ObjC app, perform the following steps:

  1. Add a method declaration to the ObjC .h file.

  2. Copy this method declaration to the ObjC .m file and add a dummy code block to it.

  3. Add the equivalent Pascal method to the .pas file's interface and implementation sections, then implement the method.
In our example library, we'll wrap the TStringList Add, Count and Strings members, as follows:

  1. Add method declarations to the PasStringList.h header file. For help with ObjC class and method syntax, see Apple's introduction.
    @interface PasStringList : NSObject
    - (NSInteger) add:(NSString *)s;
    - (NSInteger) count;
    - (NSString *) strings:(NSInteger)index;
    @end
    
  2. Add methods to the PasStringList.m source file. For each method, add a code block and return a dummy value to eliminate Xcode warnings. It doesn't matter what value you return since the ObjC code will not be used.
    - (NSInteger) add:(NSString *)s;
    {
        return 0;
    }
    
    - (NSInteger) count;
    {
        return 0;
    }
    
    - (NSString *) strings:(NSInteger)index;
    {
        return NULL;
    }
    
  3. Add methods to the PasStringListU.pas file. First declare the methods in the PasStringList wrapper class:
        function add(s: NSString) : NSInteger; message 'add:';
        function count : NSInteger; message 'count';
        function strings(index : NSInteger) : NSString; message 'strings:';
    
    Then implement the methods:
    function PasStringList.add(s: NSString) : NSInteger;
    begin
      Result := FStrList.Add(s.UTF8String);
    end;
    
    function PasStringList.count : NSInteger;
    begin
      Result := FStrList.Count;
    end;
    
    function PasStringList.strings(index : NSInteger) : NSString;
    begin
      Result := NSString.stringWithUTF8String(PChar(FStrList.Strings[index]));
    end;
    

Wrapping Tips

  1. The example's methods do not include any error checking or debugging code. Normally you'll want to include that sort of code while developing your wrapper class and conditionally exclude it only when compiling a final version of your library. Also, it's probably a good idea to debug your Pascal class fully before you attempt to wrap it - then you'll only have to deal with errors in the wrapper code, not in the wrapped class as well.

  2. The example is a contrived one since normally you would never wrap a runtime library class like this. Instead, in your ObjC app you would use an equivalent iOS framework class (for example, NSArray or NSMutableArray).

  3. To help decide what types to pass with your wrapper methods, consult the Foundation framework's data types and classes. For example, many of the TStringList methods and properties take a signed integer parameter. NSInteger looks like a good candidate to use. With iOS, NSInteger is declared as the C "int" type (see NSObjCRuntime.h or NSObjCRuntime.inc). Checking the FPC ctypes.pp file, we see that "cint" is the same as "cint32" which is the same as the Pascal "longint" (and "Integer") type.

  4. In converting to and from NSString, the Pascal code implicitly assumes that TStringList works with AnsiString, which is currently the case with Free Pascal 2.6. However, if a future version of FPC changes the TStringList string type to UnicodeString, this code won't work. For more flexibility in your code, you can use the NSHelpers unit, which works with a variety of Pascal string types. For this example, you could add NSHelpers to the uses statement and then make these substitutions:

    In add: Result := FStrList.Add(NSStrToStr(s));

    In strings: Result := StrToNSStr(FStrList.Strings[index]);

  5. The message string that follows each Objective Pascal method declaration is composed by concatenating the Objective C method signature keywords, including any colons. The number of colons in the message string is equal to the number of parameters passed to the method.

Testing the Wrapper Class

Before testing the wrapper class, make sure you've done a Product | Clean and Product | Build for both platforms to ensure that all architectures reflect the same source code. Be sure to do this after making any changes to the library source too.

To see what architectures are contained in the library, change to the StringList project folder and use the file command:

  file libStringList.a
  libStringList.a: Mach-O universal binary with 3 architectures
  libStringList.a (for architecture armv6):	current ar archive random library
  libStringList.a (for architecture armv7):	current ar archive random library
  libStringList.a (for architecture i386):	current ar archive random library
Now create a simple Objective C app for iOS. For example, select the Empty Application project template and enter the following:

Once you've saved the new project, do the following:

Now select the Simulator scheme and choose Product | Run to compile and run the app. If you don't see the debug window, choose View | Debug Area | Show Debug Area. You should now see debug output that looks like this:

  2011-12-28 18:28:22.296 TestStringList[3108:207] 2
  2011-12-28 18:28:22.301 TestStringList[3108:207] string 1
  2011-12-28 18:28:22.302 TestStringList[3108:207] string 2
To complete your testing, connect an iOS device, then compile and run the app there too.


Notes

  1. How does all this work? If you look in the library project's main Pascal file (under Supporting Files), you'll see that it's actually a library, not a program. Normally the library keyword is used for creating dynamic libraries (.dylib), not static libraries (.a), but in this case we're just using it as a way to "collect" all of the dependent object files required by the wrapper class.

    If you look at the library project's FPC_COMMON build setting, you'll see that it includes the -Cn switch, which causes the compiler to skip the link stage, leaving behind file link.res, which includes a list of object files that the linker would normally link into your binary. Furthermore, if you look at the project's run script, you'll see that this list of object files is extracted from link.res and fed to libtool, which creates the archive (.a) for a single architecture. When compiling for an iOS device, libtool also combines both the armv6 and armv7 archives into a two-architecture archive.

    The run script's final step is to check that both platforms' archives exist and, if so, combines them into a three-architecture archive in your library project's folder.

  2. If you open link.res in a text editor, you'll see that the list of object files is a long one. As a result, the archive file can be quite large since it includes all of these .o files for three different architectures and at least a couple of these files (system.o, sysutils.o) are fairly large. However, when Xcode compiles an ObjC app for an iOS device, it includes the -dead_strip switch, which causes the linker to remove unused functions and data, thus reducing the size of the app's binary considerably.

  3. If you inspect the build log after compiling for an iOS device, you may see one or more warnings like this:

    ld: warning: 32-bit absolute address out of range
    
    This is the result of an apparently harmless linker bug that is discussed here.

  4. You can put multiple wrapper classes in the same library. With the iOS system frameworks, the convention appears to be to define only one class per .h file and you may want to adhere to that convention as well. If so, just add a set of .h/.m/.pas files for each class, then add the class's Pascal unit to the uses statement in the library project's main file (under Supporting Files).

  5. If you need to distribute your library for someone else to use, be sure to include both the archive file (for example, libStringList.a) and its header file(s) (for example, StringList.h).


Copyright 2011 by Phil Hess.

macpgmr (at) fastermac (dot) net

First posted Dec. 28, 2011; last edited May 9, 2013.