搭配 Xamarin 使用 C/C++ 連結庫

概觀

Xamarin 可讓開發人員使用 Visual Studio 建立跨平臺原生行動應用程式。 一般而言,C# 系結可用來向開發人員公開現有的平台元件。 不過,有時候 Xamarin 應用程式需要使用現有的程式代碼基底。 有時候小組不需要時間、預算或資源,即可將大型、經過良好測試且高度優化的程式代碼基底移植到 C#。

適用於跨平臺行動開發的 Visual C++ 可讓 C/C++ 和 C# 程式代碼建置為相同解決方案的一部分,提供許多優點,包括整合偵錯體驗。 Microsoft 以這種方式使用 C/C++ 和 Xamarin 來傳遞 Hyperlapse MobilePix 相機 等應用程式。

不過,在某些情況下,需要(或需求)保留現有的 C/C++ 工具和程式,並讓連結庫程式代碼與應用程式分離,將連結庫視為類似於第三方元件。 在這些情況下,挑戰不僅是向 C# 公開相關成員,而且會將連結庫管理為相依性。 當然,盡可能自動化此程式。

本文概述此案例的高階方法,並逐步解說簡單的範例。

背景

C/C++ 被視為跨平台語言,但請務必非常小心,以確保原始程式碼確實是跨平臺,只使用所有目標編譯程式支援的 C/C++,且只包含很少或不含條件式包含的平臺或編譯程式特定程序代碼。

最終,程式代碼必須在所有目標平臺上順利編譯並執行,因此這會歸結為目標平臺(和編譯程式)的通用性。 編譯程式之間仍然可能會產生一些問題,因此每個目標平臺上的徹底測試(最好是自動化)變得越來越重要。

高階方法

下圖代表將 C/C++ 原始碼轉換成透過 NuGet 共用的跨平臺 Xamarin 連結庫,然後在 Xamarin.Forms 應用程式中取用的四個階段方法。

High-level approach for using C/C++ with Xamarin

4 個階段如下:

  1. 將 C/C++ 原始程式碼編譯成平臺特定的原生連結庫。
  2. 使用 Visual Studio 解決方案包裝原生連結庫。
  3. 封裝和推送 .NET 包裝函式的 NuGet 套件。
  4. 從 Xamarin 應用程式取用 NuGet 套件。

階段 1:將 C/C++ 原始程式碼編譯成平臺特定的原生連結庫

此階段的目標是建立可由 C# 包裝函式呼叫的原生連結庫。 視您的情況而定,這可能或可能不相關。 在此常見案例中可承受的許多工具和程式都超出本文的範圍。 主要考慮是讓 C/C++ 程式代碼基底與任何原生包裝函式程式碼、足夠的單元測試和建置自動化保持同步。

逐步解說中的連結庫是使用 Visual Studio Code 搭配隨附的殼層腳本所建立。 您可以在Mobile CAT GitHub存放庫中找到此逐步解說的擴充版本,以更深入地討論此部分的範例。 在此情況下,原生連結庫會被視為第三方相依性,但此階段會針對內容說明。

為了簡單起見,逐步解說只會以架構子集為目標。 針對 iOS,它會使用 lipo 公用程式,從個別架構特定的二進位檔建立單一脂肪二進位檔。 Android 會使用具有 .so 擴展名的動態二進位檔,而 iOS 會使用具有 .a 擴展名的靜態胖二進位檔。

階段 2:使用 Visual Studio 解決方案包裝原生連結庫

下一個階段是包裝原生連結庫,以便從 .NET 輕鬆使用它們。 這是使用具有四個專案的Visual Studio解決方案來完成。 共用專案包含一般程序代碼。 以每個 Xamarin.Android、Xamarin.iOS 和 .NET Standard 為目標的專案,允許以平台無關的方式參考連結庫。

包裝函式使用「誘餌和切換技巧」。 這不是唯一的方式,但它可讓您輕鬆地參考連結庫,並避免在取用應用程式本身內明確管理平臺特定實作的需求。 訣竅基本上是確保目標 (.NET Standard、 Android、 iOS) 共用相同的命名空間、元件名稱和類別結構。 因為 NuGet 一律會偏好平臺特定的連結庫,所以運行時間永遠不會使用 .NET Standard 版本。

此步驟中的大部分工作將著重於使用 P/Invoke 來呼叫原生連結庫方法,以及管理基礎對象的參考。 目標是將連結庫的功能公開給取用者,同時擷取任何複雜度。 Xamarin.Forms 開發人員不需要具備 Unmanaged 連結庫內部工作的知識。 應該會覺得它們使用任何其他 Managed C# 連結庫。

最後,這個階段的輸出是一組 .NET 連結庫,每個目標各一個連結庫,以及一份 nuspec 檔,其中包含在下一個步驟中建置套件所需的資訊。

階段 3:封裝和推送 .NET 包裝函式的 NuGet 套件

第三個階段是使用上一個步驟中的組建成品來建立 NuGet 套件。 此步驟的結果是可從 Xamarin 應用程式取用的 NuGet 套件。 本逐步解說會使用本機目錄作為 NuGet 摘要。 在生產環境中,此步驟應該將套件發佈至公用或私人 NuGet 摘要,而且應該完全自動化。

階段 4:從 Xamarin.Forms 應用程式取用 NuGet 套件

最後一個步驟是從 Xamarin.Forms 應用程式參考和使用 NuGet 套件。 這需要在Visual Studio中設定NuGet 摘要,才能使用上一個步驟中定義的摘要。

設定摘要之後,必須從跨平臺 Xamarin.Forms 應用程式中的每個項目參考套件。 「誘餌與切換技巧」提供相同的介面,因此可以使用單一位置中定義的程式碼來呼叫原生連結庫功能。

原始程式碼存放庫包含進一 步閱讀 的清單,其中包含如何在 Azure DevOps 上設定私人 NuGet 摘要的文章,以及如何將套件推送至該摘要。 雖然需要比本機目錄多一點設定時間,但這種摘要在小組開發環境中會更好。

逐步解說

提供的步驟專屬於 Visual Studio for Mac,但結構也適用於 Visual Studio 2017

必要條件

若要跟著做,開發人員將需要:

注意

需要作用中的Apple開發人員帳戶,才能將應用程式部署到i 電話。

建立原生連結庫 (階段 1)

原生連結庫功能是以逐步 解說:建立和使用靜態庫 (C++) 中的範例為基礎。

本逐步解說會略過第一個階段,建置原生連結庫,因為此案例中提供連結庫作為第三方相依性。 先行編譯的原生連結庫隨附於範例程序代碼中,也可以直接下載

使用原生連結庫

原始 MathFuncsLib 範例包含具有下列定義的單一類別 MyMathFuncs

namespace MathFuncs
{
    class MyMathFuncs
    {
    public:
        double Add(double a, double b);
        double Subtract(double a, double b);
        double Multiply(double a, double b);
        double Divide(double a, double b);
    };
}

其他類別定義包裝函式,允許 .NET 取用者建立、處置及與基礎原生 MyMathFuncs 類別互動。

#include "MyMathFuncs.h"
using namespace MathFuncs;

extern "C" {
    MyMathFuncs* CreateMyMathFuncsClass();
    void DisposeMyMathFuncsClass(MyMathFuncs* ptr);
    double MyMathFuncsAdd(MyMathFuncs *ptr, double a, double b);
    double MyMathFuncsSubtract(MyMathFuncs *ptr, double a, double b);
    double MyMathFuncsMultiply(MyMathFuncs *ptr, double a, double b);
    double MyMathFuncsDivide(MyMathFuncs *ptr, double a, double b);
}

這將是在 Xamarin 端使用的這些包裝函式。

包裝原生連結庫 (階段 2)

此階段需要上一節所述的先行編譯連結庫。

