Главная » Хабрахабр » [Из песочницы] Работа Xamarin c SDK, написанном на C

[Из песочницы] Работа Xamarin c SDK, написанном на C

Не так давно у меня был интересный проект на Xamarin Forms для нескольких платформ:

  • Android
  • iOS
  • UWP
  • MacOS

Нам было необходимо создать библиотеку, которая смогла бы подключаться к нескольким нашим проектам: Xamarin.Forms, Android на Java, Cordova, а также позволять сторонним разработчикам использовать наше SDK в своих проектах с минимальными усилиями для интеграции.

Такое решение позволило нам иметь одну кодовую базу для SDK проекта и нам не пришлось дублировать библиотеку отдельно под разные платформы с возможными проблемами при переносе кода и дублировании тестов для покрытия и проверки кода. Командой было решено написать библиотеку на C и подключать ее к нашим проектам по мере необходимости.

В данной небольшой статье будет расписано как нам удалось это сделать, и возможно, кому-то это пригодится и позволит сэкономить время на проекте.
Для нашего Xamarin проекта мы сделали еще nuget пакет, который является оберткой над нашей C библиотекой и позволяет в одном месте вносить все необходимые изменения для расширения SDK, а также некоторым образом расширять сам SDK. Правда в итоге оказалось достаточно тяжело «подружить» библиотеку на C с разными платформами на Xamarin платформе.

Наш Xamarin проект включает в себя четыре платформы, каждая платформа имеет свои архитектуры и на каждой платформе нам необходимо собрать C библиотеку в своем собственном нативном формате файла.

Расширения нативных файлов

  • Android — *.so файл;
  • Universal Windows Platform (UWP) — *.dll файл;
  • iOS — *.a файл (файл статической библиотеки, которая по факту является fat файлом, в котором будут хранится все необходимые нам архитектуры);
  • MacOS — *.dylib файл (файл динамической библиотеки)

Возможные архитектуры на разных платформах

Android

  • arm
  • arm64
  • x86
  • x64

UWP
iOS

  • armv7
  • armv7s
  • i386
  • x86_64
  • arm64

MacOS
Нам необходимо собрать нативные файлы для нужных нам платформ и архитектур в режиме релиза.

Сборка и подготовка нативных файлов SDK

Universal Windows Platform (UWP)

Собираем проект на С для двух архитектур x86 и x64. После этого у нас будет два *.dll файла, которые нам и нужны.

Android

Чтобы создать нативные файлы для Android проекта. Нам нужно создать Xamarin С++ проект. Добавить наши С файлы и файлы с хедерами в Shared проект. После этого надо собрать проект со всеми необходимыми архитектурами (arm, arm64, x86, x64). Это даст нам *.so файлы для Android проекта.

iOS

Чтобы создать нативные файлы для iOS проекта мы могли бы использовать тот же Xamarin С++ проект, что мы использовали для Android, но тут есть нюанс. Нам необходимо соединиться с MacOS, чтобы собрать С++ проект. Но для этого нам необходимо установить vcremote на MacOS. Правда после последних обновлений это сделать сейчас просто невозможно. Может позже Microsoft обратит на это внимание и исправит его установку, но сейчас это к сожалению не так.

В XCode нам надо создать Cocos Touch Static Library проект для iOS. Из-за этого нам придется пойти другим путем. В этот проект мы добавляем наши файлы из C SDK и собираем проект два раза, чтобы получить нужный нам набор архитектур: Как это сделать, мы можем прочитать здесь.

  • for iphone simulator
  • for iphone

Затем мы можем проверить какие архитектуры включены в наши сборки статической библиотеки, используя команду терминала на 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

На MacOS все будет крайне просто. Нам надо переконвертировать файл статической библиотеки в динамическую, снова используя терминальную команду:

clang -fpic -shared -Wl, -all_load lib.a -o lib.dylib

И все. Мы получим нужный нам *.dylib файл.

Nuget пакет

