cdecl - как это работает и для чего это?

Общие вопросы программирования, алгоритмы и т.п.

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

cdecl - как это работает и для чего это?

Сообщение vitaly_l » 23.03.2013 22:12:33

Есть примерно вот такой код:

TReadCallback = procedure(Chunk: TChunkRec; Data: Pointer); cdecl;

Если я правильно понял, то cdecl - каким-то хитрым образом перемещает данные справа налево в смысле заполняет через стек левую переменную.
Справа Data: Pointer... и он априори пустой... слева Chunk и он read из Stream... (в смысле я не понимаю как это работает даже прочитав статью). http://ru.wikipedia.org/wiki/%D1%EE%E3%EB%E0%F8%E5%ED%E8%E5_%E2%FB%E7%EE%E2%E0. Как он передает справа налево, если справа пусто?
Однако именно в этом месте падает "чужой" модуль, а мне хочется(нужно) этот модуль оживить. Я попробовал заменить cdecl на pascal; но модуль всё равно падает именно в этом месте... :oops: И я не понимаю что именно делает cdecl?

запускается TReadCallback из вот такой функции.
LoadFromStream(Stream: TStream; ReadCallback: TReadCallback; UserData: Pointer): LongWord;
в теле, которой после резервирования памяти и строчки:
Stream.Read(Chunk.data^,Chunk.size); // где Chunk: TChunkRec;
вызывается TReadCallback; // который TReadCallback = procedure(Chunk: TChunkRec; Data: Pointer); cdecl; и тут модуль валится...

Вот... надеюсь изложил понятно... :cry:
:?: Объясните пожалуйста, доступным языком, что делает cdecl; ?
:?: Или как можно заменить cdecl; которая объявлена как TReadCallback = procedure(Chunk: TChunkRec; Data: Pointer); cdecl;?


.
Аватара пользователя
vitaly_l
долгожитель
 
Сообщения: 3333
Зарегистрирован: 31.01.2012 16:41:41

Re: cdecl - как это работает и для чего это?

Сообщение debi12345 » 23.03.2013 23:17:33

LoadFromStream(Stream: TStream; ReadCallback: TReadCallback; UserData: Pointer): LongWord;

CDECL есть порядок 1) помещения аргументов в стэк (начиная с последнего - что повзоляет передавать переменные списки агргументов) и 2) забора резельтата из стэка. Ессно вызывающий и вызываемый код должны быть согласованы. Есть еще нюансы по части C - при резервировании места под строки нужно заказывать как минимум один дополниnтельный байт и вбивать в него NULL,..
Аватара пользователя
debi12345
долгожитель
 
Сообщения: 5759
Зарегистрирован: 10.05.2006 23:41:15
Откуда: Ташкент (Узбекистан)

Re: cdecl - как это работает и для чего это?

Сообщение vitaly_l » 23.03.2013 23:59:28

debi12345 писал(а):помещения аргументов в стэк (начиная с последнего - что позволяет передавать переменные списки аргументов) и 2) забора результата из стэка.
Это сначала понятно, а потом нет.

Честно говоря я полагал что, абсолютно все данные, при помощи FPC - помещаются в стек, когда обрабатываются.

debi12345 писал(а):Ессно вызывающий и вызываемый код должны быть согласованы.

Здесь точно не понимаю, о чём речь?
:?: Что такое вызывающий код? :oops:
:?: Что такое вызываемый код? :oops:
:?: Где они должны быть согласованы? :cry:

:?: Вот в такой процедуре, где должен быть результат: TReadCallback = procedure(Chunk: TChunkRec; Data: Pointer); cdecl;? :oops:
вызывается она вот так: LoadFromStream(Stream: TStream; ReadCallback: TReadCallback; UserData: Pointer): LongWord;
и нет никаких глобальных полей(переменных).

:| Пример какой нить доступный и самый простой - можно увидеть? ==> пожалуйста... :roll:

Например у TChunkRec вот такая структура, в смысле нет: ни next, ни previos:
Код: Выделить всё
  PChunkRec = ^TChunkRec;
  TChunkRec = record
    id: string(5);
    size: integer;
    data: Pointer;
  end;


