Этот вопрос возник из-за этого one.
Проблема заключается в следующем: создать не визуальный компонент, который может содержать множество команд callbacks из системы. Пользователь может определить неограниченное количество обратных вызовов в среде IDE. Обратные вызовы будут определены в TCollection как TCollectionItem.
Это шаблон, который работает довольно хорошо, но имеет некоторые недостатки. (Описано ниже) Поэтому мне интересно, если это можно было бы сделать лучше ;-)
Это основной компонент, пользователь может определить в IDE неограниченное количество функции обратного вызова через CommandsTable коллекции
Компонент с системой обратного вызова со стандартным вызовом stdcall
TMainComp = class(TComponent)
private
CallbacksArray: array [0..x] of pointer;
procedure BuildCallbacksArray;
public
procedure Start;
published
property CommandsTable: TCommandCollection read FCommandsTable write SetCommandsTable;
end;
Каждый элемент коллекции выглядит так: InternalCommandFunction - это обратный вызов, который вызывается из системы. (STDCALL Calling конвенции)
TCommandCollectionItem = class(TCollectionItem)
public
function InternalCommandFunction(ASomeNotUsefullPointer:pointer; ASomeInteger: integer): Word; stdcall;
published
property OnEventCommand: TComandFunc read FOnEventCommand write FOnEventCommand;
end;
TComandFunc = function(AParam1: integer; AParam2: integer): Word of Object;
А вот реализация. Весь процесс может быть запущен с «Start» Процедура
procedure TMainComp.Start;
begin
// fill CallBackPointers array with pointers to CallbackFunction
BuildCallbacksArray;
// function AddThread is from EXTERNAL dll. This function creates a new thread,
// and parameter is a pointer to an array of pointers (callback functions).
// New created thread in system should call our defined callbacks (commands)
AddThread(@CallbacksArray);
end;
И это проблематично код. Я думаю, что единственный способ получить указатель на функцию «InternalEventFunction» - использовать функцию MethodToProcedure().
procedure TMainComp.BuildCallbacksArray;
begin
for i := 0 to FCommandsTable.Count - 1 do begin
// it will not compile
//CallbacksArray[i] := @FCommandsTable.Items[i].InternalEventFunctionWork;
// compiles, but not work
//CallbacksArray[i] := @TCommandCollectionItem.InternalCommandFunction;
// works pretty good
CallbacksArray[i] := MethodToProcedure(FCommandsTable.Items[i], @TCommandCollectionItem.InternalCommandFunction);
end;
end;
function TEventCollectionItem.InternalEventFunction(ASomeNotUsefullPointer:pointer; ASomeInteger: integer): Word; stdcall;
begin
// some important preprocessing stuff
// ...
if Assigned(FOnEventCommand) then begin
FOnEventCommand(Param1, Param2);
end;
end;
Как я описал раньше, она работает нормально, но функция MethodToProcedure() использует стук техники. Мне нравится избегать этого, потому что программа не будет работать на системах, где включена защита выполнения данных (DEP) , а также на 64-битных архитектурах, вероятно, будет новая функция MethodToProcedure().
Знаете ли вы, что для этого лучше?
только для завершения, здесь MethodToProcedure(). (Я не знаю, кто является оригинальным автором).
TMethodToProc = packed record
popEax: Byte;
pushSelf: record
opcode: Byte;
Self: Pointer;
end;
pushEax: Byte;
jump: record
opcode: Byte;
modRm: Byte;
pTarget: ^Pointer;
target: Pointer;
end;
end;
function MethodToProcedure(self: TObject; methodAddr: Pointer): Pointer;
var
mtp: ^TMethodToProc absolute Result;
begin
New(mtp);
with mtp^ do
begin
popEax := $58;
pushSelf.opcode := $68;
pushSelf.Self := Self;
pushEax := $50;
jump.opcode := $FF;
jump.modRm := $25;
jump.pTarget := @jump.target;
jump.target := methodAddr;
end;
end;
Спасибо за ответ. Непосредственно, я не могу изменить dll. (Это неправильно, но я должен жить с ним). Так что, возможно, единственное решение - thunks. – Peter