Ruby 拡張ライブラリ

 

1.GCCで共有ライブラリの作成を復習
2.Ruby拡張ライブラリの作り方を学習する
3.PhidgetRFIDキット用のRuby拡張ライブラリを作成する

はじめに

Ruby拡張ライブラリとは、「C言語でRubyのライブラリを書けます
よ」ということです。パフォーマンスを高める際に利用されたりし
ます。Javaの場合は、JNI(Java Native Interface)というものがあ
りこれもRuby拡張ライブラリと同様です。

1.GCCでの共有ライブラリの作成を復習

まずは、GCCでの共有ライブラリの作成方法を復習。

Cygwinで試す.
s-okita_01@s-okita ~/develop/ruby/clib
$ cat greeting.c
#include <stdio.h>

void hello() {
printf("hello\n");
}

void goodbye() {
printf("goodbye");
}

s-okita_01@s-okita ~/develop/ruby/clib
$ cc -c greeting.c

s-okita_01@s-okita ~/develop/ruby/clib
$ cat greeting.h
#ifndef __GREETING_H__
#define __GREETING_H__
#endif

void hello();
void goodbye();

s-okita_01@s-okita ~/develop/ruby/clib
$ ls
greeting.c greeting.h greeting.o

s-okita_01@s-okita ~/develop/ruby/clib
$ cc --shared -o greeting.dll greeting.o

s-okita_01@s-okita ~/develop/ruby/clib
$ file greeting.dll
greeting.dll: PE executable for MS Windows (DLL) (console) Intel 80386 32-bit

$ gcc -O2 -o she_greet_me she_greet_me.c -L. -lgreeting

s-okita_01@s-okita ~/develop/ruby/clib
$ cat she_greet_me.c
#include "greeting.h"

int main(int argc, char *argv[]) {
hello();
goodbye();
return 0;
}

s-okita_01@s-okita ~/develop/ruby/clib
$ gcc -O2 -o she_greet_me she_greet_me.c -L. -lgreeting

s-okita_01@s-okita ~/develop/ruby/clib
$ ./she_greet_me.exe
hello
goodbye

2.Ruby拡張ライブラリの作り方を学習する

簡単なhello, goodbyeを表示させるRuby拡張ライブラリを作成して
みる。ところでCygwinでRuby拡張ライブラリが開発できる環境にあ
るか分からないのでfindコマンドでヘッダファイルを検索してみる

s-okita_01@s-okita /lib/ruby/1.8/i386-cygwin
$ find / -name 'ruby.h'
/lib/ruby/1.8/i386-cygwin/ruby.h

s-okita_01@s-okita /lib/ruby/1.8/i386-cygwin
$ cd /lib/ruby/1.8/i386-cygwin/

s-okita_01@s-okita /lib/ruby/1.8/i386-cygwin
$ ls
Win32API.so dl.h gdbm.so racc sdbm.so tkutil.so
bigdecimal.so dl.so iconv.so rbconfig.rb socket.so util.h
config.h dlconfig.h intern.h re.h st.h version.h
curses.so dln.h missing.h readline.so stringio.so win32ole.so
dbm.so enumerator.so nkf.so regex.h strscan.so zlib.so
defines.h env.h node.h ruby.h syck.so
digest etc.so openssl.so rubyio.h syslog.so
digest.so fcntl.so pty.so rubysig.h tcltklib.so

s-okita_01@s-okita /lib/ruby/1.8/i386-cygwin

パッと見た感じではどのsoファイルがコアライブラリなのか分から
ないがruby.hが確認できたので開発できると思う。いざとなればソ
ースコードからコンパイルすればよい。

現在最新版のRuby1.8.3のソースコードをダウンロードする。

この中にREADME.EXT.jaというRuby拡張ライブラリの作り方についての
説明があるのでざっくり読む。

ここで「データタイプ」が何であるか理解する。
データタイプをチェックする方法を理解する。
VALUEをCのデータに変換する方法を理解する。
CのデータをVALUEに変換する方法を理解する。

このサイトがHello Worldを書くのに分かりやすい。
http://members.jcom.home.ne.jp/mitakelp/makeext.html


