Параметризовать конструктор

Вопросы программирования на Free Pascal, использования компилятора и утилит.

Модератор: Модераторы

Параметризовать конструктор

Сообщение Дож » 22.07.2014 21:57:54

Приветствую!

Я хочу получить указатель на конструктор объекта (именно object, а не class), дальше передать его параметром куда-то, и применить этот конструктор для инициализации объекта. Как это сделать (если это вообще возможно)? Чисто гипотетически это возможно, но как это написать на паскале, у меня пока даже идей нет.

Не надо писать, что я хочу странного, мне это нужно для реализации фабрики и я вполне осознаю опасности, связанные с непрямым вызовом конструктора.

Что забавно, но следующий код
Код: Выделить всё
{$MODE OBJFPC}
type
  TMyObject = object
    constructor Init;
  end;

constructor TMyObject.Init;
begin
end;

var
  P: function: QWordBool of object;

begin
  P := @TMyObject(nil^).Init;
end.

выдаёт весьма странную ошибку
Код: Выделить всё
constructor.pas(15,8) Error: Incompatible types: got "PROGRAM.<procedure variable type of function:QWordBool of object;Register>" expected "PROGRAM.<procedure variable type of function:QWordBool of object;Register>"

Что бы это могло значить?
Аватара пользователя
Дож
энтузиаст
 
Сообщения: 899
Зарегистрирован: 12.10.2008 16:14:47

Re: Параметризовать конструктор

Сообщение Vapaamies » 22.07.2014 22:22:35

Дож писал(а):Не надо писать, что я хочу странного, мне это нужно для реализации фабрики и я вполне осознаю опасности, связанные с непрямым вызовом конструктора.

Классная задачка! Такая штука была в Turbo Vision, где конструктор Load вписывался в регистрационные записи объектов и потом вызывался для чтения из файла ресурсов Turbo Vision -- предка дельфийских DFM. Для начала нужно смотреть там.

В приведенном коде вижу ошибку. ЕМНИП, экземпляр объекта создается внешним вызовом New/GetMem, после чего ссылка на только что выделенный блок памяти передается конструктору как параметр Self:

Код: Выделить всё
type
  PMyObject = ^TMyObject;
  TMyObject = object
    constructor Init;
  end;

  TMyObjectInit = procedure of object;

constructor TMyObject.Init;
begin
end;

var
  MyObjectFactory: TMyObjectInit = @TMyObject.Init;
  P: PMyObject;

begin
  GetMem(P, TypeInfo(P^).InstanceSize); // условный код
  MyObjectFactory(P^);
end.
Аватара пользователя
Vapaamies
постоялец
 
Сообщения: 291
Зарегистрирован: 24.07.2012 22:37:59
Откуда: Санкт-Петербург

Re: Параметризовать конструктор

Сообщение zub » 22.07.2014 23:11:50

Как сказал Vapaamies - конструктор это процедура, не функция, соответственно
Код: Выделить всё
var
  P: procedure of object;

begin
  P := @TMyObject.Init;
end.

он не возвращает экземпляр объекта, а инициализирует уже выделенный кусок памяти
Также мало знать адрес конструктора, надо знать адрес VMT создаваемого объекта (получить его можно TypeOf(TMyObject);) и соответствующим образом настроить регистры
Код: Выделить всё
//obj:pointer; указатель на ранее выделенный кусок памяти нужного размера
//pvmt:pointer; указатель на вмт нужного типа, полученный при регистрации типов объектов известных программе
//tm:tmethod;
//tm.Code - адрес конструктора,
//tm.Data:=obj т.е. указатель на создаваемый экземпляр - в данном случае неважен
//обойтись можно и без tm, просто приведенный кусок работает через него, т.к. это
//выдрано из программы которая в рантайме вызывает не только конструкторы, но и
//простые методы, с ними через tmethod проще - без ассемблера

  {$IFDEF fpc}
  {$ifdef CPU32}
  begin
  asm
     mov edx,[pvmt]
     mov eax,[obj]
     call tm.Code
  end;
  end;
{$endif CPU32}
{$ifdef CPU64}
  begin
  asm
     {$ifdef WIN64}
     mov rcx,[obj]
     mov rdx,[pvmt]
     call tm.Code
     {$else}
     mov rdi,[obj]
     mov rsi,[pvmt]
     call tm.Code
     {$endif WIN64}
  end;
  end;
{$endif CPU64}
{$ENDIF}

Вот рабочий кусок вызова конструктора в рантайме (если я не сломал чего при копипастенье и приведении к человечьему виду) работает в Win\Lin 32\64

>>Не надо писать, что я хочу странного, мне это нужно для реализации фабрики и я вполне осознаю опасности, связанные с непрямым вызовом конструктора.
Это не странно, это удобно)) Странно когда перед вызовом еще надо сформировать стек с параметрами... Ктонибудь знает как? Ато мой недоскрипт только и умеет вызывать методы без параметров((
zub
долгожитель
 
Сообщения: 2884
Зарегистрирован: 14.11.2005 23:51:26

Re: Параметризовать конструктор

Сообщение Vapaamies » 23.07.2014 00:53:40

zub писал(а):Также мало знать адрес конструктора, надо знать адрес VMT создаваемого объекта (получить его можно TypeOf(TMyObject);) и соответствующим образом настроить регистры

Проще переобъявить фабрику:

Код: Выделить всё
type
  TMyObjectInit = procedure(TypeInfo: Pointer) of object;
Аватара пользователя
Vapaamies
постоялец
 
Сообщения: 291
Зарегистрирован: 24.07.2012 22:37:59
Откуда: Санкт-Петербург

Re: Параметризовать конструктор

Сообщение zub » 23.07.2014 01:23:01

в смысле? Я сейчас уже не вспомню, но когда я ковырял это дело, оно не под какое соглашение о вызовах это не попадало, возможно плохо ковырял - но без асма создать объект тип которого определен в рантайме у меня не получалось
zub
долгожитель
 
Сообщения: 2884
Зарегистрирован: 14.11.2005 23:51:26

Re: Параметризовать конструктор

Сообщение runewalsh » 23.07.2014 01:35:25

Из RTL (Objects):
Код: Выделить всё
type
  VoidConstructor = function(VMT: pointer; Obj: pointer): pointer;
  PointerConstructor = function(VMT: pointer; Obj: pointer; Param1: pointer): pointer;

function CallVoidConstructor(Ctor: pointer; Obj: pointer; VMT: pointer): pointer; inline;
begin
  CallVoidConstructor := VoidConstructor(Ctor)(Obj, VMT);
end;

function CallPointerConstructor(Ctor: pointer; Obj: pointer; VMT: pointer; Param1: pointer): pointer; inline;
begin
  CallPointerConstructor := PointerConstructor(Ctor)(Obj, VMT, Param1);
end;


Я не помню семантику, что-то типа «если Obj = nil, память под объект автоматически выделится из кучи», а что возвращается, сам объект (т. е. Obj в случае Obj <> nil)?.. проверь, короче. Предупреждаю, когда я это использовал, у меня то ли не работал, то ли не освобождал память вызов Fail в конструкторе, лучше в самом деле добавь статические функции-обёртки.

И, чтобы не создавать бесполезную тему, спрошу заодно здесь. Есть ли способ заставить исключения, брошенные из конструкторов object'ов, освобождать только что выделенную память? new(PObj, Init) => вызов Fail внутри Init немедленно dispose'ает объект и возвращает nil, но если взамен бросить исключение, происходит утечка.
Аватара пользователя
runewalsh
энтузиаст
 
Сообщения: 578
Зарегистрирован: 27.04.2010 00:15:25

Re: Параметризовать конструктор

Сообщение Vapaamies » 23.07.2014 02:05:23

runewalsh писал(а):Есть ли способ заставить исключения, брошенные из конструкторов object'ов, освобождать только что выделенную память?

Использовать классы. :mrgreen: Классы на то и создаются реализованной в RTL фабрикой, как раз чтобы обрабатывать исключения.
Аватара пользователя
Vapaamies
постоялец
 
Сообщения: 291
Зарегистрирован: 24.07.2012 22:37:59
Откуда: Санкт-Петербург

Re: Параметризовать конструктор

Сообщение Дож » 23.07.2014 18:20:04

Хорошо, что я встретил тут понимание :) Пример, который я привёл, интересен скорее ошибкой — она сообщает о несовпадении типов в то время, как они совпадают.

Vapaamies писал(а):
Код: Выделить всё
type
  PMyObject = ^TMyObject;
  TMyObject = object
    constructor Init;
  end;

  TMyObjectInit = procedure of object;

constructor TMyObject.Init;
begin
end;

var
  MyObjectFactory: TMyObjectInit = @TMyObject.Init;
  P: PMyObject;

begin
  GetMem(P, TypeInfo(P^).InstanceSize); // условный код
  MyObjectFactory(P^);
end.


Можно уточнить для какой это версии фпц и какой режим компиляции включать? Я перепробовал следующий код в 2.6.2 и 2.7.1 в разных ОС, на разных машинах и с разными режимами
Код: Выделить всё
type
  PMyObject = ^TMyObject;
  TMyObject = object
    constructor Init;
  end;

  TMyObjectInit = procedure of object;

constructor TMyObject.Init;
begin
end;

var
  MyObjectFactory: TMyObjectInit;
  P: PMyObject;

begin
  MyObjectFactory := @TMyObject.Init;
  New(P);
  MyObjectFactory(P^);
end.


Везде получаю ошибку несовместимости типов (тип варируется от режима компиляции), например:
Код: Выделить всё
> C:\FPC\2.6.2\bin\i386-win32\fpc -Mobjfpc myobject.pas
Free Pascal Compiler version 2.6.2 [2013/02/12] for i386
Copyright (c) 1993-2012 by Florian Klaempfl and others
Target OS: Win32 for i386
Compiling myobject.pas
myobject.pas(18,22) Error: Incompatible types: got "<address of function:LongBool of object;Register>" expected "<procedure variable type of procedure of object;Register>"
myobject.pas(19,8) Warning: use extended syntax of NEW and DISPOSE for instances of objects
myobject.pas(20,21) Error: Wrong number of parameters specified for call to "<Procedure Variable>"
myobject.pas(22) Fatal: There were 2 errors compiling module, stopping
Fatal: Compilation aborted
Error: C:\FPC\2.6.2\bin\i386-win32\ppc386.exe returned an error exitcode (normal if you did not specify a source file to be compiled)
Аватара пользователя
Дож
энтузиаст
 
Сообщения: 899
Зарегистрирован: 12.10.2008 16:14:47

Re: Параметризовать конструктор

Сообщение Vapaamies » 23.07.2014 18:58:20

Дож писал(а):Можно уточнить для какой это версии фпц и какой режим компиляции включать?

Не знаю, я ж код от балды писал. :lol:

По сообщению компилятора могу предложить исправить тип фабрики так:
Код: Выделить всё
type
  TMyObjectInit = function: LongBool of object;

Отдельного исследования требует смысл возвращаемого булева значения из конструктора, если угадал с прототипом. Вполне возможно, что это сигнал о неудавшейся инициализации, ответом на которую может быть освобождение памяти:
Код: Выделить всё
var
  MyObjectFactory: TMyObjectInit = @TMyObject.Init;
  P: PMyObject;

begin
  GetMem(P, TypeInfo(P^).InstanceSize); // условный код
  try
    if not MyObjectFactory(P^) then
      FreeMem(P);
  except
    FreeMem(P);
  end;
end.
Аватара пользователя
Vapaamies
постоялец
 
Сообщения: 291
Зарегистрирован: 24.07.2012 22:37:59
Откуда: Санкт-Петербург

Re: Параметризовать конструктор

Сообщение Дож » 23.07.2014 23:47:08

Vapaamies писал(а):По сообщению компилятора могу предложить исправить тип фабрики так:
Код: Выделить всё
type
  TMyObjectInit = function: LongBool of object;



В таком варианте оно скажет
Код: Выделить всё
myobject.pas(14,36) Error: Incompatible types: got "<address of function:LongBool of object;Register>" expected "<procedure variable type of function:LongBool of object;Register>"


Но если мы ещё немного поработаем, то… придём к тому варианту, который у меня в начальном посте! И типы в сообщении ошибки совпадут, но легче от этого не станет.
Аватара пользователя
Дож
энтузиаст
 
Сообщения: 899
Зарегистрирован: 12.10.2008 16:14:47

Re: Параметризовать конструктор

Сообщение Vapaamies » 24.07.2014 01:03:13

Сейчас тыкнулся в Delphi и увидел, что уже подзабыл детали. Процедура _ObjSetup вызывается в нем с нулевым указателем, и _ObjSetup вызывает GetMem сама.

FPC я знаю почти никак, но исходники лежат. Тыкнулся, но ни имени _ObjSetup, ни аналога по-быстрому в них не нашел. В исходниках FPC вообще черт ногу сломит, на мой взгляд. Кругом include на include, будто бы и не на Паскале писано.

Нужно найти или место в RTL, отвечающее за инициализацию объектов (по аналогии с Delphi), или путем дизассемблирования выяснить фактический прототип конструктора и его связь с процедурами выделения памяти.
Аватара пользователя
Vapaamies
постоялец
 
Сообщения: 291
Зарегистрирован: 24.07.2012 22:37:59
Откуда: Санкт-Петербург

Re: Параметризовать конструктор

Сообщение Sergei I. Gorelkin » 24.07.2014 11:33:40

файл rtl/inc/generic.inc, процедура fpc_help_constructor, и до кучи fpc_help_destructor и fpc_help_fail.
Аватара пользователя
Sergei I. Gorelkin
энтузиаст
 
Сообщения: 1395
Зарегистрирован: 24.07.2005 14:40:41
Откуда: Зеленоград

Re: Параметризовать конструктор

Сообщение Vapaamies » 24.07.2014 17:23:17

А что означает в них параметр vmtpos?
Аватара пользователя
Vapaamies
постоялец
 
Сообщения: 291
Зарегистрирован: 24.07.2012 22:37:59
Откуда: Санкт-Петербург

Re: Параметризовать конструктор

Сообщение Sergei I. Gorelkin » 24.07.2014 17:41:40

vmtpos - это смещение в памяти относительно начала экземпляра объекта, по которому находится указатель на vmt.
Аватара пользователя
Sergei I. Gorelkin
энтузиаст
 
Сообщения: 1395
Зарегистрирован: 24.07.2005 14:40:41
Откуда: Зеленоград

Re: Параметризовать конструктор

Сообщение Дож » 24.07.2014 22:09:43

Дизассемблировать не проблема (это win32):
Код: Выделить всё
# [19] O.Init;
   movl    $VMT_$P$PROGRAM_$$_TMYOBJECT,%edx
   movl    $U_$P$PROGRAM_$$_O,%eax
   call   P$PROGRAM$_$TMYOBJECT_$__$$_INIT$$LONGBOOL


Т.е. в конструктор без параметров передаётся два неявных параметра — указатель на объект и указатель на его VMT. Ассемблерный код вызова конструктора zub'а выше в треде похож на это.

Это же соотносится со знанием от runewalsh (почти, потому что VMT и Obj не в том порядке идут), и тогда действительно можно написать код, который вроде бы работает ожидаемо
Код: Выделить всё
type
TParent = object
public
  constructor Init;
  procedure Test; virtual;
end;

TSon = object(TParent)
  procedure Test; virtual;
end;

constructor TParent.Init;
begin
end;

procedure TParent.Test;
begin
  Writeln('TParent');
end;

procedure TSon.Test;
begin
  Writeln('TSon');
end;

type
  TVoidConstructor = function (Obj: Pointer; VMT: Pointer): Pointer;

var
  P: TParent;
  C: TVoidConstructor;

begin
  C := TVoidConstructor(@TSon.Init);
  C(@P, TypeOf(TSon));
  P.Test;
end.


Вывод:
Код: Выделить всё
C:\proj\user\doj\fpc\constructors>fpc -gl -Mobjfpc ptr.pas && .\ptr.exe
Free Pascal Compiler version 2.7.1 [2013/11/19] for i386
Copyright (c) 1993-2013 by Florian Klaempfl and others
Target OS: Win32 for i386
Compiling ptr.pas
Linking ptr.exe
37 lines compiled, 0.2 sec, 36848 bytes code, 1284 bytes data
TSon


Теперь мне нужно немного помедитировать над этим решением, потестировать в разных окружениях, но само решение выглядит приемлемо грязным.

Кроме того, если я правильно понимаю, то умея вызывать fpc_help_constructor, можно вообще не использовать конструктор, а инициализировать VMT самостоятельно. Но fpc_help_constructor не виден из паскаля. Просто запомню это на всякий случай.
Аватара пользователя
Дож
энтузиаст
 
Сообщения: 899
Зарегистрирован: 12.10.2008 16:14:47

След.

Вернуться в Free Pascal Compiler

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 16

Рейтинг@Mail.ru