Throwing cv::Exception across DLL boundary (Feature #374)
Description
Error reporting via C++ exception introduced in OpenCV 2.x is really bad idea. Now I just have an access violation when cxcore210.dll (MSVC-compiled) throws an exception, because CRT libs linked into my app and cxcore210.dll are different (for some reasons I have to use /MD for the debug build). Even for the same compiler flags CRT versions can be different and again you'll see the crash... There's general rule: "Don't allow exceptions to propagate across module boundaries". Why not just to follow it? I think it's not very hard: wrap each exported function body with try/catch pair that (optionally) translates C++ exception into the error code.
Associated revisions
Merge pull request #374 from ivan-korolev:fix_estimateRigidTransform
History
Updated by Sebastian Krämer almost 15 years ago
I can't follow you here.
It's quite normal that there can be dependencies to different CRT versions. Often, only debug-crt dependencies are problematic anyway.
And what's the link to the exported funktions and exceptions? To be clear, I wouldn't want to have any opencv method wrapped in try-catch blocks because that can hurt performance badly. Rather, do that in the program that imports opencv. Or am I missing something?
Updated by Dmitry Bely almost 15 years ago
Do you think it's quite normal to require exactly the same CRT DLL for an .EXE and all .DLLs it uses? Otherwise you will get a crash when the .EXE's CRT will try to delete a Exception object allocated on the DLL's CRT heap. I don't even mention another compilers: say you could use MSVC-build OpenCV 1.x DLLs from the Borland-compiled code. Now you can't.
And what's the link to the exported funktions and exceptions?
Sorry, I didn't catch what you meant here. As for performance impact I don't think try/catch block introduces any performance loss when no exception is thrown. Another idea: you can have 2 variants for each exported function - throwing and not throwing exceptions (say cvCreateMatHeaderExn/cvCreateMatHeader). Non-throwing variant can be easily generated from throwing one by wrapping it into a try/catch block.
To summarize: error handling without C++ exceptions should be possible as before.
Updated by Sebastian Krämer over 14 years ago
Replying to [comment:2 dbely]:
Do you think it's quite normal to require exactly the same CRT DLL for an .EXE and all .DLLs it uses?
No. Like I said, it's not unusual to require several..
Otherwise you will get a crash when the .EXE's CRT will try to delete a Exception object >allocated on the DLL's CRT heap.
I would never try to do that. I think it's the throwing code's responsibility to clean that up (i.e., often the exception object is only a const ref&..). delete-ing an object that was created with another runtime's new operator seems to be a bad idea in any case. If that#s necessary right now the code should be changed so that the exception object is deleted within the DLL. Isn't that possible??
Admittedly I'm not too experienced with programming own exception code.
As to going back to error codes: I think getting rid of these is a good thing as it fits the c++-migration aspect. Especially when using threads you can't really rely on global error codes.
Updated by Sebastian Krämer over 14 years ago
Btw., exception in which function exactly is giving you trouble?
Updated by Dmitry Bely over 14 years ago
Replying to [comment:3 decision]:
Replying to [comment:2 dbely]:
Do you think it's quite normal to require exactly the same CRT DLL for an .EXE and all .DLLs it uses?
No. Like I said, it's not unusual to require several..
Otherwise you will get a crash when the .EXE's CRT will try to delete a Exception object >allocated on the DLL's CRT heap.
I would never try to do that. I think it's the throwing code's responsibility to clean that up (i.e., often the exception object is only a const ref&..). delete-ing an object that was created with another runtime's new operator seems to be a bad idea in any case. If that#s necessary right now the code should be changed so that the exception object is deleted within the DLL. Isn't that possible??
No, it's not possible. And that's why all these problems arise. Exception object is created in the throw statement (by DLL runtime) and deleted in the catch block (by EXE runtime). You cannot control that - it's done by the compiler.
Admittedly I'm not too experienced with programming own exception code.
As to going back to error codes: I think getting rid of these is a good thing as it fits the c++-migration aspect. Especially when using threads you can't really rely on global error codes.
In theory: yes. In practice: the DLL cannot be reliably used with another compiler, runtime etc. I don't think it's acceptable.
As for threads: I don't see any problem with C approach. You either return an error code or place it in a TLS variable (see SetLastError()/!GetLastError() calls in Win32 API).
Updated by Dmitry Bely over 14 years ago
Replying to [comment:4 decision]:
Btw., exception in which function exactly is giving you trouble?
It absolutely doesn't matter - they all behave the same when an exception occurs.
Updated by Benoit R over 14 years ago
Hello,
I've also some problem with the new OpenCV Exception when they can come from more than one thread.
I'm looking at it a little more but it seems to be a problem with shared lib (DLL) and MinGW.
you can have 2 variants for each exported function - throwing and not throwing
exceptions (say cvCreateMatHeaderExn/cvCreateMatHeader).
Non-throwing variant can be easily generated from throwing one by wrapping it into a try/catch block.
There is already a function to do something like this:
If you defined the CvErrorCallback function by using the cv::redirectError() function you can avoid that OpenCV throw the error, therefore there is no need to try and catch (Look at void error() {} in https://code.ros.org/svn/opencv/trunk/opencv/modules/core/src/system.cpp)
Updated by Dmitry Bely over 14 years ago
Replying to [comment:7 neub]:
There is already a function to do something like this:
If you defined the CvErrorCallback function by using the cv::redirectError() function you can avoid that OpenCV throw the error, therefore there is no need to try and catch (Look at void error() {} in https://code.ros.org/svn/opencv/trunk/opencv/modules/core/src/system.cpp)
No, you cannot:
void error( const Exception& exc ) { if (customErrorCallback != 0) customErrorCallback(exc.code, exc.func.c_str(), exc.err.c_str(), exc.file.c_str(), exc.line, customErrorCallbackData); else { const char* errorStr = cvErrorStr(exc.code); char buf[1 << 16]; sprintf( buf, "OpenCV Error: %s (%s) in %s, file %s, line %d", errorStr, exc.err.c_str(), exc.func.size() > 0 ? exc.func.c_str() : "unknown function", exc.file.c_str(), exc.line ); fprintf( stderr, "%s\n", buf ); fflush( stderr ); } if(breakOnError) { static volatile int* p = 0; *p = 0; } throw exc; }
Exception is thrown anyway when you return from the handler.
Updated by Benoit R over 14 years ago
Sorry I didn't notice that I have changed this part of the code...
I think If you use your own handler you know if you want to throw this exception or not.
what do you think? should I submit this patch?
Updated by Sebastian Krämer over 14 years ago
Regarding the original post/report: the quoted error method in comment #8 doesn't create the exception object on the heap so I -- again -- don't see why anyone or anything receiving that exceptions should try to delete it.
Confused..
Updated by Sebastian Krämer over 14 years ago
.. while I agree that using an error callback and re-throwing the exception seems wrong.
Updated by Dmitry Bely over 14 years ago
Replying to [comment:9 neub]:
Sorry I didn't notice that I have changed this part of the code...
I think If you use your own handler you know if you want to throw this exception or not.what do you think? should I submit this patch?
How are you going to unwind a stack without exceptions? The original C-style unwinding is already removed from the sources.
Updated by Dmitry Bely over 14 years ago
Replying to [comment:10 decision]:
Regarding the original post/report: the quoted error method in comment #8 doesn't create the exception object on the heap so I -- again -- don't see why anyone or anything receiving that exceptions should try to delete it.
Confused..
As I already explained you, an exception object is constructed/deleted by the compiler. If you use exceptions you cannot avoid that.
Updated by Benoit R over 14 years ago
while I agree that using an error callback and re-throwing the exception seems wrong.
I think the best thing is to use an error callback and not throw at all the exception.
In the case you don't want OpenCV exception going out from OpenCV.
The only problem is how do you return from the function, is there is a way to catch exception from OpenCV library.
The fact why I don't want to use exception is explained in http://www.qtforum.org/article/33205/exceptions-in-qthread-crash-the-program-when-compiled-with-mingw.html
where exception make crash my software when I use them in more than one thread.
Updated by Dmitry Bely over 14 years ago
Replying to [comment:14 neub]:
while I agree that using an error callback and re-throwing the exception seems wrong.
I think the best thing is to use an error callback and not throw at all the exception.
In the case you don't want OpenCV exception going out from OpenCV.The only problem is how do you return from the function, is there is a way to catch exception from OpenCV library.
Have you read what I wrote before? It's not "the only problem", it's THE problem that makes "use an error callback" approach impossible. There is no suitable method to unwind the stack for C++ program. For MSVC-specific code you can use setjmp()/longjmp() from SETJMPEX.H, but that's not portable at all, and longjmp() is no better than C++ exception.
What I proposed was adding for each exported C function a non-throwing variant like
extern "C" void cvSomeFunction() { // throw inside } extern "C" void cvSomeFunctionNoThrow() { try { cvSomeFunction(); } catch(cv::Exception& exn) { cvTranslateExceptionToCvError(exn); } }
or even
extern bool cvExceptionEnabled; static void cvSomeFunctionThrow() { // throw inside } extern "C" void cvSomeFunction() { if (cvExceptionEnabled) { cvSomeFunction(); } else { try { cvSomeFunction(); } catch(cv::Exception& exn) { cvTranslateExceptionToCvError(exn); } } }
A helper function can be easily generated by macro so all this won't require too much typing.
Updated by Fred Molinat over 14 years ago
Hello,
after fighting for a few days trying to get OpenCV work with Code:Blocks/Mingw/GCC, this ticket explains everything !
I quickly managed to get codes compile (openCV 2.0,2.1,svn compiled with gcc 4.3, 4.4.1, 4.5 ), but each time I faced problems with the generated samples exes. The big problem being exceptions thrown when moving some trackbars' tickers (not all trackbars did this).
After browsing the code and reading this ticket, I withdrew and installed Visual C++ 2008 Express. Then everything compiled and worked flawlessly.
Could the solution proposed by dbely be implemented, to make opencv2 more "portable" ?
Updated by Vadim Pisarevsky over 14 years ago
The proposed solution will not work until all theOpenCV C API is turned into thin wrappers around C++ functions. Right now there are many places where a C++ function is a wapper around C function that calls some other C functions that are wrappers around C++ functions, that is, we have C++ => C => C => C++ call chain. For example:
cv::calibrateCamera()
{
...
cvCalibrateCamera2()
...
}
cvCalibrateCamera2()
{
...
cvSVD(); // indirectly, via the Levenberg-Marquardt solver
...
}
cvSVD()
{
cv::SVD svd();
}
if the inner-most C++ function throws an exception, it will not be passed through the C function if the exceptions are disabled as proposed, so the obvious code:
try
{
cv::calibrateCamera();
}
catch(...)
{
}
will work differently depending on some global variable.
It's recommended to use the already suggested solution - build everything with the same compiler, which is a good practice anyway. Mingw-dwarf2, as well as the freely available VS2008/VS2010 express implement exception handling properly.
- Status changed from Open to Done
- (deleted custom field) set to wontfix
Updated by Dmitry Bely over 14 years ago
Replying to [comment:17 vp153]:
The proposed solution will not work until all the OpenCV C API is turned into thin wrappers around C++ functions. Right now there are many places where a C++ function is a wapper around C function that calls some other C functions that are wrappers around C++ functions, that is, we have C++ => C => C => C++ call chain.
I see. But isn't the following still OK:
extern "C" void cvSomeFunction() { // throw inside } extern "C" void cvSomeFunctionNoThrow() { try { cvSomeFunction(); } catch(cv::Exception& exn) { cvTranslateExceptionToCvError(exn); } } // somethere in the headers #if defined(OPENCV_NO_THROW) (...) #define cvSomeFunction cvSomeFunctionNoThrow (...) #endif
A library user will not notice any change as well as OpenCV developers.
It's recommended to use the already suggested solution - build everything with the same compiler, which is a good practice anyway. Mingw-dwarf2, as well as the freely available VS2008/VS2010 express implement exception handling properly.
So now you
1. Ban OpenCV users that use pure C.
2. Ban those who link their applications with a static CRT.
3. Require multiple OpenCV .dll for projects where several C++ compilers are used (e.g. my company uses both MSVC and Embarcadero for core development). Even different service packs for the same compiler may not work: MSVC searches for them by manifest, not by file name, and if some DLL require specific CRT version you are in trouble.
If this is not a nightmare then what the nightmare is?
- Status changed from Done to Cancelled
- (deleted custom field) deleted (
wontfix)
Updated by anonymous - over 14 years ago
Unfortunately, throwing or not throwing exceptions is only a part of the story. A working error handling mechanism requires stack unrolling in some form. In C++ this is handled by exceptions. In previous OpenCV it was handled by using macros BEGIN, END, CV_CALL() and quite strict memory allocation rules. But in reality a lot of code ignored those rules, so other error modes except for CV_ErrModeLeaf were not quite usable. I shall repeat that your solution will work only when the whole OpenCV is rewritten in C++, and the C API is only a thin layer on top of it. We are slowly moving toward it, but it's not the case yet.
That is, we probably made things more complex for some developers, but, on the other hand, we provided a consistent and more efficient way to handle bugs for many others.
If you absolutely need to build OpenCV with one compiler and your application with another one, you can create a thin C layer on top of C++ OpenCV API, where you insert try-catch statements and turn the errors into the return codes or any other error handling information. Then you build this layer with the same compiler as OpenCV and safely use it from Borland C++ or any other compiler:
==============
myOpenCVCLayer.dll:
<thread-local> errcode = 0;
extern "C" +declspec(dllexport) CvSeq* mycvHaarDetectObjects(...)
{
try
{
cvHaarDetectObjects(...)
}
catch(const cv::Exception& exc)
{
errcode = exc.code;
}
}
int mycvGetErrStatus() { return errcode; }
void mycvSetErrStatus(int c) { errcode = c; }
We are thinking of creating such a C layer on top of C++ OpenCV to simplify implementation of the wrappers for other languages. But this is quite a big amount of work. So let's leave the ticket as enhancement request that we will be working on in the future.
Updated by Vadim Pisarevsky over 13 years ago
ok, since this will never been fixed (or until OpenCV is re-rewritten in C), since OpenCV can now be built statically, so it's possible to build a custom static or shared library that will include a set of OpenCV functions, wrapped into "proper" extern-C functions that will catch exceptions inside and return a error code, this bug report is closed.
- Status changed from Cancelled to Done
- (deleted custom field) set to wontfix
Updated by Andrey Kamaev over 13 years ago
Can the following code be a workaround for this issue?
int +cdecl myErrorCallback( int status, const char* func_name, const char* err_msg, const char* file_name, int line, void* userdata ) { throw cv::Exception(status, err_msg, func_name, file_name, line); } cv::redirectError(myErrorCallback)
Exceptions will be thrown with your CRT instead of OpenCV's one.
Updated by Vadim Pisarevsky over 13 years ago
It will not help, because the exception may have to go through the call stack within OpenCV, then through the call stack within the application.
OpenCV will use exceptions internally, since it's by far the most convenient way of handling applications.
Probably, our wrapper generator could be used to automatically generate C wrappers on top of C++, where we could use try-catch inside each wrapper and return error code in the case of error. Basically, Python and Java wrappers are already such C functions.
Updated by Dmitry Bely over 13 years ago
Replying to [comment:20 vp153]:
ok, since this will never been fixed (or until OpenCV is re-rewritten in C), since OpenCV can now be built statically, so it's possible to build a custom static or shared library that will include a set of OpenCV functions, wrapped into "proper" extern-C functions that will catch exceptions inside and return a error code, this bug report is closed.
Excuse me, but why to close the ticket? I thought we had reached consensus that there was no need to rewrite OpenCV in C, but non-throwing C wrappers should be provided some day (I still think that they can be created using C preprocessor with minimal efforts). Closing the ticket means it will never happen?
Updated by Vadim Pisarevsky over 13 years ago
I don't think it requires minimal efforts (otherwise, you or someone else can have already provided some tool for that and would kindly agreed to support it to reflect the latest changes in the API :) )
Ok, let's reopen the ticket, with a different priority, though.
- Status changed from Done to Cancelled
- (deleted custom field) deleted (
wontfix)
Updated by Vadim Pisarevsky over 13 years ago
- Status deleted (
Cancelled)
Updated by Alexander Shishkov about 13 years ago
- Description changed from Error reporting via C++ exception introduced in [[OpenCV]] 2.x is really bad ... to Error reporting via C++ exception introduced in OpenCV 2.x is really bad idea... More
Updated by Alexander Shishkov almost 13 years ago
- Status set to Cancelled