Search Unity

Immediate calls from Unity to Objective-C... and vice versa!

Discussion in 'iOS and tvOS' started by jerrodputman, Jan 10, 2010.

  1. jerrodputman

    jerrodputman

    Joined:
    Jun 4, 2008
    Posts:
    181
    I just posted up a post on our website detailing methods for immediate function calls from Unity to Objective-C and vice versa, for both Advanced and Basic licensees.

    The post can be found here: http://www.tinytimgames.com/2010/01/10/the-unityobjective-c-divide/

    For Advanced users, you already know how to call Objective-C code from Unity script. And for Basic users, you've been getting by with the PlayerPrefs trick. The post expands on that trick by introducing Key-Value Observing to the mix (which I believe hasn't been discussed much here on the forums; if it has, oh well, you can skip that part).

    The real meat is in the area which details function calls from Objective-C back into Unity. Essentially, all of the information was gathered from this Mono document on Embedding Mono, and knowing that Unity uses Mono I put two and two together.

    Hopefully this will help some of you gain a little extra flexibility when working with Objective-C code.

    EDIT: I should mention some of this is advanced stuff, and a good knowledge of how languages like C work is probably necessary.
     
  2. dcp468

    dcp468

    Joined:
    Aug 25, 2009
    Posts:
    129
    Thank you!!! I have a basic license and I'm still new to Unity so I'm busy learning the ropes, and thus haven't done much research in this particular area (note: I've looked at the enhancement pack stuff) however could you explain what features you might try to implement in your game from knowing this so I can better understand what's possible!

    Thank again.
     
  3. jerrodputman

    jerrodputman

    Joined:
    Jun 4, 2008
    Posts:
    181
    Well, just off the top of my head, the Objective-C-to-Unity stuff could be used to trigger a Unity script function when you have successfully connected to another player using the Bluetooth GameKit. You might even be able to use it to call a script function when you receive data from the other player, though it may be too slow for that (haven't tested it).

    For the most part, going from Unity-to-Objective-C will suffice for most things, but this gives you flexibility so that you can use it for whatever crazy stuff you happen to come up with.
     
  4. RazorCut

    RazorCut

    Joined:
    May 7, 2009
    Posts:
    393
    Well done!
     
  5. prime31

    prime31

    Joined:
    Oct 9, 2008
    Posts:
    6,426
    I'd be very interested in a benchmark to see how fast/slow this is. Has anyone timed some calls?
     
  6. Treva

    Treva

    Joined:
    Aug 26, 2009
    Posts:
    17
    This is really helpful. Thanks.
     
  7. robotive

    robotive

    Joined:
    Apr 17, 2009
    Posts:
    59
    i'm not sure that Key-Value Observing works for NSUserDefaults. At least it doesn't work for me.

    i receive:

    Code (csharp):
    1.  
    2. Warning: NSUserDefaults may not respond to addObserver ... bla, bla, bla...
    3.  
    and it crashes with:
    Code (csharp):
    1.  
    2. [NSUserDefaults addObserver:forKeyValue:options:context:]: unrecognized selector sent to instance 0x14606d0
    3.  
     
  8. prime31

    prime31

    Joined:
    Oct 9, 2008
    Posts:
    6,426
    Works fine for me. I use this all the time. UserDefaults are fully KVO compliant.

    Code (csharp):
    1. [[NSUserDefaults standardUserDefaults] addObserver:self forKeyPath:@"SomeKeyPath" options:0 context:nil];
     
  9. robotive

    robotive

    Joined:
    Apr 17, 2009
    Posts:
    59
    ahh, found the problem:

    i was typing forKeyValue instead of forKeyPath.
     
  10. jerrodputman

    jerrodputman

    Joined:
    Jun 4, 2008
    Posts:
    181
    @robotive: Whoops, that was an error in my post. I've fixed that.

    @uprise78: By the way, thanks for mentioning KVO in another thread. Just a few days ago when I first started writing this post, I was just using NSNotificationCenter to detect when ANY NSUserDefaults were being changed, so the command parser was getting called many more times than it needed to. It worked fine, but now that I know about KVO, it only gets called when it needs to.
     
  11. refresh

    refresh

    Joined:
    May 14, 2009
    Posts:
    20
    Hi, Thanks a lot for the tips. I'm trying to get the Obj-C to Unity communication going. except that I'm not very advanced with my with C and I have not been able to get it to work yet.
    I set up a simple class in unity with a static method to test with but I'm not sure where all the code i supposed to go from your tutorial
    Are there any includes that are needed?

    I tried putting this chunk in the @implementation section off the AppController that Unity builds, :

    Code (csharp):
    1. typedef void* MonoDomain;
    2. typedef void* MonoAssembly;
    3. typedef void* MonoImage;
    4. typedef void* MonoClass;
    5. typedef void* MonoObject;
    6. typedef void* MonoMethodDesc;
    7. typedef void* MonoMethod;
    8. typedef int gboolean;
    9.  
    10. MonoDomain *mono_domain_get();
    11. MonoAssembly *mono_domain_assembly_open(MonoDomain *domain, const char *assemblyName);
    12. MonoImage *mono_assembly_get_image(MonoAssembly *assembly);
    13. MonoMethodDesc *mono_method_desc_new(const char *methodString, gboolean useNamespace);
    14. MonoMethodDesc *mono_method_desc_free(MonoMethodDesc *desc);
    15. MonoMethod *mono_method_desc_search_in_image(MonoMethodDesc *methodDesc, MonoImage *image);
    16. MonoObject *mono_runtime_invoke(MonoMethod *method, void *obj, void **params, MonoObject **exc);
    Then I tried putting this in the method I'm calling in the AppController that I want to call Unity:

    Code (csharp):
    1. MonoDomain *domain = mono_domain_get();
    2.     NSString *assemblyPath = [[[NSBundle mainBundle] bundlePath]
    3.                               stringByAppendingPathComponent:@"Data/Assembly - CSharp.dll"];
    4.     MonoAssembly *assembly = mono_domain_assembly_open(domain, assemblyPath.UTF8String);
    5.     MonoImage *image = mono_assembly_get_image(assembly);
    6.     MonoMethodDesc *desc = mono_method_desc_new("TempoModel:CallFromCocoa()", FALSE);
    7.     MonoMethod *method = mono_method_desc_search_in_image(desc, image);
    8.     mono_method_desc_free(desc);
    9.     mono_runtime_invoke(method, NULL, NULL, NULL);
    However I'm getting apparent Linking errors when I try to build like this:
    Code (csharp):
    1. "mono_assembly_get_image(void**)", referenced from:
    Any Ideas on what I'm doing wrong? Any help or an example would be very much appreciated, I realize I'm not that advanced but if I can just get a few methods firing in Unity without running a loop to check playerprefs I'll be very happy.

    thanks for your post and ideas on this.
     
  12. jerrodputman

    jerrodputman

    Joined:
    Jun 4, 2008
    Posts:
    181
    @refresh: If you're putting this into AppController.mm (which is an Objective-C++ file), you need to wrap the typedefs and function prototypes (the first chunk of code you posted) in the following:

    Code (csharp):
    1.  
    2. extern "C"
    3. {
    4.    ...
    5. }
    6.  
    That should take care of the linker errors.
     
  13. refresh

    refresh

    Joined:
    May 14, 2009
    Posts:
    20
    Thanks that did the trick, now I am able to call unity functions from my obj c classes instantly. Awesome!!! :D
     
  14. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    looking forward to hear if it boomed up and alike :)
     
  15. prime31

    prime31

    Joined:
    Oct 9, 2008
    Posts:
    6,426
    Has anyone benchmarked the calls yet? Can you get away with using them judiciously or is the C to Mono bridge too expensive?
     
  16. Treva

    Treva

    Joined:
    Aug 26, 2009
    Posts:
    17
    I've gotten this error message after calling the monomethod.
    Does anyone know what is the cause?
     
  17. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    you try to invoke a method on a NULL object instead a valid object reference
     
  18. Treva

    Treva

    Joined:
    Aug 26, 2009
    Posts:
    17
    I see. Thanks for the answer. It must be cause my C# function isn't static.

    -Edited- Got rid of the previous error.

    Now i have another problem, how do i pass the received value from Xcode and send to to a Javascript? I tried sendmessage but it gives me SIGBUS error. Thanks.
     
  19. pjohnsen

    pjohnsen

    Joined:
    Oct 22, 2009
    Posts:
    15
    Has anyone had any luck making callbacks to instance methods? I'm trying to figure out how to pass the instance from C# to C.

    Code (csharp):
    1. System.Runtime.InteropServices.GCHandle.ToIntPtr(GCHandle value)
    2.  
    3. and
    4.  
    5. MonoObject* mono_gchandle_get_target (uint32_t gchandle);
    looked promising but GCHandle.ToIntPtr doesn't seem to be available in Unity's Mono :(
     
  20. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    You can not use callbacks on unity iphone
     
  21. pjohnsen

    pjohnsen

    Joined:
    Oct 22, 2009
    Posts:
    15
  22. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    Unity does not use monotouch or the iphone mono at all, they developped it themself and released it before mono even had anything running stable with aot on the iphone :) (unity iphone was out a good year before MonoTouch when I remember correctly)


    Also Unity does not use a mono version thats compatible with the current one in the current versions (Unity 2.x, Unity iPhone 1.x) so if they upgrade to include such stuff, then likely not before Unity iPhone 2.0+ (backporting makes little sense as Mono 1.2.5 has by default enough problems with process and async stuff, such callbacks would basically put a significant unbearable risk on your stability)
     
  23. pjohnsen

    pjohnsen

    Joined:
    Oct 22, 2009
    Posts:
    15
    I realize monotouch is a very different beast than unity, I'm just saying that it would be nice to include this WHEN they do a new version, so we don't have to mess around with calling the mono runtime directly as the original poster describes.

    I am doing this now and it actually works just fine .. it's just a bit more cumbersome than I think it should be.

    Just my 2 cents.
     
  24. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    That it would be nice is out of question :)
    Would save quite a bit of headache for anything that bases on handlers on the objc side.

    But I'm not keen that it is smart to use it cause the only reason it works is that the iPhone has a single threaded gpu at the time.
    The moment it has dual threaded cpus / dual core (-> Arm9 or Apples A4), I see a very high likelyhood that this hack blows up the whole application as you get real concurrent access
     
  25. pjohnsen

    pjohnsen

    Joined:
    Oct 22, 2009
    Posts:
    15
    Now Unity iPhone 1.6 is out :)

    It has SendMessage from native code to Unity. However this is asynchronous and only allows to send one string param.

    In my case I need the return value from C#, so I'm still doing the callback using the mono runtime as described in the original post and this also works in 1.6.

    I found a couple more useful mono functions, thought it might help others:

    Code (csharp):
    1. MonoObject *mono_string_new(MonoDomain *domain, const char* str);
    2. void *mono_object_unbox(MonoObject *obj);
    3.  
     
  26. eviltenchi_84

    eviltenchi_84

    Joined:
    Feb 18, 2010
    Posts:
    99
    Did iOS 4.2 change something? My function that calls back to Unity from Xcode is no longer functioning since I upgraded my device. The game just crashes now with a EXC_BAD_ACCESS and I changed nothing. Or perhaps did something change with Mono in the latest Unity version?


    Code (csharp):
    1. - (void)sendCurrency
    2. {
    3.     MonoDomain *domain = mono_domain_get();
    4.     NSString *assemblyPath = [[[NSBundle mainBundle] bundlePath]
    5.                               stringByAppendingPathComponent:@"Data/Assembly - CSharp.dll"];
    6.     MonoAssembly *assembly = mono_domain_assembly_open(domain, assemblyPath.UTF8String);
    7.     MonoImage *image = mono_assembly_get_image(assembly);
    8.     MonoMethodDesc *desc = mono_method_desc_new("BuyCurrency:addCurrency(int)", FALSE);
    9.     MonoMethod *method = mono_method_desc_search_in_image(desc, image);
    10.     mono_method_desc_free(desc);
    11.     int param1 = 1;
    12.     void *args[] = { &param1 };
    13.     mono_runtime_invoke(method, NULL, args, NULL); 
    14. }
    It looks like the line: MonoImage *image = mono_assembly_get_image(assembly); is where it is crashing. Any ideas?
     
    Last edited: Nov 24, 2010
  27. eviltenchi_84

    eviltenchi_84

    Joined:
    Feb 18, 2010
    Posts:
    99
    bump, i really need some help on this asap.
     
  28. DaveG

    DaveG

    Joined:
    Mar 12, 2009
    Posts:
    70
  29. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    the path used to search is incorrect for U3
     
  30. eviltenchi_84

    eviltenchi_84

    Joined:
    Feb 18, 2010
    Posts:
    99
    I wish that was documented somewhere (unless I am missing something and it was documented in the U3 changelog). Anyway, it is resolved now, the path is correct.
     
  31. FragomirJagr

    FragomirJagr

    Joined:
    Feb 3, 2009
    Posts:
    12
    I love the concept here, as it is a perfect solution for what I am trying to accomplish. Unfortunately, I cannot seem to get the assembly. It returns NULL. I am using this code:


    Code (csharp):
    1.        
    2.     MonoDomain  *domain = mono_domain_get();
    3.        
    4.     if (domain) {
    5.         NSString *assemblyPath = [[[NSBundle mainBundle] bundlePath]
    6.           stringByAppendingPathComponent:@"Data/Assembly-CSharp-firstpass.dll"];
    7.            
    8.         if (assemblyPath) {
    9.             MonoAssembly *assembly = mono_domain_assembly_open(domain,             assemblyPath.UTF8String);
    10.             if (assembly) {
    11.                 MonoImage *image = mono_assembly_get_image(assembly);
    12.             } else {
    13.                 <Oops... log error here>
    14.             }
    15.         }
    16.     }
    17.  
    I am getting a domain, and an assemblyPath, but not an assembly. According to Unity, my script with the method I wish to invoke is in
    Assembly-CSharp-firstpass.dll, but the mono_domain_assembly_open with the parameters domain, assemblyPath.UTF8String returns NULL for the assembly......... I can't figure out what I am doing wrong.

    Oh yeah, I did wrap the prototypes and typedefs in extern "C" so it isn't that...

    I hope you have an idea here, because I would REALLY like to make this work......
     
    Last edited: May 6, 2011
  32. FragomirJagr

    FragomirJagr

    Joined:
    Feb 3, 2009
    Posts:
    12
    p.s. I assume it doesn't matter where in the Objective-C code you put the mono-based functions does it? I currently have it in a plain old Objective-C source file... it doesn't have to be in an Objective-C++ file or anything right? I mean i know you talked about wrapping the prototypes in extern "C" IN CASE it is called from a C++ class, but it doesn't HAVE to be in a C++ class, correct?? I mean it doesn't seem to me like it should matter, but I am very new to Objective-C, mono, plugins, Cocoa, etc. Mostly I write with Unity and C# exclusively, but it seems I have to dig a little deeper and get dirty down in the bowels of Objective-C to make this work.......

    Thanks for any help you can offer!
     
  33. FragomirJagr

    FragomirJagr

    Joined:
    Feb 3, 2009
    Posts:
    12
    Follow up... I wrote a simple little app that shows a button, and when you press it, it tries to execute your code. It simply does not return an assembly as described in the previous posts. This time I am calling the function from straight Objective-C. No go... the assembly returned by mono_domain_assembly_open returns null.

    I don't know if the last update to Unity broke this, but I would love to find out if anyone with Unity Pro 3.3 or better, and XCode 3.2.5 or better is able to make this work...
     
  34. FragomirJagr

    FragomirJagr

    Joined:
    Feb 3, 2009
    Posts:
    12
    FIXED. Okay.... so at least I am getting an assembly now.... the path in the documentation is incorrect for Unity 3. I am a bit of an XCode rookie, so I am not entirely competent when it comes to figuring out stuff like this, however, I seem to have finally figured this out...

    The documentation states that:
    stringByAppendingPathComponent:mad:"Data/Assembly-CSharp-firstpass.dll"]; (or whatever other assembly is indicated per instructions)...

    The correct path for Unity 3 is:

    stringByAppendingPathComponent:mad:"Data/Managed/Assembly-CSharp-firstpass.dll"];

    The key addition here was the "managed" subdirectory.

    Hope this helps someone else.......
     
  35. FragomirJagr

    FragomirJagr

    Joined:
    Feb 3, 2009
    Posts:
    12
    Okay so it's official. The code works and works well. No benchmark data, but it has allowed me to transmit data packets over GameCenter and bypasses UnitySendMessage altogether...

    this is very cool... thank you VERY much!

    - Alan Nelson -
    Graveck Interactive
     
  36. eviltenchi_84

    eviltenchi_84

    Joined:
    Feb 18, 2010
    Posts:
    99
    stringByAppendingPathComponent:mad:"Data/Managed/Assembly...
    Should this still be valid for 3.4?
     
  37. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    should be valid but to what I read in other places getting the mono domain fails on 3.4
     
  38. eviltenchi_84

    eviltenchi_84

    Joined:
    Feb 18, 2010
    Posts:
    99
    ugh, so basically I won't be able to use this system i posted above for communicating back with Unity then, lame.
     
  39. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    I think it would actually make Unity more valuable if they took the time to convert it to using the actual MonoTouch library and Unity essentially became an additional library in MonoTouch. Then Unity would no longer be just a game engine, but rather a full enterprise app development platform with extensive 3D functionality.
     
  40. ankit26184

    ankit26184

    Joined:
    Mar 2, 2012
    Posts:
    1
    M using unity 3.4.0f5 and xcode 4.1..
    m giving the path Data/Managed/Assembly-CSharp-firstpass.dll and i got this error...

    Non platform assembly: /private/var/mobile/Applications/720CE3A5-23BD-4106-820F-85ABBAC4364C/obctounitycall.app/Data/Managed/Assembly-CSharp-firstpass.dll

    Anybody please help