User talk:ConradPino/Christian Wimmer/Dynamic Function Invocation Update/Archive

From Project JEDI Wiki
Jump to navigationJump to search

This is a talk page archive.

Design Offer JediAPILib.inc Initial Proposal

{$IFDEF DELPHI2010_UP}
  {$IFDEF DYNAMIC_LINK}
    {$UNDEF DYNAMIC_LINK}
    {$DEFINE DELAYED_LINKING}
  {$ENDIF DYNAMIC_LINK}
{$ENDIF}

Archived here for Christian Wimmer – Conrad T. Pino 22:14, 1 February 2010 (UTC)

DELPHI2010_UP DELAYED_LINKING DYNAMIC_LINK Link Method
False False False Static
False False True Dynamic
False True False Compile Error
False True, State Error! True Dynamic
True False False Static
True False, Defined True, Undefined Delayed
True True False Delayed
True True, Defined True, Undefined Delayed

Add Comments Here

Invocation Analysis Case Optimized With Arguments

Optimized with arguments example with instructions generated included as comments:

var
  _TestFunctionReal: TJwaDynamicProcedureRecord;

function TestFunctionReal( const Index: Integer; const Value: PChar ): Integer; stdcall;
asm // push stack frame here
  //  push  ebp
  //  mov   ebp,esp

    POP   EBP
    //  pop   ebp

    JMP   [_TestFunctionReal.ProcedureAddress]
    //  jmp   dword ptr [_TestFunctionReal]

end; // pop stack frame here
//  pop   ebp
//  ret   8

Error handling function and initialization:

function _TestFunctionRealError( const Index: Integer; const Value: PChar ): Integer; stdcall;
begin
  Result := 0;
  GetProcedureAddressError( _TestFunctionReal );
end;

initialization
  GetProcedureAddress( _TestFunctionReal, @_TestFunctionRealError, LibReal, 'TestFunctionReal' );

Conrad T. Pino 11:43, 2 February 2010 (UTC)

Invocation Analysis Case Optimized Without Arguments

Optimized without arguments example with instructions generated included as comments:

var
  _TestFunction: TJwaDynamicProcedureRecord;

function TestFunction: Integer; stdcall;
asm // stack frame not pushed

    JMP   [_TestFunction.ProcedureAddress]
    //  jmp   dword ptr [_TestFunction]

end; // stack frame not popped
//  ret

Error handling function and initialization:

function _TestFunctionError: Integer; stdcall;
begin
  Result := 0;
  GetProcedureAddressError( _TestFunction );
end;

initialization
  GetProcedureAddress( _TestFunction, @_TestFunctionError, LibReal, 'TestFunction' );

Conrad T. Pino 11:50, 2 February 2010 (UTC)

JwaWinType GetProcedureAddress Optimization

There is a another way : User_talk:ChristianWimmer/GetProcedureAddress_Improvements

New Public Types

type
  EJwaErrorClass = class of EJwaError;

  TJwaDynamicProcedureRecord = record
    ProcedureAddress: Pointer;
    ExceptionClass: EJwaErrorClass;
    ExceptionMessage: AnsiString;
  end;

New Public Procedures

procedure GetProcedureAddress(var DynProcRec: TJwaDynamicProcedureRecord; const ErrProc: Pointer;
                              const ModuleName, ProcName: AnsiString); overload;

procedure GetProcedureAddress(var DynProcRec: TJwaDynamicProcedureRecord; const ErrProc: Pointer;
                              const ModuleName : AnsiString; const ProcNumber : Cardinal); overload;

procedure GetProcedureAddressError(const DynProcRec: TJwaDynamicProcedureRecord);

New Private Procedures

function JwaGetModuleHandle(const ModuleName: AnsiString): HMODULE; overload;

function JwaGetModuleHandle(var DynProcRec: TJwaDynamicProcedureRecord; const ErrProc: Pointer;
                            const ModuleName: AnsiString): HMODULE; overload;

function JwaGetProcAddress(const Handle: HMODULE; const ProcName: AnsiString): FARPROC; overload;

function JwaGetProcAddress(const Handle: HMODULE; const ProcNumber: Cardinal): FARPROC; overload;

JwaWinType Excerpts

interface

type
  EJwaError = class(Exception);
  EJwaLoadLibraryError = class(EJwaError);
  EJwaGetProcAddressError = class(EJwaError);

  EJwaErrorClass = class of EJwaError;

  TJwaDynamicProcedureRecord = record
    ProcedureAddress: Pointer;
    ExceptionClass: EJwaErrorClass;
    ExceptionMessage: AnsiString;
  end;

procedure GetProcedureAddress(var P: Pointer; const ModuleName, ProcName: AnsiString); overload;

