ger_kar писал(а):А, что за врожденная проблема? В чем она заключается?
Она в том, что COM/ActiveX-основанность просто зашкаливает. В COM ведётся подсчёт ссылок на интерфейс, интерфейсы на которые не осталось ссылок уничтожаются. В объектах введётся подсчёт живых интерфейсов, если их нет, объект уничтожается (хотя на деле всё реализуется по несколько более упрощённой схеме). А в библиотеке введётся подсчёт живых объектов. Если объектов не осталось, библиотека деинициализируется. В деинициализацию входит обнуление/освобождение внутренних структур, но и не ограничивается этим. Например, в деинициализацию входит зануление модульных (ака глобальных) переменных библиотеки.
Это означает, что чтобы вызываемые из библиотеки функции
нормально работали, библиотека должна находится в инициализированном состоянии. В FNDLL-1 для этого использовался следующий метод: в DllEntryPoint при входе с DLL_ATTACH_PROCESS создавался объект (экземпляр класса CStdDllInfo, просто потому, что он уже есть, и не надо придумывать ещё одного ради только этой цели), и тут же уничтожался.
Создание экземпляра приводило к тому, что появлялся один живой объект. Это приводило к инициализации библиотеки. Часть изменений, которые входят в понятия «инициализация», неустранимы (не исчезают после деинициализации), часть же исчезает после деинициализации, но с одним но.
Хотя деинициализация библиотеки происходила сразу же после инициализации, так получалось, что библиотека всё равно могла работать засчёт оставшегося после деинициализации
мусора. Но здесь уже никто не может гарантировать стабильность работы, основанной на мусоре. Поэтому FNDLL-библиотеки иногда падали, как только мусор по случайным причинам становился невалидным.
Хотя это намеренно привнесённая мною ошибка: в самой первой выложенной версии уничтожение только что созданного объекта в DllEntryPoint не совершалось, и библиотека была
стабильна всегда. Но была другая проблема: поскольку объект не уничтожался вообще, возникала угроза возникновения утечки памяти. Хотя рядовые библиотеки подгружаются в АП процесса только единожды, возможен всё-таки случай, когда библиотека будет периодически загружаться/выгружать из в/из АП процесса с помощью LoadLibrary/FreeLibrary. Если будет выполнена тысяча таких выгрузок-загрузок, в памяти будет висеть тысяча неуничтоженных объектов.
Тогда я через некоторое время после выкладывания самой первой версии обновил её, тем, что внёс изменение: объект (инициатор инициализации) уничтожался сразу же. Утечка была устранена, но возникла другая проблема: деинициализация, после которой вызов экспортируемых функций работает «на авось». Тогда был сделан
ошибочный вывод, что деинициализация не делает библиотеку не стабильной, потому что она (по предположению) ограничивается сбросом модульных переменных апартаментов. Ошибочным вывод стал потому, что библиотека действительно была работоспособна после деинициализации, некоторые ведь как-то пользуются FNDLL.
Глобальная проблема всего замысла состоит в том, что я просто не могу найти удобного места, чтобы прицепить к ним инициализацию/деинициализацию.
DllEntryPoint с DLL_PROCESS_ATTACH и DllEntryPoint с DLL_PROCESS_DETACH не подходят — внутри этих функций вообще мало что можно делать, а уж дёргать COM и выполнять такие тяжелые действия, как инициализация рантайма — опасно. Как видно, в Висте (в ней же, вроде?) это вызвало облом. Но кроме этого, DllEntryPoint выполняется до
WinMain. Тут есть гадкий момент: WinMain-функция EXE-файла, сделанного с помощью VB, вызывает msvbvmx0!ThunRTMain, чтобы оная инициализирована контекст EXE-проекта. Если она обнаружит, что рантайм уже инициализирован, VB-EXE-проект падает с ошибкой. Так что если DLL (сделанная с помощью FNDLL) импортируется EXE-шником (написанным на VB), и импорт осуществляется через TLB, то EXE не запустится. Поюзать такую же DLL из EXE написанного на чём-нибудь другом — запросто.
Из этого следует, что инициализация библиотеки/рантайма должна выполняться после того, как выполнение войдёт внутрь WinMain. А деинициализация должна выполняться до того, как кто-нибудь в рамках процесса выполнит CoUninitialize.
Простой выход: добавлять ко всем экспортам FNDLL-based библиотек две функции: Init, UnInit, которые пользователь библиотеки должен будет сам вручную вызывать. Очень качественно, но совершенно тупо и неудобно с точки зрения пользователя. Другой способ: использовать концепцию one-shoot — выполнять инициализацию при самом первом вызове любой экспортируемой функции. Но при этом остаётся совершенно непонятно: а когда тогда вызывать деинициализацию? Ведь самый первый вызов легко определить, а самый последний — невозможно.
В общем, таких открытых проблем просто масса, поэтому-то выпуск новой версии так долго откладывался.
ger_kar писал(а):Мне все-таки кажется, что здесь дело именно в VBA. Если VB это компилятор и интерпретатор (режим IDE), то VBA это только интерпретатор.
Строго говоря, никакого интерпретатора нет. Те, кто считают, что VB IDE интерпретирует код при отладке, глубоко заблуждаются. При отладке в IDE используется
Just-In-Time компиляция. Именно компиляция, я не интерпретация. Компиляция совершается в
P-Code и совершается
единожды. Единожды — значит если функция недавно скомпилировалась, и с тех пор её не меняли, при последующем отладочном запуске она перекомпилироваться не будет.
Хотя, JIT-компиляция это всего лишь
вкусная фишка; есть опция «Start With Full Compile», тогда никакой JIT-компиляции нет, есть компиляция всего и сразу, но всё в тот же P-код. При работе в режиме отладки в этом случае выполняется разом скомпилированный P-код.
Кстати, компиляция в P-код совершается не на основе исходного кода проекта, а на основе иерархической структуры данных, описывающей исходный код. Эта структура строится/модифицируется непосредственно в момент правки исходного кода в редакторе. Так что внутри процесса VB даже не хранится тот самый исходный код, который мы видим в редакторе. Тот код, что мы видим в редакторе, строится на основе того же дерева.
Это актуально и для VB, и для VBA. Вся эта функциональность выполняется одним и тем же движком (для продукта VB это библиотека vbaX.dll, со всеми её функциями с префиксами Eb, Tip, и кучей безымянных функций, экспортируемых по ординалу).
Именно возможность делать автономные файлы называют компиляцией, хотя компиляция имеет место и в режиме отладки (компиляция P-код), а интерпретирования нет вообще. Возможность делать автономные EXE реализована следующими способами:
- Тот же самый уже сгенерированный P-код (который выполняется в режиме отладки) тупо распихивается по obj-файлам, а потом они линкуются в один EXE/DLL-файл. Туда же прилинковывается крохотный переходничок, который передаёт на исполнение вшитый в EXE/DLL-файле P-код на исполнение движку, вшитому в MSVBVM.
- (Метод добавлен чуть позже) P-код скармливается утилите С2.EXE, которая из P-кода делает Native-код. Этот Native-код
распихивается по obj-файлам и дальше они слинковываются вместе в один EXE/DLL.
Вот и всё. Разница получается примерно как между qBasic и QuickBasic.
Остальные различия между VB и VBA проистекают из разницы в подключенных (и недопустыных для отключения) TLB-шек, списком доступных типов модулей (модулей с точки зрения Eb-движка) и прочими тонкими настройками Eb-движка, который сам позволяет себя настраивать.
* * *
А дальше мне лень писать. Все твои фразы настолько противоречивы, что к каждой можно написать такую разъяснительную мини-статью. Мне кажется, ты специально провоцируешь меня на это
.
Теперь чем же отличается VB от VBA. VB позволяет не просто запускать проект в режиме отладки (проект компилируется Eb-движком, а потом выполняется), но делать автономные EXE-файлы.