:| Как мне получить данные из стека и вывести на экран? ==> пожалуйста пример... :roll:
:| Как перемещаться по такому стеку, созданному cdecl;?


.
Последний раз редактировалось vitaly_l 24.03.2013 01:42:26, всего редактировалось 1 раз.
Аватара пользователя
vitaly_l
долгожитель
 
Сообщения: 3333
Зарегистрирован: 31.01.2012 16:41:41

Re: cdecl - как это работает и для чего это?

Сообщение bormant » 24.03.2013 01:38:55

Берём пограмму:
Код: Выделить всё
type
  PChunkRec = ^TChunkRec;
  TChunkRec = record
    id: string;
    size: integer;
    data: Pointer;
  end;

procedure Proc1(Chunk: TChunkRec; Data: Pointer);
begin
end;

procedure Proc2(Chunk: TChunkRec; Data: Pointer); cdecl;
begin
end;

procedure Proc3(Chunk: TChunkRec; Data: Pointer); pascal;
begin
end;

var
  c: TChunkRec;

begin
  Proc1(c, nil);
  Proc2(c, nil);
  Proc3(c, nil);
end.
Получаем ассемблерный листинг:
Код: Выделить всё
fpc -al -Amasm test.pas
На ошибку компиляции внимания не обращаем... Смотрим на тела процедур и на вызов в одном и другом случае.
Последний раз редактировалось bormant 24.03.2013 10:15:23, всего редактировалось 1 раз.
Аватара пользователя
bormant
постоялец
 
Сообщения: 407
Зарегистрирован: 21.03.2012 11:26:01

Re: cdecl - как это работает и для чего это?

Сообщение vitaly_l » 24.03.2013 02:01:15

bormant писал(а):На ошибку компиляции внимания не обращаем... Смотрим на тела процедур и на вызов в одном и другом случае.

У меня получилось вот это: Assembler masm.exe not found, Fatal...
Аватара пользователя
vitaly_l
долгожитель
 
Сообщения: 3333
Зарегистрирован: 31.01.2012 16:41:41

Re: cdecl - как это работает и для чего это?

Сообщение debi12345 » 24.03.2013 03:49:56

абсолютно все данные, при помощи FPC - помещаются в стек, когда обрабатываются.

Могут передаваться и через регистры (регистровые аргументы). И возвращаться тоже через регистры - AX, DX,..

Добавлено спустя 7 минут 13 секунд:
UserData: Pointer

Попробуйте заказать память с запасом и сразу ее обнулить.

TChunkRec = record

Для работы с С надежнее объявить как "packed record".

id: string(5);

STRING-тип тут не пойдет (у С и ФПЦ - они мягко говоря разные) - вместо него используйте "id: PChar" или, если неохота возиться с динамической памятью - "id: array[0..5] of char"(дополнительный символ под NULL).

size: integer;
data: Pointer;
Согласованы по размеру в памяти ? Может нужен болле конкретный тип - LONGINT, LONGWORD, WORD, BYTE, QWORD,CARDINAL,LONGINT^, LONGWORD^, WORD^, BYTE^, QWORD^,CARDINAL^..
Аватара пользователя
debi12345
долгожитель
 
Сообщения: 5759
Зарегистрирован: 10.05.2006 23:41:15
Откуда: Ташкент (Узбекистан)

Re: cdecl - как это работает и для чего это?

Сообщение alexey38 » 24.03.2013 07:13:58

Любая программа состоит из процедур и функций, в т.ч. их функций объявленных внутри классов. Если программа состоит из обычных модулей Unit, подключаемых посредством Uses, то все эти декларации про способы передачи параметров ни на что не влияют.
Другое дело, что реальная программа может использовать динамические библиотеки, для винды это DLL, которые могут подключатся статически или динамически. Основная программа, например, написана на Паскале. А DLL на другом языке с неизвестным нам компилятором, например, С++ от микрософта.

Любая программа при компиляции образует машинный код, и в т.ч. вызов функций и передача параметров в функции - это тоже машинный код. И нам важно, чтобы машинный код передачи параметров с вызываемой стороны (паскаль), и машинный код с принимаемой стороны (С++) были между собой совместимы. Исторически разные разработчики компиляторов делали это по разному, и не было ни каких стандартов. Со временем эти стандарты были созданы, но к тому моменту уже было с десяток способов передачи параметров. И некоторые из них попали в стандарт. stdcall и cdecl - это наиболее распространенные способы передачи параметров.

