<?xml version='1.0' encoding="utf-8"?>
      <rss version='2.0'>
      <channel>
      <title>Форум на Исходниках.RU</title>
      <link>https://forum.sources.ru</link>
      <description>Форум на Исходниках.RU</description>
      <generator>Форум на Исходниках.RU</generator>
  	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=413065&amp;view=findpost&amp;p=3798912</guid>
        <pubDate>Tue, 14 May 2019 12:26:34 +0000</pubDate>
        <title>Модуль для работы с многопоточностью на VB6.</title>
        <link>https://forum.sources.ru/index.php?showtopic=413065&amp;view=findpost&amp;p=3798912</link>
        <description><![CDATA[TheTrik: <span class="tag-color tag-color-named" data-value="red" style="color: red">Обновление.</span><br>
<br>
Пофикшены некоторые баги, удален неиспользуемый код, добавлены примеры. Проект переехал на GitHub.]]></description>
        <author>TheTrik</author>
        <category>Visual Basic: Общие вопросы</category>
      </item>
	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=413065&amp;view=findpost&amp;p=3771906</guid>
        <pubDate>Wed, 13 Jun 2018 07:09:26 +0000</pubDate>
        <title>Модуль для работы с многопоточностью на VB6.</title>
        <link>https://forum.sources.ru/index.php?showtopic=413065&amp;view=findpost&amp;p=3771906</link>
        <description><![CDATA[TheTrik: Всем привет&#33;<br>
Представляю модуль для работы с многопоточностью на VB6 для Standard EXE проектов. Данный модуль разработан на основе <a class='tag-url' href='http://bbs.vbstreets.ru/viewtopic.php?f=99&t=48959' target='_blank'>этого</a> решения в котором исправлены некоторые баги и добавлен дополнительный функционал. Модуль не требует никаких дополнительных зависимостей и библиотек типов, работает как в IDE (все функции работают в главном потоке) так и в скомпилированном виде.<br>
<br>
<a class='tag-url' href='https://www.youtube.com/watch?v=D1-3PlAoEnk' target='_blank'>https://www.youtube.com/watch?v=D1-3PlAoEnk</a><br>
<br>
Для начала работы с модулем нужно вызывать функцию <strong class='tag-b'>Initialize</strong>, которая инициализирует данные необходимые для работы (инициализирует критические секции для монопольного доступа к куче хидеров и потокам маршалинга, модифицирует <strong class='tag-b'>VBHeader</strong> (<a class='tag-url' href='http://bbs.vbstreets.ru/viewtopic.php?f=99&t=48959' target='_blank'>тут</a> написано для чего), выделяет <strong class='tag-b'>TLS</strong> слот для передачи параметров потоку).<br>
<br>
Основная функция создания потока - <strong class='tag-b'>vbCreateThread</strong> которая является аналогом функции <a class='tag-url' href='https://msdn.microsoft.com/en-us/library/windows/desktop/ms682453(v=vs.85).aspx' target='_blank'>CreateThread</a>.<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">&#39; // Create a new thread</div><div class="code_line">Public Function vbCreateThread(ByVal lpThreadAttributes As Long, _</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ByVal dwStackSize As Long, _</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ByVal lpStartAddress As Long, _</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ByVal lpParameter As Long, _</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ByVal dwCreationFlags As Long, _</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ByRef lpThreadId As Long, _</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Optional ByVal bIDEInSameThread As Boolean = True) As Long</div></ol></div></div></div></div><script>preloadCodeButtons('1');</script> <br>
Функция создает поток и вызывает функцию переданную в параметре <strong class='tag-b'>lpStartAddress</strong> с параметром <strong class='tag-b'>lpParameter</strong>. <br>
В IDE вызов сводится к простому вызову по указателю реализованному через <a class='tag-url' href='https://msdn.microsoft.com/en-us/library/windows/desktop/ms221473(v=vs.85).aspx' target='_blank'>DispCallFunc</a>. В скомпилированном варианте данная функция работает по-другому. Т.к. для работы потока требуется инициализация проектно-специфичных данных, а также инициализация рантайма, параметры переданные в <strong class='tag-b'>lpStartAddress</strong> и <strong class='tag-b'>lpParameter</strong> временно сохраняются в куче посредством функции <strong class='tag-b'>PrepareData</strong>, а поток создается в функции <strong class='tag-b'>ThreadProc</strong>, которая занимается непосредственно инициализацией и вызовом уже пользовательской функции с пользовательским параметром. Данная функция создает копию структуры <strong class='tag-b'>VBHeader</strong> через <strong class='tag-b'>CreateVBHeaderCopy</strong> и изменяет данные размещения публичных переменных в структурах <strong class='tag-b'>VbPublicObjectDescriptor.lpPublicBytes, VbPublicObjectDescriptor.lpStaticBytes</strong> (что не было сделано в предыдущем варианте) так чтобы глобальные переменные не затрагивались при инициализации. Далее <strong class='tag-b'>VBDllGetClassObject</strong> вызывает функцию <strong class='tag-b'>FakeMain</strong> (адрес которой записан в модифицированную структуру <strong class='tag-b'>VBHeader</strong>). Для передачи пользовательских параметров используется <strong class='tag-b'>TLS</strong> слот (т.к. функция <strong class='tag-b'>Main</strong> не принимает параметры, подробности <a class='tag-url' href='http://bbs.vbstreets.ru/viewtopic.php?f=99&t=48959' target='_blank'>тут</a>). В <strong class='tag-b'>FakeMain</strong> уже параметры извлекаются из <strong class='tag-b'>TLS</strong> и вызывается пользовательская процедура. Возвращаемое значение функции также передается обратно через <strong class='tag-b'>TLS</strong>. Тут есть один интересный момент связанный с копией хидера, который был не учтен в предыдущей версии. Т.к. рантайм использует хидер после завершения потока (при <strong class='tag-b'>DLL_THREAD_DETACH</strong>) мы не можем освободить хидер в процедуре <strong class='tag-b'>ThreadProc</strong>, и произойдет утечка памяти. Для предотвращения утечки памяти используется куча фиксированного размера, хидеры не очищаются пока есть место в этой куче. Как только место заканчивается (а оно выделяется в функции <strong class='tag-b'>CreateVBHeaderCopy</strong>) происходит очистка ресурсов. Первый <strong class='tag-b'>DWORD</strong> хидера на самом деле хранит <strong class='tag-b'>ID</strong> потока в котором был создан и в функции <strong class='tag-b'>FreeUnusedHeaders</strong> происходит проверка всех хидеров в куче. Если поток завершен - место освобождается (хотя ID может повторятся, но это не играет особой роли т.к. в любом случае в куче будут свободные места и если хидер не освободился в одном случае то он будет освобожден позже). Из-за того что очистка может быть вызвана сразу из нескольких потоков доступ к очистке разделяется критической секцией <strong class='tag-b'>tLockHeap.tWinApiSection</strong> и если какой-то поток уже занимается очисткой функция возвратит <strong class='tag-b'>True</strong> что означает что вызывающий поток должен немного подождать и память будет доступна.<br>
<br>
Еще одной из возможностей модуля является возможность инициализации рантайма и проекта и вызова <strong class='tag-b'>callback</strong>-фнкции. Это может пригодится для <strong class='tag-b'>сallback</strong>-функций которые могут быть вызваны в контексте произвольного потока (к примеру <a class='tag-url' href='https://msdn.microsoft.com/en-us/library/windows/desktop/aa385121(v=vs.85).aspx' target='_blank'>InternetStatusCallback</a>). Для этого служат функции <strong class='tag-b'>InitCurrentThreadAndCallFunction</strong> и <strong class='tag-b'>InitCurrentThreadAndCallFunctionIDEProc</strong>. Первая функция используется в скомпилированном приложении и принимает адрес функции обратного вызова которая будет вызвана после инициализации рантайма, а также параметра который будет передан в эту функцию. В <strong class='tag-b'>callback</strong>-процедуре передается адрес первого параметра чтобы потом в пользовательской процедуре ссылаться на него:<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">&#39; // This function is used in compiled form</div><div class="code_line">Public Function CallbackProc( _</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;ByVal lThreadId As Long, _</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;ByVal sKey As String, _</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;ByVal fTimeFromLastTick As Single) As Long</div><div class="code_line">&nbsp;&nbsp; &nbsp;&#39; // Init runtime and call CallBackProc_user with VarPtr(lThreadId) parameter</div><div class="code_line">&nbsp;&nbsp; &nbsp;InitCurrentThreadAndCallFunction AddressOf CallBackProc_user, VarPtr(lThreadId), CallbackProc</div><div class="code_line">End Function</div><div class="code_line">&nbsp;</div><div class="code_line">&#39; // Callback function is called by runtime/window proc (in IDE)</div><div class="code_line">Public Function CallBackProc_user( _</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;ByRef tParam As tCallbackParams) As Long</div><div class="code_line">&nbsp;</div><div class="code_line">End Function</div></ol></div></div></div></div><br>
<strong class='tag-b'>CallBackProc_user</strong> - будет вызвана уже с инициализированным рантаймом. <br>
<br>
Для работы в <strong class='tag-b'>IDE</strong> данная функция не подойдет, потому что в <strong class='tag-b'>IDE</strong> все работает в главном потоке. Для отладки в <strong class='tag-b'>IDE</strong> используется функция <strong class='tag-b'>InitCurrentThreadAndCallFunctionIDEProc</strong> которая возвращает адрес ассемблерного переходника который транслирует вызов в главный поток и вызовет пользовательскую функцию в контексте главного потока. Данная функция принимает адрес пользовательской <strong class='tag-b'>callback</strong>-функции и размер параметров в байтах. В качестве параметра пользовательской функции она всегда передает адрес первого параметра. Немного расскажу о работе этого механизма в <strong class='tag-b'>IDE</strong>. Для трансляции вызова из вызывающего потока в главный поток используется <strong class='tag-b'>message-only</strong> окно. Данное окно создается посредством вызова функции <strong class='tag-b'>InitializeMessageWindow</strong>. При первом вызове создается <strong class='tag-b'>WindowProc</strong> процедура со следующим кодом:<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">&nbsp;&nbsp; &nbsp;CMP DWORD [ESP+8], WM_ONCALLBACK</div><div class="code_line">&nbsp;&nbsp; &nbsp;JE SHORT L</div><div class="code_line">&nbsp;&nbsp; &nbsp;JMP DefWindowProcW</div><div class="code_line">&nbsp;L: &nbsp;PUSH DWORD PTR SS:[ESP+10]</div><div class="code_line">&nbsp;&nbsp; &nbsp;CALL DWORD PTR SS:[ESP+10]</div><div class="code_line">&nbsp;&nbsp; &nbsp;RETN 10</div></ol></div></div></div></div><br>
Как видно из кода данная процедура &quot;слушает&quot; сообщение <strong class='tag-b'>WM_ONCALLBACK</strong> которое содержит в параметре <strong class='tag-b'>wParam</strong> - адрес функции, а в параметре <strong class='tag-b'>lParam</strong> параметры. При получении этого сообщение она вызывает данную процедуру с данным параметром, остальные сообщения игнорируются. Данное сообщение отправляется как-раз ассемблерным переходником из <strong class='tag-b'>caller</strong>-потока.<br>
Далее создается окно и хендл этого окна и хендл кучи сохраняются в данных класса окна. Это используется для устранения утечки памяти в IDE, т.к. если класс один раз зарегистрирован то потом данные параметры можно получить в любой сессии отладки. Сама <strong class='tag-b'>callback</strong>-функция генерируется в <strong class='tag-b'>InitCurrentThreadAndCallFunctionIDEProc</strong>, но сначала проверяется не была ли уже создана подобная процедура (чтобы не создавать одинаковые переходники). Сам переходник имеет следющий код:<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">LEA EAX, [ESP+4]</div><div class="code_line">PUSH EAX</div><div class="code_line">PUSH pfnCallback</div><div class="code_line">PUSH WM_ONCALLBACK</div><div class="code_line">PUSH hMsgWindow</div><div class="code_line">Call SendMessageW</div><div class="code_line">RETN lParametersSize</div></ol></div></div></div></div><br>
Как видно из ода, что при вызове <strong class='tag-b'>callback</strong>-функции вызов транслируется через <strong class='tag-b'>SendMessage</strong> в главный поток. Параметр <strong class='tag-b'>lParametersSize</strong> используется для правильного восстановления стека.<br>
<br>
Следующая особенность модуля - это создание объектов в отдельном потоке причем создавать можно как приватные объекты (для этого используется метод основанный на коде модуля <a class='tag-url' href='http://bbs.vbstreets.ru/viewtopic.php?f=28&t=43201' target='_blank'>NameBasedObjectFactory by firehacker</a>). Для создания проектных классов используется функция <strong class='tag-b'>CreatePrivateObjectByNameInNewThread</strong> для <strong class='tag-b'>ActiveX</strong>-публичных классов <strong class='tag-b'>CreateActiveXObjectInNewThread</strong> и <strong class='tag-b'>CreateActiveXObjectInNewThread2</strong>. Прежде чем создавать экземпляры проектных классов нужно сначала разрешить маршалинг данных объектов посредством вызова функции <strong class='tag-b'>EnablePrivateMarshaling</strong>. Данные функции принимают идентификатор класса (<strong class='tag-b'>ProgID/CLSID</strong> для <strong class='tag-b'>ActiveX</strong> и имя для проектных классов), идентификатор интерфейса (по умолчанию используется <strong class='tag-b'>IDispatch/Object</strong>). При успешном вызове функции возвращают отмаршаленый объект и <strong class='tag-b'>ID</strong> асинхронного вызова. Для скомпилированного варианта это <strong class='tag-b'>ID</strong> потока для <strong class='tag-b'>IDE</strong> - указатель на объект. Объекты создаются и &quot;живут&quot; в функции <strong class='tag-b'>ActiveXThreadProc</strong>. Жизнь объектов контролируется через счетчик ссылок (когда он равен 1 значит только <strong class='tag-b'>ActiveXThreadProc</strong> ссылается на объект и можно его удалять и завершать поток. <br>
Вызывать методы можно синхронно - просто вызывать метод как обычно, либо асинхронно - используя процедуру <strong class='tag-b'>AsynchDispMethodCall</strong>. Данная процедура принимает <strong class='tag-b'>ID</strong> асинхронного вызова, имя метода, тип вызова, объект который получит уведомление о вызове, имя метода уведомления и список параметров. Процедура копирует параметры во временную область, маршалит объект уведомления и отправляет данные потоку объекта через <strong class='tag-b'>WM_ASYNCH_CALL</strong>. Стоит отметить что здесь не поддерживается маршалинг параметров, поэтому следует с осторожностью передавать ссылки на объекты. Если нужно отмаршалить объектную ссылку то следует использовать синхронный метод для маршалинга объектов и далее вызвать асинхронный метод. Процедура возвращается немедленно. В потоке <strong class='tag-b'>ActiveXThreadProc</strong> данные извлекаются и выполняется синхронный вызов посредством <strong class='tag-b'>MakeAsynchCall</strong>. Тут все просто, вызывается <strong class='tag-b'>CallByName</strong> для объекта потока и <strong class='tag-b'>CallByName</strong> для уведомления. Метод уведомления имеет следующий прототип: <div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">Public Sub CallBack(ByVal vRet As Variant)</div></ol></div></div></div></div>, где <strong class='tag-b'>vRet</strong> принимает возвращаемое значение метода.<br>
<br>
Следующие функции предназначены для маршалинга: <strong class='tag-b'>Marshal</strong>, <strong class='tag-b'>Marshal2</strong>, <strong class='tag-b'>UnMarshal</strong>, <strong class='tag-b'>FreeMarshalData</strong>. Первая создает информацию о маршалинге (<strong class='tag-b'>Proxy</strong>) интерфейса и заносит ее в поток который возвращает. Принимает в параметре <strong class='tag-b'>pInterface</strong> идентификатор интерфейса (по умолчанию <strong class='tag-b'>IDispatch/Object</strong>). Функция <strong class='tag-b'>UnMarshal</strong> наоборот принимает поток и на основе информации создает <strong class='tag-b'>Proxy</strong>-объект. Опционально можно освободить объект потока. <strong class='tag-b'>Marshal2</strong> делает тоже самое что и <strong class='tag-b'>Marshal</strong> за исключением того что она позволяет создавать <strong class='tag-b'>Proxy</strong>-объект множество раз в разных потоках. <strong class='tag-b'>FreeMarshalData</strong> соответственно освобождает данные и поток.<br>
Если к примеру требуется перенести ссылку на объект между двумя потоками то достаточно вызвать пару <strong class='tag-b'>Marshal</strong>/<strong class='tag-b'>UnMarshal</strong> соответственно в потоке создавшем объект и потоке принимающем ссылку. В другом случае если к примеру есть один глобальный объект и нужно передать ссылку на него во множество потоков (к примеру объект логирования) то в потоке объекта вызывается <strong class='tag-b'>Marshal2</strong>, а в клиентских потоках вызывается <strong class='tag-b'>UnMarshal</strong> с параметром <strong class='tag-b'>bReleaseStream</strong> равным <strong class='tag-b'>False</strong>. Когда данные больше не нужны вызывается <strong class='tag-b'>FreeMarshalData</strong>.<br>
<br>
Функция <strong class='tag-b'>WaitForObjectThreadCompletion</strong> предназначена для ожидания завершения объектного потока и принимает <strong class='tag-b'>ID</strong> асинхронного вызова. Эту функцию желательно вызывать всегда при завершении основного процесса, поскольку объектный поток так или иначе может взаимодействовать с основным потоком и его объектами (к примеру если объектный поток имеет маршаленую ссылку на интерфейс основного потока). <br>
<br>
Функция <strong class='tag-b'>SuspendResume</strong> предназначена для приостановления/возобновления объектного потока. <strong class='tag-b'>bSuspend</strong> определяет усыпить либо возобновить поток.<br>
<br>
Помимо модуля также в архиве есть несколько примеров работы с ним:<ol class="tag-list" type="1"><li><strong class='tag-b'>Callback</strong> - проект в котором продемонстрирована работа с <strong class='tag-b'>callback</strong>-функцией вызываемой из разных потоков. Также там содержится дополнительный проект нативной dll (на VB6) которая вызывает периодически функцию в разных потоках;</li><li><strong class='tag-b'>JuliaSet</strong> - генерация фрактала <strong class='tag-b'>Julia</strong> параллельно в нескольких потоках (задается пользователем);</li><li><strong class='tag-b'>CopyProgress</strong> - Копирование папки в отдельном потоке с отображением прогресса копирования;</li><li><strong class='tag-b'>PublicMarshaling</strong> - Создание публичных объектов (<strong class='tag-b'>Dictionary</strong>) в разных потоках и вызов их методов (синхронно/асинхронно); </li><li><strong class='tag-b'>PrivateMarshaling</strong> - Создание приватных объектов в разных потоках и вызов их методов (синхронно/асинхронно); </li><li><strong class='tag-b'>MarshalUserInterface</strong> - Создание приватных объектов в разных потоках и вызов их методов (синхронно/асинхронно) на основе пользовательских интерфейсов (содержит tlb и Reg-Free манифест). </li><li><strong class='tag-b'>InitProjectContextDll</strong> - Вызов экспортируемых функций из ActiveX-DLL в разных потоках. Вызов EXE-колбеков из ActiveX Dll из произвольных потоков;</li><li><strong class='tag-b'>InternetStatusCallback</strong> - Пример использования асинхронного скачивания файлов используя колбек функцию InternetStatusCallback вызываемую из разных потоков.</li></ol><br>
<a class='tag-url' href='https://github.com/thetrik/VbTrickThreading' target='_blank'>Проект на GitHub.</a><br>
<br>
Модуль слабо тестировался, поэтому возможны баги. Буду очень рад любым замечаниям, по мере возможности буду их исправлять.<br>
Всем спасибо за внимание&#33;]]></description>
        <author>TheTrik</author>
        <category>Visual Basic: Общие вопросы</category>
      </item>
	
      </channel>
      </rss>
	