Xamarin.Mac bindings in console apps
There are some scenarios where you want to use some of Apple native APIs in C# to build a headless application – one that does not have a user interface – using C#.
The project templates for Mac applications include a call to NSApplication.Init()
followed by a call to NSApplication.Main(args)
, it usually looks like this:
static class MainClass {
static void Main (string [] args)
{
NSApplication.Init ();
NSApplication.Main (args);
}
}
The call to Init
prepares the Xamarin.Mac runtime, the call to Main(args)
starts the Cocoa application main loop, which prepares the application to receive keyboard and mouse events and show the main window of your application. The call to Main
will also attempt to locate Cocoa resources, prepare a toplevel window and expects the program to be part of an application bundle (programs distributed in a directory with the .app
extension and a very specific layout).
Headless applications do not need a user interface, and do not need to run as part of an application bundle.
Creating the console app
So it is better to start with a regular .NET Console project type.
You need to do a few things:
- Create an empty project.
- Reference the Xamarin.Mac.dll library.
- Bring the unmanaged dependency to your project.
These steps are explained in more detail below:
Create an empty Console Project
Create a new .NET Console Project, make sure that it is .NET and not .NET Core, as Xamarin.Mac.dll does not run under the .NET Core runtime, it only runs with the Mono runtime.
Reference the Xamarin.Mac library
To compile your code, you will want to reference the Xamarin.Mac.dll
assembly from this directory:
/Library/Frameworks/Xamarin.Mac.framework/Versions/Current/lib/64bits/full
To do this, go to the project references, select the .NET Assembly tab, and click the Browse button to locate the file on the file system. Navigate to the path above, and then select the Xamarin.Mac.dll from that directory.
This will give you access to the Cocoa APIs at compile time. At this point, you can add using AppKit
to the top of your file, and call the NSApplication.Init()
method. There is only one more step before you can run your application.
Bring the unmanaged support library into your project
Before your application will run, you need to bring the Xamarin.Mac
support library into your project. To do this, add a new file to your project (in project options, select Add, and then Add Existing File) and navigate to this directory:
/Library/Frameworks/Xamarin.Mac.framework/Versions/Current/SDKs/Xamarin.macOS.sdk/lib
Here, select the file libxammac.dylib. You will be offered a choice of copying, linking or moving. I personally like linking, but copying works as well. Then you need to select the file, and in the property pad (select View>Pads>Properties if the property pad is not visible), go to the Build section and set the Copy to Output Directory setting to Copy if newer.
You can now run your Xamarin.Mac application.
The result in your bin directory will look like this:
Xamarin.Mac.dll
Xamarin.Mac.pdb
consoleapp.exe
consoleapp.pdb
libxammac.dylib
To run this app, you will need all those files in the same directory.
Building a standalone application for distribution
You might want to distribute a single executable to your users. To do this, you can use the mkbundle
tool to turn the various files into a self-contained executable.
First, make sure that your application compiles and runs. Once you are satisfied with the results, you can run the following command from the command line:
$ mkbundle --simple -o /tmp/consoleapp consoleapp.exe --library libxammac.dylib --config /Library/Frameworks/Mono.framework/Versions/Current/etc/mono/config --machine-config /Library/Frameworks/Mono.framework/Versions/Current/etc/mono/4.5/machine.config
[Output from the bundling tool]
$ _
In the above command line invocation, the option -o
is used to specify the generated output, in this case, we passed /tmp/consoleapp
. This is now a standalone application that you can distribute and has no external dependencies on Mono or Xamarin.Mac, it is a fully self contained executable.
The command line manually specified the machine.config file to use, and a system-wide library mapping configuration file. They are not necessary for all applications, but it is convenient to bundle them, as they are used when you use more capabilities of .NET
Project-less builds
You do not need a full project to create a self-contained Xamarin.Mac application, you can also use simple Unix makefiles to get the job done. The following example shows how you can setup a makefile for a simple command line application:
XAMMAC_PATH=/Library/Frameworks/Xamarin.Mac.framework/Versions/Current/lib/x86_64/full/
DYLD=/Library/Frameworks/Xamarin.Mac.framework/Versions/Current/lib
MONODIR=/Library/Frameworks/Mono.framework/Versions/Current/etc/mono
all: consoleapp.exe
consoelapp.exe: consoleapp.cs Makefile
mcs -g -r:$(XAMMAC_PATH)/Xamarin.Mac.dll consoleapp.cs
run: consoleapp.exe
MONO_PATH=$(XAMMAC_PATH) DYLD_LIBRARY_PATH=$(DYLD) mono --debug consoleapp.exe $(COMMAND)
bundle: consoleapp.exe
mkbundle --simple consoleapp.exe -o ncsharp -L $(XAMMAC_PATH) --library $(DYLD)/libxammac.dylib --config $(MONODIR)/config --machine-config $(MONODIR)/4.5/machine.config
The above Makefile
provides three targets:
make
will build the programmake run
will build and run the program in the current directorymake bundle
will create a self-contained executable