Delphi World - Вызов c-шной функции с переменным числом параметров
Delphi World - это проект, являющийся сборником статей и малодокументированных возможностей  по программированию в среде Delphi. Здесь вы найдёте работы по следующим категориям: delphi, delfi, borland, bds, дельфи, делфи, дэльфи, дэлфи, programming, example, программирование, исходные коды, code, исходники, source, sources, сорцы, сорсы, soft, programs, программы, and, how, delphiworld, базы данных, графика, игры, интернет, сети, компоненты, классы, мультимедиа, ос, железо, программа, интерфейс, рабочий стол, синтаксис, технологии, файловая система...
Вызов c-шной функции с переменным числом параметров

Автор: Владимир Переплетчик

Комментарий к статье по поводу wsprintf

Сама по себе статья вызывает мало интереса, кроме того, что поднята интересная проблема - вызов с-шной функции с переменным числом параметров. В ответах с использованием массивов вообще, IMHO, ошибка - на стек попадет адрес массива, а в с это совсем не то. Но решение проблемы существует, правда надо ручками повозиться со стеком. Приводимая ниже функция на скорую руку переделывается из работающей в реальном проекте похожего буфера с-паскаль, но там функция в dll имеет тип вызова cdecl и другие обязательные параметры, в связи с чем возможны "опечатки"


// Пишем функцию-переходник, маскируя с-шные "..." паскалевским
// array of const

function sprintf(out, fmt: Pchar; args: array of const): Integer;
var
  I: Integer;
  BufPtr: Pchar;
  S: string;
  buf: array[0..1024] of char;
begin
  BufPtr := buf;
  // Формируем буффер параметров. Можно, конечно, и прямо на стеке,
  // но головной боли слишком много - проще так
  for I := low(Par) to High(Par) do
    case Par[I].VType of
      vtInteger: // Здесь все просто - 4 байта на стек
        begin
          Integer(Pointer(BufPtr)^) := Par[I].VInteger;
          Inc(BufPtr, 4);
        end;
      vtExtended: // Здесь хуже - слова надо местами поменять :-((
        begin
          Integer(Pointer(BufPtr)^) :=
            Integer(Pointer(Pchar(Par[I].VExtended) + 4)^);
          Inc(BufPtr, 4);
          Integer(Pointer(BufPtr)^) :=
            Integer(Pointer(Par[I].VExtended)^);
          Inc(BufPtr, 4);
        end;
      vtPChar: // Здесь тоже все хорошо - 4 байта
        begin
          Pointer(Pointer(BufPtr)^) := Par[I].VPchar;
          Inc(BufPtr, 4);
        end;
      vtString, vtAnsiString: // А здесь во избежание чудес надо
        // копию строки снять
        begin
          if Par[I].VType = vtString then
            S := Par[I].VString^
          else
            S := string(Par[I].VAnsiString);
          Pointer(Pointer(BufPtr)^ :=
            StrPCopy(StrAlloc(Length(S) + 1), S);
            Inc(BufPtr, 4);
        end;
    end;
  // Поддержку других типов доделывать самостоятельно,
  // вооружившись толковым пособием по с и ассемблеру

  I := (BufPtr - buf) div 4; // Сколько раз на стек слово положить

  asm
      push dword ptr [out]
      push dword ptr [fmt]
      mov ecx, dword ptr [i]
      mov eax, dword ptr [buf]  // stdcall - параметры в прямом
                                // порядке
      @@1:
      push dword ptr [eax]
      add  eax, 4
      loop @@1
      call [wsprintf]
      mov  dword ptr [Result], eax // Сохранить результат
      mov eax, dword ptr [i]       // Привести в порядок стек
      shl eax, 2
      add eax, 8
      add esp, eax
  end;
  // Почистить строки
  for I := low(Par) to High(Par) do
    case Par[I].VType of
      vtInteger: Inc(BufPtr, 4);
      vtExtended: Inc(BufPtr, 8);
      vtPChar: Inc(BufPtr, 4);
      vtString, vtAnsiString:
        begin
          StrDispose(PChar(PPointer(BufPtr)^));
          Inc(BufPtr, 4);
        end;
    end;
end;

В таком виде методика уже имеет смысл. Изменения при типах вызова cdecl / pascal понятны.

Проект Delphi World © Выпуск 2002 - 2017
Автор проекта: Эксклюзивные курсы программирования