Кривоус Анатолий писал(а):из-за неициализированного контекста потока (в терминологии Хакера)
Не контекст потока, а просто
контекст. Или контекст потока-проекта. Или контекст проекта?
Давайте ещё раз. Пирамида потребностей (потребности растут снизу вверх):
Что касается райнтайма:
- Есть сущности, которые являются общими для всех потоков, сколько бы их (потоков) не было. Инициализируются глобально для всех потоков. Например это касается вызова TlsAlloc, регистрации оконных классов для контролов и т.д.
- Есть per-thread структуру данных, которые главным образом нужны для поддержки того, что на картинке размещено выше.
Если проекты расположить по горизонтали, а потоки — по вертикали, то получается матрица. Ячейку такой матрицы я называю контекстом. Код проекта
P может работать в рамках потока
T только если инициализирован контекст
P×
T.
Ни в коем случае не нужно путать контексты, и апартаменты. Контекст — это ячейка, а апартамент распространяется на всю колонку.
Кстати, помимо прочего, COM-апартамент тоже должен быть инициализирован (
CoInitializeEx).
Под контекстом не нужно понимать ничего конкретного (вроде конкретной структуры или таблицы в памяти). Под контекстом подразумевается целая совокупность вещей:
- Набор данных, которые рантайм поддерживает для каждого контекста.
- Все глобальные и статические переменные проекта (хранилище под них).
- Аппглобальный объект — COM-объект, реализуемый рантаймом, у которого есть такие свойства как App, Forms, Printers, Printer, Screen, Clipboard. App — это не глобальная переменная, это свойство COM-объекта, и каждого потока такой объект будет свой.
Тут важное замечание: у проектов с ThreadingModel=Single контекст может быть только один. Хранилищем глобальных переменных (включая неявную невидимую переменную-ссылку на аппглобальный объект) будет секция данных PE-модуля. Если ThreadingModel=Apartament, то контекстом может быть много, при этом хранилищем глобальных переменных для одного из контекстов (того, который был инициализирован первым) служит секция данных, для остальных контекстов в памяти выделяется альтернативное хранилище.
Так, если говорить о каком-то проекте, каждый поток будет обладать собственной копией всех глобальных переменных. И коллекция экземпляров форм тоже будет своя. Так работает VB-шный мезанизм TLS. Таким образом будет достигнута межпоточная изоляция.
Синяя клетка олицетворяет неинициализированный контекст, несуществующий, то есть такой, который только потенциально может существовать в данном месте, жёлтая — инициализированный. Для ActiveX DLL и OCX управление временем жизни контекстов подвязано ко времени жизни объектов. Даже если DLL/OCX загружена в АП процесса, контекст будет инициализирован только тогда, когда
снаружи придёт запрос на порождение экземпляра класса (через DllGetClassObject). В проекте типа Standard EXE контекст инициализируется при запуске проекта (через ThunRTMain), а уничтожается при завершении программы. В проекте типа ActiveX EXE новый потоки порождаются самим проектом и контексты для них инициализируются автоматически.
_________________________
Все креши проектов как в случае попыток сделать NativeDLL, так и в случае попыток сделать многопоточность, можно расписать по следующей схеме:
- Выполнение уходит в рантайм, где происходит вызов TlsGetValue
- Если рантайм не подвергся глобальной инициализации, то TLS-слот для рантайма не выделен. Аргументом для TlsGetValue будет передано некорректное значение (ноль), функция сбойнет, возврат тоже будет некорректным. Это соответствует отсутствию инициализации нижнего большого розового блока.
- Если рантайм не подвергся инициализации потоко-специфичных данных, то TlsGetValue вернёт бред, потому что предварительно не была вызвана TlsSetValue. Это соответствует отсутствию инициалиации маленького нижнего желтого блочка.
- Если контекст не был инициализирован: рантайм для каждого потока содержит двухсвязный список проектов, которые работают в рамках текущего потока. Такой список для текущего потока будет получен, но в нём не будет найден элемент, соответствующий запрашиваемому проекту.