Skip to content

Instantly share code, notes, and snippets.

@Xliff
Last active November 5, 2018 23:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Xliff/b98f00d931942b62367fafc65ad3c737 to your computer and use it in GitHub Desktop.
Save Xliff/b98f00d931942b62367fafc65ad3c737 to your computer and use it in GitHub Desktop.
Raku - The troubles with GValue

One of the more basic aspects of GTK programming is setting values using GValue objects provided from GLib. GValues are used in almost everything, as it provides a way to abstract out typed values. To push home this point, GLib itself calls them Generic Values While trying to write p6-GtkPlus, getting the GValue object has turned into one of the more ahem difficult aspects of the port.

Take this simple C example:

// test_gvalue.c
#include <gtk/gtk.h>

void main(int argc, char **argv) {
  gtk_init(&argc, &argv);

  GtkImage *i = (GtkImage *)gtk_image_new();
  GValue v = {0};
  g_value_init(&v, G_TYPE_STRING);
  g_value_set_string(&v, "image-missing");
  const char *props[] = { "icon-name" };
  GValue vals[] = { v };
  g_object_setv((GObject *)i, 1, props, vals);

  g_value_unset(&v);
  g_value_init(&v, G_TYPE_STRING);
  g_object_getv((GObject *)i, 1, props, &v);
  printf(
    "%s, %ld, %ld\n",
    g_value_get_string(&v),
    G_TYPE_STRING,
    sizeof(v)
  );

}

The purpose of this program is to set the GtkImage.icon-name property to the value "image-missing". Here is the equivalent Raku program:

# ./test_gvalue.pl6
use v6.c;
use lib '.';
use NativeCall;
use test_gvalue;

gtk_init(CArray[uint32], CArray[Str]);

my $i = gtk_image_new();
my $v = GValue.new;
g_value_init($v, G_TYPE_STRING);
g_value_set_string($v, 'image-missing');
my $props = CArray[Str].new;
$props[0] = 'icon-name';
my $vals = CArray[GValue].new;
$vals[0] = $v;
# Whuuut?
g_object_setv(nativecast(GObject, $i), 1, $props, $vals);

g_value_unset($v);
g_value_init($v, G_TYPE_STRING);
g_object_getv(nativecast(GObject, $i), 1, $props, $vals);
sprintf("%s, %d, %d",
  g_value_get_string($v) // '(Str)',  # Prints '(Str)' if undefined.
  G_TYPE_STRING.Int,
  nativesizeof(GValue)
).say;

