D/BUS IDL compiler ...

Michael Meeks michael@ximian.com
Fri, 05 Mar 2004 16:23:15 +0000


--=-Ja3LglJd6Mr4gsObEil7
Content-Type: text/plain
Content-Transfer-Encoding: 7bit

Hi guys,

	So - attached is my 1st pass at a dbus IDL compiler; so far it parses a
chunk of (what looks like) pseudo-IDL pinched from the HAL spec, and
there is an embedded test that generates working code for (at least)
oneway methods, implemented by test-service.c (couldn't find much more
meat to throw it at).

	There are a number of issues / problems which I'd like to preempt with
it's introduction however:

	* It's written in perl - I don't like perl - perl, being a 
	  pre-requisite for autotools is available on every developer
	  platform; the IDL only needs re-compiling by developers. QED.
	  Indeed, perl is (empirically) more portable than autotools,
	  it's also an excellent language for string processing, and
	  made it very fast to write this. It's also not that bad if
	  you think for a few seconds before typing.

	* It appears to re-implement dbus_message_append_valist etc.
	  however - it needs to do this to return the advanced va_list
	  to allow return value de-marshalling.

	* It doesn't implement the '[in],out' specifiers for parameters
	  - a simple limitation, to be fixed.

	* It doesn't implement the array types - correct, the lack of a
	  decent recursive type description is sufficiently annoying to
	  me that I can't stomach it yet.

	* The pre-processor doesn't implement /* */ comments - and/or it
	  doesn't fork right - again, fixable bugs.

	* The license is LGPL/X11 - I'm still deeply concerned with the
	  unresolved AFL legal issue.

	* It doesn't generate stubs/skels - true, clearly we don't need
	  stubs (since we have control of the calling convention), but
	  skel support [ in the form of easy-to-use glib-genmarshal type
	  macros ] is planned & should be easy enough.

	* It handles error conditions as leakily as the existing _valist
	  methods: true enough ;-) this should be easy enough to fix
	  though.

	There are a number of other issues that this throws up with D/BUS that
I hadn't noticed before, and it'd be nice to understand:

	* Why do we have a 'service' and a 'path' concept ? surely a 
	  given service will bind to a given path ? someone must know
	  this ? it's slightly surreal having a method taking three
	  strings which are ~mostly identical in ~every case. Is it
	  really worth having this extra dimension to the space ? is
	  it really orthogonal ?

	* 'signals' - I'm confused as to what to generate for these;
	  it seems to me that there are 2 needs:

		a) marshalling type information for the emitter
		b) proxying type information for the listener

	  of course - we could duplicate this information in some
	  way - but given that a 'signal' seems to be (simply) a 
	  oneway method call; it would seem more sensible (perhaps)
	  to use a 'Listener' pattern to allow a simpler
	  implementation.

	* signal emission - in Bonobo we discovered that often
	  signals are emitted with no listener :-) thus it makes
	  sense to be able to detect this eventuality from the	
	  emitter and avoid the cost - is that something that makes
	  sense with dbus ? and/or that is done already.

	More questions to come I guess;

	My hope is - that this will be something useful; I guess it'll be more
useful on the server-side though - at least, judging from my quick
skimming of HAL there is too much hand-coded error-prone
marshal/de-marshal logic around that's verbose & hard to audit. 

	I hope to do a little work on this in the next few days, so flames,
outrage, constructive comments etc. appreciated :-) and/or is this
something that could live in dbus CVS ?

	Thanks,

		Michael.

-- 
 michael@ximian.com  <><, Pseudo Engineer, itinerant idiot

--=-Ja3LglJd6Mr4gsObEil7
Content-Disposition: attachment; filename=diddle.diff
Content-Type: text/x-patch; name=diddle.diff; charset=ISO-8859-1
Content-Transfer-Encoding: 7bit

Index: Makefile.am
===================================================================
RCS file: /cvs/dbus/dbus/Makefile.am,v
retrieving revision 1.18
diff -u -p -u -r1.18 Makefile.am
--- a/Makefile.am	17 Oct 2003 16:23:19 -0000	1.18
+++ b/Makefile.am	5 Mar 2004 16:00:06 -0000
@@ -27,7 +27,7 @@ dist-local:
 		echo "You have to build with Qt and GLib to make dist" ;	\
 	fi 
 
-SUBDIRS=dbus bus doc $(GLIB_SUBDIR) $(GCJ_SUBDIR) $(MONO_SUBDIR) $(QT_SUBDIR) $(PYTHON_SUBDIR) test tools
+SUBDIRS=dbus bus doc $(GLIB_SUBDIR) $(GCJ_SUBDIR) $(MONO_SUBDIR) $(QT_SUBDIR) $(PYTHON_SUBDIR) test tools idl
 
 pkgconfigdir = $(libdir)/pkgconfig
 pkgconfig_DATA = dbus-1.pc $(GLIB_PC)
