A basic, but useful primer on cross compiling native C Source for the iPhone platform and interopping with those libraries in MonoTouch.
Initial tutorial by Jim Bentley, Portland ME - james.bentley@live.com
1. Basic walk-through on how to compile Native C libraries from Open Source projects to use with the iPhone Arm6 processor.
2. Example on how to interop with those libraries using Monotouch, plus a few tips on how to identify the functions in the libraries. I will demonstrate this by writing a basic wrapper for a light-weight Text-to-Speech (TTS) library.
By no means is this an indepth or high level discussion on this subject. This is merely an example of one OpenSource project I managed to compile for the Arm6 iPhone processor. I have no reason to believe it would not be successful in other projects, but I'm certain that not all C and C++ projects are created equal.
Theoretically, if a project is programmed well in C or C++, it should work and compile for the Arm6 processor. Note above "If a project is programmed well," and also note that this is GREATLY simplifying what has to happen in the original developer's build and programming to have a project to be able to universally cross-compile above. This won't always be the case.
However, for the most part, many good OpenSource C and C++ projects shouldn't have too great a difficulty being cross compiled for the iPhone platform. Often times, you'll find that the project even has a 'lite' version specifically designed for mobile processors, with handy stuff like power reduction optimizations built in.
Posted below is a very handy script I found to do the heavy-lifting for me. The script uses GCC Make to do its work. You will need to edit a few lines to address the version of GCC and G++ you want to use in the configure strings below, as well as what version of the iPhone SDK you want to use in the compile.
#!/bin/sh
# build_fat.sh
#
# Created by Robert Carlsen on 15.07.2009.
# build an arm / i686 lib of standard linux project
#
# adopted from: http://latenitesoft.blogspot.com/200...ding-unix.html
#
# initially configured for tesseract-ocr
# Set up the target lib file / path
# easiest to just build the package normally first and watch where the files are created.
LIBFILE=ccmain/libtesseract_full
# Select the desired iPhone SDK
export DEVROOT=/Developer/Platforms/iPhoneOS.platform/Developer
export SDKROOT=$DEVROOT/SDKs/iPhoneOS3.0.sdk
# Set up relevant environment variables
export CPPFLAGS="-I$SDKROOT/usr/lib/gcc/arm-apple-darwin9/4.0.1/include/ -I$SDKROOT/usr/include/"
export CFLAGS="$CPPFLAGS -arch armv6 -pipe -no-cpp-precomp -isysroot $SDKROOT"
export CPP="$DEVROOT/usr/bin/cpp $CPPFLAGS"
export CXXFLAGS="$CFLAGS"
# Dynamic library location generated by the Unix package
LIBPATH=$LIBFILE.dylib
LIBNAME=`basename $LIBPATH`
export LDFLAGS="-L$SDKROOT/usr/lib/ -Wl,-dylib_install_name,@executable_path/$LIBNAME"
# Static library that will be generated for ARM
LIBPATH_static=$LIBFILE.a
LIBNAME_static=`basename $LIBPATH_static`
# TODO: add custom flags as necessary for package
./configure CXX=$DEVROOT/usr/bin/arm-apple-darwin9-g++-4.0.1 CC=$DEVROOT/usr/bin/arm-apple-darwin9-gcc-4.0.1 LD=$DEVROOT/usr/bin/ld --host=arm-apple-darwin
make -j4
# Copy the ARM library to a temporary location
mkdir -p lnsout
cp $LIBPATH_static lnsout/$LIBNAME_static.arm
# Do it all again for native cpu
make distclean
# Restore default environment variables
unset CPPFLAGS CFLAGS CPP LDFLAGS CXXFLAGS
# Overwrite LDFLAGS
# Dynamic linking, relative to executable_path
# Use otool -D to check the install name
export LDFLAGS="-Wl,-dylib_install_name,@executable_path/$LIBNAME"
# TODO: error checking
./configure
make -j4
# Copy the native library to the temporary location
cp $LIBPATH_static lnsout/$LIBNAME_static.i386
# Create fat lib by combining the two versions
#$DEVROOT/usr/bin/lipo -arch arm lnsout/$LIBNAME_static.arm -arch i386 lnsout/$LIBNAME_static.i386 -create #-output lnsout/$LIBNAME_static
Link to Script's Original site:
- It compiles libraries for both the i386 platform (which you'll need, if you want to test your applications inside the simulator) as well as the Arm6 platform.
- The script should be executed in the base directory of the entire OpenSource project you're wanting to work with. It will output the compiled libraries into a folder that the project itself will dictate. In this case, it is the output directory "Build"
- The libraries the script makes are static libraries, which are required, because Apple does not allow the use of dynamic libraries on the iPhone.
- This is the line that actually tells the platform to prepare the library to be compiled for the Arm proccessor for your information, if you want to understand how to do this without the script:
./configure CXX=$DEVROOT/usr/bin/arm-apple-darwin9-g++-4.0.1 CC=$DEVROOT/usr/bin/arm-apple-darwin9-gcc-4.0.1 LD=$DEVROOT/usr/bin/ld --host=arm-apple-darwin
- I have commented out the final line of the script because I wanted two seperate types of libraries (Arm and i386), and this script actually creates a "Fat" universal library that has both the Arm and i386 operating instructions in it. Since the iPhone has a 10 MB limit for applications downloaded on the 'Cell' networks, I felt it important to keep the libraries going on the phone as slim as possible.
For the purpose of this tutorial, the C libraries we're going to work with are part of the the Flite lightweight text-to-speech project from Carnegie Mellon University, which can be found for download Here. You will find that a great many of the Text to Speech and Speech to Text projects are by universities, and were funded under DARPA, the same grant that funded the overall Internet.
Download this project, and extract it using stuffit (or whatever) into a directory on your mac. I used /test/speech as the directory, which is what we'll use the rest of the tutorial.
Copy the script from above into this directory, using vi or whatever editor you choose, and put it into a new shell script file called build_fat.sh.
You should have a directory tree that looks like so in finder:

Open a terminal session, SU and then navigate to the /test/speech/flite-1.3-release directory, and then type ./build_fat.sh
You will get a bunch of gcc make gibberish as the project compiles for several minutes - don't panic if it seems you get lots of errors. If your script is successful in compiling your libraries, you should now have the following build folder and i386 and arm sub-trees under /test/speech/flite-1.3-release:

If you have these files, you have successfully cross-compiled the Flite Text to Speech project! You should now move to the second portion of the tutorial on how to interop with the libraries and call them from C#.
To interop with the Flite libraries, you'll need to add the four libraries below to your project that Flite requires to run:

This is the Computerized Voice library, and the default person "Kal" that compiles with the project. This is the library that contains the acoustic variables that make human-speech-like-sounds. What this means is you get a different gender, tenor, sound, etc from each voice library you have. This is where voice gets super complex, and strays outside of the scope of this document. Suffice to say, you at least get a voice library that you can use out of the box, so to speak.
NOTE: There are two seperate 'Kal files - one is marked kal16.a and one is just kal.a
Either of these files will work, but the 16 bit file is higher quality. It just depends on how much space vs quality you need in your voice.
This is the lexicon file that your project will need to call in order for the Text-to-Speech functions to have a digital copy of "how stuff should sound," sort of like phonetics. The lexicon is essentially a dictionary of words, and numbers, so the voice knows to interpret stuffm like saying "one thousand" instead of "1, 0, 0, 0" for instance, and also has a basic idea of how to say a lot of basic words well.
This is the language file that your project will need to call. You can generate various other language files, and there are some available on the flite website.
This is the main executable library file that you use to call everything else. It contains all of the functions you'll need to call but one.
When you add the libraries, select Copy so it'll add them to your project folder and not just link them:

If everything is looking good so far, you'll have a project that looks like so:

Also, you need to add these libraries to your GCC flags for your project. This is important, because this is where your code is told where to find these libraries.
This is the GCC string you'll need to be able to use the libraries:
-gcc_flags "-L${ProjectDir} -lflite_cmu_us_kal16 -lflite_usenglish -lflite_cmulex -lflite -all_load"
Go to Project, ProjectName Options, and then click on iPhone Build. Set the options for whatever configuration you're wanting to compile on. This is a good time to double check WHICH LIBRARIES you're using, the i386 libraries, or the ARM libraries. Make sure you've got it all right in your project. If you do, the project configuration portion of this is complete.

This is the class (with a bunch of comments, some redundant) that I wrote as a wrapper for the DLL.
It's dirty, but you'll get the idea.
using System;
using System.Runtime.InteropServices;
namespace VoiceTest123
{
// You should to create a new class to deal with wrapping each native library you need to call // functions from,
// because you'll ultimately want to have a classname.functionname way of calling the library.
public class TextToWave
{
// Dllimport function from System.Runtime.InteropServices
// For this application, you import the DLL through GCC flags and call it via
// [Dllimport "__Internal")]. This is because
// Monotouch statically compiles everything, so you're actually declaring the function whose body // is INSIDE the Native Library (after compiliation)
// This first function is in libflite.a, the compiled C library you're linking up with.
// Research Cross Compiling to find out how to convert C and C++ projects to the iPhone Arm6 // processor.
// Any native library must be compiled into a .a extension for the Arm6 processor on the iPhone:
// HOWEVER!!! NOTE: When you use the iPhone simluator to debug this, you must use a library that // was compiled for
// the 1386 processor in order for it to work.
// This is where I learned that the iPhone simulator is NOT an iPhone emulator.
// This first DllImport deals with the first function called in libflite.a
[DllImport ("__Internal")]
// The flite_init function of the library must be called before any other function in the // library towork.
// This is to allow room to for the flite devs build in future functionality, and right now // does nothing more than allow the rest of the functions to be called after its activation.
static extern void flite_init ();
public void fliteInitFunc() {
flite_init();
}
// You must do a new DllImport for each function you link up inside your class to the C // libraries. This second link creates an IntPtr to the library where the "Computerized // Voice Object" lives.
[DllImport ("__Internal")]
// Esentially, for each one of these voice libraries you have, you'll have a different // "person's" voice.
// This is how you can modularly change the voice sound, clarity, gender, age, etc, of the // voice.
// The register_cmu_us_kal function is a function in the c library - any of these voices will // have a register_cmu_country_name function will exist in any properly made voice library // for flite.
static extern IntPtr register_cmu_us_kal();
// Third Dllimport to link flite_text_to_speech
[DllImport ("__Internal")]
// Actual C Function in libflite.a that you're calling:
// float flite_text_to_speech(const char *text, cst_voice *voice, const char *outtype);
// The three arguments this function takes are: The string you're outputting to voice,
// the voice library you've declared above for use, and the output type you want.
// There are three out-types, but I'm only certain that one works for the iPhone at this // time.
// You can set the out-type to play directly to the device using the argument "play" (Proven // not working 'out of the box"
// you can set it to write to the file system by providing a path string )This works, and is // how I call it later to be replayed- just record the .wav file, and then play it using the // Mono-touch API.
// Or you you can set it to none. Not sure why you'd need that, but you have the option.
// I have only tested this with output to the filesystem
static extern void flite_text_to_speech (string arg1, IntPtr register_cmu_us_kal, string _ arg2);
// The function to call all the functionalties above to output a text string to speech:
public void ConvertTextToWav(string arg3, string arg4) {
// Call first required init function to allow to call the rest
fliteInitFunc();
// Call Flite Text to Speech function
flite_text_to_speech (arg3, register_cmu_us_kal(), arg4);
}
}
}
Here's an example Button event delgate that can call your output -
button.TouchUpInside += delegate {
string textToSpeak = textField.Text;
var basedir = Path.Combine (Environment.GetFolderPath) (Environment.SpecialFolder.Personal), "..");
var tmpdir = Path.Combine(basedir, "tmp");
string audioFilePath = Path.Combine(tmpdir, "foo.wav");
_speaker.ConvertTextToWav(textToSpeak, audioFilePath);
AVAudioPlayer audioPlayer = new _ AVAudioPlayer(NSUrl.FromFilename(audioFilePath), null);
audioPlayer.PrepareToPlay();
audioPlayer.Play();
};