Как конкретно передаются параметры - Вам не так важно. Важно чтобы при описании функции на паскале и на С++ у одной и той же функции стояло одинаковое определение. Если stdcall, то в обоих языках. Если cdecl - то в обоих языках.

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

Если Вы не пишите на ассемблере и не работаете с DLL. То можете вообще не заморачиваться с этим. А когда Вам нужно реализовать у Вас вызов функции WinAPI или иного API, то смотрите, что там говорят, если написали cdecl, то и у себя пишите cdecl.
alexey38
долгожитель
 
Сообщения: 1627
Зарегистрирован: 27.04.2011 19:42:31

Re: cdecl - как это работает и для чего это?

Сообщение bormant » 24.03.2013 09:59:13

vitaly_l писал(а):
bormant писал(а):На ошибку компиляции внимания не обращаем... Смотрим на тела процедур и на вызов в одном и другом случае.

У меня получилось вот это: Assembler masm.exe not found, Fatal...

Да, именно на неё не обращаем внимания, нам нужен был ассемблерный листинг, мы его получили в файле с расширением .s, например, если исходник назывался test.pas, то в файле test.s.
Немного поменял исходник для иллюстрации fascall, cdecl, pascal, stdcall:
Код: Выделить всё
type
  PChunkRec = ^TChunkRec;
  TChunkRec = record
    id: string;
    size: integer;
    data: Pointer;
  end;

procedure Proc1(Chunk: TChunkRec; Data: Pointer);
begin
end;

procedure Proc2(Chunk: TChunkRec; Data: Pointer); cdecl;
begin
end;

procedure Proc3(Chunk: TChunkRec; Data: Pointer); pascal;
begin
end;

procedure Proc4(Chunk: TChunkRec; Data: Pointer); stdcall;
begin
end;

var
  c: TChunkRec;

begin
  Proc1(c, nil);
  Proc2(c, nil);
  Proc3(c, nil);
  Proc4(c, nil);
end.

Смотрим на тела процедур и на код вызова, обращаем внимание на то, кто корректирует стек по завершении вызова.

Добавлено спустя 9 минут 18 секунд:
Proc1, тело:
Код: Выделить всё
   PUBLIC   P$PROGRAM_PROC1$TCHUNKREC$POINTER
P$PROGRAM_PROC1$TCHUNKREC$POINTER:
; Temps allocated between ebp-280 and ebp-8
; [test.pas]
; [10] begin
      push   ebp
      mov   ebp,esp
      sub   esp,280
      mov   dword ptr [ebp-280],esi
      mov   dword ptr [ebp-276],edi
; Var Chunk located at ebp-4
; Var Data located at ebp-8
      mov   dword ptr [ebp-4],eax
      mov   dword ptr [ebp-8],edx
      mov   esi,dword ptr [ebp-4]
      lea   edi,dword ptr [ebp-272]
      cld
      mov   ecx,66
      rep   movsd
; [11] end;
      mov   esi,dword ptr [ebp-280]
      mov   edi,dword ptr [ebp-276]
      leave
      ret

Proc2, тело:
Код: Выделить всё
   PUBLIC   P$PROGRAM_PROC2$TCHUNKREC$POINTER
P$PROGRAM_PROC2$TCHUNKREC$POINTER:
; Temps allocated between ebp+0 and ebp+0
; [14] begin
      push   ebp
      mov   ebp,esp
; Var Chunk located at ebp+8
; Var Data located at ebp+272
; [15] end;
      leave
      ret

Proc3, тело:
Код: Выделить всё
   PUBLIC   P$PROGRAM_PROC3$TCHUNKREC$POINTER
P$PROGRAM_PROC3$TCHUNKREC$POINTER:
; Temps allocated between ebp-272 and ebp+0
; [18] begin
      push   ebp
      mov   ebp,esp
      sub   esp,272
      mov   dword ptr [ebp-272],esi
      mov   dword ptr [ebp-268],edi
