[fpc-pascal] typesafe typecasting
JC Chu
jcchu at acm.org
Wed Apr 25 22:54:19 CEST 2012
When you assign a “specialized” pointer, say a PInteger, to a generic
Pointer variable, the assignment is valid because the compiler knows
that PInteger is a subtype of Pointer. There is however no general way
of determining a subtype relation between procedural types, even if some
of their parameters are known to be related. Even in unary cases,
T1 ≺ T2 does not imply that a PROCEDURE (T1) can be safely assigned to
a PROCEDURE (T2)–typed variable, nor vice versa; for example, given
PMyRecord ≺ Pointer, you probably wouldn’t want
◦ @FreeMem() assigned to
VAR finalizeMyRecord: PROCEDURE (rec: PMyRecord); or
◦ something like @InitMyRecord() assigned to
VAR: reserve100GB: PROCEDURE (ptr: Pointer).
Using a generic callback procedure as in your scenario does potentially
permit such unwanted operations.
I do vaguely remember a UNIV modifier that’s available in the
Macintosh Pascal mode, but I couldn’t find any documentation about it
(nor do I know if it can be enabled in other modes) and I’m not sure
what it exactly does. Although it does seem to eliminate the need for
manual typecasts, type-safety is generally not guaranteed.
Your last proposal is not completely safe, because you can still use
non-PMyData pointers with RegisterCallback().
So to answer your question, my suggestion would be to define a custom
callback type with a custom RegisterCallback() for each of your custom
data structures.
Hope this helps.
On April 26, at 02:39, Bernd wrote:
> Hi,
>
> While translating some headers I have run across the following problem
> (I am sure had this in different variations many times before already
> and always lived with it and ignored it by casting from untyped
> pointers). Consider this situation:
>
> unit Unit1;
>
> {$mode objfpc}{$H+}
>
> interface
>
> type
> PData = Pointer;
> PCallback = procedure(Data: PData); cdecl;
>
> procedure RegisterCallback(Proc: PCallback; Data: PData);
>
>
> implementation
>
> procedure RegisterCallback(Proc: PCallback; Data: PData);
> begin
> //just call the callback now for demonstration purpose
> Proc(Data);
> end;
>
> end.
>
> The above unit might be an API for some C library or some other piece
> of code that will allow me to register a callback (a timer or
> something similar) and pass additional data via a pointer to my
> callback function. The following unit will use this API:
>
> program project1;
>
> {$mode objfpc}{$H+}{$T+}
>
> uses Unit1;
>
> type
> PMyData = ^TMyData;
> TMyData = record
> Foo: Integer;
> Bar: Integer;
> end;
>
>
> procedure MyUglyCallback(D: Pointer); cdecl;
> begin
> // ugly typecasting everytime I access my data
> writeln(PMyData(D)^.Foo);
> Dispose(PMyData(D));
> end;
>
> procedure MyNiceCallback(var D: TMyData); cdecl;
> begin
> // this function looks much nicer but although it is binary
> // identical its signature is deemed incompatible by FPC
> writeln(D.Foo);
> Dispose(@D);
> end;
>
> procedure Run;
> var
> D : PMyData;
> begin
> New(D);
> D^.Foo := 42;
> RegisterCallback(@MyUglyCallback, D);
>
> New(D);
> D^.Foo := 23;
> // unsafe typecasting, would accept *any* procedure
> RegisterCallback(PCallback(@MyNiceCallback), D);
> end;
>
> begin
> Run;
> end.
>
> None of the two possibilities feels right, the first one is what I
> have always used (out of desperation) because it will at least enforce
> that I do not accidentally forget the cdecl in my callback function
> but the casting of the Pointer inside the function looks just ugly to
> me and simply does not feel right, it is against the spirit of the
> language and it is still unsafe.
>
> The second option will almost guarantee that in one of my (many)
> callback functions I will sooner or later accidentally forget the
> cdecl because It will allow me to pass *anything* to RegisterCallback,
> this one feels even more type-unsafe to me.
>
> Now I have come up with the following:
>
> program project1;
>
> {$mode objfpc}{$H+}{$T+}
>
> uses Unit1;
>
> type
> PMyData = ^TMyData;
> TMyData = record
> Foo: Integer;
> Bar: Integer;
> end;
>
> PMyCallback = procedure(var Data: TMyData); cdecl;
>
> function CastMyCallback(Proc: PMyCallback): PCallback; inline;
> begin
> Result := PCallback(Proc);
> end;
>
> procedure MyNiceCallback(var D: TMyData); cdecl;
> begin
> // this function looks much nicer but although it is binary
> // identical its signature is deemed incompatible by FPC
> writeln(D.Foo);
> Dispose(@D);
> end;
>
> procedure Run;
> var
> D : PMyData;
> begin
> New(D);
> D^.Foo := 5;
> // this is the only way I have found to let me have my
> // own types and still retain total type-safety
> RegisterCallback(CastMyCallback(@MyNiceCallback), D);
> end;
>
> begin
> Run;
> end.
>
> This has an overhead of only 4 code lines (the CastMyCallback
> function) and only once for all my different callback functions that
> use the same data structure and it would completely retain strong type
> safety. What do you think, is this a good idea or would there have
> been an even more elegant way to achieve the same?
>
> Bernd
> _______________________________________________
> fpc-pascal maillist - fpc-pascal at lists.freepascal.org
> http://lists.freepascal.org/mailman/listinfo/fpc-pascal
--
Best Regards,
JC Chu
More information about the fpc-pascal
mailing list