Так как мы делали nuget пакет и в нем добавляли специфическую логику для Xamarin проекта, то нам необходимо было сделать враппер для C SDK. На C# для подключения C методов нам необходимо использовать атрибут DllImport. Но тут снова есть нюанс. Нам необходимо использовать const для пути нативного С файла. При этом у каждого проекта путь к файлу будет своим и даже название самого файла будет другое. Из-за этого нам пришлось немного изощриться и написать для этого свои обертки.

Итак, наш основной класс, который описывает методы С файла.

public abstract class BaseLibraryClass { public abstract int Init (IntPtr value);
}

Затем для каждой платформы нам необходимо имплементировать абстрактный класс.

Android

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);
}

Universal Windows Platform (UWP)

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);
}

iOS

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);
}

MacOS

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 }
}

Также нам нужен Init метод для настройки всего что мы создали внутри наших проектов на Xamarin.

public static class Init { public static PlatformArchitecture PlatformArchitecture { get; set; }
}

Подключение сгенерированных библиотек к проектам

Universal Windows Platform (UWP)

Мы копируем сгенерированные файлы библиотек в папки:

  • lib/x86/lib.dll
  • lib/x64/lib.dll

И устанавливаем при старте приложения в Init методе нашу архитектуру:

Wrapper.Init.PlatformArchitecture = Wrapper.Enums.PlatformArchitecture.X64;

Android

Для Android проекта нам необходимо поправить *.csproj файл, сохранить проект и скопировать *.so файлы в папки. В Android проекте, мы указываем название сгенерированного файла, так как пути к файлам мы прописываем в *.csproj файле. Также нам необходимо помнить следующее при копировании файлов в папки:

  • armeabi — arm *.so файл
  • armeabi-v7a — arm *.so файл
  • arm64-v8a — arm64 *.so файл
  • x86 — x86 *.so файл
  • x64 — x64 *.so файл

Изменения для *.csproj файла:

<ItemGroup> <AndroidNativeLibrary Include="lib\armeabi\lib.so"> <Abi>armeabi</Abi> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </AndroidNativeLibrary> <AndroidNativeLibrary Include="lib\armeabi-v7a\lib.so"> <Abi>armeabi-v7a</Abi> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </AndroidNativeLibrary> <AndroidNativeLibrary Include="lib\arm64-v8a\lib.so"> <Abi>arm64-v8a</Abi> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </AndroidNativeLibrary> <AndroidNativeLibrary Include="lib\x86\lib.so"> <Abi>x86</Abi> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </AndroidNativeLibrary> <AndroidNativeLibrary Include="lib\x86_64\lib.so"> <Abi>x86_64</Abi> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </AndroidNativeLibrary>
</ItemGroup>

И устанавливаем архитектуру для nuget пакета:

Wrapper.Init.PlatformArchitecture = Wrapper.Enums.PlatformArchitecture.Droid;

iOS

Нужно добавить сгенерированный *.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;

MacOS

Добавляем наш *.dylib файл в Native References проекта и прописываем нужную архитектуру:

Wrapper.Init.PlatformArchitecture = Wrapper.Enums.PlatformArchitecture.Mac;

После данных манипуляций проекты для всех наших платформ подхватили сгенерированные нативные файлы и мы смогли использовать все функции из нашего СДК внутри проекта.


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

[Из песочницы] Haiku β1 — сделаем /b/ OS великой снова

Совсем недавно (почти 4 месяца назад) вышла новая Haiku (далее — просто BeOS, ибо проект гораздо удачнее ReactOS — настолько, что разница между Haiku и BeOS уже пренебрежимо мала). Да и недавно прочитанный киберпанк-роман Александра Чубарьяна давал понять, что BeOS ...

Минкомсвязи одобрило законопроект об изоляции рунета

Министерство цифрового развития, связи и массовых коммуникаций РФ поддержало законопроект №608767-7 об автономной работе рунета, внесённый в Госдуму 14 декабря 2018 года. Об этом сегодня сообщил замглавы Минкомсвязи Олег Иванов в ходе расширенного заседания комитета Госдумы по информационной политике, информационным технологиям ...