16 апреля 2009 г.

Pro/Engineer в Delphi aka компилируем DLL для Delphi из Сишных lib-ов

Решил записать одну свою историю - вдруг кому будет интересно/пригодится.

Небольшое предупреждение: я ничего не понимаю в языке С, поэтому здесь могут быть весьма грубые ошибки в высказываниях про него.

В общем, нужно было организовать взаимодействие с Pro/Engineer из своей программы. Поскольку пишу я на Delphi, то я (естественно) и решил использовать Delphi :)

Для взаимодействия, Pro/Engineer предлагает нам Pro/Toolkit. В папочке можно найти доки (*.html), заголовочники под C (*.h) и Сишные библиотеки (*.lib).

Если бы там была бы DLL-ка - этого поста бы не было. Потому что в этом случае я мог бы просто выкинуть lib-файлы, перевести h-файлы на Delphi и подключить готовую DLL-ку напрямую.

Но по какой-то причине никакой DLL нет, есть только lib (понятно, что это два разных случая - в первом в lib, грубо говоря, содержатся только ссылки на DLL, а во втором - там содержится вся реализация функций). Поэтому, единственный вариант использовать Pro/Engineer в Delphi - это собрать эту самую DLL-ку самому. Именно этому и посвящён этот пост.

Собираем DLL для Delphi из Сишных lib-файлов.

1). Создаём проект DLL в C++.

Для начала нам надо бы поставить MSVS (Microsoft Visual Studio), а точнее - её C++ часть. После установки я залез в File/New/Project и выбрал "Win32 Project". Затем указал тип проекта DLL и отметил галочку "Exports symbols" (в конце-концов, это единственное, зачем нам нужна эта DLL - чтобы она экспортировала функции из lib-файлов).

После сохранения (да, перед этим я удалил демо-код, который создал wizard) я создал подпапки includes и lib в папке с проектов, в которые перетащил *.h и *.lib файлы из папки с Pro/Toolkit. После чего, залез в настройки проекта и после 10-ти минутного копания в настройках нашёл опции, куда можно вписать эти два каталога ("Additional Include/Library directories").

2). "Подключаем uses-ы".

Далее, нам надо как-то подключить все заголовочники. Не проблема: вспоминаем свои навыки в DOS, открываем cmd, входим в каталог Includes (поскольку я использую Total Commander, то я сперва вошёл в каталог, а затем запустил cmd) и пишем:
dir *.h > t.txt
После этого мы получаем список всех файлов в текстовом файле t.txt. Они очень красиво расположены по колонкам, поэтому осталось только открыть файл в продвинутом редакторе (я воспользовался редактором Delphi) и удалить начала строк. Строки
...
2009.04.08 17:20 14'191 ProAnimate.h
2009.04.08 17:20 13'416 ProAnnotation.h
2009.04.08 17:20 31'698 ProAnnotationElem.h
2009.04.08 17:20 5'268 ProAnnotationFeat.h
2009.04.08 17:20 3'100 ProANSI.h
2009.04.08 17:20 6'621 ProArray.h
...
Становятся:
...
ProAnimate.h
ProAnnotation.h
ProAnnotationElem.h
ProAnnotationFeat.h
ProANSI.h
ProArray.h
...
После чего, два Search & Replace сделают нам:
...
#include "ProAnimate.h"
#include "ProAnnotation.h"
#include "ProAnnotationElem.h"
#include "ProAnnotationFeat.h"
#include "ProANSI.h"
#include "ProArray.h"
...
(к счастью, каждый заголовочник начинается с 'Pro', поэтому мне легко удалось сделать замену 'Pro' -> '#include "Pro').

Полученный текст я вставил в проект (насколько я понимаю, это можно сделать в главный cpp-файл или же в главный h-заголовочник проекта).

3). Подготовка списка функций.

Окей, следующий шаг - сказать, что каждая функция из этих заголовочников должна быть экспортирована. Для этого нам нужен список всех функций в h-файлах.

