gi-crystal

Tool to generate Crystal bindings for gobject-based libraries (i.e. GTK) gobject-introspection glib gobject bindings gtk gtk4 desktop gui linux
0.10.0 released

Build Status

GI Crystal

GI Crystal is a binding generator used to generate Crystal bindings for GObject based libraries using GObject Introspection.

If you are looking for GTK4 bindings for Crystal, go to GTK4

I wrote this while studying GObject Introspection to contribute with crystal-gobject but at some point I decided to take a different approach on how to generate the bindings, so I started this.

Installation

You are probably looking for the GTK4 shard, not this one, since this shard is only useful if you are creating a binding for a GObject based library.

  1. Add the dependency to your shard.yml:

    developer_dependencies:
      gtk:
        github: hugopl/gi-crystal
    
  2. Run shards install

Usage

Bindings are specified in binding.yml files. When you run the generator it will scan all binding.yml files under the project directory and generate the bindings at lib/gi-crystal/src/auto/.

The generator is compiled in a post-install task and can be found at bin/gi-crystal after you run shards install.

See https://github.com/hugopl/gtk4.cr for an example of how to use it.

Memory Management ❤️‍🔥️

Crystal is a garbage collected language, you create objects and have faith that the GC will free them at some point in time, while on the other hand GLib uses reference count, the clash of these two approaches of how to deal with memory management can't end up in something beautiful without corner cases, etc... but we try our best to reduce the mess.

The basic rules are:

  • All objects (except enums, flags and unions) are created in the heap (including non GObject C Structs).
  • Boxed structs (except GValue) are always allocated by GLib but owned by Crystal wrappers.
  • If the struct is passed from C to Crystal with "transfer none", the struct is copied anyway to ensure that every Crystal object wrapper always points to valid memory. On "transfer full" no copy is needed.
  • All Crystal GObject wrappers have just a pointer to the C object (always allocated by GLib) and always hold a reference during their lifetime.

If you don't know what means Transfer full, Transfer none and few other terms about GOBject introspection, is worth to read the docs.

Debugging

To help debug memory issues you can compile your code with -Ddebugmemory, this will print the object address and reference counter to STDOUT when any wrapper object finalize method is called.

How GObject is mapped to Crystal world

Despite of being written in a language that doesn't have object oriented features, GObject is an object oriented library by design so many things maps easily to OO languages. However each language has its way of doing things and some adaptation is always needed to have a better blending and let the bindings feels more native to the language.

Class names

Class names do not have the module prefix, i.e. GFile from GLib module is mapped to GLib::File, GtkLabel is be mapped to Gtk::Label, where GLib and Gtk are modules.

Interfaces

GObject interfaces are mapped to Crystal modules + a dummy class that only implements this module, used when there's some function returning the interface.

Down Casts

Must be done using ClassName.cast(instance) or ClassName.cast?(instance), since Crystal type system doesn't knows about GObject type system. .cast throws a TypeCastError if the cast can't be made while .cast? just returns nil.

  builder = Gtk::Builder.new_from_string("...") # Returns a Gtk::Object
  label = Gtk::Label.cast(builder["label"])

A cast just creates a new wrapper object, so it increases the object reference count and allocate memory for the Crystal object instance.

Signal Connections

Suppose you want to connect the Gtk::Widget focus signal, the C signature is:

gboolean
user_function (GtkWidget       *widget,
               GtkDirectionType direction,
               gpointer         user_data)

The user_data parameter is used internally by bindings to pass closure data, so forget about it.

All signals are translated to a method named #{signal_name}_signal, that returns the signal object, the _signal suffix exists to solve name conflicts like Gtk::Window destroy method and destroy signal.

So there are 3 ways to connect this signal to a callback:

def slot_with_sender(widget, direction)
  # ...
end
# Connect to a slot with all arguments
widget.focus_signal.connect(->slot_with_sender(Gtk::Widget, Gtk::Direction)

def slot_without_sender(direction)
  # ...
end
# Connect to a slot without the sender
widget.focus_signal.connect(->slot_without_sender(Gtk::Direction)

# Connect to a block (always without sender parameter)
widget.focus_signal.connect do |direction|
  # ...
end

If the signal requires a slot that returns nothing, a slot that returns nothing (Nil) must be used, this is a limitation of the current implementation that will probably change in the future to just ignore the return value on those slots.

After signals

Instead of widget.focus_signal.connect, use widget.focus_signal.connect_after.

Signals with details

# To connect the equivalent in C to "notify::my_property" do
widget.notify_signal["my_property"].connect do
  # ...
end

Disconnecting signals

  • TBD

GValue

When returned by methods or as signal parameters they are represented by GObject::Value class, however if a method accepts a GValue as parameter you can pass any supported value. I.e. you can pass e.g. a plain Int32 to a method that in C expects a GValue.

GObject inheritance

You can inherit GObjects, when you do so a new type is registered in GObject type system. Crystal objects that inherit GObjects must always have a reference in Crystal world, otherwise they will be collected by the GC.

Trying to cast a GObject that was already collected by Crystal GC will result in a GICrystal::ObjectCollectedError exception.

Different from wrappers, crystal objects that inherit GObject returns the same object reference on casts, i.e. no memory allocation is done. For more examples see the inheritance tests.

Declaring GObject signals

  • TBD

Declaring GObject properties

  • TBD

GLib GError

GI-Crystal translates all GLib errors to different exceptions.

Example: G_FILE_ERROR_EXIST is a GLib error from domain FILE_ERROR with the code name EXIST, GICrystal translates this in these the following exception classes:

module GLib
  class GLibError < RuntimeError
  end

  class FileError < GLibError
    class Exist < FileError
      def code : Int32
        # ...
      end
    end
    # ...
  end
end

So if you want to rescue from this specific error you must rescue e : GLib::FileError::Exist, if you want to rescue from any error in this domain you must rescue e : GLib::FileError, and finally if you want to rescue from any GLib errors you do rescue e : GLib::GLibError.

Contributing

See HACKING.md for details about how the generator works.

  1. Fork it (https://github.com/hugopl/gi-crystal/fork)
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

Contributors

gi-crystal:
  github: hugopl/gi-crystal
  version: ~> 0.10.0
License BSD-3-Clause
Crystal >= 1.3.2

Authors

Libraries 1

  • libgirepository: ~> 1.70

Dependencies 1

Development Dependencies 0

Dependents 4

Last synced .
search fire star recently