サイトのまねをしてどんどん作ってみるとCでコードが出来上がる。
ついでになんとなく概要がつかめてくる。

s-okita_01@s-okita ~/develop/ruby/clib
$ cat rubyext_test.c
#include "ruby.h"

/* Step by Step create Ruby Extension Library .
*
* Step.1 define function
* Step.2 create Init function
* Step.3 create class define by a rb_define_class function
* Step.4 add function to Step.3 class.
* Step.5 create extconf.rb (NO editing makefile)
* Step.6 make
* Step.7 use by Ruby Program.
*/

int main(int argc, char *argv[]) {
return 0;
}

/* Step.1 define function */
VALUE add(VALUE self, VALUE va, VALUE vb) {
VALUE r;
int c_a;
int c_b;
int result;

/* NUM2INT macro execute to convert the int value of C language. */
c_a = NUM2INT(va);
c_b = NUM2INT(vb);

result = c_a + c_b;

printf("DEBUG:%d\n", c_a);

r = INT2FIX(result);

return r;
}

/* Step.2 create Init function */
/*
* library name is xxxxx in INIT_xxxxx, so following line mean 'phidget_r'
* library.
*/
void Init_phidget_r(void) {

/* Step.3 */
VALUE rb_cHoge;
rb_cHoge = rb_define_class("Hoge", rb_cObject);

/* Step.4 */
/* set pointer of function to 3 argument. */
rb_define_method(rb_cHoge, "add", add, 2);
}


s-okita_01@s-okita ~/develop/ruby/clib
$ cat extconf.rb
require "mkmf"
create_makefile("phidget_r")

s-okita_01@s-okita ~/develop/ruby/clib
$ ruby extconf.rb
creating Makefile

s-okita_01@s-okita ~/develop/ruby/clib
$ make
gcc -g -O2 -I. -I/usr/lib/ruby/1.8/i386-cygwin -I/usr/lib/ruby/1.8/i386-cygwin
-I. -c rubyext_test.c
gcc -shared -s -Wl,--enable-auto-import,--export-all -L"/usr/lib" -o phidget_r.
so rubyext_test.o -lruby -lcrypt

s-okita_01@s-okita ~/develop/ruby/clib
$ ls
Makefile phidget_r.so rubyext_test.o
extconf.rb rubyext_test.c test_phidget_r.rb

s-okita_01@s-okita ~/develop/ruby/clib
$ cat test_phidget_r.rb
#!/usr/bin/ruby

require "phidget_r"

hoge = Hoge.new
p hoge.class

s-okita_01@s-okita ~/develop/ruby/clib
$ ruby test_phidget_r.rb
Hoge


とりあえず、C言語でRuby言語のHogeクラスを作成できた。実行も
問題なく出来た。こいつをベースにしていけばPhidgets用のRuby
ライブラリが出来そうなのでもうちょっと頑張ってみる。

3.PhidgetRFIDキット用のRuby拡張ライブラリを作成する

PhidgetRFIDキットは、APIとしてC言語のインタフェースと、Java
用のインタフェースがJNIで提供されている。他の言語もいっぱい
あるが、基本はC言語をラップしているだけでしょう。

RubyでPhidgetRFID開発キットを利用するためには以下が挙げられる

・バッククォートなどで他言語で書かれたプログラムを呼び出す。
・Ruby拡張ライブラリを作る
・Phidgets社に催促.

バッククォートなどで多言語で書かれたプログラムを呼び出す方法
はどのスクリプト言語にも対応できるので非常に汎用性がある。た
だ多言語との通信にオーバーヘッドが発生するデメリットもある。
Ruby拡張ライブラリの場合、C言語で直接Ruby用のライブラリを作成
するためパフォーマンスは良いが、開発コストが若干掛かる。どち
らも一長一短なのでやりたいほうを選択して欲しい。

今回は、Rubyの勉強もかねてRuby拡張ライブラリを作成する。

1. C言語でPhidgetRFIDキットに触れてみる.
2. C言語の関数をRuby言語でラップしてみる.


