[Gtk-sharp-list] Easy version of the boilerplate gtype code

Ben Maurer bmaurer@users.sourceforge.net
Mon, 29 Mar 2004 17:34:59 -0500


--=-7Mz8xxWyMKJ+0GsJPa/1
Content-Type: text/plain
Content-Transfer-Encoding: 7bit

Hello,

After the issue of the annoyance of the 8 line boilerplate code that is
needed to do overriding, I decided to take a look at the issue.

The attached patch takes care of it, I think.

Here is how it works:

You do your class as you would normally do it. That is, you can type:

	public class MyButton : Gtk.Button {
		public MyButton ()
		{
			Label = "I'm a subclassed button";
		}
		
		protected override void OnClicked ()
		{
			Console.WriteLine ("Button::Clicked default handler fired.");
		}
	}

And it will work as you expect it to.

On the inside, what happens is that the default ctor for an object now
looks like:

> 		protected Window () : base (true) {
> 			Raw = g_object_new (GetGType (GetType ()).Val, IntPtr.Zero);
> 		}
The base (true) construct is to lead to a constructor that will not do
stuff like registering gtypes. I realize this is messy, and welcome
suggestions.

GetGType will get the gtype for a given Type, or create it if it does not exist.

If the class has a default ctor, like Button, it generates code like:

> 		public Button() : base (true) 
> 		{
> 			if (GetType () != typeof (Button)) {
> 				Raw = g_object_new (GetGType (GetType ()).Val, IntPtr.Zero);
> 				return;
> 			}
> 			Raw = gtk_button_new();
> 		}

So, what this means is that if the object is a subclass of Button that
it will do the gtype creation rather than the default ctor. I think that
inside this if statement is where the bug that Mike was telling me about
(that you cant both register a gtype and call a ctor that sets values)
could be fixed. As I mention in the comments, someone smarter than I am
might know how to do this.

Mike brought up a valid concern about this patch, which I would like to
address here. He stated that the overhead of reflection operations
inside such a common operation would hurt performance. There are a few
mitigating factors here, however:

     1. This construct is only activated when a user creates one of his
        own types. This is a pretty rare operation in the scheme of
        things.
     2. A user who found that this construct was still too expensive
        could use the 8 line boiler plate code, the new sugar is not
        required.
     3. Object creation in gtk# already involves a hashtable lookup (in
        the GLib.Object.Raw property).

I would really appreciate feedback on this patch. I realize it is a bit
messy right now, and I admit it needs some cleaning. Mike, you always
have ideas on how to make code cleaner, so I would value your input.

