Objective-CコードからCの構造体を簡単ログ出力(with MacRuby)

MacRubyでプロジェクトを作成すれば、Objective-Cのコード中から簡単にCの構造体をダンプできる。

Ruby側。NSValueに詰め込まれた構造体と型名を受け取って、ダンプするユーティリティ。

class Util

   def self.dump_struct_withName(o,klass_name)

      if (o.kind_of?(NSValue))
         pointer = o.pointerValue
         puts pointer.class       #=>Pointer
         
         pointer.cast!(TopLevel.const_get(klass_name).type)
         struct = pointer[0]
         return unless struct.class.respond_to?(:fields)
         puts "dumping struct #{struct.class}"
         struct.class.fields.each do |field_name|
            puts "\t#{field_name.to_s} = #{struct.__send__(field_name)}"
         end
      end
   end
end

Pointerクラスの使い方についてはMacRubyのPointerクラスについて - Watsonのメモを参考に。

MacRuby側ではCの構造体はBoxedというクラスを継承したクラスになり、#fieldsでメンバ一覧が取得できる。


Objective-C(++)側

#include <typeinfo>   
#include "CoreAudio/CoreAudio.h"
#import "Controller.h"
#import "MacRuby/MacRuby.h"   //for [MacRuby sharedRuntime]

#include <string>

//demangle function (gcc only)
//http://d.hatena.ne.jp/hidemon/20080731/1217488497
#include <string>
extern "C" char *__cxa_demangle (
                         const char *mangled_name,
                         char *output_buffer,
                         size_t *length,
                         int *status);

std::string demangle(const char * name) {
    size_t len = strlen(name) + 256;
    char output_buffer[len];
    int status = 0;
    return std::string(
                  __cxa_demangle(name, output_buffer, 
                             &len, &status));
}


//MacRubyへのブリッジ関数
template <typename T>
void dump_struct(const T &t){
   
   //型名を文字列で取得
   const std::type_info &type = typeid(t);
   std::string demangled_type_name = demangle(type.name());
   
   NSValue *v = [NSValue valueWithPointer:&t];
   NSString *typeName = [NSString stringWithCString:demangled_type_name.c_str() encoding:kCFStringEncodingUTF8 ];
   id ruby_util = [[MacRuby sharedRuntime] evaluateString:@"Util"];
   [ruby_util performRubySelector:@selector(dump_struct_withName:) withArguments:v,typeName,NULL];
}

@implementation Controller

- (IBAction)callRubyMethod:(id)sender{
   
   //例えばこういうちょっとメンバが多い構造体でも簡単に列挙できる。
   AudioStreamBasicDescription format;
   format.mSampleRate = 44100.0;
   format.mFormatID = kAudioFormatLinearPCM; //1819304813
   format.mFormatFlags = 41;
   format.mBytesPerPacket = 4;
   format.mFramesPerPacket = 1;
   format.mBytesPerFrame = 4;
   format.mChannelsPerFrame = 2;
   format.mBitsPerChannel = 32;
   format.mReserved = 0;
   
   dump_struct(format);
   
}
@end

結果

dumping struct AudioStreamBasicDescription
	mSampleRate = 44100.0
	mFormatID = 1819304813
	mFormatFlags = 41
	mBytesPerPacket = 4
	mFramesPerPacket = 1
	mBytesPerFrame = 4
	mChannelsPerFrame = 2
	mBitsPerChannel = 32
	mReserved = 0

dumpstruct()では、Ruby側に構造体の型名を渡すためにRTTIとデマングルを使っている。デマングルのためのコードはC++ のtype_info.name() - hidemonのブログからそのまま使いました。

まぁ最初から全部MacRubyで書けよって話もあるだろうけど、メインがObjective-Cのコードで、少しだけ楽をする方法。

プロジェクト一式は

git://github.com/kyab/CallRubyMethod_fromObjC.git