建立 Visual Studio 解決方案

  1. Visual Studio for Mac 中,單擊 [新增專案 ] (從 [歡迎頁面] 或 [新增方案 ] (從 [ 檔案 ] 功能表)。

  2. 從 [新增專案] 視窗中,選擇 [共享專案] [從多平台>連結庫] ,然後按 [下一步]。

  3. 更新下列欄位,然後按兩下列欄位[ 建立]:

    • 項目名稱: MathFuncs.Shared
    • 解決方案名稱: MathFuncs
    • 位置: 使用預設儲存位置 (或挑選替代位置)
    • 在方案目錄中建立專案: 將此設定為已核取
  4. 方案總管,按兩下MathFuncs.Shared專案並流覽至Main 設定

  5. 移除 。從預設命名空間共用,使其只設定為MathFuncs,然後按兩下 [確定]。

  6. 開啟 MyClass.cs (由範本建立),然後將 類別和檔名重新命名為 MyMathFuncsWrapper,並將命名空間變更為 MathFuncs

  7. CONTROL + 按兩下解決方案MathFuncs,然後從[新增] 選單中選擇 [新增專案...]。

  8. 從 [新增專案] 視窗中,選擇 [.NET Standard 連結庫] [從多平台>連結庫] ,然後按 [下一步]。

  9. 選擇 [.NET Standard 2.0] ,然後按 [ 下一步]。

  10. 更新下列欄位,然後按兩下列欄位[ 建立]:

    • 項目名稱: MathFuncs.Standard
    • 位置: 使用與共享專案相同的儲存位置
  11. 方案總管,按兩下MathFuncs.Standard專案。

  12. 流覽至Main 設定,然後將 [預設命名空間] 更新MathFuncs

  13. 流覽至 [輸出] 設定,然後將 [元件名稱] 更新MathFuncs

  14. 流覽至編譯程式設定,將 [組態] 變更為 [發行],將 [偵錯資訊] 設定[僅限符號],然後按兩下 [確定]。

  15. 從專案刪除 Class1.cs/用戶入門 (如果其中一個包含在範本中)。

  16. CONTROL + 按兩下 專案 [相依性/參考 ] 資料夾,然後選擇 [ 編輯參考]。

  17. 從 [專案] 索引標籤選取 [MathFuncs.Shared],然後按兩下 [確定]。

  18. 使用下列設定重複步驟 7-17 (忽略步驟 9):

    專案名稱 範本名稱 新增項目功能表
    MathFuncs.Android 類別庫 Android 連結 > 庫
    MathFuncs.iOS 系結連結庫 iOS > 連結庫
  19. 方案總管,按兩下MathFuncs.Android專案,然後流覽至編譯程式設定。

  20. 將 [組態] 設定為 [偵錯],編輯 [定義符號] 以包含 Android;

  21. 將 [ 組態 ] 變更為 [發行],然後編輯 [定義符號 ] 以包含 Android;

  22. 針對 MathFuncs.iOS重複步驟19-20,編輯 定義符號 以包含 iOS; 而不是 Android; 在這兩種情況下。

  23. 發行 組態中建置解決方案(CONTROL + COMMAND + B),並驗證所有三個輸出元件 (Android、iOS、.NET Standard) (在個別專案 bin 資料夾中)共用相同的名稱 MathFuncs.dll

在這個階段,解決方案應該有三個目標,一個適用於Android、iOS和.NET Standard,以及三個目標中每個參考的共享專案。 這些應該設定為使用相同的預設命名空間和具有相同名稱的輸出元件。 這是先前所述的「誘餌和切換」方法的必要專案。

新增原生連結庫

將原生連結庫新增至包裝函式解決方案的程式在 Android 和 iOS 之間會稍有不同。

MathFuncs.Android 的原生參考

  1. CONTROL + 按兩下MathFuncs.Android專案,然後從[新增] 選單選擇 [新增資料夾],將它命名為lib

  2. 針對每個 ABI (應用程式二進位介面),CONTROL + 單擊lib 資料夾,然後從 [新增] 功能表中選擇 [新增資料夾],將它命名為個別的 ABI。 在此案例中:

    • arm64-v8a
    • armeabi-v7a
    • x86
    • x86_64

    注意

    如需更詳細的概觀,請參閱 NDK 開發人員指南中的架構和 CPU 主題,特別是有關解決應用程式套件中的機器碼一節。

  3. 確認資料夾結構:

    - lib
        - arm64-v8a
        - armeabi-v7a
        - x86
        - x86_64
    
  4. 根據下列對應,將對應的 .so 連結庫新增至每個 ABI 資料夾:

    arm64-v8a: lib/Android/arm64

    armeabi-v7a: lib/Android/arm

    x86: lib/Android/x86

    x86_64: lib/Android/x86_64

    注意

    若要新增檔案,CONTROL + 單擊代表個別 ABI 的資料夾,然後從 [新增] 功能表選擇 [新增檔案...]。 選擇適當的連結庫(從 PrecompiledLibs 目錄),然後按兩下 [開啟],然後按兩下 [確定],保留預設選項以將檔案複製到目錄

  5. 針對每個 .so 檔案,CONTROL + CLICK,然後從 [建置動作] 功能表選擇 [EmbeddedNativeLibrary] 選項。

現在,lib 資料夾應該會顯示如下:

- lib
    - arm64-v8a
        - libMathFuncs.so
    - armeabi-v7a
        - libMathFuncs.so
    - x86
        - libMathFuncs.so
    - x86_64
        - libMathFuncs.so

MathFuncs.iOS 的原生參考

  1. CONTROL + 按兩下MathFuncs.iOS專案,然後從 [新增] 功能表選擇 [新增原生參考]。

  2. 選擇 libMathFuncs.a 連結庫(從 PrecompiledLibs 目錄下的 libs/ios),然後按兩下 [開啟]

  3. CONTROL + 按兩下libMathFuncs 檔案 (在 [原生參考 ] 資料夾中,然後從功能表中選擇 [屬性] 選項

  4. 設定原生參考屬性,以便在 Properties Pad 中檢查它們(顯示刻度圖示):

    • 強制載入
    • 是 C++
    • 智能連結

    注意

    使用系結連結庫項目類型與 原生參考 內嵌靜態庫,並讓它與參考它的 Xamarin.iOS 應用程式自動連結(即使它透過 NuGet 套件包含)。

  5. 開啟 ApiDefinition.cs,刪除樣板化批注程式代碼(只 MathFuncs 保留命名空間),然後針對 Structs.cs執行相同的步驟

    注意

    系結連結庫專案需要這些檔案(使用 ObjCBindingApiDefinitionObjCBindingCoreSource 建置動作),才能建置。 不過,我們將撰寫程序代碼,以使用標準 P/Invoke 在 Android 和 iOS 連結庫目標之間共用這些檔案之外,呼叫原生連結庫。

撰寫Managed連結庫程式碼

現在,撰寫 C# 程式代碼以呼叫原生連結庫。 目標是隱藏任何基礎複雜性。 取用者不應該需要原生連結庫內部或 P/Invoke 概念的任何工作知識。

建立 保管庫 Handle

  1. CONTROL + 按兩下MathFuncs.Shared專案,然後從[新增] 選單選擇 [新增檔案...]。

  2. 從 [新增檔案] 視窗中選擇 [空白類別],將它命名為 MyMathFuncs 保管庫 Handle,然後按兩下 [新增]

  3. 實作 MyMathFuncs 保管庫 Handle 類別:

    using System;
    using Microsoft.Win32.SafeHandles;
    
    namespace MathFuncs
    {
        internal class MyMathFuncsSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
        {
            public MyMathFuncsSafeHandle() : base(true) { }
    
            public IntPtr Ptr => handle;
    
            protected override bool ReleaseHandle()
            {
                // TODO: Release the handle here
                return true;
            }
        }
    }
    

    注意

    保管庫 Handle 是使用 Managed 程式代碼中 Unmanaged 資源的慣用方式。 這會抽象化許多與重大最終化和物件生命週期相關的未定案程序代碼。 此句柄的擁有者後續可以將它視為任何其他受控資源,而且不需要實作完整的 可處置模式

建立內部包裝函式類別

  1. 開啟 MyMathFuncsWrapper.cs,將其變更為內部靜態類別

    namespace MathFuncs
    {
        internal static class MyMathFuncsWrapper
        {
        }
    }
    
  2. 在相同的檔案中,將下列條件語句新增至 類別:

    #if Android
        const string DllName = "libMathFuncs.so";
    #else
        const string DllName = "__Internal";
    #endif
    

    注意

    這會根據為 Android 或 iOS 建置連結庫,設定 DllName數值。 這是為了處理每個個別平臺所使用的不同命名慣例,但也處理此案例中所使用的連結庫類型。 Android 正在使用動態連結庫,因此需要包含擴展名的檔名。 針對 iOS,「__Internal」是必要專案,因為我們使用的是靜態庫。

  3. 在MyMathFuncsWrapper.cs檔案頂端新增 System.Runtime.InteropServices參考

    using System.Runtime.InteropServices;
    
  4. 新增包裝函式方法,以處理 MyMathFuncs 類別的建立和處置:

    [DllImport(DllName, EntryPoint = "CreateMyMathFuncsClass")]
    internal static extern MyMathFuncsSafeHandle CreateMyMathFuncs();
    
    [DllImport(DllName, EntryPoint = "DisposeMyMathFuncsClass")]
    internal static extern void DisposeMyMathFuncs(MyMathFuncsSafeHandle ptr);
    

    注意

    我們會將常數 DllName 傳遞至 DllImport 屬性,以及 EntryPoint ,明確告知 .NET 運行時間函式名稱,以在該連結庫內呼叫。 就技術上說,如果我們的 Managed 方法名稱與 Unmanaged 名稱相同,則不需要提供 EntryPoint 值。 如果未提供Managed方法名稱,則會改用做 EntryPoint 。 不過,最好是明確的。

  5. 新增包裝函式方法,讓我們能夠使用下列程式代碼來處理 MyMathFuncs 類別:

    [DllImport(DllName, EntryPoint = "MyMathFuncsAdd")]
    internal static extern double Add(MyMathFuncsSafeHandle ptr, double a, double b);
    
    [DllImport(DllName, EntryPoint = "MyMathFuncsSubtract")]
    internal static extern double Subtract(MyMathFuncsSafeHandle ptr, double a, double b);
    
    [DllImport(DllName, EntryPoint = "MyMathFuncsMultiply")]
    internal static extern double Multiply(MyMathFuncsSafeHandle ptr, double a, double b);
    
    [DllImport(DllName, EntryPoint = "MyMathFuncsDivide")]
    internal static extern double Divide(MyMathFuncsSafeHandle ptr, double a, double b);
    

    注意

    在此範例中,我們會針對參數使用簡單類型。 因為封送處理在此案例中是位複製,因此我們不需要額外的工作。 另請注意使用 MyMathFuncs 保管庫 Handle 類別,而不是標準 IntPtrIntPtr 會自動對應至封送處理程式的 保管庫 Handle

  6. 確認已完成的 MyMathFuncsWrapper 類別如下所示:

    using System.Runtime.InteropServices;
    
    namespace MathFuncs
    {
        internal static class MyMathFuncsWrapper
        {
            #if Android
                const string DllName = "libMathFuncs.so";
            #else
                const string DllName = "__Internal";
            #endif
    
            [DllImport(DllName, EntryPoint = "CreateMyMathFuncsClass")]
            internal static extern MyMathFuncsSafeHandle CreateMyMathFuncs();
    
            [DllImport(DllName, EntryPoint = "DisposeMyMathFuncsClass")]
            internal static extern void DisposeMyMathFuncs(MyMathFuncsSafeHandle ptr);
    
            [DllImport(DllName, EntryPoint = "MyMathFuncsAdd")]
            internal static extern double Add(MyMathFuncsSafeHandle ptr, double a, double b);
    
            [DllImport(DllName, EntryPoint = "MyMathFuncsSubtract")]
            internal static extern double Subtract(MyMathFuncsSafeHandle ptr, double a, double b);
    
            [DllImport(DllName, EntryPoint = "MyMathFuncsMultiply")]
            internal static extern double Multiply(MyMathFuncsSafeHandle ptr, double a, double b);
    
            [DllImport(DllName, EntryPoint = "MyMathFuncsDivide")]
            internal static extern double Divide(MyMathFuncsSafeHandle ptr, double a, double b);
        }
    }
    

完成 MyMathFuncs 保管庫 Handle 類別

  1. 開啟 MyMathFuncs 保管庫 Handle 類別,流覽至 ReleaseHandle 方法內的佔位元 TODO 批注:

    // TODO: Release the handle here
    
  2. 取代 TODO 行:

    MyMathFuncsWrapper.DisposeMyMathFuncs(this);
    

撰寫 MyMathFuncs 類別

現在包裝函式已完成,請建立 MyMathFuncs 類別,以管理 Unmanaged C++ MyMathFuncs 對象的參考。

  1. CONTROL + 按兩下MathFuncs.Shared專案,然後從[新增] 選單選擇 [新增檔案...]。

  2. 從 [新增檔案] 視窗中選擇 [空白類別],將它命名為 MyMathFuncs,然後按兩下 [新增]

  3. 將下列成員新增至 MyMathFuncs 類別:

    readonly MyMathFuncsSafeHandle handle;
    
  4. 實作 類別的建構函式,以便在類別具現化時建立並儲存原生 MyMathFuncs 物件的句柄:

    public MyMathFuncs()
    {
        handle = MyMathFuncsWrapper.CreateMyMathFuncs();
    }
    
  5. 使用下列程式代碼實作 IDisposable 介面:

    public class MyMathFuncs : IDisposable
    {
        ...
    
        protected virtual void Dispose(bool disposing)
        {
            if (handle != null && !handle.IsInvalid)
                handle.Dispose();
        }
    
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    
        // ...
    }
    
  6. 使用 MyMathFuncsWrapper 類別實作 MyMathFuncs 方法,藉由傳入儲存至基礎 Unmanaged 物件的指標,在幕後執行實際工作。 程式代碼應該如下所示:

    public double Add(double a, double b)
    {
        return MyMathFuncsWrapper.Add(handle, a, b);
    }
    
    public double Subtract(double a, double b)
    {
        return MyMathFuncsWrapper.Subtract(handle, a, b);
    }
    
    public double Multiply(double a, double b)
    {
        return MyMathFuncsWrapper.Multiply(handle, a, b);
    }
    
    public double Divide(double a, double b)
    {
        return MyMathFuncsWrapper.Divide(handle, a, b);
    }
    

建立 nuspec

為了透過 NuGet 封裝並散發連結庫,解決方案需要 nuspec 檔案。 這會識別每個支援平臺將包含哪些產生的元件。

  1. CONTROL + 單擊解決方案 MathFuncs,然後從 [新增] 選單選擇 [新增方案資料夾],將其命名為 SolutionItems

  2. CONTROL + 按兩下SolutionItems 資料夾,然後從 [新增] 選單中選擇 [新增檔案...]。

  3. 從 [新增檔案] 視窗中選擇 [空白 XML 檔案],將它命名為 MathFuncs.nuspec,然後按兩下 [新增]。

  4. 使用要顯示給 NuGet 取用者的基本套件元數據來更新 MathFuncs.nuspec。 例如:

    <?xml version="1.0"?>
    <package>
        <metadata>
            <id>MathFuncs</id>
            <version>$version$</version>
            <authors>Microsoft Mobile Customer Advisory Team</authors>
            <description>Sample C++ Wrapper Library</description>
            <requireLicenseAcceptance>false</requireLicenseAcceptance>
            <copyright>Copyright 2018</copyright>
        </metadata>
    </package>
    
  5. <files>將 專案新增為 元素的<package>子系(就在下方<metadata>),以個別<file>元素識別每個檔案:

    <files>
    
        <!-- Android -->
    
        <!-- iOS -->
    
        <!-- netstandard2.0 -->
    
    </files>
    

    注意

    當套件安裝到專案中,以及相同名稱指定多個元件時,NuGet 會有效地選擇最適合指定平臺的元件。

  6. <file>新增 Android 元件的元素

    <file src="MathFuncs.Android/bin/Release/MathFuncs.dll" target="lib/MonoAndroid81/MathFuncs.dll" />
    <file src="MathFuncs.Android/bin/Release/MathFuncs.pdb" target="lib/MonoAndroid81/MathFuncs.pdb" />
    
  7. <file>新增 iOS 元件的元素

    <file src="MathFuncs.iOS/bin/Release/MathFuncs.dll" target="lib/Xamarin.iOS10/MathFuncs.dll" />
    <file src="MathFuncs.iOS/bin/Release/MathFuncs.pdb" target="lib/Xamarin.iOS10/MathFuncs.pdb" />
    
  8. <file>新增 netstandard2.0 元件的元素

    <file src="MathFuncs.Standard/bin/Release/netstandard2.0/MathFuncs.dll" target="lib/netstandard2.0/MathFuncs.dll" />
    <file src="MathFuncs.Standard/bin/Release/netstandard2.0/MathFuncs.pdb" target="lib/netstandard2.0/MathFuncs.pdb" />
    
  9. 確認 nuspec 指令清單:

    <?xml version="1.0"?>
    <package>
    <metadata>
        <id>MathFuncs</id>
        <version>$version$</version>
        <authors>Microsoft Mobile Customer Advisory Team</authors>
        <description>Sample C++ Wrapper Library</description>
        <requireLicenseAcceptance>false</requireLicenseAcceptance>
        <copyright>Copyright 2018</copyright>
    </metadata>
    <files>
    
        <!-- Android -->
        <file src="MathFuncs.Android/bin/Release/MathFuncs.dll" target="lib/MonoAndroid81/MathFuncs.dll" />
        <file src="MathFuncs.Android/bin/Release/MathFuncs.pdb" target="lib/MonoAndroid81/MathFuncs.pdb" />
    
        <!-- iOS -->
        <file src="MathFuncs.iOS/bin/Release/MathFuncs.dll" target="lib/Xamarin.iOS10/MathFuncs.dll" />
        <file src="MathFuncs.iOS/bin/Release/MathFuncs.pdb" target="lib/Xamarin.iOS10/MathFuncs.pdb" />
    
        <!-- netstandard2.0 -->
        <file src="MathFuncs.Standard/bin/Release/netstandard2.0/MathFuncs.dll" target="lib/netstandard2.0/MathFuncs.dll" />
        <file src="MathFuncs.Standard/bin/Release/netstandard2.0/MathFuncs.pdb" target="lib/netstandard2.0/MathFuncs.pdb" />
    
    </files>
    </package>
    

    注意

    此檔案會指定發行組建中的元件輸出路徑,因此請務必使用該組態來建置方案。

此時,解決方案包含 3 個 .NET 元件和支援的 nuspec 指令清單。

使用 NuGet 散發 .NET 包裝函式

下一個步驟是封裝和散發 NuGet 套件,以便應用程式輕鬆取用,並以相依性的形式加以管理。 包裝和耗用量都可以在單一解決方案內完成,但透過 NuGet 散發連結庫有助於分離,並讓我們能夠獨立管理這些程式代碼基底。

準備本機套件目錄

NuGet 摘要最簡單的形式是本機目錄:

  1. [尋找工具] 中,流覽至方便的目錄。 例如, /Users
  2. 從 [檔案] 功能表中選擇 [新增資料夾],提供有意義的名稱,例如local-nuget-feed

建立套件

  1. 將 [建置組態] 設定為 [發行],然後使用 COMMAND + B 執行組建

  2. 開啟 終端機 ,並將目錄變更為包含 nuspec 檔案的資料夾。

  3. 終端機中,執行 nuget pack 命令,指定 nuspec 檔案、Version(例如 1.0.0),以及使用在上一個步驟建立的資料夾 OutputDirectory,也就是 local-nuget-feed。 例如:

    nuget pack MathFuncs.nuspec -Version 1.0.0 -OutputDirectory ~/local-nuget-feed
    
  4. 確認MathFuncs.1.0.0.nupkg已在local-nuget-feed目錄中建立

[選擇性]搭配 Azure DevOps 使用私人 NuGet 摘要

在 Azure DevOps 中開始使用 NuGet 套件中說明更健全的技術,其中說明如何建立私人摘要,並將套件推送至該摘要(在上一個步驟中產生)。

理想的做法是讓此工作流程完全自動化,例如使用 Azure Pipelines。 如需詳細資訊,請參閱 開始使用 Azure Pipelines

從 Xamarin.Forms 應用程式取用 .NET 包裝函式

若要完成逐步解說,請建立 Xamarin.Forms 應用程式,以取用剛發行至本機 NuGet 摘要的套件。

建立 Xamarin.Forms 專案

  1. 開啟 Visual Studio for Mac 的新實例。 這可以從終端機完成

    open -n -a "Visual Studio"
    
  2. Visual Studio for Mac 中,單擊 [新增專案 ] (從 [歡迎頁面] 或 [新增方案 ] (從 [ 檔案 ] 功能表)。

  3. 從 [新增專案] 視窗中,選擇 [空白窗體應用程式] [從多平臺>應用程式] ,然後按 [下一步]。

  4. 更新下列欄位,然後按 [下一步] :

    • 應用程式名稱: MathFuncsApp。
    • 組織標識碼: 使用反向命名空間,例如 com.{your_org}
    • 目標平臺: 使用預設值 (Android 和 iOS 目標)。
    • 共用程式代碼: 將此設定為 .NET Standard(可能提供「共用連結庫」解決方案,但超出本逐步解說的範圍)。
  5. 更新下列欄位,然後按兩下列欄位[ 建立]:

    • 項目名稱: MathFuncsApp。
    • 解決方案名稱: MathFuncsApp。
    • 位置: 使用預設儲存位置(或挑選替代位置)。
  6. 方案總管 中,CONTROL + 按兩下目標 (MathFuncsApp.AndroidMathFuncs.iOS) 進行初始測試,然後選擇 [設定為啟始專案]。

  7. 選擇慣用的裝置模擬器/模擬器。

  8. 執行方案 (COMMAND + RETURN) 來驗證樣板化 Xamarin.Forms 專案是否建置並正常執行。

    注意

    iOS (特別是模擬器)通常會有最快的組建/部署時間。

將本機 NuGet 摘要新增至 NuGet 組態

  1. 在 Visual Studio,選擇 [喜好設定] (從 Visual Studio 功能表)。

  2. 從 [NuGet] 區段下選擇 [來源],然後按兩下 [新增]。

  3. 更新下列欄位,然後按兩下列欄位,然後按下列欄位: 新增來源

    • 名稱: 提供有意義的名稱,例如 Local-Packages。
    • 位置:指定在上一個步驟中建立的local-nuget-feed資料夾。

    注意

    在此情況下,不需要指定 [用戶名稱] 和 [密碼]。

  4. 按一下 [確定]

參考套件

針對每個項目重複下列步驟(MathFuncsApp、MathFuncsApp.AndroidMathFuncsApp.iOS)。

  1. CONTROL + 按兩下專案,然後從 [新增] 選單選擇 [新增 NuGet 套件...]。
  2. 搜尋 MathFuncs
  3. 確認套件的版本1.0.0,而其他詳細數據會顯示為預期,例如 TitleDescription也就是 MathFuncs範例 C++ 包裝函式連結庫
  4. 選取MathFuncs套件,然後按兩下 [新增套件]。

使用連結庫函式

現在,每個專案中都有 MathFuncs 套件的參考,函式可供 C# 程式代碼使用。

  1. 從MathFuncsApp通用 Xamarin.Forms專案中開啟MainPage.xaml.cs (由MathFuncsApp.AndroidMathFuncsApp.iOS 參考)。

  2. 檔案頂端新增 System.DiagnosticsMathFuncs 的 using 語句:

    using System.Diagnostics;
    using MathFuncs;
    
  3. 在類別頂端MainPage宣告 類別的MyMathFuncs實體:

    MyMathFuncs myMathFuncs;
    
  4. OnAppearing覆寫基類中的 ContentPageOnDisappearing 方法:

    protected override void OnAppearing()
    {
        base.OnAppearing();
    }
    
    protected override void OnDisappearing()
    {
        base.OnDisappearing();
    }
    
  5. OnAppearing更新 方法,以初始化先前宣告的myMathFuncs變數:

    protected override void OnAppearing()
    {
        base.OnAppearing();
        myMathFuncs = new MyMathFuncs();
    }
    
  6. OnDisappearing更新 方法以在 上myMathFuncs呼叫 Dispose 方法:

    protected override void OnDisappearing()
    {
        base.OnAppearing();
        myMathFuncs.Dispose();
    }
    
  7. 實作名為 TestMathFuncs 的私人方法,如下所示:

    private void TestMathFuncs()
    {
        var numberA = 1;
        var numberB = 2;
    
        // Test Add function
        var addResult = myMathFuncs.Add(numberA, numberB);
    
        // Test Subtract function
        var subtractResult = myMathFuncs.Subtract(numberA, numberB);
    
        // Test Multiply function
        var multiplyResult = myMathFuncs.Multiply(numberA, numberB);
    
        // Test Divide function
        var divideResult = myMathFuncs.Divide(numberA, numberB);
    
        // Output results
        Debug.WriteLine($"{numberA} + {numberB} = {addResult}");
        Debug.WriteLine($"{numberA} - {numberB} = {subtractResult}");
        Debug.WriteLine($"{numberA} * {numberB} = {multiplyResult}");
        Debug.WriteLine($"{numberA} / {numberB} = {divideResult}");
    }
    
  8. 最後,在 方法結尾OnAppearing呼叫TestMathFuncs

    TestMathFuncs();
    
  9. 在每個目標平臺上執行應用程式,並驗證應用程式輸出板中的輸出,如下所示:

    1 + 2 = 3
    1 - 2 = -1
    1 * 2 = 2
    1 / 2 = 0.5
    

    注意

    如果您在 Android 上測試時遇到 'DLLNotFoundException',或在 iOS 上發生建置錯誤,請務必檢查您使用的裝置/模擬器 CPU 架構是否與我們選擇支援的子集相容。

摘要

本文說明如何透過透過透過 NuGet 套件散發的通用 .NET 包裝函式,建立使用原生連結庫的 Xamarin.Forms 應用程式。 本逐步解說中提供的範例會刻意簡化,以便更輕鬆地示範方法。 實際的應用程式必須處理複雜度,例如例外狀況處理、回呼、更複雜類型的封送處理,以及與其他相依性連結庫的連結。 關鍵考慮是 C++ 程式代碼的演進與包裝函式和用戶端應用程式協調和同步的程式。 此程式可能會根據其中一個或兩者是否為單一小組的責任而有所不同。 無論哪種方式,自動化都是真正的好處。 以下是一些資源,提供有關一些重要概念以及相關下載的進一步閱讀。

下載

範例

深入閱讀

與本文章內容相關的文章