Неделю мучаюсь над странным поведением SendMessage() из WinAPI -- растолкуйте кто знает.
Условия задачи:
есть чужая программа на Delphi7, исходников нет;
к программе можно подключать простые плагины в виде DLL с экспортируемой точкой входа вида ShowModForm(appWnd: HWND);
подключаемый модуль может общаться с программой посредством SendMessage;
тестовый пример на Delphi работает, а на WTL -- нет.
Код модуля на Delphi:
unit TestFormUnit;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TTestForm = class(TForm)
private{ Private declarations }public{ Public declarations }end;
var
TestForm: TTestForm;
procedure ShowModForm(appWnd: HWND);
implementation{$R *.dfm}procedure ShowModuleForm(appWnd: HWND);
var
rslt : Integer;
msgCode : Integer;
begin
with TTestForm.Create(Application) do
try
msgCode := RegisterWindowMessage('WM_ACME_REGISTERMODWND');
{WM_ACME_REGISTERMODWND -- одно из сообщений для взаимодействия с приложением,
{Возвращаемое значение должно быть 1 или 2}
rslt := SendMessage(appWnd, msgCode, Handle, 0);
Caption := 'DelphiTestModule - ' + IntToStr(rslt);
ShowModal
finally
Free
end
end;
end.
Здравствуйте, savitar, Вы писали:
S>в паскаль-коде процедура ShowModForm не имеет явного указания на calling convention, что означает register. в C этому соответсвует __fastcall.
Точно, но если объявить
procedure ShowModuleForm(WPWnd: HWND); stdcall;
то перестаёт работать и модуль на Дельфи -- в заголовке окна так же как и в случае с WTL выводится 0.
Я также поэкспериментировал со спецификациями вызова на С++ (Visual Studio 2005):
#define MOD_WTL_CALLSPEC
// __syscall -- ошибка компиляции
// __far __pascal -- ошибка компиляции
// WINAPI -- dll не загружается, выдаётся сообщение об ошибке
// __stdcall -- dll не загружается, выдаётся сообщение об ошибке
// __fastcall -- dll не загружается, выдаётся сообщение об ошибке
// __cdecl -- 0 в заголовке окнаextern"C" MOD_WTL_API BOOL MOD_WTL_CALLSPEC ShowModForm(HWND parent);
Выходит, программисты внешнего приложения "не учли" спецификации вызова и теперь к их приложению можно писать плагины на Дельфи? Если так, то придётся писать обёртку для стандартных dll.
Re: Подключение плагина на C++ к программе на Delphi
Здравствуйте, bordo, Вы писали:
B>procedure ShowModForm(appWnd: HWND);
Аргумент appWnd передаётся в регистре EAX — такое соглашение используется в Delphi по умолчанию. Совет использовать __fastcall на С++ относится к C++ Builder'у. У Microsoft свой __fastcall, там параметр будет ожидаться в регистре ECX — http://msdn.microsoft.com/en-us/library/6xa169sk(VS.71).aspx
По нормальному конечно процедура должна была быть объявлена в программе на Delphi так:
procedure ShowModForm(appWnd: HWND);stdcall;
А если это не так, будут понятные проблемы при использовании не-борландовских компиляторов для написания плагинов.
---
The optimist proclaims that we live in the best of all possible worlds; and the pessimist fears this is true
Re[3]: Подключение плагина на C++ к программе на Delphi
Здравствуйте, bordo, Вы писали:
B>Здравствуйте, savitar, Вы писали:
S>>в паскаль-коде процедура ShowModForm не имеет явного указания на calling convention, что означает register. в C этому соответсвует __fastcall.
B>Точно, но если объявить B>
B>то перестаёт работать и модуль на Дельфи -- в заголовке окна так же как и в случае с WTL выводится 0.
естественно. хост то ожидает, что функция фастсоловская, а ты стдкол пихаешь.
B>Я также поэкспериментировал со спецификациями вызова на С++ (Visual Studio 2005): B>
B>#define MOD_WTL_CALLSPEC
B>// __syscall -- ошибка компиляции
B>// __far __pascal -- ошибка компиляции
B>// WINAPI -- dll не загружается, выдаётся сообщение об ошибке
B>// __stdcall -- dll не загружается, выдаётся сообщение об ошибке
// __fastcall -- dll не загружается, выдаётся сообщение об ошибке
вот тут ты где то накосчил. нуно смотреть, что за ошибки.
B>// __cdecl -- 0 в заголовке окна
B>extern"C" MOD_WTL_API BOOL MOD_WTL_CALLSPEC ShowModForm(HWND parent);
B>
B>Выходит, программисты внешнего приложения "не учли" спецификации вызова и теперь к их приложению можно писать плагины на Дельфи? Если так, то придётся писать обёртку для стандартных dll.
ничего не выходит.
Re[4]: Подключение плагина на C++ к программе на Delphi
Здравствуйте, Jack128, Вы писали:
S>>>в паскаль-коде процедура ShowModForm не имеет явного указания на calling convention, что означает register. в C этому соответсвует __fastcall.
B>>Точно, но если объявить B>>
B>>то перестаёт работать и модуль на Дельфи -- в заголовке окна так же как и в случае с WTL выводится 0.
J>естественно. хост то ожидает, что функция фастсоловская, а ты стдкол пихаешь.
Да, просто теперь я понял в чём корень проблемы.
B>>Я также поэкспериментировал со спецификациями вызова на С++ (Visual Studio 2005): B>>
B>>#define MOD_WTL_CALLSPEC
B>>// WINAPI -- dll не загружается, выдаётся сообщение об ошибке
B>>// __stdcall -- dll не загружается, выдаётся сообщение об ошибке
J>// __fastcall -- dll не загружается, выдаётся сообщение об ошибке
J>вот тут ты где то накосчил. нуно смотреть, что за ошибки.
B>>// __cdecl -- 0 в заголовке окна
B>>extern"C" MOD_WTL_API BOOL MOD_WTL_CALLSPEC ShowModForm(HWND parent);
B>>
С этой проблемой, я уже разобрался -- там декорируются имена функций и хост-программа их не видит. К сожалению, extern "C" не отменяет декорацию для __fastcall функций. К тому же, как правильно заметил wallabyfastcall в C++ <> register в Delphi.
B>>Выходит, программисты внешнего приложения "не учли" спецификации вызова и теперь к их приложению можно писать плагины на Дельфи? Если так, то придётся писать обёртку для стандартных dll. J>ничего не выходит.
Именно так и выходит: они объявили возможность написания сторонних плагинов, но не сказали, что писать их можно только на Борландовских компиляторах, а также проигнорировали стандартные соглашения вызова экспортируемых функций (stdcall).
Re[5]: Подключение плагина на C++ к программе на Delphi
Итак, всё дело было в разъезжании стека из-за несоответствия calling conventions. Проблему удалось решить с помощью простой обёртки на Дельфи:
unit TestFormUnit;
interface
type
HWND = type LongWord;
{$EXTERNALSYM HWND}procedure ShowModuleFormProxy(appWnd: HWND);
implementation
procedure ShowModForm(appWnd: HWND); stdcall; External'mod_wtl.dll';
procedure ShowModuleFormProxy(appWnd: HWND);
begin
mod_wtl_show(appWnd);
end;
end.
Код на C++ остался без изменений.
Параллельно пытаюсь разобраться с вариантом, описанным в http://www.codeguru.com/forum/showthread.php?t=466266 . Если кто-нибудь поделится рабочим вариантом для Visual C++ 2005, а ещё лучше для gcc 3.4.5 под mingw, буду очень признателен, потому что в ассемблере я совершенно не разбираюсь.
Re[6]: Подключение плагина на C++ к программе на Delphi
Здравствуйте, bordo, Вы писали:
B>Параллельно пытаюсь разобраться с вариантом, описанным в http://www.codeguru.com/forum/showthread.php?t=466266 . Если кто-нибудь поделится рабочим вариантом для Visual C++ 2005, а ещё лучше для gcc 3.4.5 под mingw, буду очень признателен, потому что в ассемблере я совершенно не разбираюсь.
Танцы с бубном, основанные на не- или слабо- документированных особенностях сишных компиляторов. Беда в том что неборландовские компиляторы C++ ни при каком соглашении о вызовах не ожидают аргумент функции в регистре EAX, а именно туда он передаётся. Если не разбирался на уровне ассемблера в коде, который компилятор генерирует при вызове функций, лучше этим не заниматься. Правильный вариант — это proxy dll.
---
The optimist proclaims that we live in the best of all possible worlds; and the pessimist fears this is true
Re[7]: Подключение плагина на C++ к программе на Delphi
Здравствуйте, wallaby, Вы писали:
W>Танцы с бубном, основанные на не- или слабо- документированных особенностях сишных компиляторов. Беда в том что неборландовские компиляторы C++ ни при каком соглашении о вызовах не ожидают аргумент функции в регистре EAX, а именно туда он передаётся. Если не разбирался на уровне ассемблера в коде, который компилятор генерирует при вызове функций, лучше этим не заниматься. Правильный вариант — это proxy dll.
Не вижу танцев:
имеем соглашения о вызовах функций;
объявляем рабочую функцию в Visual C++ как __stdcall;
объявляем в proxy-функцию в Visual C++ как __naked;
в proxy-функции перетасовываем регистры и стек и делаем jmp на рабочую функцию;
в данном конкретном случае нужно передать один параметр и не нужно возвращать результат.
Осталось запрограммировать это на ассемблере, не зная его
Re[8]: Подключение плагина на C++ к программе на Delphi
Проще __fastcall, тогда не нужно будет возиться со стеком
B> объявляем в proxy-функцию в Visual C++ как __naked; B> в proxy-функции перетасовываем регистры и стек и делаем jmp на рабочую функцию;
Просто нужно переслать EAX в ECX, стек не трогаем
B> в данном конкретном случае нужно передать один параметр и не нужно возвращать результат. B>
B>Осталось запрограммировать это на ассемблере, не зная его
Здравствуйте, wallaby, Вы писали:
W>Дело ваше, я предупредил
На то есть две причины: деплой на 1 длл меньше (у меня их и так несколько), плюс нет лицензионной Дельфи, а есть требование чистоты.
Пробовал собрать под Lazarus, но он отказался экспортировать процедуру без stdcal.
B>> объявляем рабочую функцию в Visual C++ как __stdcall; W> Проще __fastcall, тогда не нужно будет возиться со стеком
VC++ декорирует __fastcall-функции, поэтому пришлось использовать DEF-файл, но это проще, чем возиться со стеком.
B>> в proxy-функции перетасовываем регистры и стек и делаем jmp на рабочую функцию; W>Просто нужно переслать EAX в ECX, стек не трогаем
Я так написал потому что совсем не разбираюсь в ассемблере.
B>>Осталось запрограммировать это на ассемблере, не зная его W>На msdn есть примеры naked-функций — http://msdn.microsoft.com/en-us/library/4d12973a(VS.71).aspx
Обязательно почитаю.
W>Поскольку на C не пишу, могу только набросать идею:
W>
B>Восхитительно, оба способа работают! Теперь буду переписывать то же самое под mingw-gcc (да, вот такой я извращенец)
Провозился с ассемблером два дня впустую. После чего родилось простое решение -- воспользоваться дебаггером (например, OllyDbg) и скопировать ассемблерный код из proxy-dll на Delphi. Всё заработало
Итак, нашёлся окончательный ответ из двух вариантов: использовать proxy-dll на Delphi;
написать proxy-функцию на ассемблере (на Visual C++ можно воспользоваться __naked функциями).
Плюсы и минусы: Пишем на высокоуровневых языках ObjectPascal и C++, но в поставке идёт две dll на один плагин, и нужно иметь лицензионную Дельфи;
нужно немного позаморачиваться с ассемблером и DEF-файлами, но зато всё компактно.