Jump to content

CFLNs and exception handling


Recommended Posts

I am refactoring code that works as an interface between c++ exceptions and LabVIEW error cluster. So far I have something like this (compressed to an example)

typedef struct
{
	LVBoolean status;
	int32_t code;
	LStrHandle message;
} LVErrorCluster;

template<typename Func> int64_t ExceptionGuard_LVErr(Func&& guardfunc, LVErrorCluster *LVError)
{
	try
	{
		return guardfunc();
	}
	catch (WrpExcUser &e)
	{
		return CopyErrorUS(e.whaterr(), e.what(), LVError);
	}
	return NOERROR;
}

//then use the guard like this in a LabVIEW CLFN call

int64_t ErrorTest(LVErrorCluster *lverr)
{
	return ExceptionGuard_LVErr([&]
	{
		throw(WrpExcUser("Some funky user error", -6426429678568));
		return NOERROR;
	}, lverr);
}

But my code doesn't catch just the WrpExcUser exception, but also generic stuff like std::bad_alloc, std::bad_function_call that can occur within the guarded section of code and makes them into some user friendly messages instead of the standard "exception occured -1xxx message". Is it legal to catch these exceptions? I wanted to keep my CLFNs error checking to default.

Link to comment

I am refactoring code that works as an interface between c++ exceptions and LabVIEW error cluster. So far I have something like this (compressed to an example)

typedef struct
{
	LVBoolean status;
	int32_t code;
	LStrHandle message;
} LVErrorCluster;

template<typename Func> int64_t ExceptionGuard_LVErr(Func&& guardfunc, LVErrorCluster *LVError)
{
	try
	{
		return guardfunc();
	}
	catch (WrpExcUser &e)
	{
		return CopyErrorUS(e.whaterr(), e.what(), LVError);
	}
	return NOERROR;
}

//then use the guard like this in a LabVIEW CLFN call

int64_t ErrorTest(LVErrorCluster *lverr)
{
	return ExceptionGuard_LVErr([&]
	{
		throw(WrpExcUser("Some funky user error", -6426429678568));
		return NOERROR;
	}, lverr);
}

But my code doesn't catch just the WrpExcUser exception, but also generic stuff like std::bad_alloc, std::bad_function_call that can occur within the guarded section of code and makes them into some user friendly messages instead of the standard "exception occured -1xxx message". Is it legal to catch these exceptions? I wanted to keep my CLFNs error checking to default.

 

Several remarks first:

 

1) You should put lv_prolog.h and lv_epilog.h includes around the error structure definition to make sure the element alignment is correct.

 

2) You don't show the definition of WrpExcUser exception class but if it derives from some other exception class it will catch those too.

 

3) Your attempt to generalize the code for catching the exception through a function pointer, so you can reuse it in multiple functions, is in principle not bad, but you loose the ability to call functions which take parameters. Not really very interesting for a bigger library. I suppose that is why you made it a template so you can replace the function pointer with specific definitions for each function but that tends to get heavy and pretty hard to maintain too.

 

I'm not sure what your question about default error checking should mean. As far as external code goes, you as implementor define what the error checking is and how it should be performed. It's a pipe dream to have template error checking in all places the same way, reality simply doesn't work that way. Sometimes an error is fatal, sometimes it is temporary and sometimes it is even expected. Your code has to account for this on a case to case situation.

 

As far as calling code from LabVIEW goes, unless you disable the error handling level in the Call Library Node configuration, LabVIEW will wrap the call into an exception handler of its own and return an according error in the error cluster of the Call Library Node. The reported error is not very detailed as LabVIEW has to use the most generic exception class there is in order to catch all possible exceptions but it is at least something. So generally if you don't want to do custom error handling in the external code you could leave it all to LabVIEW.

  • Like 1
Link to comment

1) I know, I just didn't include it there. Thx for mentioning it for people who wander in here.

 

2) It derives from std::excception, but it can never leave the guard.

 

