Markus Gerwinski wrote:
I just did a little experiment. The full source code is attached, but what I'm doing is, in brief, the following:
- Declaring an object type tTestObj with a method `foo',
- giving this method an attribute name,
- declaring (in the same source!) an external-declared function with the same attribute name and a pointer (for the object) as an additional parameter,
- assigning this method to a procedure variable,
- calling the variable with a pTestObj as a parameter.
Result: It works! :-) The method is executed as if it was "correctly" called via tTestObj.foo, including access to private fields of the object.
Question: Is this supposed to work? I.e. can I rely on this feature remaining in GPC? Or is it rather looked upon as a bug, so I have to expect it'll be removed some day?
I'd consider it perhaps a misfeature, but I think it will keep working, as long as you use explicit linker names (as you do), and take care of the parameter list ("Self" last, in contrast to BP where it's first, no difference in the example).
However, actually "Self" is a reference parameter of object type, not a value parameter ob object pointer type. Though both are implemented the same, and this is also unlikely to change, the example can easily be changed to avoid depending on this.
I'd like to use it as a feature, since it provides me with a way to assign methods to procedure/function variables, something I've been missing in GPC so far. OTOH, I would, of course, even prefer to have real, "syntax-approved"
Yes, the first problem is to define a syntax. I think there might have been some suggestions before, but nothing concrete so far AFAIR. Do other dialects have something like that, if so which syntax?
method variables that also take into account polymorphy and the fact that methods might be overridden.
Here's an alternative:
gFoo:= tFooFunc (@tTestObj.foo);
This avoids the linker name tricks, but requires a type-cast. It has the advantage that it works with (non-)overridden methods, i.e. you can put in a derived type of tTestObj, whether in inherits tTestObj.foo or defines its own foo. For a compiler-implemented solution, this issue would also be easily solved.
For polymorphy (i.e., virtual) methods, the problem is that a procedural variable needs a constant address. One way to solve it is to write a wrapper routine that does the actual virtual call. This solution (see below) doesn't even require a type-cast or other dirty stuff. It also works for non-virtual methods, of course, if you're willing to accept the small wrapper overhead (which seems inevitable for virtual methods, but can be avoided for non-virtual methods, as in your example).
For a compiler-implemented solution, polymorphy would also be an issue, and AFAICS, the compiler would need to emit such a wrapper routine automatically then. That might not be too difficult per se (just some work to do ...), but the important question is, when and where to emit such wrappers (looks a bit like the C++ template problems). We could emit one on each usage, at the danger of emitting the same one several times (might even be in different units). Or emit one for each virtual method, at the danger of emitting some (often many) that aren't used at all. Of course, it's not a huge issue (just a few bytes per routine), but since a manual solution is available, I'd first wonder if the effort is justified for a feature that might not be used all that often ...
Program Test;
type
pTestObj = ^tTestObj;
tTestObj = object public Constructor init ( aBar: integer ); Procedure foo; virtual; private fBar: integer; end (* tTestObj *);
p2 = ^t2;
t2 = object (tTestObj) Procedure foo; virtual; end (* tTestObj *);
tFooFunc = Procedure ( var obj: tTestObj );
var g1, g2: pTestObj; gFoo: tFooFunc;
Constructor tTestObj.init ( aBar: integer );
begin (* tTestObj.init *) fBar:= aBar; end (* tTestObj.init *);
Procedure tTestObj.foo;
begin (* tTestObj.foo *) writeln ( 'I''m one.' ); end (* tTestObj.foo *);
Procedure t2.foo;
begin (* tTestObj.foo *) writeln ( 'Me two.' ); end (* tTestObj.foo *);
Procedure FooWrapper ( var obj: tTestObj ); begin obj.foo end;
begin g1 := new ( pTestObj, init ( 42 ) ); g2 := new ( p2, init ( 42 ) ); gFoo:= FooWrapper; gFoo ( g1^ ); gFoo ( g2^ ); end.
Frank
Frank Heckenbach a écrit:
Markus Gerwinski wrote:
Yes, the first problem is to define a syntax. I think there might have been some suggestions before, but nothing concrete so far AFAIR. Do other dialects have something like that, if so which syntax?
in delphi you can define procedural types by
procedure g(i:integer) of object;
or
function f(i:integer): integer of object;
which passes the self as additional pointer to the VMT.
Maurice
Frank Heckenbach wrote:
I'd consider it perhaps a misfeature, but I think it will keep working, as long as you use explicit linker names (as you do), and take care of the parameter list ("Self" last, in contrast to BP where it's first, no difference in the example).
...?
Let me get this straight: If I defined a method like this:
tFoo = object Procedure bar ( baz: cinteger ); attribute ( name = 'foo_bar' ); end (* tFoo *);
and linked it from a C library, would the header then be
external void foo_bar ( int baz, void *self );
or
external void foo_bar ( void *self, int baz );?
I'm only asking because I've been using the latter one for years, and it worked fine.
However, actually "Self" is a reference parameter of object type, not a value parameter ob object pointer type. Though both are implemented the same, and this is also unlikely to change, the example can easily be changed to avoid depending on this.
Good to know, thanks!
Yes, the first problem is to define a syntax. I think there might have been some suggestions before, but nothing concrete so far AFAIR. Do other dialects have something like that, if so which syntax?
Maurice mentioned the Delphi syntax. I already thought about it myself, but I think you've been right when you said:
So IIUC, the difference to Markus' way is that here the procedural variable also contains the object, while there it only points to the method, and can be applied to any object (of matching type), right?
I would need the latter solution indeed.
Here's an alternative:
gFoo:= tFooFunc (@tTestObj.foo);
#-) Thanks again! I could have thought of something like that myself. Hmm... In fact, this might turn out even more useful for my purposes than a "real" method variable type. A syntax solution would tie me (resp. my potential library users) to always use objects. This one leaves the choice to use arbitrary data sets instead.
So if tFooFunc was a "method" type with a parameter list, what would be the correct declaration?
tFooFunc = Procedure ( aParam: integer; aSelf: pTestObj );
or
tFooFunc = Procedure ( aSelf: pTestObj; aParam: integer );?
For a compiler-implemented solution, polymorphy would also be an issue, and AFAICS, the compiler would need to emit such a wrapper routine automatically then. That might not be too difficult per se (just some work to do ...), but the important question is, when and where to emit such wrappers (looks a bit like the C++ template problems). We could emit one on each usage, at the danger of emitting the same one several times (might even be in different units). Or emit one for each virtual method, at the danger of emitting some (often many) that aren't used at all. Of course, it's not a huge issue (just a few bytes per routine), but since a manual solution is available, I'd first wonder if the effort is justified for a feature that might not be used all that often ...
True. You've halfway convinced me to stick with the wrappers; I'm not sure if the flaws I'd catch by using method variables (syntax-approved or not) wouldn't more than outweigh their possible benefits. At least with the wrappers, I have a well-defined and most flexible way of handling "method" vars, and the overhead is, though sometimes annoying, acceptable altogether. I'll have to sleep over it.
Best,
Markus
Markus Gerwinski wrote:
Frank Heckenbach wrote:
I'd consider it perhaps a misfeature, but I think it will keep working, as long as you use explicit linker names (as you do), and take care of the parameter list ("Self" last, in contrast to BP where it's first, no difference in the example).
...?
Let me get this straight: If I defined a method like this:
tFoo = object Procedure bar ( baz: cinteger ); attribute ( name = 'foo_bar' ); end (* tFoo *);
and linked it from a C library, would the header then be
external void foo_bar ( int baz, void *self );
or
external void foo_bar ( void *self, int baz );?
I'm only asking because I've been using the latter one for years, and it worked fine.
Sorry, I mixed it up in the paragraph above. With GPC it's first, with BP it's last.
So if tFooFunc was a "method" type with a parameter list, what would be the correct declaration?
tFooFunc = Procedure ( aParam: integer; aSelf: pTestObj );
or
tFooFunc = Procedure ( aSelf: pTestObj; aParam: integer );?
Procedure tTestObj.Foo ( aParam: integer; bParam: Real );
=>
tFooFunc = Procedure ( var Self: tTestObj; aParam: integer; bParam: Real );
(i.e., Self first, then method parameters in order).
For a compiler-implemented solution, polymorphy would also be an issue, and AFAICS, the compiler would need to emit such a wrapper routine automatically then. That might not be too difficult per se (just some work to do ...), but the important question is, when and where to emit such wrappers (looks a bit like the C++ template problems). We could emit one on each usage, at the danger of emitting the same one several times (might even be in different units). Or emit one for each virtual method, at the danger of emitting some (often many) that aren't used at all. Of course, it's not a huge issue (just a few bytes per routine), but since a manual solution is available, I'd first wonder if the effort is justified for a feature that might not be used all that often ...
True. You've halfway convinced me to stick with the wrappers; I'm not sure if the flaws I'd catch by using method variables (syntax-approved or not) wouldn't more than outweigh their possible benefits. At least with the wrappers, I have a well-defined and most flexible way of handling "method" vars, and the overhead is, though sometimes annoying, acceptable altogether. I'll have to sleep over it.
OK. BTW, the wrapper way is type-safe, i.e. the compiler will find any parameter list mismatches, and you don't have to worry about the placement of Self etc. (In fact, in a manual wrapper, you can put Self anywhere you like.) So it would probably even work with BP and other compilers without changes, though I didn't test it. With the type-cast and linker-name versions, the compiler can't check matching parameter lists.
Frank