Дата публикации статьи: 22.05.2004 12:05

Гергерт Сергей
Доступ к безымянным параметрам

    Эта статья предполагает, что вы знакомы с функциями Get/PutMem. Ознакомиться можно здесь. Существует несколько соглашений о вызовах функций. VB использует только одно: stdcall. Его же используют все функции Windows. Это соглашение гласит: - параметры помещаются в стек подряд, в обратном порядке (справа налево) - вызываемая функция сама производит удаление параметров из стека после окончания работы Нам важно только первое утверждение. Из него очень многое следует. Создадим простейший проект:

function func(a as long) as long
  debug.print varptr(a)
end function

sub CallFunction()
  dim v as long
  debug.print varptr(v)
  func v
end sub

    Запустив CallFunction, мы увидим, что в дебаге появятся два одинаковых числа. Так и должно быть. Ведь в функцию func идёт указатель на v, и когда мы пытаемся вызвать VarPtr(a), VB возвращает нам не VarPtr(копия указателя), а VarPtr(переменная, на которую он указывает). Немного изменим функцию:

function func(byval a as long) as long
  debug.print varptr(a)
end function

    Теперь при запуске CallFunction в дебаге появятся два разных числа - и снова ничего удивительного. В функцию передаётся копия переменной v, а вовсе не указатель на v. Поэтому VB не видит связи между переменной v и параметром функции a, и VarPtr(a) возвращает совсем не то же самое, что VarPtr(v). Ещё раз изменим код:

function func(byval p1 as long, byval p2 as long, byval p3 as long, byval p4 as long) as long
  debug.print varptr(p1)
  debug.print varptr(p2)
  debug.print varptr(p3)
  debug.print varptr(p4)
end function

sub CallFunction()
  dim a as long, b as long, c as long, d as long
  func a,b,c,d
end sub

    В дебаге появились четыре разных числа. Обращаем внимание: они идут в возрастающем порядке с интервалом 4. Если мы теперь заменим параметры, к примеру, с Long на Integer или на Byte (все или часть - неважно) - то ничего не изменится, в дебаге будут числа, последовательно отстоящие на 4 байта. Single? То же самое. А Double? И Double подчиняется этому правилу, только следующий за Double параметр отстоит от неё не на 4, а на 8 байт. Почему? Да потому что stdcall. Исключением здесь являются String. Мне не удалось установить правило образования их VarPtr, не исключено, что его нет ;). Однако если за параметром String следует, к примеру, Long, то этот самый Long будет иметь VarPtr, на 8 байт отстоящий от параметра, который идёт непосредственно перед стринговым. Variant не тестировал, ну его :)
Какой же вывод мы можем из этого почерпнуть? Если у нас есть byval-параметры, среди которых нет string, то можно определить varptr первого параметра, а все остальные получать через

GetMem4 VarPtr([первый параметр]) + 4 * [желаемый номер параметра - 1], [куда копировать]

    И таким вот незамысловатым образом можно обращаться к параметрам функции из цикла, не задумываясь о том, как эти параметры зовут! Именно эта технология была использована мною, чтобы отказаться от Variant ParamArray() при вызове asm-функций (скорость, люди, скорость...) :)
 

ЗЫ: Всё это только для ByVal, ещё раз напомню! Ну и Double на 8 байт, тоже не забываем :)