6

I am writing a plugin for Rhythmbox, wherein a signal raised is passing in an object of type GArray. The documentation for GLib Arrays shows me a few methods I am interested in, but am unable to access.

For example, g_array_index can get me the nth item in a GArray, but I am unable to call it. The GArray object doesn't show me any useful methods either.

To see what I mean, do this in a Python console:

from gi.repository.GLib import Array
x = Array()
dir(x)

Here is the output of dir(x)

['__class__', '__delattr__', '__dict__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__gtype__', '__hash__', '__info__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_free_on_dealloc', 'copy', 'data', 'len']

I see no methods in there read from the array, and nothing about the g_array_index or any other methods mentioned on the GLib Arrays documentation page. I also tried

    for a in x:
        print a

And also

list(x)

But I receive an error:

TypeError: 'Array' object is not iterable

Attempting x[0] gives this:

TypeError: 'Array' object does not support indexing

The len property gives the length of the array as is expected.

The data property gives this

enter image description here

How can I work with this GLib.Array that I am being passed?

I am running Python 2.7.4

Mendhak
  • 8,194
  • 5
  • 47
  • 64
  • You're looking at the C documentation, not the Python documentation. Unfortunately, I don't know if the current Python documentation is available anywhere online… – abarnert May 17 '13 at 22:25
  • Also: does `dir` show `__getitem__`? Does `x[0]` raise an `IndexError`, or an `AttributeError`? And does `list(x)` raise the same exception as `for a in x`? – abarnert May 17 '13 at 22:28
  • 1
    More generally, instead of just saying "I receive an error", show the error (with traceback), and show what `dir(x)` gave you, and so on. – abarnert May 17 '13 at 22:30
  • Added to the post - the output of the various things I tried, and the errors. I've included what you can do in Python console to see this for yourself to experiment with, in case I'm missing something obvious. – Mendhak May 18 '13 at 06:44
  • I'm still not sure why you're getting raw `Array` objects instead of some wrapper. (In pre-gobject bindings, you'd get a `GArray` and call the C-style functions on it, but everything I try with gobject seem to give me a `Variant` or similar object which has a pythonic interface (and seems to offer automatic boxing with optional manual packing, which is very cool). So… maybe you need a newer gobject/glib/rhythmbox? But I think we can work through this anyway. – abarnert May 20 '13 at 18:35
  • Your `Array` object has members named `data` and `len`. So, try logging `x.len` and `type(x.data)` and `dir(x.data)`. If either `data` or `len` are methods, call them and log the result, of course. – abarnert May 20 '13 at 18:35
  • Did you try connecting a callback to the signal? You should not be getting a GArray in the callback but rather a first class Python list since PyGI automatically marshals GArrays into Python lists... – Simon Feltman Sep 03 '14 at 13:06
  • @abarnert Do you have any documentation on the pre-gobject method of calling the "C-style functions on it"? I'm also getting a raw GArray from a GnomeKeyring API, and can't figure out how to unpack it in my python script. – Christopher Aug 29 '16 at 14:48

2 Answers2

2

Introduction

In this answer, I'll attempt to summarize what I've learned about this problem and provide a solution that can address some situations in which this problem arises. Unfortunately, in some cases, there seems to be no ready solution, though I think the Python GI module could be modified to enable workarounds (see Closing Thoughts, below).

Background

The core issue is that GArray is only a rather thin wrapper around malloc(), realloc(), and free(). Beyond that, it adds a couple features, such as ref-counting and built-in support for zero-termination. However, a notable feature it lacks is any knowledge of the array's element type! What this means is that the Python GI (GObject Introspection) module is unable to adapt an arbitrary GArray to work as a Python sequence type, without further information about what the GArray contains.

Official Method

The method of using GArrays that's supported by the gi module is to generate a .typelib file, which contains the additional information it needs, in order to adapt each specific GArray instance. Fortunately, there's a toolchain that exists to help you generate these files directly from your sourcecode.

To use this method, start by documenting your source code with comment blocks according to the format defined here:

Next, run the g-ir-scanner tool, in order to generate a .gir file. The docs for this tool can be found here:

Finally, the g-ir-compiler tool can be used to create the .typelib file. It's documented here:

A walk-through of this process is written up here:

Here's one with a Javascript focus:

And I can vouch for the fact that it works. Once you generate a .typelib file providing the necessary details of your GArray, the gi module will provide a familiar sequence-style interface to it, so you can use it like a list.

Workarounds

Unfortunately, what you cannot do is use the gi framework to expose functions for working with under-specified GArrays that you might get from other APIs! If you try to pass one of these GArrays into your function, Python complains about it not being a sequence type.

In my case, I was writing a GStreamer app, in which a particular pipeline element was generating GstMessages that contained a couple GArray members. While I couldn't write accessor functions to directly read the contents of these members, I found I could write functions which took the GstStructure, then read the desired member and returned it as a fully-specified GArray that gi could adapt as a proper Python sequence.

References

For more details on GArray, see:

In particular, note that while garray.h defines a GArray as a struct containing only a data and len member, you can see in garray.c that this interface type is backed by struct _GRealArray, which contains an additional 6 members.

For further information about the GObject Introspection framework and the Python gi module, see:

Closing Thoughts

Finally, what PyGObject could do to enable workarounds for libraries you don't control, is to expose the data member as a Python bytes object, with a length equal to GArray.len * GRealArray.elt_size.

Droid Coder
  • 381
  • 2
  • 10
0

GArray is not properly annotated/exported, hence it does not map to a Python object as you would expect. In fact you can check with the C snippet at the end what exactly is exported and what the Python module will see.

info_type = 3 [3 == struct]
n_fields = 2, n_methods = 0

As you can see, only two fields (len and data) are exported. So, to answer your question: as of now you cannot really work with GLib.Array in Python.

Here's the code:

#include <girepository.h>

int
main (int argc, char const* argv[])
{
    GIBaseInfo *info;
    GIStructInfo *struct_info;
    GITypelib *typelib;
    GIInfoType info_type;

    typelib = g_irepository_require (NULL, "GLib", NULL, 0, NULL);
    info = g_irepository_find_by_name (NULL, "GLib", "Array");
    info_type = g_base_info_get_type(info);

    g_print ("info_type = %i [3 == struct]\n", info_type);

    struct_info = (GIStructInfo *) info;

    g_print ("n_fields = %i, n_methods = %i\n",
             g_struct_info_get_n_fields (struct_info),
             g_struct_info_get_n_methods (struct_info));

    g_base_info_unref (info);
    g_typelib_free (typelib);
    return 0;
}
matthias
  • 2,161
  • 15
  • 22
  • I think you may have the wrong idea here. Boxed structures fully support memory management as well as methods. A GObject just adds more features (properties, signals, inheritance). It just happens that in this particular case, GArray does not expose anything and probably doesn't need to because language bindings generally translate the type into a native list type. – Simon Feltman Sep 03 '14 at 13:36
  • I will edit my answer later. One question then since you are the maintainer of PyGObject: why isn't then GArray properly translated to a Python list type? – matthias Sep 03 '14 at 16:32
  • It's unclear if there is actually a problem here (still waiting for OP to respond). The example given is manually constructing a GArray, not using the signal in question. If the signal is receiving a GArray, then that is a bug in either the annotations for the signals or PyGI. – Simon Feltman Sep 04 '14 at 19:54
  • I'm in the same situation - currently working with a library that sometimes returns a gi.repository.GLib.Array. I see the same attrs copy, data, len available, and none of the return python types (data is just an empty string, len is == 4). I tried to figure out if I could make a swig handler for them, but I don't see how to even get the C declaration for gi.repository.GLib.Array. I cloned pygoboject - it heavily uses meta classes and I got stuck there. – Paul Mikesell Feb 22 '19 at 06:26
  • Any updates on this issue? Is it possible to set the values in a GLib Array from Python? – Steve Osborne May 16 '19 at 21:21
  • `GArray` is a thin wrapper around `realloc()`, not unlike `std::vector`. The difference is that `std::vector` has static type information about its elements, while `GArray` requires the element type to be specified at runtime. Also, `GArray` has an embedded refcount and a couple other features. Hint: search for `struct _GRealArray`, in garray.c -- that will give you the full picture of what's going on. Anyway, I don't see how anyone can ague that exposing `data` as a zero-length string is in any way useful. If it were at least a `bytes` object, that would give us something to work with. – Droid Coder Oct 03 '19 at 08:02
  • Forgot to add that the `bytes` object for `data` should be `len * elt_size` long. – Droid Coder Oct 03 '19 at 08:10
  • @PaulMikesell I tried using the g-ir framework to define a function which enabled me to extract members from GArrays of ints, but when I imported that function into Python, it wouldn't accept my Glib.GArrays - instead, it wanted only Python sequence types! In your case, since you're defining your own library, you should be able to use g-ir-scanner and g-ir-compiler to generate your own .typelib file that specifies the needed information for gi to convert your Arrays for you. It's only in cases of using a 3rd party library where we're really stuck. – Droid Coder Oct 06 '19 at 20:05
  • To write your own GObject Introspection bindings, here's a straight-forward tutorial I found: https://storageapis.wordpress.com/2014/07/25/minimalistic-example-of-the-glibs-gboxedtype-usage/ The syntax of the annotations is defined here: https://wiki.gnome.org/Projects/GObjectIntrospection/Annotations And the framework is documented here: https://gi.readthedocs.io/en/latest/ – Droid Coder Oct 06 '19 at 20:07