Все знают, что в fpc можно динамически импортировать функции из библиотек. Но мало кто может сделать это реально динамически в run-time, т.е. во время выполнения, как это делают некоторые скриптовые языки. Для языка си есть библиотека ffi, которая позволяет такое реализовать, для паскаля же даже примеров нет как это реализовать.
Методом проб и ошибок, подглядываний у си и т.д., получились вот такие классы - TDynCall и TDynRecord, которые поддерживают 2 вида вызова - cdecl и stdcall. Если кто сделает fastcall буду рад (мне лень, и он не стандартизирован).
Возможности модуля:
- Импорт функций во время выполнения, в том числе и из dll, so и т.д.
- Поддержка таких типов как integer, byte, char, boolean, pansichar, pwidechar, pointer, double, single, word, shortint
- Возможность передавать out, var параметры в функцию
- TDynRecord - класс для эмуляции рекорда во время выполнение с выравниванием в 4 байта (это важный параметр), он поддерживает также все описанные типы
- Возвращение результата, кроме рекорда поддерживает все типы (в том числе и double)
- Совместим с fpc и delphi, тестил пока только под windows x86 (для 64бит скорее надо что-то переделывать).
* Последние версии fpc не требуют у меня вставки {$ASMMODE INTEL}, может быть это ставится в настройках.
Класс TDynCall.
- Код: Выделить всё
type
TDynCall = class(TObject)
protected
FuncPtr: Pointer;
StackSize: Longint;
ParamCount: Integer;
Blocks: Array[0..100] of PushParam;
function Resize(const IncSize: Integer): Pointer; inline;
public
Return: ReturnParam;
Method: TCallMethod;
ReturnType: TCallVarType;
procedure Clear;
procedure PushLongInt(const I: Longint);
procedure PushPtr(const P: Pointer);
procedure PushWord(const W: Word);
procedure PushDouble(const D: Double);
procedure PushSingle(const S: Single);
procedure PushByte(const B: Byte);
procedure PushBool(const B: Boolean);
procedure PushChar(const C: AnsiChar);
procedure PushVar(var X);
procedure PushPAnsiChar(const PA: PAnsiChar);
procedure PushPWideChar(const PW: PWideChar);
procedure PushRecord(const Rec: TDynRecord);
procedure SetFunc(aFunc: Pointer);
procedure Call;
constructor Create;
destructor Destroy; override;
end;
Методы и свойства
- Method: TCallMethod - способ вызова cdecl или stdcall (по-умолчанию установлен в cdecl).
- ReturnType - тип возвращаемого значения, если ничего не возвращает устанавливаем в cvtVoid.
- Return - собственно сам результат функции записывается в этот рекорд.
- SetFunc(aFunc: Pointer) - устанавливаем ссылку на функцию
- Push* - запись параметров функции на стек
- Clear - очистка параметров в стеке
- Call - вызов функции
Класс TDynRecord.
Класс эмулирует record запись, в си это struct. Важно, не используете packed records - он их не поддерживает, только рекорды с выравниванием в 4 байта.
- Код: Выделить всё
TDynRecord = class(TObject)
protected
Align: Integer;
procedure WriteItem;
procedure WriteConst(const V; const Size: Integer);
public
Seek: Integer;
Size: Integer;
Data: PByteArray;
procedure WriteInt(const I: Longint);
procedure WriteDouble(const D: Double);
procedure WriteSingle(const S: Single);
procedure WriteByte(const B: Byte);
procedure WriteChar(const C: AnsiChar);
procedure WriteBool(const B: Boolean);
procedure WriteWord(const W: Word);
procedure WritePtr(const P: Pointer);
procedure WritePAChar(const P: PAnsiChar);
procedure WritePWChar(const P: PWideChar);
procedure Clear;
constructor Create(aAling: Integer = PtrSize); overload;
constructor Create(Vals: array of TVarRec; aAling: Integer = PtrSize); overload;
destructor Destroy; override;
end;
Тут все просто, пишем значения в порядке объявления переменных в рекорде, только учитывая тип!
Примеры использования:
1. Рекорды в параметрах
- Код: Выделить всё
type
PMy = ^TMy;
TMy = record
x,y: integer;
w: double;
end;
function test(const R: TMy): integer; cdecl;
begin
Result := Trunc( R.x + R.y + R.w );
end;
procedure TfmMain.BitBtn1Click(Sender: TObject);
var
C: TDynCall;
R: TDynRecord;
begin
R := TDynRecord.Create();
R.WriteInt(20); // .x = 20
R.WriteInt(40); // .y = 40
R.WriteDouble(50.55); // .w = 50.55
// R := TDynRecord.Create([20,40,50.55]); // или так
C := TDynCall.Create;
C.Method := cmCdecl; // cdecl вызов
C.ReturnType := cvtInteger; // функция возвращает Longint
C.SetFunc(@test); // задаем вызываемую функцию
C.PushRecord(R); // помещаем рекорд в стек
C.Call; // вызываем
ShowMessage(IntToStr(C.Return.lval));
C.Free;
R.Free;
end;
Тут даже не важно, вы можете также передавать PMy, т.е. ссылочный тип, в этом случае код останется тот же.
2. Var параметр у функции
- Код: Выделить всё
procedure test2(var x: integer); stdcall;
begin
X := X + 7520;
end;
procedure TfmMain.BitBtn1Click(Sender: TObject);
var
C: TDynCall;
my: integer;
begin
my := 30;
C := TDynCall.Create;
C.Method := cmStdcall; // stdcall вызов
C.ReturnType := cvtVoid; // функция ничего не возвращает
C.SetFunc(@test2); // задаем вызываемую функцию
C.PushVar(my); // помещаем переменную в стек
C.Call; // вызываем
ShowMessage(IntToStr(my)); // выведет 7550, my := 30 + 7520
C.Free;
end;
Грубо говоря переменная передается как Pointer.
Где скачать класс: Тут http://code.google.com/p/orionphp/source/browse/trunk/libs/ori_DynCall.pas
Он не имеет никаких зависимостей.
P.S. Модификатор const никак не влияет на функциональность класса.