<?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=414088&amp;view=findpost&amp;p=3789381</guid>
        <pubDate>Sun, 03 Feb 2019 12:06:07 +0000</pubDate>
        <title>PNG изображения в стандартных контролах VB6.</title>
        <link>https://forum.sources.ru/index.php?showtopic=414088&amp;view=findpost&amp;p=3789381</link>
        <description><![CDATA[TheTrik: <strong class='tag-b'><em class='tag-i'>3.02.2019</em></strong><br>
<strong class='tag-b'><span class="tag-color tag-color-named" data-value="red" style="color: red">Обновление.</span></strong><br>
Добавлена поддержка 32 битных иконок с альфаканалом, курсоров и анимированных курсоров.]]></description>
        <author>TheTrik</author>
        <category>Visual Basic: Общие вопросы</category>
      </item>
	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=414088&amp;view=findpost&amp;p=3789193</guid>
        <pubDate>Wed, 30 Jan 2019 15:13:11 +0000</pubDate>
        <title>PNG изображения в стандартных контролах VB6.</title>
        <link>https://forum.sources.ru/index.php?showtopic=414088&amp;view=findpost&amp;p=3789193</link>
        <description><![CDATA[B.V.: Осилил. Страшная магия, конечно. И только ради бесшовной работы с PNG. Не уверен, что цель оправдывает средства, но иметь выбор всегда, в принципе, хорошо. Да и не ради PNG, как я понимаю, всё это затевалось. А вот проверить, к сожалению, не на чем]]></description>
        <author>B.V.</author>
        <category>Visual Basic: Общие вопросы</category>
      </item>
	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=414088&amp;view=findpost&amp;p=3788683</guid>
        <pubDate>Thu, 24 Jan 2019 19:25:15 +0000</pubDate>
        <title>PNG изображения в стандартных контролах VB6.</title>
        <link>https://forum.sources.ru/index.php?showtopic=414088&amp;view=findpost&amp;p=3788683</link>
        <description><![CDATA[TheTrik: Всем привет.<br>
<br>
Как известно встроенные средства <strong class='tag-b'>Visual Basic 6.0</strong> не поддерживают возможности работы с PNG изображениями, т.е. к примеру нельзя ипользовать Png картинку в качестве свойства <strong class='tag-b'>Form.Picture</strong>. Я представляю небольшую библиотеку и Add-in которые позволяют обойти эти ограничения. Данная библиотека позволяет загружать и сохранять Png изображения (с альфа каналом) стандартными средствами (<strong class='tag-b'>LoadPicture</strong> /<strong class='tag-b'> SavePicture</strong>), а также включает поддержку Png изображений (с альфа каналом) в контролы. Любой контрол который в своей работе использует стандарнтые Ole Picture объекты будет поддерживать загрузку Png изображений. В свою очередь если изображение выводится посредством <strong class='tag-b'>IPicture::Render</strong> то картинка будет отрисовываться с учетом альфа канала. Данная библиотека должна работать на всех версиях Windows начиная с XP:<br>
<br>
<div class='tag-align-center'><img class='tag-img' src='https://s8.hostingkartinok.com/uploads/images/2019/01/921c0f41b27c6f87edf0382fd6a16db0.png' alt='user posted image'></div><br>
<br>
<br>
<span class='tag-size' data-value='21' style='font-size:21pt;'><div class='tag-align-center'>Как использовать?</div></span><br>
Библиотека может быть использована как внешняя DLL либо быть прилинкована к исполняемому файлу (только native code). Для использования в качестве Dll необходимо вызвать функцию <strong class='tag-b'>Initialize</strong> которая вернет 1 в случае успеха. После этого можно пользоваться возможностями библиотеки. Если необходимо выгрузить библиотеку то нужно вызвать функцию <strong class='tag-b'>CanUnloadNow</strong> которая сообщит можно ли в данный момент выгрузить библиотеку. Если библиотека готова к выгрузке функция вернет <strong class='tag-b'>S_OK</strong> после которой нужно вызвать <strong class='tag-b'>Uninitialize</strong>. Если функция возвращает <strong class='tag-b'>S_FALSE</strong> то библиотеку нельзя выгружать т.к. имеются активные Picture объекты которые еще не выгружены и они используют библиотеку. Для IDE создан специальный Add-in который автоматически загружает библиотеку при старте среды. В скомпилированном варианте можно к примеру в событии <strong class='tag-b'>Initialize</strong> или в процедуре <strong class='tag-b'>Main</strong> вызывать <strong class='tag-b'>Initialize</strong>, а при завершении <strong class='tag-b'>Uninitialize</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">Private Declare Function Initialize Lib &quot;VBPng.dll&quot; () As Long</div><div class="code_line">Private Declare Sub Uninitialize Lib &quot;VBPng.dll&quot; ()</div><div class="code_line">&nbsp;</div><div class="code_line">Private Sub Form_Initialize()</div><div class="code_line">&nbsp;</div><div class="code_line">&nbsp;&nbsp; &nbsp;If Initialize() = 0 Then</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;MsgBox &quot;Unable to initialize png dll&quot;, vbCritical</div><div class="code_line">&nbsp;&nbsp; &nbsp;End If</div><div class="code_line">&nbsp;&nbsp; &nbsp;</div><div class="code_line">End Sub</div><div class="code_line">&nbsp;</div><div class="code_line">Private Sub Form_Terminate()</div><div class="code_line">&nbsp;&nbsp; &nbsp;Uninitialize</div><div class="code_line">End Sub</div></ol></div></div></div></div><script>preloadCodeButtons('1');</script><br>
<br>
Для статической линковки необходимо использовать более новый линкер (в своих примерах я использовал линкер из <strong class='tag-b'>Visual Studio 2010</strong>), поскольку оригинальный имеет баги при использовании опции <strong class='tag-b'>/OPT:REF</strong>, а также в секцию <strong class='tag-b'>VBCompiler</strong> файла проекта (vbp) необходимо добавить параметры:<br>
Для EXE:<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">LinkSwitches= ..\Libs\msvcrt_winxp.obj ..\Libs\VBPng.lib -ENTRY:mainCRTStartup</div></ol></div></div></div></div><br>
Для DLL:<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">LinkSwitches= ..\Libs\msvcrt_winxp.obj ..\Libs\VBPng.lib -ENTRY:VBDllMain -EXPORT:Initialize -EXPORT:Uninitialize</div></ol></div></div></div></div><br>
В случае DLL, в скомпилированном виде необходимо сделать инициализацию, вызвав <strong class='tag-b'>Initialize</strong> из себя же при первом запуске.<br>
<br>
<br>
<span class='tag-size' data-value='21' style='font-size:21pt;'><div class='tag-align-center'>Как это работает?</div></span><br>
Библиотека написана на C++. Принцип работы библиотеки основан на перехвате функций <strong class='tag-b'>OleLoadPictureEx</strong> и <strong class='tag-b'>OleLoadPicture</strong>. Данные функции не поддерживают загрузку PNG изображений, поэтому если загружается PNG файл, библиотека <strong class='tag-b'>VbPng</strong> пытается загрузить файл с помощью <strong class='tag-b'>GDI+</strong>. При успехе создается аналогичный <strong class='tag-b'>StdPicture</strong> объект который и возвращается функцией. Для вызывающей стороны все это выглядит как-будто она работает с оригинальным объектом. Сам объект поддерживает интерфейсы <strong class='tag-b'>IPicture</strong>, <strong class='tag-b'>IPictureDisp</strong>, <strong class='tag-b'>IPersistStream</strong>, <strong class='tag-b'>IConnectionPointContainer</strong> (не поддерживает connection point&#39;ы возвращает <strong class='tag-b'>E_NOTIMPL</strong>), <strong class='tag-b'>IDispatch</strong>, поэтому может быть присвоен <strong class='tag-b'>Object</strong> переменной или к примеру быть сохраненным в <strong class='tag-b'>PropertyBag</strong>.<br>
<br>
Перехватчик функций реализован в классе <strong class='tag-b'>CHooker</strong>. Данный класс использует дизассемблер длин (<strong class='tag-b'>ldasm</strong>) от <strong class='tag-b'>Ms-Rem</strong> с небольшой доработкой. Доработка заключается в добавление флага <strong class='tag-b'>OP_REL32</strong> к некоторым инструкциям (к примеру <strong class='tag-b'>JMP SHORT</strong>), поскольку в оригинале на некоторых относительных инструкциях этот флаг отсутствовал. Для перехвата функции используется простейший метод сплайсинга при котором в начало функции всталяется инструкция <strong class='tag-b'>JMP</strong> которая переводит поток исполнения на функцию-перехватчик. Поскольку в начале оригинальной функции содержатся инструкции которые мы перезаписываем, необходимо правильно перенести инструкции для того чтобы была возможность вызвать оригинальную функцию. При вызове метода <strong class='tag-b'>Hook</strong> с помощью дизассемблера длин определяется целое количество инструкций которое будет перезаписано инструкцией <strong class='tag-b'>JMP</strong> (5 байт). После этого выделяется временный буфер (с разрешением на исполнение данных) в который будут скопированы данные инструкции + <strong class='tag-b'>JMP</strong> на инструкцию следующую за перезаписываемой. Это позволит, передав управление на этот буфер, вызвать оригинальную функцию как-будто перехвата не было. Тут существует одна сложность заключающаяся в том, что мы не можем просто так скопировать инструкции, поскольку существуют относительные инструкции типа <strong class='tag-b'>JMP</strong>, <strong class='tag-b'>CALL</strong>, <strong class='tag-b'>JNE</strong> которые &quot;прыгают&quot; относительно своего адреса. Для определения типа инструкции как раз и служит флаг <strong class='tag-b'>OP_REL32</strong> который показывает является ли инструкция относительной или нет. Другая сложность заключается в том что существуют &quot;короткие&quot; относительные инструкции которые &quot;прыгают&quot; в пределах 255 байт, а при переносе кода в буфер расстояние может значительно увеличится. Поэтому после определения количества перезаписываемых инструкций выделяется буфер размером как минимум чтобы обеспечить транслирование из коротких в длинные инструкции. После этого производится анализ каждой инструкции и при необходимости происходит корректировка смещения и типа. В конце буфера добавляется инструкция <strong class='tag-b'>JMP</strong> со смещением на инструкцию следующую за последней перезаписаной. Наконец начало функции перезаписывается на безусловный <strong class='tag-b'>JMP</strong> на функцию-перехватчик. <br>
<br>
<strong class='tag-b'>CHooker</strong> объекты используют в качестве буфера кода кучу (<strong class='tag-b'>Heap</strong>) с разрешением на исполнение, поэтому код является <strong class='tag-b'>DEP</strong> безопасным. Куча автоматически создается при создании первого перехватчика и удаляется при уничтожении последнего. В проекте используются 2 таких объекта для перехвата 2-х функций <strong class='tag-b'>OleLoadPictureEx</strong> и <strong class='tag-b'>OleLoadPicture</strong>, с соответствующим перехватчиками <strong class='tag-b'>OleLoadPictureEx_user</strong> и <strong class='tag-b'>OleLoadPicture_user</strong>. В системах до <strong class='tag-b'>Windows 8</strong> можно было перехватывать только одну <strong class='tag-b'>OleLoadPictureEx</strong> функцию которая вызывается из <strong class='tag-b'>OleLoadPicture</strong>, но начиная с <strong class='tag-b'>Windows 8</strong> <strong class='tag-b'>OleLoadPicture</strong> вызывает уже недокументированную <strong class='tag-b'>OleLoadPictureExt</strong>, поэтому для обеспечения правильной работы некоторых контролов (к примеру <strong class='tag-b'>ImageList</strong>) нужно перехватывать 2 этих функции. Конечно можно пробовать перехватывать <strong class='tag-b'>OleLoadPictureExt</strong>, но эта функция недокументирована и не факт что в новых версиях <strong class='tag-b'>Microsoft</strong> не изменят эту функцию на другую. В перехватчиках вызывается оригинальная функция и если вызов окончился неудачей вызывается наша реализация. Чтобы обеспечить возможность узнать был ли перехват уже осуществлен (к примеру подгруженная DLL уже перехватила и нет смысла делать это еще раз) используется переменна окружения <strong class='tag-b'>&quot;VBPng&quot;</strong>.<br>
<br>
Основа библиотеки - класс <strong class='tag-b'>CPicture</strong> который и реализует всю логику работы изображений. Данный класс создавался на основе реверс-инжиниринга библиотеки <strong class='tag-b'>oleaut32</strong> некоторые функции возможно реализованы не точно. Данный класс позволяет загружать PNG изображения из <strong class='tag-b'>COM</strong> потока (<strong class='tag-b'>IStream</strong>), а также сохранять их в него. Библиотека ведет учет созданных объектов в глобальной переменной <strong class='tag-b'>g_lCountOfObject</strong> для того чтобы обеспечить контроль при выгрузке библиотеки вызовом <strong class='tag-b'>CanUnloadNow</strong>. В противном случае не было бы способа узнать можно ли выгрузить библиотеку или нет. Соответственно при выгрузке библиотеки которой пользуются активные объекты происходило бы падение.<br>
<br>
Загрузка изображения выполняется в методе <strong class='tag-b'>LoadFromStream</strong>. Поскольку при загрузке из потока <strong class='tag-b'>GDI+</strong> автоматически устанавливает указатель в начало, приходится создавать поток в коотром содержатся только данные <strong class='tag-b'>PNG</strong> файла. Эта задача выполняется методом <strong class='tag-b'>CreatePngStream</strong> в котором происходит также первичная валидация PNG чанков. Далее с помощью <strong class='tag-b'>GDI+</strong> происходит создание объекта Bitmap из данных временного потока. Далее создается <strong class='tag-b'>DIB</strong>-секция и в нее копируются данные PNG пикселей в формате <strong class='tag-b'>PixelFormat32bppPARGB</strong>. Это позволяет выводить изображение с альфа-каналом посредством функции <strong class='tag-b'>AlphaBlend</strong>, а также имеется возможность доступа к <strong class='tag-b'>GDI</strong>-совместимому HBITMAP. <strong class='tag-b'>Далее</strong>, если установлено свойство <strong class='tag-b'>KeepOriginalFormat</strong> равным <strong class='tag-b'>true</strong>, происходит сохранение PNG потока (это позволяет легко сохранять PNG файл без перекодировки). <br>
<br>
Второй по важности метод - это <strong class='tag-b'>Render</strong>. Тут все просто, происходит подготовка координат для вывода изображения в <strong class='tag-b'>HIMETRIC</strong> и происходит вывод с помощью <strong class='tag-b'>AlphaBlend</strong>. Т.к. свойство <strong class='tag-b'>get_Attributes</strong> возвращает <strong class='tag-b'>PICTURE_TRANSPARENT</strong> то пользователь перед выводом изображения сам заботится о восстановлении фона за изображением.<br>
<br>
Метод <strong class='tag-b'>SaveAsFile</strong> сохраняет изображение в поток. Тут все тоже самое только наоборот. Также стоит отметить что если использовалось сохранение оригинального формата то данные изображение берутся из сохраненного PNG потока. В противном случае создается временный <strong class='tag-b'>GDI+</strong> битмап из пикселей <strong class='tag-b'>DIB</strong>-секции, извлекается <strong class='tag-b'>CLSID</strong> PNG кодека и происходит сохранение изображения во временный поток. Далее из этого потока данные копируются в поток назначения.<br>
<br>
Следующая группа методов это реализация интерфейса <strong class='tag-b'>IDispatch</strong>. Поскольку данные о типе <strong class='tag-b'>IPicture</strong> хранятся в стандартной библиотеке <strong class='tag-b'>stdole2.tlb</strong> то в методе <strong class='tag-b'>GetTypeInfo</strong> происходит загрузка этой библиотеки с извлечением нужного интерфейса типа через <strong class='tag-b'>ITypeLib::GetTypeInfoOfGuid</strong>. Тоже самое относится к методу <strong class='tag-b'>GetIDsOfNames</strong>, тут просто происходит транслирование вызова стандартному <strong class='tag-b'>ITypeInfo::GetIDsOfNames</strong>. Метод Invoke реализован напрямую с проверкой параметров.<br>
<br>
Для того чтобы можно было статически прилинковать библиотеку к <strong class='tag-b'>VB6 EXE</strong> файлу необходимо инициализировать сишный рантайм передачей управления на функцию <strong class='tag-b'>mainCRTStartup</strong> и передать управление на метку <strong class='tag-b'>___vbaS</strong>. Для этой цели служит файл <strong class='tag-b'>gostartup.asm</strong> написаный на <strong class='tag-b'>fasm&#39;е</strong>. Для <strong class='tag-b'>EXE</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">_main:</div><div class="code_line">call Initialize</div><div class="code_line">jmp ___vbaS</div></ol></div></div></div></div><br>
Сишный рантайм вызывает функцию <strong class='tag-b'>main</strong>, а она в свою очередь инициализирует библиотеку <strong class='tag-b'>VbPng</strong>. Тут существует проблема со старым линкером, поскольку то ли из-за бага, то ли из-за чего то еще, ликер отбрасывает весь VB-шный импорт из результирующего файла при использовании опции <strong class='tag-b'>-OPT:REF</strong>. Решается данная проблема просто - заменой линкера на современный.<br>
Для DLL выполняются похожие действия, только в этом случае необходимо указать в качестве точки входа <strong class='tag-b'>_VBDllMain</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">_VBDllMain:</div><div class="code_line">&nbsp;</div><div class="code_line">push dword [esp + 12]</div><div class="code_line">push dword [esp + 12]</div><div class="code_line">push dword [esp + 12]</div><div class="code_line">&nbsp;</div><div class="code_line">; // Init CRT</div><div class="code_line">call &nbsp;__DllMainCRTStartup@12</div><div class="code_line">&nbsp;</div><div class="code_line">; // Init runtime</div><div class="code_line">jmp ___vbaS</div></ol></div></div></div></div><br>
В этом случае сначала вызывается инициализации сишного рантайма, а затем происходит переход на функцию <strong class='tag-b'>DllMain ActiveX Dll</strong>.<br>
<br>
Для олегчения работы в IDE был написан Add-in который автоматически загружает <strong class='tag-b'>VbPng.dll</strong> для того чтобы было удобно работать с проектами. Для отключения библиотеки просто нужно отключить Add-in. Тут есть ньюанс, если есть активные PNG-изображения, то Add-in выгрузится, но <strong class='tag-b'>VbPng</strong> нет, при этом покажется предупреждение. В любой момент можно будет включить Add-in, найти изображения, удалить их, и заново отключить Add-in, тогда DLL выгрузится.<br>
<br>
<hr><br>
<br>
Некоторые контролы, к примеру <strong class='tag-b'>ListView</strong>, не будут отображать альфа канал, поскольку отрисовывают себя не методом <strong class='tag-b'>Render</strong>, а через <strong class='tag-b'>StretchBlt</strong>, для них <strong class='tag-b'>premultiplied</strong> фон будет черный. Это следует иметь в виду при работе с библиотекой. Также не поддерживаются уведомления <strong class='tag-b'>IPropertyNotifySink</strong> (при желании можно реализовать). Ресурсы в <strong class='tag-b'>FRX</strong> файлах и скомпилированных файлах также хранятся в PNG поэтому проекты не будут открываться и работать без библиотеки. Для комфортной работы рекомендуется установить Add-in с автоматическим запуском при загрузке IDE.<br>
<br>
В директории содержатся также несколько примеров работы:<ul class="tag-list"><li><strong class='tag-b'>Test_EXE_Linked</strong> - демонстрация <strong class='tag-b'>32bpp</strong> PNG изображений на стандартных контролах с использованием статической линковки;</li><li><strong class='tag-b'>Test_EXE_Dll</strong> - тоже самое только с использованием dll;</li><li><strong class='tag-b'>Test_AXDll</strong> - ActiveX DLL библиотека с использованием PNG ресурсов на форме;</li><li><strong class='tag-b'>Test_SavePng</strong> - пример сохранения изображения посредством <strong class='tag-b'>SavePicture</strong>.</li></ul><br>
Также в директории содержатся PNG файлы, собраные мной еще давно посредством спутниковой рыбалки.<br>
<br>
Модуль слабо тестировался, поэтому возможны баги. Буду очень рад любым замечаниям, по мере возможности буду их исправлять.<br>
Всем спасибо за внимание, надеюсь модуль кому-то будет полезен.<br>
<br>
<a class='tag-url' href='https://github.com/thetrik/VbPng' target='_blank'>Проект на GitHub.</a><br>
<br>
<a class='tag-url' href='https://www.youtube.com/watch?v=ivmYQeyIDr8' target='_blank'>https://www.youtube.com/watch?v=ivmYQeyIDr8</a><br>
<br>
<strong class='tag-b'>The trick</strong>,<br>
2019.]]></description>
        <author>TheTrik</author>
        <category>Visual Basic: Общие вопросы</category>
      </item>
	
      </channel>
      </rss>
	