-
Bordo (01.09.09 13:52) [0]Здравствуйте, коллеги!
Неделю мучаюсь над странным поведением 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.
Процедура [i]ShowModuleForm[/i] экспортируется так:
library ModDelphi;
uses
ShareMem,
SysUtils,
Classes,
TestFormUnit in 'TestFormUnit.pas' {TestForm};
{$R *.res}
exports ShowModuleForm;
begin
end.
При открытии формы этого модуля в заголовке будет написано DelphiTestModule - 1.
Вот код на C++ c использованием WTL:
#include "stdafx.h"
#define MOD_WTL_API __declspec(dllexport)
#include <windows.h>
#include <atlbase.h>
#include <atlapp.h>
extern CAppModule _Module;
#include <atlwin.h>
#include <atlgdi.h>
#include <atlmisc.h>
CAppModule _Module;
using namespace std;
// Функция вызова плагина
extern "C" MOD_WTL_API BOOL ShowModForm(HWND parent);
// Точка входа в DLL
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
// Инициализируем модуль
_Module.Init(0, hModule, 0);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
_Module.Term();
break;
}
return TRUE;
}
// Главное окно плагина
class CMainWindow :
public CWindowImpl<CMainWindow, CWindow, CFrameWinTraits>
{
BEGIN_MSG_MAP(CMainWindow)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
END_MSG_MAP()
LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
PostQuitMessage(0);
return 0;
}
};
MOD_WTL_API BOOL ShowModForm(HWND parent)
{
// Создаём главное окно модуля.
CMainWindow wnd;
wnd.Create(NULL, CWindow::rcDefault);
const UINT WM_ACME_REGISTERMODWND = ::RegisterWindowMessage(L"WM_ACME_REGISTERMODWND");
const LRESULT rslt = ::SendMessage(parent, WM_ACME_REGISTERMODWND, (WPARAM)((HWND)wnd), 0); ///< Возвращает всегда 0!
char buffer[80] = {0};
_itoa(rslt, buffer, 10);
CString title(L"WtlTestModule - ");
title += rslt;
::SetWindowText(wnd, title);
wnd.ShowWindow(SW_SHOW);
// Запускаем цикл сообщений
CMessageLoop loop;
loop.Run();
return TRUE;
}
При открытии формы этого модуля в заголовке будет написано WtlTestModule - 0. Т.е. объявленный в программе API для подключения модулей не работает.
Подозреваю, что тайна может заключаться в использовании TTestForm.Create(Application), но как его использовать в C++? -
clickmaker © (01.09.09 13:58) [1]TTestForm.CreateParented(appWnd)
-
Rouse_ © (01.09.09 14:20) [2](WPARAM)((HWND)wnd)
замени на
wnd::m_hWnd -
Bordo (01.09.09 15:35) [3]Rouse_, тогда уж на
reinterpret_cast<WPARAM>(wnd.m_hWnd)
но к вопросу это не относится. -
Bordo (01.09.09 15:41) [4]
> clickmaker © (01.09.09 13:58) [1]
> TTestForm.CreateParented(appWnd)
Это на CBuilder? К сожалению, не могу его использовать -- работаю на Visual Studio 2005 и gcc 3.4.5 (mingw-vista special r3). Есть способ использовать VCL в этих компиляторах? -
Bordo (01.09.09 15:44) [5]Мне тут указали, что procedure ShowModForm(appWnd: HWND); вызывается как register (__fastcall в С++).
И правда, если объявитьprocedure ShowModForm(appWnd: HWND); stdcall;
, то в заголовке Дельфийской формы тоже будет ноль. К сожалению, пока что не удалось подобрать нужный спецификатор вызова для C++ версии. -
Rouse_ © (01.09.09 16:25) [6]
> но к вопросу это не относится.
Это как это не относится? :) У тебя же код разные вещи делает :)
В дельфи аналоге (который работает) ты передаешь в SendMessage хэндл вновь созданной формы, а в сишном варианте передаешь указатель на CMainWindow который зачем-то еще и к хэндлу приводишь. вместо того чтобы передать тот-же хэндл, хранящийся в параметре m_hWnd. Разница есть?
Ну а stdcall применить к ShowModForm - это тебя хитро надоумили. Стек на 4 байта уплыл, понятно почему после этого и дельфийский аналог перестал работать. -
Bordo (01.09.09 17:16) [7]
> Rouse_ © (01.09.09 16:25) [6]
> > но к вопросу это не относится.Это как это не относится?
> :) У тебя же код разные вещи делает :)В дельфи аналоге
> (который работает) ты передаешь в SendMessage хэндл вновь
> созданной формы, а в сишном варианте передаешь указатель
> на CMainWindow который зачем-то еще и к хэндлу приводишь.
> вместо того чтобы передать тот-же хэндл, хранящийся в параметре
> m_hWnd. Разница есть?
1. не указатель, а локальную переменную;
2. CWindow автоматически приводится к HWND через тот же m_hWnd.
> Ну а stdcall применить к ShowModForm
> - это тебя хитро надоумили. Стек на 4 байта уплыл, понятно
> почему после этого и дельфийский аналог перестал работать.
Да всё дело оказалось в calling conventions. Именно по этому не работает код на С++: я как порядочный человек использовал stdcall для экспорта, а авторы хост-программы плевать хотели на стандарты и соглашения.
Теперь разбираю примерчик отсюда: http://www.codeguru.com/forum/showthread.php?t=466266 -
Сергей М. © (01.09.09 17:26) [8]
> авторы хост-программы плевать хотели на стандарты и соглашения
Имеют полное право плевать.
А вот то что они якобы не указали в своем PluginSDK требуемые соглашения о вызовах - это довольно подозрительно. -
Bordo (01.09.09 17:41) [9]
> Сергей М. © (01.09.09 17:26) [8]
>
> Имеют полное право плевать. А вот то что они якобы не указали
> в своем PluginSDK требуемые соглашения о вызовах - это довольно
> подозрительно.
Иметь-то имеют, но не надо тогда заявлять, что можно разрабатывать модули на чём угодно. И всё-таки мне кажется, что они были "не в курсе" каких-то там соглашений. -
> Bordo (01.09.09 13:52)
> TTestForm.Create(Application), но как его использовать в
> C++?
Application - никак. Т.е. влиять на него можно только сабклассив его окно.
Кроме того, есть глобальные переменные Screen, PopupMenues и т.п.,
в каждой из которых притаились ржавые грабли.
Подозреваю даже неправомерность
> // Запускаем цикл сообщений
если, конечно, это не модальный диалог.
--
Regards, LVT. -
Bordo (02.09.09 05:46) [11]Итак, всё дело было в разъезжании стека из-за несоответствия 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, буду очень признателен, потому что в ассемблере я совершенно не разбираюсь.
Если кто-нибудь знает, как правильно написать