Форум: "Основная";
Поиск по всему сайту: delphimaster.net;
Текущий архив: 2002.02.28;
Скачать: [xml.tar.bz2];




Вниз

Некоторые замечания к статье 


Sleepyhead (http://www.excelsior-usa.com/)   (2002-02-03 14:18) [0]

Здравствуйте!

В статье Кариха Николая (Московская область, г.Жуковский) "Использование и создание DLL в Delphi" http://delphi.mastak.ru/articles/usedll/index.html, по словам автора "отображены основные стороны использования и создания библиотек DLL в Borland Delphi", что, несомненно, так - аккуратно и достаточно подробно описаны случаи и методы использования DLL. Я, пользуясь предложением автора "еще лучше - пишите в конференции этого сайта (Delphi. Общие вопросы)", хочу отметить, что нередко возникает проблема использования в программах, написанных под Delphi, сторонних DLL, разработанных с помощью других систем программирования (СП). Есть и другая, обратная, проблема - как Dll, разработанную под Delphi, использовать в приложениях, разработанных с помощью других СП?

Использование DLL, экспортирующих, скажем так, "вычислительные" функции, не представляет почти никаких проблем, как при использовании из программ на Delphi, так и наоборот. Хотя и здесь, к сожалению, не все сторонние библиотеки могут быть использованы из Delphi. В частности, в Delphi не поддерживается механизм вызова функций с произвольным числом параметров с stdcall-ным соглашением о вызовах (не путать с функциями с произвольным числом параметров в Oject Pascal - это совершенно другое соглашение о вызовах, поддерживаемое в Delphi, которое, очевидно, не может быть использовано для работы с alien-DLL) - а такие функции не редкость в программах на С. Кажется, что это не такая уж и проблема, однако многие программисты, пытающиеся по каким-либо причинам использовать чужие DLL, с завидной регулярностью наступают на эти хорошо замаскированные грабли, когда после вызова из DLL функции, принимающей произвольное число параметров, на стеке остается мусор, не убранный вызывающей Delphi-функцией.

Как ни странно, довольно много программистов, работающих под Delphi, используют внешние Dll, написанные на Java (приношу свои извинения за, возможно, непринятое тут слово Java...). Borland предоставляет даже специальный модуль JNI.pas, который почти эквивалентен Sun"новского jni.h. Я сказал "почти эквивалентен" потому, что в jni.h огромное количество функций как раз могут принимать произвольное число параметров, так что вместо функций CallXXXMethod необходимо использовать CallXXXMethodV, что, конечно, просто запомнить, однако работать с функциями CallXXXMethodV гораздо неудобнее, чем с CallXXXMethod (и, кроме того, замечено, что использование функций CallXXXMethodV чаще приводит к ошибкам). Кстати, подобные проблемы и решения обсуждаются тут Delphi-Java Bridge (JNI Wrapper for Delphi) http://sourceforge.net/projects/djbridge/ Примеры использования JNI для Delphi можно найти тут http://www.excelsior-usa.com/jet.html

Другая проблема - как использовать DLL, написанную под Delphi, в других приложениях? В особенности, если из такой DLL экспортируются формы? Это уже не так тривиально, как может показаться на первый взгляд. Во-первых, всегда необходимо помнить о том, что VCL - сугубо однопоточная библиотека. Все формы, базирующиеся на или разработанные с использованием VCL, должны работать в одном потоке. Если это требование не удовлетворяется, возникает множество проблем! Если "главное" приложение, использующее такую DLL, тоже GUI-вое, то возникает еще и определенный конфликт, что сообщения от всех окон (как от окон "главного" приложения, так и от форм Delphi) будут передаваться в процедуру потока, где все эти окна были созданы - как правильно диспетчирезировать эти сообщения? В таком случае очень желательно разнести создание окон "главного" приложения и окон-форм Delphi в разные потоки. Однако тогда возникает еще одна проблема - как доступаться до форм Delphi и других визульных Delphi-компонентов из другого потока (в котором работает "главная" программа), ведь, как правило, в таких случаях происходит dead-lock всего приложения - VCL не допускает этого! (Решить все эти проблемы можно, и я успешно решал их, однако это уже тема для отдельной статьи)

До свидания,

Sleepyhead



Иван Шихалев   (2002-02-03 15:40) [1]

Во-первых, если соглашение о вызовах stdcall , то очищать стек должна вызываемая функция, а не вызывающая.

Во-вторых, НА КОЙ ЛЕШИЙ экспортировать из DLL формы?! DLL предназначена для экспорта функций - это весьма общая конструкция, которая совершенно не обязана учитывать объектную модель какого бы то ни было языка и библиотеки компонентов. Для таких вещей Borland и предоставляет механизм run-time packages.



Иван Шихалев   (2002-02-03 15:41) [2]

Прошу прощения за забытый закрывающий тег.



Набережных С.   (2002-02-03 17:22) [3]

Никогда не экспортировал формы из DLL, т.к. абсолютно согласен с г. Шихалевым, но внутри использовал. Откуда могут взяться проблемы с диспетчирезацией, если этим занимается функция API, а ей все равно - что DLL, что черт с рогами? Или я не прав?
При разных потоках блокировки тоже быть не должно, т.к. блокировки происходят из-за особенностей работы Delphi-программ с графическим контекстом, а DLL будет использовать собственный механизм, никак не связанный с основным модулем. Или я и здесь не прав?



Sleepyhead (http://www.excelsior-usa.com/)   (2002-02-04 14:26) [4]

Здравствуйте!

Целью моих замечаний было не развертывание каких-либо дискуссий насчет целесообразности использования сторонних DLL в Delphi-программах и наоборот, а обозначение действительно существующих проблем в этой "пограничной" области. Как правило, программисты не от хорошей жизни "смешивают" программы, разработанные под разными СП, а из жизненной необходимости. В особенности, когда часть программы используется очень давно, и ее переписывание - слишком дорогое удовольствие. Другой пример - разработка привычных GUI приложений в некоторых СП хотя и возможна, но крайне затруднена и обременительна (писать на чистом Win API, конечно, можно, но не многие это любят - а большиство сейчас просто не умеет). Поэтому экономически целесообразно разработать графический интерфейс, например, на Delphi, и использовать его в существующих программах. Именно такой случай я и имею в виду, говоря об экспорте форм из DLL.

Насчет соглашения о вызовах stdcall, боюсь, что вы в данном случае не правы. Если вы разрабатываете программу на С, то по соглашению о вызовах stdcall, вызываемая функция очищает стек - за исключением случая, когда она принимает переменное число параметров. В этом случае вызываемая функция просто не знает, какое количество аргументов ей передали! Вызывающая функци обязана при вызове функций с переменным числом аргументов сама зачищать стек после вызова. Это одно из, я бы сказал, "болотных" мест в С/C++, которые вам, знатоку Delphi, простительно не знать. Попробуйте написать маленький пример и посмотреть (в дизассемблере), как происходит передача и прием параметров в таких случаях. Очевидно, что попытки использовать такие функции в Delphi (Object Pascal"е) ни к чему хорошему не приведут. Именно на это я постарался обратить внимание.

Конечно, DLL никак не обязана поддерживать чью-либо объектную модель (гм... мне кажется, я нигде и не говорил этого). А RTP (Run-time Package) - это вообще совершенно из другой области, никакого отношения к использованию Delphi-программ в других программах, разработанных под другими системами программирования не имеет.

Что касается "всем сестрам по серьге", т.е. раздачи событий в оконном интерфейсе, то, конечно же, по сравнению с обычным способом (GetMessage -> TranslateMessage -> DispatchMessage), VCL работает не так, для примера могу порекомендовать посмотреть исходники Forms.pas, function TApplication.ProcessMessage, в особенности на используемые функции IsHintMsg, IsMDIMsg, IsKeyMsg, IsDlgMsg. Для корректной работы VCL вы обязаны использовать метод Run (который, впрочем, ничего особого не делает, кроме как крутит цикл пока не надоест). Таким образом, из-за различия в дизайне визуальных библиотек VCL и той, которая используется для "главной" программы (например, хоть MFC) программа не будет работать.

Именно из-за особенностей работы Delphi-программ с графическим контекстом, практически никогда нельзя доступаться (точнее, модифицировать) графические компоненты ИЗ ВТОРИЧНЫХ ПОТОКОВ без Delphi-синхронизации (не спроста рекомендуют все "заворачивать" в Synchronize метод потока, но, к сожалению, в alien-программе нет возможно использовать дельфевый же класс...)

Если вы не сталкивались ни с одной из указанных проблем, то вам крупно повезло! :-) Желаю и дальше с ними не иметь дела!

Надеюсь, что теперь-то уж точно ни вопросов, ни претензий не осталось. :-)

До свидания,

Sleepyhead

P.S. Чтобы такого сдулать плохого? Пойду еще статей почитаю, глядишь к чему-нибудь прикопаюсь... Ну, вот например http://delphi.mastak.ru/articles/Dapi/index.html от Ketmar"а. :-)

P.P.S. Я и правда не хочу докопаться до кого-нибудь. Я только стараюсь не оставить неясностей - потому и написал эти свои замечания к статье про использование DLL.



Иван Шихалев   (2002-02-04 17:05) [5]

> Другой пример - разработка привычных GUI
> приложений в некоторых СП хотя и возможна,
> но крайне затруднена и обременительна
> (писать на чистом Win API, конечно, можно,
> но не многие это любят - а большиство сейчас
> просто не умеет). Поэтому экономически
> целесообразно разработать графический интерфейс,
> например, на Delphi, и использовать его
> в существующих программах. Именно такой
> случай я и имею в виду, говоря об экспорте форм из DLL.


Можно возвращать не TForm, а его Handle и использовать механизм сообщений. Что снимет, заодно, проблемы с потоками.

> Насчет соглашения о вызовах stdcall,
> боюсь, что вы в данном случае не правы.
> Если вы разрабатываете программу на С,
> то по соглашению о вызовах stdcall,
> вызываемая функция очищает стек -
> за исключением случая, когда она
> принимает переменное число параметров.
> В этом случае вызываемая функция просто
> не знает, какое количество аргументов
> ей передали! Вызывающая функци обязана
> при вызове функций с переменным числом
> аргументов сама зачищать стек после вызова.


Кривизна компилятора C++. stdcall означает, что стек освобождает функция. Для вызова тех функций C, которые требуют, чтобы освобождала вызывающая программа требуется cdecl .

> Именно из-за особенностей работы
> Delphi-программ с графическим контекстом,
> практически никогда нельзя доступаться
> (точнее, модифицировать) графические компоненты
> ИЗ ВТОРИЧНЫХ ПОТОКОВ без Delphi-синхронизации
> (не спроста рекомендуют все "заворачивать"
> в Synchronize метод потока,


Как ни странно :) без синхронизации потоков нельзя обойтись, даже если писать на чистом API.



Набережных С.   (2002-02-05 17:11) [6]

Возможно, я чего-то недопонимаю?... Любой желающий может скачать простенький пример по адресу http://sergeynbr.newmail.ru/TestDll.zip
после чего может высказать все, что он думает о примере и о его авторе.



PVOzerski   (2002-02-06 13:40) [7]

К вопросу о stdcall и cdecl:
а чем stdcаll с переменным числом параметров в таком случае отсличается от cdecl по внутренней
реализации. У меня нехорошее подозрение, что сишный компилятор, наткнувшись на переменное
число параметров, просто игнорирует слово stdcall (есле вообще не откажется компилировать). Он
же даже mangled name для такого монстра сгенерить не сможет (_имя@байты). А для импорта cdecl c
переменным числом параметров в рамках Delphi/FPC проблема зачастую вполне обходима:

procedure CProg1(x:longint);cdecl;external "Cdll.dll" name "_CProg");
procedure CProg2(x,y:longint);cdecl;external "Cdll.dll" name "_CProg");

