Передача параметров в VB

Персональный блог одноименного форумчанина. Человека и парохода, не побоюсь этого сравнения :)

Модератор: tyomitch

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Передача параметров в VB

Сообщение tyomitch » 15.07.2006 (Сб) 21:20

Как обнаружилось, семантика передачи параметров в VB (ByRef/ByVal) существенно различается в зависимости от сочетания типов формального и фактического параметров.

* С простыми типами (Boolean, Byte, Currency, Date, Double, Integer, Long, Single) всё просто: ByVal передаёт копию значения, приведённую к типу формального параметра; ByRef -- адрес значения.

Если ByVal передаётся 64-битная переменная (т.е. типа Currency, Date или Double), то она занимает в стеке две позиции. Если из функции возвращается 64-битная переменная, то она либо занимает пару регистров edx:eax (для Currency), либо передаётся на стеке сопроцессора (для Date, Double и Single).

При передаче ByRef, если передаётся переменная, типы формального и фактического параметров должны строго совпадать; если же передаётся константа, то она приводится к типу формального параметра, сохраняется во временную переменную, и затем передаётся.

* UDT можно передавать только ByRef и только при совпадении типов формального и фактического параметров. Передаётся адрес данных структуры: тут, слава богу, без неожиданностей. Когда функция возвращает UDT, её содержимое либо помещается в edx:eax (для UDT размером 8 байт и менее), либо копируется по ссылке, передаваемой в невидимом последнем параметре, во временную переменную, создаваемую вызывающей стороной.

* Массивы можно передавать только ByRef и только при совпадении типов формального и фактического параметров. Передаётся адрес 32-битной переменной, которая содержит указатель на SAFEARRAY -- т.е. ссылка на ссылку на ссылку на сами данные. ("тройной ByRef") В частности, вызываемая функция имеет право перекроить переданный массив, или даже удалить его совсем.

Когда функция возвращает массив, она помещает в eax указатель на SAFEARRAY, т.е. ссылку на ссылку на сами данные.

* Со строками интереснее. При передаче ByVal передаётся ссылка на копию строки, т.е. фактически передаётся временная переменная по ссылке. При передаче ByRef передаётся либо адрес строковой переменной ("двойной ByRef"), либо -- когда ByRef передаётся фиксированная строка -- адрес адреса временной копии строки. В последнем случае строка копируется дважды -- перед вызовом функции и после возврата из неё. Данное исследование затруднялось тем, что для фиксированной строки и StrPtr, и VarPtr возвращают ложный результат (им приходит адрес временной копии строки -- его они и возвращают).

Когда функция возвращает строку, она просто помещает в eax указатель на её данные.

* При передаче объектов ByVal передаётся ссылка на данные объекта, и функция может делать с этим объектом произвольные действия (фактически, объект передаётся по ссылке). При передаче ByRef передаётся адрес объектной переменной, т.е. адрес 32-битного указателья на данные объекта ("двойной ByRef"). В этом случае функция получает возможность очистить или переназначить объектную переменную.

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

Это второй из случаев (первый -- с фиксированными строками), когда изменения формального параметра, переданного ByRef, отражаются в фактическом параметре не немедленно, а только после возврата из функции. Эта особенность может быть принципиальной, если фактический параметр -- глобальная переменная, и вызванная функция (или одна из вызываемых ей, в свою очередь, функций) использует эту глобальную переменную как через формальный параметр, так и непосредственно.

* Самый интересный тип параметров -- это Variant. При передаче ByVal все его 16 байт кладутся на стек, т.е. он занимает в нём 4 позиции. Далее, возвращаемое значение типа Variant всегда передаётся в невидимом последнем параметре -- как обычно для 16-байтной структуры.

В составе варианта можно передавать ByVal любой тип -- даже UDT и массивы, для которых передача ByVal напрямую невозможна. В этом случае внутри варианта передаётся указатель на временную копию фактического параметра. В частности, для массивов передаётся указатель на SAFEARRAY ("двойной ByRef").

Когда переменная типа Variant передаётся ByRef, на стек кладётся её адрес. Но когда формальный параметр объявлен с типом Variant, фактический параметр может иметь любой приводимый к варианту тип -- т.е. вообще любой, кроме приватного UDT. В этом случае на стек кладётся адрес временной переменной типа Variant, в которой включена "магия": установлен флаг VT_BYREF, и вместо самих данных хранится ссылка на них. Обнаружить, что переданный параметр типа Variant -- такая вот временная "магическая" переменная, непросто: функция VarType скрывает этот флаг. Кроме того, при присваивании магической переменной в обычную переменную типа Variant магия теряется, и в переменную присваивается копия переданного значения, а не копия ссылки. Однако, магический параметр можно передать как ByRef As Variant в другую функцию -- при этом его магические свойства сохранятся.

Суть магии состоит в том, что присваивание значения магической переменной сразу же действует на переданный фактический параметр. В частности, внутри функции нельзя присвоить произвольную строку параметру типа Variant, если в качестве него была передана переменная типа Long: сразу же, внутри функции, возникает ошибка несоответствия типов. Такой способ передачи параметра также можно назвать "двойным ByRef", потому что передаётся адрес варианта, содержащего ссылку на данные. Однако для ссылочных типов (строки, объекты и массивы) он оказывается "тройным ByRef", потому что ссылка в варианте указывает на переменную, являющуюся указателем на собственно данные. Конкретно для массивов ссылка в варианте указывает на указатель на SAFEARRAY, содержащий ссылку на сами данные массива ("четверной ByRef").


* Для всех типов, кроме Variant, значения необязательных параметров (если значение по умолчанию не указано явно, то используется пустое) подставляются на этапе компиляции, и функция не имеет возможности узнать, был ли ей передан фактический параметр в качестве необязательного. Если необязательный параметр передавался ByRef, то создаётся временная переменная, в которую помещается значение по умолчанию, и эта временная переменная передаётся по ссылке -- как обычно при передаче констант ByRef.

Для параметров типа Variant используется специальное значение, обозначающее пропущенный фактический параметр. Это значение имеет подтип vbError и код &H80020004=DISP_E_PARAMNOTFOUND, который при попытке использования автоматически переводится в код 448 "Named argument not found", в соответствии с недокументированной таблицей перевода кодов ошибок. (Далее это специальное значение будет условно называться varMissing) Это означает, что проверку функцией IsMissing(a) можно заменить на равносильную проверку IsError(a) And CLng(a) = 448. Однако, "сфальсифицировать" пропущенный параметр при помощи CVErr не удастся: она допускает только коды от 1 до 65535, и вместо перевода их по таблице просто добавляет &H800A0000.

Поскольку при передаче параметров в другие функции магические переменные не создаются (они создаются только при передаче переменных), то невозможно получить ни "дважды магическую" переменную, ни магическую переменную, ссылающуюся на varMissing. Проверено, что использование созданных вручную многократно-магических переменных (в пределе -- зацикленных :-D) сносит крышу VB.

* В качестве параметра ParamArray передаётся, как и для обычных массивов типа Variant, указатель на указатель на SAFEARRAY. Элементами массива являются, как и для обычных ByRef-параметров типа Variant, обычные варианты для переданных констант, магические -- для переданных переменных, и varMissing для пропущенных параметров. Если передавалась переменная типа Variant, то в массиве будет переменная с подтипом vbVariant+VT_BYREF, указывающая на неё. Это единственный случай, когда в VB может появиться вариант с подтипом vbVariant без одновременно установленного флага vbArray -- хотя VarType будет для него показывать не vbVariant, а подтип того варианта, на который этот ссылается.

Как ни обидно, при передаче магического варианта, полученного в составе ParamArray, в другую процедуру -- его магия теряется. При чтении элемента массива всегда создаётся копия его значения, даже если этот элемент был магическим.

Можно для интереса прикинуть уровень передачи по ссылке для массива, передаваемого в составе ParamArray: передаётся ссылка на ссылку на SAFEARRAY, содержащий ссылку на Variant, содержащий ссылку на ссылку на SAFEARRAY, содержащий ссылку на собственно данные. Шестикратный ByRef! И после этого кто-то ещё будет заявлять, что в VB нет ссылок? :-D

* Практический вывод такой: формирование вручную магических вариантов и передача их в качестве параметров позволит работать с указателями несравнимо более удобно, чем через одноэлементные SAFEARRAY. Предлагаю GSerg-у написать продолжение к своей статье о работе с указателями в VB :-)

--------------------
* Всё написанное относится к передаче параметров внутри модулей VB. При вызове членов классов VB всегда добавляется невидимый первый параметр объектного типа (в нём передаётся Me), и невидимый последний параметр под возвращаемое значение, если член возвращает значение. Все члены классов VB возвращают в eax свой код ошибки (к положительным кодам добавляется &H800A0000), либо 0 если ошибки не было. Ни в каком случае член класса VB не может вернуть в eax положительное значение; также невозможно вызвать через Err.Raise ошибку с кодом, большим 65535 (но можно с любым отрицательным).

Всё написанное не имеет непосредственного отношения к передаче параметров в API, объявленные в Declare либо в TLB. Как всё обстоит там, подробно (хотя и с ошибками ;-)) расписано у Аппельмана.
Последний раз редактировалось tyomitch 17.07.2006 (Пн) 6:46, всего редактировалось 1 раз.
Изображение

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 16.07.2006 (Вс) 9:37

Ап! вашему вниманию предлагается издание второе, исправленное и дополненное :-)
Изображение


Вернуться в Tyomitch

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

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

    TopList