Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
The Objective-C language is based upon selectors. A selector is a message that can be sent to an object or a class. Xamarin.iOS maps instance selectors to instance methods, and class selectors to static methods.
Unlike normal C functions (and like C++ member functions), you cannot
directly invoke a selector using
P/Invoke Instead,
selectors are sent to an Objective-C class or instance using the
objc_msgSend
function.
For more information about messages in Objective-C, take a look at Apple's Working with Objects guide.
Example
Suppose you want to invoke the
sizeWithFont:forWidth:lineBreakMode:
selector on NSString.
The declaration (from Apple's documentation) is:
- (CGSize)sizeWithFont:(UIFont *)font forWidth:(CGFloat)width lineBreakMode:(UILineBreakMode)lineBreakMode
This API has the following characteristics:
- The return type is
CGSizefor the Unified API. - The
fontparameter is a UIFont (and a type (indirectly) derived from NSObject, and is mapped to System.IntPtr. - The
widthparameter, aCGFloat, is mapped tonfloat. - The
lineBreakModeparameter, aUILineBreakMode, has already been bound in Xamarin.iOS as theUILineBreakModeenumeration.
Putting it all together, the objc_msgSend declaration should match:
CGSize objc_msgSend(
IntPtr target,
IntPtr selector,
IntPtr font,
nfloat width,
UILineBreakMode mode
);
Declare it as follows:
[DllImport (Constants.ObjectiveCLibrary, EntryPoint="objc_msgSend")]
static extern CGSize cgsize_objc_msgSend_IntPtr_float_int (
IntPtr target,
IntPtr selector,
IntPtr font,
nfloat width,
UILineBreakMode mode
);
To call this method, use code such as the following:
NSString target = ...
Selector selector = new Selector ("sizeWithFont:forWidth:lineBreakMode:");
UIFont font = ...
nfloat width = ...
UILineBreakMode mode = ...
CGSize size = cgsize_objc_msgSend_IntPtr_float_int(
target.Handle,
selector.Handle,
font == null ? IntPtr.Zero : font.Handle,
width,
mode
);
Had the returned value been a structure that was less than 8 bytes in size (like the older SizeF used before switching to the Unified APIs), the above code would have run on the simulator but crashed on the device. To call a selector that returns a value less than 8 bits in size, declare the objc_msgSend_stret function:
[DllImport (MonoTouch.Constants.ObjectiveCLibrary, EntryPoint="objc_msgSend_stret")]
static extern void cgsize_objc_msgSend_stret_IntPtr_float_int (
out CGSize retval,
IntPtr target,
IntPtr selector,
IntPtr font,
nfloat width,
UILineBreakMode mode
);
To call this method, use code such as the following:
NSString target = ...
Selector selector = new Selector ("sizeWithFont:forWidth:lineBreakMode:");
UIFont font = ...
nfloat width = ...
UILineBreakMode mode = ...
CGSize size;
if (Runtime.Arch == Arch.SIMULATOR)
size = cgsize_objc_msgSend_IntPtr_float_int(
target.Handle,
selector.Handle,
font == null ? IntPtr.Zero : font.Handle,
width,
mode
);
else
cgsize_objc_msgSend_stret_IntPtr_float_int(
out size,
target.Handle, selector.Handle,
font == null ? IntPtr.Zero: font.Handle,
width,
mode
);
Invoking a selector
Invoking a selector has three steps:
- Get the selector target.
- Get the selector name.
- Call
objc_msgSendwith the appropriate arguments.
Selector targets
A selector target is either an object instance or an Objective-C class. If
the target is an instance and came from a bound Xamarin.iOS type, use the ObjCRuntime.INativeObject.Handle property.
If the target is a class, use ObjCRuntime.Class to get a reference to the class
instance, then use the Class.Handle property.
Selector names
Selector names are listed in Apple's documentation. For example, NSString includes sizeWithFont: and sizeWithFont:forWidth:lineBreakMode: selectors. The embedded and trailing colons are part of the selector name and cannot be omitted.
Once you have a selector name, you can create a ObjCRuntime.Selector instance for it.
Calling objc_msgSend
objc_msgSend sends a message (selector) to an object. This family of
functions takes at least two required arguments: the selector target (an
instance or class handle), the selector itself, and any arguments
required for the selector. The instance and selector arguments must be
System.IntPtr, and all remaining arguments must match the type the
selector expects, for example an nint for an int, or a
System.IntPtr for all NSObject-derived types. Use the
NSObject.Handle
property to obtain an IntPtr for an Objective-C type instance.
There is more than one objc_msgSend function:
- Use
objc_msgSend_stretfor selectors that return a struct. On ARM, this includes all return types that are not an enumeration or any of the C built-in types (char,short,int,long,float,double). On x86 (the simulator), this method needs to be used for all structures larger than 8 bytes in size (CGSizeis 8 bytes and doesn't useobjc_msgSend_stretin the simulator). - Use
objc_msgSend_fpretfor selectors that return a floating point value on x86 only. This function does not need to be used on ARM; instead, useobjc_msgSend. - The main objc_msgSend function is used for all other selectors.
Once you've decided which objc_msgSend function(s) you need to call
(simulator and device may each require a different method), you can use
a normal [DllImport]
method to declare the function for later invocation.
A set of pre-made objc_msgSend declarations can be found in
ObjCRuntime.Messaging.
Different invocations on simulator and device
As described above, Objective-C has three kinds of objc_msgSend
methods: one for regular invocations, one for invocations that return
floating point values (x86 only), and one for invocations that return
struct values. The latter includes the suffix _stret in
ObjCRuntime.Messaging.
If you are invoking a method that will return certain structs (rules
described below), you must invoke the method with the return value as the first
parameter as an out value:
// The following returns a PointF structure:
PointF ret;
Messaging.PointF_objc_msgSend_stret_PointF_IntPtr (out ret, this.Handle, selConvertPointFromWindow.Handle, point, window.Handle);
The rule for when to use the _stret_ method differs on x86 and ARM.
If you want your bindings to work on both the simulator and the device,
add code such as the following:
if (Runtime.Arch == Arch.DEVICE)
{
PointF ret;
Messaging.PointF_objc_msgSend_stret_PointF_IntPtr (out ret, myHandle, selector.Handle);
return ret;
}
else
{
return Messaging.PointF_objc_msgSend_PointF_IntPtr (myHandle, selector.Handle);
}
Using the objc_msgSend_stret method
When building for ARM, use the
objc_msgSend_stret
for any value type that is not an enumeration or any of the base types
for an enumeration (int, byte, short, long, double, float).
When building for x86, use
objc_msgSend_stret
for any value type that is not an enumeration or any of the base types
for an enumeration (int, byte, short, long, double, float)
and whose native size is larger than 8 bytes.
Creating your own signatures
The following gist can be used to create your own signatures, if required.