3) The code might be confusing, it does this: the ExceptionGuard_LVErr is a utility function that accepts a function object, that gets invoked in the try-catch block. I used some retard code (throw and unreachable return) but you can invoke any code inside. The "&&" tells the compiler how to pass parameters. So I can call it like this:

int64_t LVflushencoder(venc *vencoder, uintptr_t *LVPkt, int32_t *GotPacket, LVErrorCluster *LVError)
{
	return ExceptionGuard([&]
	{
		return vencoder->FlushEncoder(LVPkt, GotPacket);
	}, LVError);
}

It indeed serves as a generic layer to translate exceptions, that I fired up somwhere in the code and are supposed to be passed into this layer. So the advantage is you do not need to pass the errorcluster as an argument through all the nested functions. The code executed in the guard might have it's own try-catch or ifs that deal with exceptions and errors, that should never traverse to the top layer, but for things like:

auto ptr = allocatestuff();
if(!ptr)
    throw("couldn not even allocate stuff, so don't bother")
 

There is this layer.

 

 

Rereading my original question, it is confusing.

 

I was expecting an answer like: Do not catch exception X and Y, because they might be thrown even before your code is executed and you would catch them and translate to something even more confusing.

Link to comment

I was expecting an answer like: Do not catch exception X and Y, because they might be thrown even before your code is executed and you would catch them and translate to something even more confusing.

 

Who would throw them then? When LabVIEW calls your function, the actual thread is really blocked for your function and there will be nothing else executing in that thread until you return from your function. So not sure what you mean with this. Exception handling is AFAIK thread specific so other threads in LabVIEW throwing exceptions should not affect your code. Otherwise exception handling would be pretty meaningless in a multithreaded application.

Link to comment

I typically do something along these lines.

 

Similar to your approach, but I use the standard try-catch and an exception class derived from std::runtime_error to return my own exceptions (with error code functionality). I have a static function to return other standard errors. To make it more elegant, I could easily handle the first catch statement together with the second (downcast or string-only error). From what rolfk wrote, I could also drop the final catch, and allow the default catch mechanism of LabVIEW to handle non-standard exceptions.

double foo(LVErrorCluster *lvErr, DBLArray1D** arr){
	try{
		return 1.0;
	}
	catch (LVException &ex) {
		ex.returnError(lvErr);
		(*arr)->dimSize = 0;
		return std::nan("");
	}
	catch (std::exception &ex) {
		LVException::returnStdException(lvErr, ex);
		(*arr)->dimSize = 0;
		return std::nan("");
	}
	catch (...) {
		LVException ex("Unknown exception has occurred");
		ex.returnError(lvErr);
		(*arr)->dimSize = 0;
		return std::nan("");
	}
}

It works well and allows me to ensure that garbage values are not read by LabVIEW. In LabVIEW, I use a merge error node with the error wire going through the call library function node.

 

I'm a fan of generic programming and C++ templates myself, but I don't see the benefit of the exception guard, as you need to wrap the function call in a lambda expression anyway. Might as well be more explicit and use the try block.

Edited by oysstu
  • Like 1
Link to comment

Who would throw them then? When LabVIEW calls your function, the actual thread is really blocked for your function and there will be nothing else executing in that thread until you return from your function. So not sure what you mean with this. Exception handling is AFAIK thread specific so other threads in LabVIEW throwing exceptions should not affect your code. Otherwise exception handling would be pretty meaningless in a multithreaded application.

 

This applies to windows only:

 

I read you well. BUT!!

 

My code can throw exceptions that are generated by NI code. Once again, I wrote the "expected answer incorrectly", because i wrote "before your code is executed" which should be any time your code is executed.

 

The thing with CLFNs is, that if you for example attempt to execute stuff like this:

int *pointer_of_doom = nullptr;
*pointer_of_doom = 666;

The CLFN will still report it as exception. Which is nice, since it doesn't just straight forward crash LabVIEW, when you make a pointer arithmethics error.

 