procedure GetProcedureAddress(var P: Pointer; const ModuleName : AnsiString; const ProcNumber : Cardinal); overload;

procedure GetProcedureAddress(var DynProcRec: TJwaDynamicProcedureRecord; const ErrProc: Pointer;
                              const ModuleName, ProcName: AnsiString); overload;

procedure GetProcedureAddress(var DynProcRec: TJwaDynamicProcedureRecord; const ErrProc: Pointer;
                              const ModuleName : AnsiString; const ProcNumber : Cardinal); overload;

procedure GetProcedureAddressError(const DynProcRec: TJwaDynamicProcedureRecord);

implementation

function JwaWinType_GetModuleHandle(lpModuleName: LPCSTR): HMODULE; stdcall; external kernel32 name 'GetModuleHandleA';
function JwaWinType_LoadLibrary(lpLibFileName: LPCSTR): HMODULE; stdcall; external kernel32 name 'LoadLibraryA';
function JwaWinType_GetProcAddress(hModule: HMODULE; lpProcName: LPCSTR): FARPROC; stdcall; external kernel32 name 'GetProcAddress';

const
  RsELibraryNotFound = 'Library not found: %0:s';
  RsEFunctionNotFound = 'Function not found: %0:s.%1:s';
  RsEFunctionNotFound2 = 'Function not found: %0:s.%1:d';

function JwaGetModuleHandle(const ModuleName: AnsiString): HMODULE; overload;
begin
  Result := jwaWinType_GetModuleHandle(PAnsiChar(ModuleName));
  if Result = 0 then
  begin
    Result := jwaWinType_LoadLibrary(PAnsiChar(ModuleName));
  end;
end;

function JwaGetModuleHandle(var DynProcRec: TJwaDynamicProcedureRecord; const ErrProc: Pointer;
                            const ModuleName: AnsiString): HMODULE; overload;
begin
  Result := JwaGetModuleHandle(ModuleName);
  if Result = 0 then
  begin
    DynProcRec.ExceptionClass := EJwaLoadLibraryError;
    DynProcRec.ExceptionMessage := Format(RsELibraryNotFound, [ModuleName]);
    DynProcRec.ProcedureAddress := ErrProc;
  end;
end;

function JwaGetProcAddress(const Handle: HMODULE; const ProcName: AnsiString): FARPROC; overload;
begin
    Result := jwaWinType_GetProcAddress(Handle, PAnsiChar(ProcName));
end;

function JwaGetProcAddress(const Handle: HMODULE; const ProcNumber: Cardinal): FARPROC; overload;
begin
    Result := jwaWinType_GetProcAddress(Handle, PAnsiChar(ProcNumber));
end;

procedure GetProcedureAddress(var P: Pointer; const ModuleName, ProcName: AnsiString);
var
  ModuleHandle: HMODULE;
begin
  if not Assigned(P) then
  begin
    ModuleHandle := JwaGetModuleHandle(ModuleName);
    if ModuleHandle = 0 then
      raise EJwaLoadLibraryError.CreateFmt(RsELibraryNotFound, [ModuleName]);

    P := JwaGetProcAddress(ModuleHandle, ProcName);
    if not Assigned(P) then
      raise EJwaGetProcAddressError.CreateFmt(RsEFunctionNotFound, [ModuleName, ProcName]);
  end;
end;

procedure GetProcedureAddress(var P: Pointer; const ModuleName : AnsiString; const ProcNumber : Cardinal);
var
  ModuleHandle: HMODULE;
begin
  if not Assigned(P) then
  begin
    ModuleHandle := JwaGetModuleHandle(ModuleName);
    if ModuleHandle = 0 then
      raise EJwaLoadLibraryError.CreateFmt(RsELibraryNotFound, [ModuleName]);

    P := JwaGetProcAddress(ModuleHandle, ProcNumber);
    if not Assigned(P) then
      raise EJwaGetProcAddressError.CreateFmt(RsEFunctionNotFound2, [ModuleName, ProcNumber]);
  end;
end;

procedure GetProcedureAddress(var DynProcRec: TJwaDynamicProcedureRecord; const ErrProc: Pointer;
                              const ModuleName, ProcName: AnsiString); overload;
var
  ModuleHandle: HMODULE;
begin
  ModuleHandle := JwaGetModuleHandle(DynProcRec, ErrProc, ModuleName);
  if ModuleHandle = 0 then Exit;

  DynProcRec.ProcedureAddress := JwaGetProcAddress(ModuleHandle, ProcName);
  if not Assigned( DynProcRec.ProcedureAddress ) then
  begin
    DynProcRec.ExceptionClass := EJwaGetProcAddressError;
    DynProcRec.ExceptionMessage := Format(RsEFunctionNotFound, [ModuleName, ProcName]);
    DynProcRec.ProcedureAddress := ErrProc;
  end;
