tag:blogger.com,1999:blog-49468644124550036942024-03-13T16:33:25.492+05:00Программирование, Delphi и ЛоцманБлог о программировании, об особенностях Delphi и разработке компонентов, о работе с ЛОЦМАН:PLM и подключаемых модуляхChaahttp://www.blogger.com/profile/14387721107858333063noreply@blogger.comBlogger20125tag:blogger.com,1999:blog-4946864412455003694.post-67974338232563365942022-04-22T08:00:00.040+05:002023-04-04T12:45:21.152+05:00KB5004442 и старые версии ЛОЦМАН:PLM<span style="color: #800180;">С момента публикации ситуация изменилась, и проблема, описанная в статье, уже не так актуальна.</span>
<br/>
<br/>
<span style="color: #800180;">В январе 2023 года Microsoft выпустила обновление, которое автоматически заменяет уровень проверки подлинности на минимально необходимый (auto-elevation patch).</span>
<br/>
<br/>
Тем не менее, если у вас разные поколения Windows на клиенткских и серверных машинах, или где-то нет последних обновлений, то информация может пригодится.
<br/>
<br/>
Оригинальный текст:
<br/>
<br/>
В сентябре 2021 года вышло обновление Windows с исправлением уязвимости
<a href="https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-26414" target="_blank">CVE-2021-26414</a>.
Подробности уязвимости не раскрываются, сказано лишь, что
уязвимость связана с кражей учетных данных пользователя при подключении
к злонамеренному серверу.
<br/>
<br/>
Для закрытия уязвимости исправление
<a href="https://support.microsoft.com/en-us/topic/kb5004442-manage-changes-for-windows-dcom-server-security-feature-bypass-cve-2021-26414-f1400b52-c141-43d2-941e-37ed901c769c" target="_blank">KB5004442</a>
повышает минимальный уровень проверки подлинности при удаленных вызовах DCOM.
<br/>
<br/>
Для пользователей ЛОЦМАН:PLM это означает, что для версии меньше 2017 могут возникнуть проблемы в работе.
<br /><br /><a name='more'></a>
Так как исправление потенциально затрагивает большое количество
программ, Microsoft вводит обновление в три этапа:
<ol>
<li>До 14 июня 2022 г. ужесточенные настройки отключены по умолчанию,
но могут быть включены с помощью ключа реестра.</li>
<li>С 14 июня 2022 г. по 14 марта 2022 г. ужесточенные настройки будут включены по умолчанию,
но их можно будет отключить с помощью реестра.</li>
<li>С 14 марта 2023 г. ужесточенные настройки останутся включенными постоянно, и отключить их будет невозможно.</li>
</ol>
<br/>
Чтобы помочь пользователям увидеть проблемные приложения
в Журнал событий Windows записываются примерно такие сообщения:
<blockquote>Приложение C:\Program Files (x86)\ASCON\Loodsman\Client\Loodsman.exe с ИД процесса 2d98 запрашивает активацию CLSID {392BA982-A82F-44AB-BA8A-69BE25199F73} на компьютере AS с уровнем проверки подлинности по умолчанию на 2. Самый низкий уровень проверки подлинности активации, требуемой DCOM, составляет 5 (RPC_C_AUTHN_LEVEL_PKT_INTEGRITY). Чтобы повысить уровень проверки подлинности активации, обратитесь к поставщику приложения.</blockquote>
<br/>
Так как старые версии ЛОЦМАН:PLM официально не поддерживаются
(<a href="https://support.ascon.ru/conditions/available_versions/" target="_blank">список поддерживаемых версий</a>),
то есть несколько вариантов самостоятельного решения данной проблемы.
<br/>
<br/>
Во-первых, не устанавливать обновления или использовать старые версии Windows,
для которых нет данного обновления.
В большинстве случаев это невозможно из соображений безопасности.
<br/>
<br/>
Во вторых, можно пропатчить исполняемые файлы.
Достаточно заменить один параметр в вызове <code>CoInitializeSecurity</code>.
Ниже <code>5</code> — это необходимый уровень проверки подлинности <code>RPC_C_AUTHN_LEVEL_PKT_INTEGRITY</code>.
В оригинальном коде на этом месте <code>1</code>.
<br/>
<pre class="pascal">CoInitializeSecurity<span style="color: #009900;">(</span><span style="color: #000000; font-weight: bold;">nil</span><span style="color: #000066;">,</span> <span style="color: #000066;">-</span><span style="color: #cc66cc;">1</span><span style="color: #000066;">,</span> <span style="color: #000000; font-weight: bold;">nil</span><span style="color: #000066;">,</span> <span style="color: #000000; font-weight: bold;">nil</span><span style="color: #000066;">,</span> <span style="color: #cc66cc;">5</span><span style="color: #000066;">,</span> <span style="color: #cc66cc;">4</span><span style="color: #000066;">,</span> <span style="color: #000000; font-weight: bold;">nil</span><span style="color: #000066;">,</span> <span style="color: #cc66cc;">0</span><span style="color: #000066;">,</span> <span style="color: #000000; font-weight: bold;">nil</span><span style="color: #009900;">)</span><span style="color: #000066;">;</span></pre>
Делать это нужно самостоятельно, так как по закону только конечный пользователь
может модифицировать купленное ПО для обеспечения его работоспособности.
<br/>
<br/>
Третий вариант очень похож на второй, но исполняемый файл модифицируется уже
после загрузки в память. Для этого в процесс Лоцман Клиент нужно загрузить свой
код. Проще всего рядом с <code>Loodsman.exe</code> положить специально созданную DLL вроде
<code>shfolder.dll</code>. Загрузчик операционной системы выполнит
код из нашей DLL, который подменит <code>CoInitializeSecurity</code>.
<br/>
<br/>
Готовая <a href="https://uselwf.ru/files/KB5004442.zip">скомпилированная <code>shfolder.dll</code></a>
данного варианта и ее
<a href="https://github.com/achechulin/loodsman/tree/master/Plugins/KB5004442/" target="_blank">исходный код на GitHub</a>.
DLL нужно положить в папку рядом с <code>Loodsman.exe</code>.
<br/>
<br/>
Есть еще четвертый вариант — перед запуском Лоцман Клиент запустить приложение,
которое делает правильный вызов <code>CoInitializeSecurity</code> и создает
удаленный COM-объект на сервере приложений, например <a href="https://uselwf.ru/" target="_blank">LWF</a>
(версии 1.1.0.930 и старше).
<br/>
<br/>
Судя по всему, контекст активации компонента DCOM кэшируется сервером,
поэтому последующий запуск Лоцман Клиент проходит успешно.
И до тех пор, пока первый клиент с правильным <code>CoInitializeSecurity</code>
не освободит объекты, они будут работать.
Так же будет возможно создание новых объектов
(например, движение по бизнес-процессу с помощью библиотеки
<code>WorkflowBusinessLogic.dll</code>,
запуск интегратора или запуск дизайнер бизнес-процессов).
<br/>
<br/>
Недостаток метода в том, что LWF заберет клиентские лицензии.
Теоретически, это можно обойти, если сделать специальный режим работы, в котором
LWF будет устанавливать правильный уровень проверки подлинности и
создавать объекты на сервере приложений без вызова <code>ConnectToDB</code>.
Но так как есть третий вариант, думаю этот пока не актуален.
<br/>
<br/>
Пользователям старых версий рекомендую установить
ключ реестра <code>RequireIntegrityActivationAuthenticationLevel</code>
(подробнее в статье
<a href="https://support.microsoft.com/en-us/topic/kb5004442-manage-changes-for-windows-dcom-server-security-feature-bypass-cve-2021-26414-f1400b52-c141-43d2-941e-37ed901c769c" target="_blank">KB5004442</a>)
и попробовать разные варианты.
<br/>
<br/>
В <a href="https://uselwf.ru/" target="_blank">LWF</a> уровень проверки подлинности
исправлен в версия <a href="https://uselwf.ru/files/LWF_1.1.0.930.zip">1.1.0.930</a> (и старше).
<br/>
Chaahttp://www.blogger.com/profile/14387721107858333063noreply@blogger.com0tag:blogger.com,1999:blog-4946864412455003694.post-91367356635988416532018-10-29T08:00:00.000+05:002019-07-03T14:01:17.105+05:00Пишем подключаемый модуль на C#У начинающих разработчиков часто появляются вопросы,
как написать плагин к ЛОЦМАН Клиент на C#.
<br><br>
Основные проблемы составляет следующее:
<ul>
<li>Как экспортировать из сборки функции <code>InitUserDLLCom</code>,
<code>PgiCheckMenuItemCom</code> и другие?
<li>Как получить описания интерфейсов клиентского приложения?
<li>Как реализовать функцию <code>InitUserDLLCom</code>?
<li>Как разместить сборку и ее зависимости в отдельной папке?
<li>Как добавить значки для пунктов меню?
</ul>
<br>
Подробнее об этом ниже.
<br/><br/>
<a name='more'></a>
Общую информацию о создании плагинов можно почитать в статье
<a href="/2012/04/plugin-sample.html">Пишем подключаемый модуль для ЛОЦМАН Клиент</a>.
<br><br>
<h3>Экспорт функций</h3>
<br>
Для экспорта функций из DLL сборки понадобятся дополнительные библиотеки.
Самый простой путь это установить библиотеку
<a href="https://sites.google.com/site/robertgiesecke/Home/uploads/unmanagedexports" target="_blank">Unmanaged Exports</a>.
<br><br>
В меню проекта выбираем «Manage NuGet Packages», ищем и устанавливаем Unmanaged Exports.
<br><br>
<div class="separator" style="clear: both; text-align: center;"><a href="https://4.bp.blogspot.com/-xQB1PPbjUPY/W9LryoaRr5I/AAAAAAAAAjI/ADYTDrQnXOAOQZbTHl68JAvE4VM7dGZogCLcBGAs/s1600/UnmanagedExports.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://4.bp.blogspot.com/-xQB1PPbjUPY/W9LryoaRr5I/AAAAAAAAAjI/ADYTDrQnXOAOQZbTHl68JAvE4VM7dGZogCLcBGAs/s400/UnmanagedExports.png" width="400" height="272" data-original-width="899" data-original-height="611" /></a></div>
<br>
У библиотеки Unmanaged Exports есть дальнейшее развитие
<a href="https://github.com/3F/DllExport" target="_blank">github.com/3F/DllExport</a>,
но я с ним не работал.
<br><br>
Экспортируемые плагином функции должны выглядеть так:
<br>
<pre style='color:#000000;background:#ffffff;'><span style='color:#575757; font-weight:bold; '>public</span> <span style='color:#575757; font-weight:bold; '>class</span> PluginFunctions
<span style='color:#555555; '>{</span>
<span style='color:#555555; '>[</span>DllExport<span style='color:#555555; '>(</span><span style='color:#2a2a2a; '>"</span><span style='color:#4c4c4c; '>GetPluginInfo</span><span style='color:#2a2a2a; '>"</span><span style='color:#555555; '>,</span> CallingConvention<span style='color:#555555; '>.</span>StdCall<span style='color:#555555; '>)</span><span style='color:#555555; '>]</span>
<span style='color:#575757; font-weight:bold; '>public</span> <span style='color:#575757; font-weight:bold; '>static</span> Int32 GetPluginInfo<span style='color:#555555; '>(</span>Int32 Param<span style='color:#555555; '>,</span> IntPtr Value<span style='color:#555555; '>)</span>
<span style='color:#555555; '>{</span>
<span style='color:#575757; font-weight:bold; '>return</span> <span style='color:#2e2e2e; '>0</span><span style='color:#555555; '>;</span>
<span style='color:#555555; '>}</span>
<span style='color:#555555; '>[</span>DllExport<span style='color:#555555; '>(</span><span style='color:#2a2a2a; '>"</span><span style='color:#4c4c4c; '>InitUserDLLCom</span><span style='color:#2a2a2a; '>"</span><span style='color:#555555; '>,</span> CallingConvention<span style='color:#555555; '>.</span>StdCall<span style='color:#555555; '>)</span><span style='color:#555555; '>]</span>
<span style='color:#575757; font-weight:bold; '>public</span> <span style='color:#575757; font-weight:bold; '>static</span> Int32 InitUserDLLCom<span style='color:#555555; '>(</span>IntPtr Value<span style='color:#555555; '>)</span>
<span style='color:#555555; '>{</span>
<span style='color:#575757; font-weight:bold; '>return</span> <span style='color:#2e2e2e; '>0</span><span style='color:#555555; '>;</span>
<span style='color:#555555; '>}</span>
<span style='color:#555555; '>[</span>DllExport<span style='color:#555555; '>(</span><span style='color:#2a2a2a; '>"</span><span style='color:#4c4c4c; '>PgiCheckMenuItemCom</span><span style='color:#2a2a2a; '>"</span><span style='color:#555555; '>,</span> CallingConvention<span style='color:#555555; '>.</span>StdCall<span style='color:#555555; '>)</span><span style='color:#555555; '>]</span>
<span style='color:#575757; font-weight:bold; '>public</span> <span style='color:#575757; font-weight:bold; '>static</span> Int32 PgiCheckMenuItemCom<span style='color:#555555; '>(</span>IntPtr Function<span style='color:#555555; '>,</span> IntPtr IPC<span style='color:#555555; '>)</span>
<span style='color:#555555; '>{</span>
<span style='color:#575757; font-weight:bold; '>return</span> <span style='color:#2e2e2e; '>0</span><span style='color:#555555; '>;</span>
<span style='color:#555555; '>}</span>
<span style='color:#555555; '>[</span>DllExport<span style='color:#555555; '>(</span><span style='color:#2a2a2a; '>"</span><span style='color:#4c4c4c; '>ProjectList</span><span style='color:#2a2a2a; '>"</span><span style='color:#555555; '>,</span> CallingConvention<span style='color:#555555; '>.</span>StdCall<span style='color:#555555; '>)</span><span style='color:#555555; '>]</span>
<span style='color:#575757; font-weight:bold; '>public</span> <span style='color:#575757; font-weight:bold; '>static</span> <span style='color:#575757; font-weight:bold; '>void</span> ProjectList<span style='color:#555555; '>(</span>IntPtr IPC<span style='color:#555555; '>)</span>
<span style='color:#555555; '>{</span>
<span style='color:#575757; font-weight:bold; '>return</span><span style='color:#555555; '>;</span>
<span style='color:#555555; '>}</span>
<span style='color:#555555; '>}</span>
</pre>
<h3>Импорт описания интерфейсов клиентского приложения</h3>
<br>
В проекте выбираем «References → Add Reference»,
далее «COM → Type Libraries».
В списке находим «Loodsman Client Library»,
и ставим напротив галочку.
<br><br>
<div class="separator" style="clear: both; text-align: center;"><a href="https://2.bp.blogspot.com/-rsi1EKeO6Fk/W9MAHHkpehI/AAAAAAAAAjU/GM46wf8XxEMcMlKn3s9-cBFiOHEdU7NZwCLcBGAs/s1600/ImportRef.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://2.bp.blogspot.com/-rsi1EKeO6Fk/W9MAHHkpehI/AAAAAAAAAjU/GM46wf8XxEMcMlKn3s9-cBFiOHEdU7NZwCLcBGAs/s400/ImportRef.png" width="400" height="280" data-original-width="800" data-original-height="560" /></a></div>
<br>
В проект должны будут добавиться зависимости
<code>Ask</code>, <code>BOSimple</code>, <code>DataProvider</code>,
<code>Loodsman</code> и <code>PDMObjects</code>.
Список может немного отличаться для разных версий ЛОЦМАН:PLM.
<br><br>
Для плагина понадобятся минимум две:
<br>
<pre style="color:#000000;background:#ffffff;"><span style="color:#575757; font-weight:bold; ">using</span> DataProvider<span style="color:#555555; ">;</span> <span style="color:#696969; ">// IDataSet</span>
<span style="color:#575757; font-weight:bold; ">using</span> Loodsman<span style="color:#555555; ">;</span> <span style="color:#696969; ">// IPluginCall</span>
</pre>
<h3>Реализация экспортируемых функций</h3>
<br>
Для реализации функции <code>InitUserDLLCom</code> можно пойти двумя путями:
описать структуру <code>MenuItem</code> а затем с помощью <code>Marshal.StructureToPtr</code>
записать ее в переданную память, либо просто скопировать байты строк в переданную память
с нужным смещением используя <code>Marshal.Copy</code>.
<br><br>
Хранить меню плагина будем в массиве парами строк:
<br>
<pre style='color:#000000;background:#ffffff;'><span style='color:#575757; font-weight:bold; '>public</span> <span style='color:#575757; font-weight:bold; '>static</span> <span style='color:#575757; font-weight:bold; '>string</span><span style='color:#555555; '>[</span><span style='color:#555555; '>]</span> PluginMenu <span style='color:#555555; '>=</span>
<span style='color:#555555; '>{</span>
<span style='color:#2a2a2a; '>"</span><span style='color:#4c4c4c; '>ProjectList</span><span style='color:#2a2a2a; '>"</span><span style='color:#555555; '>,</span> <span style='color:#2a2a2a; '>"</span><span style='color:#4c4c4c; '>BEFORE_MI_TOOLS#Мои плагины#Тестовый#Список проектов</span><span style='color:#2a2a2a; '>"</span><span style='color:#555555; '>,</span>
<span style='color:#2a2a2a; '>"</span><span style='color:#4c4c4c; '>LinkedFast</span><span style='color:#2a2a2a; '>"</span><span style='color:#555555; '>,</span> <span style='color:#2a2a2a; '>"</span><span style='color:#4c4c4c; '>BEFORE_MI_TOOLS#Мои плагины#Тестовый#Состав</span><span style='color:#2a2a2a; '>"</span> <span style='color:#555555; '>,</span>
<span style='color:#555555; '>}</span><span style='color:#555555; '>;</span>
</pre>
Первый вариант со структурой <code>MenuItem</code>:
<br>
<pre style="color:#000000;background:#ffffff;"><span style="color:#555555; ">[</span>StructLayout<span style="color:#555555; ">(</span>LayoutKind<span style="color:#555555; ">.</span>Sequential<span style="color:#555555; ">,</span> CharSet <span style="color:#555555; ">=</span> CharSet<span style="color:#555555; ">.</span>Ansi<span style="color:#555555; ">)</span><span style="color:#555555; ">]</span>
<span style="color:#575757; font-weight:bold; ">struct</span> MenuItem
<span style="color:#555555; ">{</span>
<span style="color:#555555; ">[</span>MarshalAs<span style="color:#555555; ">(</span>UnmanagedType<span style="color:#555555; ">.</span>ByValTStr<span style="color:#555555; ">,</span> SizeConst <span style="color:#555555; ">=</span> <span style="color:#2e2e2e; ">255</span><span style="color:#555555; ">)</span><span style="color:#555555; ">]</span>
<span style="color:#575757; font-weight:bold; ">public</span> <span style="color:#575757; font-weight:bold; ">string</span> menu<span style="color:#555555; ">;</span>
<span style="color:#555555; ">[</span>MarshalAs<span style="color:#555555; ">(</span>UnmanagedType<span style="color:#555555; ">.</span>ByValTStr<span style="color:#555555; ">,</span> SizeConst <span style="color:#555555; ">=</span> <span style="color:#2e2e2e; ">255</span><span style="color:#555555; ">)</span><span style="color:#555555; ">]</span>
<span style="color:#575757; font-weight:bold; ">public</span> <span style="color:#575757; font-weight:bold; ">string</span> func<span style="color:#555555; ">;</span>
<span style="color:#555555; ">}</span>
<span style="color:#555555; ">[</span>DllExport<span style="color:#555555; ">(</span><span style="color:#2a2a2a; ">"</span><span style="color:#4c4c4c; ">InitUserDLLCom</span><span style="color:#2a2a2a; ">"</span><span style="color:#555555; ">,</span> CallingConvention<span style="color:#555555; ">.</span>StdCall<span style="color:#555555; ">)</span><span style="color:#555555; ">]</span>
<span style="color:#575757; font-weight:bold; ">public</span> <span style="color:#575757; font-weight:bold; ">static</span> Int32 InitUserDLLCom<span style="color:#555555; ">(</span>IntPtr Value<span style="color:#555555; ">)</span>
<span style="color:#555555; ">{</span>
<span style="color:#575757; font-weight:bold; ">if</span> <span style="color:#555555; ">(</span>Value <span style="color:#555555; ">!</span><span style="color:#555555; ">=</span> IntPtr<span style="color:#555555; ">.</span>Zero<span style="color:#555555; ">)</span>
<span style="color:#555555; ">{</span>
<span style="color:#575757; font-weight:bold; ">for</span> <span style="color:#555555; ">(</span><span style="color:#575757; font-weight:bold; ">int</span> i <span style="color:#555555; ">=</span> <span style="color:#2e2e2e; ">0</span><span style="color:#555555; ">;</span> i <span style="color:#555555; "><</span> PluginMenu<span style="color:#555555; ">.</span>Length <span style="color:#555555; ">/</span> <span style="color:#2e2e2e; ">2</span><span style="color:#555555; ">;</span> <span style="color:#555555; ">+</span><span style="color:#555555; ">+</span>i<span style="color:#555555; ">)</span>
<span style="color:#555555; ">{</span>
<span style="color:#575757; font-weight:bold; ">var</span> item <span style="color:#555555; ">=</span> <span style="color:#575757; font-weight:bold; ">new</span> MenuItem<span style="color:#555555; ">(</span><span style="color:#555555; ">)</span> <span style="color:#555555; ">{</span> menu <span style="color:#555555; ">=</span> PluginMenu<span style="color:#555555; ">[</span>i <span style="color:#555555; ">*</span> <span style="color:#2e2e2e; ">2</span> <span style="color:#555555; ">+</span> <span style="color:#2e2e2e; ">1</span><span style="color:#555555; ">]</span><span style="color:#555555; ">,</span>
func <span style="color:#555555; ">=</span> PluginMenu<span style="color:#555555; ">[</span>i <span style="color:#555555; ">*</span> <span style="color:#2e2e2e; ">2</span><span style="color:#555555; ">]</span> <span style="color:#555555; ">}</span><span style="color:#555555; ">;</span>
Marshal<span style="color:#555555; ">.</span>StructureToPtr<span style="color:#555555; ">(</span>item<span style="color:#555555; ">,</span> Value<span style="color:#555555; ">,</span> <span style="color:#575757; font-weight:bold; ">false</span><span style="color:#555555; ">)</span><span style="color:#555555; ">;</span>
Value <span style="color:#555555; ">+</span><span style="color:#555555; ">=</span> Marshal<span style="color:#555555; ">.</span>SizeOf<span style="color:#555555; ">(</span><span style="color:#575757; font-weight:bold; ">typeof</span><span style="color:#555555; ">(</span>MenuItem<span style="color:#555555; ">)</span><span style="color:#555555; ">)</span><span style="color:#555555; ">;</span>
<span style="color:#555555; ">}</span>
<span style="color:#555555; ">}</span>
<span style="color:#575757; font-weight:bold; ">return</span> PluginMenu<span style="color:#555555; ">.</span>Length <span style="color:#555555; ">/</span> <span style="color:#2e2e2e; ">2</span><span style="color:#555555; ">;</span>
<span style="color:#555555; ">}</span>
</pre>
Второй вариант с копированием байтов строк по нужным смещениям:
<br>
<pre style="color:#000000;background:#ffffff;"><span style="color:#555555; ">[</span>DllExport<span style="color:#555555; ">(</span><span style="color:#2a2a2a; ">"</span><span style="color:#4c4c4c; ">InitUserDLLCom</span><span style="color:#2a2a2a; ">"</span><span style="color:#555555; ">,</span> CallingConvention<span style="color:#555555; ">.</span>StdCall<span style="color:#555555; ">)</span><span style="color:#555555; ">]</span>
<span style="color:#575757; font-weight:bold; ">public</span> <span style="color:#575757; font-weight:bold; ">static</span> Int32 InitUserDLLCom<span style="color:#555555; ">(</span>IntPtr Value<span style="color:#555555; ">)</span>
<span style="color:#555555; ">{</span>
<span style="color:#575757; font-weight:bold; ">if</span> <span style="color:#555555; ">(</span>Value <span style="color:#555555; ">!</span><span style="color:#555555; ">=</span> IntPtr<span style="color:#555555; ">.</span>Zero<span style="color:#555555; ">)</span>
<span style="color:#555555; ">{</span>
<span style="color:#575757; font-weight:bold; ">for</span> <span style="color:#555555; ">(</span><span style="color:#575757; font-weight:bold; ">int</span> i <span style="color:#555555; ">=</span> <span style="color:#2e2e2e; ">0</span><span style="color:#555555; ">;</span> i <span style="color:#555555; "><</span> PluginMenu<span style="color:#555555; ">.</span>Length <span style="color:#555555; ">/</span> <span style="color:#2e2e2e; ">2</span><span style="color:#555555; ">;</span> <span style="color:#555555; ">+</span><span style="color:#555555; ">+</span>i<span style="color:#555555; ">)</span>
<span style="color:#555555; ">{</span>
<span style="color:#575757; font-weight:bold; ">byte</span><span style="color:#555555; ">[</span><span style="color:#555555; ">]</span> func <span style="color:#555555; ">=</span> Encoding<span style="color:#555555; ">.</span>Default<span style="color:#555555; ">.</span>GetBytes<span style="color:#555555; ">(</span>PluginMenu<span style="color:#555555; ">[</span>i <span style="color:#555555; ">*</span> <span style="color:#2e2e2e; ">2</span><span style="color:#555555; ">]</span><span style="color:#555555; ">)</span><span style="color:#555555; ">;</span>
<span style="color:#575757; font-weight:bold; ">byte</span><span style="color:#555555; ">[</span><span style="color:#555555; ">]</span> menu <span style="color:#555555; ">=</span> Encoding<span style="color:#555555; ">.</span>Default<span style="color:#555555; ">.</span>GetBytes<span style="color:#555555; ">(</span>PluginMenu<span style="color:#555555; ">[</span>i <span style="color:#555555; ">*</span> <span style="color:#2e2e2e; ">2</span> <span style="color:#555555; ">+</span> <span style="color:#2e2e2e; ">1</span><span style="color:#555555; ">]</span><span style="color:#555555; ">)</span><span style="color:#555555; ">;</span>
<span style="color:#575757; font-weight:bold; ">byte</span><span style="color:#555555; ">[</span><span style="color:#555555; ">]</span> zero <span style="color:#555555; ">=</span> <span style="color:#555555; ">{</span><span style="color:#2e2e2e; ">0</span><span style="color:#555555; ">}</span><span style="color:#555555; ">;</span>
Marshal<span style="color:#555555; ">.</span>Copy<span style="color:#555555; ">(</span>menu<span style="color:#555555; ">,</span> <span style="color:#2e2e2e; ">0</span><span style="color:#555555; ">,</span> Value<span style="color:#555555; ">,</span> menu<span style="color:#555555; ">.</span>Length<span style="color:#555555; ">)</span><span style="color:#555555; ">;</span>
Marshal<span style="color:#555555; ">.</span>Copy<span style="color:#555555; ">(</span>zero<span style="color:#555555; ">,</span> <span style="color:#2e2e2e; ">0</span><span style="color:#555555; ">,</span> Value <span style="color:#555555; ">+</span> menu<span style="color:#555555; ">.</span>Length<span style="color:#555555; ">,</span> zero<span style="color:#555555; ">.</span>Length<span style="color:#555555; ">)</span><span style="color:#555555; ">;</span>
Value <span style="color:#555555; ">+</span><span style="color:#555555; ">=</span> <span style="color:#2e2e2e; ">255</span><span style="color:#555555; ">;</span>
Marshal<span style="color:#555555; ">.</span>Copy<span style="color:#555555; ">(</span>func<span style="color:#555555; ">,</span> <span style="color:#2e2e2e; ">0</span><span style="color:#555555; ">,</span> Value<span style="color:#555555; ">,</span> func<span style="color:#555555; ">.</span>Length<span style="color:#555555; ">)</span><span style="color:#555555; ">;</span>
Marshal<span style="color:#555555; ">.</span>Copy<span style="color:#555555; ">(</span>zero<span style="color:#555555; ">,</span> <span style="color:#2e2e2e; ">0</span><span style="color:#555555; ">,</span> Value <span style="color:#555555; ">+</span> func<span style="color:#555555; ">.</span>Length<span style="color:#555555; ">,</span> zero<span style="color:#555555; ">.</span>Length<span style="color:#555555; ">)</span><span style="color:#555555; ">;</span>
Value <span style="color:#555555; ">+</span><span style="color:#555555; ">=</span> <span style="color:#2e2e2e; ">255</span><span style="color:#555555; ">;</span>
<span style="color:#555555; ">}</span>
<span style="color:#555555; ">}</span>
<span style="color:#575757; font-weight:bold; ">return</span> PluginMenu<span style="color:#555555; ">.</span>Length <span style="color:#555555; ">/</span> <span style="color:#2e2e2e; ">2</span><span style="color:#555555; ">;</span>
<span style="color:#555555; ">}</span>
</pre>
С функцией <code>PgiCheckMenuItemCom</code> все просто, нужно только не забыть
блок <code>try...catch</code>, иначе вместо сообщения об ошибке мы увидим
«External exception E0434352» и, возможно, утечет память, связанная
с объектом исключения.
<br>
<pre style="color:#000000;background:#ffffff;"><span style="color:#555555; ">[</span>DllExport<span style="color:#555555; ">(</span><span style="color:#2a2a2a; ">"</span><span style="color:#4c4c4c; ">PgiCheckMenuItemCom</span><span style="color:#2a2a2a; ">"</span><span style="color:#555555; ">,</span> CallingConvention<span style="color:#555555; ">.</span>StdCall<span style="color:#555555; ">)</span><span style="color:#555555; ">]</span>
<span style="color:#575757; font-weight:bold; ">public</span> <span style="color:#575757; font-weight:bold; ">static</span> Int32 PgiCheckMenuItemCom<span style="color:#555555; ">(</span>IntPtr Function<span style="color:#555555; ">,</span> IntPtr IPC<span style="color:#555555; ">)</span>
<span style="color:#555555; ">{</span>
<span style="color:#575757; font-weight:bold; ">try</span>
<span style="color:#555555; ">{</span>
<span style="color:#575757; font-weight:bold; ">string</span> func <span style="color:#555555; ">=</span> Marshal<span style="color:#555555; ">.</span>PtrToStringAnsi<span style="color:#555555; ">(</span>Function<span style="color:#555555; ">)</span><span style="color:#555555; ">;</span>
IPluginCall pc <span style="color:#555555; ">=</span> <span style="color:#555555; ">(</span>IPluginCall<span style="color:#555555; ">)</span>Marshal<span style="color:#555555; ">.</span>GetTypedObjectForIUnknown<span style="color:#555555; ">(</span>IPC<span style="color:#555555; ">,</span> <span style="color:#575757; font-weight:bold; ">typeof</span><span style="color:#555555; ">(</span>IPluginCall<span style="color:#555555; ">)</span><span style="color:#555555; ">)</span><span style="color:#555555; ">;</span>
...
<span style="color:#575757; font-weight:bold; ">return</span> <span style="color:#2e2e2e; ">1</span><span style="color:#555555; ">;</span>
<span style="color:#555555; ">}</span>
<span style="color:#575757; font-weight:bold; ">catch</span> <span style="color:#555555; ">(</span>Exception<span style="color:#555555; ">)</span>
<span style="color:#555555; ">{</span>
<span style="color:#575757; font-weight:bold; ">return</span> <span style="color:#2e2e2e; ">0</span><span style="color:#555555; ">;</span>
<span style="color:#555555; ">}</span>
<span style="color:#555555; ">}</span>
</pre>
<h3>Плагин и зависимости в отдельной папке</h3>
<br>
При установке плагина на рабочие места пользователей удобно,
когда DLL плагина, используемые ей сборки и другие нужные
файлы лежат в своей папке.
Но по умолчанию поиск зависимостей будет происходить в той папке,
где находится EXE-файл.
<br><br>
Для решения этой задачи можно добавить обработчик события
<code>AppDomain.AssemblyResolve</code> и в нем загрузить
требуемую сборку:
<br>
<pre style="color:#000000;background:#ffffff;"><span style="color:#575757; font-weight:bold; ">private</span> <span style="color:#575757; font-weight:bold; ">static</span> Assembly currentDomain_AssemblyResolve<span style="color:#555555; ">(</span><span style="color:#575757; font-weight:bold; ">object</span> sender<span style="color:#555555; ">,</span>
ResolveEventArgs args<span style="color:#555555; ">)</span>
<span style="color:#555555; ">{</span>
<span style="color:#575757; font-weight:bold; ">string</span> fileName <span style="color:#555555; ">=</span> Path<span style="color:#555555; ">.</span>Combine<span style="color:#555555; ">(</span>InstallPath<span style="color:#555555; ">,</span>
args<span style="color:#555555; ">.</span>Name<span style="color:#555555; ">.</span>Substring<span style="color:#555555; ">(</span><span style="color:#2e2e2e; ">0</span><span style="color:#555555; ">,</span> args<span style="color:#555555; ">.</span>Name<span style="color:#555555; ">.</span>IndexOf<span style="color:#555555; ">(</span><span style="color:#2a2a2a; ">"</span><span style="color:#4c4c4c; ">,</span><span style="color:#2a2a2a; ">"</span><span style="color:#555555; ">)</span><span style="color:#555555; ">)</span> <span style="color:#555555; ">+</span> <span style="color:#2a2a2a; ">"</span><span style="color:#4c4c4c; ">.dll</span><span style="color:#2a2a2a; ">"</span><span style="color:#555555; ">)</span><span style="color:#555555; ">;</span>
<span style="color:#575757; font-weight:bold; ">if</span> <span style="color:#555555; ">(</span>File<span style="color:#555555; ">.</span>Exists<span style="color:#555555; ">(</span>fileName<span style="color:#555555; ">)</span><span style="color:#555555; ">)</span>
<span style="color:#575757; font-weight:bold; ">return</span> Assembly<span style="color:#555555; ">.</span>LoadFile<span style="color:#555555; ">(</span>fileName<span style="color:#555555; ">)</span><span style="color:#555555; ">;</span>
<span style="color:#575757; font-weight:bold; ">return</span> <span style="color:#575757; font-weight:bold; ">null</span><span style="color:#555555; ">;</span>
<span style="color:#555555; ">}</span>
</pre>
<h3>Значки для пунктов меню</h3>
<br>
При необходимости, для пункта меню можно задать значок:
в DLL плагина добавить ресурс типа <code>RT_BITMAP</code>,
с именем как у экспортируемой функции.
<br><br>
Для этого понадобиться создать файл ресурсов <code>PluginResources.rc</code>
и скомпилировать его в файл <code>PluginResources.res</code> компилятором
ресурсов <code>rc.exe</code> из комплекта Platform SDK.
Ресурсы .NET (файлы *.resx) в данном случае не подойдут.
<br><br>
Затем в свойствах проекта необходимо указать компилятору использовать
созданный файл ресурсов <code>PluginResources.res</code>.
<br><br>
<div class="separator" style="clear: both; text-align: center;"><a href="https://4.bp.blogspot.com/-D2bPBNf8wcw/W9amEFiUM_I/AAAAAAAAAjg/10EfDi53XI8zm6E1M8gchTfz1owR-X-ywCLcBGAs/s1600/Props.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://4.bp.blogspot.com/-D2bPBNf8wcw/W9amEFiUM_I/AAAAAAAAAjg/10EfDi53XI8zm6E1M8gchTfz1owR-X-ywCLcBGAs/s400/Props.png" width="400" height="278" data-original-width="848" data-original-height="589" /></a></div>
<br>
<h3>Вызов функции из автооперации Workflow</h3>
<br>
Автооперации Workflow выполняются на клиенте при движении по бизнес-процессу.
Из автооперации можно вызвать как обычную функцию плагина, с помощью <code>CallLoodsmanPlugin</code>,
так и функцию, предназначенную специально для автоопераций, с помощью <code>ExecPluginFunction</code>
(в этом случае в функцию плагина можно передать дополнительные аргументы).
<br>
<br>
Для начала добавим в проект библиотеку типов бизнес-логики Workflow.
В проекте выбираем «References → Add Reference», далее «COM → Type Libraries». В списке находим «WorkflowBusinessLogic Library», и ставим напротив галочку.
<br>
<br>
Добавляем в код:
<br>
<pre style="color:#000000;background:#ffffff;"><span style="color:#575757; font-weight:bold; ">using</span> WorkflowBusinessLogic<span style="color:#555555; ">;</span> <span style="color:#696969; ">// IWFBusinessLogic</span>
</pre>
Затем нужно описать прототип функции, которую будет вызывать <code>ExecPluginFunction</code>.
К сожалению, просто использовать тип <code>object</code> вместо <code>Variant</code> у меня не получилось.
Пришлось отдельно описать тип <code>Variant</code> в C#:
<br>
<pre style="color:#000000;background:#ffffff;">namespace PluginSampleNet
<span style="color:#555555; ">{</span>
<span style="color:#555555; ">[</span>StructLayout<span style="color:#555555; ">(</span>LayoutKind<span style="color:#555555; ">.</span>Sequential<span style="color:#555555; ">)</span><span style="color:#555555; ">]</span>
public <span style="color:#575757; font-weight:bold; ">struct</span> Variant
<span style="color:#555555; ">{</span>
public ushort vt<span style="color:#555555; ">;</span>
public ushort wReserved1<span style="color:#555555; ">;</span>
public ushort wReserved2<span style="color:#555555; ">;</span>
public ushort wReserved3<span style="color:#555555; ">;</span>
public IntPtr data1<span style="color:#555555; ">;</span>
public IntPtr data2<span style="color:#555555; ">;</span>
public Variant<span style="color:#555555; ">(</span>VarEnum type<span style="color:#555555; ">)</span>
<span style="color:#555555; ">{</span>
vt <span style="color:#555555; ">=</span> <span style="color:#555555; ">(</span>ushort<span style="color:#555555; ">)</span>type<span style="color:#555555; ">;</span>
wReserved1 <span style="color:#555555; ">=</span> <span style="color:#2e2e2e; ">0</span><span style="color:#555555; ">;</span>
wReserved2 <span style="color:#555555; ">=</span> <span style="color:#2e2e2e; ">0</span><span style="color:#555555; ">;</span>
wReserved3 <span style="color:#555555; ">=</span> <span style="color:#2e2e2e; ">0</span><span style="color:#555555; ">;</span>
data1 <span style="color:#555555; ">=</span> <span style="color:#555555; ">(</span>IntPtr<span style="color:#555555; ">)</span><span style="color:#2e2e2e; ">0</span><span style="color:#555555; ">;</span>
data2 <span style="color:#555555; ">=</span> <span style="color:#555555; ">(</span>IntPtr<span style="color:#555555; ">)</span><span style="color:#2e2e2e; ">0</span><span style="color:#555555; ">;</span>
<span style="color:#555555; ">}</span>
public Variant<span style="color:#555555; ">(</span>bool value<span style="color:#555555; ">)</span>
<span style="color:#555555; ">{</span>
vt <span style="color:#555555; ">=</span> <span style="color:#555555; ">(</span>ushort<span style="color:#555555; ">)</span>VarEnum<span style="color:#555555; ">.</span>VT_BOOL<span style="color:#555555; ">;</span>
wReserved1 <span style="color:#555555; ">=</span> <span style="color:#2e2e2e; ">0</span><span style="color:#555555; ">;</span>
wReserved2 <span style="color:#555555; ">=</span> <span style="color:#2e2e2e; ">0</span><span style="color:#555555; ">;</span>
wReserved3 <span style="color:#555555; ">=</span> <span style="color:#2e2e2e; ">0</span><span style="color:#555555; ">;</span>
data1 <span style="color:#555555; ">=</span> <span style="color:#555555; ">(</span>IntPtr<span style="color:#555555; ">)</span>Convert<span style="color:#555555; ">.</span>ToInt32<span style="color:#555555; ">(</span>value<span style="color:#555555; ">)</span><span style="color:#555555; ">;</span>
data2 <span style="color:#555555; ">=</span> <span style="color:#555555; ">(</span>IntPtr<span style="color:#555555; ">)</span><span style="color:#2e2e2e; ">0</span><span style="color:#555555; ">;</span>
<span style="color:#555555; ">}</span>
public Variant<span style="color:#555555; ">(</span><span style="color:#575757; font-weight:bold; ">int</span> value<span style="color:#555555; ">)</span>
<span style="color:#555555; ">{</span>
vt <span style="color:#555555; ">=</span> <span style="color:#555555; ">(</span>ushort<span style="color:#555555; ">)</span>VarEnum<span style="color:#555555; ">.</span>VT_I4<span style="color:#555555; ">;</span>
wReserved1 <span style="color:#555555; ">=</span> <span style="color:#2e2e2e; ">0</span><span style="color:#555555; ">;</span>
wReserved2 <span style="color:#555555; ">=</span> <span style="color:#2e2e2e; ">0</span><span style="color:#555555; ">;</span>
wReserved3 <span style="color:#555555; ">=</span> <span style="color:#2e2e2e; ">0</span><span style="color:#555555; ">;</span>
data1 <span style="color:#555555; ">=</span> <span style="color:#555555; ">(</span>IntPtr<span style="color:#555555; ">)</span>value<span style="color:#555555; ">;</span>
data2 <span style="color:#555555; ">=</span> <span style="color:#555555; ">(</span>IntPtr<span style="color:#555555; ">)</span><span style="color:#2e2e2e; ">0</span><span style="color:#555555; ">;</span>
<span style="color:#555555; ">}</span>
public Variant<span style="color:#555555; ">(</span>string value<span style="color:#555555; ">)</span>
<span style="color:#555555; ">{</span>
vt <span style="color:#555555; ">=</span> <span style="color:#555555; ">(</span>ushort<span style="color:#555555; ">)</span>VarEnum<span style="color:#555555; ">.</span>VT_BSTR<span style="color:#555555; ">;</span>
wReserved1 <span style="color:#555555; ">=</span> <span style="color:#2e2e2e; ">0</span><span style="color:#555555; ">;</span>
wReserved2 <span style="color:#555555; ">=</span> <span style="color:#2e2e2e; ">0</span><span style="color:#555555; ">;</span>
wReserved3 <span style="color:#555555; ">=</span> <span style="color:#2e2e2e; ">0</span><span style="color:#555555; ">;</span>
data1 <span style="color:#555555; ">=</span> <span style="color:#555555; ">(</span>IntPtr<span style="color:#555555; ">)</span>UnsafeNativeMethods<span style="color:#555555; ">.</span>SysAllocString<span style="color:#555555; ">(</span>value<span style="color:#555555; ">)</span><span style="color:#555555; ">;</span>
data2 <span style="color:#555555; ">=</span> <span style="color:#555555; ">(</span>IntPtr<span style="color:#555555; ">)</span><span style="color:#2e2e2e; ">0</span><span style="color:#555555; ">;</span>
<span style="color:#555555; ">}</span>
<span style="color:#555555; ">}</span>
<span style="color:#555555; ">}</span>
</pre>
Описание структуры <code>TPDMVersionData</code>:
<br>
<pre style="color:#000000;background:#ffffff;">namespace PluginSampleNet
<span style="color:#555555; ">{</span>
<span style="color:#555555; ">[</span>StructLayout<span style="color:#555555; ">(</span>LayoutKind<span style="color:#555555; ">.</span>Sequential<span style="color:#555555; ">,</span> CharSet <span style="color:#555555; ">=</span> CharSet<span style="color:#555555; ">.</span>Ansi<span style="color:#555555; ">)</span><span style="color:#555555; ">]</span>
public <span style="color:#575757; font-weight:bold; ">struct</span> PDMVersionData
<span style="color:#555555; ">{</span>
<span style="color:#555555; ">[</span>MarshalAs<span style="color:#555555; ">(</span>UnmanagedType<span style="color:#555555; ">.</span>I4<span style="color:#555555; ">)</span><span style="color:#555555; ">]</span>
public <span style="color:#575757; font-weight:bold; ">int</span> routeId<span style="color:#555555; ">;</span>
<span style="color:#555555; ">[</span>MarshalAs<span style="color:#555555; ">(</span>UnmanagedType<span style="color:#555555; ">.</span>ByValTStr<span style="color:#555555; ">,</span> SizeConst <span style="color:#555555; ">=</span> <span style="color:#2e2e2e; ">255</span><span style="color:#555555; ">)</span><span style="color:#555555; ">]</span>
public string appServer<span style="color:#555555; ">;</span>
<span style="color:#555555; ">[</span>MarshalAs<span style="color:#555555; ">(</span>UnmanagedType<span style="color:#555555; ">.</span>ByValTStr<span style="color:#555555; ">,</span> SizeConst <span style="color:#555555; ">=</span> <span style="color:#2e2e2e; ">255</span><span style="color:#555555; ">)</span><span style="color:#555555; ">]</span>
public string db<span style="color:#555555; ">;</span>
<span style="color:#555555; ">[</span>MarshalAs<span style="color:#555555; ">(</span>UnmanagedType<span style="color:#555555; ">.</span>I4<span style="color:#555555; ">)</span><span style="color:#555555; ">]</span>
public <span style="color:#575757; font-weight:bold; ">int</span> id<span style="color:#555555; ">;</span>
<span style="color:#555555; ">[</span>MarshalAs<span style="color:#555555; ">(</span>UnmanagedType<span style="color:#555555; ">.</span>ByValTStr<span style="color:#555555; ">,</span> SizeConst <span style="color:#555555; ">=</span> <span style="color:#2e2e2e; ">255</span><span style="color:#555555; ">)</span><span style="color:#555555; ">]</span>
public string product<span style="color:#555555; ">;</span>
<span style="color:#555555; ">[</span>MarshalAs<span style="color:#555555; ">(</span>UnmanagedType<span style="color:#555555; ">.</span>ByValTStr<span style="color:#555555; ">,</span> SizeConst <span style="color:#555555; ">=</span> <span style="color:#2e2e2e; ">255</span><span style="color:#555555; ">)</span><span style="color:#555555; ">]</span>
public string type<span style="color:#555555; ">;</span>
<span style="color:#555555; ">[</span>MarshalAs<span style="color:#555555; ">(</span>UnmanagedType<span style="color:#555555; ">.</span>ByValTStr<span style="color:#555555; ">,</span> SizeConst <span style="color:#555555; ">=</span> <span style="color:#2e2e2e; ">255</span><span style="color:#555555; ">)</span><span style="color:#555555; ">]</span>
public string version<span style="color:#555555; ">;</span>
<span style="color:#555555; ">[</span>MarshalAs<span style="color:#555555; ">(</span>UnmanagedType<span style="color:#555555; ">.</span>ByValTStr<span style="color:#555555; ">,</span> SizeConst <span style="color:#555555; ">=</span> <span style="color:#2e2e2e; ">255</span><span style="color:#555555; ">)</span><span style="color:#555555; ">]</span>
public string state<span style="color:#555555; ">;</span>
<span style="color:#555555; ">[</span>MarshalAs<span style="color:#555555; ">(</span>UnmanagedType<span style="color:#555555; ">.</span>I1<span style="color:#555555; ">)</span><span style="color:#555555; ">]</span>
public byte accessLevel<span style="color:#555555; ">;</span>
<span style="color:#555555; ">[</span>MarshalAs<span style="color:#555555; ">(</span>UnmanagedType<span style="color:#555555; ">.</span>I1<span style="color:#555555; ">)</span><span style="color:#555555; ">]</span>
public byte lockLevel<span style="color:#555555; ">;</span>
<span style="color:#555555; ">[</span>MarshalAs<span style="color:#555555; ">(</span>UnmanagedType<span style="color:#555555; ">.</span>I1<span style="color:#555555; ">)</span><span style="color:#555555; ">]</span>
public byte document<span style="color:#555555; ">;</span>
<span style="color:#555555; ">[</span>MarshalAs<span style="color:#555555; ">(</span>UnmanagedType<span style="color:#555555; ">.</span>I1<span style="color:#555555; ">)</span><span style="color:#555555; ">]</span>
public byte revision<span style="color:#555555; ">;</span>
<span style="color:#555555; ">}</span>
<span style="color:#555555; ">}</span>
</pre>
И прототип функции:
<pre style="color:#000000;background:#ffffff;">
namespace PluginSampleNet
<span style="color:#555555; ">{</span>
public class PluginFunctions
<span style="color:#555555; ">{</span>
public <span style="color:#575757; font-weight:bold; ">static</span> bool WFProjectList<span style="color:#555555; ">(</span>IWFBusinessLogic wf<span style="color:#555555; ">,</span>
PDMVersionData versionData<span style="color:#555555; ">,</span> object<span style="color:#555555; ">[</span><span style="color:#555555; ">]</span> userData<span style="color:#555555; ">)</span>
<span style="color:#555555; ">{</span>
<span style="color:#575757; font-weight:bold; ">return</span> true<span style="color:#555555; ">;</span>
<span style="color:#555555; ">}</span>
<span style="color:#555555; ">[</span>DllExport<span style="color:#555555; ">(</span><span style="color:#2a2a2a; ">"</span><span style="color:#4c4c4c; ">WFProjectList</span><span style="color:#2a2a2a; ">"</span><span style="color:#555555; ">,</span> CallingConvention<span style="color:#555555; ">.</span>StdCall<span style="color:#555555; ">)</span><span style="color:#555555; ">]</span>
public <span style="color:#575757; font-weight:bold; ">static</span> Variant _wfProjectList<span style="color:#555555; ">(</span>Variant wfo<span style="color:#555555; ">,</span>
IntPtr versionData<span style="color:#555555; ">,</span> object<span style="color:#555555; ">[</span><span style="color:#555555; ">]</span> userData<span style="color:#555555; ">)</span>
<span style="color:#555555; ">{</span>
<span style="color:#575757; font-weight:bold; ">if</span> <span style="color:#555555; ">(</span><span style="color:#555555; ">!</span>_initialized<span style="color:#555555; ">)</span>
<span style="color:#555555; ">{</span>
Initialize<span style="color:#555555; ">(</span><span style="color:#555555; ">)</span><span style="color:#555555; ">;</span>
_initialized <span style="color:#555555; ">=</span> true<span style="color:#555555; ">;</span>
<span style="color:#555555; ">}</span>
try
<span style="color:#555555; ">{</span>
<span style="color:#575757; font-weight:bold; ">if</span> <span style="color:#555555; ">(</span>wfo<span style="color:#555555; ">.</span>vt <span style="color:#555555; ">!</span><span style="color:#555555; ">=</span> <span style="color:#555555; ">(</span>ushort<span style="color:#555555; ">)</span>VarEnum<span style="color:#555555; ">.</span>VT_DISPATCH<span style="color:#555555; ">)</span>
<span style="color:#555555; ">{</span>
throw new ArgumentException<span style="color:#555555; ">(</span><span style="color:#555555; ">)</span><span style="color:#555555; ">;</span>
<span style="color:#555555; ">}</span>
IWFBusinessLogic wf <span style="color:#555555; ">=</span>
<span style="color:#555555; ">(</span>IWFBusinessLogic<span style="color:#555555; ">)</span>Marshal<span style="color:#555555; ">.</span>GetTypedObjectForIUnknown<span style="color:#555555; ">(</span>wfo<span style="color:#555555; ">.</span>data1<span style="color:#555555; ">,</span>
typeof<span style="color:#555555; ">(</span>IWFBusinessLogic<span style="color:#555555; ">)</span><span style="color:#555555; ">)</span><span style="color:#555555; ">;</span>
PDMVersionData data <span style="color:#555555; ">=</span>
<span style="color:#555555; ">(</span>PDMVersionData<span style="color:#555555; ">)</span>Marshal<span style="color:#555555; ">.</span>PtrToStructure<span style="color:#555555; ">(</span>versionData<span style="color:#555555; ">,</span>
typeof<span style="color:#555555; ">(</span>PDMVersionData<span style="color:#555555; ">)</span><span style="color:#555555; ">)</span><span style="color:#555555; ">;</span>
bool res <span style="color:#555555; ">=</span> WFProjectList<span style="color:#555555; ">(</span>wf<span style="color:#555555; ">,</span> data<span style="color:#555555; ">,</span> userData<span style="color:#555555; ">)</span><span style="color:#555555; ">;</span>
<span style="color:#575757; font-weight:bold; ">return</span> new Variant<span style="color:#555555; ">(</span>res<span style="color:#555555; ">)</span><span style="color:#555555; ">;</span>
<span style="color:#555555; ">}</span>
catch <span style="color:#555555; ">(</span>Exception ex<span style="color:#555555; ">)</span>
<span style="color:#555555; ">{</span>
<span style="color:#575757; font-weight:bold; ">MessageBox</span><span style="color:#555555; ">.</span>Show<span style="color:#555555; ">(</span>ex<span style="color:#555555; ">.</span>ToString<span style="color:#555555; ">(</span><span style="color:#555555; ">)</span><span style="color:#555555; ">,</span> PluginCaption<span style="color:#555555; ">,</span>
MessageBoxButtons<span style="color:#555555; ">.</span>OK<span style="color:#555555; ">,</span> MessageBoxIcon<span style="color:#555555; ">.</span>Error<span style="color:#555555; ">)</span><span style="color:#555555; ">;</span>
<span style="color:#575757; font-weight:bold; ">return</span> new Variant<span style="color:#555555; ">(</span>VarEnum<span style="color:#555555; ">.</span>VT_NULL<span style="color:#555555; ">)</span><span style="color:#555555; ">;</span>
<span style="color:#555555; ">}</span>
<span style="color:#555555; ">}</span>
<span style="color:#555555; ">}</span>
<span style="color:#555555; ">}</span>
</pre>
<br>
<h3>Исходный код</h3>
<br>
Исходный код примера плагина доступен на GitHub —
<a href="https://github.com/achechulin/loodsman/tree/master/Plugins/PluginSampleNet" target="_blank">github.com/achechulin/loodsman/Plugins/PluginSampleNet</a>.
<br><br>
Текущую версию кода можно скачать без клиента Git или SVN, нажав кнопку
«<a href="https://github.com/achechulin/loodsman/archive/master.zip">Download ZIP</a>»
в правой части страницы репозитория.
<br><br>
Chaahttp://www.blogger.com/profile/14387721107858333063noreply@blogger.com7tag:blogger.com,1999:blog-4946864412455003694.post-38882660465017794052018-07-04T08:00:00.000+05:002018-07-04T15:45:28.326+05:00mORMot и LinuxВ настоящее время активно идет улучшение поддержки Linux в
<a href="https://synopse.info/" target="_blank">mORMot</a>.
В основном, конечно, для серверных приложений.
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://2.bp.blogspot.com/-6C8m4tkfZgs/WyJLRZ9tnhI/AAAAAAAAAiw/cbZJAcSk2ecV2fQoUaJ57jNAmW5qJW2YACLcBGAs/s1600/mormot-fpc1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="864" data-original-width="1152" height="300" src="https://2.bp.blogspot.com/-6C8m4tkfZgs/WyJLRZ9tnhI/AAAAAAAAAiw/cbZJAcSk2ecV2fQoUaJ57jNAmW5qJW2YACLcBGAs/s400/mormot-fpc1.png" width="400" /></a></div>
<br />
Несмотря на то, что в Delphi с версии 10.2 есть компилятор для Linux,
в качестве основного компилятора под Linux для mORMot был выбран
<a href="https://freepascal.org/" target="_blank">FreePascal</a>.
Главная причина в том, что он открытый и бесплатный.
И при этом FreePascal в части генерации кода лучше компилятора Delphi для Linux.
<br />
<br />
<a name='more'></a>Основные минусы компилятора Delphi для Linux:
<br />
<ul>
<li>доступен только в редакции Enterprise, что очень дорого;
</li>
<li>ARC (Automatic Reference Counting), что требует существенной доработки исходного кода.
</li>
</ul>
<br />
Кроме того, использование открытого и бесплатного компилятора позволяет ограничится поддержкой
только одной (последней) версии компилятора.
<br />
<br />
Итак, после многих лет, сообщество mORMot пришло к полностью открытой модели разработки:
необходимые платформы и инструменты доступны в Open Source.
Наличие Delphi все еще очень удобно для многих применений, но не обязательно.
<br />
<br />
<h3>Single Sign-On</h3>
<br />
Не могу не поделиться своим небольшим вкладом в улучшение поддержки Linux.
Очень давно, в 2012 году, для mORMot я написал код прозрачной аутентификации пользователя (Single Sign-On)
в домене Active Directory с помощью Windows SSPI.
Поэтому и сейчас добавил реализацию этой функциональности под Linux
для сервера и клиента (а также браузера в качестве клиента).
<br />
<br />
В Linux для аутентификации в домене Active Directory используется Kerberos и интерфейс
<a href="https://tools.ietf.org/html/rfc2743.html" target="_blank">GSSAPI</a>.
mORMot поддерживает реализации Kerberos как
<a href="http://web.mit.edu/kerberos/krb5-latest/doc/appdev/gssapi.html" target="_blank">MIT</a>,
так и <a href="https://www.h5l.org/" target="_blank">Heimdal</a>.
<br />
<br />
Машина с Linux должна быть включена в домен.
В качестве руководства рекомендую статью
<a href="https://www.mssqltips.com/sqlservertip/5075/configure-sql-server-on-linux-to-use-windows-authentication/"
target="_blank">Configure SQL Server on Linux to Use Windows Authentication</a>.
<br />
<br />
<h3>Знакомство</h3>
<br />
Для знакомства с новыми возможностями mORMot вам
потребуется FPC версии 3.1 (на данный момент development release).
В старых версиях FPC 3.0 и 2.6 нет достаточной поддержки RTTI.
<br />
<br />
Начать проще всего с
<a href="https://github.com/newpascal/fpcupdeluxe/releases" target="_blank">fpcupdeluxe</a>.
Скачайте последний релиз для вашей платформы и запустите его.
В Windows все должно заработать сразу, в Ubuntu 18.04 пришлось сначала
установить необходимые пакеты:
<pre>
sudo apt-get install build-essential mingw32-binutils libgtk2.0-dev subversion git
</pre>
Запустив fpcupdeluxe вы можете выбрать версии компилятора и среды разработки,
приложение скачает и соберет все необходимое из svn/git.
<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://2.bp.blogspot.com/-aM6WSVyzQVY/WyJRJNIcNoI/AAAAAAAAAi8/45sVMLmA2xUME2MyKOWkbJaZKT7WwfiHQCLcBGAs/s1600/mormot-fpc2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://2.bp.blogspot.com/-aM6WSVyzQVY/WyJRJNIcNoI/AAAAAAAAAi8/45sVMLmA2xUME2MyKOWkbJaZKT7WwfiHQCLcBGAs/s400/mormot-fpc2.png" width="400" height="300" data-original-width="1152" data-original-height="864" /></a></div>
<br />
Для установки подходящих FreePascal и Lazarus в ряду нижних кнопок нажмите
«Trunk» или «NewPascal»
(рекомендую <a href="http://newpascal.org/" target="_blank">NewPascal</a>).
<br />
Там же нужно нажать «mORMot» для получения последней версии с
<a href="https://github.com/synopse/mORMot" target="_blank">GitHub</a>.
<br />
<br />
После завершения сборки на рабочем столе должен появиться ярлык «Lazarus_fpcupdeluxe».
Уже можно запустить Lazarus.
В меню выбрать «File → Open...»,
перейти в папку <code>~fpcupdeluxe/ccr/mORMot/SQLite3/Samples</code>,
выбрать из примеров подходящий проект, открыть и запустить его.
<br />
<br />
Также рекомендую настроить современный вид IDE по инструкции
<a href="http://newpascal.org/docked.html" target="_blank">Lazarus Docked Desktops</a>.
<br />
<br />
Немного из личного опыта (Ubuntu 18.04 и Lazarus 1.9.0).
Работает стабильно, в общем и целом не сильно хуже Delphi XE5,
а 64-х битный отладчик даже лучше.
<br />
<br />
Поначалу непривычен синтаксис ассемблера, но разобраться можно.
<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://sites.google.com/site/achechulin/files/fpc_error1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://sites.google.com/site/achechulin/files/fpc_error1.png" width="400" height="300" data-original-width="800" data-original-height="600" /></a></div>
<br />
Chaahttp://www.blogger.com/profile/14387721107858333063noreply@blogger.com0tag:blogger.com,1999:blog-4946864412455003694.post-70716863476072222422017-09-25T09:00:00.000+05:002017-09-28T13:45:26.597+05:00Подключаемые модули в версии 2017Версия «ЛОЦМАН:PLM 2017» принесла новые изменения в работу подключаемых модулей.
<br />
<br />
Теперь есть три варианта клиентских лицензий: Базовая, Стандартная и Максимальная.
Тип лицензии определяет набор функций, которые будут доступны в клиентском приложении.
В том числе использование подключаемых модулей:
<br />
<ul>
<li>Для базовой лицензии использование подключаемых модулей недоступно.</li>
<li>Для стандартной лицензии необходимо приобретение дополнительной опции «Использование расширений».</li>
<li>Для максимальной лицензии нет ограничений.</li>
</ul>
<br /><a name='more'></a>
Ограничения не распространяются на подключаемые модули из поставки ЛОЦМАН:PLM
(Извещения, Архив, Технология, Обмен данными и другие).
<br />
<br />
<h3>Доверенные расширения</h3>
<br />
Клиент отличает обычные и «доверенные»
(подключаемые модули из комплекта поставки)
расширения с помощью вызова функции
<code>lcpi_authenticate_vendor_module</code>, экспортируемой подключаемым модулем.
Функция вызывается при загрузке модуля и в нее передается зашифрованное
полное имя файла загружаемого модуля. Для шифрования используется блочный шифр
<a href="http://www.delphisources.ru/pages/faq/base/rc5_encryption.html" target="_blank">RC5</a>.
<br />
<br />
Подключаемый модуль сначала расшифровывает имя файла (<code>Request</code>) и,
если оно совпадает с его именем,
еще раз шифрует уже зашифрованное имя (<code>Request</code>)
и возвращает в клиент (<code>Response</code>).
Клиент дешифрует значение, которое вернул модуль, и сравнивает с переданным.
Если они совпадают, расширение считается «доверенным».
<br />
<pre style="background: #ffffff; color: black;"><span style="color: #575757; font-weight: bold;">procedure</span> lcpi_authenticate_vendor_module<span style="color: #555555;">(</span><span style="color: #575757; font-weight: bold;">var</span> Response<span style="color: #555555;">:</span> <span style="color: #575757; font-weight: bold;">WideString</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">const</span> Request<span style="color: #555555;">:</span> <span style="color: #575757; font-weight: bold;">WideString</span><span style="color: #555555;">)</span><span style="color: #555555;">;</span> <span style="color: #575757; font-weight: bold;">stdcall</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">begin</span>
Response <span style="color: #555555;">:</span><span style="color: #555555;">=</span> RC5<span style="color: #555555;">(</span>Request<span style="color: #555555;">,</span> Key<span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
</pre>
<h3>Размещение пунктов меню</h3>
<br />
С версии 2013 изменилось размещение пунктов меню.
В <a href="http://achechulin.blogspot.ru/2014/04/plugin-v14.html">прошлый раз</a>
новое поведение меню было описано неполно.
<br />
<br />
Итак, <code>stMenu</code> — это названия пунктов меню, разделенные символом <code>'#'</code>.
Название первого пункта меню указывает, как меню подключаемого модуля будет расположено относительно собственного меню клиента. Возможные значения перечислены ниже:
<br />
<br />
<table border="1" cellpadding="4" cellspacing="0"><tbody>
<tr><th><b>Название первого пункта</b></th> <th>Меню клиента</th></tr>
<tr><td>MI_EDIT</td> <td>Правка</td></tr>
<tr><td>MI_OBJECTS</td> <td>Объекты</td> </tr>
<tr><td>MI_TOOLS</td> <td>Инструменты</td></tr>
<tr><td>MI_HELP</td> <td>Справка</td></tr>
<tr><td>BEFORE_*, AFTER_* (любой текст вместо звездочки)</td> <td>Дополнения</td></tr>
<tr><td>Любое другое название (например, «Мой плагин»)</td> <td>Мой плагин</td></tr>
</tbody></table>
<br />
Пример размещение пункта меню модуля в меню «Дополнения» клиента:
название пунктов меню
<code>'BEFORE_MI_TOOLS#Мои плагины#Тестовый#Состав'</code>.
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://3.bp.blogspot.com/-iUIQ3PDCCGo/WcoQsYz_43I/AAAAAAAAAhA/3ulUeCf-yEc7CmrnNmmUxqs3nX6yGeHZQCLcBGAs/s1600/PluginMenu1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="151" data-original-width="930" height="102" src="https://3.bp.blogspot.com/-iUIQ3PDCCGo/WcoQsYz_43I/AAAAAAAAAhA/3ulUeCf-yEc7CmrnNmmUxqs3nX6yGeHZQCLcBGAs/s640/PluginMenu1.png" width="640" /></a></div>
<br />
Пример размещение пункта меню модуля в меню «Инструменты» клиента:
название пунктов меню
<code>'MI_TOOLS#Мои плагины#Тестовый#Состав'</code>.
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://3.bp.blogspot.com/--UW2TcJ7P98/WcoS_1RjY-I/AAAAAAAAAhM/liOhSPX1DzMyNPwjvRkFkZl3ib4M3oInwCLcBGAs/s1600/PluginMenu2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="289" data-original-width="777" height="238" src="https://3.bp.blogspot.com/--UW2TcJ7P98/WcoS_1RjY-I/AAAAAAAAAhM/liOhSPX1DzMyNPwjvRkFkZl3ib4M3oInwCLcBGAs/s640/PluginMenu2.png" width="640" /></a></div>
<br />
Пример размещение пункта меню модуля в главном меню клиента:
название пунктов меню
<code>'Мои плагины#Тестовый#Состав'</code>.
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-pyXl68f62jQ/WcoUpCyVMcI/AAAAAAAAAhY/YnVQyxw0yroRk4LCDzngBVuZaEtDiCRLwCLcBGAs/s1600/PluginMenu3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="140" data-original-width="864" height="104" src="https://1.bp.blogspot.com/-pyXl68f62jQ/WcoUpCyVMcI/AAAAAAAAAhY/YnVQyxw0yroRk4LCDzngBVuZaEtDiCRLwCLcBGAs/s640/PluginMenu3.png" width="640" /></a></div>
<br />
<h3>Изменения в API</h3>
<br />
Описание интерфейсов нового
<a href="https://github.com/achechulin/loodsman/tree/master/Plugins/PluginSample2/APIv17" target="_blank">APIv17</a>
доступно на GitHub.
<br />
<br />
Все функции из версии 14 остались на своих местах, проблем с переходом на версию 17 замечено не было.
<br />
<br />
Из нового стоит отметить интерфейсы <code>ILoodsmanClientUtils</code> и <code>IPDMEntityManager</code>. Но, к сожалению, документации по ним в комплекте поставки нет.
<br />
<br />
Chaahttp://www.blogger.com/profile/14387721107858333063noreply@blogger.com1tag:blogger.com,1999:blog-4946864412455003694.post-54470778332756212682015-10-30T08:00:00.000+05:002015-10-30T16:09:51.615+05:00Новая версия LWF 1.1.0.850Доступна для загрузки новая версия <a href="/p/lwf.html">LWF</a>.
<br/><br/>
<a href="http://uselwf.ru/files/LWF_1.1.0.850.zip">LWF_1.1.0.850.zip</a>
<br/><br/>
Два самых заметных изменения в новой версии:
переработаны просмотр и аннотирование вторичных представлений
и добавлена возможность создавать письма в бизнес-процессе.
<br/><br/>
Подробнее об изменениях ниже.
<br/><br/>
<a name='more'></a>
<h3>Просмотр и аннотирование вторичных представлений</h3>
<br/>
В новой версии была добавлена поддержка вторичного представления в формате PDF.
Поддерживается работа с Adobe Reader X и Adobe Reader DC.
<br/><br/>
Сперва аннотирование было сделано примерно как в Комплексе решений 2014.
Но по результатам опытной эксплуатации выяснилось, что работать со вторичным представлением
и аннотированием в таком виде неудобно.
<br/><br/>
Часто требуется видеть несколько документов одновременно,
или хотя бы иметь возможность быстро переключаться между ними.
<br/><br/>
<div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-jl1WVkLaQcY/VjMAiLzj5CI/AAAAAAAAAfE/TNOxPRZGKsU/s1600/SecView3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-jl1WVkLaQcY/VjMAiLzj5CI/AAAAAAAAAfE/TNOxPRZGKsU/s320/SecView3.png" /></a></div>
<br/>
Также нужна возможность прямо во время работы с аннотированием
писать ответ на задание или письмо по бизнес-процессу.
<br/><br/>
<div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-bL6NTNYnG1g/VjMAiOjk2UI/AAAAAAAAAfA/zeX_ZQUKVX8/s1600/SecView4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-bL6NTNYnG1g/VjMAiOjk2UI/AAAAAAAAAfA/zeX_ZQUKVX8/s320/SecView4.png" /></a></div>
<br/>
Для реализации этих требований пришлось изменить работу со вторичными представлениями.
Теперь в комплекте LWF появился еще один файл — <code>LUHost.exe</code>.
Это отдельный процесс для работы со вторичными представлениями.
<br/><br/>
Решение компромиссное — лучше было бы создавать отдельные потоки для показа
окон вторичного представления, но библиотека VCL из Delphi не очень хорошо
относится к многопоточности графического интерфейса.
Вместо этого окна вторичного представления показываются в отдельном процессе.
У такого решения есть недостаток: у сервера приложений забирается лицензия,
но ненадолго — всего на одну минуту.
<br/><br/>
По умолчанию вторичные представления работают по-старому. Чтобы они показывались
в отдельном процессе, нужно добавить в файл конфигурации параметр <code>svhost="1"</code>.
Например, так:
<br/>
<pre style='color:#000000;background:#ffffff;'><span style='color:#545454; '><</span><span style='color:#2a2a2a; '>configuration</span><span style='color:#545454; '>></span>
<span style='color:#545454; '><</span><span style='color:#2a2a2a; '>mainform</span> svhost<span style='color:#555555; '>=</span><span style='color:#2a2a2a; '>"</span><span style='color:#4c4c4c; '>1</span><span style='color:#2a2a2a; '>"</span><span style='color:#545454; '>></span>
<span style='color:#545454; '></</span><span style='color:#2a2a2a; '>mainform</span><span style='color:#545454; '>></span>
<span style='color:#545454; '></</span><span style='color:#2a2a2a; '>configuration</span><span style='color:#545454; '>></span>
</pre>
Также была добавлена поддержка SolidWorks eDrawings 2013. Теперь поддерживаются версии
eDrawings 2007, 2010, 2012 и 2013.
<br/>
Поддерживаются и модули просмотра документов на базе <code>IDocumentViewer</code>,
такие как <code>PDFViewer.ocx</code>, <code>LoodsmanEDrawings.ocx</code> и <code>VrpIntegrator.ocx</code>
(подробнее см. <code>LoodsmanClientApi.chm</code>).
<br/><br/>
<h3>Письма в бизнес-процессе</h3>
<br/>
Появилась возможность создавать письма в бизнес-процессе.
В окно «Переписка» добавлены команды «Новое письмо»,
«Ответить» и «Удалить».
И еще в окно «Переписка» добавлен показ получателей письма
(это полезно, когда письмо адресовано нескольким получателям).
<br/><br/>
<div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-yuZnbpYXRg8/VjM7qfpT09I/AAAAAAAAAfY/VIvPnCwgNDQ/s1600/wfmail1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-yuZnbpYXRg8/VjM7qfpT09I/AAAAAAAAAfY/VIvPnCwgNDQ/s200/wfmail1.png" /></a><a href="http://2.bp.blogspot.com/-nD-o_V7t4uY/VjM7qaG-EuI/AAAAAAAAAfc/DGNs917jpVs/s1600/wfmail2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-nD-o_V7t4uY/VjM7qaG-EuI/AAAAAAAAAfc/DGNs917jpVs/s200/wfmail2.png" /></a></div>
<br/>
<h3>Параметры вызова интегратора</h3>
<br/>
В файл конфигурации добавлены параметры вызова интегратора при выполнении
команды «Получить информацию».
<br/><br/>
Параметры определяют, каким образом информация, полученная из файла,
должна повлиять на данные в ЛОЦМАН:PLM.
По умолчанию данные изменяются в соответствии с данными из файла.
<br/><br/>
Атрибут <code>syncflags</code> может быть комбинирован из следующих битовых флагов:
<br/><br/>
<table border="1" cellpadding="4" cellspacing="0">
<thead>
<tr>
<th>Значение</th>
<th>Описание</th>
</tr>
</thead>
<tbody>
<tr><td align="center"> 0</td><td>По умолчанию. Данные в ЛОЦМАН:PLM изменяются в соответствии с данными из файла.</td></tr>
<tr><td align="center"> 1</td><td>Не удалять существующие атрибуты. Если в данных из файла отсутствуют атрибуты, которые присутствуют в ЛОЦМАН:PLM, то эти атрибуты из ЛОЦМАН:PLM не удаляются.</td></tr>
<tr><td align="center"> 2</td><td>Не удалять существующие связи. Если в данных из файла отсутствуют связи, которые присутствуют в ЛОЦМАН:PLM, то эти связи из ЛОЦМАН:PLM не удаляются.</td></tr>
<tr><td align="center"> 4</td><td>Зарезервировано. Не используется.</td></tr>
<tr><td align="center"> 8</td><td>Формировать вторичное представление на головной документ.</td></tr>
<tr><td align="center">16</td><td>Формировать вторичное представление на все возвращаемые документы.</td></tr>
</tbody>
</table>
<br/>
Таким образом, если задать значение <code>syncflags="3"</code>, то есть 1 + 2, то это будет означать
выполнение обоих условий «Не удалять существующие атрибуты» и «Не удалять существующие связи».
<br/><br/>
Пример включения формирования вторичного представления на головной документ:
<br/>
<pre style='color:#000000;background:#ffffff;'><span style='color:#545454; '><</span><span style='color:#2a2a2a; '>configuration</span><span style='color:#545454; '>></span>
<span style='color:#545454; '><</span><span style='color:#2a2a2a; '>loodsman</span> auto<span style='color:#555555; '>=</span><span style='color:#2a2a2a; '>"</span><span style='color:#4c4c4c; '>1</span><span style='color:#2a2a2a; '>"</span><span style='color:#545454; '>></span>
<span style='color:#545454; '><</span><span style='color:#2a2a2a; '>bases</span><span style='color:#545454; '>></span>
<span style='color:#545454; '><</span><span style='color:#2a2a2a; '>base</span> base<span style='color:#555555; '>=</span><span style='color:#2a2a2a; '>"</span><span style='color:#4c4c4c; '>Демо</span><span style='color:#2a2a2a; '>"</span> syncflags<span style='color:#555555; '>=</span><span style='color:#2a2a2a; '>"</span><span style='color:#4c4c4c; '>8</span><span style='color:#2a2a2a; '>"</span> <span style='color:#545454; '>/></span>
<span style='color:#545454; '></</span><span style='color:#2a2a2a; '>bases</span><span style='color:#545454; '>></span>
<span style='color:#545454; '></</span><span style='color:#2a2a2a; '>loodsman</span><span style='color:#545454; '>></span>
<span style='color:#545454; '></</span><span style='color:#2a2a2a; '>configuration</span><span style='color:#545454; '>></span>
</pre>
<h3>Настройка меню</h3>
<br/>
Команда «Вид → Вкладки → Настроить...» предназначалась
в основном администраторам. После настройки контекстного меню вкладок и панели инструментов
администратор распространял файл <code>LWF.menu</code> на все машины сети.
Поэтому диалог «Настройка меню» не был переведен, а перед его использованием
нужно было хотя бы раз открыть контекстное меню вкладки.
<br/><br/>
В новой версии диалог «Настройка меню» переведен на русский язык, и перед его отображением
загружаются все вкладки. Теперь настраивать меню будет удобно всем пользователям.
<br/><br/>
<div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-INVI1pBiJ_U/VjNNRo-8cfI/AAAAAAAAAfw/9R4GFwzQQ7Y/s1600/mainform1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-INVI1pBiJ_U/VjNNRo-8cfI/AAAAAAAAAfw/9R4GFwzQQ7Y/s320/mainform1.png" /></a></div>
<br/>
<h3>Сортировка файлов</h3>
<br/>
Файлы документа в области файлов теперь сортируются по имени.
<br/><br/>
Для сравнения имен файлов используется алгоритм «natural ordering»,
при котором порядок буквенно-цифровых строк будет привычным для человека
(как в Проводнике Windows).
<br/><br/>
Файлы можно сортировать по размеру, дате создания или изменения, щелкнув
по соответствующей колонке заголовка.
<br/><br/>
<a href="http://uselwf.ru/files/changes.txt" target="_blank">Полный список изменений</a>.
<br/><br/>
<a href="http://uselwf.ru/files/LWF_1.1.0.850.zip">LWF_1.1.0.850.zip</a>
<br/><br/>
Chaahttp://www.blogger.com/profile/14387721107858333063noreply@blogger.com0tag:blogger.com,1999:blog-4946864412455003694.post-31276606715776792352015-09-28T16:47:00.000+05:002017-06-08T09:00:10.438+05:00Illustrated Spare Parts Catalogue<a href="http://achechulin.blogspot.ru/p/kds.html">Illustrated Spare Parts Catalogue</a> — Software for creating and publishing spare parts catalogs. It works closely with the <a href="http://achechulin.blogspot.ru/p/plm.html">PLM</a> system.
<br/>
<br/>
It uses SQLite database,
<a href="http://synopse.info/fossil/wiki?name=SQLite3+Framework" target="_blank">mORMot</a> for client-server communication and ORM, and
<a href="http://synopse.info/fossil/wiki?name=PDF+Engine" target="_blank">SynPDF</a> for creating PDF.
<br/>
<br/>
<a name='more'></a>
<div class="separator" style="clear: both; text-align: center;"><a href="//4.bp.blogspot.com/-11gerzqzhUY/VgkcinH08jI/AAAAAAAAAdA/wC409Y8yXu8/s1600/kds_2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="//4.bp.blogspot.com/-11gerzqzhUY/VgkcinH08jI/AAAAAAAAAdA/wC409Y8yXu8/s320/kds_2.png"></a></div>
<br/>
In practice, there are three SQLite database: main <nobr>(typical of about 10 MB)</nobr>,
one for text blobs <nobr>(100 MB)</nobr>,
and one for pictures <nobr>(20 GB)</nobr>.
For this purpose, we use an
<a href="http://synopse.info/files/html/Synopse%20mORMot%20Framework%20SAD%201.18.html#TITL_27" target="_blank">external database</a> feature of mORMot.
For distributing interactive catalogues all SQLite databases may be merged in one.
<br/>
<br/>
Printing engine uses SynPDF for creating PDF files. Thanks to
<code>TPdfDocument.SaveToStreamDirectPageFlush</code>
— we can create really big PDF files.
We have tested creating PDF file with the size of about 700 MB.
Without the <code>SaveToStreamDirectPageFlush</code> there was thrown
an <code>EOutOfMemory</code> exception.
<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-_-8rWz4P3Ac/Vgjy6GIcp_I/AAAAAAAAAbo/-sSh5jjanAw/s1600/kds_11.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="//4.bp.blogspot.com/-_-8rWz4P3Ac/Vgjy6GIcp_I/AAAAAAAAAbo/-sSh5jjanAw/s320/kds_11.png"></a></div>
<br/>
Generated PDF sample:
<a href="https://sites.google.com/site/achechulin/files/078.505.9.0100.00PC.pdf" target="_blank">078.505.9.0100.00.pdf</a>.
<br />
<br/>
A very useful feature of mORMot is the
<a href="http://synopse.info/files/html/Synopse%20mORMot%20Framework%20SAD%201.18.html#TITL_16" target="_blank">Enhanced logging</a>,
which can show stack trace with unit names and line numbers.
Due to the low execution overhead of the logging,
we keep them enabled for the production server.
<br />
<br/>
Chaahttp://www.blogger.com/profile/14387721107858333063noreply@blogger.com0tag:blogger.com,1999:blog-4946864412455003694.post-64881123917688059042015-09-01T08:00:00.000+05:002015-10-05T15:18:54.057+05:00История LWFВ 2008 году мы внедряли систему ЛОЦМАН Workflow (версии 8.5).
Так выглядел клиент Workflow в то время:
<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-JeMz94U7AxU/VgzKvPbwORI/AAAAAAAAAdc/6BbQT2Gw76Q/s1600/wftask_6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-JeMz94U7AxU/VgzKvPbwORI/AAAAAAAAAdc/6BbQT2Gw76Q/s320/wftask_6.png" /></a></div>
<br/>
Но при внедрении возникли две существенные проблемы:
<br/>
<ol>
<li>Необходимость ввода пароля для подтверждение каждого действия.</li>
<li>В клиенте Workflow нет документа, с которым нужно работать.</li>
</ol>
<a name='more'></a>
<h3>Проблемы внедрения</h3>
<br/>
В ЛОЦМАН Workflow при выполнении любого действия показывалось окно
«Подтверждение полномочий». В нем нужно было ввести пароль,
затем нажать кнопку «Контроль», затем «OK».
<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-NNvffIs7-U4/Vgy8oOv5MaI/AAAAAAAAAdM/JDpJwOAoBNI/s1600/wftask_7.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-NNvffIs7-U4/Vgy8oOv5MaI/AAAAAAAAAdM/JDpJwOAoBNI/s320/wftask_7.png" /></a></div>
<br/>
Из-за требований безопасности пароли были не самые простые,
а вводить их требовалось минимум по два раза на каждое задание.
Если сотруднику в день поступало несколько десятков заданий, то это очень раздражало.
<br/>
<br/>
Если учесть, что больше всего заданий получали руководители,
от которых напрямую зависел результат внедрения, то понятно, что внедрение
Workflow было близко к провалу.
<br/>
<br/>
Второй проблемой стало то, что получив задание пользователь видит, что он должен что-то сделать,
но не видит, с каким документом.
Нажав кнопку «Открыть объект ЛОЦМАН в [ЛОЦМАН Клиент]» пользователь попадает
в ЛОЦМАН Клиент и работает там с документом.
И что еще интересно: при открытии файлов пользователя постоянно спрашивают,
действительно ли он хочет открыть файл в режиме «Только для чтения», или нет.
<br/>
<br/>
Изучив поступивший документ, необходимо выполнить какие-нибудь действия с заданием Workflow,
для этого нужно переключиться в клиент Workflow и найти там задание.
Что может быть непросто, когда у вас несколько десятков заданий.
<br/>
<br/>
И опять, чем выше должность руководителя, тем он старше, хуже видит,
и тем больше у него заданий. Найти нужное задание становится трудно,
а крах внедрения становится все ближе.
<br/>
<br/>
<h3>Свой клиент</h3>
<br/>
Чтобы спасти положение, было принято решение написать свой клиент Workflow.
Приложение получилось не очень большим, около 16 000 строк кода,
и было названо «Полученные задания и инициированные бизнес-процессы».
<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-LVJAUnbVdgE/VgzOf361_yI/AAAAAAAAAds/lTw6fgpXocs/s1600/wftask_5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-LVJAUnbVdgE/VgzOf361_yI/AAAAAAAAAds/lTw6fgpXocs/s200/wftask_5.png" /></a><a href="http://3.bp.blogspot.com/-3JDovys9EbY/VgzOf5qvD2I/AAAAAAAAAdo/AVooGJVLsio/s1600/wftask_4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-3JDovys9EbY/VgzOf5qvD2I/AAAAAAAAAdo/AVooGJVLsio/s200/wftask_4.png" /></a></div>
<br/>
Новый клиент не спрашивал пароль у пользователя для подтверждения каждого действия
и позволял видеть задание и документ одновременно.
А специально по просьбе генерального директора была добавлена возможность
установить большой размер шрифта для всего интерфейса программы.
<br/>
<br/>
Внедрение Workflow завершилось успешно, я занялся другими проблемами и о приложении
«Полученные задания и инициированные бизнес-процессы» было забыто.
<br/>
<br/>
<h3>Fly V2</h3>
<br/>
А в 2010 году появился Fly V2.
<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-2T40Hl7kPMg/VgzeGHPIXuI/AAAAAAAAAeA/nsU1ijo-GlY/s1600/wftask_8.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-2T40Hl7kPMg/VgzeGHPIXuI/AAAAAAAAAeA/nsU1ijo-GlY/s320/wftask_8.png" /></a></div>
<br/>
Fly V2 был хорош, особенно для задач ОРД (организационно-распорядительного документооборота).
Но в то же время он был вещью в себе — многие функции в нем были реализованы по-своему,
несовместимым со стандартным ЛОЦМАН Клиент образом.
<br/>
<br/>
Он не поддерживал плагины стандартного клиента, на которых у нас было многое завязано.
Он не поддерживал справочники стандартных изделий и материалов.
Он использовал свои библиотеки интеграции с Компасом вместо стандартного интегратора.
И функционал Workflow был в нем довольно ограниченным (хотя был простым и удобным),
а нам нужны были все возможности Workflow.
<br/>
<br/>
Тогда и появилась идея сделать свой клиент. Такой, чтобы он был простой и удобный,
как Fly V2, но полностью совместимый со стандартным клиентом.
Чтобы он использовал стандартные подходы к интеграции и бизнес-процессам.
<br/>
<br/>
На фоне давнего успеха «Полученных заданий и инициированных бизнес-процессов»
задача выглядела не такой уж и сложной.
<br/>
<br/>
<h3>LWF</h3>
<br/>
Для предприятия новый клиент был не нужен —
«Полученные задания и инициированные бизнес-процессы»
работали без нареканий, к ним все привыкли.
Поэтому я занимался новым клиентом по вечерам, в свободное от основной работы время.
Новый проект назывался «Loodsman2» и был построен на кодовой базе
«Полученных заданий и инициированных бизнес-процессов»,
в которую я постепенно добавлял функции редактирования данных.
<br/>
<br/>
Уже после непродолжительного периода разработки стало понятно, что на базе текущего
кода не удастся сделать нормальное редактирование данных в Лоцмане.
Весь код проекта «Loodsman2» был отброшен как бесперспективный.
<br/>
<br/>
В 2011 году был полностью с чистого листа начат проект «Loodsman3»,
который позже стал LWF.
Но так как работал я в свободное время, которого всегда не хватает, разработка шла медленно.
Сначала было сделано дерево объектов, потом списки заданий и процессов.
Только к концу 2011 года появилось более-менее работающее приложение.
<br/>
<br/>
Правда, радость по поводу работающего приложения была недолгой.
Тестирование показало, что вся система прозрачной работы с чекаутами работает плохо
и непрозрачно для обычного пользователя.
Всю систему автоматического взятия в работу и возврата изменений пришлось переделывать.
<br/>
<br/>
Из-за того, что вся кодовая база была новая, и довольно большая, в приложении
находилось огромное число крупных и мелких багов.
Большинство из них было исправлено в 2012 году, но некоторые прожили значительно дольше.
Одна из самых интересных и редко проявляющихся ошибок дожила до середины 2013 года, на
нее была потрачена куча времени и сил, см. <a href="/2013/02/new-wmpaint-dispatch.html">WM_PAINT во время ожидания вызова COM-сервера</a>.
<br/>
<br/>
В 2012 году, несмотря на предрекаемый скорый конец света, работа медленно, но шла.
Оставалось множество багов и приложение зависало при закрытии.
Но, имея терпение, им можно было пользоваться.
<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-GDFXGEtjJhI/Vg0Pr41TzZI/AAAAAAAAAeg/KUDpx0GkUjw/s1600/InitList1-win7.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-GDFXGEtjJhI/Vg0Pr41TzZI/AAAAAAAAAeg/KUDpx0GkUjw/s320/InitList1-win7.png" /></a></div>
<br/>
<h3>Обновление Лоцмана</h3>
<br/>
В середине 2012 на предприятии приняли решение перейти на новую версию
«Комплекс решений 2011».
В новой версии Лоцмана была значительно переработана подсистема Workflow.
Функции клиента ЛОЦМАН Workflow были перенесены
в стандартный ЛОЦМАН Клиент.
Теперь вместо отдельной базы данных бизнес-процессы стали храниться
в базах ЛОЦМАН:PLM.
Соответственно, встал вопрос о доработке клиента
«Полученные задания и инициированные бизнес-процессы».
<br/>
<br/>
Я предложил вместо доработки старого клиента перейти на LWF,
но большого энтузиазма у коллег мое предложение не вызвало.
Старый клиент был прост и надежен, а в LWF на тот момент было
полно проблем.
<br/>
<br/>
Все же было решено попробовать, так как запас по времени еще был.
Старый клиент был запущен в ограниченном режиме работы с одной базой данных
(есть такой параметр в настройках сервера приложений для обеспечения совместимости).
А новый был установлен пользователям, наиболее загруженным заданиями Workflow.
<br/>
<br/>
Практически все пользователи отозвались о новом клиенте в положительном ключе.
И к концу 2012 года старые
«Полученные задания и инициированные бизнес-процессы»
были везде заменены на LWF.
Все замеченные ошибки удалось исправить, проблемы возникали редко.
<br/>
<br/>
<h3>uselwf.ru</h3>
<br/>
Наступил 2013 год.
К этому времени автор Fly V2 перешел работать в Аскон и Fly V2 перестал развиваться.
Пришло время выпустить в мир новый клиент для ЛОЦМАН:PLM.
<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-nP9rEEuehO0/Vg0KxUu7oUI/AAAAAAAAAeQ/2M--XwFlVxw/s1600/wftask_9.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-nP9rEEuehO0/Vg0KxUu7oUI/AAAAAAAAAeQ/2M--XwFlVxw/s320/wftask_9.png" /></a></div>
<br/>
На сайте
<a href="http://uselwf.ru/" target="_blank">uselwf.ru</a> можно скачать последнюю версию и ознакомиться со списком изменений в ней. Там же находится подробное описание и документация. По вопросам работы с LWF лучше всего писать по электронной почте на <a href="mailto:support@uselwf.ru">support@uselwf.ru</a>.
<br />
<br />
Если вы посмотрите на
<a href="http://uselwf.ru/files/changes.txt" target="_blank">список изменений</a>,
то увидите, что с 2013 года многое было сделано.
Надеюсь, в будущем получится сделать еще немало.
<br/>
<br/>
Очень многое зависит и от вас, уважаемые пользователи.
В отличие от бездушных корпораций, делающих продукт для бизнеса,
LWF разрабатывается для людей.
При разработке прилагается максимум усилий для того, чтобы работать
с ним было просто, удобно и приятно
(насколько вообще может быть приятной работать с PLM-системой).
<br/>
<br/>
Chaahttp://www.blogger.com/profile/14387721107858333063noreply@blogger.com0tag:blogger.com,1999:blog-4946864412455003694.post-12304120164518495162015-02-13T08:00:00.000+05:002015-02-13T15:32:20.544+05:00Пробуем перейти на XE5В 2012 году я уже пытался перевести свои основные проекты с Delphi XE на XE2. Тогда дело встало из-за <a href="http://stackoverflow.com/questions/11463888/compiler-bug-when-using-generics-and-forward-declaration-in-delphi-xe2" target="_blank">ошибки компилятора в XE2</a>.
<br/>
На этот раз я решил повторить эксперимент и попытался перейти с Delphi XE на версию XE5 (Update 2).
<br/><br/>
Кратко: сразу не заработало, проблемы удалось решить, но осадок остался.<br/>
Для интересующихся подробности ниже.
<br/><br/>
<a name='more'></a>
<h3>Check</h3>
<br/>
Начал с перевода базовой инфраструктуры, исходный код здесь: <a href="https://github.com/achechulin/loodsman" target="_blank">github.com/achechulin/loodsman</a>.
<br/><br/>
После сборки в XE5 все тесты выполнились успешно. Приложение тоже работает.
<br/><br/>
Запускаю приложение на машине тестового пользователя — завершается с ошибкой «Access violation at address...». Запускаю тесты на машине пользователя — все тесты, использующие <code>TClientDataSet</code>, завершаются с исключением <code>EAccessViolation</code>.
<br/><br/>
Здесь уже стало очевидно, что проблема в <code>midas.dll</code>. Вместе с «Комплексом решений Аскон» устанавливается версия 6.0 библиотеки (с версии «Комплекс 2013» устанавливается еще и версия 16.0, но мне надо чтобы работало и со старыми версиями). Быстрое изучение нового кода <code>TClientDataSet</code> под отладчиком и сравнение со старым кодом из Delphi XE подтвердило догадку.
<br/><br/>
В Delphi 2009 и более поздних версиях используется кодировка UTF-8 для метаданных <code>TClientDataSet</code> (и сделано это неудачно — пакеты данных не совместимы между старыми и новыми версиями, см. <a href="http://achechulin.blogspot.ru/2012/07/dataset.html">Наборы данных</a>). Для этого вызывается <code>DSBase.SetProp(dspropUTF8METADATA, True)</code>. Так как старые версии <code>midas.dll</code> не знают флага <code>dspropUTF8METADATA</code>, то <code>SetProp</code> возвращает ошибку <code>DBIERR_INVALIDPARAM</code> (9986). Решение очевидно — установить правильную версию <code>midas.dll</code>. Вот только не совсем понятно появление исключения <code>EAccessViolation</code> вместо <code>EDBClient</code> c сообщением «Invalid parameter».
<br/><br/>
Смотрим дальше. Немного кода:
<pre>
<span style='color:#575757; font-weight:bold; '>procedure</span> TCustomClientDataSet<span style='color:#555555; '>.</span>CreateDataSet<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>begin</span>
<span style='color:#555555; '>..</span><span style='color:#555555; '>.</span>
<span style='color:#696969; '>// Создает объект DSBase, реализованный в midas.dll</span>
<span style='color:#696969; '>// Переменная FDSBase пока nil</span>
FDSBase <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> CreateDSBase<span style='color:#555555; '>;</span>
<span style='color:#555555; '>..</span><span style='color:#555555; '>.</span>
<span style='color:#575757; font-weight:bold; '>end</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>function</span> TCustomClientDataSet<span style='color:#555555; '>.</span>CreateDSBase<span style='color:#555555; '>:</span> IDSBase<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>begin</span>
CreateDbClientObject<span style='color:#555555; '>(</span>CLSID_DSBase<span style='color:#555555; '>,</span> IDSBase<span style='color:#555555; '>,</span> <span style='color:#575757; font-weight:bold; '>Result</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#696969; '>// Вызов SetProp вернет ошибку и мы попадем в InternalCheck</span>
Check<span style='color:#555555; '>(</span><span style='color:#575757; font-weight:bold; '>Result</span><span style='color:#555555; '>.</span>SetProp<span style='color:#555555; '>(</span>dspropANSICODEPAGE<span style='color:#555555; '>,</span> DefaultSystemCodePage<span style='color:#555555; '>)</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#555555; '>..</span><span style='color:#555555; '>.</span>
<span style='color:#575757; font-weight:bold; '>end</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>procedure</span> TCustomClientDataSet<span style='color:#555555; '>.</span>Check<span style='color:#555555; '>(</span>Status<span style='color:#555555; '>:</span> DBResult<span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>begin</span>
<span style='color:#575757; font-weight:bold; '>if</span> Status <span style='color:#555555; '><</span><span style='color:#555555; '>></span> <span style='color:#2e2e2e; '>0</span> <span style='color:#575757; font-weight:bold; '>then</span> InternalCheck<span style='color:#555555; '>(</span>Status<span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>end</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>procedure</span> TCustomClientDataSet<span style='color:#555555; '>.</span>InternalCheck<span style='color:#555555; '>(</span>Status<span style='color:#555555; '>:</span> DBResult<span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>begin</span>
<span style='color:#575757; font-weight:bold; '>if</span> Status <span style='color:#555555; '><</span><span style='color:#555555; '>></span> <span style='color:#2e2e2e; '>0</span> <span style='color:#575757; font-weight:bold; '>then</span>
<span style='color:#575757; font-weight:bold; '>begin</span>
<span style='color:#555555; '>..</span><span style='color:#555555; '>.</span>
<span style='color:#696969; '>// FDSBase все еще nil</span>
<span style='color:#696969; '>// Здесь и возникнет исключение</span>
FDSBase<span style='color:#555555; '>.</span>GetErrorString<span style='color:#555555; '>(</span>Status<span style='color:#555555; '>,</span> <span style='color:#575757; font-weight:bold; '>Pointer</span><span style='color:#555555; '>(</span><span style='color:#555555; '>@</span>ErrMsgBuffer<span style='color:#555555; '>[</span><span style='color:#2e2e2e; '>0</span><span style='color:#555555; '>]</span><span style='color:#555555; '>)</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#555555; '>..</span><span style='color:#555555; '>.</span>
<span style='color:#696969; '>// А сюда уже не попадем</span>
<span style='color:#575757; font-weight:bold; '>raise</span> EDBClient<span style='color:#555555; '>.</span>Create<span style='color:#555555; '>(</span>ErrMsg<span style='color:#555555; '>,</span> Status<span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>end</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>end</span><span style='color:#555555; '>;</span>
</pre>
Теперь возникновение исключения <code>EAccessViolation</code> понятно. Но знания эти приносят только печаль — большое количество изменений в коде DataSnap не тестировалось.
<br/><br/>
<h3>StrLen</h3>
<br/>
Так получилось, что большинство моих проблем с TClientDataSet связано с русскими буквами в именах полей наборов данных. ЛОЦМАН:PLM может возвращать в наборе данных поля с атрибутами. Названия атрибутов обычно пишутся русскими буквами и могут быть любой длины (в пределах разумного). Например «Группа материала по сортаменту» (30 символов, 57 байт в UTF-8).
<br/><br/>
При сборке <code>Loodsman.Infrastructure.DataSet.pas</code> в новой версии компилятор выдал предупреждение:
<br/><br/>
<code>W1000 Symbol 'StrLen' is deprecated: 'Moved to the AnsiStrings unit'</code>
<br/><br/>
Ничего страшного, но лучше исправить. Этот фрагмент кода был скопирован из <code>DBClient.pas</code>, и я решил посмотреть как избавились от предупреждения разработчики Delphi:
<br/><br/>
<pre>
<span style='color:#7d7d7d; '>- LName := MetaDataToUnicode(szName);</span>
<span style='color:#4c4c4c; '>+ LName := TMarshal.ReadStringAsAnsi(CP_UTF8, szName);</span>
<span style='color:#7d7d7d; '>- if StrLen(szName) = SizeOf(MIDASNAME) - 1 then</span>
<span style='color:#4c4c4c; '>+ if LName.Length = SizeOf(MIDASNAME) - 1 then</span>
begin
V := InternalGetOptionalParam(szFIELDNAME, FieldID);
if not VarIsNull(V) and not VarIsClear(V) then
LName := VarToStr(V);
end;
</pre>
— Как же так? — воскликнет внимательный читатель, — ведь длина строки в UTF-16 это совсем не то же самое, что длина строки в UTF-8!
<br/><br/>
И действительно, простой тест подтверждает нашу догадку:
<pre>
<span style='color:#575757; font-weight:bold; '>procedure</span> TTestDataSet<span style='color:#555555; '>.</span>TestLongFieldName<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>const</span>
CFieldName <span style='color:#555555; '>=</span> <span style='color:#4c4c4c; '>'Группа материала по сортаменту'</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>var</span>
DS<span style='color:#555555; '>:</span> TClientDataSet<span style='color:#555555; '>;</span>
LFieldDef<span style='color:#555555; '>:</span> TFieldDef<span style='color:#555555; '>;</span>
LData<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>OleVariant</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>begin</span>
DS <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> TClientDataSet<span style='color:#555555; '>.</span>Create<span style='color:#555555; '>(</span><span style='color:#404040; '>nil</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>try</span>
LFieldDef <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> DS<span style='color:#555555; '>.</span>FieldDefs<span style='color:#555555; '>.</span>AddFieldDef<span style='color:#555555; '>;</span>
LFieldDef<span style='color:#555555; '>.</span>Name <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> CFieldName<span style='color:#555555; '>;</span>
LFieldDef<span style='color:#555555; '>.</span>DataType <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> ftString<span style='color:#555555; '>;</span>
LFieldDef<span style='color:#555555; '>.</span>Size <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> <span style='color:#2e2e2e; '>255</span><span style='color:#555555; '>;</span>
DS<span style='color:#555555; '>.</span>CreateDataSet<span style='color:#555555; '>(</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
LData <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> DS<span style='color:#555555; '>.</span>Data<span style='color:#555555; '>;</span>
DS<span style='color:#555555; '>.</span>Data <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> LData<span style='color:#555555; '>;</span>
LFieldDef <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> DS<span style='color:#555555; '>.</span>FieldDefs<span style='color:#555555; '>.</span>Items<span style='color:#555555; '>[</span><span style='color:#2e2e2e; '>0</span><span style='color:#555555; '>]</span><span style='color:#555555; '>;</span>
CheckEquals<span style='color:#555555; '>(</span>CFieldName<span style='color:#555555; '>,</span> LFieldDef<span style='color:#555555; '>.</span>Name<span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>finally</span>
DS<span style='color:#555555; '>.</span>Free<span style='color:#555555; '>(</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>end</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>end</span><span style='color:#555555; '>;</span>
</pre>
Результат:
<br/><br/>
<code>TestLongFieldName: ETestFailure</code><br/>
<code>expected: <Группа материала по сортаменту> but was: <Группа материал�></code>
<br/><br/>
Новые знания приносят новую печаль — изменения вообще не тестируются.
Embarcadero выпускает два релиза в год, а в старых версиях ошибки уже не исправляются.
<br/><br/>
Похоже, придется пока остаться на XE. По крайней мере с ее багами я уже знаком.
<br/><br/>Chaahttp://www.blogger.com/profile/14387721107858333063noreply@blogger.com0tag:blogger.com,1999:blog-4946864412455003694.post-47500058452901996002014-11-07T08:00:00.000+05:002014-11-17T10:38:30.326+05:00mORMotСегодня я хочу рассказать о <a href="http://synopse.info/" target="_blank">mORMot</a>. Надеюсь, эта статья заинтересует вас и подтолкнет попробовать библиотеку.
<br/>
<br/>
<a href="http://synopse.info/" target="_blank">mORMot</a> — это набор библиотек для разработки программного обеспечения на основе сервис-ориентированной архитектуры (распределенных, слабо связанных заменяемых компонентов, со стандартизированными интерфейсами и протоколами, в данном случае на основе REST). Все библиотеки доступны в виде исходного кода и распространяются по лицензии MPL, GPL или LGPL (на выбор).
<br/>
<br/>
Набор библиотек содержит большое количество полезных компонентов: сетевого взаимодействия, доступа к БД, шифрования, сжатия данных, создания PDF, выполнения JavaScript, ORM и многое другое.
Все компоненты минимально связаны друг с другом — можно использовать только те, которые нужны. Например, для создания PDF нужно добавить в свой проект всего три файла: <code>SynPDF.pas</code>, <code>SynCommons.pas</code> и <code>SynLZ.pas</code>.
<br/>
<br/>
<a name='more'></a>
Разработка ведется в основном одним человеком — Арно Буше (Arnaud Bouchez). Желающие помогают ему с кодом разных компонентов.
Например, код выполнения JavaScript с помощью движка Spider Monkey написал Павел Машляковский.
Я написал код прозрачной аутентификации пользователя в домене Active Directory.
Все-таки, открытый исходный код и совместная разработка — это здорово!
<br/>
<br/>
В одном из своих приложений я использовал практически весь набор библиотек.
С приложением активно работают несколько десятков пользователей, в базе данных SQLite около 20 Гб данных.
За время разработки и эксплуатации приложения библиотека на деле подтвердила свое качество и стабильность.
Ошибки конечно бывают, но оперативно исправляются.
<br/>
<br/>
Именно быстрая (буквально в течение пары дней) и адекватная реакция автора на баги и недоработки является серьезным преимуществом библиотеки.
А если что-то не получается, всегда можно спросить совета на <a href="http://synopse.info/forum/" target="_blank">форуме</a>.
Сравните с тем, как я пытался решить
<a href="http://stackoverflow.com/questions/11463888/compiler-bug-when-using-generics-and-forward-declaration-in-delphi-xe2" target="_blank">проблему с багом в компиляторе Delphi XE2</a>.
В результате, всего через год ошибку починили, в версии XE4, которую нужно купить, чтобы компилятор заработал правильно.
<br/>
<br/>
Библиотека имеет хорошую документацию, основной документ
«<a href="http://synopse.info/files/html/Synopse%20mORMot%20Framework%20SAD%201.18.html" target="_blank">Software Architecture Design</a>»
содержит 1500 страниц (в <a href="http://synopse.info/files/pdf/Synopse%20mORMot%20Framework%20SAD%201.18.pdf" target="_blank">формате PDF</a>).
Читать их все на первых порах необязательно, но очень желательно.
<br/>
<br/>
<h3>Пример использования</h3>
<br/>
Приведу краткий пример для быстрого знакомства с mORMot.
<br/>
<br/>
Первым делом следует определить модель данных, с которой будет работать наше приложение.
Для этого мы объявляем классы-наследники <code>TSQLRecord</code>.
Модель данных мы поместим в отдельный модуль <a href="https://github.com/synopse/mORMot/blob/master/SQLite3/Samples/01%20-%20In%20Memory%20ORM/SampleData.pas" target="_blank">SampleData.pas</a>, общий для клиента и сервера. В нашей модели будет один класс — <code>TSQLSampleRecord</code>. Поля класса из секции <code>published</code> будут участвовать в сериализации, то есть передаваться между клиентом и сервером, сохраняться в базе данных, использоваться <code>ObjectToJSON</code>/<code>JSONToObject</code> и в других местах.
<br/>
<pre>
TSQLSampleRecord <span style='color:#555555; '>=</span> <span style='color:#575757; font-weight:bold; '>class</span><span style='color:#555555; '>(</span>TSQLRecord<span style='color:#555555; '>)</span>
<span style='color:#575757; font-weight:bold; '>private</span>
FQuestion<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>String</span><span style='color:#555555; '>;</span>
FName<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>String</span><span style='color:#555555; '>;</span>
FTime<span style='color:#555555; '>:</span> TModTime<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>published</span>
<span style='color:#575757; font-weight:bold; '>property</span> Time<span style='color:#555555; '>:</span> TModTime <span style='color:#575757; font-weight:bold; '>read</span> FTime <span style='color:#575757; font-weight:bold; '>write</span> FTime<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>property</span> Name<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>String</span> <span style='color:#575757; font-weight:bold; '>read</span> FName <span style='color:#575757; font-weight:bold; '>write</span> FName<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>property</span> Question<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>String</span> <span style='color:#575757; font-weight:bold; '>read</span> fQuestion <span style='color:#575757; font-weight:bold; '>write</span> FQuestion<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>end</span><span style='color:#555555; '>;</span>
</pre>
У каждого наследника <code>TSQLRecord</code> есть поле <code>ID</code>, это первичный ключ в базе данных.
<br/>
<br/>
Для нашего класса <code>TSQLSampleRecord</code> в базе данных будет создана таблица <code>SampleRecord</code>
(при вызове <code>TSQLRestServer.CreateMissingTables</code>):
<br/>
<pre>
<span style='color:#575757; font-weight:bold; '>CREATE</span> <span style='color:#575757; font-weight:bold; '>TABLE</span> SampleRecord <span style='color:#555555; '>(</span>
ID <span style='color:#575757; font-weight:bold; '>INTEGER</span> <span style='color:#575757; font-weight:bold; '>NOT</span> <span style='color:#575757; font-weight:bold; '>NULL</span> <span style='color:#575757; font-weight:bold; '>PRIMARY</span> <span style='color:#575757; font-weight:bold; '>KEY</span><span style='color:#555555; '>,</span>
Time <span style='color:#575757; font-weight:bold; '>INTEGER</span><span style='color:#555555; '>,</span>
Name <span style='color:#575757; font-weight:bold; '>TEXT</span><span style='color:#555555; '>,</span>
Question <span style='color:#575757; font-weight:bold; '>TEXT</span>
<span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
</pre>
Разные классы-наследники <code>TSQLRecord</code> могут сохраняться в разных базах данных.
<br/>
<br/>
Далее мы создаем модель — экземпляр <code>TSQLModel</code>, в которой будут собраны вместе наследники <code>TSQLRecord</code>.
В модели нужно указывать только классы, сохраняемые в базе данных. Каждый наследник <code>TSQLRecord</code>, добавленный в модель <code>TSQLModel</code>, будет представлен таблицей в базе данных, и сервер будет предоставлять удаленный доступ к нему.
<br/>
<pre>
<span style='color:#575757; font-weight:bold; '>function</span> CreateSampleModel<span style='color:#555555; '>:</span> TSQLModel<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>begin</span>
<span style='color:#575757; font-weight:bold; '>Result</span> <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> TSQLModel<span style='color:#555555; '>.</span>Create<span style='color:#555555; '>(</span><span style='color:#555555; '>[</span>TSQLSampleRecord<span style='color:#555555; '>]</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>end</span><span style='color:#555555; '>;</span>
</pre>
Создаем экземпляр сервера. Сервер будет сохранять и получать классы-наследники <code>TSQLRecord</code> в базе данных.
<br/>
<pre>
FDatabase <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> TSQLRestServerDB<span style='color:#555555; '>.</span>Create<span style='color:#555555; '>(</span>FModel<span style='color:#555555; '>,</span>
ChangeFileExt<span style='color:#555555; '>(</span>ParamStr<span style='color:#555555; '>(</span><span style='color:#2e2e2e; '>0</span><span style='color:#555555; '>)</span><span style='color:#555555; '>,</span> <span style='color:#4c4c4c; '>'.db3'</span><span style='color:#555555; '>)</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
FDatabase<span style='color:#555555; '>.</span>CreateMissingTables<span style='color:#555555; '>(</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
</pre>
Теперь мы можем сохранять в базе данных экземпляры <code>TSQLSampleRecord</code>:
<br/>
<pre>
<span style='color:#575757; font-weight:bold; '>var</span>
LSampleRecord<span style='color:#555555; '>:</span> TSQLSampleRecord<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>begin</span>
LSampleRecord<span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> TSQLSampleRecord<span style='color:#555555; '>.</span>Create<span style='color:#555555; '>(</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>try</span>
LSampleRecord<span style='color:#555555; '>.</span>Name <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> NameEdit<span style='color:#555555; '>.</span><span style='color:#575757; font-weight:bold; '>Text</span><span style='color:#555555; '>;</span>
LSampleRecord<span style='color:#555555; '>.</span>Question <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> QuestionMemo<span style='color:#555555; '>.</span><span style='color:#575757; font-weight:bold; '>Text</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>if</span> Database<span style='color:#555555; '>.</span>Add<span style='color:#555555; '>(</span>LSampleRecord<span style='color:#555555; '>,</span> <span style='color:#404040; '>True</span><span style='color:#555555; '>)</span> <span style='color:#555555; '>=</span> <span style='color:#2e2e2e; '>0</span> <span style='color:#575757; font-weight:bold; '>then</span>
<span style='color:#575757; font-weight:bold; '>raise</span> Exception<span style='color:#555555; '>.</span>Create<span style='color:#555555; '>(</span><span style='color:#4c4c4c; '>'Error adding the data'</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>finally</span>
LSampleRecord<span style='color:#555555; '>.</span>Free<span style='color:#555555; '>(</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>end</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>end</span><span style='color:#555555; '>;</span>
</pre>
И получать экземпляры <code>TSQLSampleRecord</code> из базы данных:
<br/>
<pre>
<span style='color:#575757; font-weight:bold; '>var</span>
LSampleRecord<span style='color:#555555; '>:</span> TSQLSampleRecord<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>begin</span>
LSampleRecord <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> TSQLSampleRecord<span style='color:#555555; '>.</span>Create<span style='color:#555555; '>(</span>Database<span style='color:#555555; '>,</span>
<span style='color:#4c4c4c; '>'Name=?'</span><span style='color:#555555; '>,</span> <span style='color:#555555; '>[</span>NameEdit<span style='color:#555555; '>.</span><span style='color:#575757; font-weight:bold; '>Text</span><span style='color:#555555; '>]</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>try</span>
<span style='color:#575757; font-weight:bold; '>if</span> LSampleRecord<span style='color:#555555; '>.</span>ID <span style='color:#555555; '>=</span> <span style='color:#2e2e2e; '>0</span> <span style='color:#575757; font-weight:bold; '>then</span>
QuestionMemo<span style='color:#555555; '>.</span><span style='color:#575757; font-weight:bold; '>Text</span> <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> <span style='color:#4c4c4c; '>'Not found'</span>
<span style='color:#575757; font-weight:bold; '>else</span>
QuestionMemo<span style='color:#555555; '>.</span><span style='color:#575757; font-weight:bold; '>Text</span> <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> LSampleRecord<span style='color:#555555; '>.</span>Question<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>finally</span>
LSampleRecord<span style='color:#555555; '>.</span>Free<span style='color:#555555; '>(</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>end</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>end</span><span style='color:#555555; '>;</span>
</pre>
Немного изменив код, мы можем перейти к клиент-серверной архитектуре на основе REST. Модель данных будет общая для клиента и для сервера. На сервер мы добавим создание экземпляра <code>TSQLHttpServer</code>, а на клиенте вместо <code>TSQLRestServerDB</code> будем использовать <code>TSQLHttpClient</code>.
<br/>
<br/>
Организовываем доступ к серверу по протоколу HTTP:
<br/>
<pre>
FServer <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> TSQLHttpServer<span style='color:#555555; '>.</span>Create<span style='color:#555555; '>(</span><span style='color:#4c4c4c; '>'80'</span><span style='color:#555555; '>,</span> <span style='color:#555555; '>[</span>FDatabase<span style='color:#555555; '>]</span><span style='color:#555555; '>,</span>
<span style='color:#4c4c4c; '>'+'</span><span style='color:#555555; '>,</span> HTTP_DEFAULT_MODE<span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
</pre>
Подключение клиента к серверу:
<br/>
<pre>
FDatabase <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> TSQLHttpClient<span style='color:#555555; '>.</span>Create<span style='color:#555555; '>(</span><span style='color:#4c4c4c; '>'localhost'</span><span style='color:#555555; '>,</span> <span style='color:#4c4c4c; '>'80'</span><span style='color:#555555; '>,</span> FModel<span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
FDatabase<span style='color:#555555; '>.</span>SetUser<span style='color:#555555; '>(</span><span style='color:#4c4c4c; '>'User'</span><span style='color:#555555; '>,</span> <span style='color:#4c4c4c; '>'synopse'</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
</pre>
Здесь описана только очень небольшая часть возможностей mORMot, только чтобы заинтересовать читателя.
Подробней лучше почитать в документации, посмотреть примеры.
<br/>
<br/>
Полный код примеров, части из которых были показаны выше:
<br/>
<a href="https://github.com/synopse/mORMot/tree/master/SQLite3/Samples/01%20-%20In%20Memory%20ORM" target="_blank">01 - In Memory ORM</a>,
<br/>
<a href="https://github.com/synopse/mORMot/tree/master/SQLite3/Samples/02%20-%20Embedded%20SQLite3%20ORM" target="_blank">02 - Embedded SQLite3 ORM</a>,
<br/>
<a href="https://github.com/synopse/mORMot/tree/master/SQLite3/Samples/04%20-%20HTTP%20Client-Server" target="_blank">04 - HTTP Client-Server</a>.
<br/>
<br/>
Быстрая сборка примеров:
<ol>
<li>
Скачайте архив текущей версии исходного кода с GitHub — файл <a href="https://github.com/synopse/mORMot/archive/master.zip">mORMot-master.zip</a>. Распакуйте архив в любое удобное место.</li>
<li>Скачайте файл <a href="http://synopse.info/files/sqlite3obj.7z">sqlite3obj.7z</a> и распакуйте его в папку <code>mORMot-master</code>, получившуюся на предыдущем шаге.</li>
<li>Откройте нужный файл проекта в Delphi (например, <code>mORMot-master\SQLite3\Samples\02 - Embedded SQLite3 ORM\Project02.dpr</code>) и в свойствах проекта добавьте в <code>Search path</code> пути <code>..\..\;..\..\..\</code>, должно получиться, как на рисунке ниже.</li>
<li>Для запуска примеров «13 - StandAlone JSON SQL server» и «17 - TClientDataset use» нужна база данных <code>test.db3</code>. Она создается при запуске тестов, файл проекта <code>mORMot-master\SQLite3\TestSQL3.dpr</code>.</li>
</ol>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-eMva0z_INn0/VGmFFx6aK9I/AAAAAAAAAE8/ZbyR3SFXYGA/s1600/mormot-samples-searchpath.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-eMva0z_INn0/VGmFFx6aK9I/AAAAAAAAAE8/ZbyR3SFXYGA/s320/mormot-samples-searchpath.png" /></a></div>
<br/>
<br/>
В следующей статье я планирую описать код аутентификации пользователя в домене Active Directory,
написанный мной для mORMot.
<br/>
<br/>Chaahttp://www.blogger.com/profile/14387721107858333063noreply@blogger.com12tag:blogger.com,1999:blog-4946864412455003694.post-70463022975391173342014-04-12T11:07:00.000+06:002014-04-23T16:58:23.617+06:00Подключаемые модули в новых версиях ЛОЦМАН КлиентДля <a href="http://achechulin.blogspot.ru/2012/04/plugin-sample.html">разработчиков плагинов</a> в новых версиях 13 и 14 комплекса произошли заметные изменения (под версиями 13 и 14 здесь подразумевается «Комплекс решений АСКОН 2013» и «Комплекс решений АСКОН 2014»).
<br />
<br />
Положительная сторона изменений в том, что API плагинов теперь документирован (см. <code>LoodsmanClientApi.chm</code>) и предоставляет разработчику больше возможностей. Из недостатков стоит отметить несовместимость API со старыми версиями плагинов, и даже между 13 и 14 версиями. И документации мало, и местами она не соответствует реальному положению дел. И баги в <code>IDataSet</code>... Но не будем о грустном.
<br /><br />
<a name='more'></a>
Итак, основные изменения, которые удалось увидеть на сегодняшний день:
<br />
<ol>
<li>Объект, реализующий <code>IDataSet</code>, был вынесен в отдельную библиотеку <code>DataProvider.dll</code>.
И это было бы незначительное изменение, не затрагивающее плагины, если бы не изменился идентификатор (GUID) интерфейса <code>IDataSet</code>. Дело осложняется тем, что в Delphi вызов <code>QueryInterface</code> может присутствовать неявно при приведении типов.</li>
<li>В версии 14.0.0.905 у интерфейса <code>IDataSet</code> не работают методы <code>IDispatch</code>, такие как <code>GetIDsOfNames</code> и <code>Invoke</code>. В службе поддержки обещали исправить.</li>
<li>Объекты, реализующие <code>IPDMObject</code>, <code>IWFObject</code> и подобные переехали в <code>PDMObjects.dll</code>. В версии 14 в иерархии интерфейсов этих объектов появился новый предок <code>IProtectedPDMData</code>, в результате чего все эти объекты несовместимы между 13 и 14 версиями.</li>
<li>У <code>IPluginCall.GetDataSet</code> изменился тип возвращаемого значения с <code>IDataSet</code> на <code>IDispatch</code>. Это изменение неудобно тем, что в старых плагинах потребуется исправить все вызовы <code>GetDataSet</code>.</li>
<li>Пункты меню плагина теперь неактивны, если в клиенте выбрано что-нибудь, отличное от объекта или документа, или если не выбрано ничего. В документации сказано, что это сделано «для предотвращения ошибок, которые могут возникнуть в уже разработанных модулях расширения». Если плагину нужно работать с бизнес-процессами или заданиями, или если нужно, чтобы пункты меню были доступны, когда в клиенте ничего не выбрано, DLL плагина должна экспортировать функцию <code>GetPluginInfo</code> (есть в шаблоне подключаемого модуля).
</li>
<li>По умолчанию теперь все пункты меню подключаемых модулей помещаются в отдельное меню «Дополнения». При необходимости их можно перенести в другое место меню, вынести на панель инструментов или добавить в контекстное меню.</li>
</ol>
<div class="separator" style="clear: both; text-align: center;">
<a href="http://4.bp.blogspot.com/-XBFKdLE7TEg/U1dy7MdqLWI/AAAAAAAAADY/qFfBh0O_OJk/s1600/PluginMenu.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-XBFKdLE7TEg/U1dy7MdqLWI/AAAAAAAAADY/qFfBh0O_OJk/s1600/PluginMenu.png" height="92" width="640" /></a></div>
<br />
Для перевода плагинов на новый API от разработчика потребуется использовать новый модуль <code>Loodsman_TLB.pas</code> (и связанные с ним) от нужной версии комплекса.
<br />
<br />
В приведенных ниже модулях <code>Loodsman_TLB.pas</code> тип возвращаемого значения <code>IPluginCall.GetDataSet</code> исправлен на <code>IDataSet</code>, и в версиях 13 и 14 добавлен тип <code>IDataSet = DataProvider_TLB.IDataSet</code>. В результате старые плагины можно собирать с новыми заголовочными файлами без изменения кода.
<br />
<br />
Теперь есть две версии шаблона подключаемого модуля: полностью самостоятельный проект
<a href="https://github.com/achechulin/loodsman/tree/master/Plugins/PluginSample2" target="_blank">PluginSample2</a> и зависящий от общей инфраструктуры <a href="https://github.com/achechulin/loodsman/tree/master/Plugins/PluginSample" target="_blank">PluginSample</a>.
<br />
<br />
Если вам не нужно отдельное подключение к серверу приложений, или вы используете старую версию Delphi (до Delphi 2010), то лучше использовать независимый <a href="https://github.com/achechulin/loodsman/tree/master/Plugins/PluginSample2" target="_blank">PluginSample2</a>. В настройках проекта необходимо будет указать путь к файлам нужной версии API в <code>SearchPath</code>. Для версий 8.5−10 это APIv10, для версии 11, 13 и 14 это APIv11, APIv13 и APIv14 соответственно.
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://2.bp.blogspot.com/-qGWV9SAiGtI/U1eB7PIkADI/AAAAAAAAADo/UEhDdgbVxV0/s1600/ProjSettings.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-qGWV9SAiGtI/U1eB7PIkADI/AAAAAAAAADo/UEhDdgbVxV0/s1600/ProjSettings.png" height="520" width="640" /></a></div>
<br />
<br />
Шаблон подключаемого модуля <a href="https://github.com/achechulin/loodsman/tree/master/Plugins/PluginSample" target="_blank">PluginSample</a> может использовать самостоятельное подключение к серверу приложений <a href="http://achechulin.blogspot.ru/2012/09/remote-connection.html">IRemoteConnection</a> и собственную реализацию <a href="http://achechulin.blogspot.ru/2012/07/dataset.html">IDataSet</a>. По умолчанию плагин собирается для API версии 14, но можно собрать со старой версией <code>IDataSet</code> (директива <code>UseV11DataSet</code>) или с объектами <code>IPDMObject</code>, <code>IWFObject</code> версии 13 (директива <code>UseV13PDMData</code>).
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://2.bp.blogspot.com/-tfUwXUfegfE/U1eRDcK-ZYI/AAAAAAAAAD4/fhQGVCdjC9M/s1600/ProjSettings2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-tfUwXUfegfE/U1eRDcK-ZYI/AAAAAAAAAD4/fhQGVCdjC9M/s1600/ProjSettings2.png" height="496" width="640" /></a></div>
<br />
<br />
Для новых версий комплекса доработан код из статьи <a href="http://achechulin.blogspot.ru/2012/07/dataset.html">Подключение к серверу приложений. Наборы данных</a>. Реализация теперь поддерживает интерфейсы <code>IDataSet</code> старых и новых версий.
<br/><br/>
Весь код, описанный в статье, находится на GitHub:
<br/>
<a href="https://github.com/achechulin/loodsman" target="_blank">github.com/achechulin/loodsman</a>.
<br/><br/>
Текущую версию кода можно скачать без клиента Git или SVN, нажав кнопку «<a href="https://github.com/achechulin/loodsman/archive/master.zip">Download ZIP</a>» в правой части страницы репозитория.Chaahttp://www.blogger.com/profile/14387721107858333063noreply@blogger.com0tag:blogger.com,1999:blog-4946864412455003694.post-26016981680206033262013-12-21T09:00:00.000+06:002013-12-24T14:21:20.145+06:00GitHubИсходный код примера подключаемого модуля из статьи
<a href="/2012/04/plugin-sample.html">Пишем подключаемый модуль для ЛОЦМАН Клиент</a>
и исходный код <a href="/2012/09/remote-connection.html">IRemoteConnection</a>
теперь доступны на GitHub — <a href="https://github.com/achechulin/loodsman" target="_blank">github.com/achechulin/loodsman</a>.
<br/><br/>
Текущую версию кода можно скачать без клиента Git или SVN, нажав кнопку «<a href="https://github.com/achechulin/loodsman/archive/master.zip">Download ZIP</a>» в правой части страницы репозитория.
Chaahttp://www.blogger.com/profile/14387721107858333063noreply@blogger.com0tag:blogger.com,1999:blog-4946864412455003694.post-49851554475305345522013-05-15T10:00:00.000+06:002013-05-15T20:54:46.916+06:00Использование HTMLHelpViewer в DLLВ модуле <a href="http://docwiki.embarcadero.com/Libraries/en/Vcl.HtmlHelpViewer" target="_blank">HTMLHelpViewer</a> со времени его появления в Delphi 2005 и до выхода Update 4 для Delphi XE2 была ошибка, не позволяющая использовать этот модуль в DLL. Ошибка состоит в том, что в секции <code>finalization</code> всегда выполняется вызов <code>HtmlHelp(HH_CLOSE_ALL)</code>, что может приводить к загрузке библиотеки <code>HHCTRL.OCX</code>. Для обычного приложения это не страшно, просто теряется немного времени на загрузку-выгрузку библиотеки. Но если мы пишем DLL (например, COM-объект или плагин для другого приложения), то выполнение секции <code>finalization</code> будет происходить внутри <a href="http://msdn.microsoft.com/en-us/library/windows/desktop/ms682583.aspx" target="_blank">DllMain</a>, и попытка загрузить <code>HHCTRL.OCX</code> приведет либо к зависанию приложения, либо к ошибке Access Violation (в зависимости от версии Windows).
<br/><br/>
<a name='more'></a>
<a href="http://msdn.microsoft.com/en-us/library/windows/desktop/ms682583.aspx" target="_blank">В статье MSDN</a> вполне понятно написано, что можно и что нельзя делать в <code>DllMain</code>:
<br/>
<blockquote>
The entry-point function should perform only simple initialization or termination tasks. It must not call the <code>LoadLibrary</code> or <code>LoadLibraryEx</code> function (or a function that calls these functions).
<br/>
Similarly, the entry-point function must not call the <code>FreeLibrary</code> function (or a function that calls FreeLibrary) during process termination.
</blockquote>
Несмотря на это, вызов <code>LoadLibrary</code> и <code>FreeLibrary</code> широко распространен в секциях <code>initialization</code> и <code>finalization</code>.
Обычно это работает, так как загрузчик Windows достаточно умен, и умеет обходить большинство создаваемых разработчиками приложений проблем. Но иногда, как в данном случае, не справляется.
О другой подобной ошибке можно почитать в статье <a href="http://blogs.msdn.com/b/oldnewthing/archive/2010/01/15/9948740.aspx" target="_blank">How you might be loading a DLL during DLL_PROCESS_DETACH without even realizing it</a>.
<br/><br/>
Эта же ошибка присутствует и в других модулях, сделанных на основе <code>HTMLHelpViewer</code>, например в описанном в статье <a href="http://www.gunsmoker.ru/2011/02/delphi.html" target="_blank">Как использовать справку в программах Delphi</a> модуле <code>HTMLHelpViewerEx</code>.
<br/><br/>
С богатой историей ошибки можно ознакомится по ссылкам ниже:
<br/><br/>
<table border="1" cellpadding="4" cellspacing="0">
<tr>
<th>Номер бага</th>
<th>Дата добавления</th>
<th>Описание</th>
</tr>
<tr>
<td><a href="http://qc.embarcadero.com/wc/qcmain.aspx?d=23172" target="_blank">QC23172</a></td>
<td>06.01.2006</td>
<td>Unload HTMLHelpViewer</td>
</tr>
<tr>
<td><a href="http://qc.embarcadero.com/wc/qcmain.aspx?d=36810" target="_blank">QC36810</a></td>
<td>20.11.2006</td>
<td>HTMLHelpViewer hangs dll</td>
</tr>
<tr>
<td><a href="http://qc.embarcadero.com/wc/qcmain.aspx?d=48983" target="_blank">QC48983</a></td>
<td>13.07.2007</td>
<td>HTMLHelpViewer causing problems in DLLs</td>
</tr>
<tr>
<td><a href="http://qc.embarcadero.com/wc/qcmain.aspx?d=67463" target="_blank">QC67463</a></td>
<td>02.10.2008</td>
<td>On unload vcl bpl application stopped</td>
</tr>
<tr>
<td><a href="http://qc.embarcadero.com/wc/qcmain.aspx?d=68973" target="_blank">QC68973</a></td>
<td>15.11.2008</td>
<td>Adding HTMLHelpViewer prevents Excel from shutting down</td>
</tr>
<tr>
<td><a href="http://qc.embarcadero.com/wc/qcmain.aspx?d=78998" target="_blank">QC78998</a></td>
<td>26.10.2009</td>
<td>HTMLHelpViewer Initialization/Finalization bug</td>
</tr>
<tr>
<td><a href="http://qc.embarcadero.com/wc/qcmain.aspx?d=89616" target="_blank">QC89616</a></td>
<td>12.11.2010</td>
<td>HTMLHelpViewer.pas bug in initialization finalization methods</td>
</tr>
<tr>
<td><a href="http://qc.embarcadero.com/wc/qcmain.aspx?d=102083" target="_blank">QC102083</a></td>
<td>24.12.2011</td>
<td>HTMLHelpViewer.pas critical issue in initialization and finalization methods</td>
</tr>
</table>
<br>
Исправление ошибки, сделанное в Update 4 для Delphi XE2, состоит из одной строчки кода:
<pre style='color:#000000;background:#ffffff;'><html><body style='color:#000000; background:#ffffff; '>
<span style='color:#007997; '>--- Vcl.HtmlHelpViewer.pas_dxe2u3</span>
<span style='color:#007997; '>+++ Vcl.HtmlHelpViewer.pas_dxe2u4</span>
<span style='color:#e34adc; '>@@ -301,6 +301,7 @@</span>
procedure THtmlHelpViewer.SoftShutDown;
begin
<span style='color:#0000e6; '>+ if FInitialized then</span>
HtmlHelp(<span style='color:#008c00; '>0</span>, nil, HH_CLOSE_ALL, <span style='color:#008c00; '>0</span>);
end;
</pre>
<h3>Решаем проблему в DLL</h3>
<br>
Если вы используете версию Delphi младше XE2, то просто скопируйте файл <code>HTMLHelpViewer.pas</code> из папки установки Delphi в папку с исходным кодом приложения, добавьте строку <code>if FInitialized then</code> в процедуру <code>THtmlHelpViewer.SoftShutDown</code>, как показано выше, и добавьте этот модуль в проект.
<br><br>
<h3>Решаем проблему в EXE</h3>
<br>
Описанная ошибка не страшна для приложения, так как загрузка-выгрузка <code>HHCTRL.OCX</code> выполняется в ходе обычного выполнения программы, когда загрузчик операционной системы свободен от блокировок.
<br><br>
Решать проблему в приложении может понадобиться, если вы загружаете DLL, собранную в Delphi с модулем <code>HTMLHelpViewer</code>, содержащим описанную ошибку, и у вас нет возможности исправить эту DLL.
<br>
В этом случае необходимо добиться, чтобы библиотека <code>HHCTRL.OCX</code> была загружена во время выгрузки проблемной DLL. Это можно сделать, добавив к проекту такой модуль:
<br>
<pre style='color:#000000;background:#ffffff;'><html><body style='color:#000000; background:#ffffff; '>
<span style='color:#575757; font-weight:bold; '>unit</span> VCLPatch<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>interface</span>
<span style='color:#575757; font-weight:bold; '>implementation</span>
<span style='color:#575757; font-weight:bold; '>uses</span>
Windows<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>var</span>
HtmlHelpModule<span style='color:#555555; '>:</span> HModule<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>initialization</span>
HtmlHelpModule <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> LoadLibrary<span style='color:#555555; '>(</span><span style='color:#4c4c4c; '>'hhctrl.ocx'</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>finalization</span>
FreeLibrary<span style='color:#555555; '>(</span>HtmlHelpModule<span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>end</span><span style='color:#555555; '>.</span>
</pre>
Модуль нужно поместить в секцию <code>uses</code> проекта как можно раньше, чтобы библиотека <code>HHCTRL.OCX</code> оставалась загруженной во время выгрузки проблемной DLL.
<br><br>
Об общих вопросах использования справки в Delphi можно почитать в уже упоминавшейся выше статье <a href="http://www.gunsmoker.ru/2011/02/delphi.html" target="_blank">Как использовать справку в программах Delphi</a>.
<br><br>Chaahttp://www.blogger.com/profile/14387721107858333063noreply@blogger.com0tag:blogger.com,1999:blog-4946864412455003694.post-37163187491352481432013-04-26T08:00:00.000+06:002013-04-26T08:23:01.860+06:00WM_PAINT во время ожидания вызова COM-сервераВ новых версиях Windows, начиная с Vista, приложение получает сообщение <code>WM_PAINT</code> во время ожидания вызова COM-сервера.
Как написано в статье «<a href="http://blogs.msdn.com/b/yvesdolc/archive/2009/08/06/do-you-receive-wm-paint-when-waiting-for-a-com-call-to-return.aspx" target="_blank">Do you receive WM_PAINT when waiting for a COM call to return?</a>», основной причиной для этого стала необходимость в правильной работе UAC (контроль учётных записей пользователей).
Если приложение не рассчитано на такое поведение, то это может привести к ошибкам.<br />
<br />
<a name='more'></a><br />
Например, я в своих приложениях часто использую компонент <a href="http://code.google.com/p/virtual-treeview/" target="_blank">TVirtualStringTree</a>, который запрашивает данные именно во время обработки сообщения <code>WM_PAINT</code>.
Получать все данные с удаленного сервера нерационально и довольно долго, компонент TVirtualStringTree для того и используется, чтобы получался только необходимый минимум данных, и тогда, когда в них возникнет необходимость.
Проблема с моим кодом была в том, что одно соединение использовалось для работы с несколькими базами данных, и для получения данных выполнялось два действия: подключение к базе данных и выборка данных из нее.
При этом во время первого вызова ConnectToDB приходило сообщение <code>WM_PAINT</code> и выполнялся вызов ConnectToDB уже для подключения к другой базе. Во второй раз данные запрашивались из неправильной базы данных и возникала ошибка.
<br />
<br />
<a href="http://4.bp.blogspot.com/-9soW_NdBtQA/USH101oFy_I/AAAAAAAAABk/f9_qXrc8je8/s1600/newwmpaintdisp.png" imageanchor="1"><img border="0" src="http://4.bp.blogspot.com/-9soW_NdBtQA/USH101oFy_I/AAAAAAAAABk/f9_qXrc8je8/s1600/newwmpaintdisp.png" /></a>
<br />
<br />
Есть несколько вариантов решения проблемы:
<br />
<ol>
<li>Не обращаться к серверу из обработчика <code>WM_PAINT</code>. Помечать флагами узлы дерева, которые необходимо загрузить и отправлять специальное сообщение с помощью <code>PostMessage</code>, чтобы данные были загружены позже.</li>
<li>Официальный способ. Используя <a href="http://www.microsoft.com/downloads/details.aspx?FamilyId=24DA89E9-B581-47B0-B45E-492DD6DA2971&displaylang=en" target="_blank">Application Compatibility Toolkit</a> создать для приложения SDB-файл с флагом <code>DisableNewWMPAINTDispatchInOLE</code> и при установке приложения регистрировать SDB-файл в системе. Как это сделать написано в упомянутой выше статье «<a href="http://blogs.msdn.com/b/yvesdolc/archive/2009/08/06/do-you-receive-wm-paint-when-waiting-for-a-com-call-to-return.aspx" target="_blank">Do you receive WM_PAINT when waiting for a COM call to return?</a>».</li>
<li>Приложение может самостоятельно при запуске установить флаг <code>DisableNewWMPAINTDispatchInOLE</code>. Проблема здесь только в том, что это недокументированная возможность и она может измениться в следующих версиях Winodws.</li>
</ol>
Первый вариант не подошел из-за высокой трудоемкости для встраивания в готовое приложение. Второй вариант неудобен тем, что нужно устанавливать SDB-файл, а очень хотелось, чтобы приложение можно было просто скопировать, без установки. После рассмотрения всех вариантов я для себя решил использовать третий вариант, как наиболее простой для реализации в моих условиях. В других условиях, наверное, правильным будет использовать второй вариант.
<br />
<br />
Скачав и установив <a href="http://msdn.microsoft.com/en-us/windows/hardware/gg463028.aspx" target="_blank">Windows Symbol Packages</a>, загрузил приложение в отладчик. В недрах ole32.dll был найден код функции _DisableNewWM_PAINTDispatch.
<br />
<pre style="background: #ffffff; color: black;"><span style="color: #575757; font-weight: bold;">function</span> _DisableNewWM_PAINTDispatch<span style="color: #555555;">:</span> <span style="color: #575757; font-weight: bold;">LongBool</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">asm</span>
<span style="color: #575757; font-weight: bold;">mov</span> <span style="color: #2a2a2a;">eax</span><span style="color: #555555;">,</span><span style="color: #575757; font-weight: bold;">dword</span> <span style="color: #575757; font-weight: bold;">ptr</span> <span style="color: #2a2a2a;">fs</span><span style="color: #555555;">:</span><span style="color: #555555;">[</span><span style="color: #2a2a2a;">00000018h</span><span style="color: #555555;">]</span><span style="color: dimgrey;">// Thread Environment Block</span>
<span style="color: #575757; font-weight: bold;">mov</span> <span style="color: #2a2a2a;">eax</span><span style="color: #555555;">,</span><span style="color: #575757; font-weight: bold;">dword</span> <span style="color: #575757; font-weight: bold;">ptr</span> <span style="color: #555555;">[</span><span style="color: #2a2a2a;">eax</span><span style="color: #555555;">+</span><span style="color: #2a2a2a;">30h</span><span style="color: #555555;">]</span> <span style="color: dimgrey;">// Process Environment Block</span>
<span style="color: #575757; font-weight: bold;">mov</span> <span style="color: #2a2a2a;">eax</span><span style="color: #555555;">,</span><span style="color: #575757; font-weight: bold;">dword</span> <span style="color: #575757; font-weight: bold;">ptr</span> <span style="color: #555555;">[</span><span style="color: #2a2a2a;">eax</span><span style="color: #555555;">+</span><span style="color: #2a2a2a;">1D8h</span><span style="color: #555555;">]</span> <span style="color: dimgrey;">// AppCompatFlags</span>
<span style="color: #575757; font-weight: bold;">and</span> <span style="color: #2a2a2a;">eax</span><span style="color: #555555;">,</span><span style="color: #2a2a2a;">100000h</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
</pre>
Немного изменив код, получаем необходимую процедуру.
<br />
<pre style="background: #ffffff; color: black;"><span style="color: #575757; font-weight: bold;">procedure</span> DisableNewWMPaintDispatch<span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">asm</span>
<span style="color: #575757; font-weight: bold;">mov</span> <span style="color: #2a2a2a;">eax</span><span style="color: #555555;">,</span><span style="color: #575757; font-weight: bold;">dword</span> <span style="color: #575757; font-weight: bold;">ptr</span> <span style="color: #2a2a2a;">fs</span><span style="color: #555555;">:</span><span style="color: #555555;">[</span><span style="color: #2a2a2a;">00000018h</span><span style="color: #555555;">]</span>
<span style="color: #575757; font-weight: bold;">mov</span> <span style="color: #2a2a2a;">eax</span><span style="color: #555555;">,</span><span style="color: #575757; font-weight: bold;">dword</span> <span style="color: #575757; font-weight: bold;">ptr</span> <span style="color: #555555;">[</span><span style="color: #2a2a2a;">eax</span><span style="color: #555555;">+</span><span style="color: #2a2a2a;">30h</span><span style="color: #555555;">]</span>
<span style="color: #575757; font-weight: bold;">mov</span> <span style="color: #2a2a2a;">edx</span><span style="color: #555555;">,</span><span style="color: #575757; font-weight: bold;">dword</span> <span style="color: #575757; font-weight: bold;">ptr</span> <span style="color: #555555;">[</span><span style="color: #2a2a2a;">eax</span><span style="color: #555555;">+</span><span style="color: #2a2a2a;">1D8h</span><span style="color: #555555;">]</span>
<span style="color: #575757; font-weight: bold;">or</span> <span style="color: #2a2a2a;">edx</span><span style="color: #555555;">,</span><span style="color: #2a2a2a;">100000h</span>
<span style="color: #575757; font-weight: bold;">mov</span> <span style="color: #575757; font-weight: bold;">dword</span> <span style="color: #575757; font-weight: bold;">ptr</span> <span style="color: #555555;">[</span><span style="color: #2a2a2a;">eax</span><span style="color: #555555;">+</span><span style="color: #2a2a2a;">1D8h</span><span style="color: #555555;">]</span><span style="color: #555555;">,</span><span style="color: #2a2a2a;">edx</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">initialization</span>
<span style="color: #575757; font-weight: bold;">if</span> CheckWin32Version<span style="color: #555555;">(</span><span style="color: #2e2e2e;">6</span><span style="color: #555555;">)</span> <span style="color: #575757; font-weight: bold;">then</span>
DisableNewWMPaintDispatch<span style="color: #555555;">(</span><span style="color: #555555;">)</span><span style="color: #555555;">;</span>
</pre>
<br />
Для получения указателя на информацию о потоке можно было бы использовать функцию <code>NtCurrentTeb</code> вместо <code>mov eax,dword ptr fs:[18h]</code>, но это вряд ли добавит совместимости с будущими версиями Windows, так как структуры TEB и PEB не документированы, и в Windows SDK про них явно написано:
«The PEB and TEB structures are subject to changes between Windows releases, thus the fields offsets may change as well as the Reserved fields».
<br />
<br />
Код проверялся в Windows 7 и Windows 8. Дополнительно можно почитать комментарии к статье MSDN «<a href="http://msdn.microsoft.com/en-us/library/windows/desktop/ms694352(v=vs.85).aspx" target="_blank">IMessageFilter::MessagePending method (COM)</a>» и топик «<a href="http://social.msdn.microsoft.com/forums/en-US/windowsgeneraldevelopmentissues/thread/5a28a9f5-5711-4efa-843e-e98927fa2b92/" target="_blank">DCOM in Vista specifically processing WM_PAINT messages</a>» в форумах MSDN.
Chaahttp://www.blogger.com/profile/14387721107858333063noreply@blogger.com1tag:blogger.com,1999:blog-4946864412455003694.post-34325588082618984252013-03-01T08:30:00.000+06:002013-03-29T09:07:15.238+06:00Запись в качестве ключа TDictionaryВ данной статье мы рассмотрим один тонкий момент при использования записи в качестве ключа для <a href="http://docwiki.embarcadero.com/Libraries/en/System.Generics.Collections.TDictionary" target="_blank">TDictionary</a>.
<br /><br />
Следуя общепринятым практикам разработки ПО, попробуем реализовать шаблон проектирования <a href="http://martinfowler.com/eaaCatalog/identityMap.html" target="_blank">Коллекция объектов (Identity Map)</a>, с помощью <code>TDictionary</code>. В качестве ключа будем использовать идентификатор сущности, а в качестве значения объект сущности.
Идентификаторы сущностей могут быть как простые, так и составные. Для составных будем использовать запись — идентификатор должен быть <a href="http://martinfowler.com/eaaCatalog/valueObject.html" target="_blank">объектом-значением (Value Object)</a>. В примере мы создадим коллекцию объектов «Пользователь» для ЛОЦМАН:PLM.
<a name='more'></a><br /><br />
В Лоцмане есть два вида пользователей: пользователи собственно PLM-системы и пользователи Workflow. Оба типа пользователей имеют одинаковые атрибуты. С точки зрения большинства компонентов пользовательского интерфейса между ними нет никакой разницы. Знать вид пользователя нужно только при вызове методов сервера приложений, при этом, при необходимости, для пользователя одного вида можно получить такого же пользователя другого вида. Поэтому мы реализуем одну сущность «Пользователь» и для PLM-системы, и для Workflow. У сущности будет составной идентификатор, в котором, кроме идентификатора пользователя ЛОЦМАН:PLM, указан вид пользователя. Получается примерно такой код:
<br />
<pre style='color:#000000;background:#ffffff;'><span style='color:#575757; font-weight:bold; '>type</span>
TUserID <span style='color:#555555; '>=</span> <span style='color:#575757; font-weight:bold; '>record</span>
<span style='color:#575757; font-weight:bold; '>private</span>
FUserID<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>Integer</span><span style='color:#555555; '>;</span>
FIsWFUser<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>Boolean</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>public</span>
<span style='color:#575757; font-weight:bold; '>constructor</span> Create<span style='color:#555555; '>(</span>AUserID<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>Integer</span><span style='color:#555555; '>;</span>
AIsWFUser<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>Boolean</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>property</span> UserID<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>Integer</span> <span style='color:#575757; font-weight:bold; '>read</span> FUserID<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>property</span> IsWFUser<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>Boolean</span> <span style='color:#575757; font-weight:bold; '>read</span> FIsWFUser<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>end</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>constructor</span> TUserID<span style='color:#555555; '>.</span>Create<span style='color:#555555; '>(</span>AUserID<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>Integer</span><span style='color:#555555; '>;</span>
AIsWFUser<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>Boolean</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>begin</span>
FUserID <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> AUserID<span style='color:#555555; '>;</span>
FIsWFUser <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> AIsWFUser<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>end</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>type</span>
TUser <span style='color:#555555; '>=</span> <span style='color:#575757; font-weight:bold; '>class</span><span style='color:#555555; '>(</span>TEntityBase<span style='color:#555555; '>)</span>
<span style='color:#575757; font-weight:bold; '>private</span>
FUserID<span style='color:#555555; '>:</span> TUserID<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>public</span>
<span style='color:#575757; font-weight:bold; '>property</span> UserID<span style='color:#555555; '>:</span> TUserID <span style='color:#575757; font-weight:bold; '>read</span> FUserID<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>end</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>var</span>
UserMap<span style='color:#555555; '>:</span> TObjectDictionary<span style='color:#555555; '><</span>TUserID<span style='color:#555555; '>,</span> TUser<span style='color:#555555; '>></span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>begin</span>
UserMap <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> TObjectDictionary<span style='color:#555555; '><</span>TUserID<span style='color:#555555; '>,</span> TUser<span style='color:#555555; '>></span><span style='color:#555555; '>.</span>Create<span style='color:#555555; '>(</span>
<span style='color:#555555; '>[</span>doOwnsValues<span style='color:#555555; '>]</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>end</span><span style='color:#555555; '>.</span>
</pre>
<br />
Попробуем добавить в коллекцию один объект и затем получить его:
<br />
<pre style='color:#000000;background:#ffffff;'><span style='color:#575757; font-weight:bold; '>procedure</span> Test<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>var</span>
UserID1<span style='color:#555555; '>:</span> TUserID<span style='color:#555555; '>;</span>
UserID2<span style='color:#555555; '>:</span> TUserID<span style='color:#555555; '>;</span>
Entity<span style='color:#555555; '>:</span> TUser<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>begin</span>
UserID1 <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> TUserID<span style='color:#555555; '>.</span>Create<span style='color:#555555; '>(</span><span style='color:#2e2e2e; '>1</span><span style='color:#555555; '>,</span> <span style='color:#404040; '>True</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
UserMap<span style='color:#555555; '>.</span>Add<span style='color:#555555; '>(</span>UserID1<span style='color:#555555; '>,</span> TUser<span style='color:#555555; '>.</span>Create<span style='color:#555555; '>(</span><span style='color:#555555; '>)</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
UserID2 <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> TUserID<span style='color:#555555; '>.</span>Create<span style='color:#555555; '>(</span><span style='color:#2e2e2e; '>1</span><span style='color:#555555; '>,</span> <span style='color:#404040; '>True</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
Entity <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> UserMap<span style='color:#555555; '>.</span>Items<span style='color:#555555; '>[</span>UserID2<span style='color:#555555; '>]</span><span style='color:#555555; '>;</span>
<span style='color:#696969; '>// EListError: Item not found</span>
<span style='color:#575757; font-weight:bold; '>end</span><span style='color:#555555; '>;</span>
</pre>
<br />
Как это ни удивительно, но такой простой код не работает. При попытке получить объект из коллекции, выбрасывается исключение <code>EListError</code> с сообщением «Item not found». В чем дело?
<br /><br />
Причина кроется в <a href="http://docwiki.embarcadero.com/RADStudio/en/Align_fields_(Delphi)" target="_blank">выравнивании полей записи</a>, из-за чего размер записи <code>TUserID</code> составляет 8 байт, в то время как используются только 5 байт, а в 3-х оставшихся хранится мусор. Используемый по умолчанию <code>TComparer<T></code> сравнивает записи с помощью <nobr><code>CompareMem(@Left, @Right, SizeOf(TUserID))</code></nobr>, из-за чего в сравнение попадает мусор.
<br /><br />
Решений проблемы, как обычно, может быть несколько:
<ol>
<li>Вместо используемого по умолчанию <code>TComparer<T></code> написать свой класс,
который будет корректно сравнивать записи, не обращая внимания на мусор внутри.</li>
<li>Использовать в записи типы данных, размер которых кратен используемому выравниванию. В этом случае в записи не будет пустых мест. Например, заменить <code>Boolean</code> на <code>LongBool</code>.</li>
<li>Убрать случайные значения из неиспользуемых байт записи, например, заполнив их нулями.</li>
<li>Использовать для записи ключевое слово <code>packed</code>. В этом случае для записи
не будет использоваться выравнивание полей. Несмотря на свою простоту, этот способ
самый неэффективный. Например, в описанном случае размер записи будет 5 байт.
Записи будут размещены в массиве друг за другом. Для получения поля <code>UserID</code>
из второй записи процессор загрузит в регистр 4 байта по смещению 4 в массиве, выделит из них последний байт, затем загрузит в регистр 4 байта по смещению 8, выделит из них первые 3 байта, затем объединит полученные байты в одно значение. При записи все будет еще хуже.
</li>
</ol>
Я предпочитаю использовать третий вариант, как более простой и надежный. К тому же хранить мусор внутри записи — это некрасиво, даже если он нигде и не будет использоваться.
<br /><br />
Итак, общие правила:
<ol>
<li>Для записей, создаваемых на стеке, всегда вызывать <a href="http://docwiki.embarcadero.com/Libraries/en/System.FillChar" target="_blank">FillChar</a>.</li>
<li>Для записей, под которые память выделяется динамически, использовать <a href="http://docwiki.embarcadero.com/Libraries/en/System.AllocMem" target="_blank">AllocMem</a>, который выделяет память, заполненную нулями. Если используется <a href="http://docwiki.embarcadero.com/Libraries/en/System.New" target="_blank">New</a>, то вызывать <a href="http://docwiki.embarcadero.com/Libraries/en/System.FillChar" target="_blank">FillChar</a>.</li>
<li>Записи, используемые в качестве полей классов, заполняются нулями при создании объектов.</li>
</ol>
Исправляем тестовый пример:
<br />
<pre style='color:#000000;background:#ffffff;'><span style='color:#575757; font-weight:bold; '>constructor</span> TUserID<span style='color:#555555; '>.</span>Create<span style='color:#555555; '>(</span>AUserID<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>Integer</span><span style='color:#555555; '>;</span>
AIsWFUser<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>Boolean</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>begin</span>
FillChar<span style='color:#555555; '>(</span><span style='color:#575757; font-weight:bold; '>Self</span><span style='color:#555555; '>,</span> SizeOf<span style='color:#555555; '>(</span><span style='color:#575757; font-weight:bold; '>Self</span><span style='color:#555555; '>)</span><span style='color:#555555; '>,</span> <span style='color:#2e2e2e; '>0</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
FUserID <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> AUserID<span style='color:#555555; '>;</span>
FIsWFUser <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> AIsWFUser<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>end</span><span style='color:#555555; '>;</span>
</pre>
<br />
Код в статье проверялся в Delphi XE. Для старых версий Delphi все написанное так же актуально, хоть в них и нет конструкторов записей, мусор из стека все так же будет попадать в неиспользуемые байты.
Chaahttp://www.blogger.com/profile/14387721107858333063noreply@blogger.com5tag:blogger.com,1999:blog-4946864412455003694.post-17582308924799598232013-02-23T10:00:00.000+06:002013-02-23T10:00:02.429+06:00Всплывающие подсказки и QC85705В VCL есть незакрытый баг <a href="http://qc.embarcadero.com/wc/qcmain.aspx?d=85705" target="_blank">QC85705</a>. Он был открыт в июне 2010 года для Delphi 2010, но присутствует и в старых, и в новых версиях.
<br /><br />
В ходе разработки одного из <a href="http://achechulin.blogspot.ru/2012/04/plugin-sample.html">плагинов для ЛОЦМАН:PLM</a> я столкнулся с ошибкой «Access violation at address...» при выгрузке плагина. Ошибка возникала не каждый раз, а только иногда, и приводила к краху всего приложения.
<a name='more'></a><br /><br />
При запуске под отладчиком, в качестве адреса ошибки показывалась пустая область памяти, не занятая ни кодом, ни данными. Судя по всему, что-то портило память. Но с другой стороны никакого сложного кода в плагине не было, использовались давно проверенные и отлаженные решения.
<br /><br />
<div style="text-align: center;">
<a href="http://4.bp.blogspot.com/-qJx23O--ds4/USRGJ06kvYI/AAAAAAAAAB0/4wj6gwfk-a4/s1600/DebugWindow0.png" imageanchor="1"><img border="0" height="306" src="http://4.bp.blogspot.com/-qJx23O--ds4/USRGJ06kvYI/AAAAAAAAAB0/4wj6gwfk-a4/s400/DebugWindow0.png" width="400" /></a>
</div>
<br />
После некоторого времени размышлений, я обратил внимание на адрес загрузки плагина <code>$03500000</code> — он был немного меньше адреса возникновения ошибки <code>$035AB036</code>. Стало ясно, что после выгрузки DLL плагина остался выполняться поток, а так как код DLL уже выгружен, то возникает ошибка доступа к памяти. Добившись повторной загрузки плагина по адресу <code>$03500000</code>, я посмотрел в отладчике, что же находится по адресу <code>$035AB036</code>. В нормально режиме там находился код функции <code>HintMouseThread</code> из модуля <code>Forms.pas</code>.
<br /><br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://3.bp.blogspot.com/-3Ox8qnIJ8ko/USRHONP25wI/AAAAAAAAAB8/T9jddQ11uwQ/s1600/DebugWindow1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="307" src="http://3.bp.blogspot.com/-3Ox8qnIJ8ko/USRHONP25wI/AAAAAAAAAB8/T9jddQ11uwQ/s400/DebugWindow1.png" width="400" /></a></div>
<br />
К сожалению, на тот момент, времени дальше разбираться с ошибкой не было, и проблема была решена максимально просто: <code>Application.ShowHint := False</code>.
<br /><br />
<h4>Второй подход</h4>
<br />
При эксплуатации приложения выяснилось, что обходится без подсказок в принципе можно, но с подсказками было бы гораздо лучше. Все-таки всплывающие подсказки были придуманы не зря, и часто помогают пользователям приложения.
Поэтому было решено вернуться к проблеме и найти какой-нибудь способ ее решения.
<br /><br />
Баг, судя по всему, был либо в VCL, либо в компонентах Virtual Trees, так как с данной ошибкой я столкнулся только при работе с Virtual Trees. При работе с другими компонентами эта ошибка не проявляется. Поэтому я попутно отправил <a href="http://code.google.com/p/virtual-treeview/issues/detail?id=229" target="_blank">Issue 229</a> в Issue Tracker компонентов Virtual Trees, с вопросом, не могут ли они как-нибудь самостоятельно победить этот баг. Но там никто не откликнулся.
<br /><br />
Решено было посмотреть, что по этому поводу пишут в Интернете.
После недолгих поисков нашелся в Quality Central баг <a href="http://qc.embarcadero.com/wc/qcmain.aspx?d=85705" target="_blank">QC85705</a>. Там был описан как раз мой случай — выгрузка DLL и оставшийся поток HintMouseThread. В качестве решения предлагалось исправить код TApplication.ActivateHint, но я решил поступить проще — написать маленькую функцию, которая вызовет <code>UnhookHintHooks</code>. Править VCL это все-таки крайний случай, и если без этого можно обойтись, то лучше поступить именно так. Решением стал такой модуль, включаемый в DLL:
<br />
<pre style='color:#000000;background:#ffffff;'><span style='color:#575757; font-weight:bold; '>unit</span> VCLPatch<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>interface</span>
<span style='color:#575757; font-weight:bold; '>implementation</span>
<span style='color:#575757; font-weight:bold; '>uses</span>
Classes<span style='color:#555555; '>,</span> Controls<span style='color:#555555; '>,</span> Forms<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>type</span>
TApplicationHelper <span style='color:#555555; '>=</span> <span style='color:#575757; font-weight:bold; '>class helper </span><span style='color:#575757; font-weight:bold; '>for</span> TApplication
<span style='color:#575757; font-weight:bold; '>procedure</span> _CancelHint<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>end</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>procedure</span> TApplicationHelper<span style='color:#555555; '>.</span>_CancelHint<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>begin</span>
<span style='color:#575757; font-weight:bold; '>Self</span><span style='color:#555555; '>.</span>FHintControl <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> TControl<span style='color:#555555; '>(</span><span style='color:#2e2e2e; '>1</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>Self</span><span style='color:#555555; '>.</span>CancelHint<span style='color:#555555; '>(</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>end</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>initialization</span>
<span style='color:#575757; font-weight:bold; '>finalization</span>
Application<span style='color:#555555; '>.</span>_CancelHint<span style='color:#555555; '>(</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>end</span><span style='color:#555555; '>.</span>
</pre>
<br />
Код использует <a href="http://docwiki.embarcadero.com/RADStudio/en/Class_and_Record_Helpers" target="_blank">Class Helpers</a> и будет компилироваться только в новых версиях Delphi (начиная с версии 2009).
Chaahttp://www.blogger.com/profile/14387721107858333063noreply@blogger.com0tag:blogger.com,1999:blog-4946864412455003694.post-70053129833827217872012-09-01T09:00:00.000+06:002013-12-24T14:27:51.185+06:00Подключение к серверу приложенийВ данной статье мы рассмотрим самостоятельное подключение к серверу приложений. Из <a href="http://achechulin.blogspot.ru/2012/06/remote-connection-params.html">предыдущей статьи</a> мы знаем, как прочитать параметры подключения из реестра. Теперь мы подключимся к серверу приложений, выберем базу данных и рабочий проект, и сможем читать и изменять объекты базы данных.<br /><br />
<a name='more'></a><br />
Итак, нам необходим удобный способ работы с сервером приложений Лоцман. Основные требования:<br />
<ol>
<li>Несколько частей приложения или плагина, работающие с разными базами данных и разными рабочими проектами, должны использовать одно подключение к серверу приложений. В первой пробной версии я сделал по одному подключению на каждую базу данных, но выяснилось, что каждое подключение забирает лицензию. Чтобы понапрасну не расходовать дорогие лицензии, должно быть только одно подключение. Из этого следует, что одно подключение должно переключаться между разными базами данных и рабочими проектами, и знать, к какой базе данных оно в настоящее время подключено.</li>
<li>Для экономии лицензий необходимо автоматическое отключения от сервера приложений при длительном простое. При этом использующая подключение часть программы или плагин не должны заботиться о восстановлении подключения. При обращении к серверу приложений, если соединение было разорвано, компонент должен заново подключится к серверу приложений, подключиться к нужной базе данных и рабочему проекту.</li>
<li>Код, использующий подключение к серверу приложений, не должен зависеть от способа соединения с сервером, будь то обычное подключение или вызов метода сервера приложений через интерфейс плагина <code>IPluginCall</code>.</li>
<li>Должна быть возможность создать подключение, отдельное от основного (общего для всех). Например, чтобы подключиться к базе данных от имени другого пользователя, или для того, чтобы использовать подключение в отдельном потоке приложения. Для того, чтобы одинаково работать и с общим подключением, и с отдельными подключениями, необходимо обеспечить автоматический подсчет ссылок на подключение, этого проще всего добиться при использовании интерфейса.</li>
<li>Было бы удобно работать с серверами приложений Лоцман и Workflow через одно подключение, добавляя к имени метода префикс, например <code>WF</code> для методов Workflow. К тому же клиентское приложение Лоцмана обеспечивает такую возможность для плагинов (с версии 10).</li>
</ol>
Исходя из изложенных выше требований, работа с подключением будет выглядеть примерно так:<br />
<pre style='color:#000000;background:#ffffff;'><span style='color:#575757; font-weight:bold; '>var</span>
LConnection<span style='color:#555555; '>:</span> IRemoteConnection<span style='color:#555555; '>;</span>
LDataSet<span style='color:#555555; '>:</span> IDataSet<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>begin</span>
LConnection <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> GetLoodsmanConnection<span style='color:#555555; '>(</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
LConnection<span style='color:#555555; '>.</span>CurrentBase <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> <span style='color:#4c4c4c; '>'Демо_Машиностроение'</span><span style='color:#555555; '>;</span>
LConnection<span style='color:#555555; '>.</span>CurrentCheckOutID <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> IdCheckOut<span style='color:#555555; '>;</span>
LDataSet <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> LConnection<span style='color:#555555; '>.</span>GetDataSet<span style='color:#555555; '>(</span><span style='color:#4c4c4c; '>'GetLinkedFast'</span><span style='color:#555555; '>,</span>
<span style='color:#555555; '>[</span>IdVersion<span style='color:#555555; '>,</span> <span style='color:#4c4c4c; '>'Состоит из ...'</span><span style='color:#555555; '>,</span> <span style='color:#404040; '>False</span><span style='color:#555555; '>]</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#555555; '>..</span><span style='color:#555555; '>.</span>
<span style='color:#575757; font-weight:bold; '>end</span>
</pre>
Интерфейс подключения к серверу приложений:<br />
<pre style='color:#000000;background:#ffffff;'>IRemoteConnection <span style='color:#575757; font-weight:bold; '>= interface</span><span style='color:#555555; '>(</span>IInterface<span style='color:#555555; '>)</span>
<span style='color:#555555; '>[</span><span style='color:#4c4c4c; '>'</span>{0FBADFEF-7975-4C8E-8CC8-7D5323187F5F}<span style='color:#4c4c4c; '>'</span><span style='color:#555555; '>]</span>
<span style='color:#575757; font-weight:bold; '>function</span> GetDataSet<span style='color:#555555; '>(</span><span style='color:#575757; font-weight:bold; '>const</span> AName<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>String</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>const</span> AParams<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>array</span> <span style='color:#575757; font-weight:bold; '>of</span> <span style='color:#575757; font-weight:bold; '>Variant</span><span style='color:#555555; '>)</span><span style='color:#555555; '>:</span> IDataSet<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>function</span> RunMethod<span style='color:#555555; '>(</span><span style='color:#575757; font-weight:bold; '>const</span> AName<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>String</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>const</span> AParams<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>array</span> <span style='color:#575757; font-weight:bold; '>of</span> <span style='color:#575757; font-weight:bold; '>Variant</span><span style='color:#555555; '>)</span><span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>Variant</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>property</span> Connected<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>Boolean</span> <span style='color:#575757; font-weight:bold; '>read</span> <span style='color:#555555; '>..</span><span style='color:#555555; '>.</span> <span style='color:#575757; font-weight:bold; '>write</span> <span style='color:#555555; '>..</span><span style='color:#555555; '>.</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>property</span> ConnectedWF<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>Boolean</span> <span style='color:#575757; font-weight:bold; '>read</span> <span style='color:#555555; '>..</span><span style='color:#555555; '>.</span> <span style='color:#575757; font-weight:bold; '>write</span> <span style='color:#555555; '>..</span><span style='color:#555555; '>.</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>property</span> CurrentBase<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>String</span> <span style='color:#575757; font-weight:bold; '>read</span> <span style='color:#555555; '>..</span><span style='color:#555555; '>.</span> <span style='color:#575757; font-weight:bold; '>write</span> <span style='color:#555555; '>..</span><span style='color:#555555; '>.</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>property</span> CurrentCheckOutID<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>Integer</span> <span style='color:#575757; font-weight:bold; '>read</span> <span style='color:#555555; '>..</span><span style='color:#555555; '>.</span> <span style='color:#575757; font-weight:bold; '>write</span> <span style='color:#555555; '>..</span><span style='color:#555555; '>.</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>property</span> ServerName<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>String</span> <span style='color:#575757; font-weight:bold; '>read</span> <span style='color:#555555; '>..</span><span style='color:#555555; '>.</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>property</span> ServerVersion<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>Integer</span> <span style='color:#575757; font-weight:bold; '>read</span> <span style='color:#555555; '>..</span><span style='color:#555555; '>.</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>end</span><span style='color:#555555; '>;</span>
</pre>
Код реализации интерфейса <code>IRemoteConnection</code> достаточно прост, все сложности в нем связаны в основном с тонкостями поддержки одновременно старых и новых версий Лоцмана, в которых существенно изменилась работа с базой Workflow.<br />
<br />
При использовании интерфейса <code>IRemoteConnection</code> нужно помнить о том, что он хранит состояние и может подключаться к разным базам данных и рабочим проектам. Поэтому перед вызовом функций сервера приложений необходимо убедиться, что свойства <code>CurrentBase</code> и <code>CurrentCheckOutID</code> установлены в необходимые значения. Например так:<br />
<pre style='color:#000000;background:#ffffff;'><span style='color:#575757; font-weight:bold; '>type</span>
TMyForm <span style='color:#555555; '>=</span> <span style='color:#575757; font-weight:bold; '>class</span><span style='color:#555555; '>(</span>TForm<span style='color:#555555; '>)</span>
<span style='color:#575757; font-weight:bold; '>procedure</span> BtnOKClick<span style='color:#555555; '>(</span>Sender<span style='color:#555555; '>:</span> TObject<span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>private</span>
FBase<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>String</span><span style='color:#555555; '>;</span>
FCheckOutID<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>Integer</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>function</span> GetConn<span style='color:#555555; '>:</span> IRemoteConnection<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>protected</span>
<span style='color:#575757; font-weight:bold; '>property</span> Base<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>String</span> <span style='color:#575757; font-weight:bold; '>read</span> FBase<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>property</span> CheckOutID<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>Integer</span> <span style='color:#575757; font-weight:bold; '>read</span> FCheckOutID<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>property</span> Connection<span style='color:#555555; '>:</span> IRemoteConnection <span style='color:#575757; font-weight:bold; '>read</span> GetConn<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>public</span>
<span style='color:#575757; font-weight:bold; '>constructor</span> Create<span style='color:#555555; '>(</span>AOwner<span style='color:#555555; '>:</span> TComponent<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>const</span> ABase<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>String</span><span style='color:#555555; '>;</span> ACheckOutID<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>Integer</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>end</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>constructor</span> TMyForm<span style='color:#555555; '>.</span>Create<span style='color:#555555; '>(</span>AOwner<span style='color:#555555; '>:</span> TComponent<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>const</span> ABase<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>String</span><span style='color:#555555; '>;</span> ACheckOutID<span style='color:#555555; '>:</span> <span style='color:#575757; font-weight:bold; '>Integer</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>begin</span>
<span style='color:#575757; font-weight:bold; '>inherited</span> Create<span style='color:#555555; '>(</span>AOwner<span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
FBase <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> ABase<span style='color:#555555; '>;</span>
FCheckOutID <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> ACheckOutID<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>end</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>function</span> TMyForm<span style='color:#555555; '>.</span>GetConn<span style='color:#555555; '>:</span> IRemoteConnection<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>begin</span>
<span style='color:#696969; '>// Перед тем, как вернуть подключение,</span>
<span style='color:#696969; '>// его свойства устанавливаются</span>
<span style='color:#696969; '>// в нужные значения</span>
<span style='color:#575757; font-weight:bold; '>Result</span> <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> GetLoodsmanConnection<span style='color:#555555; '>(</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>Result</span><span style='color:#555555; '>.</span>CurrentBase <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> FBase<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>Result</span><span style='color:#555555; '>.</span>CurrentCheckOutID <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> FCheckOutID<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>end</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>procedure</span> TMyForm<span style='color:#555555; '>.</span>BtnOKClick<span style='color:#555555; '>(</span>Sender<span style='color:#555555; '>:</span> TObject<span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>var</span>
LDataSet<span style='color:#555555; '>:</span> IDataSet<span style='color:#555555; '>;</span>
<span style='color:#575757; font-weight:bold; '>begin</span>
<span style='color:#696969; '>// Мы используем подключение, которое</span>
<span style='color:#696969; '>// соединено с нужной базой данных</span>
<span style='color:#696969; '>// и рабочим проектом</span>
LDataSet <span style='color:#555555; '>:</span><span style='color:#555555; '>=</span> Connection<span style='color:#555555; '>.</span>GetDataSet<span style='color:#555555; '>(</span><span style='color:#4c4c4c; '>'GetLinkedFast'</span><span style='color:#555555; '>,</span>
<span style='color:#555555; '>[</span>IdVersion<span style='color:#555555; '>,</span> <span style='color:#4c4c4c; '>'Состоит из ...'</span><span style='color:#555555; '>,</span> <span style='color:#404040; '>False</span><span style='color:#555555; '>]</span><span style='color:#555555; '>)</span><span style='color:#555555; '>;</span>
<span style='color:#555555; '>..</span><span style='color:#555555; '>.</span>
<span style='color:#575757; font-weight:bold; '>end</span><span style='color:#555555; '>;</span>
</pre>
Основой при создании компонента послужил модуль <a href="http://sites.google.com/site/achechulin/files/RemoteConnect.pas">RemoteConnect.pas</a>, высланный Асконом в примере вызова из автоматической операции WorkFlow функции DLL. Также его код можно найти в файле справки <code>LWFScript_API.chm</code> новых версий Лоцмана. Дополнительные сведения можно почерпнуть из тем форума <a href="http://forum.ascon.ru/index.php/topic,16415.0.html" target="_blank">Выполнение функции напрямую на сервере приложений</a> и <a href="http://forum.ascon.ru/index.php/topic,19108.0.html" target="_blank">Подключение к серверу баз данных</a>.<br />
<br />
Код предназначен для Delphi XE. В остальных версиях Delphi могут понадобиться небольшие правки, например убрать <code>{$SCOPEDENUMS ON}</code> и переименовать модули.<br />
<br />
<a href="http://sites.google.com/site/achechulin/files/RemoteConnect.zip" target="_blank">Исходный код реализации подключения к серверу приложений</a>
<br /><br />
Текущая версия кода на GitHub —
<a href="https://github.com/achechulin/loodsman">github.com/achechulin/loodsman</a>.
<br />
<br />Chaahttp://www.blogger.com/profile/14387721107858333063noreply@blogger.com0tag:blogger.com,1999:blog-4946864412455003694.post-10799999067298181562012-07-05T00:00:00.000+06:002012-07-06T14:48:37.970+06:00Подключение к серверу приложений. Наборы данныхПеред тем, как перейти к вызову методов сервера приложений, нужно разобраться с форматом, в котором сервер приложений возвращает наборы данных.<br />
Сервер может возвращать наборы данных в двух форматах: внутреннее двоичное представление <a href="http://docwiki.embarcadero.com/Libraries/en/Datasnap.DBClient.TClientDataSet" target="_blank">TClientDataSet</a> или XML. По умолчанию используется первый формат, для использования XML необходимо вызвать метод <code>SetFormat</code> сервера приложений с параметром <code>'xml'</code>.
<br /><br />
<a name='more'></a>
Данные в формате XML выглядят следующим образом (результат GetDBProperties):<br />
<br />
<pre><?xml version="1.0" encoding="UTF-16"?>
<ROOT>
<fieldset>
<field Id="c0" Name="_PARAMNAME" DataType="string"/>
<field Id="c1" Name="_PARAMVALUE" DataType="string"/>
<field Id="c2" Name="_PARAMTYPE" DataType="int"/>
</fieldset>
<rowset>
<row c0="Checkunique" c1="1" c2="5" />
<row c0="CryptFiles" c1="0" c2="5" />
<row c0="DefaultDir" c1="...CheckOuts" c2="0" />
<row c0="DefaultFileFolder" c1="...Files" c2="0" />
<row c0="DriveLetter" c1="L" c2="0" />
<row c0="GournalSize" c1="500000" c2="1" />
<row c0="GournalTruncPercent" c1="30" c2="1" />
<row c0="Internal" c1="20070405" c2="0" />
<row c0="LastUpdate" c1="23.11.2007" c2="0" />
<row c0="MaximageSize" c1="-1" c2="1" />
<row c0="MaxTextSize" c1="-1" c2="1" />
<row c0="SP" c1="0" c2="0" />
<row c0="UpdateBuild" c1="8.5.2.78" c2="0" />
<row c0="Version" c1="20061001" c2="0" />
<row c0="VersionGroupQuantity" c1="1" c2="1" />
<row c0="WriteLog" c1="1" c2="5" />
</rowset>
</ROOT>
</pre>
<br />
На форуме Аскона один из участников <a href="http://forum.ascon.ru/index.php/topic,12726.msg133666.html#msg133666" target="_blank">приводил пример</a> использования набора данных в формате XML в .NET (получение из него объекта <code>System.Data.DataSet</code>):<br />
<br />
<pre>AppServer.SetFormat("xml", out lReturn, out lError);
AppServer.ConnectToDBEx(DBName, "", "", out lReturn,
out lError);
Result = AppServer.GetDBProperties(out lReturn,
out lError).ToString();
System.Data.DataSet DS = new DataSet();
System.Xml.XmlDocument XmlDoc = new System.Xml.XmlDocument();
XmlDoc.LoadXml(Result);
DS.ReadXml(new System.Xml.XmlNodeReader(lXmlDoc));
</pre>
<br />
Использование формата XML связано со значительными накладными расходами как на стороне сервера, так и на стороне клиента. Если есть возможность, то эффективнее использовать наборы данных в двоичном формате <a href="http://docwiki.embarcadero.com/Libraries/en/Datasnap.DBClient.TClientDataSet" target="_blank">TClientDataSet</a>. В Delphi сделать это очень просто:<br />
<br />
<pre><span style="color: #575757; font-weight: bold;">var</span>
LDataSet<span style="color: #555555;">:</span> TClientDataSet<span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">begin</span>
LDataSet <span style="color: #555555;">:</span><span style="color: #555555;">=</span> TClientDataSet<span style="color: #555555;">.</span>Create<span style="color: #555555;">(</span><span style="color: #404040;">nil</span><span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">try</span>
LDataSet<span style="color: #555555;">.</span>Data <span style="color: #555555;">:</span><span style="color: #555555;">=</span> AppServer<span style="color: #555555;">.</span>GetDBProperties<span style="color: #555555;">(</span><span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">while</span> <span style="color: #575757; font-weight: bold;">not</span> LDataSet<span style="color: #555555;">.</span>Eof <span style="color: #575757; font-weight: bold;">do</span>
<span style="color: #575757; font-weight: bold;">begin</span>
ParamName <span style="color: #555555;">:</span><span style="color: #555555;">=</span> LDataSet<span style="color: #555555;">.</span>FieldValues<span style="color: #555555;">[</span><span style="color: #4c4c4c;"><span style="color: black;">'</span>_PARAMNAME<span style="color: black;">'</span></span><span style="color: #555555;">]</span><span style="color: #555555;">;</span>
ParamValue <span style="color: #555555;">:</span><span style="color: #555555;">=</span> LDataSet<span style="color: #555555;">.</span>FieldValues<span style="color: #555555;">[</span><span style="color: #4c4c4c;"><span style="color: black;">'</span>_PARAMVALUE<span style="color: black;">'</span></span><span style="color: #555555;">]</span><span style="color: #555555;">;</span>
LDataSet<span style="color: #555555;">.</span>Next<span style="color: #555555;">(</span><span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">finally</span>
LDataSet<span style="color: #555555;">.</span>Free<span style="color: #555555;">(</span><span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
</pre>
<br />
Пожалуй, если бы все было так просто, как написано выше, то не было бы и этой статьи. На самом деле при переходе на новые версии Delphi (Delphi 2009 и более поздние) не обошлось без проблем.<br />
<br />
В Delphi 2009 и более поздних версиях используется кодировка UTF-8 для метаданных, а в предыдущих версия Delphi для метаданных использовалась кодировка ANSI. Так как сервер приложений написан на Delphi 6, он возвращает метаданные в кодировке ANSI, и, чтобы использовать их в новых версиях Delphi, необходимо использовать специальный класс, наследник <a href="http://docwiki.embarcadero.com/Libraries/en/Datasnap.DBClient.TClientDataSet" target="_blank">TClientDataSet</a>. Проблема с кодировкой метаданных обсуждалась в теме <a href="http://forum.ascon.ru/index.php/topic,15373.0.html" target="_blank">Написание плагинов на Delphi 2009</a> на форуме Аскона.<br />
<br />
Итак, нам необходим класс-наследник <a href="http://docwiki.embarcadero.com/Libraries/en/Datasnap.DBClient.TClientDataSet" target="_blank">TClientDataSet</a>, который выполнял бы необходимую обработку метаданных. К тому же, хотелось бы избавиться от постоянного повторения конструкции <code>try...finally</code> при использовании этого класса. Здесь на ум сразу приходит решение создать интерфейс <code>IDataSet</code> для работы с набором данных, и функцию, которая бы возвращала этот интерфейс. Заглядывая немного вперед, мы увидим и еще один плюс данного решения: если с нашей реализацией все-таки возникнут проблемы, то с минимальными переделками мы сможем вынести реализацию этого интерфейса в DLL, написанную на Delphi 6, с точно таким же <a href="http://docwiki.embarcadero.com/Libraries/en/Datasnap.DBClient.TClientDataSet" target="_blank">TClientDataSet</a>, что и у сервера приложений.<br />
<br />
И еще подталкивает нас в сторону использования <code>IDataSet</code> тот факт, что такой интерфейс уже описан в библиотеки типов клиентского приложения Лоцмана, и используется в плагинах. Отсюда следует и еще одно преимущество: возможность написания такого кода, который будет без изменения работать как внутри плагина, так и в отдельном клиентском приложении.<br />
<br />
Исходный код реализации <code>IDataSet</code> достаточно прост, все методы интерфейса соответствуют таковым у <a href="http://docwiki.embarcadero.com/Libraries/en/Datasnap.DBClient.TClientDataSet" target="_blank">TClientDataSet</a>. Единственное отличие в том, что в реализацию свойства <code>IDataSet.FieldValue</code> добавлена небольшая оптимизация, укоряющая поиск поля по имени. Подробнее можно почитать в статье <a href="http://delphi.about.com/od/database/ss/faster-fieldbyname-delphi-database.htm" target="_blank">How Fast Can You FieldByName?</a> (на английском).<br />
<br />
Код предназначен для Delphi версий 2009-XE. В XE2 необходимы небольшие правки, связанные с тем, что изменились имена модулей. Если убрать <code>TCompatDataSet</code>, то код можно использовать и в более старых версиях Delphi.<br />
<br />
<a href="http://sites.google.com/site/achechulin/files/DataSet.zip" target="_blank">Исходный код реализации IDataSet</a>.<br />
<br />Chaahttp://www.blogger.com/profile/14387721107858333063noreply@blogger.com0tag:blogger.com,1999:blog-4946864412455003694.post-38458261643893038692012-06-07T08:00:00.000+06:002012-06-07T14:03:34.414+06:00Подключение к серверу приложений. Параметры подключенияВ каких случаях нам понадобится самостоятельно подключаться к серверу приложений?<br />
<br />
Во-первых, если вы пишете свое приложение для работы с Лоцман.<br />
<br />
Во-вторых, если вы пишете плагин и необходимо работать с WorkFlow из клиентов версии меньше 11, или когда необходимо выполнить какие-либо действия от имени пользователя, отличного от того, который запустил клиент.<br />
<br />
Для подключения к серверу приложений необходимы следующие данные:<br />
<ol>
<li>Тип соединения: DCOM, сокет-соединение или веб-соединение. Работа с сервером приложений будет осуществляться с помощью классов <a href="http://docwiki.embarcadero.com/Libraries/en/Datasnap.Win.MConnect.TDCOMConnection" target="_blank">TDCOMConnection</a>, <a href="http://docwiki.embarcadero.com/Libraries/XE2/en/Datasnap.Win.SConnect.TSocketConnection" target="_blank">TSocketConnetion</a> или <a href="http://docwiki.embarcadero.com/Libraries/XE2/en/Datasnap.Win.SConnect.TWebConnection" target="_blank">TWebConnection</a> соответственно.</li>
<li>Имя сервера, порт для сокет-соединения,
имя, пароль пользователя и имя прокси-сервера для веб-соединения.</li>
<li>Имя базы данных.</li>
<li>Способ аутентификации в базе данных: аутентификация средствами Windows или
аутентификация средствами SQL-сервера.</li>
<li>Имя и пароль пользователя для аутентификации средствами SQL-сервера.</li>
</ol>
В данной статье мы рассмотрим, где и как хранит параметры подключения клиентский модуль Лоцман. В представленной реализации подключения к серверу приложений будут использоваться те же самые параметры подключения.<br />
<br />
<a name='more'></a><br />
Параметры подключения к серверу приложений хранятся в реестре, ими можно достаточно просто управлять администраторам предприятия, например, с помощью групповых политик.<br />
<br />
<h4>
Сервер приложений</h4>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://1.bp.blogspot.com/-AFI5YCbQEO8/T82mQc6UInI/AAAAAAAAAA8/xIZthKh4yms/s1600/loodsman-sp.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-AFI5YCbQEO8/T82mQc6UInI/AAAAAAAAAA8/xIZthKh4yms/s1600/loodsman-sp.png" /></a></div>
<br />
Список серверов приложений хранится в реестре, в ветке <code>HKCU\Software\ASCON\Loodsman</code>, в одном из значений: <code>SP</code>, <code>ClientSP</code> или <code>WorkFlowSP</code>. Если установлена галочке «Использовать общий список серверов», то в значении с именем <code>SP</code>. Если галочка не установлена, то в значении <code>ClientSP</code> для выбранного «Клиент» в списке «Приложение», или в значении <code>WorkFlowSP</code> для выбранного «WorkFlow».
<br />
Параметры подключения к разным серверам хранятся в виде строки с разделителем <code>;</code> (точка с запятой).<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://4.bp.blogspot.com/-JZBtKl1XXZw/T82mPj1HaEI/AAAAAAAAAA4/edfO3Sz4yF4/s1600/loodsman-conn.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-JZBtKl1XXZw/T82mPj1HaEI/AAAAAAAAAA4/edfO3Sz4yF4/s1600/loodsman-conn.png" /></a></div>
<br />
В строке подключения к серверу может находиться либо просто имя сервера (при этом тип соединения будет DCOM), либо набор <code>имя=значение</code>, разделенный символом <code>|</code>. Имена значений следующие:<br />
<br />
<table border="1" cellpadding="4" cellspacing="0"><tbody>
<tr><th>Имя параметра</th><th>Описание</th></tr>
<tr><td>DisplayName</td><td>Название подключения</td></tr>
<tr><td>ConnectionType</td><td>Тип соединения:<br />
0 — DCOM<br />
1 — сокет-соединения<br />
2 — веб-соединение</td></tr>
<tr><td>Host</td><td>IP-адрес для сокет-соединения, URL для веб-соединения</td></tr>
<tr><td>Port</td><td>Порт сокет-сервера, по умолчанию 4804</td></tr>
<tr><td>Proxy</td><td>Прокси-сервер для веб-соединения</td></tr>
<tr><td>User</td><td>Имя пользователя для веб-соединения</td></tr>
<tr><td>Password</td><td>Пароль для веб-соединения</td></tr>
</tbody></table>
<br />
Например, строка подключения к сокет-серверу выглядит примерно так:<br />
<code>DisplayName=192.168.0.1:4801|ConnectionType=1|Host=192.168.0.1|Port=4801</code><br />
<br />
При установке соединения необходимо подключаться к серверам из списка по-порядку до тех пор, пока не удастся установить соединение.<br />
<br />
Чтение списка серверов приложений в виде кода:<br />
<br />
<pre style="background-color: white; background-position: initial initial; background-repeat: initial initial;"><span style="color: #575757; font-weight: bold;">procedure</span><span style="background-color: white;"> GetAppServerList</span><span style="color: #555555;">(</span><span style="color: #575757; font-weight: bold;">const</span><span style="background-color: white;"> AWorkflow</span><span style="color: #555555;">:</span><span style="background-color: white;"> </span><span style="color: #575757; font-weight: bold;">Boolean</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">const</span> AList<span style="color: #555555;">:</span> TStrings<span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">var</span>
R<span style="color: #555555;">:</span> TRegistry<span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">begin</span>
AList<span style="color: #555555;">.</span>Clear<span style="color: #555555;">(</span><span style="color: #555555;">)</span><span style="color: #555555;">;</span>
AList<span style="color: #555555;">.</span>LineBreak <span style="color: #555555;">:</span><span style="color: #555555;">=</span> <span style="color: #4c4c4c;">';'</span><span style="color: #555555;">;</span>
R <span style="color: #555555;">:</span><span style="color: #555555;">=</span> TRegistry<span style="color: #555555;">.</span>Create<span style="color: #555555;">(</span><span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">try</span>
<span style="color: #575757; font-weight: bold;">if</span> R<span style="color: #555555;">.</span>OpenKeyReadOnly<span style="color: #555555;">(</span><span style="color: #4c4c4c;">'Software\ASCON\Loodsman'</span><span style="color: #555555;">)</span> <span style="color: #575757; font-weight: bold;">then</span>
<span style="color: #575757; font-weight: bold;">begin</span>
<span style="color: #575757; font-weight: bold;">if</span> AWorkflow <span style="color: #575757; font-weight: bold;">and</span> R<span style="color: #555555;">.</span>ValueExists<span style="color: #555555;">(</span><span style="color: #4c4c4c;">'WorkFlowSP'</span><span style="color: #555555;">)</span> <span style="color: #575757; font-weight: bold;">then</span>
AList<span style="color: #555555;">.</span><span style="color: #575757; font-weight: bold;">Text</span> <span style="color: #555555;">:</span><span style="color: #555555;">=</span> R<span style="color: #555555;">.</span>ReadString<span style="color: #555555;">(</span><span style="color: #4c4c4c;">'WorkFlowSP'</span><span style="color: #555555;">)</span>
<span style="color: #575757; font-weight: bold;">else</span> <span style="color: #575757; font-weight: bold;">if</span> <span style="color: #555555;">(</span><span style="color: #575757; font-weight: bold;">not</span> AWorkflow<span style="color: #555555;">)</span>
<span style="color: #575757; font-weight: bold;">and</span> R<span style="color: #555555;">.</span>ValueExists<span style="color: #555555;">(</span><span style="color: #4c4c4c;">'ClientSP'</span><span style="color: #555555;">)</span> <span style="color: #575757; font-weight: bold;">then</span>
AList<span style="color: #555555;">.</span><span style="color: #575757; font-weight: bold;">Text</span> <span style="color: #555555;">:</span><span style="color: #555555;">=</span> R<span style="color: #555555;">.</span>ReadString<span style="color: #555555;">(</span><span style="color: #4c4c4c;">'ClientSP'</span><span style="color: #555555;">)</span>
<span style="color: #575757; font-weight: bold;">else</span>
AList<span style="color: #555555;">.</span><span style="color: #575757; font-weight: bold;">Text</span> <span style="color: #555555;">:</span><span style="color: #555555;">=</span> R<span style="color: #555555;">.</span>ReadString<span style="color: #555555;">(</span><span style="color: #4c4c4c;">'SP'</span><span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">finally</span>
R<span style="color: #555555;">.</span>Free<span style="color: #555555;">(</span><span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
</pre>
<br />
Разбор строки с параметрами подключения:<br />
<br />
<pre style="background-color: white; background-position: initial initial; background-repeat: initial initial;"><span style="color: #575757; font-weight: bold;">procedure</span><span style="background-color: white;"> GetAppServerParams</span><span style="color: #555555;">(</span><span style="color: #575757; font-weight: bold;">const</span><span style="background-color: white;"> AConnectionString</span><span style="color: #555555;">:</span><span style="background-color: white;"> </span><span style="color: #575757; font-weight: bold;">String</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">var</span> AConnection<span style="color: #555555;">:</span> TAppServerConnection<span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">var</span> AHost<span style="color: #555555;">:</span> <span style="color: #575757; font-weight: bold;">String</span><span style="color: #555555;">;</span> <span style="color: #575757; font-weight: bold;">var</span> APort<span style="color: #555555;">:</span> <span style="color: #575757; font-weight: bold;">Integer</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">var</span> AProxy<span style="color: #555555;">,</span> AUserName<span style="color: #555555;">,</span> APassword<span style="color: #555555;">:</span> <span style="color: #575757; font-weight: bold;">String</span><span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">var</span>
LParams<span style="color: #555555;">:</span> TStringList<span style="color: #555555;">;</span>
i<span style="color: #555555;">:</span> <span style="color: #575757; font-weight: bold;">Integer</span><span style="color: #555555;">;</span>
LName<span style="color: #555555;">:</span> <span style="color: #575757; font-weight: bold;">String</span><span style="color: #555555;">;</span>
LValue<span style="color: #555555;">:</span> <span style="color: #575757; font-weight: bold;">String</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">begin</span>
AConnection <span style="color: #555555;">:</span><span style="color: #555555;">=</span> TAppServerConnection<span style="color: #555555;">.</span>DCOM<span style="color: #555555;">;</span>
AHost <span style="color: #555555;">:</span><span style="color: #555555;">=</span> AConnectionString<span style="color: #555555;">;</span>
APort <span style="color: #555555;">:</span><span style="color: #555555;">=</span> <span style="color: #2e2e2e;">4804</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">if</span> Pos<span style="color: #555555;">(</span><span style="color: #4c4c4c;">'|'</span><span style="color: #555555;">,</span> AConnectionString<span style="color: #555555;">)</span> <span style="color: #555555;">></span> <span style="color: #2e2e2e;">0</span> <span style="color: #575757; font-weight: bold;">then</span>
<span style="color: #575757; font-weight: bold;">begin</span>
LParams <span style="color: #555555;">:</span><span style="color: #555555;">=</span> TStringList<span style="color: #555555;">.</span>Create<span style="color: #555555;">(</span><span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">try</span>
LParams<span style="color: #555555;">.</span>LineBreak <span style="color: #555555;">:</span><span style="color: #555555;">=</span> <span style="color: #4c4c4c;">'|'</span><span style="color: #555555;">;</span>
LParams<span style="color: #555555;">.</span>NameValueSeparator <span style="color: #555555;">:</span><span style="color: #555555;">=</span> <span style="color: #4c4c4c;">'='</span><span style="color: #555555;">;</span>
LParams<span style="color: #555555;">.</span><span style="color: #575757; font-weight: bold;">Text</span> <span style="color: #555555;">:</span><span style="color: #555555;">=</span> AConnectionString<span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">for</span> i <span style="color: #555555;">:</span><span style="color: #555555;">=</span> <span style="color: #2e2e2e;">0</span> <span style="color: #575757; font-weight: bold;">to</span> LParams<span style="color: #555555;">.</span>Count <span style="color: #555555;">-</span> <span style="color: #2e2e2e;">1</span> <span style="color: #575757; font-weight: bold;">do</span>
<span style="color: #575757; font-weight: bold;">begin</span>
LName <span style="color: #555555;">:</span><span style="color: #555555;">=</span> LParams<span style="color: #555555;">.</span>Names<span style="color: #555555;">[</span>i<span style="color: #555555;">]</span><span style="color: #555555;">;</span>
LValue <span style="color: #555555;">:</span><span style="color: #555555;">=</span> LParams<span style="color: #555555;">.</span>ValueFromIndex<span style="color: #555555;">[</span>i<span style="color: #555555;">]</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">if</span> LName <span style="color: #555555;">=</span> <span style="color: #4c4c4c;">'ConnectionType'</span> <span style="color: #575757; font-weight: bold;">then</span>
AConnection <span style="color: #555555;">:</span><span style="color: #555555;">=</span> TAppServerConnection<span style="color: #555555;">(</span>
StrToIntDef<span style="color: #555555;">(</span>LValue<span style="color: #555555;">,</span> <span style="color: #2e2e2e;">0</span><span style="color: #555555;">)</span><span style="color: #555555;">)</span>
<span style="color: #575757; font-weight: bold;">else</span> <span style="color: #575757; font-weight: bold;">if</span> LName <span style="color: #555555;">=</span> <span style="color: #4c4c4c;">'Host'</span> <span style="color: #575757; font-weight: bold;">then</span>
AHost <span style="color: #555555;">:</span><span style="color: #555555;">=</span> LValue
<span style="color: #575757; font-weight: bold;">else</span> <span style="color: #575757; font-weight: bold;">if</span> LName <span style="color: #555555;">=</span> <span style="color: #4c4c4c;">'Port'</span> <span style="color: #575757; font-weight: bold;">then</span>
APort <span style="color: #555555;">:</span><span style="color: #555555;">=</span> StrToIntDef<span style="color: #555555;">(</span>LValue<span style="color: #555555;">,</span> <span style="color: #2e2e2e;">4804</span><span style="color: #555555;">)</span>
<span style="color: #575757; font-weight: bold;">else</span> <span style="color: #575757; font-weight: bold;">if</span> LName <span style="color: #555555;">=</span> <span style="color: #4c4c4c;">'Proxy'</span> <span style="color: #575757; font-weight: bold;">then</span>
AProxy <span style="color: #555555;">:</span><span style="color: #555555;">=</span> LValue
<span style="color: #575757; font-weight: bold;">else</span> <span style="color: #575757; font-weight: bold;">if</span> LName <span style="color: #555555;">=</span> <span style="color: #4c4c4c;">'User'</span> <span style="color: #575757; font-weight: bold;">then</span>
AUserName <span style="color: #555555;">:</span><span style="color: #555555;">=</span> LValue
<span style="color: #575757; font-weight: bold;">else</span> <span style="color: #575757; font-weight: bold;">if</span> LName <span style="color: #555555;">=</span> <span style="color: #4c4c4c;">'Password'</span> <span style="color: #575757; font-weight: bold;">then</span>
APassword <span style="color: #555555;">:</span><span style="color: #555555;">=</span> DeCrypt<span style="color: #555555;">(</span>LValue<span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">finally</span>
LParams<span style="color: #555555;">.</span>Free<span style="color: #555555;">(</span><span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
</pre>
<br />
<h4>
База данных</h4>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://2.bp.blogspot.com/-ks-Daj24--I/T83BEpYS4cI/AAAAAAAAABM/d3vzv6VSrLM/s1600/loodsman-db.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-ks-Daj24--I/T83BEpYS4cI/AAAAAAAAABM/d3vzv6VSrLM/s1600/loodsman-db.png" /></a></div>
<br />
Информация о параметрах подключения к базам данных хранится в реестре в ветке <code>HKCU\Software\ASCON\Loodsman</code> в значении <code>DBList</code>. Значение имеет тип <code>REG_BINARY</code> и в нем хранится набор данных <a href="http://docwiki.embarcadero.com/Libraries/en/Datasnap.DBClient.TClientDataSet" target="_blank">TClientDataSet</a>.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://2.bp.blogspot.com/-uwtwbs72z3M/T83BFSGmKCI/AAAAAAAAABQ/jzs5AtGHoeY/s1600/loodsman-dbp.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-uwtwbs72z3M/T83BFSGmKCI/AAAAAAAAABQ/jzs5AtGHoeY/s1600/loodsman-dbp.png" /></a></div>
<br />
Набор данных содержит следующие поля:<br />
<br />
<table border="1" cellpadding="4" cellspacing="0"><tbody>
<tr><th>Имя поля</th><th>Тип</th><th>Описание</th></tr>
<tr><td>_DataBase</td><td>VARCHAR(255)</td><td>Имя базы данных</td></tr>
<tr><td>_UserName</td><td>VARCHAR(255)</td><td>Имя пользователя (только для аутентификации средствами SQL-сервера)</td></tr>
<tr><td>_AccessMethod</td><td>INTEGER</td><td>Способ аутентификации:<br />
0 — средствами Windows<br />
1 — средствами SQL-сервера<br />
2 — средствами SQL-сервера, пароль сохранен<br />
3 — недоступная база данных</td></tr>
<tr><td>_Password</td><td>VARCHAR(255)</td><td>Пароль пользователя (только для аутентификации средствами SQL-сервера)</td></tr>
<tr><td>_Connected</td><td>INTEGER</td><td>Назначение поля точно не известно и в представленном коде не используется</td></tr>
</tbody></table>
<br />
Для подключения к базе данных необходимо найти в наборе данных параметры подключения, если необходим пароль и он не сохранен, то спросить пароль у пользователя. Затем вызвать метод сервера приложений ConnectToDBEx и передать ему полученные параметры.<br />
<br />
Код получения набора данных с параметрами подключения:
<br />
<br />
<pre style="background: #ffffff; color: black;"><span style="color: #575757; font-weight: bold;">function</span> GetDBAuthListData<span style="color: #555555;">:</span> <span style="color: #575757; font-weight: bold;">OleVariant</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">var</span>
R<span style="color: #555555;">:</span> TRegistry<span style="color: #555555;">;</span>
LSize<span style="color: #555555;">:</span> <span style="color: #575757; font-weight: bold;">Integer</span><span style="color: #555555;">;</span>
P<span style="color: #555555;">:</span> <span style="color: #575757; font-weight: bold;">Pointer</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">begin</span>
R <span style="color: #555555;">:</span><span style="color: #555555;">=</span> TRegistry<span style="color: #555555;">.</span>Create<span style="color: #555555;">(</span><span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">try</span>
<span style="color: #575757; font-weight: bold;">if</span> R<span style="color: #555555;">.</span>OpenKeyReadOnly<span style="color: #555555;">(</span><span style="color: #4c4c4c;">'Software\ASCON\Loodsman'</span><span style="color: #555555;">)</span> <span style="color: #575757; font-weight: bold;">then</span>
<span style="color: #575757; font-weight: bold;">begin</span>
LSize <span style="color: #555555;">:</span><span style="color: #555555;">=</span> R<span style="color: #555555;">.</span>GetDataSize<span style="color: #555555;">(</span><span style="color: #4c4c4c;">'DBList'</span><span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">if</span> LSize <span style="color: #555555;">></span> <span style="color: #2e2e2e;">0</span> <span style="color: #575757; font-weight: bold;">then</span>
<span style="color: #575757; font-weight: bold;">begin</span>
<span style="color: #575757; font-weight: bold;">Result</span> <span style="color: #555555;">:</span><span style="color: #555555;">=</span> VarArrayCreate<span style="color: #555555;">(</span><span style="color: #555555;">[</span><span style="color: #2e2e2e;">0</span><span style="color: #555555;">,</span> LSize <span style="color: #555555;">-</span> <span style="color: #2e2e2e;">1</span><span style="color: #555555;">]</span><span style="color: #555555;">,</span>
varByte<span style="color: #555555;">)</span><span style="color: #555555;">;</span>
P <span style="color: #555555;">:</span><span style="color: #555555;">=</span> VarArrayLock<span style="color: #555555;">(</span><span style="color: #575757; font-weight: bold;">Result</span><span style="color: #555555;">)</span><span style="color: #555555;">;</span>
R<span style="color: #555555;">.</span>ReadBinaryData<span style="color: #555555;">(</span><span style="color: #4c4c4c;">'DBList'</span><span style="color: #555555;">,</span> P<span style="color: #555555;">^</span><span style="color: #555555;">,</span> LSize<span style="color: #555555;">)</span><span style="color: #555555;">;</span>
VarArrayUnLock<span style="color: #555555;">(</span><span style="color: #575757; font-weight: bold;">Result</span><span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">finally</span>
R<span style="color: #555555;">.</span>Free<span style="color: #555555;">(</span><span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
</pre>
<pre style="background: #ffffff; color: black;"><span style="color: #575757; font-weight: bold;">procedure</span> ConnectTo<span style="color: #555555;">(</span><span style="color: #575757; font-weight: bold;">const</span> ABase<span style="color: #555555;">:</span> <span style="color: #575757; font-weight: bold;">String</span><span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">var</span>
LDatabaseAuth<span style="color: #555555;">:</span> <span style="color: #575757; font-weight: bold;">Integer</span><span style="color: #555555;">;</span>
LDBListData<span style="color: #555555;">:</span> <span style="color: #575757; font-weight: bold;">OleVariant</span><span style="color: #555555;">;</span>
LDataSet<span style="color: #555555;">:</span> TClientDataSet<span style="color: #555555;">;</span>
LUserName<span style="color: #555555;">:</span> <span style="color: #575757; font-weight: bold;">String</span><span style="color: #555555;">;</span>
LPassword<span style="color: #555555;">:</span> <span style="color: #575757; font-weight: bold;">String</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">begin</span>
LDatabaseAuth <span style="color: #555555;">:</span><span style="color: #555555;">=</span> <span style="color: #2e2e2e;">0</span><span style="color: #555555;">;</span>
LDBListData <span style="color: #555555;">:</span><span style="color: #555555;">=</span> GetDBAuthListData<span style="color: #555555;">(</span><span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">if</span> <span style="color: #575757; font-weight: bold;">not</span> VarIsEmpty<span style="color: #555555;">(</span>LDBListData<span style="color: #555555;">)</span> <span style="color: #575757; font-weight: bold;">then</span>
<span style="color: #575757; font-weight: bold;">begin</span>
LDataSet <span style="color: #555555;">:</span><span style="color: #555555;">=</span> TClientDataSet<span style="color: #555555;">.</span>Create<span style="color: #555555;">(</span><span style="color: #404040;">nil</span><span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">try</span>
LDataSet<span style="color: #555555;">.</span>Data <span style="color: #555555;">:</span><span style="color: #555555;">=</span> LDBListData<span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">if</span> LDataSet<span style="color: #555555;">.</span>Locate<span style="color: #555555;">(</span><span style="color: #4c4c4c;">'_DataBase'</span><span style="color: #555555;">,</span> ABase<span style="color: #555555;">,</span> <span style="color: #555555;">[</span><span style="color: #555555;">]</span><span style="color: #555555;">)</span> <span style="color: #575757; font-weight: bold;">then</span>
<span style="color: #575757; font-weight: bold;">begin</span>
LDatabaseAuth <span style="color: #555555;">:</span><span style="color: #555555;">=</span>
LDataSet<span style="color: #555555;">.</span>FieldValue<span style="color: #555555;">[</span><span style="color: #4c4c4c;">'_AccessMethod'</span><span style="color: #555555;">]</span><span style="color: #555555;">;</span>
LUserName <span style="color: #555555;">:</span><span style="color: #555555;">=</span> LDataSet<span style="color: #555555;">.</span>FieldValue<span style="color: #555555;">[</span><span style="color: #4c4c4c;">'_UserName'</span><span style="color: #555555;">]</span><span style="color: #555555;">;</span>
LPassword <span style="color: #555555;">:</span><span style="color: #555555;">=</span> LDataSet<span style="color: #555555;">.</span>FieldValue<span style="color: #555555;">[</span><span style="color: #4c4c4c;">'_Password'</span><span style="color: #555555;">]</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">if</span> LPassword <span style="color: #555555;"><</span><span style="color: #555555;">></span> <span style="color: #4c4c4c;">''</span> <span style="color: #575757; font-weight: bold;">then</span>
LPassword <span style="color: #555555;">:</span><span style="color: #555555;">=</span> DeCrypt<span style="color: #555555;">(</span>LPassword<span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">if</span> <span style="color: #555555;">(</span>LDatabaseAuth <span style="color: #575757; font-weight: bold;">in</span> <span style="color: #555555;">[</span><span style="color: #2e2e2e;">1</span><span style="color: #555555;">,</span> <span style="color: #2e2e2e;">2</span><span style="color: #555555;">]</span><span style="color: #555555;">)</span>
<span style="color: #575757; font-weight: bold;">and</span> <span style="color: #555555;">(</span>LPassword <span style="color: #555555;">=</span> <span style="color: #4c4c4c;">''</span><span style="color: #555555;">)</span> <span style="color: #575757; font-weight: bold;">then</span>
<span style="color: #575757; font-weight: bold;">begin</span>
<span style="color: dimgrey;">// Запрос имени пользователя и пароли,</span>
<span style="color: dimgrey;">// если они не сохранены</span>
ShowLoodsmanLoginDialog<span style="color: #555555;">(</span>ABase<span style="color: #555555;">,</span>
LUserName<span style="color: #555555;">,</span> LPassword<span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">finally</span>
LDataSet<span style="color: #555555;">.</span>Free<span style="color: #555555;">(</span><span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">if</span> LDatabaseAuth <span style="color: #575757; font-weight: bold;">in</span> <span style="color: #555555;">[</span><span style="color: #2e2e2e;">1</span><span style="color: #555555;">,</span> <span style="color: #2e2e2e;">2</span><span style="color: #555555;">]</span> <span style="color: #575757; font-weight: bold;">then</span>
DCOMConnection<span style="color: #555555;">.</span>ConnectToDBEx<span style="color: #555555;">(</span>ABase<span style="color: #555555;">,</span>
LUserName<span style="color: #555555;">,</span> LPassword<span style="color: #555555;">)</span>
<span style="color: #575757; font-weight: bold;">else</span>
DCOMConnection<span style="color: #555555;">.</span>ConnectToDB<span style="color: #555555;">(</span>ABase<span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
</pre>
<br />
Краткий пересказ статьи я писал на форуме Аскона, в теме <a href="http://forum.ascon.ru/index.php/topic,22319.0.html" target="_blank">Как в плагине Лоцмана определить имя сервера, к которому подключен клиент?</a><br />
<br />
В следующей статье мы рассмотрим подключение к серверу и вызов методов сервера приложений.Chaahttp://www.blogger.com/profile/14387721107858333063noreply@blogger.com0tag:blogger.com,1999:blog-4946864412455003694.post-47433881549556435892012-04-12T11:07:00.000+06:002014-04-23T17:07:26.587+06:00Пишем подключаемый модуль для ЛОЦМАН КлиентПодключаемый модуль (плагин) для ЛОЦМАН Клиент предназначен для добавления новых функций в клиентское приложение.<br />
<br />
Снаружи подключаемый модуль выглядит как несколько пунктов в меню клиентского модуля. Начиная с версии 10, эти пункты меню могут быть добавлены в контекстное меню и на панели инструментов. <br />
<br />
Внутри подключаемый модуль — это DLL, реализующая специальные функции. Есть два основных вида интерфейсов для взаимодействия подключаемого модуля с клиентом Лоцман: PAS-интерфейс и COM-интерфейс. Описание PAS-интерфейса отсутствует в официальной документации, не рекомендовано к использованию и создает множество проблем при использовании новых версий Delphi, поэтому мы будем писать подключаемый модуль с COM-интерфейсом. Описание его вы найдете в папке <code>SDK</code> дистрибутива в файле <code>LoodsmanClientApi.chm</code>.
<br />
<br />
<a name='more'></a>DLL подключаемого модуля должна экспортировать функции <code>InitUserDLLCom</code>, <code>PgiCheckMenuItemCom</code> и функции, которые будут вызываться при выборе пользователем пунктов меню. Вот прототипы этих функций:<br />
<br />
<pre><span style="color: dimgrey;">// Используется ЛОЦМАН Клиент для построения меню,</span>
<span style="color: dimgrey;">// которое поддерживает модуль</span>
<span style="color: #575757; font-weight: bold;">function</span> InitUserDLLCom<span style="color: #555555;">(</span>Value<span style="color: #555555;">:</span> <span style="color: #575757; font-weight: bold;">Pointer</span><span style="color: #555555;">)</span><span style="color: #555555;">:</span> <span style="color: #575757; font-weight: bold;">Integer</span><span style="color: #555555;">;</span> <span style="color: #575757; font-weight: bold;">stdcall</span><span style="color: #555555;">;</span>
<span style="color: dimgrey;">// Используется ЛОЦМАН Клиент для определения</span>
<span style="color: dimgrey;">// активности команд меню модуля</span>
<span style="color: #575757; font-weight: bold;">function</span> PgiCheckMenuItemCom<span style="color: #555555;">(</span>stFunction<span style="color: #555555;">:</span> <span style="color: #575757; font-weight: bold;">PAnsiChar</span><span style="color: #555555;">;</span>
<span style="color: #555555;"></span> PluginCall<span style="color: #555555;">:</span> IPluginCall<span style="color: #555555;">)</span><span style="color: #555555;">:</span> <span style="color: #575757; font-weight: bold;">Integer</span><span style="color: #555555;">;</span> <span style="color: #575757; font-weight: bold;">stdcall</span><span style="color: #555555;">;</span>
<span style="color: dimgrey;">// Функция, соответствующая команде меню</span>
<span style="color: #575757; font-weight: bold;">procedure</span> MenuClick<span style="color: #555555;">(</span>PluginCall<span style="color: #555555;">:</span> IPluginCall<span style="color: #555555;">)</span><span style="color: #555555;">;</span> <span style="color: #575757; font-weight: bold;">stdcall</span><span style="color: #555555;">;</span>
</pre>
<br />
Модуль может быть подключен к одной или нескольким базам данных, либо являться общим (с версии 10), то есть использоваться при работе с любой из баз данных. Информация о подключенных модулях хранится либо в реестре в ветке <code>HKCU\Software\ASCON\Loodsman\Client\PluginManager</code> (с версии 10), либо для старых версий она хранится в файле <code>Loodsman.ini</code>.<br />
<br />
При открытии окна базы данных, ЛОЦМАН Клиент получает информацию о подключенных для нее модулях из реестра или INI-файла. После чего подключаемый модуль загружается и из него вызывается функция получения меню — <code>InitUserDLLCom</code>. Функция <code>PgiCheckMenuItemCom</code> вызывается, когда ЛОЦМАН Клиент понадобится проверить доступность меню. Нужно отметить, что для пунктов меню, вынесенных на панели инструментов, <code>PgiCheckMenuItemCom</code> может вызываться очень часто, и если в ней будет происходить обращение к серверу приложений, то это может существенно замедлить работу клиента. <br />
<br />
В старых версиях клиента DLL загружалась и выгружалась постоянно для вызова любой из функций. Так как DLL подключаемых модулей могут быть достаточно большими и иметь множество зависимостей, то постоянная их загрузка и выгрузка может отнимать заметное количество времени. В связи с тем, что в новых версиях ЛОЦМАН Клиент появилась возможность помещать кнопки вызова подключаемого модуля на панели инструментов, то вызываться функции подключаемого модуля стали чаще, и, чтобы ускорить работу,
начиная с версии 10, подключаемый модуль по умолчанию выгружается только при закрытии клиента.
Для целей отладки в версии 10 есть возможность вернуть режим, когда подключаемые модули постоянно выгружаются после вызова их функций. Для этого необходимо установить параметр системного реестра (типа <code>DWORD</code>) <code>HKCU\Software\ASCON\Loodsman\Client\PluginManager\DebugMode = 1</code>.<br />
<br />
После загрузки подключаемого модуля клиент дважды вызывает функцию <code>InitUserDLLCom</code>. В первый раз, передавая в качестве <code>Value</code> значение <code>nil</code>, клиент запрашивает количество пунктов меню, которые хочет добавить подключаемый модуль. Затем клиент выделяет память для нужного количества пунктов меню и вызывает <code>InitUserDLLCom</code> во второй раз, передавая ей в качестве <code>Value</code> указатель на выделенную память.<br />
<br />
Каждый добавляемый пункт меню описывается следующей структурой данных:<br />
<br />
<pre style="background: #ffffff; color: black;"><span style="color: #575757; font-weight: bold;">const</span>
MAX_TEXT_LENGTH <span style="color: #555555;">=</span> <span style="color: #2e2e2e;">255</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">type</span>
TLoodsmanAddMenuCom <span style="color: #555555;">=</span> <span style="color: #575757; font-weight: bold;">record</span>
stMenu<span style="color: #555555;">:</span> <span style="color: #575757; font-weight: bold;">array</span> <span style="color: #555555;">[</span><span style="color: #2e2e2e;">0</span><span style="color: #555555;">..</span>MAX_TEXT_LENGTH<span style="color: #555555;">-</span><span style="color: #2e2e2e;">1</span><span style="color: #555555;">]</span> <span style="color: #575757; font-weight: bold;">of</span> <span style="color: #575757; font-weight: bold;">AnsiChar</span><span style="color: #555555;">;</span>
stFunction<span style="color: #555555;">:</span> <span style="color: #575757; font-weight: bold;">array</span> <span style="color: #555555;">[</span><span style="color: #2e2e2e;">0</span><span style="color: #555555;">..</span>MAX_TEXT_LENGTH<span style="color: #555555;">-</span><span style="color: #2e2e2e;">1</span><span style="color: #555555;">]</span> <span style="color: #575757; font-weight: bold;">of</span> <span style="color: #575757; font-weight: bold;">AnsiChar</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;
PLoodsmanAddMenuCom <span style="color: #555555;">=</span> <span style="color: #555555;">^</span>TLoodsmanAddMenuCom<span style="color: #555555;">;</span>
</span>
</pre>
<br />
Где <code>stFunction</code> — это имя экспортируемой функции из DLL, которая будет вызвана, когда пользователь выберет данный пункт меню. А <code>stMenu</code> — это названия пунктов меню, разделенные символом <code>'#'</code>. Название первого пункта меню указывает, как меню подключаемого модуля будет расположено относительно собственного меню клиента. Возможные значения перечислены ниже:<br />
<br />
<table border="1" cellpadding="4" cellspacing="0"><tbody>
<tr><th>Меню клиента</th><th><b>Перед</b></th><th><b>Дочернее</b></th><th><b>После</b></th></tr>
<tr><td>Файл</td> <td>BEFORE_MI_FILE</td> <td>MI_FILE</td> <td>AFTER_MI_FILE</td></tr>
<tr><td>Вид</td> <td>BEFORE_MI_VIEW</td> <td>MI_VIEW</td> <td>AFTER_MI_VIEW</td></tr>
<tr><td>Правка</td> <td>BEFORE_MI_EDIT</td> <td>MI_EDIT</td> <td>AFTER_MI_EDIT</td></tr>
<tr><td>Инструменты</td> <td>BEFORE_MI_TOOLS</td> <td>MI_TOOLS</td> <td>AFTER_MI_TOOLS</td></tr>
<tr><td>Окно</td> <td>BEFORE_MI_ZOOM</td> <td>MI_ZOOM</td> <td>AFTER_MI_ZOOM</td></tr>
<tr><td>Справка</td> <td>BEFORE_MI_HELP</td> <td>MI_HELP</td> <td>AFTER_MI_HELP</td></tr>
</tbody></table>
<br />
Например, подключаемый модуль добавляет два пункта меню перед меню «Инструменты»:<br />
<code>'<b>BEFORE_MI_TOOLS</b>#Мои плагины#Тестовый#Список проектов'</code> и<br />
<code>'<b>BEFORE_MI_TOOLS</b>#Мои плагины#Тестовый#Состав'</code>.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://1.bp.blogspot.com/-R6STPArnMFY/TWYym2lK8zI/AAAAAAAAAAY/KTlUjs9ZXnY/s1600/pluginmenu1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-R6STPArnMFY/TWYym2lK8zI/AAAAAAAAAAY/KTlUjs9ZXnY/s1600/pluginmenu1.png" /></a></div>
<br />
Подключаемый модуль добавляет два пункта меню после меню «Инструменты»:<br />
<code>'<b>AFTER_MI_TOOLS</b>#Мои плагины#Тестовый#Список проектов'</code> и<br />
<code>'<b>AFTER_MI_TOOLS</b>#Мои плагины#Тестовый#Состав'</code>.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://2.bp.blogspot.com/-3o5BCpgPhzw/TWYynFNeuMI/AAAAAAAAAAc/GDntFAXCPAE/s1600/pluginmenu2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-3o5BCpgPhzw/TWYynFNeuMI/AAAAAAAAAAc/GDntFAXCPAE/s1600/pluginmenu2.png" /></a></div>
<br />
Подключаемый модуль добавляет два пункта меню внутрь меню «Инструменты»:<br />
<code>'<b>MI_TOOLS</b>#Мои плагины#Тестовый#Список проектов'</code> и<br />
<code>'<b>MI_TOOLS</b>#Мои плагины#Тестовый#Состав'</code>.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://2.bp.blogspot.com/-ml2ksMm3B8k/TWYymbsTxJI/AAAAAAAAAAU/1pR4AbzvfKw/s1600/pluginmenu3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-ml2ksMm3B8k/TWYymbsTxJI/AAAAAAAAAAU/1pR4AbzvfKw/s1600/pluginmenu3.png" /></a></div>
<br />
Пример реализации функции <code>InitUserDLLCom</code>:<br />
<br />
<pre style="background: #ffffff; color: black;"><span style="color: #575757; font-weight: bold;">function</span> InitUserDLLCom<span style="color: #555555;">(</span>AValue<span style="color: #555555;">:</span> <span style="color: #575757; font-weight: bold;">Pointer</span><span style="color: #555555;">)</span><span style="color: #555555;">:</span> <span style="color: #575757; font-weight: bold;">Integer</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">var</span>
LMenuItem<span style="color: #555555;">:</span> PLoodsmanAddMenuCom<span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">begin</span>
<span style="color: #575757; font-weight: bold;">if</span> AValue <span style="color: #555555;"><</span><span style="color: #555555;">></span> <span style="color: #404040;">nil</span> <span style="color: #575757; font-weight: bold;">then</span>
<span style="color: #575757; font-weight: bold;">begin</span>
LMenuItem <span style="color: #555555;">:</span><span style="color: #555555;">=</span> AValue<span style="color: #555555;">;</span>
StrLCopy<span style="color: #555555;">(</span>LMenuItem<span style="color: #555555;">.</span>stMenu<span style="color: #555555;">,</span>
<span style="color: #4c4c4c;">'BEFORE_MI_TOOLS#LoodsmanPlugIn#Список проектов'</span><span style="color: #555555;">,</span>
SizeOf<span style="color: #555555;">(</span>LMenuItem<span style="color: #555555;">.</span>stMenu<span style="color: #555555;">)</span> <span style="color: #555555;">-</span> <span style="color: #2e2e2e;">1</span><span style="color: #555555;">)</span><span style="color: #555555;">;</span>
StrLCopy<span style="color: #555555;">(</span>LMenuItem<span style="color: #555555;">.</span>stFunction<span style="color: #555555;">,</span>
<span style="color: #4c4c4c;">'ProjectList'</span><span style="color: #555555;">,</span>
SizeOf<span style="color: #555555;">(</span>LMenuItem<span style="color: #555555;">.</span>stFunction<span style="color: #555555;">)</span> <span style="color: #555555;">-</span> <span style="color: #2e2e2e;">1</span><span style="color: #555555;">)</span><span style="color: #555555;">;</span>
Inc<span style="color: #555555;">(</span>LMenuItem<span style="color: #555555;">)</span><span style="color: #555555;">;</span>
StrLCopy<span style="color: #555555;">(</span>LMenuItem<span style="color: #555555;">.</span>stMenu<span style="color: #555555;">,</span>
<span style="color: #4c4c4c;">'BEFORE_MI_TOOLS#LoodsmanPlugIn#Список'</span><span style="color: #555555;">,</span>
SizeOf<span style="color: #555555;">(</span>LMenuItem<span style="color: #555555;">.</span>stMenu<span style="color: #555555;">)</span> <span style="color: #555555;">-</span> <span style="color: #2e2e2e;">1</span><span style="color: #555555;">)</span><span style="color: #555555;">;</span>
StrLCopy<span style="color: #555555;">(</span>LMenuItem<span style="color: #555555;">.</span>stFunction<span style="color: #555555;">,</span>
<span style="color: #4c4c4c;">'LinkedFast'</span><span style="color: #555555;">,</span>
SizeOf<span style="color: #555555;">(</span>LMenuItem<span style="color: #555555;">.</span>stFunction<span style="color: #555555;">)</span> <span style="color: #555555;">-</span> <span style="color: #2e2e2e;">1</span><span style="color: #555555;">)</span><span style="color: #555555;">;</span>
Inc<span style="color: #555555;">(</span>LMenuItem<span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">Result</span> <span style="color: #555555;">:</span><span style="color: #555555;">=</span> <span style="color: #2e2e2e;">2</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
</pre>
<br />
При необходимости, для пункта меню можно задать значок (как у пункта меню «Состав» на рисунке выше). Для этого
в DLL нужно добавить ресурс типа <code>RT_BITMAP</code>, с именем как у экспортируемой функции, указанной в <code>stFunction</code>.<br />
Проще всего это сделать, создав файл <code>PluginIcon.rc</code> и добавив его к проекту.
Например:
<br />
<br />
<pre>LinkedFast BITMAP Images\LinkedFast.bmp
</pre>
<br />
Для определения доступности пунктов меню подключаемого модуля в зависимости от выбранного объекта, клиент будет вызывать функцию <code>PgiCheckMenuItemCom</code>, экспортируемую клиентским модулем. В <code>PgiCheckMenuItemCom</code> передается имя функции для пункта меню (<code>stFunction</code>) и интерфейс <code>IPluginCall</code>, с помощью которого можно получить информацию о текущей базе данных и о выбранном объекте. На основании полученной информации подключаемый модуль должен принять решение, может он выполнить запрошенную функцию или нет, и вернуть соответствующий результат.
<br />
<br />
Пример реализации функции <code>PgiCheckMenuItemCom</code>:
<br />
<br />
<pre style="background: #ffffff; color: black;"><span style="color: #575757; font-weight: bold;">function</span> PgiCheckMenuItemCom<span style="color: #555555;">(</span>AFunction<span style="color: #555555;">:</span> <span style="color: #575757; font-weight: bold;">PAnsiChar</span><span style="color: #555555;">;</span>
APluginCall<span style="color: #555555;">:</span> IPluginCall<span style="color: #555555;">)</span><span style="color: #555555;">:</span> <span style="color: #575757; font-weight: bold;">Boolean</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">var</span>
LFuncName<span style="color: #555555;">:</span> <span style="color: #575757; font-weight: bold;">String</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">begin</span>
<span style="color: #575757; font-weight: bold;">Result</span> <span style="color: #555555;">:</span><span style="color: #555555;">=</span> <span style="color: #404040;">False</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">try</span>
LFuncName <span style="color: #555555;">:</span><span style="color: #555555;">=</span> <span style="color: #575757; font-weight: bold;">String</span><span style="color: #555555;">(</span>AFunction<span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">if</span> LFuncName <span style="color: #555555;">=</span> <span style="color: #4c4c4c;">'ProjectList'</span> <span style="color: #575757; font-weight: bold;">then</span>
<span style="color: #575757; font-weight: bold;">begin</span>
<span style="color: #575757; font-weight: bold;">Result</span> <span style="color: #555555;">:</span><span style="color: #555555;">=</span> <span style="color: #404040;">True</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">if</span> LFuncName <span style="color: #555555;">=</span> <span style="color: #4c4c4c;">'LinkedFast'</span> <span style="color: #575757; font-weight: bold;">then</span>
<span style="color: #575757; font-weight: bold;">begin</span>
<span style="color: #575757; font-weight: bold;">Result</span> <span style="color: #555555;">:</span><span style="color: #555555;">=</span> APluginCall<span style="color: #555555;">.</span>stType <span style="color: #555555;"><</span><span style="color: #555555;">></span> <span style="color: #4c4c4c;">'Документ'</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">except</span>
<span style="color: #575757; font-weight: bold;">on</span> E<span style="color: #555555;">:</span> Exception <span style="color: #575757; font-weight: bold;">do</span>
<span style="color: #575757; font-weight: bold;">begin</span>
Application<span style="color: #555555;">.</span>ShowException<span style="color: #555555;">(</span>E<span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
</pre>
<br />
Если пункт меню доступен и пользователь выберет его, то клиент попытается найти среди экспортируемых из DLL функцию, указанную для данного пункта меню (в <code>stFunction</code>), и вызвать ее. В функцию будет передан интерфейс <code>IPluginCall</code>, с информацией о текущей базе данных и о выбранном объекте.
<br />
<br />
Пример реализации функции:
<br />
<br />
<pre style="background: #ffffff; color: black;"><span style="color: #575757; font-weight: bold;">procedure</span> LinkedFast<span style="color: #555555;">(</span>APluginCall<span style="color: #555555;">:</span> IPluginCall<span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">begin</span>
InitializeApplication<span style="color: #555555;">(</span>APluginCall<span style="color: #555555;">.</span>AppHandle<span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">try</span>
ShowLinkedObjectsDialog<span style="color: #555555;">(</span>Application<span style="color: #555555;">,</span> APluginCall<span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">except</span>
<span style="color: #575757; font-weight: bold;">on</span> E<span style="color: #555555;">:</span> Exception <span style="color: #575757; font-weight: bold;">do</span>
<span style="color: #575757; font-weight: bold;">begin</span>
Application<span style="color: #555555;">.</span>ShowException<span style="color: #555555;">(</span>E<span style="color: #555555;">)</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
FinalizeApplication<span style="color: #555555;">;</span>
<span style="color: #575757; font-weight: bold;">end</span><span style="color: #555555;">;</span>
</pre>
<br />
Обратите внимание, что функции, вызываемые из клиента, не должны выбрасывать за свои пределы исключения — в каждой такой функции должен быть блок <code>try/except</code>. Дело в том, что формат объекта исключений (<code><a href="http://docwiki.embarcadero.com/VCL/en/SysUtils.Exception" target="_blank">Exception</a></code>) изменялся в разных версия Delphi (например, в Delphi 2009 в нем произошли серьезные изменения связанные с поддержкой юникода и вложенных исключений).
<br />
<br />
Так же нельзя без дополнительных действий использовать <code><a href="http://docwiki.embarcadero.com/VCL/en/Forms.TApplication.Handle" target="_blank">Application.Handle</a></code>, передаваемый из клиента в подключаемый модуль. Это опять же связано с тем, что в разных версиях Delphi объекты, передаваемые с помощью сообщений, будут отличаться.
<br />
<br />
Описанные выше проблемы решены в предлагаемом примере подключаемого модуля. Код предназначен для Delphi версии 7 и выше. Работоспособность примера проверялась в ЛОЦМАН Клиент версий 8.5, 10 и 11.
<br />
<br />
<a href="http://sites.google.com/site/achechulin/files/PluginSample.zip">Исходный код примера подключаемого модуля</a>.
<br />
<br />
Текущая версия кода на GitHub —
<a href="https://github.com/achechulin/loodsman">github.com/achechulin/loodsman</a>.
<br />
<br />
О том, как подключить модуль к клиенту написано в справке (файл <code>Loodsman.chm</code>), в разделе «Инструменты → Параметры → Подключаемые модули» или просто «Подключаемые модули», в зависимости от версии клиента.
<br />
<br />
Продолжение статьи:
<a href="http://achechulin.blogspot.ru/2014/04/plugin-v14.html">Подключаемые модули в новых версиях ЛОЦМАН Клиент</a>.
<br />
<br />
Chaahttp://www.blogger.com/profile/14387721107858333063noreply@blogger.com0tag:blogger.com,1999:blog-4946864412455003694.post-36257610158577397772012-04-02T08:15:00.000+06:002012-05-17T15:28:08.362+06:00Подключаемые модули для ЛОЦМАН КлиентПодключаемые модули предназначены для расширения возможностей клиентского модуля ЛОЦМАН:PLM. Например, модуль «Извещения» из стандартной поставки добавляет функции по созданию, согласованию и проведению извещений.<br />
<br />
С точки зрения пользователя подключаемый модуль выглядит как несколько пунктов в главном меню клиентского модуля. Начиная с версии 10, эти пункты меню могут быть добавлены в контекстное меню и на панели инструментов.
<br />
<br />
<a name='more'></a><br />
Для разработчика подключаемый модуль представляет собой динамическую подключаемую библиотеку (DLL), созданную определенным образом.<br />
<br />
Зачем вообще нужны подключаемые модули? Вообще говоря, все то, что делает, например, модуль «Извещения», можно сделать и с использование штатных средств клиентского модуля. Проблема в том, что в этом случае трудоемкость вырастет в разы, а вероятность совершения ошибок при проведении извещений станет очень высокой.<br />
<br />
Основная цель подключаемых модулей — автоматизация часто выполняемых операций с целью снижения трудоемкости и уменьшения количества ошибок.<br />
<br />
Даже простое добавление отсканированного документа состоит из множества шагов. А в случае с извещениями количество операций будет значительно больше, ведь кроме самого извещения необходимо проводить изменения в объектах, в нем описанных.<br />
<br />
Далее в этой и следующих статьях я буду рассматривать в качестве примера работу с простым объектом типа «Документ» (или, в некоторых случаях, «Письмо»), содержащим файлы с отсканированным текстом, атрибуты и связь «Документы» с другим объектом типа «Папка». В случае чертежа или извещения все будет значительно сложнее. Но сами операции останутся те же самые — создание документа, добавление связей, добавление атрибутов и файлов. Просто станет больше количество операций.<br />
<br />
Рассмотрим операции, необходимые для такой типовой задачи, как сканирование и размещение в базе данных документа. При этом никакой дополнительной информации, вроде связи с другими документами, вводиться не будет — только сам документ.<br />
<ol>
<li>Сканировать документ. Сохранить его, например, в PDF</li>
<li>Взять в работу папку, в которую предполагается поместить документ, например «Входящие 2012 год»</li>
<li>Открыть взятую в работу папку в окне изменяемого объекта</li>
<li>Выделить папку, выбрать в меню «Создать»</li>
<li>В открывшемся окне выбрать тип объекта</li>
<li>Ввести обозначение документа</li>
<li>Выбрать состояние нового документа, например «Архив»</li>
<li>Выбрать связь, которой будут связаны папка и новый документ</li>
<li>Перейти на закладку «Атрибуты» (или «Карточка»)</li>
<li>Ввести атрибуты</li>
<li>Нажать OK</li>
<li>Перейти на закладку «Файлы», выбрать «Добавить»</li>
<li>Найти отсканированный на первом шаге файл, выбрать его</li>
<li>Выбрать на рабочем столе вкладку в работе, на объекте «Папка Входящие 2012 год» щелкнуть правой кнопкой мыши и выбрать «Вернуть»</li>
<li>Перейти к окну просмотра базы данных, найти в нем созданный документ</li>
<li>Выбрать в меню «Бизнес-процессы по текущему объекту»</li>
<li>Перейти в клиент WorkFlow (немного подождав)</li>
<li>В нем в появившемся окне «Бизнес-процессы по объекту Лоцман» выбрать «Создать по типовому и стартовать», выбрать нужный процесс для рассылки документа на ознакомление</li>
<li>Ввести наименования для создаваемого бизнес-процесса</li>
</ol>
<br />
Итак, мы отсканировали документ и разослали для ознакомления. Это бы заняло у меня всего 5-10 минут. Недостаток метода в том, что у обычного сотрудника (например, секретаря) это займет минут 20-30, и он обязательно сделает пару ошибок. Потом еще примерно 20 минут уйдет на работу над ошибками. Это, конечно, в лучшем случае, если сотрудник уже знает, как их можно исправить. Несложно посчитать, что в день удастся добавить 15-20 документов.<br />
<br />
Рассмотрим такую задачу: мы хотим упорядочить работу с почтой — размещать все входящие и исходящие письма в ЛОЦМАН:PLM, входящие письма направлять сотрудникам для ознакомления и ответа, а исходящие письма согласовывать с помощью WorkFlow. В организации от 10 до 20 входящих писем каждый день, и столько же исходящих. Кроме регистрации документов секретарь отслеживает их судьбу — исходящие отсылает почтой или факсом, для входящих следит, чтобы они поступили к исполнителю. Очевидно, что имеющийся секретарь, прекрасно справляющийся с этой задачи для бумажных писем, не будет успевать обрабатывать письма в случае внедрения нашей системы.<br />
<br />
Попробуем ускорить выполнение задачи, создав подключаемый модуль для помещения документа. Как минимум нужно выполнить следующие операции:<br />
<ol>
<li>Выбрать в меню «Сканировать документ»</li>
<li>Сканировать документ</li>
<li>В появившемся окне ввести атрибуты документа</li>
<li>Нажать кнопку «OK»</li>
</ol>
<br />
При вводе документа можно проконтролировать наличие и правильный формат сканированных файлов, правильность заполнения атрибутов и тем самым свести возможные ошибки к минимуму.<br />
<br />
На практике, созданием подключаемого модуля удалось добиться того, чтобы время размещения документа равнялось времени работы сканера для сканирования всех листов документа плюс одна минута для ввода атрибутов. В среднем, около 3-х минут для обычного документа и для сканера с автоподачей.<br />
<br />
Для чего нужен подключаемый модуль, мы выяснили. Как его сделать, написано в следующей статье <a href="http://achechulin.blogspot.com/2012/04/plugin-sample.html">Пишем подключаемый модуль для ЛОЦМАН Клиент</a>.Chaahttp://www.blogger.com/profile/14387721107858333063noreply@blogger.com0