Categories
Programmierung

C/C++-Bibliotheken aus Xamarin-Code aufrufen

Mit Hilfe der Xamarin-Plattform lassen sich iOS- und Android-Anwendungen vollständig in C# entwickeln. Es gibt jedoch Fälle, in denen existierender Code zu groß oder komplex ist, um eine Portierung nach C# sinnvoll werden zu lassen. Die meisten Beispiele, die man online findet, zeigen, wie man existierenden Objective-C-Code in Xamarin.iOS und existierenden Java-Code in Xamarin.Android einbindet, obwohl es durchaus möglich ist, C-Code aus einer Xamarin-App aufzurufen. Seit kurzem unterstützt Microsofts C/C++-Projekte für iOS und Android in Visual Studio, wodruch diese Aufgabe deutlich einfacher wird.

Die einzubindende-Bibliothek muss im Quelltext vorliegen, um sie für iOS und Android zu kompilieren. Der Code darf keine Bibliotheken verwenden, die nur in Binärform für eine andere Plattform kompiliert vorliegen.

Als erstes sollte ein neues “C++ Cross Platform Project” mit Visual Studio 2015 erzeugt werden.
Neues Cross-platform-Projekt erzeugen
Hierzu muss das passende Visual-Studio-Paket bei der Installation ausgewählt worden sein.
Visual Studio Setup Cross-Platform Mobile C++
Hierdurch werden drei Projekte erzeugt: Eins für Android, eins für iOS und ein Shared Project.
Projektstruktur

Hierzu muss man das Shared-Project-Konzept verstanden haben. Der Code im Shared Project wird, anders als andere Visual-Studio-Projekte, nicht in eine DLL oder Bibliothek übersetzt. Ein Shared Project kann jede Art Datei beinhalten. Projekte, die das Shared Project referenzieren, können auf den Code in jeder Datei im Shared Project zugreifen. Wird Code nicht referenziert, wird er auch nicht kompiliert. Aus diesem Grund ist es notwendig, Aufrufe in den geteilten Code in die plattformspezifischen Projekte aufzunehmen. Dies heißt, dass auch die plattformspezifischen Projekt Code enthalten müssen. Der Code kann für Android und iOS identisch sein und aus der jeweils gleichen Datei stammen. Im Gegensatz zu .NET wird hierfür kein File-Linking benötigt, da der Code nicht Projektverzeichnis oder -unterverzeichnis liegen muss.

Im Beispiel habe ich eine einfache Funktion clib_add_internal() erstellt, die zwei Integers addiert und im Shared Project liegt.


Dieser Code wird aus einer Funktion in den plattformspezifischen Projekten aufgerufen.

Das ergibt für das Beispielprojekt eigentlich keinen Sinn. In einem echten Projekt würden man den Code, der aus dem .NET-Code aufgerufen werden soll (das Interface) in die plattformspezifischen Projekte nehmen und den darunterliegenden Code in das Shared Project.

Die Aufrufe aus .NET heraus erfolgen mit Hilfe von P/Invoke, da C++/CLI für iOS und Android nicht verfügbar ist. Daher müssen auch die übrlichen Regeln für P/Invoke für den Interface-Code eingehalten werden: Entweder ist der Code reiner C-Code oder, wenn C++ verwendet wird, sind die Aufrufe entweder einfache Funktionen oder statische Methoden und sie beinhalten nur POC-Parameter und Rückgabewerte.

Für das iOS-Projekt muss das Outgabeformat in statische Bibliothek (.a-Datei) geändert werden, da iOS kein dynamisches Nachladen von Code erlaubt. Für Android sollte eine dynamische Bibliothek (.so) erstellt werden.

iOS-Einstellungen
Android-Einstellungen

Das Android-Projekt lässt sich komplett in Visual Studio kompilieren. Für iOS wird jedoch eine Verbindung zu einem Mac-Build-Agent benötigt. Dieser ist nicht der aus der normalen Xamarin.iOS-Installation sondern ein eigenes Microsoft-Tool, das unter OS X mit Hilfe von npm installiert wird. Visual Studio kommuniziert mit dem Tool über TCP/IP.

Verbindung zum Build Host

Um den nativen Code aufzurufen sollte ein Xamarin-Projekt für je iOS und Android erstellt werden. Die nativen Bibliotheken sollten in die Xamarin-Projekte eingefügt werden.

Das iOS lässt sich mit dem Plattform-Dropdown für unterschiedliche Prozessorarchitekturen kompilieren (um z.B. iPhones vor dem iPhone 5s zu unterstützen, oder auch den iOS-Simulator). Mit Hilfe des Tools lipo lassen sich die Bibliotheken für die unterschiedlichen Plattformen zu einer Bibliothek vereinen (siehe Xamarin-Dokumentation). Unter Android muss die .csproj-Datei von Hand angepasst werden, um die unterschiedlichen Plattformen anzugeben (siehe Xamarin-Dokumentation).

Die Aufrufe in den nativen sind normale P/Invoke-Aufrufe (obwohl DLLs hier nicht im Spiel sind). Unter Android muss der Name der Shared Library spezifiziert werden.

Unter iOS muss der Bibliotheksname __Internal angegeben werden, um einen Aufruf in die statisch Bibliothek zu machen.

Und das wars! Der Beispielcode liegt unter https://github.com/lothrop/XamarinNative. Im Beispiel habe ich den C#-Marshaling-Code in ein weiteres Shared Project gelegt, das jeweils vom Xamarin.iOS- und vom Xamarin.Android-Project eingebunden wird.