And while I have your attention, here are the definitions that make that small program possible (It's short...ish):

# ./test_gvalue.pm6
use v6.c;

use NativeCall;

unit package test_gvalue;

constant gtk      is export = 'gtk-3',v0;
constant gobject  is export = 'gobject-2.0',v0;

class GObject   is repr('CPointer') is export { }
class GtkWidget is repr('CPointer') is export { }

class GTypeValueList is repr('CUnion') is export {
  has int32	          $.v_int     is rw;
  has uint32          $.v_uint    is rw;
  has long            $.v_long    is rw;
  has ulong           $.v_ulong   is rw;
  has int64           $.v_int64   is rw;
  has uint64          $.v_uint64  is rw;
  has num32           $.v_float   is rw;
  has num64           $.v_double  is rw;
  has OpaquePointer   $.v_pointer is rw;
}

class GValue is repr('CStruct') is export {
  has ulong           $.g_type is rw;
  HAS GTypeValueList  $.data1  is rw;
  HAS GTypeValueList  $.data2  is rw;
}

our enum GTypeEnum is export (
  G_TYPE_INVALID   => 0,
  G_TYPE_NONE      => (1  +< 2),
  G_TYPE_INTERFACE => (2  +< 2),
  G_TYPE_CHAR      => (3  +< 2),
  G_TYPE_UCHAR     => (4  +< 2),
  G_TYPE_BOOLEAN   => (5  +< 2),
  G_TYPE_INT       => (6  +< 2),
  G_TYPE_UINT      => (7  +< 2),
  G_TYPE_LONG      => (8  +< 2),
  G_TYPE_ULONG     => (9  +< 2),
  G_TYPE_INT64     => (10 +< 2),
  G_TYPE_UINT64    => (11 +< 2),
  G_TYPE_ENUM      => (12 +< 2),
  G_TYPE_FLAGS     => (13 +< 2),
  G_TYPE_FLOAT     => (14 +< 2),
  G_TYPE_DOUBLE    => (15 +< 2),
  G_TYPE_STRING    => (16 +< 2),
  G_TYPE_POINTER   => (17 +< 2),
  G_TYPE_BOXED     => (18 +< 2),
  G_TYPE_PARAM     => (19 +< 2),
  G_TYPE_OBJECT    => (20 +< 2),
  G_TYPE_VARIANT   => (21 +< 2),

  G_TYPE_RESERVED_GLIB_FIRST => 22,
  G_TYPE_RESERVED_GLIB_LAST  => 31,
  G_TYPE_RESERVED_BSE_FIRST  => 32,
  G_TYPE_RESERVED_BSE_LAST   => 48,
  G_TYPE_RESERVED_USER_FIRST => 49
);

sub g_value_unset (GValue $v)
  is native(gobject)
  is export
  { * }

sub g_object_setv (
  GObject $object,
  uint32 $n_properties,
  CArray[Str] $names,
  # Note... not an array.
  CArray[GValue] $values
)
  is native(gobject)
  is export
  { * }

sub g_object_getv (
  GObject $object,
  uint32 $n_properties,
  CArray[Str] $names,
  CArray[GValue] $values
)
  is native(gobject)
  is export
  { * }

sub gtk_init(CArray[uint32], CArray[Str])
  is native(gtk)
  is export
  { * }

sub g_value_init (GValue $value, uint64 $type)
  returns GValue
  is native(gobject)
  is export
  { * }

sub g_value_get_string (GValue $value)
  returns Str
  is native(gobject)
  is export
  { * }

sub g_value_set_string (GValue $value, Str $val)
  is native(gobject)
  is export
  { * }

sub gtk_image_new ()
  returns GtkWidget
  is native(gtk)
  is export
  { * }

OK... I lied. But still, you can take both the script and the definition and reproduce what I am about to show next, which is the point.

When I run the C test program, I get what I'd expect: proof that the value was set, and that I can get it back (along with other data, which I will explain later:

$ ./test_gvalue 
image-missing, 64, 24

So we have confirmation that we can get the string "image-missing" back, along with the value 64 which represents G_TYPE_STRING. G_TYPE_STRING tells us the GValue contains a string value. The last number is 24, which is the size of the GValue type. Let's compare this with what we get back from the Raku program:

$ perl6 test_gvalue.pl6 

(moar:23295): GLib-GObject-CRITICAL **: 13:37:13.616: g_value_transform: assertion 'G_IS_VALUE (src_value)' failed

(moar:23295): GLib-GObject-WARNING **: 13:37:13.616: unable to set property 'icon-name' of type 'gchararray' from value of type 'focus-tab'
(Str), 64, 24

Confused? Yes. It has been. The first thing that stands out are the warnings from GLib. The first notifies us that GLib does not like our Raku version of the GValue. When using something like NativeCall, things like this are expected, but we hope proper use of the ABI will let everything work. The next warning is the one that sets the alarm bells off. This is mostly due to the fact that it will change at every execution. If I run that program 5 more times, I get many different answers:

unable to set property 'icon-name' of type 'gchararray' from value of type 'GParamGType'
unable to set property 'icon-name' of type 'gchararray' from value of type 'CairoRectangleInt'
unable to set property 'icon-name' of type 'gchararray' from value of type 'font-options'
unable to set property 'icon-name' of type 'gchararray' from value of type 'focus-tab'

This tells me that some corruption is entering the call, and I cannot find out where from.

ODDLY ENOUGH!

There is a weird fix to this problem, but it only works when setting a single attribute:

# MODIFIED--sortaworking ./test_gvalue.pl6
use v6.c;
use lib '.';
use NativeCall;
use test_gvalue;

gtk_init(CArray[uint32], CArray[Str]);

my $i = gtk_image_new();
my $v = GValue.new;
g_value_init($v, G_TYPE_STRING);
g_value_set_string($v, 'image-missing');
my $props = CArray[Str].new;
$props[0] = 'icon-name';
# Whuuut?
g_object_setv(nativecast(GObject, $i), 1, $props, nativecast(Pointer, $v));

g_value_unset($v);
g_value_init($v, G_TYPE_STRING);
g_object_getv(nativecast(GObject, $i), 1, $props, nativecast(Pointer, $v));
sprintf("%s, %d, %d",
  g_value_get_string($v) // '(Str)',  # Prints '(Str)' if undefined.
  $v.g_type,
  nativesizeof(GValue)
).say;

sub g_object_setv (
  GObject $object,
  uint32 $n_properties,
  CArray[Str] $names,
  # Note... not an array.
  #CArray[GValue] $values
  Pointer $v
)
  is native(gobject)
  is export
  { * }

sub g_object_getv (
  GObject $object,
  uint32 $n_properties,
  CArray[Str] $names,
  #CArray[GValue] $values
  Pointer $v
)
  is native(gobject)
  is export
  { * }

This works because the we are effectively doing the same thing, just for the first element in the Array-that-isn't-but-should-be-$vals. Since we want to pass a pointer to $v as the first element, we can pass in that pointer and it works. But we really want a general purpose solution for further development. In this case, however. We can use this as a workaround.

So what would be the best solution for this problem?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment