(13 Sep 2015) This article is fairly old. See https://developer.xamarin.com/samples/mac/XMBindingExample/ for a more up-to-date sample that works with the Unified API. And see http://developer.xamarin.com/guides/ios/advanced_topics/binding_objective-c/objective_sharpie/ for the current Objective Sharpie documentation.
Let's start by making our very own Objective-C framework project in Xcode. That way we can look at exactly how the Xcode side lines up with the Xamarin side.
Select the Cocoa Framework
template project.
Follow the prompts to finish setting up the library. I'll call the framework Simple
for this example.
Add a method to the default class (in Simple.m
). For example something like this will be fine:
@implementation Simple - (NSString *)run:(BOOL *)successful { *successful = YES; return @"OK"; } @end
Add a corresponding method declaration in the header file (Simple.h
):
@interface Simple : NSObject - (NSString *)run:(BOOL *)successful; @end
Add the header file to the Public
section of the Copy Headers
build phase. This will copy Simple.h
into the Simple.framework/Headers
directory, so that end users will have access to header. This is the conventional setup for pre-built frameworks that you download.
Set Build Settings -> Architectures -> Architectures to 32-bit Intel (i386). This is required because Xamarin.Mac does not yet support 64-bit libraries.
Again under Build Settings, disable Automatic Reference Counting.
This is necessary because the 32-bit legacy
Objective-C runtime does not support ARC. Without this change, Xcode will hit an error during compilation:
error: -fobjc-arc is not supported on platforms using the legacy runtime
Build!
You can now build the project in Xcode via Product -> Build. If you like, you can first set Xcode to use the Release
configuration under Product -> Scheme -> Edit Scheme -> Run -> Info [tab] -> Build Configuration -> Release. If you'd rather build from the command line, that's fine too:
/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild -project Simple.xcodeproj -target Simple -sdk macosx10.9 -configuration Release clean build
The compiled framework will now be present in:
Built using Xcode GUI: | ~/Library/Developer/Xcode/DerivedData/Simple-${UUID}/Build/Products/Release/Simple.framework |
Built using example commandline: | build/Release/Simple.framework |
Create a new Xamarin.Mac application project.
For convenience, copy the Simple.framework
directory into the project folder, for example into a lib/
subdirectory.
Confirm that Mono can load the dynamic library from the compiled framework. To do this, add a few lines like these at the beginning of the Main()
method:
if (Dlfcn.dlopen("/Users/username/Projects/MacApp1/MacApp1/lib/Simple.framework/Simple", 0) == IntPtr.Zero) { Console.Error.WriteLine("Unable to load the dynamic library."); }
Then try running the app. If you hit the error, double-check that the binary for the library is 32-bit. For example, you can use lipo
on the command line:
lipo -i Simple.framework/Simple
Non-fat file: Simple.framework/Simple is architecture: i386
(13 Sep 2015) This section is out-of-date for both Unified and Classic API bindings. See http://developer.xamarin.com/guides/ios/advanced_topics/binding_objective-c/objective_sharpie/ for the current Objective Sharpie documentation.
Given that our example framework only contains one class and one method, we could easily write the binding definition by hand, but let's use Objective Sharpie so that we can get an idea of how that tool works too.
Download and unzip Objective Sharpie.
Create a prefix header that matches Simple/Simple/Simple/Simple-Prefix.pch
. For example, create a file named prefix.pch
with the following contents:
#import <Cocoa/Cocoa.h>
On the first window of the Objective Sharpie wizard, select a target OS X framework, and add the prefix header under extra clang command line arguments.
-include /Users/username/Projects/MacApp1/MacApp1/lib/prefix.pch
On the next window, add the Simple.h
header file from the library, and leave Follow #include and #import directives
ON.
Complete the Objective Sharpie wizard by entering a namespace, selecting Generate, and saving the resulting .cs
file.
Create the prefix header file (prefix.pch
) as described for the GUI, and then run sharpie
from the command line:
LIB_DIR=/Users/username/Projects/MacApp1/MacApp1/lib
sharpie -sdk:macosx10.9 -follow-includes -scope:"$LIB_DIR/Simple.framework" "$LIB_DIR/Simple.Framework/Headers/Simple.h" -n Simple -clang -F "$LIB_DIR" -include "$LIB_DIR/prefix.pch" > "$LIB_DIR/SimpleBindingDefinition.cs"
The prefix header tells clang
to import Cocoa/Cocoa.h
implicitly in every source file. In our sample based on the default Xcode framework template project, this isn't strictly necessary because Simple.h
already explicitly imports Foundation/Foundation.h
. But some pre-compiled frameworks depend on the prefix header. We can test this problem by deleting the Foundation/Foundation.h
import from Simple.h
. If we then run sharpie again, without the -import
argument, clang
produces a few errors:
A/Headers/Simple.h:9:21: error: cannot find interface declaration for 'NSObject', superclass of 'Simple' @interface Simple : NSObject ~~~~~~~~~~~~~~~~~ ^ /Users/username/Projects/MacApp1/MacApp1/lib/Simple.framework/Versions/A/Headers/Simple.h:10:4: error: expected a type - (NSString *)run:(BOOL *)successful; ^ /Users/username/Projects/MacApp1/MacApp1/lib/Simple.framework/Versions/A/Headers/Simple.h:10:20: error: expected a type - (NSString *)run:(BOOL *)successful;
.dll
with bmac
Now that we have a binding definition file, we can try running bmac
on it to create the binding library .dll
. Since Objective Sharpie only provides a rough draft of the definition, some hand-adjustments are usually necessary at this step.
Unmapped pointer
/Library/Frameworks/Xamarin.Mac.framework/Versions/Current/usr/bin/bmac -oSimple.dll --tmpdir=/tmp -baselib=/Library/Frameworks/Xamarin.Mac.framework/Versions/Current/usr/lib/mono/XamMac.dll -r=System.Drawing SimpleBindingDefinition.cs
SimpleBindingDefinition.cs(9,16): warning CS0658: `unmapped' is invalid attribute target. All attributes in this attribute section will be ignored
SimpleBindingDefinition.cs(9,33): warning CS0658: `:' is invalid attribute target. All attributes in this attribute section will be ignored
SimpleBindingDefinition.cs(9,41): error CS1525: Unexpected symbol `]', expecting `(', `,', `.', or `]'
These warning and errors are caused by line 9 in the binding definition:
string Run ([unmapped: pointer: Pointer] successful);
The problem is that the BOOL*
trips up Objective Sharpie. Let's change this to a ref bool
:
string Run (ref bool successful);
using MonoMac.Foundation;
SimpleBindingDefinition.cs(5,21): error CS0246: The type or namespace name `NSObject' could not be found. Are you missing `MonoMac.Foundation' using directive?
SimpleBindingDefinition.cs(8,4): error CS0246: The type or namespace name `Export' could not be found. Are you missing an assembly reference?
Simply add the required using
line:
using MonoMac.Foundation;
After these two small changes, the binding builds successfully.
We can take a look at the result by opening Simple.dll
in Xamarin Studio's Assembly Browser:
One interesting thing to note here is the SimpleBindingDefinition
namespace. This namespace contains the helper classes
for the binding. By default, bmac
uses the name of the binding definition file for this namespace, but this can sometimes cause trouble.
As an experiment, let's see what would have happened if we had named the binding definition Simple.cs
:
/Library/Frameworks/Xamarin.Mac.framework/Versions/Current/usr/bin/bmac -oSimple.dll --tmpdir=/tmp -baselib=/Library/Frameworks/Xamarin.Mac.framework/Versions/Current/usr/lib/mono/XamMac.dll -r=System.Drawing Simple.cs
/tmp/Simple/Simple.g.cs(79,40): error CS0117: `Simple.Simple' does not contain a definition for `Messaging'
/tmp/Simple/Simple.g.cs(29,30): (Location of the symbol related to previous error)
/tmp/Simple/Simple.g.cs(81,40): error CS0117: `Simple.Simple' does not contain a definition for `Messaging'
/tmp/Simple/Simple.g.cs(29,30): (Location of the symbol related to previous error)
What's happening here is that at two places in the generated Simple.g.cs
file, Messaging
is referred to as Simple.Messaging
rather than global::Simple.Messaging
. Since these references occur within a class named Simple
, the compiler tries to look them up within that class rather than within the enclosing namespace.
The easiest workaround is to change the namespace for the helper classes. Renaming the file is one option, but bmac
also has a command line argument for this:
--ns=VALUE Sets the namespace for storing helper classes
For example, this will work:
/Library/Frameworks/Xamarin.Mac.framework/Versions/Current/usr/bin/bmac -oSimple.dll --tmpdir=/tmp -baselib=/Library/Frameworks/Xamarin.Mac.framework/Versions/Current/usr/lib/mono/XamMac.dll -r=System.Drawing Simple.cs --ns=SimpleHelpers
A different, trickier workaround is to fix-up the generated Simple.g.cs
file by hand. This wouldn't be much fun for a large binding, but it's easy to do for this small example binding, and gives us a little more insight into the binding process.
Run bmac
with additional verbose output:
/Library/Frameworks/Xamarin.Mac.framework/Versions/Current/usr/bin/bmac -oSimple.dll --tmpdir=/tmp -baselib=/Library/Frameworks/Xamarin.Mac.framework/Versions/Current/usr/lib/mono/XamMac.dll -r=System.Drawing Simple.cs -v
Copy the final compiler invocation from the verbose output:
mcs -unsafe -target:library -out:'Simple.dll' /tmp/ObjCRuntime/Messaging.g.cs /tmp/Simple/Simple.g.cs -r:'System.Drawing' -r:'/Library/Frameworks/Xamarin.Mac.framework/Versions/Current/usr/lib/mono/XamMac.dll'
Edit /tmp/Simple/Simple.g.cs
, and replace the two occurrences of (Simple.Messaging
with (global::Simple.Messaging
.
Simple.Simple.Run(ref bool)
in the Xamarin.Mac appAdd a reference to the newly created Simple.dll
.
Keep the Dlfcn.dlopen()
method from earlier.
Add a few lines to test out the Simple.Simple.Run(ref bool)
method.
All together, it might look something like this:
using System; using MonoMac.ObjCRuntime; using MonoMac.AppKit; namespace MacApp1 { class MainClass { static void Main(string[] args) { if (Dlfcn.dlopen("/Users/username/Projects/MacApp1/MacApp1/lib/Simple.framework/Simple", 0) == IntPtr.Zero) { Console.Error.WriteLine("Unable to load the dynamic library."); } bool success = false; string result = (new Simple.Simple()).Run(ref success); Console.WriteLine("Ran the Objective-C method with return value: {0} and success state: {1}", result, success); NSApplication.Init(); NSApplication.Main(args); } } }
Now run the app! With a little luck, the console output will show that the method ran successfully:
Ran the Objective-C method with return value: OK and success state: True
Submit a comment or correction
Copyright (c) 2014 Xamarin Inc http://www.xamarin.com
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
13 Sep 2015 | Added notes about up-to-date sample and new Objective Sharpie docs. |
01 May 2014 | Posted |