Уважаемые авторы вопросов! Большая просьба сообщить о результатах решения проблемы на этой странице. Иначе, следящие за обсуждением, возможно имеющие аналогичные проблемы, не получают ясного представления об их решении. А авторы ответов не получают обратной связи. Что можно расценивать, как проявление неуважения к отвечающим от автора вопроса.
08-02-2008 11:32 | Комментарий к предыдущим ответам
есть четыре метода решения вопроса.
Добавлю что метод "Решение 4. Универсальный способ." не будет нормально работать на современных процессорах/операционках, если не выставить создаваемому блоку памяти доступ как минимум на PAGE_EXECUTE (нормальный вариант можно посмотреть тремя сообщениями ниже).
А способ "Решение 3. MakeObjectInstance." вообще мало для чего пригоден потому как жёстко завязан на формат TMessage, то есть там придётся по всякому извращаться, например передавать результат callback-функции через стек.
В итоге я просто переписал код DLL, дабы она ожидала TNotifyEvent, благо
у меня были сорцы.
Но, если кто столкнётся с подобным вопросом, то вот тут: http://tokolist.com/articles.php?methodtoproc
есть четыре метода решения вопроса. Статья так и называется:
"Callback-функция как метод класса в Delphi".
Код из модуля по приведенной мною ссылке делает то же самое, с минимальными и непринципиальными отличиями.
Именно так, там даже лучше, с точки зрения "жадности" к памяти, ну а я просто неслишком внимательно читал и не заметил эту ссылку.
В инете можно найти множество примеров по преобразованию методов в функции. Например в библиотеке madBasic есть функция MethodToProcedure и даже обратная ей ProcedureToMethod. Вот их исходники:
function MethodToProcedure(method: TMethod) : pointer;
begin
result := MethodToProcedure(TObject(method.data), method.code);
end;
function ProcedureToMethod(self: TObject; procAddr: pointer) : TMethod;
begin
result.Data := self;
result.Code := procAddr;
end;
Есть ещё вариант:
3. В DLL передаётся процедура, в приложении используется метод класса. А превратить одно в другое нам поможет функция MakeObjectInstance.
MakeObjectInstance поддерживает только WndProc-совместимые функции.
25-01-2008 07:55 | Комментарий к предыдущим ответам
Если DLL Ваша, то у Вас два честных варианта:
1. Переписать функцию в DLL, чтобы ей передавася метод, а не процедура.
2. Передавать в функцию DLL специально созданную процедуру, а не метод класса.
Есть ещё вариант:
3. В DLL передаётся процедура, в приложении используется метод класса. А превратить одно в другое нам поможет функция MakeObjectInstance.
Quote]Т.е. как я понимаю, единственный вариант решения вопроса - это описать вне класса обычную процедуру
и передавать ссылку на неё?
Именно так.
Метод и процедура, внешне похожие по передаваемым параметрам (от бишь например procedure и procedure of object) на амом деле - две большие разницы. У них разный стек. Дело в том, что в метод дельфа неявно в качестве параметра передает указатель Self. Т.е.
procedure of object - это (с т.з. стека процедуры) эквивалентно procedure(Self:pointer).
Господа, ну что вы человека совсем запутали.
Проблема решается стандартно.
Нужно написать процедуру в той форме, которую принимает DLL.
Внутри этой процедуры вызвать метод класса по указателю на класс, который может быть передан двумя способами:
- если процедура без параметров, то указатель на класс передаеть через глобальную переменную;
- если процедура имеет параметром указатель, то передать указатель на класс через этот параметр.
Если DLL Ваша, то у Вас два честных варианта:
1. Переписать функцию в DLL, чтобы ей передавася метод, а не процедура.
2. Передавать в функцию DLL специально созданную процедуру, а не метод класса.
Всем спасибо, за подробное объяснение.
Выход как я понял один - пересобрать DLL, изменив процедуру создания таймера таким образом, чтобы она ожидала TNotifyEvent.
25-01-2008 00:01 | Комментарий к предыдущим ответам
>>> но я все равно не понял по поводу чего начался спор
Перечитал и сам не понял. Наверное, просто поговорить захотелось с хорошим человеком ;-)
Просто у автора какая-то стороння DLL (по крайней мере, я так понял) и навряд ли в ней вызывается именно TNotifyEvent. Скорее всего, обычная процедура. Вот всех и переклинило на разнице между процедурами и методами.
P.S. Хм... Значит нет в Питере никакой аномалии? А я уже собрался научное иссследование выполнять :D
24-01-2008 19:20 | Комментарий к предыдущим ответам
все это замечательно, но я все равно не понял по поводу чего начался спор - что я написал в своем примере такого, что может нарушить устойчивость системы?)) а то както получается разговор немого с глухим) а на счет того что вы мне прокоментировали я с вами полностю согласен))
если автору был важен НЕПОСРЕДСТВЕННЫЙ адрес процедуры в классе то да кончено нада писать addr(Proc) . но на мой взгляд это уже немного отклонение от ООП() - мало ли авдруг автор решит передать туда процедуру из приватных или защищенных. работать - не спорю будет - но ООП не для того создавался чтобы его обходить - проще(лучьше сказать грамотнее) тогдапосылать сообщение классу чтобы он вызвал свою внутреннюю процедуру
24-01-2008 09:28 | Комментарий к предыдущим ответам
>>> я так понял что вы мне эту историю рассказали поповоду того что <...>
Я Вам эту историю рассказал по поводу того, что хороший стиль программирования подразумевает учет всех возможных вариантов. Необходимость этого существенно возрастает если Вы при разработке используете недокументированные возможности или полузаконные приемы, так как в этом случае разработчик инструментальных средств Вам вообще ничего не гарантирует.
Данный конкретный прием не является законным. И я уже показал одну проблему, которая может возникнуть, и набросал еще гипотез о возможном возникновении проблем. То есть, мало сказать, какой оператор использовать, надо еще определить, в каком случае его можно использовать, а в каком не стоит, так как это может привести к печальным последствиям. Называется это область применимости метода. Область применимости документированных возможностей приводится в хелпе и прочей документации. Ответственность за это несет Borland и CodeGear. Область применимости данного метода не приводится нигде. Надо самим НИР выполнять. Откачества проведения этого НИРа зависит дальнейшая судьба разработки.
>>> ну я вам не буду рассказывать про жизненный цикл прогр продуктов
Делайте разницу между жизненным циклом тиражируемого коммерческого программного продукта и системы собственной разработки для внутреннего употребления. У меня программы на Delphi-1 (и даже мелкие утилитки на Turbo Pascal) живы и до сих пор работают. А вот система, конечная версия которой была написана на Delphi-3, благополучно загнулась. И произошло это только потому, что автор в процессе разработкипрорабатывал только основную линию, но не тратил время на тщательную проработку деталей. И жила эта система ровно до тех пор, пока разработчик был доступен и на вносил в систему правки на каждый случай, когда пользователи споткались о непроработанную неучтенную им ситуацию.
>>> это я к тому что лень - двигатель прогресса - чем проще код тем лучьше
Невольно возникает желание поиграть словами и ответить Вам старой русской поговоркой "Простота хуже воровства" ;-)
Дабы все же заниматься делом, а не жонглировать словами поясню, что приведенный Вами упрощенный вариант вычисления значения логического выражения является документированной возможностью языка, то есть разработчик гарантирует, что такая форма записи корректна во всех ситуациях, а также гарантирует, что эта корректность сохранится и в последующих версиях языка. Могу привести даже более яркий пример. Антон Григорьев не любит в своем коде ставить точку-с-запятой после оператора, ха которым следует слово end. И это тоже не хак. Это документированная возможность языка, которая изначально постулировалась Виртом. Есть мой любимый прием подмены классов VCL собственными модификациями этих классов. Полузаконный прием. Но после тщательного всестороннего анализа и обсуждения было определено, что в существующих условиях использование данного приема безопасно. А также были выявлены условия, при возникновении которых применять этот прием бездумно не стоит.
Еще разобраться, как лучше поступить ленивому человеку. Что легче: написать чуть более длинный код или обосновать применимость метода, который позволяет писть более экономный код :D
P.S. И маленькая просьба: сообщения, которые не являются непосредственным ответом на вопрос, принято помечать как "Комментарий к предыдущим ответам".
ухты))) мир тесен))) я так понял что вы мне эту историю рассказали поповоду того что я решил поставить там TNotyfyEvent)) или нет - если да - то в 99% случаях этот код подойдет для той проблемы что представил автор. а на счет 1996-1999 - ну я вам не буду рассказывать про жизненный цикл прогр продуктов)))ведь мы пишем куски кода на асме - а это самый опасный участок в плане переносимости на новые платформы. если вы про то что можно разными путями решать данную проблему то я отвечу(как я уже гдето писал) что да можно написать
If P then а можно и If P=true then а можно и как некоторые тут предложили If BoolToStr(P)='True' then...)))) это я к тому что лень - двигатель прогресса - чем проще код тем лучьше.
24-01-2008 06:52 | Комментарий к предыдущим ответам
to Knight:
Знаете, Ваш "прямолинейный" подход напомнил мне подход к работе одного моего знакомого. При разработке он действовал прямолинейно, как перпендикуляр. Детально расписать не получится, но если выразиться образно, то получится что-то вроде "Если нажата эта клавиша, то делаем это, а если эта, то вот это". О том, что кроме этих двух клавиш на клавиатуре остается еще сотня, он просто не задумывался. В 1996 году он написал средней сложности программу. И периодически занимался ее переделкой (инициированной, в основном, замечаниями пользователей) вплоть до 1999 года, после чего благополучно сменил место работы. Годика через два после очередной смены софта его программа напрочь отказалась запускаться. Исправить ситуацию не удалось никакими администраторскими ухищрениями. Держать для работы с этой программой второй пентиум с Вин-95 посчитали излишней роскошью, поэтому программу просто выбросили. Благо это был не коммерческий продукт, а инструмент для автоматизации собственной деятельности.
P.S. Кстати, этот человек тоже с Петропавловска-Камчатского ;-) У ваас там какая-то аномалия наверное, потому что всего лишь 30 км на север (Елизово, то бишь я) и такого уже нет :D
что вы раздули из мухи слона прямо уж не знаю даже)))
ну вы передаете адрес процедуры - и ошибка - ясен пень вам то нада передавать метод - это как уже упомянали адрес процедуры в совокупности с адресом класса
так в чем проблема пишем
procedure DoMyProc(Proc : TNotifyEvent);
begin
Proc(nil);
end;
procedure TForm1.A;
begin
showmessage('sd');
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
DoMyProc(A);
end;
все пашет как миленькое))) сам тестил - че спрашивется такие дебаты устроили
24-01-2008 04:03 | Комментарий к предыдущим ответам
Если изменим модель вызова и будем передавать параметры через стек, то теоретически возможны проблемы
То-то я думаю, почему не падает при модели вызова register :-)
>>> но нужно это как-то убедительно доказать
Первый вариант, который касается использования полей объекта, я уже доказал. По поводу остальных пока только гипотезы, которые нужно проверить, чтобы доказательство выглядело убедительно. Излагаю в форме гипотез.
1. Вызов виртуального метода. Какая именно из реализаций метода будет вызвана, зависит от того, к какому именно классу относится данный объект. Не факт, что без знания объекта будет вызван именно тот метод, который нужен, а не реализация его для предка.
2. Передача параметров через стек. Каюсь, я вчера все же заглянул немного в реализацию. При передаче параметров через регистры в памяти создается структура TMethod (сначала указатель на процедуру, потом указатель на класс), а в функцию передается указатель на эту структуру. Детали не рыл, но, вроде бы, безопасно. Если изменим модель вызова и будем передавать параметры через стек, то теоретически возможны проблемы, так как в этом случае ссылка на объект должна тоже передаваться через стек, но функция из DLL этого не знает. Могут возникнуть расхождения при освобождении стека.
На всякий случай повторю: это гипотезы. Их сначала нужно проверить. Ну, или знать "от" и "до" все нюансы вызовов методов и процедур.
24-01-2008 03:37 | Комментарий к предыдущим ответам
Если procedure DoMyProc(ProcAddress: Pointer); описана с моделью вызова stdcall в случае, описанном Сергем Хачатуровым, рискуем получить AV при выходе из TForm1.BitBtn1Click.
З.Ы. Вспомнился вопрос, где автор хотел передать для вызова обычную процедуру там, где требовалась передача метода »вопрос КС №56522«.
Может чем поможет.
>>>единственный вариант решения вопроса - это описать вне класса обычную процедуру, но нужно это как-то убедительно доказать
В смысле, что доказать? Что метод не совместим с обычной процедурой? Так это просто - в метод передается еще один неявный параметр - Self, кроме тех, что явно заявлены в его заголовке.
если процедура, пусть и описанная внутри класса, не использует его полей, то вполне проходит то, что я написал.
если использует, то на первый взгляд единственный вариант решения вопроса - это описать вне класса обычную процедуру, но нужно это как-то убедительно доказать
>>> Т.е. как я понимаю, единственный вариант решения вопроса - это описать вне класса обычную процедуру и передавать ссылку на неё?
Это честный вариант. Можно воспользоваться и нечестным, но тогда (как и всегда при использованеии читерских приемов и недокументированных возможностей) вся ответственность за возможные сбои при работе программы ляжет на Вас.
Сергей Хачатуров привел пример работоспособного вызова метода класса таким способом.
Мне необходим именно указатель на метод
Эта процедура описанна в моём классе как событие
Скорее всего чужая DLL понятия не имеет о классах Delphi и их методах.
требует в качестве параметра именно указатель на процедуру, которою будет вызывать по этому таймеру
Обычно есть возможность передать в указанную процедуру обратного вызова произвольный параметр типа Pointer. И передавать в нем ссылку на Ваш класс (так реализован класс TTread).
Например, если прототип процедуры, вызываемой по таймеру
VOID CALLBACK TimerCallback(
PVOID lpParameter);
то при создании таймера можно указать параметр для функции обратного вызова и передать в нем Pointer(SomeObject).
То в lpParameter можно передать
>>> ну... а что получится?
Ничего не получится. Текста в окошке нет.
>>> ссылка на метод передается, процедура выполняется, что и требовалось доказать...
Эт прям как сисадмин на стрельбище...
"С моей стороны пули вылетели. Если не можете найти их в мишени, то проблемы у вас" ;-)
Пока спал, продолжал думать (во, запала задача!). Насчет утечек, скорее всего, я не прав. Но зато придумал еще один потенциальнго сбойный момент дл такого подхода, касающийся виртульных методов. Но на проверку сейчас уже нет времени :(
23-01-2008 18:27 | Комментарий к предыдущим ответам
ну... а что получится?
ссылка на метод передается, процедура выполняется, что и требовалось доказать... а переменная... вопрос-то был в сущности про то "как получить .. процедуру ... , которою будет вызывать таймер"
А Ваш подход основан в основном на остутствии фундаментальных знаний (для чего и пишем, что неправильно - укажут на ошибки умные люди)
procedure DoMyProc(ProcAddress : Pointer);
var
MyProc : procedure;
begin
MyProc := ProcAddress;
MyProc;
end;
procedure TForm1.ShowHelloMessage;
begin
ShowMEssage(FMsgStr);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FMsgStr:='Hallo!';
end;
procedure TForm1.BitBtn1Click(Sender: TObject);
var
M : TMyMethod;
begin
M := ShowHelloMessage;
DoMyProc(@M);
end;
Для простоты ушел от DLL, используя локальную функцию вместо вызываемой из DLL. Ну, и еще изменил название типа с TMethod на TMyMethod, так как тип TMethod уже имеется и не хочется его переписывать.
И еще один момент. Я не уверен, что Ваш подход не рпиведет к каким-нибудь неприятностям, типа утечек памяти. Но разбираться в этом в два часа ночи уже лениво ;-)
Вызов процедуры и вызов метода класса -- это совершенно разные вещи. Если функция из DLL требует адрес процедуры, которую она потом будет вызывать (подозреваю, что вызывать она будет именно процедуру), то, передав ей вместо адреса процедуры адрес метода, Вы рискуете получить... даже не соображу сходу, что именно.
Так что уточните, что именно требует функция из DLL.
>>> Может я ошибаюсь, но почему то в моей голове вертятся слова "of object"
При описании процедурного типа добавление в конце "of Object" говорит, что этот тип не процедура, а метод класса.
type
TMyProc = procedure(N : Integer); // процедура
TMyMethod = procedure(N : Integer) of object; // метод класса
Только я в упор не понял, какой Вам от этого гешефт. Вы то сами как раз ничего не объявляете.
Мне необходим именно указатель на метод.
Я использую чужую DLL, одна из функций которой создаёт таймер, и требует в качестве параметра
именно указатель на процедуру, которою будет вызывать по этому таймеру. Эта процедура описанна в моём классе как событие. Вот указатель на неё я и хочу получить.
Может я ошибаюсь, но почему то в моей голове вертятся слова "of object".
Не нужно ли их использовать при описании метода, адрес которого я хочу получить?
>>> При попытке получить адрес, выскакивает ошибка: "Variable required"
А это Delphi от Вас защищается, чтобы Вы глупостей не наделали ;-) Потому что работать с методами класса просто обращаясь по адресам этих методов нельзя. Правильное обращение состоит не только из указателя на метод, но и из указателя на экземпляр класса.
Лучше скажите, чего именно Вы хотите добиться таким способом. Возможно, это можно сделать более честными способами, используя по полной возможности ООП, реализованные в Delphi.
Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter. Функция может не работать в некоторых версиях броузеров.