- PVSM.RU - https://www.pvsm.ru -
Не так давно у меня был интересный проект на Xamarin Forms для нескольких платформ:
Нам было необходимо создать библиотеку, которая смогла бы подключаться к нескольким нашим проектам: Xamarin.Forms, Android на Java, Cordova, а также позволять сторонним разработчикам использовать наше SDK в своих проектах с минимальными усилиями для интеграции.
Командой было решено написать библиотеку на C и подключать ее к нашим проектам по мере необходимости. Такое решение позволило нам иметь одну кодовую базу для SDK проекта и нам не пришлось дублировать библиотеку отдельно под разные платформы с возможными проблемами при переносе кода и дублировании тестов для покрытия и проверки кода.
Правда в итоге оказалось достаточно тяжело «подружить» библиотеку на C с разными платформами на Xamarin платформе. В данной небольшой статье будет расписано как нам удалось это сделать, и возможно, кому-то это пригодится и позволит сэкономить время на проекте.
Для нашего Xamarin проекта мы сделали еще nuget пакет, который является оберткой над нашей C библиотекой и позволяет в одном месте вносить все необходимые изменения для расширения SDK, а также некоторым образом расширять сам SDK.
Наш Xamarin проект включает в себя четыре платформы, каждая платформа имеет свои архитектуры и на каждой платформе нам необходимо собрать C библиотеку в своем собственном нативном формате файла.
Android
UWP
iOS
MacOS
Нам необходимо собрать нативные файлы для нужных нам платформ и архитектур в режиме релиза.
Собираем проект на С для двух архитектур x86 и x64. После этого у нас будет два *.dll файла, которые нам и нужны.
Чтобы создать нативные файлы для Android проекта. Нам нужно создать Xamarin С++ проект. Добавить наши С файлы и файлы с хедерами в Shared проект. После этого надо собрать проект со всеми необходимыми архитектурами (arm, arm64, x86, x64). Это даст нам *.so файлы для Android проекта.
Чтобы создать нативные файлы для iOS проекта мы могли бы использовать тот же Xamarin С++ проект, что мы использовали для Android, но тут есть нюанс. Нам необходимо соединиться с MacOS, чтобы собрать С++ проект. Но для этого нам необходимо установить vcremote на MacOS. Правда после последних обновлений это сделать сейчас просто невозможно. Может позже Microsoft обратит на это внимание и исправит его установку, но сейчас это к сожалению не так.
Из-за этого нам придется пойти другим путем. В XCode нам надо создать Cocos Touch Static Library проект для iOS. Как это сделать, мы можем прочитать здесь [1]. В этот проект мы добавляем наши файлы из C SDK и собираем проект два раза, чтобы получить нужный нам набор архитектур:
Затем мы можем проверить какие архитектуры включены в наши сборки статической библиотеки, используя команду терминала на MacOS — «lipo». К примеру можем сделать такой вызов:
lipo -info /path_to_your_a_file/lib.a
Результат должен быть таким:
Architectures in the fat file: /path_to_your_a_file/lib.a are : armv7 armv7s i386 x86_64 arm6
После того как мы приготовили файлы статической библиотеки, мы можем их соединить в один fat файл, со списком всех архитектур в одном файле, снова используя терминальную команду:
lipo -create lib_iphone.a lib_iphone_simulator.a -output lib.a
На MacOS все будет крайне просто. Нам надо переконвертировать файл статической библиотеки в динамическую, снова используя терминальную команду:
clang -fpic -shared -Wl, -all_load lib.a -o lib.dylib
И все. Мы получим нужный нам *.dylib файл.
Так как мы делали nuget пакет и в нем добавляли специфическую логику для Xamarin проекта, то нам необходимо было сделать враппер для C SDK. На C# для подключения C методов нам необходимо использовать атрибут DllImport. Но тут снова есть нюанс. Нам необходимо использовать const для пути нативного С файла. При этом у каждого проекта путь к файлу будет своим и даже название самого файла будет другое. Из-за этого нам пришлось немного изощриться и написать для этого свои обертки.
Итак, наш основной класс, который описывает методы С файла.
public abstract class BaseLibraryClass {
public abstract int Init (IntPtr value);
}
Затем для каждой платформы нам необходимо имплементировать абстрактный класс.
internal class BaseLibraryClassDroid : BaseLibraryClass {
private const string Path = "lib";
[DllImport (Path, EntryPoint = "Init", CallingConvention = CallingConvention.Cdecl)]
private static extern int InitExtern (IntPtr value);
public override int Init (IntPtr value) => InitExtern (value);
}
internal class BaseLibraryClassx64 : BaseLibraryClass {
private const string Path = "lib/x64/lib";
[DllImport (Path, EntryPoint = "Init", CallingConvention = CallingConvention.Cdecl)]
private static extern int InitExtern (IntPtr value);
public override int Init (IntPtr value) => InitExtern (value);
}
internal class BaseLibraryClassx86 : BaseLibraryClass {
private const string Path = "lib/x86/lib";
[DllImport (Path, EntryPoint = "Init", CallingConvention = CallingConvention.Cdecl)]
private static extern int InitExtern (IntPtr value);
public override int Init (IntPtr value) => InitExtern (value);
}
internal class BaseLibraryClassIOS : BaseLibraryClass {
private const string Path = "__Internal";
[DllImport (Path, EntryPoint = "Init", CallingConvention = CallingConvention.Cdecl)]
private static extern int InitExtern (IntPtr value);
public override int Init (IntPtr value) => InitExtern (value);
}
public class BaseLibraryClassMac : BaseLibraryClass {
private const string Path = "lib";
[DllImport (Path, EntryPoint = "Init", CallingConvention = CallingConvention.Cdecl)]
private static extern int InitExtern (IntPtr value);
public override int Init (IntPtr value) => InitExtern (value);
}
Теперь нам необходимо сделать enum файл со списком платформ / архитектур:
public enum PlatformArchitecture {
Undefined,
X86,
X64,
Droid,
Ios,
Mac
}
И фабрику для использования внутри нашего враппера:
public class SdkCoreFactory {
public static BaseLibraryClass GetCoreSdkImp () {
switch (Init.PlatformArchitecture) {
case PlatformArchitecture.Undefined:
throw new BaseLibraryClassInitializationException ();
case PlatformArchitecture.X86:
return new BaseLibraryClassx86 ();
case PlatformArchitecture.X64:
return new BaseLibraryClassx64 ();
case PlatformArchitecture.Droid:
return new BaseLibraryClassDroid ();
case PlatformArchitecture.Ios:
return new BaseLibraryClassIOS ();
case PlatformArchitecture.Mac:
return new BaseLibraryClassMac ();
default:
throw new BaseLibraryClassInitializationException ();
}
}
}
Также нам нужен Init метод для настройки всего что мы создали внутри наших проектов на Xamarin.
public static class Init {
public static PlatformArchitecture PlatformArchitecture { get; set; }
}
Мы копируем сгенерированные файлы библиотек в папки:
И устанавливаем при старте приложения в Init методе нашу архитектуру:
Wrapper.Init.PlatformArchitecture = Wrapper.Enums.PlatformArchitecture.X64;
Для Android проекта нам необходимо поправить *.csproj файл, сохранить проект и скопировать *.so файлы в папки. В Android проекте, мы указываем название сгенерированного файла, так как пути к файлам мы прописываем в *.csproj файле. Также нам необходимо помнить следующее при копировании файлов в папки:
Изменения для *.csproj файла:
<ItemGroup>
<AndroidNativeLibrary Include="libarmeabilib.so">
<Abi>armeabi</Abi>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</AndroidNativeLibrary>
<AndroidNativeLibrary Include="libarmeabi-v7alib.so">
<Abi>armeabi-v7a</Abi>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</AndroidNativeLibrary>
<AndroidNativeLibrary Include="libarm64-v8alib.so">
<Abi>arm64-v8a</Abi>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</AndroidNativeLibrary>
<AndroidNativeLibrary Include="libx86lib.so">
<Abi>x86</Abi>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</AndroidNativeLibrary>
<AndroidNativeLibrary Include="libx86_64lib.so">
<Abi>x86_64</Abi>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</AndroidNativeLibrary>
</ItemGroup>
И устанавливаем архитектуру для nuget пакета:
Wrapper.Init.PlatformArchitecture = Wrapper.Enums.PlatformArchitecture.Droid;
Нужно добавить сгенерированный *.a fat файл в корневую папку проекта и установить дополнительные инструкции при компилировании проекта (iOS properties => iOS build => Additional mtouch arguments). Устанавливаем следующие инструкции:
-gcc_flags "-L${ProjectDir} -llib -force_load ${ProjectDir}/lib.a"
Также не забываем в свойствах к *.a файлу указать Build Action как None.
И снова устанавливаем архитектуру для nuget пакета:
Wrapper.Init.PlatformArchitecture = Wrapper.Enums.PlatformArchitecture.Ios;
Добавляем наш *.dylib файл в Native References проекта и прописываем нужную архитектуру:
Wrapper.Init.PlatformArchitecture = Wrapper.Enums.PlatformArchitecture.Mac;
После данных манипуляций проекты для всех наших платформ подхватили сгенерированные нативные файлы и мы смогли использовать все функции из нашего СДК внутри проекта.
Автор: Sergey
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/xamarin/304318
Ссылки в тексте:
[1] Как это сделать, мы можем прочитать здесь: https://mobileprogrammerblog.wordpress.com/2017/09/17/objective-c-library-binding-for-xamarin-ios/
[2] Источник: https://habr.com/post/435210/?utm_source=habrahabr&utm_medium=rss&utm_campaign=435210
Нажмите здесь для печати.