Я пробежался по исходникам и заметил, что каждая экспортируемая функция начинается с 'extern'. Следовательно, нам просто нужно вытащить все такие строки из всех h-файлов. Для этого, я сперва слил все файлы в один большой файл командой:
copy *.h data.txt
Получил один большой файл в 4 Мб. Далее, я помнил, что в DOS была вроде бы утилитка grep, которая могла дёргать строки из файла по условию - её также применяли для фильтрации вывода (и, вроде бы, она пришла из unix). Я попробовал вызвать grep из cmd на удачу - и, действительно, такая утилитка нашлась, хотя, как оказалось, она не входит в состав ОС, а была установлена с каким-то средством разработки (скорее всего - Delphi).

Почитав справку (grep ?), я запустил её с такими параметрами:
grep ^extern* data.txt > grep.txt
Я ничего не понимаю в регулярных выражениях и, по моему мнению, эта команда должна была выдернуть из файла все строки, начинающиеся с 'extern'. На самом деле она выдернула все строки, содержащие 'extern' (все лишние - это слова типа external и т.п.). Если указать "extern " - строк будет поменьше, хотя в заголовочниках также встречаются extern + TAB. TAB в командной строке мне задать не удалось. Поэтому сделал в несколько этапов: сперва
grep extern data.txt > grep.txt
Затем:
grep -v+ extern[a-z] grep.txt > grep2.txt
Конечный список пришлось слегка подрихтовать вручную. В итоге получил список типа:
...
ProMdlDisplay ( ProMdl model );
ProMdlErase (ProMdl handle);
ProMdlEraseAll(
ProMdlEraseNotDisplayed( void );
ProMdlfileCopy ( ProMdlType mdl_type,
ProMdlGtolVisit( ProMdl model,
ProMdlIdGet ( ProMdl model,
ProMdlInit (ProName name,
ProMdlIsModifiable(ProMdl p_model,
...
Потом пришлось написать небольшую программку на Delphi, которая обрезала бы скобки с параметрами (примитив, но как это сделать без программы - мне в голову не пришло).

4). "Выписываем exports".

Как только мы получили полный список функций - осталось оформить его в виде списка функций для экспорта из DLL. Для этого, насколько я помнил, в Сях есть def-файлы. Щёлкнув правой кнопкой по проекту в Solution Explorer, я выбрал Add new item, "Module definition file (.def)".

Далее, я запустил поиск по всему диску на *.def файлы, чтобы посмотреть - как же они должны выглядеть :)

Оказалось, что очень просто. Я просто взял список функций с предыдущего шага и вставил его в def-файл вот так:
LIBRARY ProEToolkit
EXPORTS

Pro2dCadamImportCreate
Pro2dExport
Pro2dImportAppend
Pro2dImportCreate
ProAccessorywindowCreate
ProAnalysisAttrIsSet
...
Осталось щёлкнуть по Build и... получить кучу сообщений о unresolved external symbol. Что есть логично - ведь никакой lib-файл мы ещё не подключили. У нас объявлена куча экспортируемых функций, но линкёр не знает, где брать реализацию.

5). Поиск реализации.

Побегав ещё пять минут по опциям проекта, я нашёл опцию "Additional dependencies", где увидел вписанные kernel32.lib, user32.lib, gdi32.lib и т.д. Туда же я добавил protk_dllmd.lib. Собственно, у Pro/Engineer-а есть такие файлы:
protkmd.lib
protkmt.lib
protk_dll.lib
protk_dllmd.lib
protoolkit.lib
ptasyncmd.lib
ptasyncmt.lib
ptasync_coremt.lib
pt_asynchronous.lib
На всякие разные случаи. Ведь можно писать плагин для ProE, а можно - совершенно внешнюю программу. Плюс разные варианты одного способа. Например, есть подозрение, что mt - это от MultiThreaded, т.е. этот вариант либы скомпилирован с многопоточной версией RTL (или как так это в Сях называется). Я взял наугад (окей, вообще документацию надо читать, но мне лениво). Сборка.... и всего 8 функций не хватает. Причём все предназначены для установки соединения с Pro/Engineer. Похоже, этот вариант lib - для плагинов. Я ещё потыкался наугад, но ничего путного у меня не вышло. Ладно, пора читать документацию :D

Быстро пробежавшись по докам, выясняем, что нам нужен так называемый асинхронный режим, для которого нужно подключать pt_asynchronous.lib. К сожалению, этого не достаточно для успешной компиляции, поэтому я запустил поиск файлов в папке Pro/Toolkit, содержащих слово 'pt_asynchronous'. И нашёл пример (файл make_async):
# Libraries
PTCLIBS = $(PRODEV_SYS)/obj/prodevelop.lib $(PROTOOL_SYS)/obj/protoolkit.lib \
$(PROTOOL_SYS)/obj/pt_asynchronous.lib
LIBS = libc.lib kernel32.lib user32.lib wsock32.lib advapi32.lib mpr.lib winspool.lib netapi32.lib psapi.lib


# Object files
OBJS = pt_async_src.obj pt_utils.obj
Возможно, можно было бы просто использовать готовый make-файл, но я не умею с ними работать. Поэтому я просто взял список lib-файлов. Кстати, в этом списке оказались и некоторые стандартные (не Pro/Engineer-ные) lib-ы, которых не было в настройках моего проекта. Однако, глянув в папку, я увидел так ещё make_async_md и make_async_mt. Судя по описанию в комментах, мне подходит именно make_async_md, хотя никакого pt_asynchronous у него не указано.

Запустив повторный поиск по документации с именем make-файла (это ведь make-файлы?), я, наконец, нашёл нужную информацию:
Standard Libraries
Most Pro/TOOLKIT users will be able to use the standard Pro/TOOLKIT libraries. These libraries are available on all platforms and are used by the majority of Pro/TOOLKIT sample applications.





Library Name




Purpose
protoolkit.lib (.a) Spawn mode library
pt_asynchronous.lib (.a) Asynchronous mode library
protk_dll.lib (.a)DLL mode library


Alternate Libraries
Pro/TOOLKIT offers alternate libraries that may be useful for specialized applications. These libraries are similar to the standard Pro/TOOLKIT libraries in content, but differ in their construction. This makes them compatible with other static and dynamic libraries.

Multi-Threaded DLL Libraries for Windows




Library Name




Purpose
protkmd.lib Spawn mode library
ptasyncmd.lib Asynchronous mode library
protk_dllmd.lib DLL mode library


Multi-Threaded DLL (MD) libraries are used to build a multithreaded DLL for Windows using the /MD compiler flags. You can use these libraries for the following type of applications:
  • DLL mode applications compiled with the MD flags (if required to link with other MD compiled libraries).
  • Asynchonous mode applications compiled as DLLs to be loaded into processes external to Pro/ENGINEER.
The makefiles make_install_md and make_async_md build with these libraries.

Note: Although the library flags provide compatibility with multithreaded components, Pro/TOOLKIT calls must be made within a single thread. Pro/ENGINEER does not respond to calls made from multiple threads.

Multi-Threaded Libraries for Windows





Library Name




Purpose
protkmt.lib Spawn mode library
ptasyncmt.lib Asynchronous mode library


Multi-Threaded (MT) libraries are used to build a multithreaded executable for Windows using the /MT compiler flags. You can use these libraries for spawn and asynchonous mode applications compiled as executables (.exe). The makefiles make_install_mt and make_async_mt build with these libraries.

Note: Although the library flags provide compatibility with multithreaded components, Pro/TOOLKIT calls must be made within a single thread. Pro/ENGINEER does not respond to calls made from multiple threads.
Откуда окончательно следует, что нам нужен вариант "Multi-Threaded DLL Libraries for Windows" для "Asynchronous mode library" и нас интересует файлик make_async_md (всё-таки я угадал верно).

6). Подключаем реализацию.

Ладно, после прописывания библиотек (кстати, во всех файлах первой идёт ссылка на несуществующую lib-у со словом 'develop' в названии - я её просто выкинул) и компиляции получаем конфликты линковки (unresolved symbol-ов уже нет): одна и та же функция входит в несколько lib-файлов. Компилятор MSVS посоветовал использовать ключ /NODEFAULTLIB. Покопавшись в опциях проекта, я решил, что это будет опция "Ignore specific library". Я вписал туда msvcrt.lib и проверил, что это именно то, что мне нужно - увидев в командной строке линкёра слова '/NODEFAULTLIB:"msvcrt.lib"'. Правда, от этого стало только хуже: похоже линкёр вообще на неё забил, и я получил кучу unresolved symbols. Тогда я подсмотрел ещё раз в make_async_md и увидел такие строчки:
$(LINK) /dll /force /ignore:4006 /subsystem:console -out:$(EXE) /debug:none
Похоже, что здесь линкёр запускается с доп. параметрами /force и /ignore:4006 - я просто добавил эти параметры в "Additional parameters" в опциях командной строки линкёра проекта.
Сборка... и успешно! Правда, линкёр ругнулся (warning-ом) на мой флаг: "image being generated due to /FORCE option; image may not run". Тем не менее, DLL-ка была получена всего с 1-м варнингом.

А самое главное, что эта DLL даже работает! :D

Совсем неплохо для того, кто в первый раз запустил MSVS? Вообще, это был мой первый проект на Сях.

Мораль сей истории: не будьте беспомощны! Вы сами можете найти ответы на вопросы! Гугл + эксперименты - и у вас всё получится.

P.S. Да, кстати, я там ещё в опциях проекта нашёл опцию генерации map-файла - пригодится для отладки: EurekaLog его подцепит, если вдруг будет исключение в этой DLL.

P.P.S. Вообще-то я сделал эту DLL ещё год назад, просто недавно мне нужно было использовать новую функцию, а её не оказалось в этой DLL - при сборке первый раз она не попала в def-файл (там был случай extern + TAB - я эти случаи не заметил в первый раз). Поэтому пришлось DLL-ку пересобрать. Ну и заодно решил рассказать об этом здесь. Откровенно говоря, чтобы придумать план генерации DLL по lib-ам и использования уже готовой DLL в Delphi, мне пришлось предварительно погуглить. Я вообще сперва думал, что использовать Pro/Engineer в Delphi невозможно и боялся, что придётся изучать новый язык :) Не, не пришлось.

P.P.P.S. Мне подсказали, что можно приаттачить DLL и демку. Брать тут. В архиве исходники DLL на MS C++. И сама DLL. А также пример-приложение на Delphi, её использующее. Для перекомпиляции папку ProToolkit надо добавить в Search path проекта Delphi (в папке ProToolkit лежат транслированные для Delphi заголовочники (не все, разумеется, а только те, что мне нужны были)). Сама демка представляет автономный exe-файл, который можно запустить отдельно от ProE, ткнуть ей на любой файл ProEngineer-а, и она покажет его атрибутику и состав. В файле ProEClass.pas - весь код по работе с ProE. Это я навыдергал из рабочего проекта, поэтому тут много лишнего, но основные моменты можно посмотреть. Далее, для работы с ProE из внешних программ он требует установки системной переменной окружения PRO_COMM_MSG_EXE. В принципе, демка устанавливает переменную при запуске, но если у вас она не была раньше установлена - вам придётся сделать рестарт.

21 комментарий :

  1. 2) у dir есть удобный свитч /b (bare mode) - который позволяет получать на выходе только имена файлов. А при задании имени, советую использовать вослицательный знак, ибо такой файл всегда будет первым в списке файлов отсортированных по алфавиту.
    dir *.h /b > !.txt
    Я очень часто использую подобную конструкцию для создания package-й из кучи .pas файлов.

    п.с. А для добавления #include " можно использовать search i replace для символа перевода строки(Shift+Enter в Notepad++).

    ОтветитьУдалить
  2. Привет! Спасибо за статью. Я пока только начинаю разбираться с PRO TOOLKIT. Подскажи у него оболочка какая-нибудь есть? Или смысл в том, чтобы написать программу на С и потом запустить ее из ProEngineera Я для себя хочу написать кнопку, ну или добавить менюшку в про_Инженер, чтобы оттуда вызывать нужные мне функции. Возможно что можно и написать dll но их я тоже писать не умею (((

    ОтветитьУдалить
  3. >>> Подскажи у него оболочка какая-нибудь есть?
    Не ясно, о чём идёт речь.

    >>> смысл в том, чтобы написать программу на С и потом запустить ее из ProEngineera
    Я предлагаю заглянуть в документацию по Pro/Toolkit - там достаточно понятно описаны основные варианты интеграции (тема "Developing a Pro/TOOLKIT Application" и далее).
    Затем, если вы планируете писать в Delphi, то собираете себе DLL-ку, по аналогии с тем, как я это сделал выше. Только, вероятно, вы будете подключать другой набор lib-файлов и список функций будет слегка отличаться, т.к. вам нужен плагин для Pro/E, а не внешнее управляющее приложение, как у меня.

    >>> Возможно что можно и написать dll но их я тоже писать не умею (((
    Наверное, сперва стоит всё же овладеть инструментом (языком и средой разработки), а уж потом решать практические задачи?

    ОтветитьУдалить
  4. P.S. Этот пост говорит не столько о Pro/Engineer и Pro/Toolkit, сколько об использовании lib-файлов C в Delphi-приложениях.

    ОтветитьУдалить
  5. Раз вы так хорошо все расписали то не могли бы вы выложить и готовую dll-ку. С уважением за ваш труд!

    ОтветитьУдалить
  6. Не знаю, кому от этого польза будет, но вот: http://dl.getdropbox.com/u/201788/ProEDllExample.7z (680 Кб)

    ОтветитьУдалить
  7. В любом случае польза будет, у нас вот тут идет активное обсуждение, программирования под ProEngineer, если интересно:
    http://www.sapr2k.ru/index.php?showtopic=27306&st=30

    И еще раз огромное спасибо Вам

    ОтветитьУдалить
  8. Спасибо за ссылку - не думал, что это так актуально.

    Посему собрал ещё небольшую демку, скачать тут. Демка представляет автономный exe-файл, который можно запустить отдельно от ProE, ткнуть ей на любой файл ProEngineer-а, и она покажет его атрибутику и состав.

    ПроЕшную DLL-ку брать по предыдущей ссылке.

    В демке скомпиленная прога и исходники. В папке ProToolkit лежат транслированные для Delphi заголовочники (не все, разумеется, а только те, что мне нужны были).
    В файле ProEClass.pas - весь код по работе с ProE. Это я навыдергал из рабочего проекта, поэтому тут много лишнего, но основные моменты можно посмотреть.

    Далее, для работы с ProE из внешних программ он требует установки системной переменной окружения PRO_COMM_MSG_EXE. В принципе, демка устанавливает переменную при запуске, но если у вас она не была раньше установлена - вам придётся сделать рестарт.

    ОтветитьУдалить
  9. P.S. Это пример ВНЕШНЕГО приложения.
    Если вам нужен какой-нибудь встраиваемый расширитель менюшек - вам придётся курить API Toolkit-а самостоятельно.

    ОтветитьУдалить
  10. На будущее про grep: в ДОСе (cmd) ^ - это эскейп символ, поэтому для поиска надо было делать grep "^extern" или grep ^^extern
    ;)

    ОтветитьУдалить
  11. разрешите мой вопрос есть ли возможность автоматически узнавать и записывать (в параметры) габариты мат модели по тиу ширина длина и высота в любом месте дерева постраения сборки очень надо

    ОтветитьУдалить
  12. Я же сказал, что ничего не понимаю в Pro/Engineer, а уж тем более в этом вашем "по тиу ширина длина и высота в любом месте дерева постраения" (ругательства-то какие страшные).

    Ещё раз: эта статья о том, как использовать Сишные файлы в Delphi. Всё. Точка.

    Со своими проблемами идите на форумы.

    ОтветитьУдалить
  13. P.S. Ах, да, забыл сказать: использовал WildFire 3.0.

    ОтветитьУдалить
  14. Александр, респект за объем и качество работы! С уважением,бывший коллега.

    ОтветитьУдалить
  15. На мой взгляд вместо ваяния программы (Потом пришлось написать небольшую программку на Delphi, которая обрезала бы скобки с параметрами (примитив, но как это сделать без программы - мне в голову не пришло).) можно было открыть файл в обычной MS Exel, как табуляцию указать символ "(", проделать необходимые действия (как то: удалить все колонки слева, добавление колонки с текстом "#include ") и сохранить как текстовый файл или в буфер.
    Данный метод спасал по жизни очень часто =)

    ОтветитьУдалить
  16. Тема интересная, GunSmoker, подскажи плииз, как воспользоваться твоей демкой, вываливается ошибка инициализации ProE. Может нужна библиотечка какая, кстати, не получилось скачать файл по ссылке http://dl.getdropbox.com/u/201788/ProEDllExample.7z (680 Кб). Спасибо!

    ОтветитьУдалить
  17. Ссылка на демку есть в тексте поста: http://dl.dropbox.com/u/201788/Projects/Demos/ProEDemo.7z.

    Ошибка инициализации - смотря какая ошибка. Вообще, непонятно как проверяли, если демку скачать не смогли.

    Что нужно проверить:
    1. DLL находится в путях поиска программы.
    2. Моя DLL собрана для ProE/WF 3. Без понятия, что там будет в других версиях.
    3. Для работы с Pro/E из внешних приложений требуется установка системной переменной окружения PRO_COMM_MSG_EXE. См. SDK Pro/E для дальнейшей информации.

    ОтветитьУдалить
  18. Александр, подскажите пожалуйста. Скачал, вашу библиотеку и демку. Теперь хочу написать подобную программу в Delphi, только для 4 ProE. Ваша библиотека подойдет для моей задачи? Так же может у вас остались какие-то материалы по Pro/E а именно по апи функциям. Какие вы использовали для получения состава сборки и атрибутов? Спасибо!

    ОтветитьУдалить
  19. Я понятия не имею, ProE 4 я даже не видел. Я этим давно уже на занимаюсь, ProE у меня нет, доков тоже.

    Посмотрите по документации ProToolkit для ProE 4. Там должно быть написано, какие требования, что делать и т.п.

    Будет ли работать библиотека - тоже без понятия. Если не будет - надо пересобрать с тулкитом от 4-го ProE.

    ОтветитьУдалить
  20. Конвертация форматов COFF и OMF — это больная тема, и про UniLink не упоминают, потому что он не находится по ключевым словам COFF OMF converter. Это линкер, а нужен якобы конвертер.

    Тем не менее, при помощи UniLink можно переделать COFF (GCC) формат в OMF (Delphi) формат. Таргеты UniLink включают в себя библиотеки C++ Builder, как динамические, так и статические. Статическую библиотеку можно в исходниках на Delphi подключить командой $L, далее, используя export; сделать все те же объявления, и на выходе получить единый исполняемый файл.

    ОтветитьУдалить

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и (опционально) ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку.

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.

Примечание. Отправлять комментарии могут только участники этого блога.