И вызываем мы ее, любезную, и так, и так. Иное дело, что без извратов мы такое в Паскале сами
не напишем.



ilysha   (2002-02-07 09:23) [8]

RE: Набережных С.

Я ничего не думаю об авторе, но вот, что думает о примере Delphi:

[Error] PrjDll.dpr(45): Undeclared identifier: "TModalResult"
[Warning] PrjDll.dpr(53): Comparing signed and unsigned types - widened both operands

Вопрос ко всем: а почему во всех примерах, авторы намертво привязывают DLL к программе - ведь это самый простой способ. А как насчет динамической загрузки?



Набережных С.   (2002-02-07 18:28) [9]

>ilysha

1.TModalResult описан в модуле Forms(в D5). Можно просто заменить на Integer. На всякий случай: idOk = 1.
2.Warning сам пропадет. Да и остался-бы - здесь не важно.
На пример ушло примерно полчаса, так что могут быть и ляпы. Например там-же, в PrjDll.dpr, в строке 56:

Move(PChar(s)^,Txt^, MAX_PATH);

нужно конечно исправить на:

if Length(s) < MAX_PATH then Move(PChar(s)^,Txt^,Length(s))
else Move(PChar(s)^,Txt^, MAX_PATH);

3.Статическое, динамическое связывание - какая, собственно, разница? Если вам лично нужен пример - напишите, пришлю. Да и на этом форуме они, по-моему, были.

