Проблемы с PostMessage из C++ .dll в приложение Delphi Forms

У меня есть приложение Windows Forms, написанное на Delphi 7 и C++.dll, написанное с использованием MFC.

В настоящее время я пытаюсь реализовать базовую публикацию сообщений из .dll в основной исполняемый файл, чтобы показать процесс расчета пользователя на индикаторе выполнения, но столкнулся с несколькими проблемами.

Позвольте мне сначала описать мой подход. Я регистрирую простое сообщение в своем приложении Delphi, например:

WM_MSG := RegisterWindowMessage('WM_MSG');

и сделайте то же самое в библиотечной части:

UINT nMsgID = RegisterWindowMessage(_T("WM_MSG"));

Это нормально: при отладке я вижу одинаковые значения с обеих сторон.

Моя библиотечная функция выглядит так (просто фиктивный пример для проверки индикатора выполнения):

extern "C" __declspec(dllexport) int MyFunction() {  
  UINT nMsgID = RegisterWindowMessage(_T("WM_MSG"));
  HWND hWnd = FindWindow(NULL, "Form1");
  if (hWnd > 0)
    for (int i = 0; i < 100000; i++) {
      int param = ceil(100 * (double) i / (double) 100000);
      PostMessage(hWnd, nMsgID, param, NULL);
    }
  return 1;
}

Исполняемое событие OnMessage:

procedure TForm1.OnMessageEvent(var Msg: tagMSG; var Handled: Boolean);
begin
  Handled := True;
  if Msg.message = WM_MSG then
    ProgressBar1.Position := Msg.wParam
  else Handled := False;
end;

Вызов функции С++ из исполняемого файла:

procedure TMyFunctionDLL.Execute;
var
  i: Integer;
  tHWND: HWND;
begin
  tHWND := FindWindow(nil, 'mainF');
  i := Func;
end;

Первая проблема заключается в том, что значения переменных tHWND и hWnd необъяснимо отличаются. После некоторых исследований я обнаружил 3 ситуации: 1. Отрицательный или положительный огромный hWnd 2. Нулевой hWnd 3. Неопределенный ('???')

Во всех случаях переменная hWnd помечается как неиспользуемая, и я не знаю, что это значит. Самое интересное, что код РАБОТАЕТ, если я тестирую его в очень простой форме Delphi (только с одним модулем). Эта простая форма Delphi хорошо работает с моим реальным кодом C++ .dll, где вычисляются реальные данные. Но когда я использую свое обычное приложение Delphi (много модулей, но все же одну форму), кажется, что событие OnMessage основного приложения не перехватывает какие-либо события из C++ dll.

Итак, есть 2 вопроса: 1. почему значения hWnd всегда разные и почему они "не используются"? 2. как я могу заставить мое основное приложение правильно работать с индикатором прогресса?

Я использовал разные подходы для решения этой проблемы. Например, передача Application.Handle или Form1.Handle в качестве параметра функции в библиотеку C++. Ни один из них не работал, даже не говоря об изменении значения параметра при передаче (я думаю, это должен быть отдельный вопрос). Также я пробовал использовать ::FindWindow() и ::PostMessage() вместо FindWindow() и PostMessage(). (в чем между ними разница?), это тоже не помогло. Я пытаюсь улучшить ситуацию уже целый день, но понятия не имею, как это решить. Помогите мне с любыми идеями, пожалуйста.


person Rail Suleymanov    schedule 07.06.2012    source источник
comment
FindWindow возвращает 0 в случае сбоя. Отрицательный HWND не проблема. Одна функция ищет Form1, а другая — mainF, поэтому, конечно, вы получаете разные дескрипторы окон.   -  person David Heffernan    schedule 07.06.2012
comment
Извините, я забыл заменить «mainF» на «Form1». «mainF» — это имя моей общей формы приложения, «Form1» — имя моей тестовой формы приложения. Думайте о «mainF» как о «Form1» здесь, пожалуйста, я имею в виду, что ошибки возникают в любом случае.   -  person Rail Suleymanov    schedule 07.06.2012


Ответы (4)


В дополнение к тому, что заявляли другие, лучшим дизайном было бы, чтобы EXE передал свой HWND напрямую в DLL, тогда DLL не нужно было бы искать его. Это имеет дополнительное преимущество, заключающееся в том, что EXE может затем решить, в какой HWND файл DLL должен отправлять свои сообщения. Я бы использовал AllocateHWnd(), чтобы создать для этого специальное окно.

Попробуй это:

UINT nMsgID = RegisterWindowMessage(_T("WM_MSG")); 

extern "C" __declspec(dllexport) int __stdcall MyFunction(HWND hWnd) {   
    if ((nMsgID != 0) && (hWnd != NULL)) {
        for (int i = 0; i < 100000; i++) { 
            int param = ceil(100 * (double) i / (double) 100000); 
            PostMessage(hWnd, nMsgID, param, 0); 
        } 
    }
    return 1; 
} 

.

unit Unit1;

interface

...

var
  DllWnd: HWND = 0;

implementation

var
  WM_MSG: UINT = 0;

procedure TForm1.FormCreate(Sender: TObject); 
begin 
  DllWnd := AllocateHWnd(DllWndProc);
end; 

procedure TForm1.FormDestroy(Sender: TObject); 
begin 
  if DllWnd <> 0 then
  begin
    DeallocateHWnd(DllWnd);
    DllWnd := 0;
  end;
end; 

procedure TForm1.DllWndProc(var Message: TMessage); 
begin 
  if (Message.Msg = WM_MSG) and (WM_MSG <> 0) then 
    ProgressBar1.Position := Message.WParam
  else
    Message.Result := DefWindowProc(DllWnd, Message.Msg, Message.WParam, Message.LParam); 
end; 

...

initialization
  WM_MSG := RegisterWindowMessage('WM_MSG');     

end.

.

uses
  Unit1;

function DllFunc(Wnd: HWND): Integer; stdcall; external 'My.dll' name 'MyFunction'; 

procedure TMyFunctionDLL.Execute;   
var   
  i: Integer;   
begin   
  i := DllFunc(DllWnd);   
end;   
person Remy Lebeau    schedule 07.06.2012
comment
Спасибо, Реми. Я попробую использовать __stdcall и другие вещи, которые вы предложили. О прямой передаче HWND: я пытался передать Application.Handle, Form1.Handle и Form1.ActiveControl.Handle. Первые два параметра изменились при передаче, последний приводил к падению программы. Может AllocateHWnd поможет, проверю. - person Rail Suleymanov; 08.06.2012
comment
когда я отмечаю свою функцию С++ __stdcall, она даже не позволяет отладчику вводить код... - person Rail Suleymanov; 08.06.2012
comment
Убедитесь, что объявление Delphi функции DLL соответствует объявлению C++, особенно соглашению о вызовах (__stdcall в C++, stdcall в Delphi). Несоответствия могут вызвать симптомы, которые вы описали. Я обновил свой ответ. - person Remy Lebeau; 09.06.2012
comment
Теперь мой исполняемый файл Delphi даже не отлаживает. Вылетает с ошибкой Точка входа в процедуру MyFunction не может быть расположена в динамической библиотеке My.dll... - person Rail Suleymanov; 09.06.2012
comment
Это из-за украшения имени. Самый быстрый способ вернуться к здравомыслию — это удалить __stdcall из C++ и пометить импорт Delphi с помощью cdecl. - person David Heffernan; 09.06.2012
comment
Или используйте файл .def в проекте DLL. - person Remy Lebeau; 09.06.2012
comment
Или просто измените объявление Delphi, чтобы оно соответствовало фактическому оформленному имени DLL: function DllFunc(Wnd:HWND): Integer; stdcall; external 'my.dll' name '_MyFunction'; - person Remy Lebeau; 09.06.2012

Результаты FindWindow могут быть нулевыми или ненулевыми. Значения дескриптора не лежат на числовой прямой. Это просто разные значения, поэтому нет смысла применять к ним операторы неравенства. Иными словами, значения дескрипторов могут казаться отрицательными, поэтому не думайте, что действительные дескрипторы всегда будут больше нуля.

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

Используйте FindWindow только в крайнем случае. Он не предлагает средства для обнаружения, когда есть несколько окон, которые соответствуют вашим критериям поиска. Он всегда возвращает ровно один результат. По возможности вообще избегайте поиска. Вместо этого сообщите отправителю, в какое именно окно отправлять сообщения. Вы говорите, что пробовали это и потерпели неудачу, но я призываю вас продолжить поиски в этом направлении. Проблема, с которой вы столкнулись, вероятно, заключалась в несовпадающем соглашении о вызовах. Убедитесь, что и DLL, и хост-приложение используют stdcall.


Как только вы убедитесь, что отправляете сообщения в нужное окно, тогда вы можете беспокоиться о том, почему ваши сообщения не работают с индикатором выполнения правильно. Я вижу как минимум две причины:

  1. Пока работает функция DLL, ваша основная программа, вызвавшая DLL, не работает. Он ожидает возврата кода DLL. Это означает, что ваша основная программа не обрабатывает никаких сообщений. DLL отправляет кучу сообщений, но они еще не обрабатываются. Они не будут обработаны, пока программа не вернется к своему циклу обработки сообщений.

  2. Размер очереди сообщений Windows по умолчанию составляет 10 000. Вы отправляете в очередь в 10 раз больше сообщений и не обрабатываете их до того, как остановитесь, поэтому, даже если очередь была полностью пуста до того, как вы начали (что маловероятно, поскольку вы, вероятно, запускаете эту функцию с помощью ввода с клавиатуры или мыши) , вы получите только одну десятую сообщения. Когда очередь заполнена, PostMessage просто отбрасывает сообщение. А поскольку значение, которое вы отправляете вместе с сообщением, представляет собой целое число от 0 до 100, довольно бессмысленно отправлять 100 000 из них, когда только 101 из них будет содержать значимую информацию.

person Rob Kennedy    schedule 07.06.2012
comment
Спасибо, Роб, за ваше предложение, теперь я вижу, что моя проверка hWnd на положительность была неправильной. Я попробую ваши предложения, надеюсь, они помогут. - person Rail Suleymanov; 08.06.2012

Если идентичные вызовы FindWindow возвращают разные окна, у вас должно быть несколько окон с именем Form1. Попробуйте дать этим разным окнам разные имена, чтобы их можно было однозначно идентифицировать.

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

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

person David Heffernan    schedule 07.06.2012
comment
Дэвид, вот что показывает WATCH: Имя: hWnd; Значение: 0x000108a4 {неиспользуемый=2097227}; Тип: HWND__ *. Именно это я имел в виду под неиспользованным. Значение hWnd не всегда такое. Иногда может быть??? или 0. В случае с простой тестовой формой это всегда работало (Form1), в случае с моим общим приложением это не работало никогда. - person Rail Suleymanov; 08.06.2012

Хорошо, кажется, я решил проблему... Я попробовал предложение Реми объявить экспортированную функцию

function MyFunction (fHWND: HWND): Integer; cdecl; external 'My.dll'

с соглашением о вызовах cdecl, как советовал Дэвид. Мое предыдущее объявление функции выглядело так

TMyFunction = function (fHWND: HWND): Integer;

и я полагаю, что это была проблема. Спасибо за вашу помощь!

P.S. Теперь, как я могу закрыть вопрос?

person Rail Suleymanov    schedule 09.06.2012