Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Statement not true #18

Open
alpinistbg opened this issue Jan 10, 2023 · 3 comments
Open

Statement not true #18

alpinistbg opened this issue Jan 10, 2023 · 3 comments

Comments

@alpinistbg
Copy link
Contributor

alpinistbg commented Jan 10, 2023

In 8.2 Callbacks (aka events, aka pointers to functions, aka procedural variables)
at the end of page 70 (in .pdf):

Unfortunately, you need to write ugly @TMyClass(nil).Add instead of just @TMyClass.Add.

That's not true.

It didn't occur to me to use class methods for procedural variable of object namely because I know there is no hidden Self in it (procedure of object means it is a <object_reference, method_address> pair).

To my surprise, your example works! But IMO it shouldn't. May be it works because one can invoke a class method using an instance only to specify indirectly it's class and the actual reference is just ignored?

Back to the statement:

Unfortunately, you need to write ugly @TMyClass(nil).Add instead of just @TMyClass.Add.

I've tried:

  M := @TMyClass.Add;

And it works (thus your statement is not true).

Only when Add is a normal method, not a class one, then it won't work and it must be written as @TMyClass(nil).Add, i.e. a reference must be specified.

I'm still confused that it is possible to assign Self-less class method to a procedural variable of object.

Lazarus 1.9.0 r64660M FPC 3.1.1 x86_64-linux-gtk2

Edit: Corrections. There is a Self pointer in class methods according to: https://www.freepascal.org/docs-html/ref/refsu28.html and it points to the VMT. But what I've tried still works.

@michaliskambi
Copy link
Owner

michaliskambi commented Jan 11, 2023

Thank you for this information!

It seems that FPC since 3.2.0 (at least partially) removed this limitation.

I tested on the attached test_method_callbacks.dpr, that deliberately does @TMyMethod.Add instead in @TMyMethod(nil).Add.

In the ObjFpc mode it definitely fails with older FPC versions. Tested now on FPC 2.6.4, 3.0.0, 3.0.2, 3.0.4 -- all fail like this:

$ fpc test_method_callbacks.dpr
Free Pascal Compiler version 2.6.4 [2014/03/03] for x86_64
Copyright (c) 1993-2014 by Florian Klaempfl and others
Target OS: Linux for x86-64
Compiling test_method_callbacks.dpr
test_method_callbacks.dpr(24,8) Error: Incompatible types: got "<class method type of function(const LongInt,const LongInt):LongInt of object;Register>" expected "<procedure variable type of function(const LongInt,const LongInt):LongInt of object;Register>"
test_method_callbacks.dpr(25,8) Error: Incompatible types: got "<class method type of function(const LongInt,const LongInt):LongInt of object;Register>" expected "<procedure variable type of function(const LongInt,const LongInt):LongInt of object;Register>"
test_method_callbacks.dpr(27) Fatal: There were 2 errors compiling module, stopping
Fatal: Compilation aborted
Error: /home/michalis/installed/fpclazarus/2.6.4-sf/bin/ppcx64 returned an error exitcode (normal if you did not specify a source file to be compiled)

However, since FPC 3.2.0, including 3.2.2, it just works :)

Strangely, I didn't find a mention about this change in https://wiki.freepascal.org/FPC_New_Features_3.2.0 or https://wiki.freepascal.org/User_Changes_3.2.0 . Well, nevermind, the problem is gone.

Unfortunately, it seems it works at simple assignment, but not when method is a parameter. I.e. this still fails to compile, even with FPC 3.2.0 and 3.2.2:

function ActOnCallback(const A, B: Integer; const M: TMyMethod): Integer;
begin
  Result := M(A, B);
end;

...
Writeln(ActOnCallback(10, 20, @TMyClass.Add));

So it is inconsistent now. One has to use the hack @TMyClass(nil).Add when passing a parameter.

I'll update the page shortly, and also submit FPC bugreport about it. It should fail consistently, or be accepted consistently (also in parameters).

I'm still confused that it is possible to assign Self-less method to a procedural variable of object.

This possibility (using class method for of object callback type) is because class methods actually also have a Self pointer, but it points to the class and not the instance.

This pointer allows to use virtual class methods from the class function/procedure implementation, because at runtime you know the exact class. The class methods that are passes are also allowed to be virtual.

See attached test_class_method_self.dpr for what is enables.

As a side-effect, it also makes class methods and regular (instance) methods binary compatible. They both get extra pointer, in one case it is just a pointer to a class, in another a pointer to instance. From what I understand, when calling, the pointer to instance is also effectively a valid pointer to a class (though I couldn't find anymore exact spec how it is internally done, though I can imagine some ways).

This is an extra gain, because it means one can use class method as a value of of object, and it just works.

This is different in static class methods, described on https://castle-engine.io/modern_pascal#_static_class_methods , that indeed have no Self and thus their call is incompatible from calling of object, i.e. they expect one less internal pointer. The static class methods are similar to regular (non-class) routines in this sense, i.e. calling them is just like calling a non-class routine.

See also

Again thanks for reporting this, I'll follow up with FPC report (to make it consistent) and updates to the "Modern Pascal Introduction" text.

@michaliskambi
Copy link
Owner

Attaching the tests I mentioned above.

test_class_method_self.dpr.txt
test_method_callbacks.dpr.txt

Rereading, I also see you found that Self is indeed present in class methods. I hope that my explanation is still helpful :)

@alpinistbg
Copy link
Contributor Author

As a side-effect, it also makes class methods and regular (instance) methods binary compatible. They both get extra pointer, in one case it is just a pointer to a class, in another a pointer to instance. From what I understand, when calling, the pointer to instance is also effectively a valid pointer to a class (though I couldn't find anymore exact spec how it is internally done, though I can imagine some ways).

AFAIK the first entry into the instance memory is a pointer to VMT. Generalizing, classes can be thought of as a single instances of their meta-class, so that is a nice uniformity.

I'm working on a translation of your book, currently somewhat about 40-50% done. I've got some small (linguistic) troubles but that's quite normal. As a LT Pascal practitioner, back to TP3.0, I am nicely surprised to be able to discover some new things in that text.

Regards,

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants