Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-pascal / 2012-04

typesafe typecasting

2 messages · 2 people ·

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Bernd

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

Reply by email

JC Chu
to Bernd

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.

Bernd wrote 149 lines
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 - http://lists.freepascal.org/mailman/listinfo/fpc-pascal
Best Regards, JC Chu

Reply by email