Embedding wxruby?

Hello,

is it possible to code a wxWidgets GUI in C++ and pass the whole wxApp
or just single windows to embedded Ruby code? This would be very nice to
provide a scriptable plugin system.

Greetings,
Niklas

Is it possible? Yes. How can you do it? I haven’t a clue. Basically,
you
would need to wrap the widgets you want available on the Ruby Side,
through
the wxRuby controls, using the Swigified Wrappers to create the objects,
and
so forth.

Sorry, it’s not been a very much explored system, and I wouldn’t have a
clue
in how to do it.

On Fri, Sep 12, 2008 at 12:25 PM, Niklas B. <

Hello,

“Mario S.” [email protected] wrote:

Is it possible? Yes. How can you do it? I haven’t a clue. Basically, you
would need to wrap the widgets you want available on the Ruby Side, through
the wxRuby controls, using the Swigified Wrappers to create the objects, and
so forth.

Sorry, it’s not been a very much explored system, and I wouldn’t have a clue
in how to do it.

OK, thanks for you answer. Maybe I’ll dive a bit into it to try.

Greetings,
Niklas

Hello,

as announced, I played a bit around with swig and now seem to have found
a first solution for my embedded ruby GUI plugin system. So far, I
managed to wrap a C++ wxWindow object into a ruby Wx::Window object. I
had to do a change to the wxRuby source to add a “wxWindow_to_ruby”
function to the SWIG interface file for wxWindow (Window.i).
Below is the (necessary?) code change and some example code based on the
minimal.cpp, where i abused the about dialog to test my code.

===== SAMPLE START =====

=========================================================
Append to the bottom of “wxruby2/swig/classes/Window.i”:

%{

VALUE wxWindow_to_ruby(wxWindow* w) {
return SWIG_NewPointerObj(
(void *) w,
SWIGTYPE_p_wxWindow,
0);
}

%}

=============================================
My sample app (it’s a modified minimal.cpp):


#include <ruby.h>

extern “C” void Init_wxruby2();
extern “C” void Init_wxWindow();

extern VALUE wxWindow_to_ruby(wxWindow* w);

void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event))
{
ruby_init();

ruby_init_loadpath();
// the ruby file is in a sub-directory
rb_eval_string(" $: << 'src' ");
ruby_script("embed");

Init_wxruby2();
Init_wxWindow();

VALUE rubyWindow = wxWindow_to_ruby(this);
cout << "Window in C++: " << rubyWindow << endl;
rb_define_variable("$window", &rubyWindow);

rb_require("ruby_test.rb");

ruby_finalize();

}

==================
the ruby_test.rb:

if $0 != “embed”
puts “WARNING: this script is supposed to be run only from ‘embed’”
exit
end

puts "Window in ruby: " + $window.inspect
puts "Name of the window: " + $window.get_name

==================
the output

Window in C++: 3019414920
Window in ruby: #Wxruby2::Window:0xb3f89d88
Name of the window: frame

===== SAMPLE END =====

the linker has to be told to link to wxruby2.so, wxwidgets and ruby, of
course.

i share this because it may be of some use for someone.
i think this seems fine for a first attempt, but

  • it is limited to be used with a wxWindow / Wx::Window and not at all
    generic.

  • the ruby script doesn’t know about wxRuby. It just gets the raw
    Wx::Window object. it can’t use $window.name instead of
    $window.get_name, for instance, and it can’t access it’s non-trivial
    members, because the corresponding types are not initialized.
    i didn’t figure out a way to setup the whole wxRuby namespace in one
    rush
    (i would have to call every single Init_XY).

    the wx.rb could probably be included via rb_eval_string or something
    similiar for the sugar functionalities. didn’t try it yet, however.

  • less important: the wxruby2.so has a size of 40MB. is it statically
    linked against wxWidgets? If so, can this easily be turned off?

It would be great if someone has an idea on how to solve (some of) these
problems.

Greetings,
Niklas

Hello,

Alex F. [email protected] wrote:

You will in some way need to link the wx C++ App object to a ruby
instance and make it a constant (like Wx::THE_APP in wxRuby). Having the
App as a constant is the link that wxRuby uses to protect long-lived C++
objects like Windows from Ruby’s garbage collection (see mark_wxApp).

OK, this is more sophisticated than i need it. i just need to wrap a
wxPanel, because the main interface is managed by C++.

Take a look at the function wxRuby_WrapWxObjectInRuby in swig/wx.i -
this does exactly what you want. It uses wxWidgets type-information
system to wrap a C++ object in the correct ruby class.
It is applied to (almost) all methods in wxWidgets that return wxWindow*
by a typemap.

oh, very nice, this function is what i was searching for :slight_smile: by mario’s
answer i guessed it would not exist so i tried to write one by myself.

The rake build system (in rake/rakewx.rb) appends a function called
InitializeOtherModules to the generated file src/wx.cpp that initialises
all the other modules, in alphabetic order. This is then called in
Init_wxruby (in swig/wx.i).

OK good to know.

The other important thing is that each class should not be initialised
before its parent class. The script swig/fixmodule.rb adds a line to
each generated cpp file that calls Init_wx[PARENTCLASS] before
proceeding with initialising self.

i discovered this already.

