How to Create a NuGet Package for Both Desktop and Mobile Development
In my previous article, I demonstrated how to package C++ libraries into a NuGet package. This week, I’ll be expanding the package to include Android and iOS binding libraries, enabling multi-platform development across Windows, Linux, macOS, Android and iOS. The advantage of an all-in-one NuGet package is that you can utilize unified .NET APIs to create barcode and QR Code scanning applications for both desktop and mobile platforms, eliminating the need for platform-specific code.
Download BarcodeQRCodeSDK
https://www.nuget.org/packages/BarcodeQRCodeSDK
What doe the All-in-One NuGet Package Look Like?
- The
BarcodeQRCodeSDK.targets
file contains the build instructions for Visual C++ projects. - The
net6.0
folder contains the .NET binding libraries for Windows, Linux and macOS. Theruntimes
folder contains the C++ libraries (*.dll
,*.so
and*.dylib
) invoked by the .NET binding libraries. - The
net6.0-android31.0
folder contains the .NET binding libraries and the*.aar
files for Android. - The
net6.0-ios16.1
folder contains the .NET binding libraries and the.xcframework
package for iOS.
How to Create Unified .NET APIs for Android and iOS Binding Libraries
To add new Android and iOS binding library projects to the BarcodeQRCodeSDK
solution in Visual Studio, simply right-click on the solution and select “Add”. For detailed steps of building the Android and iOS libraries, please refer to https://www.dynamsoft.com/codepool/dotnet-android-ios-nuget-package.html.
The BarcodeQRCodeSDK solution now includes three sub-projects. You need to run Pair to Mac
before building the iOS binding library on Windows. While attempting to build the solution on macOS, you will encounter the following error when importing the project into the Visual Studio for Mac. The workaround is to build the iOS binding project independently on macOS.
Since our goal is to create an all-in-one NuGet package, it is best to ensure that the output names of the Android and iOS binding libraries are consistent with the .NET binding library. To achieve this, we can add the AssemblyName
property to the *.csproj
file of the Android and iOS binding libraries.
<AssemblyName>BarcodeQRCodeSDK</AssemblyName>
At this point, both binding libraries are ready for use. However, the APIs are not yet unified. To achieve this, you need to create a BarcodeQRCodeReader.cs
file in the Android and iOS binding library projects respectively to implement additional functionality. Referring to the existing BarcodeQRCodeReader.cs
file in the .NET binding library project, you can write the following code for the Android and iOS binding libraries.
-
Import the dependent namespaces.
Android
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using Android.Hardware.Lights; using Com.Dynamsoft.Dbr; using Java.Nio; using Java.Nio.FileNio.Attributes;
iOS
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using com.Dynamsoft.Dbr; using System.Runtime.InteropServices; using Foundation;
-
Create a
BarcodeQRCodeReader
class, aResult
class and anImagePixelFormat
enum:Android
namespace Dynamsoft { public class BarcodeQRCodeReader { private BarcodeReader? reader; public class Result { public string? Text { get; set; } public int[]? Points { get; set; } public string? Format1 { get; set; } public string? Format2 { get; set; } } public enum ImagePixelFormat { IPF_BINARY, IPF_BINARYINVERTED, IPF_GRAYSCALED, IPF_NV21, IPF_RGB_565, IPF_RGB_555, IPF_RGB_888, IPF_ARGB_8888, IPF_RGB_161616, IPF_ARGB_16161616, IPF_ABGR_8888, IPF_ABGR_16161616, IPF_BGR_888 } } }
iOS
namespace Dynamsoft { public class BarcodeQRCodeReader { private DynamsoftBarcodeReader? reader; public class Result { public string? Text { get; set; } public int[]? Points { get; set; } public string? Format1 { get; set; } public string? Format2 { get; set; } } public enum ImagePixelFormat { IPF_BINARY, IPF_BINARYINVERTED, IPF_GRAYSCALED, IPF_NV21, IPF_RGB_565, IPF_RGB_555, IPF_RGB_888, IPF_ARGB_8888, IPF_RGB_161616, IPF_ARGB_16161616, IPF_ABGR_8888, IPF_ABGR_16161616, IPF_BGR_888 } } }
-
Implement the
InitLicense()
,Create()
,GetVersionInfo()
,DecodeFile()
,DecodeBuffer()
,DecodeBase64()
andSetParameters
methods:Android
public class DBRLicenseVerificationListener : Java.Lang.Object, IDBRLicenseVerificationListener { public void DBRLicenseVerificationCallback(bool isSuccess, Java.Lang.Exception error) { if (!isSuccess) { System.Console.WriteLine(error.Message); } } } public static void InitLicense(string license) { BarcodeReader.InitLicense(license, new DBRLicenseVerificationListener()); } private BarcodeQRCodeReader() { reader = new BarcodeReader(); } public static BarcodeQRCodeReader Create() { return new BarcodeQRCodeReader(); } ~BarcodeQRCodeReader() { Destroy(); } public void Destroy() { reader = null; } public static string? GetVersionInfo() { return BarcodeReader.Version; } private Result[]? OutputResults(TextResult[]? results) { Result[]? output = null; if (results != null && results.Length > 0) { output = new Result[results.Length]; for (int i = 0; i < results.Length; ++i) { TextResult tmp = results[i]; Result r = new Result(); output[i] = r; r.Text = tmp.BarcodeText; r.Format1 = tmp.BarcodeFormatString; r.Format2 = tmp.BarcodeFormatString; if (tmp.LocalizationResult != null && tmp.LocalizationResult.ResultPoints != null) { IList<Point> points = tmp.LocalizationResult.ResultPoints; r.Points = new int[8] { points[0].X, points[0].Y, points[1].X, points[1].Y, points[2].X, points[2].Y, points[3].X, points[3].Y }; } else r.Points = null; } } return output; } public Result[]? DecodeFile(string filename) { if (reader == null) { return null; } TextResult[]? results = reader.DecodeFile(filename); return OutputResults(results); } public Result[]? DecodeBuffer(byte[] buffer, int width, int height, int stride, ImagePixelFormat format) { if (reader == null) { return null; } TextResult[]? results = reader.DecodeBuffer(buffer, width, height, stride, (int)format); return OutputResults(results); } public Result[]? DecodeBase64(string base64string) { if (reader == null) { return null; } TextResult[]? results = reader.DecodeBase64String(base64string); return OutputResults(results); } public void SetParameters(string parameters) { if (reader == null) { return; } reader.InitRuntimeSettingsWithString(parameters, EnumConflictMode.CmOverwrite); }
iOS
public class Listener : DBRLicenseVerificationListener { public void DBRLicenseVerificationCallback(bool isSuccess, NSError error) { if (error != null) { System.Console.WriteLine(error.UserInfo); } } } public static void InitLicense(string license) { DynamsoftBarcodeReader.InitLicense(license, new Listener()); } private BarcodeQRCodeReader() { reader = new DynamsoftBarcodeReader(); } public static BarcodeQRCodeReader Create() { return new BarcodeQRCodeReader(); } ~BarcodeQRCodeReader() { Destroy(); } public void Destroy() { reader = null; } public static string? GetVersionInfo() { return DynamsoftBarcodeReader.Version; } private Result[]? OutputResults(iTextResult[]? results) { Result[]? output = null; if (results != null && results.Length > 0) { output = new Result[results.Length]; for (int i = 0; i < results.Length; ++i) { iTextResult tmp = results[i]; Result r = new Result(); output[i] = r; r.Text = tmp.BarcodeText; r.Format1 = tmp.BarcodeFormatString; r.Format2 = tmp.BarcodeFormatString; if (tmp.LocalizationResult != null && tmp.LocalizationResult.ResultPoints != null) { NSObject[] points = tmp.LocalizationResult.ResultPoints; r.Points = new int[8] { (int)((NSValue)points[0]).CGPointValue.X, (int)((NSValue)points[0]).CGPointValue.Y, (int)((NSValue)points[1]).CGPointValue.X, (int)((NSValue)points[1]).CGPointValue.Y, (int)((NSValue)points[2]).CGPointValue.X, (int)((NSValue)points[2]).CGPointValue.Y, (int)((NSValue)points[3]).CGPointValue.X, (int)((NSValue)points[3]).CGPointValue.Y }; } else r.Points = null; } } return output; } public Result[]? DecodeFile(string filename) { if (reader == null) { return null; } NSError error; iTextResult[]? results = reader.DecodeFileWithName(filename, out error); return OutputResults(results); } public Result[]? DecodeBuffer(byte[] myBytes, int width, int height, int stride, ImagePixelFormat format) { if (reader == null) { return null; } NSError error; IntPtr buffer = Marshal.AllocHGlobal(myBytes.Length); Marshal.Copy(myBytes, 0, buffer, myBytes.Length); NSData data = NSData.FromBytes(buffer, (nuint)myBytes.Length); iTextResult[]? results = reader.DecodeBuffer(data, width, height, stride, (EnumImagePixelFormat)format, out error); Marshal.FreeHGlobal(buffer); return OutputResults(results); } public Result[]? DecodeBase64(string base64string) { if (reader == null) { return null; } NSError error; iTextResult[]? results = reader.DecodeBase64(base64string, out error); return OutputResults(results); } public void SetParameters(string parameters) { if (reader == null) { return; } NSError error; reader.InitRuntimeSettingsWithString(parameters, EnumConflictMode.Overwrite, out error); }
Building the Android binding library does not present any issues. However, when attempting to build the iOS binding library using Visual Studio on macOS, red squiggles may appear.
Don’t panic. You can safely ignore these red squiggles and build the project successfully.
How to Configure *.nuspec File to Include Android and iOS Binding Libraries
If you have successfully built the Android and iOS binding libraries, the next step is to configure the *.nuspec
file to include them in the NuGet package.
<!-- Android -->
<file src="android\bin\Release\net6.0-android\**\*.*" target="lib\net6.0-android31.0" />
<!-- iOS -->
<file src="ios/manifest" target="lib/net6.0-ios16.1/BarcodeQRCodeSDK.resources" />
<file src="ios\bin\Release\net6.0-ios\BarcodeQRCodeSDK.dll" target="lib\net6.0-ios16.1" />
<file src="ios\bin\Release\net6.0-ios\BarcodeQRCodeSDK.resources\**\*.*" target="lib\net6.0-ios16.1\BarcodeQRCodeSDK.resources" />
<file src="ios/bin/Release/net6.0-ios/BarcodeQRCodeSDK.resources/DynamsoftBarcodeReader.xcframework/ios-arm64/DynamsoftBarcodeReader.framework/DynamsoftBarcodeReader" target="lib/net6.0-ios16.1/BarcodeQRCodeSDK.resources/DynamsoftBarcodeReader.xcframework/ios-arm64/DynamsoftBarcodeReader.framework" />
<file src="ios/bin/Release/net6.0-ios/BarcodeQRCodeSDK.resources/DynamsoftBarcodeReader.xcframework/ios-arm64_x86_64-simulator/DynamsoftBarcodeReader.framework/DynamsoftBarcodeReader" target="lib/net6.0-ios16.1/BarcodeQRCodeSDK.resources/DynamsoftBarcodeReader.xcframework/ios-arm64_x86_64-simulator/DynamsoftBarcodeReader.framework" />
Please note that while the wildcard **\*.*
includes all files and subfolders in the designated folder, it will not account for the DynamsoftBarcodeReader
assembly file. Therefore, you must add this file manually.
How to Update MAUI Barcode QR Code Scanner with the All-in-One NuGet Package
To try out the all-in-one NuGet package, you can download the https://github.com/yushulx/dotnet-barcode-qr-code-sdk/tree/main/example/maui project. This project was developed using the Android and iOS binding libraries of Dynamsoft Barcode Reader.
Here are the steps to update the project with the all-in-one NuGet package:
-
Replace
Barcode.NET.Mobile
withBarcodeQRCodeSDK
. -
Modify the platform-specific code as follows:
- using BarcodeQrScanner.Services; + using Dynamsoft; - BarcodeQRCodeService _barcodeQRCodeService; + BarcodeQRCodeReader _reader; - data = _barcodeQRCodeService.DecodeFile(path); + BarcodeQRCodeReader.Result[] results = _reader.DecodeFile(imagepath); - TextResult[] results = barcodeReader.DecodeBuffer(image.GetYuvData(), previewWidth, previewHeight, stridelist[0], EnumImagePixelFormat.IpfNv21); + BarcodeQRCodeReader.Result[] results = barcodeReader.DecodeBuffer(image.GetYuvData(), previewWidth, previewHeight, stridelist[0], BarcodeQRCodeReader.ImagePixelFormat.IPF_NV21); - results = reader.DecodeBuffer(buffer, width, height, bpr, EnumImagePixelFormat.Argb8888, out errorr); + byte[] bytearray = new byte[buffer.Length]; + System.Runtime.InteropServices.Marshal.Copy(buffer.Bytes, bytearray, 0, Convert.ToInt32(buffer.Length)); + results = reader.DecodeBuffer(bytearray, (int)width, (int)height, (int)bpr, BarcodeQRCodeReader.ImagePixelFormat.IPF_ARGB_8888);
-
Run the MAUI Barcode QR Code Scanner on Android and iOS.
The modified MAUI Barcode QR Code Scanner project is available at https://github.com/yushulx/dotnet-barcode-qr-code-sdk/tree/main/example/maui.