Tuesday, August 28, 2012

Gtk3, GObject Introspection and Free Pascal

A while back the Gtk community started a project called GObject Introspection. In short it exports the Gtk and Glib api, as well as many others, to an easily parsed xml format.

The result is gir2pascal.

gir2pascal can create bindings to pascal from any library that supports gobject introspection in just a few moments! The result is that Gtk3 bindings have been created fairly easily for pascal and can easily be updated simply by running gir2pascal against the latest version.

Here is part of the XML code from the Gtk3.gir file relating to the caption of the button:

<class name="Button"
           c:symbol-prefix="button"
           c:type="GtkButton"
           parent="Bin"
           glib:type-name="GtkButton"
           glib:get-type="gtk_button_get_type"
           glib:type-struct="ButtonClass">      
      <implements name="Atk.ImplementorIface"/>
      <implements name="Actionable"/>
      <implements name="Activatable"/>
      <implements name="Buildable"/>
      <constructor name="new" c:identifier="gtk_button_new">
        <return-value transfer-ownership="none">
          <type name="Widget" c:type="GtkWidget*"/>
        </return-value>
      </constructor>
      <method name="get_label" c:identifier="gtk_button_get_label">
        <return-value transfer-ownership="none">
          <type name="utf8" c:type="gchar*"/>
        </return-value>
      </method>      <method name="set_label" c:identifier="gtk_button_set_label">
        <return-value transfer-ownership="none">
          <type name="none" c:type="void"/>
        </return-value>
        <parameters>
          <parameter name="label" transfer-ownership="none">
            <type name="utf8" c:type="gchar*"/>
          </parameter>
        </parameters>
      </method>
      <property name="label"                construct="1"
                transfer-ownership="none">
        <type name="utf8"/>
      </property>
 </class>
  
One improvement over the current (Gtk2) bindings in use is that Pascal objects (not classes) have been used in place of records. The advantage is that GObjects, a C style quasi-object, can be accessed in an object oriented way instead of the flat C functions we use currently.

Here is the corresponding generated pascal code from the XML above:

type
  TGtkButton = object(TGtkBin)
    priv3: PGtkButtonPrivate; 
    function new: PGtkButton; cdecl; inline; static;
    function get_label: Pgchar; cdecl; inline;
    procedure set_label(label_: Pgchar); cdecl; inline;
    property label_: Pgchar read get_label write set_label;
  end;

function gtk_button_new: PGtkButton; cdecl; external;
function gtk_button_get_label(AButton: PGtkButton): Pgchar; cdecl; external;
procedure gtk_button_set_label(AButton: PGtkButton; label_: Pgchar); cdecl; external;

implementation

function TGtkButton.new: PGtkButton; cdecl;
begin
  Result := Gtk3.gtk_button_new();
end;

function TGtkButton.get_label: Pgchar; cdecl;
begin
  Result := Gtk3.gtk_button_get_label(@self);
end;

procedure TGtkButton.set_label(label_: Pgchar); cdecl;
begin
  Gtk3.gtk_button_set_label(@self, label_);
end;

You can see that we are using objects instead of records, compare this to the current Gtk2 bindings:

type
  PGtkButton = ^TGtkButton;
  TGtkButton = record
    bin : TGtkBin;
    event_window : PGdkWindow;
    label_text : Pgchar;
    activate_timeout : guint;
    flag0 : word;
  end; 

function gtk_button_new:PGtkWidget; cdecl; external gtklib;
procedure gtk_button_set_label(button:PGtkButton; _label:Pgchar); cdecl; external gtklib;
function gtk_button_get_label(button:PGtkButton):Pgchar; cdecl; external gtklib;


Before, our code would look like this:

procedure Foo;
var
  Button: PGtkWidget;
begin
  Button := gtk_button_new;
  gtk_button_set_label(PGtkButton(Button), 'Hello World!');
end;


But now it's possible to use this instead:

procedure Foo;
var
  Button: PGtkButton;
begin
  Button := TGtkButton.new;
  Button^.label_ := 'Hello World!';
end;

As you see it's a bit shorter to type the second way. Although either will work since the 'flat' functions are still available to use.

Using objects instead of records it is of course possible to access inherited methods and properties with greater ease than before, especially when using the code completion feature of Lazarus (which is extremely close to 1.0 now!). Button in the example above descends from GtkWidget which has the property tooltip_text.

This can be accessed with
Button^.tooltip_text := 'Don''t click me unless you want to!';
whereas before you had to do
gtk_widget_set_tooltip_text(PGtkWidget(Button) ,'Don''t click me unless you want to!');  

Here's the HelloWorld example included with the bindings, modified to have a tooltip.


 The Pascal Gtk3 bindings are not yet well tested. There are a couple of examples in the Lazarus-ccr repository in the folder /bindings/gtk3/examples/ including an example of GtkWebkit.

Perhaps you would like to try it out :)

10 comments:

Warren said...

Very cool technology. But do you HAVE TO USE Object() instead of Class()?

Why are people STILL using object(Something) instead of class(Something) in 2012?

And having to declare pointer types again because objects are stack values rather than reference-to-heap values? Gross!

W

Andrew Haines said...

Yes. Using Object() is possible only because objects and records have the same memory footprint, which is compatible to the GObjects created with C structs. So all I am doing is casting C structs to pascal Objects.

That is not possible with classes because classes have hidden fields such as the vmt which would conflict with GObjects fields which are all visible.

So they are in a way not actually objects. I am only using them as objects. It is not possible to subclass these objects in the same way you normally could.

I will work on an example to subclass a GtkWidget and override a virtual procedure (in the GObject sense) and add it to the examples in svn.

Andrew Haines said...

Having said all that, I have been contemplating how to use classes instead of objects and I think I have come up with a way to do it with normal pascal inheritance and virtual methods etc. The more I think about it the more details rush in to complicate it however.

Bernd said...
This comment has been removed by the author.
Bernd said...

@Warren: They are not on the stack in this case, objects (just like records) can live wherever they have been allocated. In this case they are allocated by Gtk by one of the xxx_new() functions which returns a pointer to the object on the heap, managed by gtk and not by FPC. Pascal objects are the natural and ideal way to represent this kind of data.

@Andrew: Your approach is exactly what I have done with the libpurple bindings: https://github.com/prof7bit/TorChat/tree/torchat2/src/purple/libpurple

I am even going one step further when using these objects I activate the compiler directive {$modeswitch autoderef} so that when calling a method on the pointer type I can simply omit the dereferencing^ which would mean in your example instead of

Button^.label_ := 'Hello World!';

it would become

Button.label_ := 'Hello World!';

Warren said...

Thanks for the clarifications. I understand better now. Since I would probably implement my own Class that wraps each "auto-wrapper Object()", this is only a minor issue for what I would do with this.

Time to play with some GTK3+Lazarus!

Warren

Andrew Haines said...

@Bernd I didn't know about {$modeswitch autoderef} Good to know.

MarcoV said...

Shouldn't this use records with methods instead of object() ?

This because if sb makes a small mistake and adds a virtual method, "object" layout won't match C record anymore.

Using record syntax avoids that danger.

Unknown said...

Thanks for your ideas. You can also find the details on Affity Solutions, at the C++ Developers. The main object of the Affity Solutions is to provide quality web services and is among the few software development
company in Nagpur.

Marc said...

How active is the development of the GTK3 bindings? I'm using GNU/Linux and also Lazarus and I would appreciate the further integration of GTK3 (which now is some years old ;-)). How could I contribute to the GTK3 bindings (even when I have zero experience of interfacing C-libs ;-))?