The patch is attached, as well as the `new' version of Subclass.cs using
the new sugar.

-- Ben

--=-7Mz8xxWyMKJ+0GsJPa/1
Content-Disposition: attachment; filename=gtksharp-easy-override.patch
Content-Type: text/x-patch; name=gtksharp-easy-override.patch; charset=UTF-8
Content-Transfer-Encoding: 7bit

? sample/BenSubclass.cs
Index: generator/ClassBase.cs
===================================================================
RCS file: /cvs/public/gtk-sharp/generator/ClassBase.cs,v
retrieving revision 1.22
diff -u -r1.22 ClassBase.cs
--- generator/ClassBase.cs	2 Feb 2004 20:19:43 -0000	1.22
+++ generator/ClassBase.cs	29 Mar 2004 22:01:46 -0000
@@ -21,7 +21,7 @@
 
 		protected bool hasDefaultConstructor = true;
 		private bool ctors_initted = false;
-		private Hashtable clash_map;
+		protected Hashtable clash_map;
 
 		public Hashtable Methods {
 			get {
Index: generator/Ctor.cs
===================================================================
RCS file: /cvs/public/gtk-sharp/generator/Ctor.cs,v
retrieving revision 1.17
diff -u -r1.17 Ctor.cs
--- generator/Ctor.cs	2 Feb 2004 20:19:43 -0000	1.17
+++ generator/Ctor.cs	29 Mar 2004 22:01:46 -0000
@@ -121,7 +121,8 @@
 					parent = (ObjectGen) parent.Parent;
 				}
 				
-				sw.WriteLine("\t\tpublic static " + safety + modifiers +  name + " " + clashName);
+				sw.WriteLine("\t\tpublic static " + safety + modifiers +  name + " " + clashName);
+				
 				sw.WriteLine("\t\t{");
 
 				body.Initialize(gen_info, false, false, ""); 
@@ -133,9 +134,30 @@
 					sw.Write ("new {0} (", name);
 				sw.WriteLine (cname + "(" + body.GetCallString (false) + "));");
 			} else {
-				sw.WriteLine("\t\tpublic " + safety + name + "(" + sig.ToString() + ")");
-				sw.WriteLine("\t\t{");
-
+				sw.Write ("\t\tpublic " + safety + name + "(" + sig.ToString() + ")");
+				//
+				// We need to make sure it uses the empty constructor here.
+				//
+				if (container_type is ObjectGen)
+					sw.Write (" : base (true) ");
+				
+				sw.WriteLine ();
+				
+				sw.WriteLine("\t\t{");
+				
+				if (container_type is ObjectGen && (Params == null || Params.Count == 0)) {
+					//
+					// if this class is being subclassed, we need to register the GType.
+					// Sadly, this does not call the ctor for the base class in native
+					// code, there is a bug filed on this in bugzilla. Someone smart
+					// would know how to solve this ;-).
+					//
+					sw.WriteLine ("\t\t\tif (GetType () != typeof (" + name + ")) {");
+					sw.WriteLine ("\t\t\t\tRaw = g_object_new (GetGType (GetType ()).Val, IntPtr.Zero);");
+					sw.WriteLine ("\t\t\t\treturn;");
+					sw.WriteLine ("\t\t\t}");
+				}
+				
 				body.Initialize(gen_info, false, false, ""); 
 				sw.WriteLine("\t\t\t{0} = {1}({2});", container_type.AssignToName, cname, body.GetCallString (false));
 				body.HandleException (sw, "");
Index: generator/ObjectGen.cs
===================================================================
RCS file: /cvs/public/gtk-sharp/generator/ObjectGen.cs,v
retrieving revision 1.55
diff -u -r1.55 ObjectGen.cs
--- generator/ObjectGen.cs	18 Mar 2004 20:56:32 -0000	1.55
+++ generator/ObjectGen.cs	29 Mar 2004 22:01:46 -0000
@@ -193,10 +193,23 @@
 			gen_info.Writer.WriteLine("\t\t}");
 			gen_info.Writer.WriteLine();
 			gen_info.Writer.WriteLine("\t\tprotected " + Name + "(GLib.GType gtype) : base(gtype) {}");
-			gen_info.Writer.WriteLine("\t\tpublic " + Name + "(IntPtr raw) : base(raw) {}");
-			gen_info.Writer.WriteLine();
+			gen_info.Writer.WriteLine("\t\tpublic " + Name + "(IntPtr raw) : base(raw) {}");
+			gen_info.Writer.WriteLine("\t\tpublic " + Name + "(bool ignore_me) : base(ignore_me) {}");
 
-			base.GenCtors (gen_info);
+			gen_info.Writer.WriteLine();
+
+			bool default_ctor = hasDefaultConstructor;
+			hasDefaultConstructor = false;
+			
+			base.GenCtors (gen_info);
+			
+			if (!clash_map.ContainsKey("") && default_ctor) {
+				hasDefaultConstructor = false;
+				gen_info.Writer.WriteLine("\t\tprotected " + Name + " () : base (true) {");
+				gen_info.Writer.WriteLine("\t\t\tRaw = g_object_new (GetGType (GetType ()).Val, IntPtr.Zero);");
+				gen_info.Writer.WriteLine("\t\t}");
+				gen_info.Writer.WriteLine();
+			}
 		}
 
 		private void GenVMGlue (GenerationInfo gen_info, XmlElement elem)
Index: glib/Object.cs
===================================================================
RCS file: /cvs/public/gtk-sharp/glib/Object.cs,v
retrieving revision 1.58
diff -u -r1.58 Object.cs
--- glib/Object.cs	16 Mar 2004 19:43:04 -0000	1.58
+++ glib/Object.cs	29 Mar 2004 22:01:46 -0000
@@ -138,8 +138,23 @@
 			GtkSharp.ObjectManager.RegisterType (name, t.Namespace + t.Name, t.Assembly.GetName().Name);
 			GType gtype = new GType (gtksharp_register_type (name, parent_gtype.Val));
 			ConnectDefaultHandlers (gtype, t);
+			g_types [t] = gtype;
 			return gtype;
 		}
+		
+		static Hashtable g_types = new Hashtable ();
+		public static GType GetGType (System.Type t)
+		{
+			object o = g_types [t];
+			if (o != null)
+				return (GType) o;
+			
+			PropertyInfo pi = t.GetProperty ("GType", BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public);
+			if (pi != null)
+				return (GType) pi.GetValue (null, null);
+			
+			return RegisterGType (t);
+		}
 
 		protected Object () {}
 
@@ -149,11 +164,15 @@
 		}
 
 		[DllImport("libgobject-2.0-0.dll")]
-		static extern IntPtr g_object_new (IntPtr gtype, IntPtr dummy);
+		protected static extern IntPtr g_object_new (IntPtr gtype, IntPtr dummy);
 
 		protected Object (GType gtype)
 		{
 			Raw = g_object_new (gtype.Val, IntPtr.Zero);
+		}
+		
+		protected Object (bool ignore_me)
+		{
 		}
 
 		protected virtual IntPtr Raw {

--=-7Mz8xxWyMKJ+0GsJPa/1
Content-Disposition: attachment; filename=BenSubclass.cs
Content-Type: text/x-csharp; name=BenSubclass.cs; charset=UTF-8
Content-Transfer-Encoding: 7bit

// Subclass.cs - Widget subclass Test 
//
// Author: Mike Kestner <mkestner@ximian.com>
//
// (c) 2001-2003 Mike Kestner, Novell, Inc.

namespace GtkSamples {

	using Gtk;
	using System;

	public class ButtonApp  {

		public static int Main (string[] args)
		{
			Application.Init ();
			Window win = new Window ("Button Tester");
			win.DeleteEvent += new DeleteEventHandler (Quit);
			Button btn = new MyButton ();
			win.Add (btn);
			win.ShowAll ();
			Application.Run ();
			return 0;
		}

		static void Quit (object sender, DeleteEventArgs args)
		{
			Application.Quit();
		}
	}

	public class MyButton : Gtk.Button {
		public MyButton ()
		{
			Label = "I'm a subclassed button";
		}
		
		protected override void OnClicked ()
		{
			Console.WriteLine ("Button::Clicked default handler fired.");
		}
	}
}

--=-7Mz8xxWyMKJ+0GsJPa/1--