From: Jason Roelofs Date: 2010-04-17T03:23:45+09:00 Subject: Re: Extending Ruby using C++ and Rice On Apr 16, 2010, at 12:05 PM, Lea Savage wrote: > Hi, > > I am trying to extend Ruby and my current implementation is using C++ > and Rice (http://rice.rubyforge.org/main.html). > Mac OS X 10.5.8 > Ruby 1.8.6 (2008-08-11 patchlevel 287) [universal-darwin9.0] > > I have completed the following steps as shown in Rice to create a cpp > class (my_test.cpp) > > #include "rice/Class.hpp" > #include "rice/Data_Type.hpp" > #include "rice/Constructor.hpp" > #include "MyTest.h" > > using namespace Rice; > > extern "C" > void Init_my_test() > { > define_class("MyTest"). > define_constructor(Constructor()). > define_method("read", &MyTest::read); > } > > Here are my h and cpp files for MyTest > > *** HEADER FILE *** > #ifndef __MyTest_h > #define __MyTest_h > > #include > #include "XternalDebug.hpp" > > class MyTest > { > public: > MyTest() {}; > virtual ~MyTest(void){ > SX::Terminate(); > SXMP::Terminate(); > } > std::string read(std::string filename); > }; > #endif > > > *** CPP FILE *** > #include "MyTest.h" > > extern "C" std::string MyTest::read(std::string filename) > { > if(SXMP::Initialize()) > { > // Must initialize SX before we use it > if (SX::Initialize()) > { > std::string status = ""; > bool ok; > SX myFile; > myFile.OpenFile(filename, UnknownFile, opts); > std::string buffer; > myFile.Get(&buffer); > return buffer; > } > } > return NULL; > } > > SX and SXMP are taken from another library called libXternalDebug.a > > My extconf.rb file looks as follows: > require 'rubygems' > require 'mkmf-rice' > $CPPFLAGS << ' -DMAC_ENV=1' > have_library("stdc++") > dir_config("xtoolkit") > have_library("XternalDebug") > create_makefile("my_test") > > I pass the following arguments to extconf.rb > ARCHFLAGS="-arch i386" ruby extconf.rb > --with-xtoolkit-include=/Users//xtoolkit/public/include > --with-xtoolkit-lib=/Users//xtoolkit/public/libraries > > $ ARCHFLAGS="-arch i386" ruby extconf.rb > --with-xtoolkit-include=/Users//xtoolkit/public/include > --with-xtoolkit-lib=/Users//xtoolkit/public/libraries > checking for main() in -lrice... yes > checking for main() in -lstdc++... yes > checking for main() in -lX... yes > creating Makefile > > I then run make: > $ makeg++ -I. -I. > -I/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/universal-darwin9.0 > -I. -I/Users//xtoolkit/public/include > -I/Library/Ruby/Gems/1.8/gems/rice-1.3.1/ruby/lib/include -DMAC_ENV=1 > -fno-common -arch i386 -Os -pipe -fno-common -Wall -g -c my_test.cpp > g++ -I. -I. > -I/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/universal-darwin9.0 > -I. -I/Users//xtoolkit/public/include > -I/Library/Ruby/Gems/1.8/gems/rice-1.3.1/ruby/lib/include -DMAC_ENV=1 > -fno-common -arch i386 -Os -pipe -fno-common -Wall -g -c MyTest.cpp > g++ -arch i386 -pipe -bundle -undefined dynamic_lookup -o my_test.bundle > my_test.o MyTest.o -L. > -L/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib > -L/Users//xtoolkit/public/libraries -L. -arch i386 > -L/Library/Ruby/Gems/1.8/gems/rice-1.3.1/ruby/lib/lib -lXternalDebug > -lstdc++ -lrice -lruby -lpthread -ldl -lm > > (please note i have removed my username intentionally from the above > paths) > > I then have the following ruby test: > require 'my_test' > require 'test/unit' > > class TestTest < Test::Unit::TestCase > def test_test > t = MyTest.new > assert_equal(Object, MyTest.superclass) > assert_equal(MyTest, t.class) > end > end > > When I try to run this I get the following error: > $ ruby testtest.rb > Loaded suite testtest > Started > . > Finished in 0.000476 seconds. > > 1 tests, 2 assertions, 0 failures, 0 errors > dyld: lazy symbol binding failed: Symbol not found: > __ZN9TSXMISsE9TerminateEv > Referenced from: /Users//tests/rice_test2/my_test.bundle > Expected in: dynamic lookup > > dyld: Symbol not found: __ZN9TSXISsE9TerminateEv > Referenced from: /Users//tests/rice_test2/my_test.bundle > Expected in: dynamic lookup > > Trace/BPT trap > > The above does show the 2 tests as passing but then I get the error > below when it is deconstructing the class at it uses the external > library. > > Does anyone know what I am doing wrong or what I am missing to get these > errors? Do I need to include this external library in a particular > directory for ruby to discover? > Any help is much appreciated. > > I have also heard of using a combination of FFI and SWIG but how does > this work when it is an external library and I do not have the source? > > Lea > -- > Posted via http://www.ruby-forum.com/. > For future reference, Rice has it's own mailing list at rice@librelist.com. Send a blank email to that address to register as it will be dropped, then you can send to the list. As for your question here, the one thing that stands out is the "extern 'C'" on MyTest::read. Rice expects normal C++ code and such a statement isn't required for Rice to properly use your code. I'm not sure this is causing your error but it is something I would recommend removing unless it's there for other reasons. Otherwise, as you noticed, the error says that it can't find SXM::Terminate during runtime. My guess is that the load path on your machine (DYLD_LIBRARY_PATH) doesn't include the path that includes the SXM bundles. One way to quickly test this is to place the bundle right next to your my_test.bundle. As for your final statement about FFI and SWIG, these two libraries are in effect for different purposes. FFI is about run-time linking to C libraries (C++ doesn't work because of non-portable name mangling) while SWIG will parse your C++ and generate compliant C code that wraps the C++ into Ruby. For completeness of the comparison, Rice takes wrapping a third way, and is built to allow wrapping C++ libraries into Ruby using a very simple C++ API, so you very rarely have to actually call a C-Ruby API method. I would prefer to continue this discussion on rice's mailing list, to avoid project-specific clutter in ruby-talk, but I'm fine to continue it here as well. Jason