end;

procedure GetProcedureAddress(var DynProcRec: TJwaDynamicProcedureRecord; const ErrProc: Pointer;
                              const ModuleName : AnsiString; const ProcNumber : Cardinal); overload;
var
  ModuleHandle: HMODULE;
begin
  ModuleHandle := JwaGetModuleHandle(DynProcRec, ErrProc, ModuleName);
  if ModuleHandle = 0 then Exit;

  DynProcRec.ProcedureAddress := JwaGetProcAddress(ModuleHandle, ProcNumber);
  if not Assigned( DynProcRec.ProcedureAddress ) then
  begin
    DynProcRec.ExceptionClass := EJwaGetProcAddressError;
    DynProcRec.ExceptionMessage := Format(RsEFunctionNotFound2, [ModuleName, ProcNumber]);
    DynProcRec.ProcedureAddress := ErrProc;
  end;
end;

procedure GetProcedureAddressError(const DynProcRec: TJwaDynamicProcedureRecord);
begin
  with DynProcRec do raise ExceptionClass.Create( ExceptionMessage );
end;

Conrad T. Pino 12:18, 2 February 2010 (UTC)

Add Comments Here

Conrad T. Pino 12:58, 2 February 2010 (UTC):

  • There's no downside to extending the current GetProcedureAddress implementation to add the three (3) optimization procedures.
  • Current GetProcedureAddress implementation should NOT be deprecated as it remains useful where an initialization section is undesirable.
  • There's no benefit to using conditional compilation to change procedure aliases, example GetModuleHandle or JwaWinType_GetModuleHandle.

ChristianWimmer 14:24, 2 February 2010 (UTC):

Well, this approach has several issues:
  1. We don't know if it still works with 64bit. The asm registers must be 64 wide which usually mean to use rsp and rbp instead of esp and ebp. I don't know how this is done in 64bit FP.
  2. Microsoft has removed Assembler for 64bit in their compiler. Instead they introduced Compiler Intrinsics. If Embarcadero decides to do the same we have the same situation again.
  3. You are loading the function in the initialization. That is like static function loading. If you do this for all other functions, the application will load all functions that are declared in this way, even if they are not used. Delphi won't remove them because it is executed code. Furthermore the binary will take a lot of time to be loaded. If all functions are loaded this way I even think that the binary cannot be loaded into memory.
  4. You are doing the nearly same thing as Delphi 2010 does if the reserverd word "delayed" is used. First the function is a stub that contains a loading call which then moves the function pointer itself to another location: to the real function. I have reversed this process in CPU window and it is very fast (also read the discussion "api stubs and win x64" in jedi.apiconversion) However, your are introducing new code which must be added to all ~20.000 functions in the JEDI API. That is a work which cannot be done by hand. Furthermore additional code means that the produced binary file increases in size. Even today people still care about their exe file size.

Conrad T. Pino 17:42, 2 February 2010 (UTC):

I shall follow your points strictly:
  1. We don't know if it still works with 64bit. Agreed AND this applies to the current implementation as well. Since Native 64-bit Code implementation is unknowable all we can do is proceed without considering 64-bit.
  2. Microsoft has removed Assembler ... If Embarcadero decides to do the same ... Again, all we can do is proceed without considering 64-bit.
  3. ... is like static function loading. Agreed after a good sleep however it's compelling only for JwaWindows but not compelling enough to continue.
  4. ... work which cannot be done by hand. I never planned to do the using units by hand. ... binary file increases in size. Small compared to 64-bit! LOL
Conclusion:
  • As a learning exercise this was very worthwhile.
  • There is no compelling reason to do this now.

ChristianWimmer 17:57, 2 February 2010 (UTC):

Well there is a reason to do so. As I suggested we can still use "delayed" to make it work on 64 for future Delphi because in this way Emb. has to deal with compatibility.
Of course it doesn't solve it for FP until they support "delayed". It would be interesting how FP 64 does it.

ChristianWimmer 11:16, 3 February 2010 (UTC):

initialization
GetProcedureAddress( _TestFunctionReal, @_TestFunctionRealError, LibReal, 'TestFunctionReal' );
Sorry, but this looks wrong to me. As I already have pointed out, we cannot init all functions at once. The execution of the binary would take too long and needed disk space would explode since it is all dead code that is not removed by Delphi.
This is a no go reason for me.

Conrad T. Pino 18:24, 3 February 2010 (UTC): Your 14:24, 2 February 2010 (UTC) reply was persuasive; therefore I tabled and archived this topic.