How to Use Xamarin to Bind a Fat iOS Framework on Windows

When developing an Android project with Xamarin, I didn’t get any trouble. However, it is totally a different experience when building an iOS app with Xamarin – it is more complicated. In this article, I will share my experience of binding DynamsoftBarcodeReader.framework using Xamarin.

Binding an iOS Framework with Xamarin

Download

DynamsoftBarcodeReader.framework is an SDK used for barcode detection.

Link the dependencies with Linkwith.cs File

Create an iOS bindings library project in Visual Studio 2015:

xamarin ios binding

Rename DynamsoftBarcodeReader.framework\DynamsoftBarcodeReader to DynamsoftBarcodeReader.framework\DynamsoftBarcodeReader.a, and then drag the static library to the project. The IDE will automatically generate a corresponding DynamsoftBarcodeReader.linkwith.cs file:

xamarin bind ios dbr

DynamsoftBarcodeReader.framework relies on libc++.1.dylib. Referring to ObjCRuntime.LinkWithAttribute Class, the DynamsoftBarcodeReader.linkwith.cs is written as follows:

using System;
using ObjCRuntime;

[assembly: LinkWith ("DynamsoftBarcodeReader.a", LinkTarget.ArmV7 | LinkTarget.Simulator, ForceLoad = true, LinkerFlags = "-lc++.1")]

Generate ApiDefinition.cs with Objective Sharpie

The ApiDefinition.cs is where you will define the API contract, this is the file that describes how the underlying Objective-C API is projected into C#. You can manually define all APIs for your library or automatically generate definitions with Objective Sharpie, which only runs on macOS.

Here is the command to generate ApiDeifinition.cs for DynamsoftBarcodeReader.framework:

sharpie -tlm-do-not-submit bind -framework ~/Desktop/DynamsoftBarcodeReader.framework -sdk iphoneos10.2

xmarin objective sharpie

You have to modify the generated file because Objective Sharpie uses [Verify] attributes to annotate APIs. After confirming the APIs, you can delete the lines of [verify]. Here is mine:

using System;
using CoreGraphics;
using Foundation;
using ObjCRuntime;
using UIKit;

namespace DBRiOS {
	
	[Static]
	partial interface Constants
	{
		// extern double DynamsoftBarcodeReaderVersionNumber;
		[Field ("DynamsoftBarcodeReaderVersionNumber", "__Internal")]
		double DynamsoftBarcodeReaderVersionNumber { get; }

		// extern const unsigned char [] DynamsoftBarcodeReaderVersionString;
		[Field ("DynamsoftBarcodeReaderVersionString", "__Internal")]
		NSString DynamsoftBarcodeReaderVersionString { get; }
	}

	// @interface Barcode : NSObject
	[BaseType (typeof(NSObject))]
	interface Barcode
	{
		// @property (nonatomic) long format;
		[Export ("format")]
		nint Format { get; set; }

		// @property (copy, nonatomic) NSString * formatString;
		[Export ("formatString")]
		string FormatString { get; set; }

		// @property (copy, nonatomic) NSString * displayValue;
		[Export ("displayValue")]
		string DisplayValue { get; set; }

		// @property (copy, nonatomic) NSData * rawValue;
		[Export ("rawValue", ArgumentSemantic.Copy)]
		NSData RawValue { get; set; }

		// @property (copy, nonatomic) NSArray * cornerPoints;
		[Export ("cornerPoints", ArgumentSemantic.Copy)]
		NSObject[] CornerPoints { get; set; }

		// @property (nonatomic) CGRect boundingbox;
		[Export ("boundingbox", ArgumentSemantic.Assign)]
		CGRect Boundingbox { get; set; }

		// +(long)OneD;
		[Static]
		[Export ("OneD")]
		nint OneD { get; }

		// +(long)CODE_39;
		[Static]
		[Export ("CODE_39")]
		nint CODE_39 { get; }

		// +(long)CODE_128;
		[Static]
		[Export ("CODE_128")]
		nint CODE_128 { get; }

		// +(long)CODE_93;
		[Static]
		[Export ("CODE_93")]
		nint CODE_93 { get; }

		// +(long)CODABAR;
		[Static]
		[Export ("CODABAR")]
		nint CODABAR { get; }

		// +(long)ITF;
		[Static]
		[Export ("ITF")]
		nint ITF { get; }

		// +(long)EAN_13;
		[Static]
		[Export ("EAN_13")]
		nint EAN_13 { get; }

		// +(long)EAN_8;
		[Static]
		[Export ("EAN_8")]
		nint EAN_8 { get; }

		// +(long)UPC_A;
		[Static]
		[Export ("UPC_A")]
		nint UPC_A { get; }

		// +(long)UPC_E;
		[Static]
		[Export ("UPC_E")]
		nint UPC_E { get; }

		// +(long)INDUSTRIAL_25;
		[Static]
		[Export ("INDUSTRIAL_25")]
		nint INDUSTRIAL_25 { get; }

		// +(long)PDF417;
		[Static]
		[Export ("PDF417")]
		nint PDF417 { get; }

		// +(long)DATAMATRIX;
		[Static]
		[Export ("DATAMATRIX")]
		nint DATAMATRIX { get; }

		// +(long)QR_CODE;
		[Static]
		[Export ("QR_CODE")]
		nint QR_CODE { get; }

		// +(long)UNKNOWN;
		[Static]
		[Export ("UNKNOWN")]
		nint UNKNOWN { get; }
	}

	// @interface ReadResult : NSObject
	[BaseType (typeof(NSObject))]
	interface ReadResult
	{
		// @property (nonatomic) int errorCode;
		[Export ("errorCode")]
		int ErrorCode { get; set; }

		// @property (copy, nonatomic) NSString * errorString;
		[Export ("errorString")]
		string ErrorString { get; set; }

		// @property (nonatomic) NSArray * barcodes;
		[Export ("barcodes", ArgumentSemantic.Assign)]
		Barcode[] Barcodes { get; set; }
	}

	// @interface BarcodeReader : NSObject
	[BaseType (typeof(NSObject))]
	interface BarcodeReader
	{
		// +(int)DBRERROR_OK;
		[Static]
		[Export ("DBRERROR_OK")]
		int DBRERROR_OK { get; }

		// +(int)DBRERROR_UNKNOWN;
		[Static]
		[Export ("DBRERROR_UNKNOWN")]
		int DBRERROR_UNKNOWN { get; }

		// +(int)DBRERROR_NOMEMORY;
		[Static]
		[Export ("DBRERROR_NOMEMORY")]
		int DBRERROR_NOMEMORY { get; }

		// +(int)DBRERROR_NULL_POINTER;
		[Static]
		[Export ("DBRERROR_NULL_POINTER")]
		int DBRERROR_NULL_POINTER { get; }

		// +(int)DBRERROR_LICENSE_INVALID;
		[Static]
		[Export ("DBRERROR_LICENSE_INVALID")]
		int DBRERROR_LICENSE_INVALID { get; }

		// +(int)DBRERROR_LICENSE_EXPIRED;
		[Static]
		[Export ("DBRERROR_LICENSE_EXPIRED")]
		int DBRERROR_LICENSE_EXPIRED { get; }

		// +(int)DBRERROR_BARCODE_FORMAT_INVALID;
		[Static]
		[Export ("DBRERROR_BARCODE_FORMAT_INVALID")]
		int DBRERROR_BARCODE_FORMAT_INVALID { get; }

		// +(int)DBRERROR_PARAMETER_INVALID;
		[Static]
		[Export ("DBRERROR_PARAMETER_INVALID")]
		int DBRERROR_PARAMETER_INVALID { get; }

		// -(id)initWithLicense:(NSString *)license;
		[Export ("initWithLicense:")]
		IntPtr Constructor (string license);

