Calling convention is lost when specializing generic function pointer lazarus / 64-FPC development ---------------------------------------------------------------------- ase · 2022-08-17 17:29 I have several libraries written in lazarus which represent different types of devices. The basic design of the libraries' API is the same. They more or less differ in an enumeration type that is used in conjunction with a get/set parameter style interface. In the internal implementation there is a generic base class that provides the implementation of get/set parameter stuff. Those libraries support callback functions which need to be binary compatible of course. For those callbacks a function definition exists and it is defined as stdcall. Now I stumbled upon the fact that the calling convention in generic function pointer definitions seems to be lost when specializing to a real function type. In this example I only use integers for clarity (and it avoids crashing when the wrong calling convention is used): Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i type  generic TCallProcStdCall = procedure(aArg1:T;aArg2:Integer;aArg3:Integer) of object;stdcall;   TGenericCallProcIntegerStdCall = specialize TCallProcStdCall; When I declare a function pointer variable and try to assign the implementation to it, I get an "incompatible types" error: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i var  obj     : TIntTest;  stdCallPtr: TGenericCallProcIntegerStdCall;begin  obj := TIntTest.Create;  try    //project1.lpr(51,23) Error:    //Incompatible types:    //got      ""    //expected ""     //stdCallPtr := @obj.StdCalling;     stdCallPtr := TGenericCallProcIntegerStdCall(@obj.StdCalling); There is a workaround that includes the definition of the real - non generic - type and casting to it so that the function call is made in the desired calling convention. That's acutally what I have done in the different libraries (to be more precise, I declared a default type that has the correct size of all the generic enumerations): Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i type  TCallProcIntegerStdCall = procedure(aArg1:Integer;aArg2:Integer;aArg3:Integer) of object;stdcall; begin    //call is made with correct calling convention    TCallProcIntegerStdCall(stdCallPtr)(1,2,3); Is this a bug? I found nothing about the calling convention in conjunction with generics. The whole program to demonstrate the situation: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program project1; uses  TypInfo, SysUtils; type  generic TCallProcStdCall = procedure(aArg1:T;aArg2:Integer;aArg3:Integer) of object;stdcall;  generic TCallProcReg     = procedure(aArg1:T;aArg2:Integer;aArg3:Integer) of object;   TGenericCallProcIntegerStdCall = specialize TCallProcStdCall;  TGenericCallProcIntegerReg     = specialize TCallProcReg;   TCallProcIntegerStdCall = procedure(aArg1:Integer;aArg2:Integer;aArg3:Integer) of object;stdcall;   { TTest }  generic TGenericTest = class  public  procedure StdCalling(aArg1:T;aArg2:Integer;aArg3:Integer);stdcall;  procedure RegCalling(aArg1:T;aArg2:Integer;aArg3:Integer);  end;   TIntTest = specialize TGenericTest; { TTest } procedure TGenericTest.StdCalling(aArg1:T;aArg2:Integer;aArg3:Integer); stdcall;begin  WriteLn('Self=0x'+IntToHex(IntPtr(self),SizeOf(self)*2)+          ' Arg1='+IntToStr(PtrInt(aArg1))+          ' Arg2='+IntToStr(aArg2)+        ' Arg3='+IntToStr(aArg3));end; procedure TGenericTest.RegCalling(aArg1:T;aArg2:Integer;aArg3:Integer);begin  WriteLn('Self=0x'+IntToHex(IntPtr(self),SizeOf(self)*2)+          ' Arg1='+IntToStr(PtrInt(aArg1))+          ' Arg2='+IntToStr(aArg2)+        ' Arg3='+IntToStr(aArg3));end; var  obj       : TIntTest;  stdCallPtr: TGenericCallProcIntegerStdCall;  regCallPtr: TGenericCallProcIntegerReg;begin  obj := TIntTest.Create;  try    //project1.lpr(51,23) Error:    //Incompatible types:    //got      ""    //expected ""     //stdCallPtr := @obj.StdCalling;     stdCallPtr := TGenericCallProcIntegerStdCall(@obj.StdCalling);    regCallPtr := @obj.RegCalling;     obj.StdCalling(1,2,3);    obj.RegCalling(1,2,3);     //call is made with wrong calling convention    stdCallPtr(1,2,3);    regCallPtr(1,2,3);     //call is made with correct calling convention    TCallProcIntegerStdCall(stdCallPtr)(1,2,3);     readln;  finally    obj.Free;  end;end.It will print something like this: QuoteSelf=0x01805A98 Arg1=1 Arg2=2 Arg3=3 Self=0x01805A98 Arg1=1 Arg2=2 Arg3=3 Self=0x00000003 Arg1=21167964 Arg2=4252960 Arg3=21167952 Self=0x01805A98 Arg1=1 Arg2=2 Arg3=3 Self=0x01805A98 Arg1=1 Arg2=2 Arg3=3 ---------------------------------------------------------------------- dje · 2022-08-18 06:38 I spent some time looking into this one. I'm using FPC 3.2.0, which is a little old, so maybe someone else can test. I first thought generic methods ignore the calling convention, but after checking the RTTI data, I found the calling conventions for generic methods are stored correctly. I think I found the crux of the issue. First here is a simplified version of the test class: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i type  generic TGenericTest = class    procedure StdCalling(A: T); stdcall;    procedure RegCalling(A: T); cdecl;  end;   procedure TGenericTest.StdCalling(A: T); stdcall;  begin  end;  procedure TGenericTest.RegCalling(A: T); cdecl;  begin  end;   This code compiles as expected: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure ThisWorks;var  A: procedure(A: integer) of object; stdcall;  B: procedure(A: integer) of object; cdecl;begin  with specialize TGenericTest.Create do begin    try      A := @StdCalling;    B := @RegCalling;    finally      Free;    end;  end;end; But, if you also want to define the vars A and B using generic "procedure of object"'s, the compiler fails. eg: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i type  generic TCallProcStdCall = procedure(A: T) of object; stdcall;  generic TCallProcCDecl = procedure(A: T) of object; cdecl; procedure ThisDoesNotWork;var  A: specialize TCallProcStdCall;  B: specialize TCallProcCDecl;begin  with specialize TGenericTest.Create do begin    try      A := @StdCalling;    B := @RegCalling; // <<< This fails to compile      // Error: Incompatible types:      //  got ""      //  expected ""    finally    Free;    end;  end;end; As far as I can tell, there should be no difference between the two. To simplify the problem even more, the following code uses basic functions. Assignment to procedure pointers with defined calling definitions, works. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   procedure Test1(A: integer); stdcall;  begin  end;  procedure Test2(A: integer); cdecl;  begin  end;  procedure ThisWorks;  var    A: procedure(A: integer); stdcall;    B: procedure(A: integer); cdecl;  begin    A := @Test1;    B := @Test2;  end;  While, pointers to procedures created via a specialized generic definition, does not: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i type  generic TProcedureStdCall = procedure(A: T); stdcall;  generic TProcedureCDecl = procedure(A: T); cdecl;   procedure ThisDoesNotWork;  var    A: specialize TProcedureStdCall;    B: specialize TProcedureCDecl;  begin    A := @Test1;    B := @Test2; // << This fails to compile    // Error: Incompatible types:  //  got "
"    //  expected ""  end; This might be a bug which has been fixed. Maybe someone can check. ---------------------------------------------------------------------- dje · 2022-08-18 06:56 A proposed workaround. The following works. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i type  generic TGenericTest = class  type    TStdCall = procedure(A: T) of object; stdcall;    TCDecl = procedure(A: T) of object; cdecl;  public    procedure StdCalling(A: T); stdcall;    procedure RegCalling(A: T); cdecl;  end;   procedure TGenericTest.StdCalling(A: T); stdcall;  begin end;   procedure TGenericTest.RegCalling(A: T); cdecl;  begin end;   procedure ThisWorks;  var    A: specialize TGenericTest.TStdCall;    B: specialize TGenericTest.TCDecl;  begin    with specialize TGenericTest.Create do begin      try        A := @StdCalling;        B := @RegCalling;      finally        Free;      end;    end;  end; ---------------------------------------------------------------------- PascalDragon · 2022-08-18 07:27 Please report a bug with a self contained example. ---------------------------------------------------------------------- ase · 2022-08-18 10:58 I filed a bug report and attached a program that shows the compiler error and includes the workaround that derek.john.evans has suggested. Thank you derek.john.evans for the input. https://gitlab.com/freepascal.org/fpc/source/-/issues/39869 ---------------------------------------------------------------------- ase · 2022-08-22 08:24 Quote from: PascalDragon on August 18, 2022, 09:27:40 amPlease report a bug with a self contained example. Just compiled the latest fpc trunk version - it works now. Thank you for the good work! It might be off topic, but I must say Free Pascal is so much better than Delphi when it comes to generics. E.g. we implemented a class of generic linked lists and are using pointers to the specialized record types. The pointers are specialized within the generic class. Delphi is unable to resolve those types - which renders the whole construct useless there. ---------------------------------------------------------------------- PascalDragon · 2022-08-22 11:48 Quote from: ase on August 22, 2022, 10:24:01 amQuote from: PascalDragon on August 18, 2022, 09:27:40 amPlease report a bug with a self contained example. Just compiled the latest fpc trunk version - it works now. Thank you for the good work! Good. Let me see whether I'll merge this to 3.2.3 as well. Quote from: ase on August 22, 2022, 10:24:01 amIt might be off topic, but I must say Free Pascal is so much better than Delphi when it comes to generics. E.g. we implemented a class of generic linked lists and are using pointers to the specialized record types. The pointers are specialized within the generic class. Delphi is unable to resolve those types - which renders the whole construct useless there. It might either be a bug in Delphi or due to the differences in how FPC and Delphi approach the concept of generics (there are some things you simply can't do in Delphi that you can in FPC, just as there are a few things that work in Delphi, but not (yet) in FPC).