So LabVIEW does override the signal handlers for some?! signals, and also who knows what the well documented memory manager functions may throw  :book:

 

I typically do something along these lines.

 

Similar to your approach, but I use the standard try-catch and an exception class derived from std::runtime_error to return my own exceptions (with error code functionality). I have a static function to return other standard errors. To make it more elegant, I could easily handle the first catch statement together with the second (downcast or string-only error). From what rolfk wrote, I could also drop the final catch, and allow the default catch mechanism of LabVIEW to handle non-standard exceptions.

double foo(LVErrorCluster *lvErr, DBLArray1D** arr){
	try{
		return 1.0;
	}
	catch (LVException &ex) {
		ex.returnError(lvErr);
		(*arr)->dimSize = 0;
		return std::nan("");
	}
	catch (std::exception &ex) {
		LVException::returnStdException(lvErr, ex);
		(*arr)->dimSize = 0;
		return std::nan("");
	}
	catch (...) {
		LVException ex("Unknown exception has occurred");
		ex.returnError(lvErr);
		(*arr)->dimSize = 0;
		return std::nan("");
	}
}

It works well and allows me to ensure that garbage values are not read by LabVIEW. In LabVIEW, I use a merge error node with the error wire going through the call library function node.

 

I'm a fan of generic programming and C++ templates myself, but I don't see the benefit of the exception guard, as you need to wrap the function call in a lambda expression anyway. Might as well be more explicit and use the try block.

 

You write try-multicatch for all the functions you export. I just invoke them in one or more functions if needed via lambda. That's the only benefit, that you write all that try-catch stuff at just one place.

 

Also I see you like the STD library, so beware of using the std::string or other exception capable containers in your custom exceptions, since LabVIEW overrides the terminate() handler too.

You throw some exception, that is legit and has info, like if you're trying to allocate too much memory because of wrong parameters. You catch exception, string can't allocate, throws another exception in constructor, terminate() is called, and you get from LabVIEW totally non-relevant output. I had to figure this out the hard way  :D

  • Like 1
Link to comment

This applies to windows only:

 

I read you well. BUT!!

 

My code can throw exceptions that are generated by NI code. Once again, I wrote the "expected answer incorrectly", because i wrote "before your code is executed" which should be any time your code is executed.

 

The thing with CLFNs is, that if you for example attempt to execute stuff like this:

int *pointer_of_doom = nullptr;
*pointer_of_doom = 666;

The CLFN will still report it as exception. Which is nice, since it doesn't just straight forward crash LabVIEW, when you make a pointer arithmethics error.

 

So LabVIEW does override the signal handlers for some?! signals, and also who knows what the well documented memory manager functions may throw  :book:

 

 

You write try-multicatch for all the functions you export. I just invoke them in one or more functions if needed via lambda. That's the only benefit, that you write all that try-catch stuff at just one place.

 

Also I see you like the STD library, so beware of using the std::string or other exception capable containers in your custom exceptions, since LabVIEW overrides the terminate() handler too.

You throw some exception, that is legit and has info, like if you're trying to allocate too much memory because of wrong parameters. You catch exception, string can't allocate, throws another exception in constructor, terminate() is called, and you get from LabVIEW totally non-relevant output. I had to figure this out the hard way  :D

 

Well, this is mostly guessing, but almost all functions documented in the extcode.h file existed long before LabVIEW was compiled as C++ code. Even then much of it remained as C code but just got compiled with the C++ compiler. So it is highly unlikely that any of these low level managers thow any explicit exceptions of their own. That still leaves of course the OS exceptions that are mostly directly generated from the CPU and VMM hardware as interrupts. Windows has its own exception mechanisme that predates the C++ exceptions by many years. The implementation of it requires assembly and the Windows API for it is not used by many applications explicitedly because it is somewhat difficult to handle right. Supposedly your C runtime library with exception handling would intercept those exceptions though and integrate them in its own exception handling. How well that really works I wouldn't know.

 

Now the exception handling in the C runtime is compiler specific (and patent encumbered) so each C runtime implements its own exception handling architecture that is anything but binary compatible. Therefore if you mix and match different binary object files together you are pretty lucky if your exceptions will not just crash when crossing those boundaries. I'm not sure what LabVIEW all does around the Call Library Node. Because of the binary incompatibilities between exception handling and the fact that a C only interface can't even properly use C++ exceptions in a meaningful way I'm pretty sure LabVIEW doesn't jus add a standard try catch around the Call Library Node call. That would go completely havoc in most cases. What LabVIEW can do however is hooking into the Windows exception mechanisme, This interface is standardized and therefore doesn't suffer from these compiler difficulties. How much of your C++ exceptions can get caught like this depends entirely how your C++ runtime library is able to interact with the Windows exception interface. If it can translate its exceptions from and to this interface whenever it traverses from Windows API to C++ runtime and back from that when leaving the code module (your DLL) then it will work. Otherwise you get all kind of messed up behaviour. Of course a C++ exception library that couldn't translate those low level OS exceptions into its own exceptions would be pretty useless. So that is likely covered. Where it will get shaky is about explicit C++ exceptions that are thrown in your code. How they translate back into the standard Windows exception mechanisme I have no idea. If they do it's a marvelous piece of code for sure, that I would not want to touch for any money in the world :P. If they don't, well....!!!

 

C++ exceptions are great to use but get a complete fiasco if you need to write code that spans over object modules created in different C compilers or even just versions. C++ code in general suffers from this in a great way, as ABI specifications including class memory layouts are also compiler specific.

Link to comment
...

 

So LabVIEW does override the signal handlers for some?! signals, and also who knows what the well documented memory manager functions may throw  :book:

 

You write try-multicatch for all the functions you export. I just invoke them in one or more functions if needed via lambda. That's the only benefit, that you write all that try-catch stuff at just one place.

 

Also I see you like the STD library, so beware of using the std::string or other exception capable containers in your custom exceptions, since LabVIEW overrides the terminate() handler too.

You throw some exception, that is legit and has info, like if you're trying to allocate too much memory because of wrong parameters. You catch exception, string can't allocate, throws another exception in constructor, terminate() is called, and you get from LabVIEW totally non-relevant output. I had to figure this out the hard way  :D

 

While the extcode functions marked as extern "C" can throw exceptions, doing so results in undefined behaviour. I typically handle the errors returned as MgErr by re-throwing as exceptions, and allow LabVIEW to handle those that is propagated by the events in signals.h/csignals.h ("handle" as in crash normally).

 

I see, I've done something similar previously using preprocessor definitions. Switched away from that when I started doing output value cleanup in the catch block. I suppose I could do the same automatically using your approach and variadic templates.

 

I do indeed use std::string to concatenate some error messages, I'll look into replacing them with strcat. Thanks for the tip.

Link to comment

While the extcode functions marked as extern "C" can throw exceptions, doing so results in undefined behaviour. I typically handle the errors returned as MgErr by re-throwing as exceptions, and allow LabVIEW to handle those that is propagated by the events in signals.h/csignals.h ("handle" as in crash normally).

 

It's not undifined. These exceptions are really caused by hardware interrupts and translated by Windows into its own exception handling. An application can hook into that exception handling by calling Windows API functions. If it doesn't you get the well known "Your application has caused a General Protection Fault error" or similar dialog with the option to abort your application or kill it (but not to continue)  :D. If your C++ exceptions are caught by such an application hook depends entirely on the fact if your C runtime library actually goes to the extra effort of making its exceptions play nice with the OS exception mechanisme. And no I wouldn't know if they do and which might or might not do that.

Link to comment

Well, this is mostly guessing, but almost all functions documented in the extcode.h file existed long before LabVIEW was compiled as C++ code. Even then much of it remained as C code but just got compiled with the C++ compiler. So it is highly unlikely that any of these low level managers thow any explicit exceptions of their own. That still leaves of course the OS exceptions that are mostly directly generated from the CPU and VMM hardware as interrupts. Windows has its own exception mechanisme that predates the C++ exceptions by many years. The implementation of it requires assembly and the Windows API for it is not used by many applications explicitedly because it is somewhat difficult to handle right. Supposedly your C runtime library with exception handling would intercept those exceptions though and integrate them in its own exception handling. How well that really works I wouldn't know.

 

Now the exception handling in the C runtime is compiler specific (and patent encumbered) so each C runtime implements its own exception handling architecture that is anything but binary compatible. Therefore if you mix and match different binary object files together you are pretty lucky if your exceptions will not just crash when crossing those boundaries. I'm not sure what LabVIEW all does around the Call Library Node. Because of the binary incompatibilities between exception handling and the fact that a C only interface can't even properly use C++ exceptions in a meaningful way I'm pretty sure LabVIEW doesn't jus add a standard try catch around the Call Library Node call. That would go completely havoc in most cases. What LabVIEW can do however is hooking into the Windows exception mechanisme, This interface is standardized and therefore doesn't suffer from these compiler difficulties. How much of your C++ exceptions can get caught like this depends entirely how your C++ runtime library is able to interact with the Windows exception interface. If it can translate its exceptions from and to this interface whenever it traverses from Windows API to C++ runtime and back from that when leaving the code module (your DLL) then it will work. Otherwise you get all kind of messed up behaviour.

 

C++ exceptions are great to use but get a complete fiasco if you need to write code that spans over object modules created in different C compilers or even just versions. C++ code in general suffers from this in a great way, as ABI specifications including class memory layouts are also compiler specific.

 

None of my exceptions is intended to leave the boundary of the DLL. Pretty much applies to malloc and free too.

 

What exactly do u mean by "What LabVIEW can do however is hooking into the Windows exception mechanisme" I never heard of such thing. Only know that you can reimplement standard c++ exceptions with custom code based on SEH + replace the signal handlers for machine level exceptions.

 

So what is the best option to do in CLFNs?

 

Don't catch anything you don't generate.

Keep error checking on "default".

What compiler flag to set? (I don't think it matters)

I use the default /EHsc

 

post-30811-0-22292200-1448630766.png

 

?

 

I am not willing to rewrite my code to use SEH instead of standard c++ in order to be able to do any stackwalks or stuff like that just in case I ever decide to compile the code for anything but windows.

 

 

Link to comment

They may not be meant to leave your C function but your question was about if you should catch them or any others or not.

 

As to Structured Exception Handling, that's indeed the official term for the Windows way although I have seen it used for other forms of exception handling. 

 

The Windows structured exeception handling is part of the Windows kernel and can be used from both C and C++, but Microsoft nowadays recommends to use the ISO C++ exception handling for C++ code for portability reasons. But C++ exceptions on the other hand is done in the C++ compiler itself for the most part, It may or may not be based on the Windows SEH. If it is not, you can't really mix and match them easily together.

 

Specifically this page shows that /EHsc will actually cause problems for the destruction of local objects when an SEH is triggered and that you should probably use /EHa instead to guarantee that local C++ objects are properly deallocated during stack unwinding of the SEH exception.

 

This page shows that you may have to do actual SEH transformation in order to get more context from an SEH exception in a C++ exception handler.

 

In general it seems that while C++ can catch SEH (and SEH transformation can be used to gain more detailed information about specific SEH exceptions into your C++ exception), the opposite is not true. So if LabVIEW uses SEH around the Call Library Node, which I believe it does, it will not really see (and catch) any C++ exceptions your code throws. It also can't rely on the fact that the external code may use a specific C++ exception model since that code may have been compiled with different compilers including standard C compilers which don't really support C++ exception handling at all.

 

It may be even useful to add __declspec(nothrow) to the declaration of your exported DLL functions to indicate that they do not throw exceptions to the calling code. But I'm not really sure if that makes a difference for the code generation of the function itself. It seems to be mostly for the code generation of callers who can then optimize the calling code to account for the fact that this function will never throw any exceptions. But maybe it will even cause the compiler to generate warnings if it can determine that your code could indeed cause termination by uncatched exceptions in this function. 

 

If your code is a CPP module (or C but set in the compiler options to compile as C++) the EH options will emit code that enables unwinding the stack and when you use the EHa option to also cause object destruction for SEH. However if your code isn't really C++ this indeed probably won't make any difference. C structures and variables don't have destructors so unwinding the stack won't destruct anything on the way except try to adjust the stack properly as it walks through the various stack frames.

 

As to what to catch, I would tend to only really catch what my code can generate including possible library functioins used and leave SEH alone unless I know exactly what I'm doing in a specific place. Generally catching the low level SEH exceptions is anyhow a rather complicated issue. Catching things like illegal address access, division by zero execution, and similar for more than a "Sorry you are hosed dialog" is a rather complicated endeavour. Continuing from there as if nothing has happend is generally not a good idea and trying to fix after the fact whatever has caused this, most of the times not really possible.

Link to comment

While the extcode functions marked as extern "C" can throw exceptions, doing so results in undefined behaviour. I typically handle the errors returned as MgErr by re-throwing as exceptions, and allow LabVIEW to handle those that is propagated by the events in signals.h/csignals.h ("handle" as in crash normally).

 

I see, I've done something similar previously using preprocessor definitions. Switched away from that when I started doing output value cleanup in the catch block. I suppose I could do the same automatically using your approach and variadic templates.

 

I do indeed use std::string to concatenate some error messages, I'll look into replacing them with strcat. Thanks for the tip.

 

I am not saying you should replace it. It pretty much depends on the work you do. I actually think, that if std::string starts to fail (I think the only exception it throws is connected with accessing elements outside the buffer) you have bigger fishes to fry and as long as the code runs in application level the standard containers are pretty safe. Just beware :) + if you do all the string formatting in what() called in catch (when the stack is gone) I think it's pretty safe. I do it like that myself.

 

Specifically this page shows that /EHsc will actually cause problems for the destruction of local objects when an SEH is triggered and that you should probably use /EHa instead to guarantee that local C++ objects are properly deallocated during stack unwinding of the SEH exception.

 

Nice to know. Most of the objects I use are new(ed) and stay out of the stack unwinding, and the rest of the objects are usually utility objects, that will "only" leak if not properly destroyed.

 

 

It may be even useful to add __declspec(nothrow) to the declaration of your exported DLL functions to indicate that they do not throw exceptions to the calling code. But I'm not really sure if that makes a difference for the code generation of the function itself. It seems to be mostly for the code generation of callers who can then optimize the calling code to account for the fact that this function will never throw any exceptions. But maybe it will even cause the compiler to generate warnings if it can determine that your code could indeed cause termination by uncatched exceptions in this function. 

 

Microsoft implemented nothrow first in VS2015. Till then it is _NOTHROW. It does indeed matter for compiler, as a

void func() throw()
{}

will only result in the compiler not to call the unhandled exception handler, which I think is not good idea. Better to exit with some unhandled exception handler than some fireball.

 

This is copy from MS page

 

https://msdn.microsoft.com/en-us/library/wfa0edys(v=vs.120).aspx

 

However, if an exception is thrown out of a function marked throw(), the Visual C++ compiler will not call unexpected (see unexpected (CRT) and unexpected (<exception>) for more information). If a function is marked with throw(), the Visual C++ compiler will assume that the function does not throw C++ exceptions and generate code accordingly. Due to code optimizations that might be performed by the C++ compiler (based on the assumption that the function does not throw any C++ exceptions) if a function does throw an exception, the program may not execute correctly.

 

Thx for answer

Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.