propagation of exceptions over module/language boundaries

B

ben mitch

Hi

I hope you'll see this cross-post (c/c++) as appropriate. I also admit
immediately that the question *may* turn out to be
compiler-/os-specific, in which case I apologize. But I wonder if
there's an underlying truth.

We are writing a (cross-platform) 'framework' application (in C++, as it
happens) that allows users to author 'plugins' (as shared library
modules). Since the lifetime of plugins is expected to be long, and the
framework may be updated using a later compiler version, we have chosen
to offer a C interface to those plugins to avoid issues with changing
ABIs (as far as I understand it). Plugins are expected to export a
single undecorated function, Event(EventData* data), and may call back
to the framework through a number of undecorated functions exported from
an API module, 'api', also authored in C++. Commonly, then, the
framework will load a plugin, call its Event() function, and that
function will call one of these callback functions on the API.

At this point I have to own up and say I don't fully understand linking
or calling conventions, so when I say we "offer a C interface" what I
mean is that api is compiled to export its functions undecorated (sort
of with the use of extern "C", though actually there's some other stuff
going on that I'd rather not get into so I'm hoping that's a
sufficiently good approximation - api is a C++ module but its exports
are undecorated). Also, plugins export their Event() function as extern "C".

Motivation: It would be convenient if the api could throw an exception
that the framework would recognise. It would pass painlessly up through
any user code without the user having to do anything, and the framework
could find out what happened and generate a useful error report. The
alternative is that we enforce that all API functions do not throw and
instead return error codes - the onus is then on the plugin author to
catch the error and return it to the framework (or, if they are
authoring in C++, they could throw an exception themselves, but i'm not
sure if that's not the same question again).

Now, 'api' is authored in C++, and so is the framework, but user plugins
may be authored in C or C++ (or, in fact, any other language that can
meet the requirements of the interface). So my initial thinking was that
we couldn't use exceptions, much as they're great imho. However, in my
current dev copy, i'm finding exceptions work fine. My worry is that in
my current dev copy i'm compiling the framework, the api, *and* the test
plugin, all with the same compiler (cl v14, win xp), which may be hiding
problems that would arise once we go more mix and match. In addition,
I'm writing this particular plugin in C++, so everything is C++ save the
actual exported function declarations.

So... the question is, should we avoid throwing exceptions across these
module boundaries, or is it ok to do so? I could actually test this to
some extent by installing some other compilers, but I'm thinking that
that's not going to be a thorough test anyway, so I'd rather reach some
understanding. For instance...

* framework loads "plugin"
* framework calls plugin::Event()
* plugin calls api::Sum()
* error condition occurs in Sum()
* Sum() throws exception of type X

or, to put that another way, the call stack looks like:

framework::main() (C++)
--> plugin::Event() (C)
--> api::Sum() (C++)
throw X;

Now, if X is, say, an int, will it pass safely back up through "plugin"
in all cases, even if plugin is compiled with an (old, perhaps) C
compiler that is strictly not aware of exceptions? Is this a
compiler-/os-specific question? what if X is a C struct defined in api.h?

From what I have learned about exceptions and how they are implemented,
I don't immediately see a problem with passing one 'through'
exception-unaware code in this way. There will be no handlers on the
stack associated with the C-code, but I'm supposing that this means that
control will just pass (effectively) directly from the "throw" statement
in api to the "catch" block in framework. But I'm worried that the way
the stack is arranged to accomodate the two cross-module-boundary calls
will somehow interfere with this mechanism. I am also worried that the
type of the object will not be recognised when it reaches framework - I
am fine with sticking with const char* as an exception type if that's
the only reliable option, but a struct with a code, a message, and some
source information would be preferable, of course.

Phew. I hope that's clear.

Thanks in advance for any comments.
Ben Mitch
 
J

jacob navia

ben said:
Hi

I hope you'll see this cross-post (c/c++) as appropriate. I also admit
immediately that the question *may* turn out to be
compiler-/os-specific, in which case I apologize. But I wonder if
there's an underlying truth.

We are writing a (cross-platform) 'framework' application (in C++, as it
happens) that allows users to author 'plugins' (as shared library
modules). Since the lifetime of plugins is expected to be long, and the
framework may be updated using a later compiler version, we have chosen
to offer a C interface to those plugins to avoid issues with changing
ABIs (as far as I understand it). Plugins are expected to export a
single undecorated function, Event(EventData* data), and may call back
to the framework through a number of undecorated functions exported from
an API module, 'api', also authored in C++. Commonly, then, the
framework will load a plugin, call its Event() function, and that
function will call one of these callback functions on the API.

At this point I have to own up and say I don't fully understand linking
or calling conventions, so when I say we "offer a C interface" what I
mean is that api is compiled to export its functions undecorated (sort
of with the use of extern "C", though actually there's some other stuff
going on that I'd rather not get into so I'm hoping that's a
sufficiently good approximation - api is a C++ module but its exports
are undecorated). Also, plugins export their Event() function as extern
"C".

Motivation: It would be convenient if the api could throw an exception
that the framework would recognise. It would pass painlessly up through
any user code without the user having to do anything, and the framework
could find out what happened and generate a useful error report. The
alternative is that we enforce that all API functions do not throw and
instead return error codes - the onus is then on the plugin author to
catch the error and return it to the framework (or, if they are
authoring in C++, they could throw an exception themselves, but i'm not
sure if that's not the same question again).

Now, 'api' is authored in C++, and so is the framework, but user plugins
may be authored in C or C++ (or, in fact, any other language that can
meet the requirements of the interface). So my initial thinking was that
we couldn't use exceptions, much as they're great imho. However, in my
current dev copy, i'm finding exceptions work fine. My worry is that in
my current dev copy i'm compiling the framework, the api, *and* the test
plugin, all with the same compiler (cl v14, win xp), which may be hiding
problems that would arise once we go more mix and match. In addition,
I'm writing this particular plugin in C++, so everything is C++ save the
actual exported function declarations.

So... the question is, should we avoid throwing exceptions across these
module boundaries, or is it ok to do so? I could actually test this to
some extent by installing some other compilers, but I'm thinking that
that's not going to be a thorough test anyway, so I'd rather reach some
understanding. For instance...

* framework loads "plugin"
* framework calls plugin::Event()
* plugin calls api::Sum()
* error condition occurs in Sum()
* Sum() throws exception of type X

or, to put that another way, the call stack looks like:

framework::main() (C++)
--> plugin::Event() (C)
--> api::Sum() (C++)
throw X;

Now, if X is, say, an int, will it pass safely back up through "plugin"
in all cases, even if plugin is compiled with an (old, perhaps) C
compiler that is strictly not aware of exceptions? Is this a
compiler-/os-specific question? what if X is a C struct defined in api.h?

From what I have learned about exceptions and how they are implemented,
I don't immediately see a problem with passing one 'through'
exception-unaware code in this way. There will be no handlers on the
stack associated with the C-code, but I'm supposing that this means that
control will just pass (effectively) directly from the "throw" statement
in api to the "catch" block in framework. But I'm worried that the way
the stack is arranged to accomodate the two cross-module-boundary calls
will somehow interfere with this mechanism. I am also worried that the
type of the object will not be recognised when it reaches framework - I
am fine with sticking with const char* as an exception type if that's
the only reliable option, but a struct with a code, a message, and some
source information would be preferable, of course.

Phew. I hope that's clear.

Thanks in advance for any comments.
Ben Mitch

I have studied this problem extensively in the context of dynamically
generated C code. I will not bore you with the details, but solving
the problem you see is not easy at all.

1) Using Microsoft compiler will work if all the "clients" use also
the same compiler AND the same version. With different versions of
the compiler things could change and you would experience mysterious
crashes.

2) Avoid gcc if possible. There is no documentation almost about the
exception framework and the documentation available is highly
misleading. In any case if you have to use gcc you will want to
ALWAYS use gcc ("clients" and API server) and specially, the SAME
version of gcc. Do remember that gcc changes its behaviour from
version to version without any announcement, since all this is
not really documented.
 
K

Keith Thompson

ben mitch said:
I hope you'll see this cross-post (c/c++) as appropriate. I also admit
immediately that the question *may* turn out to be
compiler-/os-specific, in which case I apologize. But I wonder if
there's an underlying truth.
[SNIP]

Motivation: It would be convenient if the api could throw an exception
that the framework would recognise. It would pass painlessly up
through any user code without the user having to do anything, and the
framework could find out what happened and generate a useful error
report. The alternative is that we enforce that all API functions do
not throw and instead return error codes - the onus is then on the
plugin author to catch the error and return it to the framework (or,
if they are authoring in C++, they could throw an exception
themselves, but i'm not sure if that's not the same question again).
[SNIP]

I think this is really a question for comp.lang.c++, not for
comp.lang.c (even though it does involve both languages. The C+
standard defines features for interfacing to C, but C doesn't define
any features for interfacing to C++. Furthermore, C says nothing
about C++-style exceptions.

I suspect that exception propagation across C code is system-specific,
but the folks in comp.lang.c++ can probably give you more information
on that point.
 
E

Erik Wikström

Hi

I hope you'll see this cross-post (c/c++) as appropriate. I also admit
immediately that the question *may* turn out to be
compiler-/os-specific, in which case I apologize. But I wonder if
there's an underlying truth.

We are writing a (cross-platform) 'framework' application (in C++, as it
happens) that allows users to author 'plugins' (as shared library
modules).

If you want it to truly be cross-platform you can not allow exceptions
to pass through to/from the plugins. The reason is simple: there is no
standardised way of doing this. The only (?) standardised calling
convention which will be available on multiple platforms is the C
calling convention and it does not support exceptions.
 
E

Erik Wikström

If you want it to truly be cross-platform you can not allow exceptions
to pass through to/from the plugins. The reason is simple: there is no
standardised way of doing this. The only (?) standardised calling
convention which will be available on multiple platforms is the C
calling convention and it does not support exceptions.

Sorry, I should mention that it is possible, but you would have to
require that all the code (framework and plugins) is compiled with the
same compiler and you will have to use a C++ API.
 
J

jacob navia

Erik said:
If you want it to truly be cross-platform you can not allow exceptions
to pass through to/from the plugins. The reason is simple: there is no
standardised way of doing this. The only (?) standardised calling
convention which will be available on multiple platforms is the C
calling convention and it does not support exceptions.

This is not true. Most C++ compilers support C and will allow C and
C++ to coexist peacefully. Problems arise when you mix different
compilers and versions or you want to generate code dynamically.
 
E

Erik Wikström

This is not true. Most C++ compilers support C and will allow C and
C++ to coexist peacefully. Problems arise when you mix different
compilers and versions or you want to generate code dynamically.

What is not true? To my knowledge there are two ways for C and C++ to
coexist: either you compile the C code as C++ (in which case it is no
longer C), or you use extern "C", in which case you use the C calling
convention.

Further mote it is true that there is no standardised cross-platform way
of propagating exceptions. An, as far as I know, the C calling
convention is the only wide-spread standardised calling convention.
 
J

jacob navia

Erik said:
What is not true? To my knowledge there are two ways for C and C++ to
coexist: either you compile the C code as C++ (in which case it is no
longer C), or you use extern "C", in which case you use the C calling
convention.

Calling conventions have nothing to do here. You are confusing calling
convention (function call interface) with propagating the exceptions
(i.e. letting a throw pass through your code), what is completely
different.
Further mote it is true that there is no standardised cross-platform way
of propagating exceptions.

Yes. That is why I said that if you stick to the same
compiler and version it will work.
An, as far as I know, the C calling
convention is the only wide-spread standardised calling convention.

Again, calling conventions are NOT the issue here.

jacob
 
I

Ian Collins

jacob said:
This is not true. Most C++ compilers support C and will allow C and
C++ to coexist peacefully.

They do, provided you use C linkage functions (extern "C" in the C++
world). Exceptions are alien to C, so you simply can't propagate
exceptions through a C function.
Problems arise when you mix different
compilers and versions or you want to generate code dynamically.
A problem in C++, but in C? The C ABI is determined by the platform,
not the compiler (at least in the Linux/Unix world). If a compiler used
a different ABI, how could it link to system libraries?
 
J

jacob navia

Ian said:
jacob navia wrote: [snip]
This is not true. Most C++ compilers support C and will allow C and
C++ to coexist peacefully.

They do, provided you use C linkage functions (extern "C" in the C++
world). Exceptions are alien to C, so you simply can't propagate
exceptions through a C function.

Yes you can. I have done this under gcc and msvc, and I think
all C++ compilers will generate compatible code.
A problem in C++, but in C? The C ABI is determined by the platform,
not the compiler (at least in the Linux/Unix world). If a compiler used
a different ABI, how could it link to system libraries?

If you use different compilers, the throw machinery ill be different
and passing throws will just not work.
 
I

Ian Collins

jacob said:
Ian said:
jacob navia wrote: [snip]
This is not true. Most C++ compilers support C and will allow C and
C++ to coexist peacefully.

They do, provided you use C linkage functions (extern "C" in the C++
world). Exceptions are alien to C, so you simply can't propagate
exceptions through a C function.

Yes you can. I have done this under gcc and msvc, and I think
all C++ compilers will generate compatible code.
More by luck than anything else. The mechanics may work, but it's a
very dangerous thing to do because the C code will not be exception
aware, let alone safe.

Like all things undefined, the next compiler release could break your code.
If you use different compilers, the throw machinery ill be different
and passing throws will just not work.
Double check my post, I said "The C ABI is determined by the platform".
C doesn't have throw machinery.
 
J

jacob navia

Ian said:
jacob said:
Ian said:
jacob navia wrote: [snip]
This is not true. Most C++ compilers support C and will allow C and
C++ to coexist peacefully.
They do, provided you use C linkage functions (extern "C" in the C++
world). Exceptions are alien to C, so you simply can't propagate
exceptions through a C function.
Yes you can. I have done this under gcc and msvc, and I think
all C++ compilers will generate compatible code.
More by luck than anything else. The mechanics may work, but it's a
very dangerous thing to do because the C code will not be exception
aware, let alone safe.


Who cares?

C doesn't have to know any exceptions!

The throw will pass through the C stack part until it finds a catch.
Most compilers generate a set of tables describing the stack layout
of each function so that the throw mechanism can walk the stack.

If you compile with the same compiler, those tables get generated for
the C code and you are in business.
Like all things undefined, the next compiler release could break your code.

I said that explicitely in my FIRST message. You need to keep
compatible versions of everything:

o The tables generated by the compiler
o The throw run time mechanism

Double check my post, I said "The C ABI is determined by the platform".
C doesn't have throw machinery.

You just do not know what the issue here is. Reread the OP
message, and my answers. Obviously C doesn't have and doesn't
need any throw machinery. That is not the issue here.
 
I

Ian Collins

jacob said:
Ian said:
jacob said:
Ian Collins wrote:
jacob navia wrote:
[snip]
This is not true. Most C++ compilers support C and will allow C and
C++ to coexist peacefully.
They do, provided you use C linkage functions (extern "C" in the C++
world). Exceptions are alien to C, so you simply can't propagate
exceptions through a C function.

Yes you can. I have done this under gcc and msvc, and I think
all C++ compilers will generate compatible code.
More by luck than anything else. The mechanics may work, but it's a
very dangerous thing to do because the C code will not be exception
aware, let alone safe.
Who cares?
Anyone who wants to write reliable, portable code.
C doesn't have to know any exceptions!
Isn't that what I said?
The throw will pass through the C stack part until it finds a catch.
Most compilers generate a set of tables describing the stack layout
of each function so that the throw mechanism can walk the stack.
Yes, so a C function calling a C++ function that throws will return at
the point of the call, bypassing any clean up and possibly leaving the
applications state in an undefined state. For example:

void f()
{
allocateSomething(); /* 1 */

callFunctionThatThrows(); /* 2 */

deallocateSomthing(); /* 3 */
}

The function will "return" at line 2, line 3 will never be called.
If you compile with the same compiler, those tables get generated for
the C code and you are in business.
That forces you to compile C as C++. C isn't C++.
I said that explicitely in my FIRST message. You need to keep
compatible versions of everything:

o The tables generated by the compiler
o The throw run time mechanism
You also appear to be implying that the C code be compiled as C++.
You just do not know what the issue here is. Reread the OP
message, and my answers.
To quote from the OP:

framework::main() (C++)
--> plugin::Event() (C)
--> api::Sum() (C++)
throw X;

Which is exactly what the issue we should be discussing.
Obviously C doesn't have and doesn't
need any throw machinery. That is not the issue here.
Then why did you say mention throw machinery in response to a question
about the C ABI?
 
B

ben mitch

It seems to me a fair summary, then, to say it's unwise for us to go
this way. It might work under some conditions, but with no restrictions
on compiler or even language..., and especially given my inexperience in
this area. I guess we'll elect to use error codes instead, I can come to
terms with that.

Thanks, all, for your time and your helpful comments.
 
J

jacob navia

Ian Collins wrote:
[snip]

Mr Collins

1) The question here is if C and sit in the middle between a catch in
C++ and a throw in C++. My answer is YES.

