|
|||||||||||||||||||||||||||
|
External Exception EEFFACEWhy Delphi cannot catch C++ exceptions and how to change it.
IntroductionPractically every C++Builder programmer - and, unfortunately, many users of programs written in C++Builder - is aware of the "External Exception EEFFACE" message box. This message pops up if a C++ exception is not caught explicitly and leaks into the VCL. To reproduce this error in a real-world-like scenario, create a new VCL Forms project and insert some code like this: Now build and run the program and click the button. You will receive this message: ![]() After closing the message box, the program continues to run. Where does the message come from?The message is, as mentioned above, originated by a C++ exception. The error in the above code should be obvious; to see what is happening, we use the debugger to step into the call of std::vector<>::at(). We end up here: Stepping into _Xran() reveals the actual throw statement: std::vector<>::at() throws an exception of the type std::out_of_range if we pass an invalid index. The error message is pretty clear and should give a clue for locating the cause of the problem - if only our program displayed it! What is happening after that exception is thrown?This can easily be monitored in the disassembly view. The compiler generates a call to the RTL function _ThrowExceptionLDTC() (you can find the implementation of that function and most of the exception handling stuff in $(BDS)\source\cpprtl\Source\except\xx.cpp). This function calls tossAnException(), which itself finally calls the RaiseException() function. RaiseException() searches for an exception handler, and the next handler that can be found is this one: As you can recognize, a general exception handler resides in the main window procedure. It catches everything, be it a Delphi exception (which is nicely dispatched and shown by Application.HandleException()), an OS exception (they are translated to Delphi exceptions) or something else, e.g. a C++ exception. For Exceptions the handler is not able to handle properly, it generates a message which contains the exception code. A few exception codes defined in System.pas are: 0x0EEFFACE is the exception code passed to RaiseException() in the C++ RTL, and this is what the "External Exception" message tells us. Why is Delphi unable to handle C++ exceptions?This is substantiated in the way Delphi and C++ work together. In the early 90s, Borland had a C++ GUI library called OWL. As Borland C++/OWL continued to lose market share against Visual C++/MFC, it was eventually decided to deprecate OWL and to adapt Delphi's highly successful VCL framework for C++. (In fact, it is not only possible but also very likely that this has been decided while the VCL was being designed; I don't know.) From this decision followed the requirement of an interface-level compatibility between Pascal and C++. While the unit interface concept of Borland's Pascal is very clear and distinct, this cannot be said for the C/C++ equivalent, header files. To enable Pascal to consume C++ header files, the Pascal compiler needed to be able to resolve all kind of dependencies to other header files and preprocessor macros, it required a mechanism to detect the correlation of header files and source modules (unlike Pascal, C++ allows to split the implementation of the interface defined in a header file across different modules - or vice versa), it would need to know about all C++ language features such as C++ templates, it even would need the capability to resolve template metaprograms (although that has most likely not been on the radar back in 1995 ;) ). Actually, it is virtually impossible to automatically generate a Delphi interface unit even for a plain C header. Although there have been several approaches to write a header converter, and some parts of this task can be simplified by helper utilities, all those rudiments are way too far from a complete solution. Probably due to the sheer difficulty of this approach, Borland decided to go the other way around. When C++Builder was first released in 1997, the supplied Delphi compiler was able to generate C++ header files that matched the interface section of Delphi units. To make this possible, Borland's C++ dialect had to be extended by the language capabilities of Delphi which affected the interface level, most notably the rooted object type system, the RTTI (__classid, __published), some language constructs (__property, __closure), new calling conventions (__fastcall) and a few #pragma directives. As a result, the interface-level language capabilities of Delphi can be considered a discrete subset of the interface-level capabilities of Borland's C++ implementation. (Note that this is, of course, an idealistic statement. Certain cases require explicit workarounds using Delphi compiler directives such as $HPPEMIT or $EXTERNALSYM. Also, some less widely used language features of Delphi, e.g. metaclass polymorphism/virtual constructors or, more recently, class helpers, cannot be used direcly in C++ code. Apart from class helpers which were primarily designed to make Delphi's type system fit into the .NET type system when Delphi for .NET was created, CodeGear is working on getting those discrepancies resolved. C++Builder 2009, for example, introduces the __classmethod keyword which makes possible to declare and override static virtual functions in C++.) As a direct consequence, C++Builder is aware of Delphi's Exception type and handles it properly, while the reverse is not true. Moreover, the C++ exception mechanism is not limited to the std::exception class and its derivates. In fact, you can throw almost everything. While I don't have a clue why anyone would want to throw integers or floats, it fits the C++ philosophy well to not limit the exception mechanism to a certain base class (right, Barry :( ). Delphi exceptions in C++BuilderIn Delphi, you only throw exceptions derived from the Exception class declared in SysUtils. (Although it is technically possible to pass any object that inherits from TObject to Raise(), this is very uncommon, and it is still a sufficient constraint due to Delphi's rooted object type system. It would be trivial to discover the actual type of the object thrown.) The Exception class and all derivates can be accessed by C++Builder through the header files generated by the Delphi compiler. Therefore, you can throw and catch Delphi exceptions in C++Builder: This little example will give us a pretty informative debug output, and the VCL exception handler displays this message: ![]() If you are familiar with the handling of Delphi-style classes in C++Builder, you might wonder why the compiler permits to construct a temporary ERangeError object on the stack as we are doing above. After all, ERangeError is derived from TObject, and every attempt to allocate TObject-derived classes on the stack usually results in an error: [BCC32 Error] main_unit.cpp(16): E2459 VCL style classes must be constructed using operator new There are many good reasons for this limitation, but I won't discuss them here (although I consider doing that in a future article). Our above case is not an exception (no pun intended ;), at least from the technical point of view. If you look at the code generated for above throw statement, you will notice that the compiler does not really construct the temporary ERangeError object on the stack. Instead, it generates almost the same code as if you had written this (only the RTTI passed to _ThrowExceptionLDTC() differs): This compiler-level workaround is a concession to the common C++ exception semantics. In C++, exception objects are thrown by value as throwing a pointer would produce a potential leak (who frees the object? How?). Delphi exceptions are instead, by definition, thrown as pointers and freed by the handler with the TObject::Free() method. Now try to think of what would be required to make Delphi handle C++ exceptions. The Delphi compiler would need to be acquainted with the types that can be thrown - as there is no limitation in C++, this would be all possible C++ types. So we end up having the problem we discussed above - converting C++ header files to Delphi unit interfaces is almost impossible. C++ exceptions in Delphi - Early Ehlinger's approachAs the subtitle implies, there is a way to change that. Delphi cannot handle C++ exceptions as they are, but it can handle wrapper classes which we can generate in a custom exception filter. This approach was taken first by Early Ehlinger in his TranslateStandardExceptions unit. When we add his unit to our project, a click on the first button results in this message: ![]() This is want we wanted, and it might satisfy you, but I will try to explain why it shouldn't. Let's take a look at the source code. I'll take out the relevant parts: To fully understand what this code is doing, read Early's explanation on his website or in the source code comments. Basically, this is what happens:
Now take a closer look at this specific part: If you don't realize why this code is evil, read "IsBadXxxPtr should really be called CrashProgramRandomly" by Raymond Chen and "Should I check the parameters to my function?" by Larry Osterman. And there are more problems which prevented me from using Early's version:
After so many problems coming up, I decided to find another solution. C++ exceptions in Delphi - my approachEarly's unit gave me the idea to use the exception filter to convert C++ exceptions to Delphi exceptions. This is where I started. To fix the Delphi application/package problem, I decided to create a Delphi unit as those can be added to both Delphi and C++Builder projects. While debugging the call of _ThrowExceptionLDTC(), I found that the C++ compiler passes a pointer to the RTTI descriptor to the function. This pointer is stored in the exception descriptor which itself is passed to RaiseException(). This allows us to read out both the exception descriptor (which helps us to free the object correctly) and the RTTI descriptor (which allows for dynamic dispatching). All this is well documented in the RTL source code; look into $(BDS)\source\cpprtl\Source\except\xx.cpp and $(BDS)\source\cpprtl\Source\except\xxtype.cpp for more details. And this is exactly what my unit is doing. I called it 'SystemCppException' because it is an addition to the exception handling mechanism in the System unit. To achieve this, I wrote a few Delphi functions that mimic the behavior of some exception-handling-related functions in the C++ RTL. Using them, the exception filter is able to recognize C++ exceptions, to identify their actual type, to cast them to any base class using the RTTI, and finally, to destroy and deallocate them. To make them usable in Delphi code, the C++ exceptions are wrapped into Delphi exceptions, the ECppException and the ECppStdException classes. Their properties and methods should explain themselves, but I will mention the most useful ones:
If the exception object derives from std::exception, we have a common base class which simplifies things a lot. In this case, the exception filter creates a wrapper object of type ECppStdException, which itself inherits from ECppException and provides one more property:
This is what the interface section looks like: You can download the unit in the Software section. Simply add this unit to your project, be it in Delphi or C++Builder. (But do not add it to packages which are linked with rtl*.bpl, for the reasons I explained above!) Similar to Early's unit, this unit installs an exception filter in its initialization section and removes it in the finalization section. Just add it to your project, and the VCL exception handler will catch your exceptions and display the expected message: ![]() The prefixed exception type name vanishes in the Release build; your customers won't see it. Advanced Exception DispatchingOf course, there is more value in SystemCppException than the ability to display proper exception messages. With the TApplicationEvents::OnException event, we can install a global exception dispatcher that does useful things such as sending bug reports, writing logfiles and displaying formatted and extensive messages. Before we go on with doing that, I want to mention a nice little feature of the C++Builder compiler which is documented here: the -xp switch aka "Location information". If this option is enabled, every exception descriptor is aware of the file and the line where the exception was thrown. The exception classes declared in SystemCppException.pas use that information and provide it as read-only properties. This example demonstrates dynamic exception dispatching and the utilization of that compiler switch: The std::vector<> example I showed at the beginning will now result in this error message (Release build): ![]() In a commercial project, you would of course not want display the exception type, file name or line number in the exception message - imagine to send that information along with the automated bug report instead ;-) (If you need more extensive bug reports including stack trace, modules list, system information etc., consider using a 3rd-party product such as JclDebug, madExcept or EurekaLog.) Exceptions derived from user_notification_exception look like this: ![]() So why did Borland not implement this in the first place?Disclaimer: This is pure speculation. As above solution seems to integrate and to perform smoothly, the question comes up why Borland did not implement that yet. Exception handling is a challenging subject, and I am sure there are many answers to this. Some potential reasons would be:
Addenda as of 28.02.2010: Windows Error ReportingThe guidelines for the Windows 7 Client Software Logo Program require that unhandled exceptions must not be caught by the application: Microsoft: Vendors must not hide unhandled exceptions from Windows error reporting (WER). ISVs must sign up to receive their crash data from WER. You can do this by signing applications at Winqual. ISVs must map their applications carrying the Windows 7 logo to their company at this site and maintain these mappings while the product is supported. The Software Logo Program came up in the Embarcadero newsgroups recently, and Allen Bauer kindly pointed to the System::JITEnable global variable which allows to configure the RTL's default exception handling behavior. To illustrate the consequences of different combinations of JITEnable and SystemCppException, I wrote a little example program which lets you experiment with the different settings: ![]() If you want to build it yourself, be sure to get the latest revision of SystemCppException. SummaryThis document explained the origin of the "External Exception EEFFACE" message and how to get rid of it. As I tried to point out in the last paragraph, my approach is not without problems: it wraps unhandled C++ exceptions into Delphi exceptions which might be questionable from the designer perspective, and it includes a few functions that must be kept in sync with their equivalents in the C++ RTL. Expect those implementation details to change as soon as Delphi and C++Builder go 64-Bit. But anyway, I still hope that Commodore will provide a way to handle C++ exceptions out-of-the-box. After reading this document, you should be able to decide whether you think that my unit adds value to your project. If you decide to use it, you can find it in the Software section. (And if you like, drop me a few lines per mail or post a comment.) References[1] Early Ehlinger, Translate C++ Exceptions to VCL Exceptions [2] Raymond Chen, IsBadXxxPtr should really be called CrashProgramRandomly, 27.09.2006 [3] Larry Osterman, Should I check the parameters to my function?, 18.05.2004 [4] Raymond Chen, Homework assignment about window subclassing, 10.11.2003 [5] Anders Hejlsberg, Historical Documents: Delphi 1 launch demos source code, launch script, and marketing video, 14.02.1995 [6] The Delphi and C++Builder Runtime Library source code (delivered with C++Builder) Comments
|
||||||||||||||||||||||||||