1 /* wrap.cc
2  *
3  * Copyright (C) 1998-2002 The gtkmm Development Team
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <glibmmconfig.h>
20 #include <glibmm/object.h>
21 #include <glibmm/quark.h>
22 #include <glibmm/wrap.h>
23 #include <vector>
24 #include <glib.h>
25 #include <glib-object.h>
26 
27 namespace
28 {
29 
30 // Although the new g_type_set_qdata() interface is used now, we still need
31 // a table because we cannot assume that a function pointer fits into void*
32 // on any platform.  Nevertheless, indexing a vector costs almost nothing
33 // compared to a map lookup.
34 
35 using WrapFuncTable = std::vector<Glib::WrapNewFunction>;
36 
37 static WrapFuncTable* wrap_func_table = nullptr;
38 
39 } // anonymous namespace
40 
41 namespace Glib
42 {
43 
44 void
wrap_register_init()45 wrap_register_init()
46 {
47   // g_type_init() is deprecated as of 2.36.
48   // g_type_init();
49 
50   if (!Glib::quark_)
51   {
52     Glib::quark_ = g_quark_from_static_string("glibmm__Glib::quark_");
53     Glib::quark_cpp_wrapper_deleted_ =
54       g_quark_from_static_string("glibmm__Glib::quark_cpp_wrapper_deleted_");
55   }
56 
57   if (!wrap_func_table)
58   {
59     // Make the first element a dummy so we can detect unregistered types.
60     // g_type_get_qdata() returns NULL if no data has been set up.
61     wrap_func_table = new WrapFuncTable(1);
62   }
63 }
64 
65 void
wrap_register_cleanup()66 wrap_register_cleanup()
67 {
68   if (wrap_func_table)
69   {
70     delete wrap_func_table;
71     wrap_func_table = nullptr;
72   }
73 }
74 
75 // Register the unique wrap_new() function of a new C++ wrapper type.
76 // The GType argument specifies the parent C type to wrap from.
77 //
78 void
wrap_register(GType type,WrapNewFunction func)79 wrap_register(GType type, WrapNewFunction func)
80 {
81   // 0 is not a valid GType.
82   // It would lead to a critical warning in g_type_set_qdata().
83   // We allow this, failing silently, to make life easier for gstreamermm.
84   if (type == 0)
85     return;
86 
87   const guint idx = wrap_func_table->size();
88   wrap_func_table->emplace_back(func);
89 
90   // Store the table index in the type's static data.
91   g_type_set_qdata(type, Glib::quark_, GUINT_TO_POINTER(idx));
92 }
93 
94 static Glib::ObjectBase*
wrap_create_new_wrapper(GObject * object)95 wrap_create_new_wrapper(GObject* object)
96 {
97   g_return_val_if_fail(wrap_func_table != nullptr, nullptr);
98 
99   const bool gtkmm_wrapper_already_deleted =
100     (bool)g_object_get_qdata((GObject*)object, Glib::quark_cpp_wrapper_deleted_);
101   if (gtkmm_wrapper_already_deleted)
102   {
103     g_warning("Glib::wrap_create_new_wrapper: Attempted to create a 2nd C++ wrapper for a C "
104               "instance whose C++ wrapper has been deleted.");
105     return nullptr;
106   }
107 
108   // Traverse upwards through the inheritance hierarchy
109   // to find the most-specialized wrap_new() for this GType.
110   //
111   for (GType type = G_OBJECT_TYPE(object); type != 0; type = g_type_parent(type))
112   {
113     // Look up the wrap table index stored in the type's static data.
114     // If a wrap_new() has been registered for the type then call it.
115     //
116     if (const gpointer idx = g_type_get_qdata(type, Glib::quark_))
117     {
118       const Glib::WrapNewFunction func = (*wrap_func_table)[GPOINTER_TO_UINT(idx)];
119       return (*func)(object);
120     }
121   }
122 
123   return nullptr;
124 }
125 
126 static gboolean
gtype_wraps_interface(GType implementer_type,GType interface_type)127 gtype_wraps_interface(GType implementer_type, GType interface_type)
128 {
129   guint n_ifaces = 0;
130   GType* ifaces = g_type_interfaces(implementer_type, &n_ifaces);
131 
132   gboolean found = FALSE;
133   while (n_ifaces-- && !found)
134   {
135     found = (ifaces[n_ifaces] == interface_type);
136   }
137 
138   g_free(ifaces);
139 
140   return found;
141 }
142 
143 Glib::ObjectBase*
wrap_create_new_wrapper_for_interface(GObject * object,GType interface_gtype)144 wrap_create_new_wrapper_for_interface(GObject* object, GType interface_gtype)
145 {
146   g_return_val_if_fail(wrap_func_table != nullptr, nullptr);
147 
148   const bool gtkmm_wrapper_already_deleted =
149     (bool)g_object_get_qdata((GObject*)object, Glib::quark_cpp_wrapper_deleted_);
150   if (gtkmm_wrapper_already_deleted)
151   {
152     g_warning("Glib::wrap_create_new_wrapper: Attempted to create a 2nd C++ wrapper for a C "
153               "instance whose C++ wrapper has been deleted.");
154     return nullptr;
155   }
156 
157   // Traverse upwards through the inheritance hierarchy
158   // to find the most-specialized wrap_new() for this GType.
159   //
160   for (GType type = G_OBJECT_TYPE(object); type != 0; type = g_type_parent(type))
161   {
162     // Look up the wrap table index stored in the type's static data.
163     // If a wrap_new() has been registered for the type then call it.
164     // But only if the type implements the interface,
165     // so that the C++ instance is likely to inherit from the appropriate class too.
166     //
167     const gpointer idx = g_type_get_qdata(type, Glib::quark_);
168     if (idx && gtype_wraps_interface(type, interface_gtype))
169     {
170       const Glib::WrapNewFunction func = (*wrap_func_table)[GPOINTER_TO_UINT(idx)];
171       return (*func)(object);
172     }
173   }
174 
175   return nullptr;
176 }
177 
178 // This is a factory function that converts any type to
179 // its C++ wrapper instance by looking up a wrap_new() function in a map.
180 //
181 ObjectBase*
wrap_auto(GObject * object,bool take_copy)182 wrap_auto(GObject* object, bool take_copy)
183 {
184   if (!object)
185     return nullptr;
186 
187   // Look up current C++ wrapper instance:
188   ObjectBase* pCppObject = ObjectBase::_get_current_wrapper(object);
189 
190   if (!pCppObject)
191   {
192     // There's not already a wrapper: generate a new C++ instance.
193     pCppObject = wrap_create_new_wrapper(object);
194 
195     if (!pCppObject)
196     {
197       g_warning("Failed to wrap object of type '%s'. Hint: this error is commonly caused by "
198                 "failing to call a library init() function.",
199         G_OBJECT_TYPE_NAME(object));
200       return nullptr;
201     }
202   }
203 
204   // take_copy=true is used where the GTK+ function doesn't do
205   // an extra ref for us, and always for plain struct members.
206   if (take_copy)
207     pCppObject->reference();
208 
209   return pCppObject;
210 }
211 
212 Glib::RefPtr<Object>
wrap(GObject * object,bool take_copy)213 wrap(GObject* object, bool take_copy /* = false */)
214 {
215   return Glib::RefPtr<Object>(dynamic_cast<Object*>(wrap_auto(object, take_copy)));
216 }
217 
218 } /* namespace Glib */
219