2) If you leave objects without proper cleanup in your C code is
another problem. HINT: Do not do that in your C code. Just never
call a C++ function that can throw if you need to cleanup.

3) In the case of the original poster of this thread everything is
done at compile time. I have solved a more difficult case when
in the framework of a JIT (Just in time compiler) you generate
code dynamically. In the OP case there is no need to modify
the C code. Obviously if any throw happens, cleanup may never
happen. That is another problem. The question was if this is
at all possible.

I think your opinion and mine are well known. I stop this thread
then.
 
I

Ian Collins

jacob said:
Ian Collins wrote:
[snip]

Mr Collins

1) The question here is if C and sit in the middle between a catch in
C++ and a throw in C++. My answer is YES.
And mine is NO. The result is undefined.

My apologies to the c.l.c readers, but here's an example that
illustrates this:

x.cc:

extern "C" void f();
extern "C" void functionThatThrows() { throw 42; }

int main ()
{
try { f(); }
catch(...) {}

return 0;
}

x.c:

#include <stdio.h>

void functionThatThrows();

void f()
{
puts("allocateSomething");
functionThatThrows();
puts("deallocateSomthing");
}

With compiler A:

c99 -c -o xc.o /tmp/x.c
CC /tmp/x.cc xc.o

../a.out
allocateSomething

