Атрибуты
in и
out напрямую с ByVal и ByRef не коррелируют вообще никак.
Будет ли со стороны VB аргумент представлен как ByVal или ByRef зависит от типа аргумента в IDL-объявлении, и никак не зависит от атрибутов.
Атрибуты
in и
out играют совершенно другую роль. Их предназначение — подсказки для маршаллинга.
Вообще-то от высказывания
ger_kar'а немножко уши вянут. То, что он называл Си-подобным синтаксисом, на самом деле является не каким-то там суррогатом, похожим на Си, а самостоятельным описательным языком MIDL.
Язык MIDL является небольшим расширением над языком IDL.
На языке IDL стоит остановиться и рассказать чуть подробнее. Язык IDL создан комитетом
The Open Group (той же организацией, которая подарила нам такие вещи как LDAP и X11).
IDL был частью концепции DCE/RPC — которая предлагала механизм RPC (remote procedure call) услуг, то есть услуг по вызову неких процедур через границы разных машин (или как минимум — разных программ). Центральной идеей DCE/RPC было понятие «интерфейс» и «точки входа».
Точка входа — это нечто, что можно вызвать удалённо, то есть некая процедура.
Интерфейс — это группа точек входа, собранная по какому-то принципу, причём предполагается, что содержимое интерфейса продумывается и потом провозглашается (как некий микро-стандарт), и затем с интерфейсом может работать кто угодно, вызывая через границы разных машин некие процедуры.
Эти интерфейсы в рамках DCE/RPC были совершенно в стороне от ActiveX и COM, от Microsoft и вообще от ООП. Это была чисто процедурная штука, и интерфейсы тут были в том же смысли, в каком это слово применимо к понятию «сетевой интерфейс» или «USB-интерфейс» — как нечто, с чем можно взаимодействовать, следуя заранее провозглашённым правилам игры.
Так вот, интерфейс представлял собой группу точек входа — другими словами процедур. У процедур были имена, и, что куда важнее, были аргументы.
Здесь начинается самое главное. Поскольку весь разговор о вызове удалённых процедур через сеть, то если кто-то вызывает процедуру, имеющую некий набор аргументов, то нужно при вызове аргументы передавать по сети сначала от вызывающей стороны к вызываемой, а затем (если вызванная процедура их как-либо меняет) передавать изменённые агументы обратно.
Собственно говоря, атрибуты in и out возникли ещё на этом этапе и предназначались для этого чисто-процедурного RPC-механизма.
Это потом уже корпорация Microsoft придумала OLE, COM и ActiveX с DCOM, и для этих технологий взяла стандарт DCE/RPC (и соответсвующий ему протокол) как базу, и поверх него построила собственную технологию и протокол MS-ORPC.
И только у Microsoft изначально чисто процедурное понятие «интерфейса» (из DCE/RPC) стало играть роль интерфейса-из-мира-ООП.
Так вот, поскольку корпорация Microsoft тоже делала RPC-механизм, но на этот раз главным образом ООП-основанный RPC-механизм. И поскольку были возможны объектные вызовы через границы процессов или машин, стало необходимо делать маршалинг (упаковывать параметры вызова, передавать их на другую машину, затем упаковывать результат и передавать вызывающей стороне), уже имеющиеся атрибуты in и out оказались полезными и здесь (не по чистой удачи, а потому что и тут и там решалась принципиально одна и та же задача — вызов удалённой процедуры).
Так что in и out нужны для маршалинга.
Что касается такой сущности TLB как «модуль», то я не представляю себе случая при которых вызов модульной функции подвергся бы маршалингу. Обычно в случае вызова модульной функции имеет место просто вызов импортированной из DLL функции.
Так что атрибуты in и out в рамках модулей в TLB на первый взгляд никакой роли не играют и никак не используются.
Можно ли их вообще не ставить? Технически — конечно можно. Но есть несколько случаев, когда от них может быть польза.
Во-первых, некая гипотетическая IDE, работающая с TLB, может учитывать их при отображении подсказки/справки по функциям, объявленным в TLB. И тогда эти атрибуты повлияют на отображаемые подсказки и помогут программисту.
Во-вторых, они могут быть напрямую восприняты программистами, читающими исходник TLB или декомпилированный код TLB. Собственно говоря, в С/С++ распространённой практикой является объявление чисто разметочных макросов (с помощью
#define), которые, раскрываясь, подменяются на пустое место. Роль таких макросов чисто в том, чтобы они повышали читаемость кода. Как раз есть макроы _IN, _OUT, _OPTIONAL.
В-третьих, нельзя исключать, что кто-нибудь захочет написать некий механизм, который берёт TLB и содержащиеся в ней модули и для каждого модуля с импортируемыми сущностями генерирует объект-обёртку, методы которого олицетворяют модульные функции (импортируемые из DLL). Простейший пример того, зачем это может быть нужно, это идея подарить VBScript-пользователям (или пользователям любых других скриптовых языков, где для безтиповых переменных под капотом используется тип Variant, а для ООП — COM, либо где просто есть возможность работы с COM (как в PHP)) возможность находясь в рамках безтипового языка вызывать функции из DLL-библиотек.
В этом случае при реализации такого механизма in/out-атрибуты, данные изначально в TLB-шном модуле, понадобятся механизму, чтобы правильно проводить маршалинг аргументов вызываемых функций.
А вот, к слову, атрибут retval играет совсем другую роль, и со стороны VB/VBA он влияет на то, как будет выглядеть функция/метод.