John, thank you for your detailed reply. I've responded inline below If you are still unconvinced please say so and I'll abandon this. Thanks again. - russ
> -----Original Message----- > From: John Ralls <[email protected]> > Sent: Thursday, February 5, 2026 9:04 PM > To: [email protected] > Cc: [email protected] > Subject: Re: exception fencing > > > > > On Feb 5, 2026, at 3:03 PM, <[email protected]> > <[email protected]> wrote: > > > > Since this is my first attempt to submit code to the GNC project I > > thought I’d pass my approach through the established developers first. > > I am interested in converting GNC code from C to C++ which will occur over > time so that the review quanta is not too overwhelming. > > I plan to convert build targets to only use the C++ compiler over time. > > Anything that is exposed with C linkage I plan to carry forward expecting at > some point all the callers will be C++ as well and most if not all of the C- > linkage requirements will go away. > > Any C++ code that is called from C-code will need a mechanism to prevent > C++ exceptions from bleeding back. > > I’ve developed a header file except-fence.hpp that provides the required > fencing. > > My first patch will be just this header file and the associated test files, > > so no > GNC code will be affected initially. > > I wanted to make the footprint small since I expect the fences will > > eventually > be removed. > > My approach is to replace the definition if the C-API functions with a > > macro that defines a stub C-API (extern “C”) Which instantiates an > ExceptFence object and uses the “forward_to” method call a local static > function with the original body. > > For Example: > > const char * > > func1(int a, gpointer b) // defined as extern “C” in header file { … } > > Becomes: > > SAFE_C_API_ARGS(const char *, func1, (int a, gpointer b), (a, b)) { … > > } Which (essentially) expands to: > > static const char *loc_func1(int a, gpointer b); extern “C” const > > char *func1(int a, gpointer b) { > > ExceptFence exf; > > return exf.forward_to(loc_func1, a, b); } static const char > > *loc_func1 (int a, gpointer b) { … } > > The essence of forward_to is the following: > > template<typename Func, typename... Args> > > auto forward_to(Func&& func, Args&&... args) > > { > > try { > > m_except_data.exception_hit = true; > > auto result = func(std::forward<Args>(args)...); > > m_except_data.exception_hit = false; > > return result; > > } > > catch (const std::exception& ex) { > > m_except_data.exception_type = &typeid(ex); > > m_except_data.exception_message = ex.what(); > > } > > using ReturnType = std::invoke_result_t<Func, Args...>; > > if constexpr (std::is_integral_v<ReturnType>) { > > return static_cast<ReturnType>(m_exception_error_integer); > > } else if constexpr (std::is_pointer_v<ReturnType>) { > > return static_cast<ReturnType>(nullptr); > > } else { > > return ReturnType{}; > > } > > } > > This definition of forward_to() supports functions that return integer > > types, > pointer types, and structure types. > > That appears to support most of what we need that I’ve seen. > > There are 4 different SAFE_C_API macros to support all the combinations of > void args and void return types. > > Anyway, if there are concerns about this approach, or request for more > details please let me know. > > Thanks for your attention and support. > > Getting rid of C entirely requires changing the GUI framework; the minimum > effort would be gtkmm but even that will be a lot of work. It isn’t going to > happen any time soon. [russ] I figured there might be some elements that are impractical to convert But I still think it makes sense to enable C++ coding in the main. There are just a lot A nice features of C++ that would be good to take advantage of. > Also to consider is that exceptions either can’t be > allowed to leak through the Python and Scheme bindings or those bindings > need to acquire exception converters. [russ] Yeah I thought that the binding interfaces would need to remain as C-Linkage but can still be C++ code underneath. > > Just switching to compiling C code with a C++ compiler doesn’t introduce > exceptions so the need to wrap calls in try/catch is limited to cases when the > function is reimplemented to use some C++ thing that throws. [russ] right but the code will be available for use of C++ constructs by developers but still callable from C compilation units > Note that > loc_func1 still has to check ext.m_except_data.exception_hit and if it’s true > do > something with the rest. I suspect that that something will turn out to be > sufficiently not generic that having the template won’t really save much over > just catching the exception in the implementation function or writing a C > wrapper that handles exceptions in a way that’s appropriate for the execution > context at hand. [russ] I'm not sure I agree completely. The fence is only for catching unhandled exceptions (coding bugs), and the fence is in func1(), but the implementation logic is in loc_func1(). Even carefully designed C++ can have unusual circumstance that result in an unexpected exception. If there is any cleanup that needs to be done (beyond returning some error value), that will need to be done with a try/catch in loc_func1(). For functions that generate a lot of side effects and have complicated unwind logic its likely they won't be able to do that with a giant catch-all at the top anyway and need layers of try/catch to properly deal with them. I agree that if we mandate a giant catch-all at the top of all the C-APIs when implementing that might work for catching everything but in my experience its tricky to get right and error prone. E.g. if we instantiate an object before the try, those constructors can throw unprotected. > > BTW, “m_exception_error_integer” is a problem because it implies that that’s > a single value used for all cases and it’s not hard at all to think of cases > where > different values would be needed for signaling an error. [russ] in the implementation of ExceptFence, the constructor can take any integer value and use that as the error value for integer return types. But you are right that the macros don't currently support that, but that would be easy to change. From what I've seen so far the return types for these APIs in the code base are either some kind of integer, some kind of pointer, or some kind of structure. The class currently supports specifying what the integer error value is for uncaught exceptions, but for the other return types it currently only supports nullptr/zeroed-out-structure, but that also could be fixed to support any error value of those types. So the exception fence really just ensures that uncaught exceptions return a known error type and that the implementation function does not need a v. carefully designed giant catch-all at the top. Void functions are tougher. Caught exceptions currently just return and leave an unknown state behind without any side effects. If there was some notification possible that would be nice. Once nice thing of having the fencing in a single class is if you suspect an unhandled exception e.g. in a void C-API func, it’s a single breakpoint in the code. > > Regards, > John Ralls _______________________________________________ gnucash-devel mailing list [email protected] https://lists.gnucash.org/mailman/listinfo/gnucash-devel
