[fpc-pascal] typesafe typecasting

Bernd prof7bit at googlemail.com
Wed Apr 25 20:39:55 CEST 2012


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



More information about the fpc-pascal mailing list