Index: configure.in
===================================================================
RCS file: /cvs/dbus/dbus/configure.in,v
retrieving revision 1.76
diff -u -p -u -r1.76 configure.in
--- a/configure.in	19 Nov 2003 21:51:09 -0000	1.76
+++ b/configure.in	5 Mar 2004 16:00:06 -0000
@@ -935,6 +935,7 @@ bus/session.conf
 bus/messagebus
 bus/dbus-daemon-1.1
 Makefile
+idl/Makefile
 dbus/Makefile
 glib/Makefile
 python/Makefile
--- /dev/null	2003-09-23 18:59:22.000000000 +0100
+++ idl/Makefile.am	2004-03-05 14:32:18.000000000 +0000
@@ -0,0 +1,21 @@
+bin_SCRIPTS = diddle
+
+lib_LTLIBRARIES=libtbus-1.la
+
+diddleincludedir=$(includedir)/libtbus-1.0/tbusa
+diddleinclude_HEADERS = tbus.h
+
+INCLUDES=-I$(top_srcdir) $(DBUS_CLIENT_CFLAGS) -DDBUS_COMPILATION
+
+libtbus_1_la_SOURCES = tbus.c
+libtbus_1_la_LIBADD= $(DBUS_CLIENT_LIBS) -ldbus-1
+
+check_PROGRAMS = test-idl
+
+test_idl_SOURCES = test-idl.c
+test_idl_LDADD = $(DBUS_CLIENT_LIBS) -ltbus-1
+
+test.h : test.idl $(srcdir)/diddle
+	$(srcdir)/diddle -otest.h test.idl 
+test-idl.c : test.h
+
--- /dev/null	2003-09-23 18:59:22.000000000 +0100
+++ idl/test.idl	2004-03-05 15:48:01.000000000 +0000
@@ -0,0 +1,12 @@
+module org.freedesktop {
+  interface TestSuite {
+    oneway void Echo (string arg);
+    oneway void EmitFoo ();
+
+// Signals on interfaces seems to me to badly
+// confuse the whole client/server scheme. We should
+// have a listener interface surely.
+    signal Foo(double length);
+  };
+};
+
--- /dev/null	2003-09-23 18:59:22.000000000 +0100
+++ idl/test-idl.c	2004-03-05 15:25:49.000000000 +0000
@@ -0,0 +1,46 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <test.h>
+#include <dbus/dbus.h>
+
+static void
+error_exit (DBusError *error)
+{
+	fprintf (stderr, "Failed to invoke Echo: '%s'\n",
+		 error ? error->name : "<NoEx>");
+	exit (1);
+}
+
+int main (int argc, char **argv)
+{
+	DBusConnection *connection;
+	DBusError error[1];
+  
+	dbus_error_init (error);
+	connection = dbus_bus_get (DBUS_BUS_ACTIVATION, error);
+	if (!connection) {
+		fprintf (stderr, "Failed to get connection\n");
+		return 1;
+	}
+
+	if (!org_freedesktop_TestSuite_Echo (connection,
+					     "org.freedesktop.DBus.TestSuiteEchoService",
+					     "/org/freedesktop/TestSuite",
+					     "Hello World", error))
+		error_exit (error);
+
+	if (!org_freedesktop_TestSuite_EmitFoo (connection,
+						"org.freedesktop.DBus.TestSuiteEchoService",
+						"/org/freedesktop/TestSuite", error))
+		error_exit (error);
+
+	dbus_connection_flush (connection);
+	dbus_connection_disconnect (connection);
+	dbus_connection_unref (connection);
+
+	printf ("All tests succeded\n");
+
+	dbus_shutdown();
+
+	return 0;
+}
--- /dev/null	2003-09-23 18:59:22.000000000 +0100
+++ idl/tbus.c	2004-03-05 15:54:51.000000000 +0000
@@ -0,0 +1,184 @@
+#include <stdio.h>
+#include <tbus.h>
+#include <dbus/dbus-protocol.h>
+#include <dbus/dbus-internals.h>
+
+#define ARG_DELIMITER '_'
+
+static dbus_bool_t
+marshal_args (DBusMessage *message,
+	      char       **method_descr,
+	      DBusError   *error,
+	      va_list     *args)
+{
+	char *p;
+	dbus_bool_t err = FALSE;
+	DBusMessageIter iter;
+
+	dbus_message_append_iter_init (message, &iter);
+
+	for (p = *method_descr; !err && *p != ARG_DELIMITER; p++) {
+
+#		define MAP(dbus_type, method, c_type) \
+		    case DBUS_TYPE_##dbus_type: \
+			err |= !dbus_message_iter_append##method (&iter, va_arg (*args, c_type)); \
+			break
+
+		switch (*p) {
+		case DBUS_TYPE_NIL:
+			err |= !dbus_message_iter_append_nil (&iter);
+			break;
+		MAP (BYTE, _byte, unsigned char);
+		MAP (BOOLEAN, _boolean, dbus_bool_t);
+		MAP (INT32, _int32, dbus_int32_t);
+		MAP (UINT32, _uint32, dbus_uint32_t);
+#ifdef DBUS_HAVE_INT64
+		MAP (INT64, _int64, dbus_int64_t);
+		MAP (UINT64, _uint64, dbus_uint64_t);
+#endif
+		MAP (DOUBLE, _double, double);
+		MAP (STRING, _string, const char *);
+		case DBUS_TYPE_ARRAY:
+		case DBUS_TYPE_OBJECT_PATH:
+		case DBUS_TYPE_CUSTOM:
+		case DBUS_TYPE_DICT:
+		default:
+			_dbus_warn ("More design / thought required %c", *p);
+			err = 1;
+			break;
+		}
+#		undef MAP
+	}
+
+	*method_descr = p;
+
+	return !err;
+}	     
+
+static dbus_bool_t
+demarshal_args (DBusMessage *message,
+		char       **method_descr,
+		DBusError   *error,
+		va_list     *args)
+{
+	char *p;
+	dbus_bool_t err = FALSE;
+	DBusMessageIter iter;
+
+	dbus_message_iter_init (message, &iter);
+
+	for (p = *method_descr; !err && *p != ARG_DELIMITER; p++) {
+		int type;
+
+		type = dbus_message_iter_get_arg_type (&iter);
+		if (type != *p) {
+			dbus_set_error (error, DBUS_ERROR_INVALID_ARGS,
+					"Argument type mismatch %s should be %s",
+					_dbus_type_to_string (type),
+					_dbus_type_to_string (*p));
+			err = TRUE;
+			break;
+		}
+
+#		define MAP(dbus_type, method, c_type) \
+		    case DBUS_TYPE_##dbus_type: \
+			*(c_type *)va_arg(*args, c_type *) = dbus_message_iter_get##method (&iter); \
+			break
+		switch (*p) {
+		case DBUS_TYPE_NIL:
+			break;
+		MAP (BYTE, _byte, unsigned char);
+		MAP (BOOLEAN, _boolean, dbus_bool_t);
+		MAP (INT32, _int32, dbus_int32_t);
+		MAP (UINT32, _uint32, dbus_uint32_t);
+#ifdef DBUS_HAVE_INT64
+		MAP (INT64, _int64, dbus_int64_t);
+		MAP (UINT64, _uint64, dbus_uint64_t);
+#endif
+		MAP (DOUBLE, _double, double);
+		MAP (STRING, _string, const char *);
+		case DBUS_TYPE_ARRAY:
+		case DBUS_TYPE_OBJECT_PATH:
+		case DBUS_TYPE_CUSTOM:
+		case DBUS_TYPE_DICT:
+		default:
+			_dbus_warn ("More design / thought for demarshalling '%c'", *p);
+			err = 1;
+			break;
+		}
+#		undef MAP
+		if (p[1] != ARG_DELIMITER && !dbus_message_iter_next (&iter)) {
+			dbus_set_error (error, DBUS_ERROR_INVALID_ARGS,
+					"More return values arrived than expected");
+			err = TRUE;
+		}
+	}
+		
+	if (p[1] != ARG_DELIMITER) {
+		dbus_set_error (error, DBUS_ERROR_INVALID_ARGS,
+				"More arguments specified than arrived");
+		err = TRUE;
+	}
+
+	return !err;
+}	     
+
+dbus_bool_t
+tbus_invoke (DBusConnection *cnx,
+	     const char     *service,
+	     const char     *path,
+	     const char     *iface,
+	     DBusError      *error,
+	     const char     *method_descr,
+	     ...)
+
+{
+	va_list args;
+	char *argp;
+	dbus_bool_t success;
+	DBusMessage *message;
+	const char  *method_name;
+
+	method_name = strrchr (method_descr, '_') + 1;
+
+	va_start (args, method_descr);
+
+	message = dbus_message_new_method_call (service, path, iface, method_name);
+	if (!message)
+		return FALSE;
+
+	argp = (char *) method_descr;
+	if (!(success = marshal_args (message, &argp, error, &args))) {
+		fprintf (stderr, "Failed to marshal\n");
+		goto out;
+	}
+
+	argp++;
+	if (*argp++ == 'o') { /* oneway */
+		dbus_message_set_no_reply (message, TRUE);
+	
+		success = dbus_connection_send (cnx, message, NULL);
+	} else {
+		DBusMessage *reply;
+
+		reply = dbus_connection_send_with_reply_and_block (cnx, message, -1, error);
+		if (!reply) {
+			fprintf (stderr, "Failed to get reply\n");
+			success = FALSE;
+			goto out;
+		}
+
+		success = demarshal_args (message, &argp, error, &args);
+		if (!success)
+			fprintf (stderr, "Failed to get demarshal args\n");
+
+		dbus_message_unref (reply);
+	}
+
+ out:
+	dbus_message_unref (message);
+
+	va_end (args);
+
+	return success;
+}
--- /dev/null	2003-09-23 18:59:22.000000000 +0100
+++ idl/tbus.h	2004-03-05 15:00:05.000000000 +0000
@@ -0,0 +1,15 @@
+#ifndef TBUS_H
+#define TBUS_H
+
+#include <dbus/dbus-connection.h>
+
+/* Called by generated macros only */
+dbus_bool_t tbus_invoke (DBusConnection *cnx,
+			 const char     *service,
+			 const char     *path,
+			 const char     *iface,
+			 DBusError      *error,
+			 const char     *method_descr,
+			 ...);
+
+#endif /* TBUS_H */
--- /dev/null	2003-09-23 18:59:22.000000000 +0100
+++ idl/bus.idl	2004-02-17 16:19:28.000000000 +0000
@@ -0,0 +1,89 @@
+interface Empty {
+
+}
+
+interface Devices {
+
+	#
+	# Return a list of all devices in the GDL
+	#
+	# @return                       List of UDI's
+	#
+	array{string} GetAllDevices();
+                                                                                                                                          
+	# Determine if a device with a given Unique Device Id exists in the GDL
+	#
+	# @param  udi                   Device UDI, for example '/org/freedesktop/Hal/devices/pci_8086_7111'
+	# @return                       TRUE iff the device with the given UDI exists
+	#
+	bool DeviceExists(string udi);
+                                                                                                                                          
+	# Find the set of devices in the GDL that has a given property matching
+	# a given value
+	#
+	# @param  key                   Key, for example 'block.fstype'
+	# @param  value                 Value, for example 'ext3'
+	# @return                       List of UDI's
+	#
+	array{string} FindDeviceStringMatch(string key, string value);
+                                                                                                                                          
+	# Find the set of devices in the GDL that has a given capability
+	#
+	# @param  capability            Capability, for example 'volume'
+	# @return                       List of UDI's
+	#
+	array{string} FindDeviceByCapability(string capability);
+                                                                                                                                          
+	# Notification that a new device have been added to the GDL
+	#
+	# @param  udi                   Unique Device Id
+	#
+	void DeviceAdded(string udi);
+                                                                                                                                          
+	# Notification that a new device have been removed from the GDL. The
+	# application cannot use this UDI anymore.
+	#
+	# @param  udi                   Unique Device Id
+	#
+	void DeviceRemoved(string udi);
+                                                                                                                                          
+	# Notification that a device in the GDL have got a new capability. Note that
+	# this is emitted even though the device already had the old capability
+	#
+	# @param  udi                   Unique Device Id
+	#
+	void NewCapability(string udi, string capability);
+};
+
+interface PropertyBag {
+	# Set property
+	#
+	# @param  key                   Property to set
+	# @param  value                 Value to set
+	# @raises                       org.freedesktop.Hal.(NoSuchDevice|TypeMismatch)
+	#
+	void SetProperty(string key, any value);
+	void SetPropertyString(string key, string value);
+	void SetPropertyInteger(string key, int32 value);
+	void SetPropertyBoolean(string key, bool value);
+	void SetPropertyDouble(string key, double value);
+
+	# Get property
+	#
+	# @param  key                   Property to get
+	# @return                       The value of the property
+	# @raises                       org.freedesktop.Hal.(NoSuchDevice|NoSuchProperty|TypeMismatch)
+	#
+	any GetProperty(string key);
+	string GetPropertyString(string key);
+	int32 GetPropertyInteger(string key);
+	bool GetPropertyBoolean(string key);
+	double GetPropertyDouble(string key);
+                                                                                                                                          
+	# Get all properties
+	#
+	# @return                       Dictionary from key to value
+	# @raises                       org.freedesktop.Hal.NoSuchDevice
+	#
+	map{string, any} GetAllProperties();
+};

--=-Ja3LglJd6Mr4gsObEil7--