PHIDGET.msiとExample.zipをダウンロード.

PHIDGET.msiをC:\Phidgetsにインストールする。PHIDGETS.msiイン
ストーラは単純に指定したディレクトリにDLLを1つ配置する。また
C:\WINDOWS\system32にphidget20.dllをインストールする。この中
にExample.zipも展開。これはPhidgetハードウェアを開発するため
の参考になります。

インストールが終わると以下のような感じのディレクトリ構造にな
るはずです。

C:\Phidgets
\Examples
\Examples.zip
PHIDGET.dll


ここにC言語でのPhidget製品の開発の仕方が載っている。
[ Use In C, C++ (Using Phidget20 Library) ]
http://phidgets.com/modules.php?op=modload&name=Sections&file=index&req=viewarticle&artid=76&page=1


[PhidgetRFID C API]
http://phidgets.com/modules.php?op=modload&name=Docs&file=index&req=cdll&id=7


s-okita_01@s-okita ~/develop/ruby/clib
$ cat test_phidgets_function.c
#include <stdio.h>
#include "phidget20.h"

int main(int argc, char *argv[]) {
printf("WWWW");
return 0;
}

s-okita_01@s-okita ~/develop/ruby/clib
$ gcc -save-temps -v -o test_phidgets_function test_phidgets_function.c -I/cygd
rive/c/Phidgets/Examples/VC++ -L/cygdrive/c/WINDOWS/system32 -lphidget20
Reading specs from /usr/lib/gcc/i686-pc-cygwin/3.4.4/specs
Configured with: /gcc/gcc-3.4.4/gcc-3.4.4-1/configure --verbose --prefix=/usr --
exec-prefix=/usr --sysconfdir=/etc --libdir=/usr/lib --libexecdir=/usr/lib --man
dir=/usr/share/man --infodir=/usr/share/info --enable-languages=c,ada,c++,d,f77,
java,objc --enable-nls --without-included-gettext --enable-version-specific-runt
ime-libs --without-x --enable-libgcj --disable-java-awt --with-system-zlib --ena
ble-interpreter --disable-libgcj-debug --enable-threads=posix --enable-java-gc=b
oehm --disable-win32-registry --enable-sjlj-exceptions --enable-hash-synchroniza
tion --enable-libstdcxx-debug : (reconfigured)
Thread model: posix
gcc version 3.4.4 (cygming special) (gdc 0.12, using dmd 0.125)
/usr/lib/gcc/i686-pc-cygwin/3.4.4/cc1.exe -E -quiet -v -I/cygdrive/c/Phidgets/E
xamples/VC++ -D__CYGWIN32__ -D__CYGWIN__ -Dunix -D__unix__ -D__unix -idirafter /
usr/lib/gcc/i686-pc-cygwin/3.4.4/../../../../include/w32api -idirafter /usr/lib/
gcc/i686-pc-cygwin/3.4.4/../../../../i686-pc-cygwin/lib/../../include/w32api tes
t_phidgets_function.c -mtune=pentiumpro -o test_phidgets_function.i
ignoring nonexistent directory "/usr/local/include"
ignoring nonexistent directory "/usr/lib/gcc/i686-pc-cygwin/3.4.4/../../../../i6
86-pc-cygwin/include"
ignoring duplicate directory "/usr/lib/gcc/i686-pc-cygwin/3.4.4/../../../../i686
-pc-cygwin/lib/../../include/w32api"
#include "..." search starts here:
#include <...> search starts here:
/cygdrive/c/Phidgets/Examples/VC++
/usr/lib/gcc/i686-pc-cygwin/3.4.4/include
/usr/include
/usr/lib/gcc/i686-pc-cygwin/3.4.4/../../../../include/w32api
End of search list.
/usr/lib/gcc/i686-pc-cygwin/3.4.4/cc1.exe -fpreprocessed test_phidgets_function
.i -quiet -dumpbase test_phidgets_function.c -mtune=pentiumpro -auxbase test_phi
dgets_function -version -o test_phidgets_function.s
GNU C version 3.4.4 (cygming special) (gdc 0.12, using dmd 0.125) (i686-pc-cygwi
n)
compiled by GNU C version 3.4.4 (cygming special) (gdc 0.12, using dmd 0
.125).
GGC heuristics: --param ggc-min-expand=98 --param ggc-min-heapsize=128870
/usr/lib/gcc/i686-pc-cygwin/3.4.4/../../../../i686-pc-cygwin/bin/as.exe -o test
_phidgets_function.o test_phidgets_function.s
/usr/lib/gcc/i686-pc-cygwin/3.4.4/collect2.exe -Bdynamic --dll-search-prefix=cy
g -o test_phidgets_function.exe /usr/lib/gcc/i686-pc-cygwin/3.4.4/../../../crt0.
o -L/cygdrive/c/WINDOWS/system32 -L/usr/lib/gcc/i686-pc-cygwin/3.4.4 -L/usr/lib/
gcc/i686-pc-cygwin/3.4.4 -L/usr/lib/gcc/i686-pc-cygwin/3.4.4/../../.. test_phidg
ets_function.o -lphidget20 -lgcc -lcygwin -luser32 -lkernel32 -ladvapi32 -lshell
32 -lgcc
/usr/lib/gcc/i686-pc-cygwin/3.4.4/../../../libcygwin.a(_cygwin_crt0_common.o)::
undefined reference to `_GetModuleHandleA@4'
collect2: ld returned 1 exit status

リンカが_GetModuleHandleA@4を参照できない?

Google検索してみるとWindowsの場合は、無償版のVC++、Borland
C++が妥当かもしれない。


http://www.phidgets.com/modules.php?op=modload&name=Downloads&file=index&req=viewdownload&cid=2

ライブラリのソースコードをダウンロードしてみる。makeファイル
が無いがmakewin.batというmake用バッチファイルが見つかった。
やはりVC++でコンパイルしている模様。

--makewin.batの中身

NMAKE /f "phidget20.mak" CFG="phidget20 - Win32 Release"
cl /EP /D_WINDOWS /DEXTERNALPROTO phidget20.h > generateC.h
"C:\Program Files\GnuWin32\bin\indent" generateC.h -nbc -l200 -i1 -sob
cl /EP /D_MACOSX /DEXTERNALPROTO phidget20.h > generateM.h
"C:\Program Files\GnuWin32\bin\indent" generateM.h -nbc -l200 -i1 -sob
cl /EP /D_WINDOWS phidget20.h > generateI.h
"C:\Program Files\GnuWin32\bin\indent" generateI.h -nbc -l200 -i1 -sob
copy release\phidget20.dll c:\windows\system32\
copy release\phidget20.dll c:\windows\system32\
copy generateI.h "..\phidget20.h"
copy constants.h "..\constants.h"
copy release\*.lib "..\"


phidget20.makというmakeファイルも発見したが、どうやらWindows
用のmakeファイルだ。Linuxの場合ソースコードからコンパイルする
には、以下から「Linux Phidget20 Port」をダウンロードする。
C言語ってポータブル・アセンブラじゃなかったのかと思う。

http://www.phidgets.com/modules.php?op=modload&name=Downloads&file=index&req=viewdownload&cid=2

 

■OSXのコマンドラインでコンパイルしてみる。

Hi All

My name is Satoshi Okita.
I am trying to build command line. but I can not linkage.
I had not probrem to build following sourcecode by Xcode 1.5
on Mac OSX 10.3.9 and All Phidgets Official application running now.

Can I build it by gcc on MacOSX ?

And, Do you know a good linkage guide or tutorial in the Internet ?

---my environment
* Mac OSX 10.3.9
* Xcode 1.5 and patch
* gcc (GCC) 3.3 20030304 (Apple Computer, Inc. build 1671)
* Phidget RFID Kit (version2? print 'PhidgetRFID' on board)

--- follow is my commandline step ---

-bash-3.00$ uname -a
Darwin es.local 7.9.0 Darwin Kernel Version 7.9.0: Wed Mar 30 20:11:17 PST 2005; root:xnu/xnu-517.12.7.obj~1/RELEASE_PPC Power Macintosh powerpc
-bash-3.00$ gcc --version
gcc (GCC) 3.3 20030304 (Apple Computer, Inc. build 1671)
Copyright (C) 2002 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

-bash-3.00$ cat main.cpp
/*
main.cpp of Phidgets Official customize by satoshiokita
*/
#include <stdio.h>

#include <CoreFoundation/CoreFoundation.h>
#include <Phidget20/phidget20.h>

CPhidgetManagerHandle phidlist =0;

int something;

int gotAttach(CPhidgetManagerHandle phidm, void *, CPhidgetHandle phid) {

char *id, *error;

CPhidget_getDeviceType((CPhidgetHandle)phid,&id);
printf("Device Added: %s\n",id);

return 0;
}

int gotDetach(CPhidgetManagerHandle phidm, void *, CPhidgetHandle phid) {
char *id;

CPhidget_getDeviceType((CPhidgetHandle )phid,&id);
printf("Device Removed: %s\n",id);

return 0;
}

//this is called by the timer in the run loop
void doStuff(CFRunLoopTimerRef timer, void *Handle) {

fflush(stdout);
}

int main (int argc, const char * argv[]) {

CFRunLoopTimerRef timer;
printf("begin initialize\n");
CPhidgetManager_initialize(&phidlist);
printf("end initialize\n");

return 0;
}


-bash-3.00$ gcc -v -o main main.cpp -framework Cocoa -framework CoreFoundation -
framework Phidget20 -L/usr/lib -lobjc
Reading specs from /usr/libexec/gcc/darwin/ppc/3.3/specs
Thread model: posix
gcc version 3.3 20030304 (Apple Computer, Inc. build 1671)
/usr/libexec/gcc/darwin/ppc/3.3/cc1plus -quiet -v -D__GNUC__=3 -D__GNUC_MINOR__=3 -D__GNUC_PATCHLEVEL__=0 -D__APPLE_CC__=1671 -D__DYNAMIC__ main.cpp -D__GNUG__=3 -fPIC -quiet -dumpbase main.cpp -auxbase main -version -D__private_extern__=extern -o /var/tmp//ccYaOKOZ.s
GNU C++ version 3.3 20030304 (Apple Computer, Inc. build 1671) (ppc-darwin)
compiled by GNU C version 3.3 20030304 (Apple Computer, Inc. build 1671).
GGC heuristics: --param ggc-min-expand=30 --param ggc-min-heapsize=131072
ignoring nonexistent directory "/usr/ppc-darwin/include"
ignoring nonexistent directory "/Local/Library/Frameworks"
#include "..." search starts here:
#include <...> search starts here:
/usr/include/gcc/darwin/3.3/c++
/usr/include/gcc/darwin/3.3/c++/ppc-darwin
/usr/include/gcc/darwin/3.3/c++/backward
/usr/local/include
/usr/include/gcc/darwin/3.3
/usr/include
End of search list.
Framework search starts here:
/System/Library/Frameworks
/Library/Frameworks
End of framework search list.
/usr/libexec/gcc/darwin/ppc/as -arch ppc -o /var/tmp//ccjV8RO3.o /var/tmp//ccYaOKOZ.s
ld -arch ppc -dynamic -o main -lcrt1.o -lcrt2.o -L/usr/lib -L/usr/lib/gcc/darwin/3.3 -L/usr/lib/gcc/darwin -L/usr/libexec/gcc/darwin/ppc/3.3/../../.. /var/tmp//ccjV8RO3.o -framework Cocoa -framework CoreFoundation -framework Phidget20 -lobjc -lgcc -lSystem |
c++filt
ld: Undefined symbols:
___gxx_personality_v0

_________________
----
UKnet, Inc.
A chief of RFID depertment.
Satoshi Okita

E-mail:s-okita@uknet.co.jp
http://www.uknet.co.jp

 

-bash-3.00$ gcc -o main main.cpp -framework Cocoa -framework CoreFoundation -framework Phidget20 -L/usr/lib -lobjc -lstdc++
-bash-3.00$ ./main
begin initialize
end initialize