Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Основная";
Текущий архив: 2006.03.05;
Скачать: [xml.tar.bz2];

Вниз

Как в из метода сделать обычную процедуру?   Найти похожие ветки 

 
Still Swamp   (2006-02-04 16:03) [0]

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

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

Подскажите.


 
TUser ©   (2006-02-04 16:20) [1]

Ну, адрес метода ты передать можешь, - через @TClassName.Method. Только из стека потом (при вызове метода) будут доставаться параметры, в т.ч. Self. Этот Self будет равен nil.

Параметры коллбеку передаются операционной системой, которая ни про какие методы и их неявно существующий параметр Self ничего не знает, и в стек их записывать не будет.


 
Still Swamp   (2006-02-04 16:46) [2]

Я это все понимаю. Есть какие либо методы борьбы?


 
Джо ©   (2006-02-04 16:56) [3]

Универсального метода нет. Можно создавать код-"обертку" наподобие того, как это сделано в MakeObjectInstance для оконной процедуры-метода класса.


 
Гаврила ©   (2006-02-04 17:10) [4]


> Держать в голбальной переменной его нельзя так как в
> приложении их может быть несколько.


Присваиваешь глоб. переменной, которая объявлена непосредственно надо функцией, и сразу вызываешь то, что тащит твой кал-бек. по окончании забываешь про глоб. переменную (или обниливаешь).
Если приложение многопоточное - глоб переменная как threadvar
и убедиться в отсутствии "processMessages" в коде между присвоением глоб. переменной и вызовом CallBack
и всех делов


 
TUser ©   (2006-02-04 17:10) [5]

Перепридумать логику приложения.


 
Anatoly Podgoretsky ©   (2006-02-04 17:55) [6]

Редизайн, редизайн и еще раз редизайн


 
begin...end ©   (2006-02-04 20:22) [7]

http://kladovka.net.ru/index.cgi?pid=board&rid=49


 
Still Swamp   (2006-02-05 12:37) [8]

Зловещий исходник.... :(
Тем более он мне похоже не поможет. Ловлю сообщения от TAPI, и как обычно при работе с железом, расчитывать на CriticalSection не рискну.

Редизайн и "передумать".... тоже сложно... те тупик?


 
jack128 ©   (2006-02-05 13:58) [9]

Still Swamp   (05.02.06 12:37) [8]
Ловлю сообщения от TAPI,


как я делал:


constructor TTAPIApplication.Create;
begin
 inherited;
 TapiCheck(lineInitialize(FlineApp, MainInstance,
   TapiCallbackProc, nil, FDeviceCount));
end;

procedure TTAPIModem.OpenLine;
var
 LineCallParams: TLINECALLPARAMS;
begin
 if FLineCount = 0 then
   TapiCheck(lineOpen(FTapiAppl.FLineApp, FDeviceId, FLine, FTapiVer, 0,
     Cardinal(Self), LINECALLPRIVILEGE_OWNER, LINEMEDIAMODE_DATAMODEM,
     LineCallParams));
 inc(FlineCount);
end;

procedure TapiCallbackProc(hDevice,
                            dwMessage,
                            dwInstance,
                            dwParam1,
                            dwParam2,
                            dwParam3 : DWORD);
                            stdcall;
begin
 if (dwInstance <> 0) then
   TTAPIModem(dwInstance).MessageProc(dwMessage, dwParam1, dwParam2, dwParam3);
end;

procedure TTAPIModem.MessageProc(dwMessage, dwParam1, dwParam2,
 dwParam3: DWORD);
begin
 case dwMessage of
   ...
 end;
end;


 
begin...end ©   (2006-02-05 16:33) [10]

> Still Swamp   (05.02.06 12:37) [8]

> Зловещий исходник.... :(
> Тем более он мне похоже не поможет. Ловлю сообщения от TAPI,
> и как обычно при работе с железом, расчитывать на CriticalSection
> не рискну.

Критические секции нужны только в том случае, если у Вас в приложении работает несколько потоков.

Хоть я и не являюсь автором исходника, на который дана ссылка в [7], всё же позволю себе объяснить, как он работает. Возможно, после этого станет более понятно.

Главное в исходнике -- функции MakeInstanceStdCall/FreeInstanceStdCall. Остальное относится к примеру использования этих функций -- назначению метода объекта в качестве оконной процедуры (посмотрите в конструктор TWndObject -- там с помощью SetWindowLong созданному окну назначается новая процедура, которая, в основном, будет исполнять код метода TWndObject.ObjWindowProc).

Итак, что произойдёт, когда операционная система посчитает нужным вызвать callback-функцию? Система помещает в стек все необходимые параметры в соответствии с принятым соглашением о вызове (stdcall), после чего с помощью команды CALL передаёт управление по адресу начала функции. Заметьте -- именно с помощью команды CALL, т.е. перед началом выполнения кода функции в стеке сохраняется адрес возврата -- это адрес, по которому callback-функция должна будет передать управление после окончания своей работы. Функция сделает это командой RET.

Вначале разберёмся, в чём суть проблемы с методом. Пусть мы имеем некоторый метод, объявленный с соглашением о вызове stdcall (посмотрите в пример): function ObjWindowProc(Wnd: HWND; AMsg: UINT; wPrm: WPARAM; lPrm: LPARAM): LRESULT; stdcall. Что будет, если в качестве адреса callback-функции передать адрес этого метода (т.е. адрес начала его исполняемого кода -- @TWndObject.ObjWindowProc)? Система сделает то, о чём написано в предыдущем абзаце, и передаст управление по этому адресу. Но методы в Delphi устроены таким образом, что содержат один неявный параметр -- Self, который при "нормальном" вызове метода всегда передаётся первым (независимо от типа соглашения о вызове). Код метода создаётся в расчёте на то, что стек перед исполнением метода (мы говорим именно о stdcall-методе ObjWindowProc) будет подготовлен так (сверху вниз -- от вершины стека):

1. адрес возврата
2. Self
3. Wnd
4. AMsg
5. wPrm
6. lPrm

Но система-то об этом ничего не знает! И в стек она поместит не 6 параметров, а только 5 -- исключив первый (Self). Поэтому, когда внутри метода мы будем обращаться к Self, мы будем получать значение Wnd, когда обратимся к Wnd -- получим значение AMsg, и т.д. А вместо параметра lPrm получим вообще мусор.

Но это ещё полбеды. Главная проблема и опасность вот в чём: метод ожидает, что ему через стек передано 6 параметров, и, поскольку именно он ответственен за балансировку стека (stdcall), он, отработав, удалит их стека именно 6 параметров (вместо 5). В результате стек балансируется неправильно, и его вершина с каждым вызовом всё больше и больше увеличивается.

Решение этой проблемы, приведённое в коде по ссылке в [7] -- ручная корректировка стека перед передачей управления методу. В функции MakeInstanceStdCall, которая делает из метода "обычную процедуру", происходит, в основном, следующее: выделяется область памяти, в неё помещается такой код:

POP EAX                     (1)
PUSH значение_self          (2)
PUSH EAX                    (3)
PUSH значение_адреса_метода (4)
RET                         (5)


(конечно, на момент вызова MakeInstanceStdCall объект должен быть уже создан), и указатель на эту выделенную область памяти с пятью командами передаётся в качестве адреса callback-функции.

Итак, сразу после вызова операционной системой этой функции стек будет сформирован следующим образом:

1. адрес возврата
2. Wnd
3. AMsg
4. wPrm
5. lPrm

После выполнения строки (1) кода адрес возврата окажется в EAX, а после строки (2) его место в стеке займёт Self. Третья строка поместит в стек сохранённый адрес возврата. В результате стек станет таким:

1. адрес возврата
2. Self
3. Wnd
4. AMsg
5. wPrm
6. lPrm

Посмотрите первый такой список в этом посте -- мы получили именно то, что и ожидает увидеть метод. Следующие две команды передают управление на его начало. Метод выполняется, удаляет из стека 6 параметров (но теперь это нормально, т.к. мы сами поместили в стек 1 дополнительный параметр, и их там действительно 6) и возвращает управление системе по тому адресу, который она сообщила нам перед вызовом callback-функции.

В результате получили возможность пользоваться Self, обращаться к параметрам по их настоящим именам, без плясок с бубном, и стек балансируется именно так, как нужно.


 
Still Swamp   (2006-02-05 19:34) [11]

to jack128

Ок. В помощи по lineOpen написано буквально следующее:
dwCallbackInstance - User-instance data passed back to the application with each message associated with this line or addresses or calls on this line. This parameter is not interpreted by the Telephony API.

Те если это именно то что возвращается в dwInstance то это самый лучший выход в конкретной ситуации, без необходимости отправлять метод в качестве CallBack.


 
Still Swamp   (2006-02-05 19:36) [12]

Ну да... Callback instance data passed back to the application in the callback. This DWORD is not interpreted by TAPI. Все извиняюсь, просто хелп недочитал.



Страницы: 1 вся ветка

Форум: "Основная";
Текущий архив: 2006.03.05;
Скачать: [xml.tar.bz2];

Наверх




Память: 0.5 MB
Время: 0.013 c
15-1139824720
TUser
2006-02-13 12:58
2006.03.05
Как закрыть файлы ...


2-1139890063
Азат
2006-02-14 07:07
2006.03.05
функции в Делфи для работы с динам. памятью


15-1139612060
Гаврила
2006-02-11 01:54
2006.03.05
Rouse - поздравлялки :-)


1-1138950001
Комбинатор
2006-02-03 10:00
2006.03.05
Ошибка памяти в Win98


4-1134737392
Nickolay
2005-12-16 15:49
2006.03.05
WM_DEVICECHANGE - отлов подключения/отключения USB-устройства





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский