Мне нужно использовать DLL в моем приложении delphi XE3, я получил демонстрационное приложение, написанное на c ++, которое показывает, как вызывать DLL.
Мне удалось вызвать DLL и метод Initialize библиотеки DLL, но я не получаю никаких сообщений от DLL.
Это источник C ++:
Создание обработчика сообщений:
#define WM_POSSTATE WM_APP+1
#define ON_WM_POSSTATE() \
{ WM_POSSTATE, 0, 0, 0, AfxSig_vwl, \
(AFX_PMSG)(AFX_PMSGW)(void (AFX_MSG_CALL CWnd::*)(UINT, LPARAM))&OnPOS },
Определение метода Initialize:
typedef int (INITIALIZE)( char * cPort , UINT Msg, HWND *hWnd_p );
INITIALIZE *Initialize;
HMODULE hPosDll;
Загрузка DLL:
if( (hPosDll = LoadLibrary( "posdll.dll" ) ) == NULL )
{
MessageBox( "Error: can not open posdll.dll",NULL,MB_OK);
exit(1);
}
if( (::Initialize = (INITIALIZE*)GetProcAddress( hPosDll, "Initialize" )) == NULL )
{
MessageBox( "Error: can not find function",NULL,MB_OK);
FreeLibrary( hPosDll );
exit(1);
}
Вызов метода Initialize:
res=::Initialize('com3', WM_POSSTATE , &m_hWnd);
if( res )
{
MessageBox( "Error opening comms",NULL,MB_OK);
FreeLibrary( hPosDll );
exit(1);
}
После вызова Initialize сообщения из DLL в приложение обрабатываются методами OnPos:
void CPosDemoDlg::OnPOS(UINT Result, LPARAM Param )
{
DoStuff;
}
В Delphi я зашел так далеко:
unit UFrmMain;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, MCPOSDLL;
const
WM_POSSTATE = WM_APP + 1;
MCDLL = 'posdll.dll';
type
TInitialize = function(cport: PAnsiChar; Msg: Integer; Handle: HWND):Integer; stdcall;
TFrmMain = class(TForm)
EdCOMPort: TEdit;
BtnConnect: TButton;
LblCOMPort: TLabel;
procedure ON_WM_POSSTATE(var Msg: TMessage); message WM_POSSTATE;
procedure BtnConnectClick(Sender: TObject);
private
DLLHandle: THandle;
public
{ Public declarations }
end;
var
FrmMain: TFrmMain;
implementation
{$R *.dfm}
{-------------------------------------------------------------------------------}
procedure TFrmMain.BtnConnectClick(Sender: TObject);
var
MCInitialize: TInitialize;
Res: Integer;
begin
DLLHandle := LoadLibrary(PChar(MCDLL));
if DLLHandle <> 0 then
begin
@MCInitialize := getProcAddress(DLLHandle, 'Initialize');
if @MCInitialize <> NIL then
begin
Res := MCInitialize(PAnsiChar('com3'), WM_POSSTATE, Self.Handle);
if Res <> 0 then
begin
MessageDlg('Error opening comms', mtWarning, [mbOK], 0);
end;
end;
end
else
begin
MessageDlg('posdll.dll could not be located.', mtWarning, [mbOK], 0);
end;
end;
{-------------------------------------------------------------------------------}
procedure TFrmMain.ON_WM_POSSTATE(var Msg: TMessage);
begin
showmessage('Message received');
end;
end.
С помощью этого решения я могу активировать устройство, подключенное к com3, но обработчик сообщений никогда не запускается.
я думаю &m_hWnd
то, что отправлено с помощью метода Initialize в c ++, отличается от Self.Handle
Я отправляю в Delphi.
Может кто-нибудь мне помочь? Заранее спасибо.
На первый взгляд, это выглядит как ваша декларация
TInitialize = function(cport: PAnsiChar; Msg: Integer; Handle: HWND):Integer;
не совпадает
typedef int (INITIALIZE)( char * cPort , UINT Msg, HWND *hWnd_p );
Обратите внимание, что объявление C ожидает указатель к переменной, удерживающей ручку.
Пытаться
TInitialize = function(cport: PAnsiChar; Msg: Integer; var Handle: HWND):Integer;
и посмотрим, как далеко вы доберетесь.
Функция C ++ объявлена как:
typedef int (INITIALIZE)( char * cPort , UINT Msg, HWND *hWnd_p );
Ваш Delphi-эквивалент объявлен как:
TInitialize = function(cport: PAnsiChar; Msg: Integer;
Handle: HWND): Integer; stdcall;
Я вижу следующие различия в вашей декларации Delphi:
Msg
параметр не подписан, но перевод Delphi подписан.hWnd_p
параметр является указателем на HWND
, В Delphi вы передаете дескриптор окна по значению.cdecl
, но вы объявили код Delphi как stdcall
,Таким образом, в Delphi объявление должно быть:
TInitialize = function(cport: PAnsiChar; Msg: Cardinal;
Handle: PHWND): Integer; cdecl;
где PHWND
тип объявлен как ^HWND
, Я подозреваю, что такой тип объявлен в Windows
блок, но если нет, то вы можете легко объявить об этом.
Или, возможно, более простой перевод будет:
TInitialize = function(cport: PAnsiChar; Msg: Cardinal;
var Handle: HWND): Integer; cdecl;
Конечно, вполне вероятно, что вы пропустили соглашение о вызовах при расшифровке кода C ++. Возможно, функция действительно stdcall
хотя в заявлении, которое вы поместили в вопросе, ничего не сказано.
Кроме того, ваш код использует дескриптор окна формы. Это может быть рискованно. В некоторых ситуациях VCL воссоздает окна. Это приведет к тому, что дескриптор, который вы передали DLL, будет уничтожен. DLL будет продолжать отправлять сообщения в это окно, но ваша форма перестанет получать их, потому что ее окно изменилось.
Способ обойти это — использовать дескриптор окна, который VCL не будет создавать заново. Получить один из тех, позвонив AllocateHwnd
,
Будьте осторожны при звонке Initialize
, Вы передаете PAnsiChar('com3')
как имя порта. Вы должны быть уверены, что буквальное 'com3'
действительно в кодировке ANSI. В идеале вы должны пройти 'com3'
прямо к Initialize
, Если это не скомпилируется (я не уверен, если это будет или нет), тогда передайте PAnsiChar(AnsiString('com3'))
,
С помощью Ханно и Дэвида я изменил свой код, и теперь он работает.
Изменения, которые я сделал:
type
PHWND = ^HWnd;
TInitialize = function(cport: PAnsiChar; Msg: Cardinal; Handle: PHWND):Integer; stdcall;
а также
var
TmpHandle
begin
...
TmpHandle := Self.Handle;
Res := MCInitialize(PAnsiChar(AnsiString('com3')), WM_POSSTATE, @TmpHandle);
Следуя совету Дэвида Хеффермана, я создам отдельный компонент в окончательном приложении, таким образом я буду использовать AllocateHwnd
создать фиксированную ручку.
…