; Var Chunk located at ebp+12
; Var Data located at ebp+8
      mov   esi,dword ptr [ebp+12]
      lea   edi,dword ptr [ebp-264]
      cld
      mov   ecx,66
      rep   movsd
; [19] end;
      mov   esi,dword ptr [ebp-272]
      mov   edi,dword ptr [ebp-268]
      leave
      ret   8

Proc4, тело:
Код: Выделить всё
   PUBLIC   P$PROGRAM_PROC4$TCHUNKREC$POINTER
P$PROGRAM_PROC4$TCHUNKREC$POINTER:
; Temps allocated between ebp+0 and ebp+0
; [22] begin
      push   ebp
      mov   ebp,esp
; Var Chunk located at ebp+8
; Var Data located at ebp+272
; [23] end;
      leave
      ret   268

Proc1, вызов:
Код: Выделить всё
; [29] Proc1(c, nil);
      mov   eax,offset U_P$PROGRAM_C
      mov   edx,0
      call   P$PROGRAM_PROC1$TCHUNKREC$POINTER

Proc2, вызов:
Код: Выделить всё
; [30] Proc2(c, nil);
      push   0
      sub   esp,264
      mov   edi,esp
      mov   esi,offset U_P$PROGRAM_C
      cld
      mov   ecx,66
      rep   movsd
      call   P$PROGRAM_PROC2$TCHUNKREC$POINTER
      add   esp,268

Proc3, вызов:
Код: Выделить всё
; [31] Proc3(c, nil);
      push   offset U_P$PROGRAM_C
      push   0
      call   P$PROGRAM_PROC3$TCHUNKREC$POINTER

Proc4, вызов:
Код: Выделить всё
; [32] Proc4(c, nil);
      push   0
      sub   esp,264
      mov   edi,esp
      mov   esi,offset U_P$PROGRAM_C
      cld
      mov   ecx,66
      rep   movsd
      call   P$PROGRAM_PROC4$TCHUNKREC$POINTER


Добавлено спустя 44 минуты 29 секунд:
Proc1, по умолчанию fastcall.
Вызывающий код в регистре eax передаёт указатель на C, в регистре edx передаёт Data.
Выполняется вызов процедуры.
Сгенерированная компилятором преамбула Proc1:
-- сохраняет в ebp указатель стека;
-- резервирует на стеке место для копий Chunk и Data;
-- копирует содержимое в локальные копии Chunk и Data.
Далее исполняется код процедуры (у нас пусто).
Перед возвратом из процедуры стек приводится в исходное значение.

Proc2, cdecl.
В стек укладываются параметры справа налево, сначала значение Data (nil, он же 0), затем в стек из C копируется содержимое Chunk.
Выполняется вызов процедуры.
Сгенерированная компилятором преамбула Proc1:
-- сохраняет в ebp указатель стека.
Далее исполняется код процедуры (у нас пусто).
Перед возвратом из процедуры стек приводится в значение перед вызовом.
Вызывающий код корректирует содержимое стека на размер Chunk и Data (268 байт).

Proc3, pascal.
В стек укаладываются параметры слева направо, сначала указатель на C, затем значние Data (nil, он же 0).
Выполняется вызов процедуры.
Сгенерированная компилятором преамбула Proc3:
-- сохраняет в ebp указатель стека;
-- резервирует на стеке место для копий Chunk и Data;
-- копирует содержимое в локальные копии Chunk и Data.
Далее исполняется код процедуры (у нас пусто).
Перед возвратом из процедуры стек приводится в исходное значение, дополнительно из него удаляются полученые параметры (8 байт, указатель на Chunk и Data).

Proc4, stdcall.
В стек укладываются параметры справа налево, сначала значение Data (nil, он же 0), затем в стек из C копируется содержимое локальной Chunk (66 двойных слов, 264 байта).
Выполняется вызов процедуры.
Сгенерированная компилятором преамбула Proc4:
-- сохраняет в ebp указатель стека.
Далее исполняется код процедуры (у нас пусто).
Перед возвратом из процедуры стек приводится в исходное значение, дополнительно из него удаляются полученые копии параметров, помещённые вызывающим кодом (268 байт).
Аватара пользователя
bormant
постоялец
 
Сообщения: 407
Зарегистрирован: 21.03.2012 11:26:01

Re: cdecl - как это работает и для чего это?

Сообщение vitaly_l » 24.03.2013 13:46:31

Громадное спасибо мультиУважаемые: bormant, alexey38, debi12345 - за столь понятные и подробные объяснения.
Теперь я более менее - ясно понимаю как это работает и для чего предназначено и даже умею получать ассемблерный листинг. (правда читать я его могу только на 10-20%, хотя и прошёл курс по asm; просто я asm - не пользуюсь и знания теряются).

bormant писал(а):В стек укладываются параметры справа налево, сначала значение Data (nil, он же 0), затем в стек из C копируется содержимое Chunk.
Вот здесь ещё нужно уточнение:

:!: По идее в программе: из левой переменной - данные должны переместиться в правую и получить адреса памяти (извините если я некорректно выражаюсь). И поэтому мне непонятно. Для меня стэк это специализированный массив из множества: |*|*|*|*|*|*|*|*|*| где * - это значение, которое можно взять или push например с любого конца |*|*|*|*|*|*|*| (стэка)? символ: | - это разделитель (для визуального восприятия).

:?: Что значит укладываются в стек: сначала правое, а затем левое? Они попеременно туда укладываются или стека два?
:?: Предположим данные у меня, прочитались из Stream и теперь данные после cdecl; переместились в Data, которое благодаря ChunkRec "отформатировано" по стандарту TChunkRec... <= Я правильно понимаю? (извините если я некорректно выражаюсь)/
:?: Как мне теперь достать из Data все значения и вывести их например в TMemo?



/

Добавлено спустя 2 часа 2 минуты 35 секунд:
debi12345 писал(а):size: integer;
data: Pointer;
Согласованы по размеру в памяти ? Может нужен болле конкретный тип - LONGINT, LONGWORD, WORD, BYTE, QWORD,CARDINAL,LONGINT^, LONGWORD^, WORD^, BYTE^, QWORD^,CARDINAL^..


На самом деле там:

Код: Выделить всё
  PChunkRec = ^TChunkRec;
  TChunkRec = record // пробовал заменить на packed record - ничего не дало.
    id: array[0..3] of AnsiChar; // а не string(5);
    size: LongWord; // а не integer;
    data: Pointer;
  end;

.
По идее я могу ещё до директивы cdecl - получить нужные мне данные, напрямую сразу после чтения из stream.

Но я не могу интерпретировать данные в понятный мне формат...
1) размер получить могу => IntToStr(Chunk.size) - возвращает много разных чисел из файла в соответствии с кол-вом объектов... Это наверно размер кол-ва ячеек для буфера.
2) Ещё могу получить много вот таких значений => IntToStr(Integer(Chunk.data)) - возвращает много разных непонятных разно многозначных чисел в соответствии с кол-вом объектов...
3) Ещё могу получить много вот таких значений => IntToStr(Integer(Chunk.data^)) - тоже возвращает много разных непонятных разно многозначных чисел в соответствии с кол-вом объектов...

Все эти цифры, за исключением размера(Chunk.size) - не соответствуют тому что там должно быть... Точнее я как-то неправильно пытаюсь их интерпретировать... cdecl - должен был загонять данные в стек... но на cdecl программа падает.
Чтобы оживить, модуль - я хочу получить данные из Chunk в TMemo. Chunk : TChunkRec см. 20 строк выше.

Chunk - заполняется из Stream и данные из Stream в Chunk - попадают. В data: Pointer; там по идее каждый раз записываются разные данные. Это массивы float, integer, и пусть будет ещё string...

:?: Как мне Chunk.data распознать и вывести в TMemo? :oops: :cry:






.
Последний раз редактировалось vitaly_l 24.03.2013 16:12:35, всего редактировалось 2 раз(а).
Аватара пользователя
vitaly_l
долгожитель
 
Сообщения: 3333
Зарегистрирован: 31.01.2012 16:41:41

Re: cdecl - как это работает и для чего это?

Сообщение debi12345 » 24.03.2013 16:09:42

id: array[0..3] of AnsiChar; // а не string(5);


Сколько ПОЛЕЗНЫХ байт в ID ? С точки зрения С-программы : 3 (0..2), в 4-й будет записываться NULL (C-ый финалайзер строк).
В FPC-программе к этому полю следует обращаться с приведением типа к PChar как :

Код: Выделить всё
pascal_ctsring:= PChar(PChunkRec.id);

( что есть по сути
Код: Выделить всё
pascal_ctsring[3]:= PChar(PChunkRec.id);
)
Аватара пользователя
debi12345
долгожитель
 
Сообщения: 5759
Зарегистрирован: 10.05.2006 23:41:15
Откуда: Ташкент (Узбекистан)

Re: cdecl - как это работает и для чего это?

Сообщение vitaly_l » 24.03.2013 16:16:20

В ID там слова из четырёх заглавных букв, типа: LOGO или POST или FACE или LAYR///
id - он выводит правильно... я не могу интерпретировать остальные данные
Последний раз редактировалось vitaly_l 24.03.2013 16:18:31, всего редактировалось 1 раз.
Аватара пользователя
vitaly_l
долгожитель
 
Сообщения: 3333
Зарегистрирован: 31.01.2012 16:41:41

Re: cdecl - как это работает и для чего это?

Сообщение debi12345 » 24.03.2013 16:17:58

Как мне Chunk.data распознать и вывести в TMemo?

По ходу проверяя на конвертируемость (попадание в диапазон #10,#13,#9,#32..#255), побайтово конвертирвать "Byte((data+i)^)" в "char" и конкатенировать это с результирующей строкой.
Аватара пользователя
debi12345
долгожитель
 
Сообщения: 5759
Зарегистрирован: 10.05.2006 23:41:15
Откуда: Ташкент (Узбекистан)

Re: cdecl - как это работает и для чего это?

Сообщение vitaly_l » 24.03.2013 16:27:11

debi12345 писал(а):побайтово конвертирвать "Byte((data+i)^)" в "char"

В Вашем примере: i - это что за значение? от 0 до чего?
Аватара пользователя
vitaly_l
долгожитель
 
Сообщения: 3333
Зарегистрирован: 31.01.2012 16:41:41

Re: cdecl - как это работает и для чего это?

Сообщение debi12345 » 24.03.2013 16:34:43

Chunk.data

Кстати, инициализируется и заказывается ли память под эту переменную? Где и каким образом ?

В Вашем примере: i - это что за значение? от 0 до чего?

i = 0...(size-1).

Добавлено спустя 2 минуты 8 секунд:
С требует понимания его "души", а имено что это - очень низкоуровневый язык безо всякой помощи при работе с памятью и строками, считайте что это по сути Assembler :)
Аватара пользователя
debi12345
долгожитель
 
Сообщения: 5759
Зарегистрирован: 10.05.2006 23:41:15
Откуда: Ташкент (Узбекистан)

Re: cdecl - как это работает и для чего это?

Сообщение vitaly_l » 24.03.2013 16:42:58

debi12345 писал(а):Кстати, инициализируется и заказывается ли память под эту переменную? Где и каким образом ?

всё нижеприведенное в теле stream

Код: Выделить всё

Read(Chunk,8);
ReverseByteOrder(@Chunk.size,4);
StartPos:=Position;
GetMem(Chunk.data,Chunk.size);
Stream.Read(Chunk.data^,Chunk.size);
if Assigned(ReadCallback) //( if Assigned(ReadCallback) - работает нормально) в смысле Assigned...
    then ReadCallback(Chunk,UserData);  // <== а вот здесь модуль всегда падает... если закомментировать то проходит весь stream
FreeMem(Chunk.data,Chunk.size);
Position:=StartPos+Chunk.size+(StartPos+Chunk.size) mod 2;



Добавлено спустя 13 минут 37 секунд:
Там есть ещё, одна параллельная возможность чтения файла которую я восстановил, но весь модуль всё равно не работает.
В смысле только FileLoad - работает и не падает. Данные насколько я понимаю заносятся в FData
код такой:
Код: Выделить всё
AStream.Read(PByteArray(FData)^[0],DataSize);


:?: Как из приведенного FData - поместить данные в TMemo?



.
Аватара пользователя
vitaly_l
долгожитель
 
Сообщения: 3333
Зарегистрирован: 31.01.2012 16:41:41

След.

Вернуться в Общее

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

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

Рейтинг@Mail.ru