the wx.rb could probably be included via rb_eval_string or something 
similiar for the sugar functionalities. didn't try it yet, however.

The ruby API function you want is rb_require. Some of the things in the
ruby code are not just “sugar” but needed to make the classes work
properly - eg GC helpers.

another one good to know.

available, either have wx-config point to it by default, or pass
WXRUBY_DYNAMIC=1 as an environment variable to rake.

I’ll try this. thank you very much!

Happy to help further but may be better to follow-up on the wxruby-dev
list as this is fairly technical…

I would like to, but i can’t subscribe to it properly. It says 403 when
i want to confirm the subscription :frowning: at least it did yesterday.

Greetings,
Niklas

Hi Niklas

Niklas B. wrote:

as announced, I played a bit around with swig and now seem to have found a first solution for my embedded ruby GUI plugin system. So far, I managed to wrap a C++ wxWindow object into a ruby Wx::Window object.

Broadly, I think you should be able to use almost all of the existing
wxRuby SWIG etc codebase in an embedded setting. The main things that
will need to change are the library initialisation code (in swig/wx.i)
and the Wx::App initialisation code (in swig/classes/App.i) .

You will in some way need to link the wx C++ App object to a ruby
instance and make it a constant (like Wx::THE_APP in wxRuby). Having the
App as a constant is the link that wxRuby uses to protect long-lived C++
objects like Windows from Ruby’s garbage collection (see mark_wxApp).

===== SAMPLE START =====

  • it is limited to be used with a wxWindow / Wx::Window and not at all generic.

Take a look at the function wxRuby_WrapWxObjectInRuby in swig/wx.i -
this does exactly what you want. It uses wxWidgets type-information
system to wrap a C++ object in the correct ruby class.

It is applied to (almost) all methods in wxWidgets that return wxWindow*
by a typemap.

  • the ruby script doesn’t know about wxRuby. It just gets the raw
    Wx::Window object. it can’t use $window.name instead of
    $window.get_name, for instance, and it can’t access it’s non-trivial
    members, because the corresponding types are not initialized.
    i didn’t figure out a way to setup the whole wxRuby namespace in one rush
    (i would have to call every single Init_XY).

SWIG’s support for multi-module libraries is weak so wxRuby has to work
around this by massaging the default generated code.

The rake build system (in rake/rakewx.rb) appends a function called
InitializeOtherModules to the generated file src/wx.cpp that initialises
all the other modules, in alphabetic order. This is then called in
Init_wxruby (in swig/wx.i).

The other important thing is that each class should not be initialised
before its parent class. The script swig/fixmodule.rb adds a line to
each generated cpp file that calls Init_wx[PARENTCLASS] before
proceeding with initialising self.

the wx.rb could probably be included via rb_eval_string or something 
similiar for the sugar functionalities. didn't try it yet, however.

The ruby API function you want is rb_require. Some of the things in the
ruby code are not just “sugar” but needed to make the classes work
properly - eg GC helpers.

  • less important: the wxruby2.so has a size of 40MB. is it statically
    linked against wxWidgets? If so, can this easily be turned off?

You could try strip -x to reduce the size. But SWIG adds a lot of
overhead - the same runtime code is repeated and compiled for each SWIG
class. I estimate that just getting round that would reduce the library
size by about a third.

On Linux and OS X at least - haven’t tried with Windows - you can have a
wxRuby that is dynamically linked. If you have a dynamic build
available, either have wx-config point to it by default, or pass
WXRUBY_DYNAMIC=1 as an environment variable to rake.

Happy to help further but may be better to follow-up on the wxruby-dev
list as this is fairly technical…

cheers
alex

Niklas B. wrote:

You will in some way need to link the wx C++ App object to a ruby
instance and make it a constant (like Wx::THE_APP in wxRuby). Having the
App as a constant is the link that wxRuby uses to protect long-lived C++
objects like Windows from Ruby’s garbage collection (see mark_wxApp).

OK, this is more sophisticated than i need it. i just need to wrap a wxPanel, because the main interface is managed by C++.

OK, but to avoid leaks, or crashes from prematurely deleted objects, you
will need some sort of memory management strategy. It gets quite
complicated but most of the work is done :wink:

Happy to help further but may be better to follow-up on the wxruby-dev
list as this is fairly technical…

I would like to, but i can’t subscribe to it properly. It says 403 when i want to confirm the subscription :frowning: at least it did yesterday.

Just tried it and it seems to be working. If clicking on the link from
the email make sure the URL isn’t truncated as it contains an
authentication string.

alex

Hello,

Alex F. [email protected] wrote:

OK, but to avoid leaks, or crashes from prematurely deleted objects, you
will need some sort of memory management strategy. It gets quite
complicated but most of the work is done :wink:

I’ll try to. thank you for the information.

Happy to help further but may be better to follow-up on the wxruby-dev
list as this is fairly technical…

I would like to, but i can’t subscribe to it properly. It says 403 when i want to confirm the subscription :frowning: at least it did yesterday.

Just tried it and it seems to be working. If clicking on the link from
the email make sure the URL isn’t truncated as it contains an
authentication string.

Yeah it’s working now. I will ask future questions on this subjects on
the dev mailing list.

Greetings,
Niklas