[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