C++ isn’t a new undertaking for GnuCash. Chris, Geert, and I have been working 
on it for over 10 years and Christian Stimming had a working Qt experimental 
GUI (since archived) 15 years ago. Libgnucash is already more than 2/3 compiled 
with C++ (191 .cpp vs 89 .c) and gnucash (where the GUI code lives) is more 
than 20% C++. While much of that is just changing the file type and cleaning up 
the compiler errors, other parts—GncNumeric, GncDateTime, GncOptions, Quotes, 
the DBI/SQL backend, and the CSV importer—have been completely rewritten into 
real C++, plus quite a few Glib containers converted to STL ones. 

Regards,
John Ralls

> On Feb 6, 2026, at 06:44, Stefan Koch <[email protected]> wrote:
> 
> 
> One more thing to keep in mind for the c to c++ conversion...  The c++ 
> compiler is stricter on some type checking.  You will have to make real code 
> changes to allow it to compile some c code.  Those changes are not 
> significant, but do have to be correct and verified.  At least that is what 
> happened when I did this for some test driver source files.  
> 
> Stefan
> 
> On Fri, Feb 6, 2026 at 9:28 AM <[email protected] 
> <mailto:[email protected]>> wrote:
>> 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] <mailto:[email protected]>>
>> > Sent: Thursday, February 5, 2026 9:04 PM
>> > To: [email protected] <mailto:[email protected]>
>> > Cc: [email protected] <mailto:[email protected]>
>> > Subject: Re: exception fencing
>> > 
>> > 
>> > 
>> > > On Feb 5, 2026, at 3:03 PM, <[email protected] 
>> > > <mailto:[email protected]>>
>> > <[email protected] <mailto:[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] <mailto:[email protected]>
>> https://lists.gnucash.org/mailman/listinfo/gnucash-devel

_______________________________________________
gnucash-devel mailing list
[email protected]
https://lists.gnucash.org/mailman/listinfo/gnucash-devel

Reply via email to