Thread: [ooc-compiler] PROPOSAL: Interface to C++ objects and methods
Brought to you by:
mva
|
From: Stewart G. <sgr...@ii...> - 2006-01-12 09:10:31
|
Hi Folks,
I'm interested in being able to use existing C++ APIs from Oberon-2.
Currently, oo2c has a limited ability to call methods of C++ objects.
When one declares a record with the VTABLE flag, oo2c builds a C++-style
virtual method table for all instantiated objects, and calls methods of
that object via VTABLE dispatch. This means (for example) that you can
call methods of COM interfaces, and even implement your own COM objects.
This is fine if one follows a design methodology like COM, which ensures
that there is a language-independent binary interface to your API. COM
uses abstract interfaces and VTABLE method dispatch. It also has a
language-independent type mechanism (ie. IUnknown::QueryInterface).
Unfortunately, most C++ coders seem completely uninterested in language
interoperability. The biggest obstacle with existing C++ APIs is the use
of static (ie. non-virtual) methods. Its only really possible to call
these methods from a C++ compiler, since the symbol names are mangled to
include type information.
The approach that I'm proposing is to:
1) Introduce a [STATIC] method flag. This causes calls to methods to be
dispatched statically, rather than using type-descriptor lookup.
2) Write (by hand, or automatically) a FOREIGN implementation that can
be processed by the C++ compiler. This dispatches method calls to the
C++ objects.
I'll try to illustrate this by means of an example. Suppose we have a
simple C++ library:
-- Foreign.h --
class T {
public:
virtual void Virtual(void);
void Static(void);
};
-- end Foreign.h --
-- Foreign.cpp --
#include <stdio.h>
#include "Foreign.h"
void T::Virtual(void) {
printf("T::Virtual\n");
}
void T::Static(void) {
printf("T::Static\n");
}
-- end Foreign.cpp --
Using a VTABLE declaration, I can call method "Virtual", but not method
"Static". Now OOC already has a concept of static methods. A method is
called statically (ie. without using a type-descriptor method lookup) if
it is known not to be overridden, or if it is a special predefined
method like INIT. I'm proposing to add a "STATIC" procedure flag, which
specifies that a method is to be treated as a static method (ie. it has
procClass = staticMethod) rather than a virtual method. The address of
the method is always based on an objects static type.
This allows us to write:
-- ForeignStatic.Mod --
MODULE ForeignStatic [ FOREIGN "C"; LINK FILE "ForeignStatic.cpp"; LIB
"foreign"; LIB "stdc++" END ];
TYPE
TDesc* = RECORD [VTABLE] END;
T* = POINTER TO TDesc;
PROCEDURE (t : T) Virtual*;
PROCEDURE (t : T) [STATIC] Static*;
PROCEDURE NewT* () : T;
END ForeignStatic.
-- end ForeignStatic.Mod --
Now to make things work, we need the FOREIGN implementation. For the
above library, it looks like this:
-- ForeignStatic.cpp --
#include "Foreign.h"
extern "C" {
#include <ForeignStatic.d>
#include <__oo2c.h>
#include <setjmp.h>
void ForeignStatic__TDesc_Virtual(ForeignStatic__T t) {
((T*) t)->Virtual();
}
void ForeignStatic__TDesc_Static(ForeignStatic__T t) {
((T*) t)->Static();
}
void OOC_ForeignStatic_init(void) {
return;
;
}
void OOC_ForeignStatic_destroy(void) {
}
ForeignStatic__TDesc* ForeignStatic__NewT(void) { return
(ForeignStatic__TDesc *) new T(); }
}
/* --- */
-- end ForeignStatic.cpp --
To generate this, I compiled a "stub" module using oo2c, and manually
inserted a few things.
- Included the header for the C++ library.
- The "extern C" is necessary so that the C++ compiler does not mangle
the symbol names.
- Inserted dispatch statements in any methods that are to be called
statically. Actually, "Virtual" is alreadly dispatched by OOC through
the VTABLE, but I could have allowed C++ to do the virtual dispatch here
by declaring it STATIC.
- Inserted a constructor.
This allows one to do this sort of thing:
-- TestForeignStatic.Mod --
MODULE TestForeignStatic;
IMPORT ForeignStatic;
PROCEDURE TestForeignStatic1;
VAR t : ForeignStatic.T;
BEGIN
t := ForeignStatic.NewT();
t.Virtual;
t.Static;
END TestForeignStatic1;
BEGIN
TestForeignStatic1;
END TestForeignStatic.
-- end TestForeignStatic.Mod --
That is, I can call virtual or static methods of objects as if they are
native objects. Note that I don't need to change the calling conventions
used within OOC. It already handles virtual and static dispatch. The
wrapper functions simply redirect the method dispatch using the host
compiler.
Anyway, with a bit of fiddling I managed to get it to work. The STATIC
flag was easy to implement, but it would be nice if this process could
be streamlined somehow. Ideally, the FOREIGN wrapper implementation
should be generated automatically by OO2C. This should not be hard to do
- If we can get some consensus regarding how it should work I'm willing
to have a go at it. Since the wrapper requires the use of
implementation-specific symbol names and structures it would make sense
for the compiler to provide this feature, rather than an external tool.
1) There would need to be a declaration that this is a special type of
FOREIGN module (eg. "WRAPPER", or "PROXY"?). Records declared within are
assumed to have no type descriptors. Stub procedures are generated that
dispatch methods in the host ("C++") language.
2) There should be a list of headers to include at the top of the object
file.
3) For each record type, we need an optional "link name" saying what the
proxy C++ type is called. This would be used to generate the type casts
in the wrapper methods.
4) For each method, we need an optional "link name" saying what the
method of the proxy C++ type is called. In the case of an overloaded
method, there will be multiple Oberon-2 names that map to the same C++ name.
5) There would need to be a way of declaring constructors which would be
dispatched via new().
What do people think about these issues? Any suggestions, or violent
objections? ;-)
Cheers,
Stewart
|
|
From: Michael v. A. <mic...@gm...> - 2006-01-18 07:37:27
|
On 12/01/06, Stewart Greenhill <sgr...@ii...> wrote: > > Hi Folks, > > I'm interested in being able to use existing C++ APIs from Oberon-2. [...] Because I am still a C++ illiterate, I cannot comment on the merits of your proposal. My suggestion is that we create a branch in the oo2c CVS that incorporates your changes and that serves as a public meeting point for work on the extension. -- mva |
|
From: Stewart G. <sgr...@ii...> - 2006-01-18 13:50:27
|
Michael van Acken wrote: > [...] > My suggestion is that we create a branch in the oo2c CVS that > incorporates your changes and that serves as a public meeting > point for work on the extension. That sounds like a good approach, since I'll probably have to change quite a few things to get it working. I haven't used CVS branches before. Do you know how it works at sourceforge? Can anyone create a branch, or does one have to be the admin? Cheers, Stewart |
|
From: Michael v. A. <mic...@gm...> - 2006-01-18 15:52:27
|
On 18/01/06, Stewart Greenhill <sgr...@ii...> wrote: > > Michael van Acken wrote: > > > [...] > > My suggestion is that we create a branch in the oo2c CVS that > > incorporates your changes and that serves as a public meeting > > point for work on the extension. > > That sounds like a good approach, since I'll probably have to change > quite a few things to get it working. I haven't used CVS branches > before. Do you know how it works at sourceforge? Can anyone create a > branch, or does one have to be the admin? In theory, everyone should be able to do this. But having created quite some branches lately, let me have a go. I need a name for the beast, though. Is "oo2c-branch-cpp" ok with you? Or would you prefer something more descriptive? -- mva |
|
From: Stewart G. <sgr...@ii...> - 2006-01-19 04:27:43
|
Hi Michael, Michael van Acken wrote: > On 18/01/06, Stewart Greenhill <sgr...@ii...> wrote: >>Michael van Acken wrote: >>>[...] >>>My suggestion is that we create a branch in the oo2c CVS that >>>incorporates your changes and that serves as a public meeting >>>point for work on the extension. >> >>That sounds like a good approach, since I'll probably have to change >>quite a few things to get it working. I haven't used CVS branches >>before. Do you know how it works at sourceforge? Can anyone create a >>branch, or does one have to be the admin? > > In theory, everyone should be able to do this. But having created > quite some branches lately, let me have a go. I need a name for > the beast, though. Is "oo2c-branch-cpp" ok with you? Or would > you prefer something more descriptive? OK. Please go ahead with it. That name is fine with me. Cheers, Stewart |
|
From: Michael v. A. <mic...@gm...> - 2006-01-19 07:37:09
|
On 19/01/06, Stewart Greenhill <sgr...@ii...> wrote: > > [...] OK. Please go ahead with it. That name is fine with me. I've done a cvs tag -b oo2c-branch-cpp ooc2 You need to do cvs co -r oo2c-branch-cpp ooc2 to get a working copy in the (new) directory ooc2 that is "tuned" to this branch. You can recognize it by its "Tag" file: $ cat ooc2/CVS/Tag Too2c-branch-cpp All checkins beneath the new directory only affect the branch, not the CVS head. For more information on CVS and branching I recommend the "CVS Branch and Tag Primer" at http://www.psc.edu/~semke/cvs_branches.html -- mva |
|
From: Stewart G. <sgr...@ii...> - 2006-01-19 07:49:19
|
Michael van Acken wrote: >[...] > All checkins beneath the new directory only affect the branch, > not the CVS head. > > For more information on CVS and branching I recommend the > "CVS Branch and Tag Primer" at > http://www.psc.edu/~semke/cvs_branches.html Thanks. I'll give it a try. Cheers, Stewart |
|
From: Stewart G. <sgr...@ii...> - 2006-01-18 13:51:02
|
Hi Frank, >> I'm interested in being able to use existing C++ APIs from Oberon-2. > > It would certainly broaden the options available. There are some C++ > APIs I have an interest in. Did you have anything special in mind? I'm particularly interested in GUI/application toolkits. There are lots of these around for C++ (eg. wxWidgets, FLTK, Qt, JUCE), but only relatively few for C. >> Currently, oo2c has a limited ability to call methods of C++ objects. >> When one declares a record with the VTABLE flag, oo2c builds a >> C++-style virtual method table for all instantiated objects, and calls >> methods of that object via VTABLE dispatch. This means (for example) >> that you can call methods of COM interfaces, and even implement your >> own COM objects. >> >> This is fine if one follows a design methodology like COM, which >> ensures that there is a language-independent binary interface to your >> API. COM uses abstract interfaces and VTABLE method dispatch. It also >> has a language-independent type mechanism (ie. >> IUnknown::QueryInterface). Unfortunately, most C++ coders seem >> completely uninterested in language interoperability. The biggest >> obstacle with existing C++ APIs is the use of static (ie. non-virtual) >> methods. Its only really possible to call these methods from a C++ >> compiler, since the symbol names are mangled to include type information. >> >> The approach that I'm proposing is to: >> >> 1) Introduce a [STATIC] method flag. This causes calls to methods to >> be dispatched statically, rather than using type-descriptor lookup. >> >> 2) Write (by hand, or automatically) a FOREIGN implementation that can >> be processed by the C++ compiler. This dispatches method calls to the >> C++ objects. > > > Would it not be simpler to have OOC mangle the symbol names the same way > as the C++ compiler? I realise that is compiler-dependant and I believe > g++ broke ABI recently by changing the mangling algorithm. Even so the > algorithms should be reasonably easy to discover (especially for g++). > There would remain the issue of telling OOC which algorithm to use. I suppose that might work. Within the current oo2c implementation it would need to be possible to access the mangled symbols from C code. I'm not sure if that's possible - presumably the compiler writers take steps to prevent the mangled names colliding with the mangled symbols. Certainly, it would be possible at the asm/linker level. There's another advantage of the wrapper approach, which is that one does not need to know about the calling conventions of the wrapped function in order to call it from client modules. At least under Windows, there are different calling conventions possible (eg. stdcall, cdecl, fastcall). It should even work for macros and functions that would normally be "inlined". There are a few other issues that I think could also be addressed within the same wrapper framework. One problem that I've hit before is that many APIs (even "C" APIs) pass small records by value. For example, GSL does this for complex numbers, and OpenCV does it for image dimensions. In the past I've solved this by manually writing a wrapper function that accepts a record by reference, and then dereferences the pointer in the function call. It would be nice to get an easier solution to this problem too. Cheers, Stewart |
|
From: Stewart G. <sgr...@ii...> - 2006-01-19 04:29:55
|
Stewart Greenhill wrote:
>> Would it not be simpler to have OOC mangle the symbol names the same
>> way as the C++ compiler? I realise that is compiler-dependant and I
>> believe g++ broke ABI recently by changing the mangling algorithm.
>> Even so the algorithms should be reasonably easy to discover
>> (especially for g++). There would remain the issue of telling OOC
>> which algorithm to use.
>
>
> I suppose that might work. Within the current oo2c implementation it
> would need to be possible to access the mangled symbols from C code. I'm
> not sure if that's possible - presumably the compiler writers take steps
> to prevent the mangled names colliding with the mangled symbols.
Ooops! What I meant was that the compiler should prevent "C" names
colliding with the mangled symbols.
> Certainly, it would be possible at the asm/linker level.
Looks like for GCC it is possible from "C". Given this definition:
class Test {
int add(int a, int b);
};
int Test::add(int a, int b) {
return a + b;
}
The compiler generates a symbol named "__ZN1T3addEii". With the
appropriate definition, this can be called from "C":
#include <stdio.h>
extern int _ZN1T3addEii(void * obj, int a, int b);
int main(int argc, char ** argv) {
printf("test -> %d\n", _ZN1T3addEii(NULL, 2, 3));
}
Some compiler mangle the names using symbols that are not valid within C
identifiers, which would prevent this approach from working. See some
examples here:
http://en.wikipedia.org/wiki/Name_mangling#How_different_compilers_mangle_the_same_functions
Cheers,
Stewart
|
|
From: Frank C. <fj...@th...> - 2006-01-19 07:33:41
|
Stewart Greenhill wrote: >>> I'm interested in being able to use existing C++ APIs from Oberon-2. >> >> >> It would certainly broaden the options available. There are some C++ >> APIs I have an interest in. > > > Did you have anything special in mind? I'm particularly interested in > GUI/application toolkits. There are lots of these around for C++ (eg. > wxWidgets, FLTK, Qt, JUCE), but only relatively few for C. GUI/application stuff to a certain extent, although gtk+ looks like a pretty solid option in C. I'm also interested in 3D rendering engines which are mostly (but not all) C++. -- Volley Theory: It is better to have lobbed and lost than never to have lobbed at all. |