-
Notifications
You must be signed in to change notification settings - Fork 27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[RFC-1] Type System Integration with GObject #46
Comments
Thank you for your document, it is amazing! I agree with the content of your document. Just some remarks we should consider:
Subclass constructor |
Fully agree.
We shouldn't, thankfully. We'll need access to the class struct(s) in order to override virtual functions, so having
It isn't necessarily the connecting bits that are the problem. We really just need a way to create objects through an interface. The factory pattern would normally work for this, but forcing devs to write an entire factory for every object seems ridiculous. Ultimately, we could just require that there is an empty constructor present and let the user do whatever they like with it. The only downside then is that if we create a subclass with |
The toggle ref should keep our instance alive so there is no need to recreate an object which existed before or do I miss s.th.? |
Don't think I worded that properly. If someone (either code from C/language bindings/GLib or some kind of GtkWidget widget = g_object_new (MY_TYPE_SUBCLASS,
"width", 800,
"title", "Hello",
NULL); Which should create an instance of MySubclass with properties If we're hijacking the constructor callback to call the C# constructor, we want these properties to be automatically set. It's just a question of how to do this. If we set them ourselves (i.e. using the returned object), they won't be accessible in the constructor. If we let the user handle (or conversely not handle) it, we lose the guarantee that the properties are set. That being said, maybe this shouldn't even be considered a problem at all. With C#'s auto initialiser syntax, you can't access the properties in the constructor anyway: public class Subclass
{
public string Title;
public int Width;
public Subclass()
{
// The following lines will not work as the properties
// haven't been assigned yet.
Console.WriteLine(Title);
Console.WriteLine(Width);
}
}
// Somewhere Else
var obj = new Subclass {
Title = "Hello",
Width = 1024,
}; We could simply follow C# convention and make the user read properties later (maybe on 'gtk_widget_realize', which would be a virtual function?). It would certainly simplify the API and isn't unreasonable to expect. EDIT: public Subclass() : this(my, default, values) {} |
This topic is quite interesting, but very hard to follow along. At least for me. So the following text could be wrong or make wrong assumptions. Please keep that in mind. I'm not sure if we need to consider this because, because the properties are registered in the type system and if
I believe if we have a constructor which just accepts an If the code is like i suggested above: Creating an object in C#: The object is created in the C world (through the inheritance chain This would look like: public class Subclass : Window
{
public IProperty<string> Title { get; }
public IProperty<int> Width { get; }
public Subclass() //: base() Implicit call of base constructor which calls the other constructor later
{
//This constructor is for public access
}
internal Subclass(IntPtr handle) : base(handle)
{
//This constructor is to initialize
Title = PropertyOfString("title"); //Connects the properties to the C world
With = PropertyOfInt("width");
Title.Value = "MyTitle set from C# in the constructor"
}
} There is one other path, for custom constructors: See. This is probably the path you have in mind in your analysis? I believe, if we register a custom constructor the code would look like you suggested. Are my assumptions correkt? If yes: Which way do you prefer and why? Just as a side note: In the code it reads a little bit like this path is not very performant, but i don't know the performance implications of this.
|
You're absolutely right there, as far as I'm aware. My proposal was indeed using custom constructors, as it gives us a much nicer API (i.e. the user doesn't have to define the public class Subclass : GObject
{
public Subclass()
{
// Init
Console.WriteLine("We can do everything here *except* access properties");
}
} The above is all we need for a custom GObject-based class, everything is handled behind the scenes. All creation paths would go through the default constructor, and in a similar manner to GLib properties, would be assigned after we have made the object. However I think you're right about the constructor pathway possibly being slower. I'm not too sure myself actually, as it seems most of the overhead involves copying construct properties. Since we'll only ever have one set (the inhibit property) perhaps the overhead will be minimal. I think it's definitely worth investigating this, because we don't want to sacrifice performance for a slightly nicer API.
I don't feel strongly about either way, although I'd prefer the constructor method since it has a much more organic feeling API. If there is a performance hit, we should definitely go with the |
Some exciting news: I had a go at implementing basic proof-of-concept type system integration and it appears to be working quite well actually. I added a type dictionary and a static method to register a new class with GLib (see here). It now creates a new GType behind the scenes for every derived class of GObject. I've also managed to override The library automatically creates a GType for the following class, as you'd expect: class Subclass : GObject.Object
{
public Subclass() {}
} We can retrieve the class name to verify: // Create a new instance of the object
var obj = new Subclass();
// The Library automatically generates a GType for Subclass
// and calls it `{Namespace}{ClassName}`, in this case
// "GObjectTestSubclass"
// To confirm it's working, let's retrieve the name back from GLib
IntPtr ptr = Sys.Methods.type_name_from_instance(obj.Handle);
string? name = Marshal.PtrToStringAnsi(ptr);
// Assert passes: we have indeed created a new type
Debug.Assert("GObjectTestSubclass" == name);
// Nothing very useful at the moment, but the groundwork is there :) I'm working on it over here: https://github.com/firox263/gir.core/tree/gobject-rewrite. |
Absolutely 👍
You are right. This approach does match with the gobject approach as both are based on initializing properties, after the object is created.
I like the thought of virtual functions, but we should be working this out in the GObject layer and not depend on any GTK functions, as all GObject based libraries will have properties which we probably want to access somehow during/after initialization. I think there are different approaches to initialize objects in C#, two of them are:
I like your aproach and the simple look of the subclass, but I think currently it limits the possibilities a little bit. If we could find a way to mitigate this it would be an absolutely amazing solution. Another possible solution would be just an explanation in the documentation. As we could simply state that GObjects are created in exactly this way: Properties are set via auto initializers, and not inside the constructor, because this is how the GObject system works. If someone wants to create objects in another style, it would simply not be a GObject. One more thought: There is the Constructed class method which is called during object creation. Perhaps we could override the constructed method and call back into a virtual function of the C# object. In this case it would be possible to access all "constructor properties" and we are alligned with the GObject system. I'm going to test a little bit with your code. |
Yep, you're right. I was mostly thinking of this in terms of Gtk widgets, but it'd make the most sense for us to use
I think this would be okay. Hopefully we can use
Have fun :) Just to let you know, only I've been experimenting with getting the rest of the bindings to work and I've encountered a few sticking points. Would greatly appreciate feedback on the following:
With `DemoWindow':
So while we are registering a GType for DemoWindow, our object is initialised by the IntPtr from GtkApplicationWindow. The result is that we simply get an app window, rather than our custom subclass. We need some way of not calling |
Regarding point 1:
Regarding point 2:
Regarding point 3
|
In a way we're doing that already. The main problem is in order to link with the GType for GtkApplicationWindow, we need someway of retrieving that type. If we look up "GtkApplicationWindow" by name, the type most likely hasn't been created yet, and therefore won't be registered in the GLib Type system, let alone ours. I think we don't have a choice other than to use the
That would be great thanks!
We're doing this for Subclass types already. The only problem I see with using We need some way to have both the get_type function, and optionally the constructor, associated with the class statically. If no constructor is present, we fall back to I've pushed my working branch to |
Sorry I used the term class constructor before, but actually meant static constructor
In which point in time or method do we need to look up "GtkApplicationWindow"? Because if the static constructor already run at this point in time, the type would be registred. As far as I can see it at the moment, we need to check the Perhaps it is enough to call
to register the types early enough. |
I think you're right. For wrappers, calling When we create a subclass, the static constructor will be called automatically and thus the type will be registered by the time it comes to creating the subclass object. The only flaw in this is that we won't be able to guarantee access to the gtype of an object from outside the object/it's inheritance chain. If we enforce this as a rule and make the GType API private then it should be fine (perhaps we should be doing this anyway? User code doesn't need access to glib internals). We probably don't need to call |
Quick idea in light of #56 I agree we should try and get this landed soon, since the entire library depends on this. I've been doing some thinking about the proposal and I feel that in trying to make the bindings bidirectional, we're sacrificing a lot of nice C# ergonomics. New Idea regarding We only need to use Later on, if we find a situation in which this is useful, we can get away with adding this without breaking API compatibility, as the setup is entirely contained within What it means in the short term is we can focus on making the library usable, and worry about this edge-case feature down the line. |
Integrating C# and GObject
RFC
Please see the RFC here: https://github.com/gircore/rfcs/blob/master/rfc-001-type-integration.md
It explains the proposal and some potential solutions.
Summary
Type Integration means that when the user creates a class that derives from GObject.Object, the library will automatically create a corresponding GType behind the scenes. Effectively, it allows us to write "native" GObject code using C#.
This enables us to implement the following features, among many others:
Like with the namespace refactor (#27, PR #45), this will be a huge change that affects almost the entire codebase. For this reason, it is probably best to focus on completing this before we write more bindings.
The text was updated successfully, but these errors were encountered: