[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