Целью примера было показать, что проблемы с формой в DLL - очень маленькие проблемы.



Sleepyhead (http://www.excelsior-usa.com/)   (2002-02-09 12:54) [10]

Набережных С. > Целью примера было показать, что проблемы
Набережных С. > с формой в DLL - очень маленькие проблемы.

Спасибо за очевидный пример использования Дельфевых форм в DLL из Дельфевых же программ.

Речь вообще-то шла об использовании чужих Dll в Дельфи-программах и, наоборот, Дельфевых форм в DLL из чужих программ. Если бы Ваша "главная" программа была написана, скажем, на С, смогли бы вы с той же легкостью использовать в ней Дельфевые формы?

Судя по всему, подавляющее большинство здесь присутствующих используют Delphi в "чистом" виде. Это очень хорошо потому, что не нужно озабачиваться проблемами взаимодействия программ, разработанных с помощью совершенно разных СП. Надеюсь, что и дальнейшем вам с этим не придется столкнуться. Предлагаю на этой радостной ноте завершить дискуссию о Dll. :-)



Набережных С.   (2002-02-09 13:49) [11]

>Sleepyhead ( http://www.excelsior-usa.com/) (09.02.02 12:54)

Если бы Ваша "главная" программа была написана, скажем, на С, смогли бы вы с той же легкостью использовать в ней Дельфевые формы?

Смог бы. Просто ВСЕ взаимодействие с DLL нужно осуществлять через экспортируемые функции - это элементарно. Еще лучше - ActiveX, и это тоже очевидно.

В частности, в Delphi не поддерживается механизм вызова функций с произвольным числом параметров с stdcall-ным соглашением о вызовах

Для этого давно существует нетипизированный указатель и динамический массив, что гораздо надежней и это опять-же очевидно.

не спроста рекомендуют все "заворачивать" в Synchronize метод потока, но, к сожалению, в alien-программе нет возможно использовать дельфевый же класс...)

Во-первых, Synchronize - просто обертка. Существует масса способов синхронизации.
Во-вторых, "все "заворачивать"" не надо. Надо понимать природу вещей и использовать синхронизацию там, где она действительно нужна. Что и показано в примере.

Действительно, продолжать дискуссию бессмысленно.



Kuibida   (2002-02-12 17:23) [12]

Удалено модератором




Форум: "Основная";
Поиск по всему сайту: delphimaster.net;
Текущий архив: 2002.02.28;
Скачать: [xml.tar.bz2];




Наверх





Память: 0.79 MB
Время: 0.046 c
14-90657          McSimm                2002-01-14 11:26  2002.02.28  
Разная реакция скрипта на обращение из IE и из программы.


3-90477           VladP                 2002-01-31 11:20  2002.02.28  
Неожиданный key violation в автоинк поле


6-90629           Станислав             2001-12-10 15:47  2002.02.28  
IP-адресс


4-90712           Prof!                 2002-01-02 08:49  2002.02.28  
Help me!!!!!!! (DLL)


4-90723           Ryder VII             2001-12-28 23:11  2002.02.28  
Что такое виндовый таймер?