Hmmm... I'm doing several things different, perhaps one or more of them are impossible :-) Basically, I'm trying to get MIDI support into Ruby via the PYMIDI obj-c library, which is really just a wrapper around CoreMIDI. One way I had thought of: create an additional obj-c class (MidiReceiver) which processes incoming packets for a given MIDI source and then call methods such as "note", "controlChange", "clockTick", etc. This MidiReceiver class can then be overridden by a custom ruby class that contains those same methods. Here is a quick version of the MidiReceiver class (with only the clockTick method for simplicity): ------------- MidiReceiver -------------- #import <MacRuby/MacRuby.h> #include <CoreMIDI/CoreMIDI.h> @interface MidiReceiver : NSObject - (void) processMIDIPacket: (MIDIPacket*) packet; - (void) clockTick; @end @implementation MidiReceiver - (void) processMIDIPacket: (MIDIPacket*) packet { if (packet->length > 0) { int statusByte = packet->data[0]; int status = statusByte >= 0xf0 ? statusByte : statusByte >> 4 << 4; switch (status) { case 0x90: // Note on, etc... case 0xf8: // Clock tick [self performRubySelector:@selector(clockTick)]; break; } } } - (void) clockTick { } @end -------------------------------------------------------- Then, I have a ruby subclass of MidiReceiver that overrides clockTick, etc.: class LiveMidiReceiver < MidiReceiver def clockTick puts "tick!" end end And then, in my Ruby ApplicationController, I'm finding the MIDI source and adding an instance of LiveMidiReceiver as a MIDI receiver: @src = PYMIDIManager.sharedInstance.realSources.find{ |s| s.displayName == 'KONTROL49 PORT A' } receiver = LiveMidiReceiver.new @src.addReceiver(receiver) The LiveMidiReceiver instance, upon receiving a midi packet, is called properly up to the point of the performRubySelector, but thereafter it launches into the debugger with EXC_BAD_ACCESS messages or other unsightly stack dumps. --- An even *better* interface would be to have the "clockTick" and other calls be performable on an arbitrary ruby object without having to subclass MidiReceiver (e.g., have MidiReceiver send "clockTick", etc. to a delegate object which has been created solely in Ruby). I tried that and it gave me similar results, although strangely it only crashed the first time on a clean build - thereafter I saw no crashes, but still no confirmation of ruby method calls either. To test that, I just added a delegate object to MidiReceiver, and then I changed the clockTick recipient from self to delegate: [delegate performRubySelector:@selector(clockTick)]; Then set receiver.delegate = self in my ApplicationController. I'll bet I need some more hooks than that, although it sure would be nice to send ruby messages from obj-c willy-nilly :-) --- I hope I'm making some sense here! I greatly appreciate any info that you can send my way. Hopefully when I get this all figured out I can write a nice, fat blog post about it :-) Regards, Mike Laurence On Wed, May 27, 2009 at 8:18 PM, Laurent Sansonetti <lsansonetti@apple.com> wrote:
Hi Mike,
On May 26, 2009, at 5:45 PM, Mike Laurence wrote:
Hello,
I'm trying to get some obj-c code to talk back to my ruby. After encountering some "EXC_BAD_ACCESS" messages and scouring the web, I've concluded that I'm probably supposed to use performRubySelector instead of just expecting selectors to work when overridden by ruby subclasses, etc.
What is the preferred way to get this to work? I looked through some of Elysium's old code (which used performRubySelector), but I'm having trouble wrapping my head around how you're supposed to use the MacRuby sharedRuntime to get things to happen. If someone could give me a quick example of how to call arbitrary ruby methods, I would highly appreciate it.
Of course, if I'm completely off base and there's some other way to call ruby code, please let me know!
Calling Ruby from Objective-C can be problematic, depending if you want to call a pure Ruby method or a Ruby method that overrides an Objective-C one. If you want to dispatch the method by yourself (and if it's a Ruby method that overrides a specialized Objective-C method), you may want to be very careful about the types of the arguments you are passing to, as well as the return value.
In any case, using performRubySelector: is better because arguments will be converted from Objective-C objects (id) into the expected type, and the return value will be converted into an Objective-C object. Also, performRubySelector: can deal with Ruby methods that have optional or splat arguments.
$ cat t.m #import <Foundation/Foundation.h> #import <MacRuby/MacRuby.h>
@interface Foo : NSObject @end
@implementation Foo - (int)aMethodReturningInt { return 123; } @end
int main(void) { [[MacRuby sharedRuntime] evaluateString:@"class X; def foo(x=1, *a); p x, a; end; end"]; Class k = NSClassFromString(@"X"); id o = [k new];
[o performRubySelector:@selector(foo)]; [o performRubySelector:@selector(foo:) withArguments:@"1", NULL]; [o performRubySelector:@selector(foo:) withArguments:@"1", @"2", @"3", NULL];
[[MacRuby sharedRuntime] evaluateString:@"class Bar < Foo; def aMethodReturningInt; 42; end; end"]; k = NSClassFromString(@"Bar"); o = [k new];
NSLog(@"%d", [(Foo *)o aMethodReturningInt]); NSLog(@"%@", [o performRubySelector:@selector(aMethodReturningInt)]);
return 0; } $ gcc t.m -o t -framework Foundation -framework MacRuby -fobjc-gc $ ./t 1 [] "1" [] "1" ["2", "3"] 2009-05-27 18:16:36.092 t[11922:10b] 42 2009-05-27 18:16:36.095 t[11922:10b] 42 $
If you still have problems, it would be easier if you could paste some code, this way I can try to help you more.
HTH, Laurent _______________________________________________ MacRuby-devel mailing list MacRuby-devel@lists.macosforge.org http://lists.macosforge.org/mailman/listinfo.cgi/macruby-devel