		// -(ReadResult *)readSingle:(UIImage *)image barcodeFormat:(long)format;
		[Export ("readSingle:barcodeFormat:")]
		ReadResult ReadSingle (UIImage image, nint format);

		// -(void)readSingleAsync:(UIImage *)image barcodeFormat:(long)format sender:(id)sender onComplete:(SEL)callback;
		[Export ("readSingleAsync:barcodeFormat:sender:onComplete:")]
		void ReadSingleAsync (UIImage image, nint format, NSObject sender, Selector callback);

		// -(ReadResult *)readSingle:(NSData *)buffer width:(int)width height:(int)height barcodeFormat:(long)format;
		[Export ("readSingle:width:height:barcodeFormat:")]
		ReadResult ReadSingle (NSData buffer, int width, int height, nint format);

		// -(void)readSingleAsync:(NSData *)buffer width:(int)width height:(int)height barcodeFormat:(long)format sender:(id)sender onComplete:(SEL)callback;
		[Export ("readSingleAsync:width:height:barcodeFormat:sender:onComplete:")]
		void ReadSingleAsync (NSData buffer, int width, int height, nint format, NSObject sender, Selector callback);
	}
}

Now you can build DBRiOS.dll.

Building a Simple iOS Barcode Reader App

Create an iOS Single View App in Visual Studio 2015 and add DBRiOS.dll to References.

Add Button, Lable and UIImageView to Main.storyboard.

xamarin storyboard

Open ViewController.cs to add the following code:

using System;

using UIKit;
using DBRiOS;

namespace BarcodeDemo
{
    public partial class ViewController : UIViewController
    {
        public ViewController(IntPtr handle) : base(handle)
        {
        }

        public override void ViewDidLoad()
        {
            base.ViewDidLoad();
            // Perform any additional setup after loading the view, typically from a nib.
        }

        public override void DidReceiveMemoryWarning()
        {
            base.DidReceiveMemoryWarning();
            // Release any cached data, images, etc that aren't in use.
        }

        partial void Button_read_TouchUpInside(UIButton sender)
        {
            BarcodeReader barcodeReader = new BarcodeReader("");
            ReadResult result = barcodeReader.ReadSingle(qrimage.Image, Barcode.QR_CODE);
            Barcode barcode = result.Barcodes[0];
            text.Text = barcode.DisplayValue;
        }

        partial void UIButton16_TouchUpInside(UIButton sender)
        {
            text.Text = "";
        }
    }
}

Build and run the iOS barcode reader on your device or simulator.

xamarin ios barcode reader

Troubleshooting

Failed to Connect Xamarin Mac Agent

xamarin failed to connect mac agent

If you suffered from the above error, check whether you have installed all required software on macOS. You can use Xamarin Installer to download xamarin.ios, xamarin.mac, Xamarin Studio, and MonoFramework. You have to ensure that matching Xamarin.iOS versions are installed on your macOS and Windows.

Couldn’t resolve address

xamarin ios connection failed

This issue is weird. Sometimes, it will succeed after rebuilding the project. Sometimes it doesn’t work at all no matter how many times you rebuild the project. If so, you probably have to re-launch Visual Studio to solve the issue.

For more information, you can read the article Connecting to Mac.

Add Native Framework Reference

Have you ever tried to add native framework reference directly? I built the same project with Windows Visual Studio and Mac Visual Studio, the result was different. Only the DBRiOS.dll built from Mac Visual Studio could work.

xamarin ios native reference windows

xamarin ios bindings library macOS

However, this way does not work perfectly. There is no ‘LinkTarget’ property. DynamsoftBarcodeReader.framework is a fat framework supporting armv7, i386, x86_64, and arm64.

xamarin ios fat framework

When building the bindings library with the framework, not all architecture slices were linked. I found the size of the generated DBRiOS.dll is much smaller than the original framework. And I can only build iOS app for the device, not the simulator.

Source Code

https://github.com/yushulx/xamarin-bind-ios-framework