API access to FLTK app

71 views
Skip to first unread message

Rob McDonald

unread,
Jan 15, 2022, 1:53:38 AM1/15/22
to fltk.general
Apologies for a long setup to what may be an elementary question.

My FLTK interactive program also has a C++ API that we use SWIG to generate wrappers to call from Python and other scripting languages.

When launched normally, a small main() handles some command line options, displays the initial windows, and starts the FLTK event loop.

When called from the API (say Python), an instance of the main data structures is loaded into memory, and calls are made from Python as needed -- no event loop.  In fact, we usually use the API from a binary that does not have any GUI, OpenGL, or FLTK code compiled or linked in.

All the complex data 'lives' in the C++ side of things.  We only pass relatively simple data back and forth through the API.  All significant program state remains on the C++ side.

We would like to be able to launch an interactive GUI from Python that allows the user to interact with the program at the same time Python is interacting.  My program is single threaded and was not designed with thread safety in mind.

To do this, I think we need to...

Add a StartGUI() routine to the API that will start a new thread, open the default GUI windows, and start the event loop (basically do what main() does for the interactive program).  The original thread will return control to Python.

Add a lock that prevents truly simultaneous access.  Whenever an event comes in, FLTK must acquire the lock before calling into my code to handle the event.  Once the event is fully handled (and control returns to the event loop), the lock is released.

At the same time, every API call must acquire the lock before doing anything.  The lock will be released when the API call is finished.

Acquiring the lock will be blocking -- whether from the GUI or the API, if the program is busy, you'll see some lag or hesitation.  However, much of the time, you will be able to interact normally with the program.

Is this the right idea?  Do I have a fundamental misunderstanding here?  Can anyone point at an example application that takes this approach?

Thanks in advance for any help,

Rob



Ian MacArthur

unread,
Jan 15, 2022, 6:32:30 AM1/15/22
to Fltk General

Hi Rob,

On 15 Jan 2022, at 06:53, Rob McDonald wrote:
>
> To do this, I think we need to...
>
> Add a StartGUI() routine to the API that will start a new thread, open the default GUI windows, and start the event loop (basically do what main() does for the interactive program). The original thread will return control to Python.
>
> Add a lock that prevents truly simultaneous access. Whenever an event comes in, FLTK must acquire the lock before calling into my code to handle the event. Once the event is fully handled (and control returns to the event loop), the lock is released.
>
> At the same time, every API call must acquire the lock before doing anything. The lock will be released when the API call is finished.
>
> Acquiring the lock will be blocking -- whether from the GUI or the API, if the program is busy, you'll see some lag or hesitation. However, much of the time, you will be able to interact normally with the program.
>
> Is this the right idea? Do I have a fundamental misunderstanding here? Can anyone point at an example application that takes this approach?
>
> Thanks in advance for any help,


I think the idea is OK, but I’m uncomfortable about the proposed implementation.
The crux, for me, is that this ends up with the fltk GUI context running in a “non-main” thread - and that, basically, is not going to be robust, I fear.

A lot (some | many | most) systems are such that the GUI context “belongs" to the main thread of a process, and GPU drivers are built for speed, not safety, and the result is that modifying the GUI context from a non-main thread can be “fragile”, though the fragility can vary from GPU to GPU. (The usual manifestation of this is bugs or crashes “elsewhere” in the program due to pointer corruptions etc. which can be insanely difficult to debug.)

Further, at least on Windows hosts, event delivery (at the OS level, not the fltk level) can be “tricky" to non-main threads.

If the Python thread *also* has a GUI, then things can get really nasty pretty quickly.

So: It might work (or at least appear to work, on some hosts, anyway) but I suspect it would just lead to pain.

Where I’ve done this in the past, I usually just spawn a whole new process and have the two processes communicate, e.g. via pipes or etc.
If this has to run on Win32 hosts as well, then I use (localhost) network sockets between the processes rather than pipes - this simply because the fltk Fl::add_fd() can pend on Windows network sockets, but not on Windows pipes (on Posix hosts add_fd() will happily listen to any suitable fd, it is only Win32 that is “weak” in this regard.)

FWIW, localhost network sockets on Win32 are cheap and high bandwidth, so it’s a pretty easy way to pass the IPC between the processes anyway - and has the advantage that, by simply setting the address to something other than 127.0.0.1 you can then split the app over two separate systems “for free” as it were.


Bill Spitzak

unread,
Jan 15, 2022, 11:41:45 AM1/15/22
to fltkg...@googlegroups.com
FLTK already has a lock exactly like this. Calling code (for instance your python wrappers for any fltk functions) should lock before calling anything.

There is a hack so if nobody calls the lock() it does not interfere with the operation of a single-threaded program, which is probably why you don't know it exists.

Calling Fl::run() or other functions that process events is pretty much the "start the gui" call. Whatever thread does this will become the main thread as far as fltk is concerned.

As you know, there are unfortunately a bunch of fltk calls that assume they are running in the main thread, especially on Windows. Grabbing the lock does not help. It would be nice to fix this but it is unusual that you have to work around it, in particular because any callbacks will execute in the main thread.

If you want a callback to run python code you must grab the Python GIL.

--
You received this message because you are subscribed to the Google Groups "fltk.general" group.
To unsubscribe from this group and stop receiving emails from it, send an email to fltkgeneral...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/fltkgeneral/EC0ADDA5-E44B-473D-ADB2-9061BC347F34%40gmail.com.

Rob McDonald

unread,
Jan 15, 2022, 1:42:30 PM1/15/22
to fltk.general
On Saturday, January 15, 2022 at 8:41:45 AM UTC-8 spitzak wrote:
FLTK already has a lock exactly like this. Calling code (for instance your python wrappers for any fltk functions) should lock before calling anything.

There is a hack so if nobody calls the lock() it does not interfere with the operation of a single-threaded program, which is probably why you don't know it exists.

Calling Fl::run() or other functions that process events is pretty much the "start the gui" call. Whatever thread does this will become the main thread as far as fltk is concerned.

As you know, there are unfortunately a bunch of fltk calls that assume they are running in the main thread, especially on Windows. Grabbing the lock does not help. It would be nice to fix this but it is unusual that you have to work around it, in particular because any callbacks will execute in the main thread.

If you want a callback to run python code you must grab the Python GIL.

On Sat, Jan 15, 2022 at 3:32 AM Ian MacArthur wrote:

Hi Rob,

On 15 Jan 2022, at 06:53, Rob McDonald wrote:
>
> To do this, I think we need to...
>
> Add a StartGUI() routine to the API that will start a new thread, open the default GUI windows, and start the event loop (basically do what main() does for the interactive program).  The original thread will return control to Python.
>
> Add a lock that prevents truly simultaneous access.  Whenever an event comes in, FLTK must acquire the lock before calling into my code to handle the event.  Once the event is fully handled (and control returns to the event loop), the lock is released.
>
> At the same time, every API call must acquire the lock before doing anything.  The lock will be released when the API call is finished.
>
> Acquiring the lock will be blocking -- whether from the GUI or the API, if the program is busy, you'll see some lag or hesitation.  However, much of the time, you will be able to interact normally with the program.
>
> Is this the right idea?  Do I have a fundamental misunderstanding here?  Can anyone point at an example application that takes this approach?
>
> Thanks in advance for any help,


I think the idea is OK, but I’m uncomfortable about the proposed implementation.
The crux, for me, is that this ends up with the fltk GUI context running in a “non-main” thread - and that, basically, is not going to be robust, I fear.

A lot (some | many | most) systems are such that the GUI context “belongs" to the main thread of a process, and GPU drivers are built for speed, not safety, and the result is that modifying the GUI context from a non-main thread can be “fragile”, though the fragility can vary from GPU to GPU. (The usual manifestation of this is bugs or crashes “elsewhere” in the program due to pointer corruptions etc. which can be insanely difficult to debug.)

Further, at least on Windows hosts, event delivery (at the OS level, not the fltk level) can be “tricky" to non-main threads.

If the Python thread *also* has a GUI, then things can get really nasty pretty quickly.

So: It might work (or at least appear to work, on some hosts, anyway) but I suspect it would just lead to pain.

Where I’ve done this in the past, I usually just spawn a whole new process and have the two processes communicate, e.g. via pipes or etc.
If this has to run on Win32 hosts as well, then I use (localhost) network sockets between the processes rather than pipes - this simply because the fltk Fl::add_fd() can pend on Windows network sockets, but not on Windows pipes (on Posix hosts add_fd() will happily listen to any suitable fd, it is only Win32 that is “weak” in this regard.)

FWIW, localhost network sockets on Win32 are cheap and high bandwidth, so it’s a pretty easy way to pass the IPC between the processes anyway - and has the advantage that, by simply setting the address to something other than 127.0.0.1 you can then split the app over two separate systems “for free” as it were.


Does it change anything that none of the code called from Python will be FLTK code?

I have no intention (for now at least) of having the C++ side (via a callback or otherwise) execute any Python code.

This does need to work on Windows, Linux, and MacOS.

Rob

 

Paul Hahn

unread,
Jan 24, 2022, 3:23:50 AM1/24/22
to fltk.general
Rob:

I am not a python user but I would like to offer some points you might still find of some value.

FWIW, I have been using the FLTK lock mechanism now for a while and I find it satisfactory and reliable. I think as the core FLTK developers have noted in the past, though, without care, one can easily wind up serializing everything anyway, where effectively only a single thread is actually running at any point in time -- in which case, that could negate the point of the MT-exercise (if that matters to you). Another concern is making sure to put a lock in all the places that need it.

However, I have also been using a different scheme to get around the "GUI calls only in the main thread" issue that is a sort of "graphics server" approach. This relies on an intercommunicating pair of processes (parent-child in my application), where one process is the "GUI server", which implements the interactive GUI and makes all the FLTK calls, plus occasionally checks for incoming "commands" from the other process (and might enqueue them), or otherwise publishes data from the GUI to it. Not ideal for "real time", but it isn't as bad as some might think in terms of performance, at least on Linux where I do it. And for me, it's not that much worse than a locks-based approach, assuming efficient IPC between the processes. And proper GUI isolation for FLTK -- without so many worries as in the locks-based approach (like, maybe trying to do something if-y while being deep in callbacks) -- is the win for me.

In your situation, perhaps one process would be the FLTK GUI server while the other process would be the python thingie.

But note all my experience using these approaches with FLTK is only on Linux w/ C++.

Gonzalo Garramuño

unread,
Apr 2, 2023, 5:12:43 PM4/2/23
to Rob McDonald, fltk.general

El 15/1/22 a las 03:53, Rob McDonald escribió:
> Apologies for a long setup to what may be an elementary question.
>
> My FLTK interactive program also has a C++ API that we use SWIG to
> generate wrappers to call from Python and other scripting languages.
>
There are two things that you should take into account.  FLTK requires
all drawing and processing to happen in the main thread. Python allows
separate threads but it locks them each with the GIL (Global Interpreter
Lock), so in principle, it is as limited as FLTK.

You might want to take a look at the code of my mrv2 video player, where
there's an embedded python that can do:

a) Run python code with a -pythonScript parameter and exit without
opening any FLTK windows.

b) Open FLTK windows and then, from the user interaction with
Panel/Python run python code.  There's a Python pybind11 function (NOT
swig which sucks), which calls Fl::check().  So, in your Python code,
you can have:

        for x in range(0,100000):
              cmd.update()  # Refresh the gui

The code of mrv2 is at:

    www.github.com/ggarra13/mrv2

And you can download binaries for all platforms from the github releases
or from sourceforge.net at:

  www.sourceforge.net/projects/mrv2/files


Message has been deleted

Matthias Melcher

unread,
Apr 2, 2023, 7:21:32 PM4/2/23
to fltk.general
Sorry, this time from the laptop without the typos from the phone:

https://github.com/MatthiasWM/fltk/tree/python2
CMake …. -DFLTK_PYTHON_BINDINGS=on
CMake —build —target=python_test

I am somewhat new to Python. The Swig stuff is a great concept, but pyFLTK has a few flaws (GC will delete widgets if they are not assigned to a global variable, for example). So I put a little bit of time in to try to write better Python bindings using the Python C API directly.. 

The link above implements the Fl_Widget class and shows that derived widgets can be easily added (Fl_Button in this example, Fl_Group to follow). My code has almost no overlap with the git GIT repo. Most stuff is in `src/bindings` plus a few lines for CMake, and a `python_test` app.

If the core devs think that this is a good approach and that it could be part of the core release, I would be willing to put the work in to bind all of FLTK to Python and have FLUiD generate Python code as an options. Since the binding code does not overlap with the core implementation, core development and binding have no conflicts. It would also be possible to bind more languages this way, also without overlaps. This would create additional work for  maintenance, but it also bundles the core devs efforts with those who author the bindings, making it easier to keep core library and bindings nicely in sync.

Comments always welcome.

Gonzalo Garramuño

unread,
Apr 3, 2023, 4:22:15 PM4/3/23
to 'Matthias Melcher' via fltk.general

El 4/2/2023 a las 8:08 PM, 'Matthias Melcher' via fltk.general escribió:
> https://github.com/MatthiasWM/fltk/tree/python2
> CMake …. -DFLTK_PYTHON_BINDINGS=on
> CMake —build —target=python_test
>
> I am somewhat new to Python. The Swig stuff is. A great idea, but
> pyFLTK has a few flaws. So I put a Little bit of time in to try to
> write a better Python binding using the Python C API directly..

For Python, there's a better C++ library than Swig called pybind11.  It
is as simple (and as powerful) as this:


    py::class_<tl::file::Path>(m, "Path")
        .def(
            py::init<const std::string&, const tl::file::PathOptions&>(),
            py::arg("path"), py::arg("options") = tl::file::PathOptions())
        .def(
            py::init<
                const std::string&, const std::string&,
                const tl::file::PathOptions&>(),
            py::arg("path"), py::arg("value"),
            py::arg("options") = tl::file::PathOptions())
        .def(
            py::init<
                const std::string&, const std::string&, const std::string&,
                uint8_t, const std::string&>(),
            py::arg("directory"), py::arg("baseName"), py::arg("number"),
            py::arg("padding"), py::arg("exetension"))
        .def(
            "get", &tl::file::Path::get, "Returns the path",
            py::arg("idx") = -1, py::arg("directoru") = true)
        .def("getDirectory", &tl::file::Path::getDirectory)
        .def("getBaseName", &tl::file::Path::getBaseName)
        .def("getNumber", &tl::file::Path::getNumber)
        .def("getPadding", &tl::file::Path::getPadding)
        .def("getExtension", &tl::file::Path::getExtension)
        .def("isEmpty", &tl::file::Path::isEmpty)
        .def("isAbsolute", &tl::file::Path::isAbsolute)
        .def(
            "__repr__", [](const tl::file::Path& a)
            { return "<mrv2.Path '" + a.get() + "'>"; })
        .doc() = "Class used to hold a file path";

It works on templates, so it deduces most parameters for you.

--
Gonzalo Garramuño
ggar...@gmail.com

Matthias Melcher

unread,
Apr 3, 2023, 4:58:09 PM4/3/23
to fltk.general
There are a few things I could not get pybind11 to do. For example, the constructor does not know the Python object yet, so our auto-add to Fl-Group can not increment the reference counter. But maybe there is a way to fix that. Also, the same method in FLTK has often multiple version with very different parameters. Swig did not handle that too well IIRC. Not sure anymore what py11 does,

My solution is relying heavily on macros and some C++11, for example:

  FlpyMETHOD_VARARGS_BEGIN(Widget, align)

    FlpyARG_I_TO_v_TYPE(Widget, align, Fl_Align)

    FlpyARG_v_TO_i(Widget, align)

  FlpyMETHOD_VARARGS_END(Widget, align, "takes no arguments or a single alignment value"),

implements Fl_Align Fl_Widget::align() and void Fl_Widget::align(Fl_Align), but as I said, so far I am only playing around.

Robert Arkiletian

unread,
Apr 8, 2023, 2:52:38 AM4/8/23
to fltk.general
On Sunday, April 2, 2023 at 4:21:32 PM UTC-7 Matthias Melcher wrote:
Sorry, this time from the laptop without the typos from the phone:

https://github.com/MatthiasWM/fltk/tree/python2
CMake …. -DFLTK_PYTHON_BINDINGS=on
CMake —build —target=python_test

I am somewhat new to Python. The Swig stuff is a great concept, but pyFLTK has a few flaws (GC will delete widgets if they are not assigned to a global variable, for example). So I put a little bit of time in to try to write better Python bindings using the Python C API directly.. 


Hi Matthias, I help with the pyFltk project. I'm very interested in your effort to create a Python binding for FLTK. I cloned your repo and checked out the python2 branch but I must be missing something as the build command did not work (using Debian 12).  Also, I think the GC issue is a feature of Python itself (or maybe I am not understanding your comment).
 
The link above implements the Fl_Widget class and shows that derived widgets can be easily added (Fl_Button in this example, Fl_Group to follow). My code has almost no overlap with the git GIT repo. Most stuff is in `src/bindings` plus a few lines for CMake, and a `python_test` app.


Manually writing ALL widgets seems like a lot of work compared to using a wrapper tool like Swig. BTW one of the nice features of the pyFltk project is that it's able to duplicate the function overload abilities of  C++ FLTK (like get/set methods where leaving out function args is a get and providing them is a set) using Python.

Matthias Melcher

unread,
Apr 8, 2023, 2:20:04 PM4/8/23
to fltk.general
rob...@gmail.com schrieb am Samstag, 8. April 2023 um 08:52:38 UTC+2:
On Sunday, April 2, 2023 at 4:21:32 PM UTC-7 Matthias Melcher wrote:
Hi Matthias, I help with the pyFltk project. I'm very interested in your effort to create a Python binding for FLTK. I cloned your repo and checked out the python2 branch but I must be missing something as the build command did not work (using Debian 12). 

Only the CMake build works. I did a few changes, so it may not compile at all times. It's important to set the compiler to C++11. I use `cmake -B build . -DFLTK_PYTHON_BINDINGS=on -DCMAKE_CXX_STANDARD=11` and `cmake --build build`
 
Also, I think the GC issue is a feature of Python itself (or maybe I am not understanding your comment).

I saw that Python sample code always declares global variables for Widgets, even though the variables are never used. It seems that not assigning variable will cause the GC to delete a widget, even though it is still used by the parent widget. This is a bug in the pyFLTK bindings IMHO, but it is also very possible that I am diagnosing this wrong.
 
The link above implements the Fl_Widget class and shows that derived widgets can be easily added (Fl_Button in this example, Fl_Group to follow). My code has almost no overlap with the git GIT repo. Most stuff is in `src/bindings` plus a few lines for CMake, and a `python_test` app.
 
Manually writing ALL widgets seems like a lot of work compared to using a wrapper tool like Swig.
 
That's what I wanted to find out. Yes, it is some work once, but it's also an opportunity to adapt methods to Python conventions. 

I will look into pyFLTK again, now that I know a lot more about the Python C API :-)

BTW one of the nice features of the pyFltk project is that it's able to duplicate the function overload abilities of  C++ FLTK (like get/set methods where leaving out function args is a get and providing them is a set) using Python.

Yes, I saw that and that's great. 

Gonzalo Garramuño

unread,
Apr 8, 2023, 3:48:10 PM4/8/23
to fltkg...@googlegroups.com


El 8/4/23 a las 15:20, 'Matthias Melcher' via fltk.general escribió:


rob...@gmail.com schrieb am Samstag, 8. April 2023 um 08:52:38 UTC+2:
On Sunday, April 2, 2023 at 4:21:32 PM UTC-7 Matthias Melcher wrote:
Hi Matthias, I help with the pyFltk project. I'm very interested in your effort to create a Python binding for FLTK. I cloned your repo and checked out the python2 branch but I must be missing something as the build command did not work (using Debian 12). 

Only the CMake build works. I did a few changes, so it may not compile at all times. It's important to set the compiler to C++11. I use `cmake -B build . -DFLTK_PYTHON_BINDINGS=on -DCMAKE_CXX_STANDARD=11` and `cmake --build build`
 
Also, I think the GC issue is a feature of Python itself (or maybe I am not understanding your comment).

I saw that Python sample code always declares global variables for Widgets, even though the variables are never used. It seems that not assigning variable will cause the GC to delete a widget, even though it is still used by the parent widget. This is a bug in the pyFLTK bindings IMHO, but it is also very possible that I am diagnosing this wrong.

No you are diagnosing it right.

With pybind11 you can choose what classes are reference counted and which ones are not.  And you can call release() to clear the object, without effecting the reference count.

https://pybind11.readthedocs.io/en/stable/advanced/cast/custom.html#manual-reference-counting


BTW one of the nice features of the pyFltk project is that it's able to duplicate the function overload abilities of  C++ FLTK (like get/set methods where leaving out function args is a get and providing them is a set) using Python.

Yes, I saw that and that's great. 

pybind supports thist with py::oveload_cast(<type>) and it also supports py::arg("X") to name the arguments passed to functions, so can call:

           function( X=10 )

You can also use default arguments for python.

Robert Arkiletian

unread,
Apr 9, 2023, 2:28:20 AM4/9/23
to fltk.general


On Saturday, April 8, 2023 at 11:20:04 AM UTC-7 Matthias Melcher wrote:
... 
I saw that Python sample code always declares global variables for Widgets, even though the variables are never used. It seems that not assigning variable will cause the GC to delete a widget, even though it is still used by the parent widget. This is a bug in the pyFLTK bindings IMHO, but it is also very possible that I am diagnosing this wrong.

 
Matthias can you please provide a link or dir/filename of the python sample code you are referring to regarding the global variable issue.  


Matthias Melcher

unread,
Apr 9, 2023, 10:58:59 AM4/9/23
to fltk.general
rob...@gmail.com schrieb am Sonntag, 9. April 2023 um 08:28:20 UTC+2:
Matthias can you please provide a link or dir/filename of the python sample code you are referring to regarding the global variable issue. 

For example, in `arc.py` in the source of the latest 1.3 binding:

window = Fl_Window(300,500)
s = [None,None,None,None,None,None]
while n < 6:
s[n] = Fl_Hor_Value_Slider(50,y,240,25,name[n])

s only exists to keep a global reference to the slider around. This should not be necessary because `window` should hold a reference to all its children, and only if `window` is unreferenced, all the sliders should be unreferenced and garbage-collected. 

This is not a huge deal if the user knows to keep the global reference around, but FLTK is a bit uncommon in that it automatically adds widgets to the last created group, and it also automatically *deletes* widgets when the parent group is deleted. I added virtual calls to Fl_Group in 1.4 to monitor and avoid those deletions, which otherwise would leave pyFLTK with an Object containing an already deleted widget.


Robert Arkiletian

unread,
Apr 9, 2023, 6:45:22 PM4/9/23
to fltk.general
Ah! Ok, I think that's just the Python way of coding. You don't have to create a new list container variable to hold widgets. Here is some code to show the idea.

from fltk import *

def but_cb(wid):
   wid.color(FL_GREEN)

win = Fl_Window(400,400)
win.begin()
n=0
#-------- pythonic way of adding widgets
'''
L=[] 
for row in range(10):
   for col in range(10):
       L.append( Fl_Button(col*40, row*40, 40, 40, str(n)) )
       L[n].callback(but_cb)
       n += 1
'''
#------- FLTK way of adding widgets
for row in range(10):
   for col in range(10):
       win.add( Fl_Button(col*40, row*40, 40, 40, str(n)) )
       win.child(n).callback(but_cb)
       n += 1
#-------

win.end()
win.show()
Fl.run()


So both ways will work, but Python programmers are more accustomed to creating a list variable to hold the widgets. I never code using .add. Also, if you wish to add single widgets then 

but = Fl_Button(......) #python

is the same idea as 

Fl_Button but(.......) //C++

Matthias Melcher

unread,
Apr 9, 2023, 7:52:11 PM4/9/23
to fltk.general
Thanks for the explanations and patience. I'll have some more fun with Python soon.

Bill Spitzak

unread,
Apr 9, 2023, 8:08:20 PM4/9/23
to fltkg...@googlegroups.com
I think you could make things mostly work as expected by having destruction of the Python object only destroy the FLTK widget if the widget does not have a parent.Any widget with a parent is assumed to be destroyed when the parent is destroyed. That would allow you to create/destroy widgets in Python normally, but if you add them to a larger FLTK object and forget about them, they don't get destroyed.


On Sun, Apr 9, 2023 at 4:52 PM 'Matthias Melcher' via fltk.general <fltkg...@googlegroups.com> wrote:
Thanks for the explanations and patience. I'll have some more fun with Python soon.

--
You received this message because you are subscribed to the Google Groups "fltk.general" group.
To unsubscribe from this group and stop receiving emails from it, send an email to fltkgeneral...@googlegroups.com.

Evan Laforge

unread,
Apr 9, 2023, 8:42:28 PM4/9/23
to fltkg...@googlegroups.com
On Sun, Apr 9, 2023 at 5:08 PM Bill Spitzak wrote:
I think you could make things mostly work as expected by having destruction of the Python object only destroy the FLTK widget if the widget does not have a parent.Any widget with a parent is assumed to be destroyed when the parent is destroyed. That would allow you to create/destroy widgets in Python normally, but if you add them to a larger FLTK object and forget about them, they don't get destroyed.

Yeah, this seems like a "straightforward" cross language ownership issue.  Straightforward does not mean it's easy to solve, but rather that any cooperation between two languages will need to solve it.  Python uses reference counting, and so the usual way this is done is that C++ keeps a reference count, and python will cooperate by incrementing and decrementing it.  Only when it reaches 0 do you know both python and c++ have given up ownership, and it can be deleted.  I haven't done this with python, but I do it with haskell and c++, there's a notion of a ForeignPtr which can have a finalizer attached, which will run when the haskell GC is done with the object.  The finalizer has a stored shared_ptr which it will drop, and if that was the last one, then bye bye.

However, fltk doesn't use reference counts internally, so I don't know if it can cooperate this way.  The way I did it for haskell bindings was that the only fltk widget shared was the windows, and those are registered in a global ViewId -> Fl_Window* map, which is then kept up to date separately in the haskell layer, relying on notifications from fltk if a window was closed or created.  It's sketchy because of course if this registry gets out of sync then you have dangling pointers, but it's been reliable over the years.  Then, I never have a direct pointer to any other Fl_Widget, but must address them relative to their owning Fl_Window.  Perhaps python can do the same kind of thing... but Bill's approach sounds like a lot less work.  Viewed through this lens, we can consider that Bill's approach is considering "has a parent" as a form of reference count that can only be 1 or 0.  Python would keep its own count, and run the finalizer (delete if no parent) once that reaches 0.

Bill Spitzak

unread,
Apr 9, 2023, 9:46:51 PM4/9/23
to fltkg...@googlegroups.com
What I would expect (and I have used this in other cases) is that the "python object" just consists of the python reference count and a pointer to the FLTK object. When the python object is destroyed (because it's reference count goes to zero) it checks the FLTK object, and if it does not have a parent it destroys it as well. It always destroys the python object and completely obeys Python reference counting rules.


--
You received this message because you are subscribed to the Google Groups "fltk.general" group.
To unsubscribe from this group and stop receiving emails from it, send an email to fltkgeneral...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages