Welcome to a series of tips and tricks for taking advantage of the new Windows 64-bit Modern toolchain in C++Builder / RAD Studio 12.2! This builds on a webinar we did sharing a bunch of useful information on why and how the new C++Builder matters to you, where this was the very end (so go watch the start!)
Tip #5: Split out C++ EH and SEH exception handling
After three separate variants of tip #4, something completely different!
Exception handling. There are multiple types and mixing them is A Bad Thing. Don’t mix C++ exception handling and SEH exception handling. It’s (a) not safe and (b) won’t work. But even better, we have a tool to help you separate them.
What does this mean?
C++ exceptions are when you throw a C++ object (which can be any type, even an int.) C++ exception handling is try-catch.
1 2 3 4 5 6 |
try { // dodgy code here throw new Ex(); } catch (Ex& e) { // handle exception here } |
Structured Exception Handling (SEH) is not a purely C++ concept, but has extra keywords added to C++ in order to support it. This is the kind of exception raised via the RaiseException() API (ie not throw) or for some hardware or kernel exceptions. You most likely don’t have a SEH handling mechanism (__try-__filter()) but you very likely may be using __try/__finally.
1 2 3 4 5 |
__try { // dangerous code here } __finally { // cleanup here } |
Both these are completely fine to use. In fact, we reworked our SEH significantly in 12.2 and believe it works very well.
The issue is when you mix the two, such as:
1 2 3 4 5 6 7 8 9 |
try { __try { // do something potentially dangerous here } __finally { // you want this to run anyway } } catch(...) { // handle exception here } |
This code has a SEH handling mechanism directly inside the C++ EH handling mechanism. You could mix the other way around too. The point is: two EH mechanisms in the same code.
Why is this bad? Codegen. It is almost impossible to support two different types of exception handling in the same “frame” (same method, think of it as a call stack entry), using two control flow mechanisms, two triggers for unwinding objects, etc. (For an example of the kind of craziness, imagine — this isn’t quite it but is close in terms of expressing the craziness of how it could work — having one method that had various returns, but also a bunch of gotos; you could goto inside loops and goto inside other logic structures; then for fun you added other control flow (say, setjmp/longjmp) to mix with gotos, so execution was moving around all over the place using different systems for moving; and then you had to ensure that code was correct, including invoking all scope-exit behaviour like destructors. This may give a sense of the kind of codegen that multiple exception handling and control flow mechanisms can create.) Visual C++ doesn’t support it, and neither do we.
But in the past you could write this code and the compiler would let you do it. Now, it will give you an error. That may appear as though previously working code now doesn’t work, but that’s not the case: it’s very likely the codegen for that old code was horrendously wrong and resulted in exception memory leaks, may have had issues calling destructors in the right order or at all, etc. It’s a case of: wrong, but you may not have known it.
Note: A big theme of C++Builder 12.2 (see all the versions of tip 4) are that the new compiler can make previously dangerous code safer. We’ve used it for this purpose ourselves!
How can I fix it?
Ok, so you might be convinced that mixing two different types of exception handling in the same frame is crazy and asking for trouble, and you should never do it. But you might still have code you need to fix.
You need to split the two kinds of exception handling. Both types of exception handling can live in the same app just fine, and live in the same code path and call stack, just not in the same method. So splitting means: taking the inner part of a try/catch, or __try/finally, block and putting it in a new method.
Let’s take this messy code (adapted for the purposes of demonstration only, please don’t judge me):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
TBitmap* Grid::Repaint(const int CellSizePx, const CellPainting painting) { try { __try { const TSize BMPSize = BitmapSize(CellSizePx); m_pBitmap->Width = BMPSize.cx; m_pBitmap->Height = BMPSize.cy; m_pBitmap->Clear(BackColor); PaintCellFill(CellSizePx, painting); PaintCellBorders(CellSizePx, painting); } __finally { return m_pBitmap.get(); } } catch(std::exception& ex) { return nullptr; } } |
Pop quiz: suppose an exception is thrown in the __try block (say, setting the Width property gives an access violation.) What is returned: m_pBitmap.get(), or nullptr?
To clean this up, we want to move the entire __try/__finally pair into its own method.
We have a tool to do this for you: simply select the code, right-click, choose Refactor, and then Extract Method:
Note that it handles parameters: it will correctly use parameters to pass on local variables, parameters from the first method, etc, also making them const or reference as appropriate. Here they were simple value types that were not modified, so they were const-by-value.
We added this refactoring in C++Builder 12.2 specifically for this use case. Extract Method takes a block of code and pulls it out into a new method. It is smart and will handle variables, create parameters in the extracted method, make them reference if required, etc.
So if you see this compiler error, you know what to do: select the code, Refactor, Extract Method. Then continue on! This once-off maintenance will solve bugs you may not even have known you had, plus, it makes exception handling reliable.
Design. Code. Compile. Deploy.
Start Free Trial Upgrade Today
Free Delphi Community Edition Free C++Builder Community Edition