Хочется развеять ряд распространённых заблуждений.
* VB6 поддерживает многопоточные программы. В частности, на VB можно -- безо всякого шаманства -- написать многопоточный COM-сервер (как в виде ActiveX EXE, так и в виде ActiveX DLL).
Для обеспечения этого рантайм написан исключительно multithreading-aware способом: все "глобальные" данные на самом деле хранятся в TLS. Отсутствие по-настоящему глобальных данных обеспечивает защиту от race-condition-ов и других ошибок синхронизации.
* Написать на VB6 многопоточную программу намного сложнее, чем просто вызвать CreateThread(AddressOf ThreadProc), проверить под IDE (работает! вот чудо!), откомпилировать в P-Code и сделать вид, что всё в порядке.
Поскольку в этом случае межнитёвая изоляция не выполняется (глобальные объекты оказываются действительно глобальными), то в случайные и непредсказуемые моменты времени окажется, что рантайм одновременно с двух нитей читает и меняет одни и те же данные. А значит, в такой "многопоточной" программе окажется полно невылавливаемых, проявляющихся раз-на-тысячу и совершенно безпричинных багов. (См. главу книги Аппельмана про многопоточность в .net-е: он подробно описывает, как всё на самом деле сложно.)
Поскольку скомпилированная в Native Code программа расчитывает обнаружить в TLS все свои "глобальные" данные, а нить, созданная по CreateThread, не содержит TLS, то такой наивный способ запустить новую нить в скомпилированном коде не работает.
* И всё же написать на VB6 нормальную многопоточную программу возможно: для этого придётся в лучших традициях процедуры bootstrap (поднятие себя за шнурки ботинков) из созданной нити инициализировать TLS вручную. (Код на VB не может работать без TLS; как же он сам создаст себе TLS? Оказывается, сможет.)
Такие трюки, однако, требуют большого геморроя c хитрым API, который непременно нужно объявлять в TLB -- иначе магия не сработает.
* Если многопоточное VB6-приложение написано правильно, то глобальных данных -- а значит, и способов обмена данными между нитями -- нет вовсе. Координация нитей в таком приложении сопряжена с большими сложностями. (Можно, например, использовать атомы: они глобальные для оконной станции.)
Можно использовать объектные вызовы между нитями, но это именно то, чего лучше избегать: при таком вызове вызывающая нить замораживается до освобождения вызываемой -- фактически, программа превращается обратно в однопоточную. Да и откуда во вновь созданной нити взять ссылку на объект из другой нити? Ведь передать её не в чем.
* Если у вас после всего сказанного осталось желание писать на VB6 многопоточную прогу -- подумайте ещё раз. Многопоточность -- не магия, которая решит за программиста вопросы проектирования программы. Если у вас зависает однопоточная программа, то зависнет и многопоточная.
Далее, есть много способов обойтись без многопоточности. В самом худшем случае, можно запустить новую нить в крохотном ассемблерном переходнике, который будет общаться с основной программой посылкой сообщений (см. Waiter от GSerg-а). Но часто удаётся решить проблему одновременного выполнения нескольких задач вовсе не выходя за рамки одной нити (см. мои посты про фиберы).