{ This file is part of the Free Pascal run time library. Copyright (c) 2017 by the Free Pascal development team RTTI Function Call Manager using Foreign Function Call (libffi) library. See the file COPYING.FPC, included in this distribution, for details about the copyright. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. **********************************************************************} unit ffi.manager; {$mode objfpc}{$H+} interface implementation uses TypInfo, Rtti, ffi; type Tpffi_typeArray = array of pffi_type; procedure FreeFFIType(t: pffi_type); var elements: Tpffi_typeArray; i: LongInt; begin if t^._type <> _FFI_TYPE_STRUCT then Exit; elements := Tpffi_typeArray(t^.elements); for i := Low(elements) to High(elements) do FreeFFIType(elements[i]); { with this the array will be freed } elements := Nil; Dispose(t); end; function TypeInfoToFFIType(aTypeInfo: PTypeInfo; aFlags: TParamFlags): pffi_type; forward; function RecordOrObjectToFFIType(aTypeInfo: PTypeInfo): pffi_type; var curindex: SizeInt; elements: Tpffi_typeArray; procedure AddElement(t: pffi_type); begin if curindex = Length(elements) then begin SetLength(elements, Length(elements) * 2); end; elements[curindex] := t; Inc(curindex); end; var td, fieldtd: PTypeData; i, j, curoffset, remoffset: SizeInt; field: PManagedField; ffitype: pffi_type; begin td := GetTypeData(aTypeInfo); if td^.TotalFieldCount = 0 then { uhm... } Exit(Nil); New(Result); FillChar(Result^, SizeOf(Result), 0); Result^._type := _FFI_TYPE_STRUCT; Result^.elements := Nil; curoffset := 0; curindex := 0; field := PManagedField(PByte(@td^.TotalFieldCount) + SizeOf(td^.TotalFieldCount)); { assume first that there are no paddings } SetLength(elements, td^.TotalFieldCount); for i := 0 to td^.TotalFieldCount - 1 do begin { ToDo: what about fields that are larger that what we have currently? } if field^.FldOffset < curoffset then begin Inc(field); Continue; end; remoffset := field^.FldOffset - curoffset; { insert padding elements } while remoffset >= SizeOf(QWord) do begin AddElement(@ffi_type_uint64); Dec(remoffset, SizeOf(QWord)); end; while remoffset >= SizeOf(LongWord) do begin AddElement(@ffi_type_uint32); Dec(remoffset, SizeOf(LongWord)); end; while remoffset >= SizeOf(Word) do begin AddElement(@ffi_type_uint16); Dec(remoffset, SizeOf(Word)); end; while remoffset >= SizeOf(Byte) do begin AddElement(@ffi_type_uint8); Dec(remoffset, SizeOf(Byte)) end; { now add the real field type (Note: some are handled differently from being passed as arguments, so we handle those here) } if field^.TypeRef^.Kind = tkObject then AddElement(RecordOrObjectToFFIType(field^.TypeRef)) else if field^.TypeRef^.Kind = tkSString then begin fieldtd := GetTypeData(field^.TypeRef); for j := 0 to fieldtd^.MaxLength + 1 do AddElement(@ffi_type_uint8); end else if field^.TypeRef^.Kind = tkArray then begin fieldtd := GetTypeData(field^.TypeRef); ffitype := TypeInfoToFFIType(fieldtd^.ArrayData.ElType, []); for j := 0 to fieldtd^.ArrayData.ElCount - 1 do AddElement(ffitype); end else AddElement(TypeInfoToFFIType(field^.TypeRef, [])); Inc(field); curoffset := field^.FldOffset; end; { add a final Nil element } AddElement(Nil); { reduce array to final size } SetLength(elements, curindex); { this is a bit cheeky, but it works } Tpffi_typeArray(Result^.elements) := elements; end; function SetToFFIType(aSize: SizeInt): pffi_type; var elements: Tpffi_typeArray; curindex: SizeInt; procedure AddElement(t: pffi_type); begin if curindex = Length(elements) then begin SetLength(elements, Length(elements) * 2); end; elements[curindex] := t; Inc(curindex); end; begin if aSize = 0 then Exit(Nil); New(Result); Result^._type := _FFI_TYPE_STRUCT; Result^.elements := Nil; curindex := 0; SetLength(elements, aSize); while aSize >= SizeOf(QWord) do begin AddElement(@ffi_type_uint64); Dec(aSize, SizeOf(QWord)); end; while aSize >= SizeOf(LongWord) do begin AddElement(@ffi_type_uint32); Dec(aSize, SizeOf(LongWord)); end; while aSize >= SizeOf(Word) do begin AddElement(@ffi_type_uint16); Dec(aSize, SizeOf(Word)); end; while aSize >= SizeOf(Byte) do begin AddElement(@ffi_type_uint8); Dec(aSize, SizeOf(Byte)); end; AddElement(Nil); SetLength(elements, curindex); Tpffi_typeArray(Result^.elements) := elements; end; function TypeInfoToFFIType(aTypeInfo: PTypeInfo; aFlags: TParamFlags): pffi_type; function TypeKindName: String; begin Result := ''; WriteStr(Result, aTypeInfo^.Kind); end; var td: PTypeData; begin Result := @ffi_type_void; if Assigned(aTypeInfo) then begin td := GetTypeData(aTypeInfo); if aFlags * [pfArray, pfOut, pfVar, pfConstRef] <> [] then Result := @ffi_type_pointer else case aTypeInfo^.Kind of tkInteger, tkEnumeration, tkBool, tkInt64, tkQWord: case td^.OrdType of otSByte: Result := @ffi_type_sint8; otUByte: Result := @ffi_type_uint8; otSWord: Result := @ffi_type_sint16; otUWord: Result := @ffi_type_uint16; otSLong: Result := @ffi_type_sint32; otULong: Result := @ffi_type_uint32; otSQWord: Result := @ffi_type_sint64; otUQWord: Result := @ffi_type_uint64; end; tkChar: Result := @ffi_type_uint8; tkFloat: case td^.FloatType of ftSingle: Result := @ffi_type_float; ftDouble: Result := @ffi_type_double; ftExtended: Result := @ffi_type_longdouble; { Comp and Currency are passed as Int64 (ToDo: on all platforms?) } ftComp: Result := @ffi_type_sint64; ftCurr: Result := @ffi_type_sint64; end; tkSet: case td^.OrdType of otUByte: begin if td^.SetSize = 1 then Result := @ffi_type_uint8 else begin { ugh... build a of suitable record } Result := SetToFFIType(td^.SetSize); end; end; otUWord: Result := @ffi_type_uint16; otULong: Result := @ffi_type_uint32; end; tkWChar, tkUChar: Result := @ffi_type_uint16; tkInterface, tkAString, tkUString, tkWString, tkInterfaceRaw, tkProcVar, tkDynArray, tkClass, tkClassRef, tkPointer: Result := @ffi_type_pointer; tkMethod: Result := RecordOrObjectToFFIType(TypeInfo(TMethod)); tkSString: { since shortstrings are rather large they're passed as references } Result := @ffi_type_pointer; tkObject: { passed around as pointer as well } Result := @ffi_type_pointer; tkArray: { arrays are passed as pointers to be compatible to C } Result := @ffi_type_pointer; tkRecord: Result := RecordOrObjectToFFIType(aTypeInfo); tkVariant: Result := RecordOrObjectToFFIType(TypeInfo(tvardata)); //tkLString: ; //tkHelper: ; //tkFile: ; else raise EInvocationError.CreateFmt(SErrTypeKindNotSupported, [TypeKindName]); end; end else if aFlags * [pfOut, pfVar, pfConst, pfConstRef] <> [] then Result := @ffi_type_pointer; end; function ArgIsIndirect(aKind: TTypeKind; aFlags: TParamFlags; aIsResult: Boolean): Boolean; const ResultTypeNeedsIndirection = [ tkAString, tkWString, tkUString, tkInterface, tkDynArray ]; begin Result := False; if (aKind = tkSString) or (aIsResult and (aKind in ResultTypeNeedsIndirection)) or (aFlags * [pfArray, pfOut, pfVar, pfConstRef] <> []) or ((aKind = tkUnknown) and (pfConst in aFlags)) then Result := True; end; procedure FFIValueToValue(Source, Dest: Pointer; TypeInfo: PTypeInfo); var size: SizeInt; td: PTypeData; begin td := GetTypeData(TypeInfo); size := 0; case TypeInfo^.Kind of tkChar, tkWChar, tkUChar, tkEnumeration, tkBool, tkInteger, tkInt64, tkQWord: case td^.OrdType of otSByte, otUByte: size := 1; otSWord, otUWord: size := 2; otSLong, otULong: size := 4; otSQWord, otUQWord: size := 8; end; tkSet: size := td^.SetSize; tkFloat: case td^.FloatType of ftSingle: size := SizeOf(Single); ftDouble: size := SizeOf(Double); ftExtended: size := SizeOf(Extended); ftComp: size := SizeOf(Comp); ftCurr: size := SizeOf(Currency); end; tkMethod: size := SizeOf(TMethod); tkSString: size := td^.MaxLength + 1; tkDynArray, tkLString, tkAString, tkUString, tkWString, tkClass, tkPointer, tkClassRef, tkInterfaceRaw: size := SizeOf(Pointer); tkVariant: size := SizeOf(tvardata); tkArray: size := td^.ArrayData.Size; tkRecord: size := td^.RecSize; tkProcVar: size := SizeOf(CodePointer); tkObject: ; tkHelper: ; tkFile: ; end; if size > 0 then Move(Source^, Dest^, size); end; { move this to type info? } function RetInParam(aCallConv: TCallConv; aTypeInfo: PTypeInfo): Boolean; begin Result := False; if not Assigned(aTypeInfo) then Exit; case aTypeInfo^.Kind of tkSString, tkAString, tkWString, tkUString, tkInterface, tkDynArray: Result := True; end; end; { on X86 platforms Currency and Comp results are passed by the X87 if the Extended type is available } {$if (defined(CPUI8086) or defined(CPUI386) or defined(CPUX86_64)) and defined(FPC_HAS_TYPE_EXTENDED) and (not defined(FPC_COMP_IS_INT64) or not defined(FPC_CURRENCY_IS_INT64))} {$define USE_EXTENDED_AS_COMP_CURRENCY_RES} {$endif} type TFFIData = record Types: array of pffi_type; Values: array of Pointer; Indirect: array of Boolean; ResultType: pffi_type; ResultValue: Pointer; ResultIndex: SizeInt; {$ifdef USE_EXTENDED_AS_COMP_CURRENCY_RES} ResultTypeData: PTypeData; ResultExtended: Extended; {$endif} { put this at the end just in case we messed up the size } CIF: ffi_cif; end; procedure CreateCIF(constref aArgInfos: array of TFunctionCallParameterInfo; constref aArgValues: array of Pointer; aCallConv: TCallConv; aResultType: PTypeInfo; aResultValue: Pointer; aFlags: TFunctionCallFlags; out aData: TFFIData); function CallConvName: String; inline; begin WriteStr(Result, aCallConv); end; var abi: ffi_abi; i, arglen, argoffset, argstart: LongInt; usevalues, retparam: Boolean; kind: TTypeKind; types: ppffi_type; begin if not (fcfStatic in aFlags) and (Length(aArgInfos) = 0) then raise EInvocationError.Create(SErrMissingSelfParam); Assert((Length(aArgInfos) = Length(aArgValues)) or (Length(aArgValues) = 0), 'Amount of arguments does not match needed arguments'); case aCallConv of {$if defined(CPUI386)} ccReg: abi := FFI_REGISTER; ccCdecl: {$ifdef WIN32} abi := FFI_MS_CDECL; {$else} abi := FFI_STDCALL; {$endif} ccPascal: abi := FFI_PASCAL; ccStdCall: abi := FFI_STDCALL; ccCppdecl: abi := FFI_THISCALL; {$else} {$ifndef CPUM68K} { M68k has a custom register calling convention implementation } ccReg, {$endif} ccCdecl, ccPascal, ccStdCall, ccCppdecl: abi := FFI_DEFAULT_ABI; {$endif} else raise EInvocationError.CreateFmt(SErrCallConvNotSupported, [CallConvName]); end; { if no values are provided we are called to prepare a callback, otherwise we are asked to prepare a invokation } usevalues := (Length(aArgInfos) > 0) and (Length(aArgValues) > 0); retparam := RetInParam(aCallConv, aResultType); arglen := Length(aArgInfos); if retparam then begin Inc(arglen); usevalues := True; argoffset := 1; aData.ResultIndex := 0; end else begin argoffset := 0; aData.ResultIndex := -1; end; SetLength(aData.Types, arglen); SetLength(aData.Indirect, arglen); if usevalues then SetLength(aData.Values, arglen); { the order is Self/Vmt (if any), Result param (if any), other params } if not (fcfStatic in aFlags) and retparam then begin aData.Types[0] := TypeInfoToFFIType(aArgInfos[0].ParamType, aArgInfos[0].ParamFlags); if Assigned(aArgInfos[0].ParamType) then kind := aArgInfos[0].ParamType^.Kind else kind := tkUnknown; aData.Indirect[0] := ArgIsIndirect(kind, aArgInfos[0].ParamFlags, False); if usevalues then if aData.Indirect[0] then aData.Values[0] := @aArgValues[0] else aData.Values[0] := aArgValues[0]; if retparam then Inc(aData.ResultIndex); argstart := 1; end else argstart := 0; for i := argstart to High(aArgInfos) do begin aData.Types[i + argoffset] := TypeInfoToFFIType(aArgInfos[i].ParamType, aArgInfos[i].ParamFlags); if (pfResult in aArgInfos[i].ParamFlags) and not retparam then aData.ResultIndex := i + argoffset; if Assigned(aArgInfos[i].ParamType) then kind := aArgInfos[i].ParamType^.Kind else kind := tkUnknown; aData.Indirect[i + argoffset] := ArgIsIndirect(kind, aArgInfos[i].ParamFlags, False); if usevalues then if aData.Indirect[i + argoffset] then aData.Values[i + argoffset] := @aArgValues[i] else aData.Values[i + argoffset] := aArgValues[i]; end; if retparam then begin aData.Types[aData.ResultIndex] := TypeInfoToFFIType(aResultType, []); aData.Indirect[aData.ResultIndex] := ArgIsIndirect(aResultType^.Kind, [], True); if usevalues then if aData.Indirect[aData.ResultIndex] then aData.Values[aData.ResultIndex] := @aResultValue else aData.Values[aData.ResultIndex] := aResultValue; aData.ResultType := @ffi_type_void; aData.ResultValue := Nil; {$ifdef USE_EXTENDED_AS_COMP_CURRENCY_RES} aData.ResultTypeData := Nil; {$endif} end else begin aData.ResultValue := Nil; {$ifdef USE_EXTENDED_AS_COMP_CURRENCY_RES} { special case for Comp/Currency as such arguments are passed as Int64, but the result is handled through the X87 } if Assigned(aResultType) and (aResultType^.Kind = tkFloat) then begin aData.ResultTypeData := GetTypeData(aResultType); case aData.ResultTypeData^.FloatType of {$ifndef FPC_CURRENCY_IS_INT64} ftCurr: begin aData.ResultType := @ffi_type_longdouble; aData.ResultValue := @aData.ResultExtended; end; {$endif} {$ifndef FPC_COMP_IS_INT64} ftComp: begin aData.ResultType := @ffi_type_longdouble; aData.ResultValue := @aData.ResultExtended; end; {$endif} end; end else aData.ResultTypeData := Nil; {$endif} if not Assigned(aData.ResultValue) then begin aData.ResultType := TypeInfoToFFIType(aResultType, []); if Assigned(aResultType) then aData.ResultValue := aResultValue else aData.ResultValue := Nil; end; end; if Assigned(aData.Types) then types := @aData.Types[0] else types := Nil; if ffi_prep_cif(@aData.CIF, abi, arglen, aData.ResultType, types) <> FFI_OK then raise EInvocationError.Create(SErrInvokeFailed); end; procedure FFIInvoke(aCodeAddress: Pointer; const aArgs: TFunctionCallParameterArray; aCallConv: TCallConv; aResultType: PTypeInfo; aResultValue: Pointer; aFlags: TFunctionCallFlags); var ffidata: TFFIData; i: SizeInt; arginfos: array of TFunctionCallParameterInfo; argvalues: array of Pointer; begin if Assigned(aResultType) and not Assigned(aResultValue) then raise EInvocationError.Create(SErrInvokeResultTypeNoValue); SetLength(arginfos, Length(aArgs)); SetLength(argvalues, Length(aArgs)); for i := 0 to High(aArgs) do begin arginfos[i] := aArgs[i].Info; argvalues[i] := aArgs[i].ValueRef; end; CreateCIF(arginfos, argvalues, aCallConv, aResultType, aResultValue, aFlags, ffidata); arginfos := Nil; argvalues := Nil; ffi_call(@ffidata.CIF, ffi_fn(aCodeAddress), ffidata.ResultValue, @ffidata.Values[0]); {$ifdef USE_EXTENDED_AS_COMP_CURRENCY_RES} if Assigned(ffidata.ResultTypeData) then begin case ffidata.ResultTypeData^.FloatType of {$ifndef FPC_CURRENCY_IS_INT64} ftCurr: PCurrency(aResultValue)^ := Currency(ffidata.ResultExtended / 10000); {$endif} {$ifndef FPC_COMP_IS_INT64} ftComp: PComp(aResultValue)^ := Comp(ffidata.ResultExtended); {$endif} end; end; {$endif} end; type TFFIFunctionCallback = class(TFunctionCallCallback) private fFFIData: TFFIData; fData: Pointer; fCode: CodePointer; fContext: Pointer; private class procedure ClosureFunc(aCIF: pffi_cif; aRet: Pointer; aArgs: PPointer; aUserData: Pointer); cdecl; static; procedure PassToHandler(aRet: Pointer; aArgs: PPointer); protected function GetCodeAddress: CodePointer; override; procedure CallHandler(constref aArgs: specialize TArray; aResult: Pointer; aContext: Pointer); virtual; abstract; public constructor Create(aContext: Pointer; aCallConv: TCallConv; constref aArgs: array of TFunctionCallParameterInfo; aResultType: PTypeInfo; aFlags: TFunctionCallFlags); destructor Destroy; override; end; TFFIFunctionCallbackMethod = class(TFFIFunctionCallback) private fHandler: TFunctionCallMethod; protected procedure CallHandler(constref aArgs: specialize TArray; aResult: Pointer; aContext: Pointer); override; public constructor Create(aHandler: TFunctionCallMethod; aContext: Pointer; aCallConv: TCallConv; constref aArgs: array of TFunctionCallParameterInfo; aResultType: PTypeInfo; aFlags: TFunctionCallFlags); end; TFFIFunctionCallbackProc = class(TFFIFunctionCallback) private fHandler: TFunctionCallProc; protected procedure CallHandler(constref aArgs: specialize TArray; aResult: Pointer; aContext: Pointer); override; public constructor Create(aHandler: TFunctionCallProc; aContext: Pointer; aCallConv: TCallConv; constref aArgs: array of TFunctionCallParameterInfo; aResultType: PTypeInfo; aFlags: TFunctionCallFlags); end; class procedure TFFIFunctionCallback.ClosureFunc(aCIF: pffi_cif; aRet: Pointer; aArgs: PPointer; aUserData: Pointer); cdecl; var this: TFFIFunctionCallback absolute aUserData; begin this.PassToHandler(aRet, aArgs); end; procedure TFFIFunctionCallback.PassToHandler(aRet: Pointer; aArgs: PPointer); var args: array of Pointer; i, arglen, argidx: SizeInt; resptr: Pointer; {$ifdef USE_EXTENDED_AS_COMP_CURRENCY_RES} {$ifndef FPC_COMP_IS_INT64} rescomp: Comp; {$endif} {$ifndef FPC_CURR_IS_INT64} rescurr: Currency; {$endif} {$endif} begin arglen := Length(fFFIData.Types); if fFFIData.ResultIndex >= 0 then Dec(arglen); SetLength(args, arglen); argidx := 0; for i := 0 to High(fFFIData.Types) do begin if i = fFFIData.ResultIndex then Continue; args[argidx] := aArgs[i]; if fFFIData.Indirect[i] then args[argidx] := PPointer(aArgs[i])^ else args[argidx] := aArgs[i]; Inc(argidx); end; if fFFIData.ResultIndex >= 0 then begin if fFFIData.Indirect[fFFIData.ResultIndex] then resptr := PPointer(aArgs[fFFIData.ResultIndex])^ else resptr := aArgs[fFFIData.ResultIndex]; end else begin {$ifdef USE_EXTENDED_AS_COMP_CURRENCY_RES} resptr := Nil; if Assigned(fFFIData.ResultTypeData) then begin case fFFIData.ResultTypeData^.FloatType of {$ifndef FPC_COMP_IS_INT64} ftComp: resptr := @rescomp; {$endif} {$ifndef FPC_CURR_IS_INT64} ftCurr: resptr := @rescurr; {$endif} end; end; if not Assigned(resptr) then {$endif} resptr := aRet; end; CallHandler(args, resptr, fContext); {$ifdef USE_EXTENDED_AS_COMP_CURRENCY_RES} if Assigned(fFFIData.ResultTypeData) then begin case fFFIData.ResultTypeData^.FloatType of {$ifndef FPC_COMP_IS_INT64} ftComp: PExtended(aRet)^ := rescomp; {$endif} {$ifndef FPC_CURR_IS_INT64} ftCurr: PExtended(aRet) ^ := rescurr * 10000; {$endif} end; end; {$endif} end; function TFFIFunctionCallback.GetCodeAddress: CodePointer; begin Result := fData; end; constructor TFFIFunctionCallback.Create(aContext: Pointer; aCallConv: TCallConv; constref aArgs: array of TFunctionCallParameterInfo; aResultType: PTypeInfo; aFlags: TFunctionCallFlags); var res: ffi_status; begin fContext := aContext; CreateCIF(aArgs, [], aCallConv, aResultType, Nil, aFlags, fFFIData); fData := ffi_closure_alloc(SizeOf(ffi_closure), @fCode); if not Assigned(fData) or not Assigned(fCode) then raise ERTTI.Create(SErrMethodImplCreateFailed); res := ffi_prep_closure_loc(pffi_closure(fData), @fFFIData.CIF, @ClosureFunc, Self, fCode); if res <> FFI_OK then raise ERTTI.Create(SErrMethodImplCreateFailed); end; destructor TFFIFunctionCallback.Destroy; begin if Assigned(fData) then ffi_closure_free(fData); end; constructor TFFIFunctionCallbackProc.Create(aHandler: TFunctionCallProc; aContext: Pointer; aCallConv: TCallConv; constref aArgs: array of TFunctionCallParameterInfo; aResultType: PTypeInfo; aFlags: TFunctionCallFlags); begin inherited Create(aContext, aCallConv, aArgs, aResultType, aFlags); fHandler := aHandler; end; procedure TFFIFunctionCallbackProc.CallHandler(constref aArgs: specialize TArray; aResult: Pointer; aContext: Pointer); begin fHandler(aArgs, aResult, aContext); end; constructor TFFIFunctionCallbackMethod.Create(aHandler: TFunctionCallMethod; aContext: Pointer; aCallConv: TCallConv; constref aArgs: array of TFunctionCallParameterInfo; aResultType: PTypeInfo; aFlags: TFunctionCallFlags); begin inherited Create(aContext, aCallConv, aArgs, aResultType, aFlags); fHandler := aHandler; end; procedure TFFIFunctionCallbackMethod.CallHandler(constref aArgs: specialize TArray; aResult: Pointer; aContext: Pointer); begin fHandler(aArgs, aResult, aContext); end; function FFICreateCallbackProc(aHandler: TFunctionCallProc; aCallConv: TCallConv; aArgs: array of TFunctionCallParameterInfo; aResultType: PTypeInfo; aFlags: TFunctionCallFlags; aContext: Pointer): TFunctionCallCallback; begin Result := TFFIFunctionCallbackProc.Create(aHandler, aContext, aCallConv, aArgs, aResultType, aFlags); end; function FFICreateCallbackMethod(aHandler: TFunctionCallMethod; aCallConv: TCallConv; aArgs: array of TFunctionCallParameterInfo; aResultType: PTypeInfo; aFlags: TFunctionCallFlags; aContext: Pointer): TFunctionCallCallback; begin Result := TFFIFunctionCallbackMethod.Create(aHandler, aContext, aCallConv, aArgs, aResultType, aFlags); end; const FFIManager: TFunctionCallManager = ( Invoke: @FFIInvoke; CreateCallbackProc: @FFICreateCallbackProc; CreateCallbackMethod: @FFICreateCallbackMethod; ); var OldManagers: TFunctionCallManagerArray; const SupportedCallConvs = [ccReg, ccCdecl, ccStdCall, {ccCppdecl,} ccPascal]; procedure InitFuncCallManager; begin SetFunctionCallManager(SupportedCallConvs, FFIManager, OldManagers); end; procedure DoneFuncCallManager; begin SetFunctionCallManagers(SupportedCallConvs, OldManagers); end; initialization InitFuncCallManager; finalization DoneFuncCallManager; end.