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
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