منذ وقت ليس ببعيد كان لدي مشروع مثير للاهتمام على نماذج Xamarin لعدة منصات:
نحتاج إلى إنشاء مكتبة يمكنها الاتصال بالعديد من مشاريعنا: Xamarin.Forms و Android في Java و Cordova ، وكذلك السماح لمطوري الطرف الثالث باستخدام SDK في مشاريعهم بأقل جهد ممكن للتكامل.
قرر الفريق كتابة مكتبة بلغة C وربطها بمشاريعنا حسب الحاجة. سمح لنا هذا الحل بأن يكون لدينا قاعدة رمز واحدة لمشروع SDK ولم يكن علينا تكرار المكتبة بشكل منفصل لأنظمة تشغيل مختلفة ذات مشاكل محتملة عند ترقية الكود وتكرار الاختبارات لتغطية الكود والتحقق منه.
صحيح ، في النهاية تبين أنه من الصعب للغاية "تكوين صداقات" مكتبة C مع منصات مختلفة على منصة Xamarin. سوف تصف هذه المقالة القصيرة كيف تمكنا من القيام بذلك ، وربما ستكون مفيدة لشخص ما وستوفر الوقت في المشروع.
بالنسبة لمشروع Xamarin الخاص بنا ، قمنا بإعداد حزمة nuget أخرى ، وهي عبارة عن غلاف على مكتبة C الخاصة بنا وتسمح لك بإجراء جميع التغييرات اللازمة في مكان واحد لتوسيع SDK ، وكذلك توسيع SDK نفسه بطريقة ما.
يشتمل مشروع Xamarin الخاص بنا على أربع منصات ، ولكل منصة بنية خاصة بها ، وعلى كل منصة نحتاج إلى إنشاء مكتبة C بتنسيق ملفها الأصلي.
امتدادات الملفات الأصلية
- Android - * .so ملف ؛
- نظام Windows الأساسي العالمي (UWP) - ملف * .dll ؛
- ملف iOS - * .a (ملف مكتبة ثابت ، وهو في الواقع ملف سمين ، والذي سيخزن جميع الهياكل التي نحتاجها) ؛
- MacOS - * .dylib ملف (ملف مكتبة ديناميكي)
البنى الممكنة على منصات مختلفة
أندرويد- الذراع
- arm64
- إلى x86
- إلى x64
UWPiOS- armv7
- armv7s
- i386
- x86_64
- arm64
ماكنحن بحاجة إلى جمع الملفات الأصلية للأنظمة الأساسية والبنية التي نحتاجها في وضع الإصدار.
بناء وإعداد ملفات SDK الأصلي
النظام الأساسي العالمي لنظام Windows (UWP)
نحن نبني مشروع C لبنيان x86 و x64. بعد ذلك ، سيكون لدينا ملفان * .dll نحتاجهما.
أندرويد
لإنشاء ملفات أصلية لمشروع أندرويد. نحتاج إلى إنشاء مشروع Xamarin C ++. أضف ملفات C وملفات الرأس الخاصة بنا إلى المشروع المشترك. بعد ذلك ، تحتاج إلى تجميع مشروع مع جميع الهياكل اللازمة (arm ، arm64 ، x86 ، x64). سيعطينا هذا ملفات * .so لمشروع Android.
iOS
لإنشاء ملفات أصلية لمشروع iOS ، يمكننا استخدام نفس مشروع Xamarin C ++ الذي استخدمناه لنظام Android ، ولكن هناك فارق بسيط. نحتاج إلى الاتصال بـ MacOS لإنشاء مشروع C ++. ولكن لهذا نحن بحاجة إلى تثبيت vcremote على ماك. صحيح ، بعد آخر التحديثات فمن المستحيل القيام به الآن. ربما ستهتم Microsoft لاحقًا بإصلاحه وتثبيته ، ولكن هذا ليس هو الحال الآن مع الأسف.
لهذا السبب ، علينا أن نذهب في الاتجاه الآخر. في Xcode ، نحتاج إلى إنشاء مشروع Cocos Touch Static Library لنظام iOS.
كيفية القيام بذلك ، يمكننا أن نقرأ هنا . نضيف ملفاتنا من 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
بعد أن نقوم بإعداد ملفات المكتبة الثابتة ، يمكننا دمجها في ملف واحد للدهون ، مع قائمة بجميع الهياكل في ملف واحد ، مرة أخرى باستخدام الأمر Terminal:
lipo -create lib_iphone.a lib_iphone_simulator.a -output lib.a
ماك
على MacOS ، سيكون كل شيء بسيطًا للغاية. نحتاج إلى تحويل ملف المكتبة الثابتة إلى ديناميكي ، مرة أخرى باستخدام الأمر Terminal:
clang -fpic -shared -Wl, -all_load lib.a -o lib.dylib
وهذا كل شيء. سنحصل على ملف * .dylib الذي نحتاجه.
حزمة Nuget
نظرًا لأننا صنعنا حزمة nuget وأضفنا منطقًا محددًا لمشروع Xamarin فيه ، فقد احتجنا إلى عمل غلاف لـ C SDK. في C # ، لتوصيل أساليب C ، نحتاج إلى استخدام سمة DllImport. ولكن هنا مرة أخرى هناك فارق بسيط. نحن بحاجة إلى استخدام const لمسار الملف C الأصلي. علاوة على ذلك ، سيكون لكل مشروع طريقه الخاص إلى الملف ، وحتى اسم الملف نفسه سيكون مختلفًا. لهذا السبب ، كان علينا أن نحسن أنفسنا قليلاً ونكتب أغلفةنا الخاصة لهذا الغرض.
لذلك ، لدينا الفئة الرئيسية التي تصف أساليب ملف C.
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); }
النظام الأساسي العالمي لنظام Windows (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); }
ماك
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); }
نحن الآن بحاجة إلى إنشاء ملف التعداد مع قائمة المنصات / البنى:
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 (); } } }
نحتاج أيضًا إلى الطريقة الأولية لتكوين كل شيء أنشأناه داخل مشاريع Xamarin الخاصة بنا.
public static class Init { public static PlatformArchitecture PlatformArchitecture { get; set; } }
ربط المكتبات المولدة بالمشاريع
النظام الأساسي العالمي لنظام Windows (UWP)
نقوم بنسخ ملفات المكتبة التي تم إنشاؤها إلى مجلدات:
- lib / x86 / lib.dll
- lib / x64 / lib.dll
وقمنا بإعداد هيكلنا عندما يبدأ التطبيق في طريقة التهيئة:
Wrapper.Init.PlatformArchitecture = Wrapper.Enums.PlatformArchitecture.X64;
أندرويد
بالنسبة لمشروع Android ، نحتاج إلى إصلاح ملف * .csproj وحفظ المشروع ونسخ الملفات * .so إلى المجلدات. في مشروع Android ، نشير إلى اسم الملف الذي تم إنشاؤه ، لأننا نصف مسارات الملفات في ملف * .csproj. نحتاج أيضًا إلى تذكر ما يلي عند نسخ الملفات إلى المجلدات:
- armeabi - ملف * .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 الذي تم إنشاؤه إلى المجلد الجذر للمشروع وتثبيت إرشادات إضافية عند تجميع المشروع (خصائص iOS => iOS build => وسائط mtouch إضافية). تثبيت الإرشادات التالية:
-gcc_flags "-L${ProjectDir} -llib -force_load ${ProjectDir}/lib.a"
أيضًا ، لا تنس تحديد Build Build as None في خصائص ملف * .a.
وقم مرة أخرى بتثبيت البنية لحزمة nuget:
Wrapper.Init.PlatformArchitecture = Wrapper.Enums.PlatformArchitecture.Ios;
ماك
أضف ملف * .dylib الخاص بنا إلى مشروع المراجع الأصلية ويصف البنية المطلوبة:
Wrapper.Init.PlatformArchitecture = Wrapper.Enums.PlatformArchitecture.Mac;
بعد هذه المعالجات ، التقطت مشاريع جميع منصاتنا الملفات الأصلية التي تم إنشاؤها وتمكنا من استخدام جميع الوظائف من KFOR لدينا داخل المشروع.