March 26, 2013

Producing Better Bindings #4: Signatures


This blog post is about producing better bindings of Objective-C libraries for Xamarin.iOS and Xamarin.Mac. Read the series introduction to get a better idea why this is important and how it can save you time and headaches.

What can go wrong ?

Binding a selector using a correct [Export("")] attribute is only half the job. The .NET method signature must match the one defined in Objective-C. Xamarin.iOS does some magic, like converting string and NSString, but you still need to be careful. The most common mistakes are:

  • signed/unsigned types: this is often minor and you might want to disable the check on some (or even all) of them. CLS compliance means .NET developers prefer signed over unsigned integers (except for byte). Apple APIs often use unsigned integers when a number cannot be negative. For example:
@property (nonatomic, readwrite) NSUInteger loops;
[Export ("loops")]
int Loops { get; set; }
// should be uint, easier (less casting) if an int
  • wrong types: a common case is a missing ref when the Objective-C API requires a pointer to a value, not the value itself. For example:
-(void) sphericalRadius:(float*) r zenith:(float*) zenith azimuth:(float*) azimuth;
[Export ("sphericalRadius:zenith:azimuth:")]
void GetSphere (float radius, float zenith, float azimuth);
// wrong, references to float (ref float) should have been used
  • wrong size: this often happens on structures, but it can also occurs when a type name is misleading (e.g. long is 32 bits in Objective-C, at least for 32bits archs like i386 and ARM, while it is 64 bits on .NET) or when common usage differs, as in this case:
-(id) initWithDuration:(ccTime)duration red:(GLshort)deltaRed green:(GLshort)deltaGreen blue:(GLshort)deltaBlue;
[Export ("initWithDuration:red:green:blue:")]
IntPtr Constructor (float duration, byte red, byte green, byte blue);
// wrong, we're almost wired for bytes when seeing RGB but it does not have to be!

What can we check for ?

Quite a lot. It’s possible to get an encoded signature for selectors. The earlier loops selector, for example, would return the “I8@0:4” string value.

From it we can extract the return value (I an unsigned int) and all the parameters, e.g. self (@) and the selector (:) are always present and there are no other parameters since it’s a getter. We also get the size of the parameters, which can be compared to the size of the managed types used—and they should always match!

Using all of the above, we can test pretty closely if an Objective-C signature matches the defined .NET signature. Simple? Not quite.

Objective-C encoding for structures is quite detailed. However, their internal names won’t match the one being used in your bindings. You’ll need to help the fixture to map them by overriding its IsValidStruct method.

Why is this important ?

Signature mistakes are dangerous. Some APIs might (appear to) work, others will crash immediately (bad) or the crash will occur later (worse) because they will mess up the stack and cause memory corruption. Debugging such issues can be very time consuming, so your time is well invested in using this fixture.

How to fix issues ?

Fixing the .NET signature to match it’s Objectice-C peer is the most common case. The failure will tell you which parameter (or the return value) looks wrong. You have to confirm/fix this using the library’s documentation and/or it’s header files.

One notable (and uncommon) exception is the use of a variable number of arguments (e.g. var_list in Objective-C, params in C#). They are not part of the encoded signature, so once confirmed you can skip them by overriding the Skip(Type,MethodInfo,string) method.

As discussed earlier, you might also want to override Check(char,Type) to skip the signed/unsigned differences for integer types.

What’s missing ?

The encoded signature is not perfect. All NSObject parameters are encoded as ‘@‘ so we cannot detect if an NSData should have been used instead of NSDate. Since all objects are pointers, they all will be the same size (IntPtr.Size) so this will also not be noticed by the size check. Typos/errors, even if less common, are still possible.

This blog post concludes the five-part series on building better bindings for Xamarin.iOS and Xamarin.Mac. Thanks for reading!

Read the rest of the series:

Discuss this post on the Xamarin forums