With compiler B:

gcc -c -o xc.o /tmp/x.c
g++ /tmp/x.cc xc.o

../a.out
allocateSomething
terminate called after throwing an instance of 'int'
Abort (core dumped)
2) If you leave objects without proper cleanup in your C code is
another problem. HINT: Do not do that in your C code. Just never
call a C++ function that can throw if you need to cleanup.
That assumes you know you are calling a C++ function.
I think your opinion and mine are well known. I stop this thread
then.
Indeed. One of us understands the meaning of undefined behaviour and
how to write portable code.
 
J

James Kanze

jacob said:
Ian Collins wrote:
[snip]
Mr Collins
1) The question here is if C and sit in the middle between a
catch in C++ and a throw in C++. My answer is YES.
And mine is NO. The result is undefined.

According to the standard. One thing an implementation can do
with undefined behavior is define it; all of the C++ compilers I
know do define it.

Not that it's much use, of course. As you correctly pointed
out, code written in C will not be exception safe; it counts on
the guarantee (valid in C) that if you call a function, it
either returns, or the process is aborted in some way.
 
L

Lew Pitcher

In comp.lang.c, James Kanze wrote:
[snip]
Not that it's much use, of course. As you correctly pointed
out, code written in C will not be exception safe; it counts on
the guarantee (valid in C) that if you call a function, it
either returns, or the process is aborted in some way.

Hmmm....... So, what does longjmp() do? If you call a function that calls
longjmp(), does the called function return? Or does longjmp() abort the
process? (assuming that setjmp() was properly invoked prior to the call to
the function)

--
Lew Pitcher

Master Codewright & JOAT-in-training | Registered Linux User #112576
http://pitcher.digitalfreehold.ca/ | GPG public key available by request
---------- Slackware - Because I know what I'm doing. ------
 
I

Ian Collins

James said:
jacob said:
Ian Collins wrote:
[snip]
Mr Collins
1) The question here is if C and sit in the middle between a
catch in C++ and a throw in C++. My answer is YES.
And mine is NO. The result is undefined.

According to the standard. One thing an implementation can do
with undefined behavior is define it; all of the C++ compilers I
know do define it.
True, but we were talking portable solutions here, so no is the only
safe answer.

Every compiler I use has its own exception propagation mechanism,
ranging from the elegant to the gross. As an internal implementation
detail, this is likely to change between releases. Thus even code built
and "working" with the same tool chain is likely to break if the tools
are upgraded.
 
J

James Kanze

James said:
jacob navia wrote:
Ian Collins wrote:
[snip]
Mr Collins
1) The question here is if C and sit in the middle between a
catch in C++ and a throw in C++. My answer is YES.
And mine is NO. The result is undefined.
According to the standard. One thing an implementation can do
with undefined behavior is define it; all of the C++ compilers I
know do define it.
True, but we were talking portable solutions here, so no is the only
safe answer.
Every compiler I use has its own exception propagation
mechanism, ranging from the elegant to the gross. As an
internal implementation detail, this is likely to change
between releases. Thus even code built and "working" with the
same tool chain is likely to break if the tools are upgraded.

Yes. It's clear that all of the code must have been compiled
with the same compiler (same version, and possibly even with the
same options).

If all the code is your application, you should have no trouble
mastering this, and I wouldn't worry too much about portability.
For things like plugins, of course, it's totally a different
matter, and if your plugin uses a C ABI, you certainly cannot
count on exceptions propagating through it (and if it uses a C++
ABI, of course, the suppliers of the plugin will have to compile
with the same compiler---including version and options enought
to ensure binary compatibility---or the plugin won't work).
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top