Mixing C# with Objective-C

2019-01-30 15:02发布

I would like to use larger body of C# code as a library for Objective-C (Cocoa) application.

I discovered MonoMac project which wraps Cocoa code, but I would rather have standard Cocoa application written in Objective-C, which can call wrapped C# code (other way around).

On Windows I am used to make C++/CLI project which wraps .NET code and exports plain old C interface for C/C++ based apps.

Is there some simple way to achieve this?

2条回答
We Are One
2楼-- · 2019-01-30 15:35

If you're doing this on Mac, then yes, it's possible. On iOS not so much.

On the Mac, if you can create a CLI app in MonoMac, then you can call your CLI app from within your Objective-C app using NSTask. NSTask allows you to easily launch a commandline tool and then capture it's output and interact with it. To do this, you'd do something like:

NSArray *args = [NSArray arrayWithObjects:@"-arg1", @"-arg2", nil];
NSTask *foo = [[NSTask alloc] init];
[foo setLaunchPath:@"/usr/bin/foo"];
[foo setArguments:args];

NSPipe *pipe = [NSPipe pipe];
[foo setStandardOutput:pipe];

NSFileHandle *output = [pipe fileHandleForReading];

[foo launch];

NSData *data = [output readDataToEndOfFile];

NSString *outputStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

NSLog(@"Got stuff: %@", outputStr);

Typically, the way you want to do this, is you include the CLI app inside your app bundle. You can then get the path to the CLI app using NSBundles -pathForResource:ofType: method.

iOS doesn't include the NSTask API, so this isn't possible there. I've heard of some folks using MonoTouch to make iOS apps using C#, but as you suggested, I think you're best off sticking with Objective-C for the bulk of your app if possible. Using a CLI app like you describe is definitely an option on the Mac and can be particularly useful when you have a body of code that's already written and tested and works that you just want to "Wrap" with a Cocoa GUI.

So, NSTask is one way to do this using an external CLI executable wrapped in your app bundle. On the other hand, you might be wondering, can you link your C# code directly into Objective-C?

Well, Objective-C is a superset of C, and as such, it has all the capabilities of C. Additionally, if you use Objective-C++ it has all the capabilities of C++ as well. So, IF you can get MonoMac to generate either a C or C++ static library, then yes, you could even just link your static library with your Objective-C cocoa code, and it'll just work. I can't tell you how to make the library from MonoMac, but linking it in is just a matter of adding it to your linked libraries in your build settings in Xcode.

EDIT: I'm not familiar with C++/CLI as a language, and I misinterpreted the meaning of C++/CLI to mean "C++ Command Line Interface app". That said... the techniques I described still apply as possible methods to do what you want to do, but it is NOT using the C++/CLI interface like you can on Windows.

查看更多
虎瘦雄心在
3楼-- · 2019-01-30 15:50

There is, obviously, no such language as C++/CLI on Mac OS. On Windows, C++/CLI actually compiles as managed code ran by the CLR, that runs native code; since on Mac OS Mono isn't integrated to the system, it's rather the other way around. Your app is native, and it can host managed code.

Mono exposes functions to host a CLR virtual machine inside a process. Since CLR classes aren't directly exposed to your C code, you'll be able to call methods of objects through reflection-like calls.

There is documentation on how to embed Mono into an application on the official site. Since you're not interested in running .NET programs directly, you should rather read the "Invoking Methods in the CIL Universe" section. On Mac OS, you'll want to link against the Mono framework from your /Library/Frameworks folder, instead of using pkg-config.

This really shouldn't replace an actual reading of the above document, but the following can be seen as a guide as to what to expect:

#include <glib/glib.h>
#include <mono/jit/jit.h>
#include <mono-metadata/assembly.h>
#include <mono/metadata/debug-helpers.h>

// create an app domain
// http://en.wikipedia.org/wiki/Application_Domain
MonoDomain* domain = mono_jit_init("Domain");

// mandatory Cocoa call to show that Mono and ObjC work together
NSBundle* mainBundle = [NSBundle mainBundle];
NSString* dll = [mainBundle pathForResource:@"your-dll" ofType:@"dll"];

// load the referenced assembly in our domain
MonoAssembly* assembly = mono_domain_assembly_open(domain, [dll UTF8String]);
MonoImage* image = mono_assembly_get_image(assembly);

// find the class we want to wrap and create an uninitialized instance
MonoClass* classHandle = mono_class_from_name(image, "Name.Space", "YourClass");
MonoObject* object = mono_object_new(domain, classHandle);

// this calls the default, argument-less ctor
// for more complex constructors, you need to find the method handle and call it
// (helpful hint: constructors are internally called ".ctor", so the description
// string will look like "Namespace.Class..ctor()")
mono_runtime_object_init(object);

// get a method handle to whatever you like
const char* descAsString = "Your.NameSpace.YourClass:YourMethod()";
MonoMethodDesc* description = mono_method_desc_new(descAsString);
MonoMethod* method = mono_method_desc_search_in_class(description, classHandle);

// call it
void* args[0];
mono_runtime_invoke(method, object, args, NULL);

// when you're done, shutdown the runtime by destroying the app domain
mono_jit_cleanup(domain);

If you don't find this very appealing, you may want to go the other way around, as you mentioned, and look into MonoMac, which provides .NET bindings to a large portion of the APIs you may want to use in a Mac application (Cocoa, CoreImage, CoreAnimation, etc) and means to create your own bindings.

查看更多
登录 后发表回答