Subject Re: [Firebird-Architect] Re: External procedures: implementation
Author Vlad Horsun
> >>A C++ API is okay for Java and .NET that the project will distribute
> >>the engines. But C++ programmers will need to use the same compiler
> >>vendor/version or compile everything all the server.
> >>The API is very small. Why not use C and distribute the C++ bridge
> >>for external engine writers?
> >>
> >>
> >
> >I hope that Vlad can tell something more here - he was able to create
> >a Delphi-based external procedure with this API.

I found that pure-virtual classes with all stdcall methods worked
fine at least between MSVC 7.1 and Delphi 6 and Delphi 9. If it will
not work on another platform or compiler we can implement plain C
functions on the top of this classes and export it. Something like

class X
virtual Y stdcall A(...) = 0;

Y stdcall X_A(void *ptrX, ...)
X *x = (X*)ptrX;
return x->A(...);

> In general, it isn't possible to fix C++ from different compilers. On
> Solaris, for example, it isn't possible to link modules compiled by the
> Sun Forte compiler and the gnu C++ compiler, and if I remember
> correctly, neither can dynamically load libraries produced by the
> other. In practice, any code that is going to be executed within the
> engine must be compiled by the same compiler as the engine itself.

I hope this is not so strong now.

> For engine extensions, I don't have any problem defining the interface
> using C++ classes. If somebody wants to produce an external procedure
> facility in Delphi, for example, it's not big deal to expect them to
> code a tiny interface modules to handle the calling sequence coming in


> and throwing the proper exceptions on the way out.

Oh no, please, exceptions must not cross unit boundaries. There are
mechanism to carry exception information (better than status vector, i hope).
So - all exceptions must be handled in compilation unit which raised it

> I think there's
> widespread agreement that the public APIs should be language neutral,
> but I don't think this should be extended to facilities to run inside
> the engine.


> It would be nicer if you threw OSRIExceptions than messed around with
> status vectors which seem oh, so, 20 years ago.

I replaced ISC_STATUS with class ErrorObject:

class ErrorObject
virtual void _stdcall clear() = 0;
virtual bool _stdcall addCode(const long) = 0;
virtual bool _stdcall addString(const char*, const long) = 0;

class ExternalFunction: public ExternalPointer
virtual void _stdcall execute(ErrorObject*, int n, PARAMDSC**, PARAMDSC* = 0) = 0;

Engine (Firebird) has its own implementation of class ErrorObject and pass
instance of it in every call of external classes. When external method catch its
own exception it call ErrorObject::addCode and\or ErrorObject::addString (as
many times as needed) to pass error information. ErrorObject implementation
copied this information into engine memory and, after call returns, throws an
Firebird native exception:

a) Firebird side:

ExternalFunction* fun = ...
ErrorImpl err;
ResultSet* rs = fun->execute(&err, n, inputs, output);
err.check(); // here we throws an exception if needed

b) external engine (Delphi) side:

procedure TExternalFunctionImpl.Execute(Err: TErrorObject; n: Integer;
var Inputs: PParamDsc; var RetVal: TParamDsc);
msg : String;
on E : Exception do
msg := Format('[%s] %s', [E.ClassName, E.Message]);

Err.addCode(isc_external_exception); // any isc_xxx code
Err.addString(PChar(msg), fb_string_ascii);

> >>Why ExternalResource class exist? Why create a "close" method that
> >>we know that will do "delete this"?
> >>Use a virtual destructor.

This method called destructor in real implementations. Therefore
i renamed it to 'release' ;)

> >Not only "delete this". If I open a connection to another database
> >(Oracle for example) and start pumping records into Firebird, I need
> >some method to tell me "now you can stop and disconnect". I think this
> >would be possible to achieve with virtual destructor too, but explicit
> >method call seems to be logical here.


> I think a mechanism for registering callbacks would be a better way to
> handle this. Perhaps a two level handshake, one when the external
> procedure facility is loaded to register call back hooks and another to
> invoke a procedure.

This is how it worked in prototype.

> Or should it be per procedure?

I don't think it is needed