Dynamic Method Invocation With GProxy
GObjects already have dynamic properties and signals. A natural extension would be to add dynamic method invocation.
Dynamic method invocation would allow for some nice modern features such as:
- Automatic export to an IPC system (such as DBus) ie. without any generated code
- Automatic import from IPC systems (such as DBus)
- Implement an object relational mapper
- Two way intergration of scripting environments
See full list of use cases below
Design Goals
- Do not depend on anything but glib
- Integrate DBus via a GModule, like GVfs is loaded in GIO via a module
- Allows stubs (for example for DBus) to accept a GProxy and expose it with signals, properties, and methods over the bus
It is a recurring scheme in dbus-glib that server implementations want to access the metadata of the method caller (like getting the bus name to detect later disconnects). We should somehow support this. In the below draft hints are used for this.
- Support of dynamic properties, signals, and methods (props and sigs are more or less given by GObject).
Be able to wrap embedded VMs such as Python by proxying method calls into a Python module. This is needed by GPlugin
- Supports compile time type checking as far as possible and runtime type checking the rest of the way. In other words "fear the var_args functions!".
- Be able to disconnect libraries and applications from the consumers of their interfaces. Ie libs/apps should not have to depend on DBus or Python, to expose them selves to these consumers
Code
I have started implementing a library called gdx "GLib Dynamic Extensions" that will provide an implementation of this spec. GDX is also slated to include the generic plugin functionality described in my GPlugin idea. The GDX is hosted on launchpad:
You can check out the code with
bzr branch lp:gdx
API
GProxy (interface)
A proxy is created by GProxyTransport.lookup() or manually much like implementing a GInterface (only dynamically, see GLocalProxy below).
- get_method (const gchar *name) : const GMethod*
get_methods (guint *num_methods) : const GMethod**, This method only returns non-NULL if the proxy binding type is STRICT
- get_binding_type () : GProxyBindingType
- invoke (GMethodInvocation *inv, GCancellable *c, GValue *return_val, GError **error) : gboolean
- invoke_async (GMethodInvocation *inv; GAsyncReadyCallback *cb, GCancellable *c, gpointer user_data) : void
- invoke_finish (GMethodResult *resp, GError **error): GValue*
GProxyBindingType (GEnum)
- SLOPPY, the available methods are not known before hand. This will be determined upon invocation
- STRICT, there is a strict set of fully qualified methods that can be invoked
- FREE, any method invocation will succeed, no matter method name or args (some web services behave like this)
GProxyParams (class)
Used to encapsulate special backend specific values, fx bus name, interface name, and object paths for dbus objects, passing it to GProxyTransport.lookup(). Each GProxyTransport subclass must specify a set of namespaced parameters they accept and which GType these values should be. For the dbus module this could be dbus:bus-name, (G_TYPE_STRING), dbus:interface, (G_TYPE_STRING), and dbus:object-path, (G_TYPE_STRING). Whether or not to use mainloop intergration or threads for async invocations could be another example.
- get_param (const gchar *name) : const GValue*
- set_param (const gchar *name, GValue *val) : void
list () : GSList<GParameter>*
GProxyTransport (interface)
- lookup (GProxyParams *params, GCancellable *c, GError **error) : GProxy*
(FIXME: Do we need an async variant?)
- export (GProxy proxy, GProxyParams *param, GCancellable *c, GError **error) : gboolean
(FIXME: Do we need an async variant?)
Note that export need not make sense on all transports (eg. Python) and they should just ignore requests to do so (logging a warning).
- get_transport (const gchar *trans_name) : GProxyTransport*
This method is a static factory to get a proxy transport corresponding to a given GModule, fx "dbus"
FIXME: To really take this the whole way we could use a GPluginLoader (see GPlugin) to load the proxy transports. This way GLib's DBus proxy transport (and thus all DBus support) could for example be implemented in pure Python. Not that we would this, but it demonstrates the flexibility and power of the GProxy/GPlugin unholy alliance.
- params (guint *n_params) : const GParamSpec*
GMethod (class)
- prepare_invocation () : GMethodInvocation*
- get_name () : const gchar*
- get_arg_types (guint *n_args) : GType*
- get_return_type () : GType
- get_num_args () : uint
- get_hint_specs (guint *n_hints) : GParamSpec**
GMethodInvocation (class) A method invocation encapsulates data passed by the client to the method. Conventional method arguments are passed via the *_arg() methods. In addition to conventional arguments special invocation hints can be used. Invocation hints can be used to augment the method invocation and request additional information from the invocation. A prime example here being a request to also get the DBus message sender's bus name. To do this one could pass "dbus:sender"" and NULL to append_hint() and the method response would contain the the sender name as a hint value.
- append_arg (GType arg_type, const GValue *val) : void
- append_CTYPE_arg (CTYPE arg) : void
- get_arg (guint i, GValue *return_val) : void
- get_CTYPE_arg (guint i) : CTYPE
- get_arg_count (): guint
- append_hint (const gchar *name, const GValue *value): void
- get_hint (const gchar *name, GValue *return_val) : void
- foreach_hint (GHintVisitor visitor, gpointer user_data): void
- get_method (): GMethod*
GMethodInvocationError (GError)
- GError/Enumeration of errors related strictly to the invocation of a method.
- INVALID_ARGUMENTS, the supplied args does not match the method signature
- INVALID_METHOD, the method is not bound in the proxy
- BROKEN_TRANSPORT, the underlying transport has reported an error - think broken dbus connection
GMethodResult (GAsyncResult)
- get_hint (const gchar *name, GValue *return_val): void
foreach_hint (GdxHintVisitor visitor_func, gpointer user_data): void
GDynamicProxy (GProxy) abstract
- g_dynamic_proxy_install_method (const gchar *method_name, GType *arg_types, guint n_args, GType return_type, GParamSpec **hints, guint n_hints) : const GMethod*
GLocalProxy (GDynamicProxy) Generic proxy implementation keeping a dynamic vtable in contrast to the static vtable of GInterfaces.
g_local_proxy_new () : GProxy* (You do not need to pass through a GProxyTransport to create a local proxy)
Problems
- Async reading of GObject properties
Use Cases
- DBus
Two way integration of native code with scripting environments. Fx C <-> Javascript, Vala <-> Python
- ORM
- Avahi
- GUPnP
- Generic REST client based on Soup
- Native class methods, ie named invocation of ordinary C/GObject methods. Think a GInterface with method introspection
Crackpot Idea: Django-like Application Development
A few configuration files, some IDL, a Django-like templating language to output GtkBuilder XML, Some C, Vala, or Genie code (or other languages via bindings), and... Presto. You have a DBus enabled application storing persistent data in a database (or what ever), interacting cleanly with other services on the bus.
Smaller apps (such as capplets) could possibly consist of only a configuration file and a GtkBuilder template file. Larger projects would probably still fare better the conventional way.