Самый простой способ вызвать диалоговое окно индикатора выполнения из C ++ dll (windows) - PullRequest
1 голос
/ 15 февраля 2012

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

Однако мне нужен внешний dll, чтобы вызвать диалоговое окно индикатора выполнения (обслуживаемый мной COM-клиент не может сделать это сам по разным причинам).Итак, как мне это сделать?Все примеры, которые я видел до сих пор, вращаются вокруг приложений Win32, которые имеют точку входа WinMain;что можно сделать, если мы уже в вызове dll, когда диалог необходим?

Я новичок в программировании Windows GUI, так что здесь я не очень разбираюсь.Существующий код включен ниже - конкретные предложения о том, что звонить, где будут оценены.Я предполагаю, что мне может понадобиться запустить второй поток, чтобы обновить диалоговое окно прогресса.

Внутренний dll .h файл (для неявных ссылок):

#define INNER_API extern "C" __declspec(dllimport) 

//create calculation, passing callbacks for warning messages and progress bar
INNER_API Calculation* __stdcall calc_create(...blah...,
    int (__cdecl *set_progressor_callback)(long),
    int (__cdecl *print_warning_callback)(const char*));

INNER_API void __stdcall calc_run(Calculation *c);

Затем во внешней dll, com-оболочке, ComWrapperObject.cpp:

    int my_progressor_callback(long progress)
    {
         //set progressor to equal progress, but how?
         return 0;
    }

    STDMETHODIMP ComWrapperObject::do_calculation()
    {
        //fire up progress bar and message window here, but how?

        Calculation *calc = calc_create(...blah..., &my_progressor_callback);
        calc_run(calc);

        //wait for user to dismiss message window, but how?
        return S_OK;
    }

Ответы [ 2 ]

5 голосов
/ 31 марта 2012

Я публикую новый ответ, который больше соответствует вашему обновленному вопросу (и для того, чтобы иметь право на получение награды). Рассмотрим сначала этот минимальный источник для обычного исполняемого файла, который содержит индикатор выполнения:

#include <Windows.h>
#include <CommCtrl.h>
#pragma comment(lib, "Comctl32.lib")
#include "resource.h"
#pragma comment(linker,"\"/manifestdependency:type='win32' \
    name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
    processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
#define PROGRESSBAR_TIMER_ID 1

/*
 * This callback is invoked each time the main window receives a message.
 */
INT_PTR CALLBACK DialogFunc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  switch(uMsg) {
  case WM_INITDIALOG: {
    /*
     * Fire a timer event each second.
     */
    SetTimer(hwndDlg, PROGRESSBAR_TIMER_ID, 1000, NULL);
    break;
  }
  case WM_TIMER: {
    /*
     * Catch the timer event that is fired each second. Increment the progress
     * bar by 10% each time.
     */
    HWND hwndProgressBar = GetDlgItem(hwndDlg, IDC_PROGRESS1);
    UINT iPos            = SendMessage(hwndProgressBar, PBM_GETPOS, 0, 0);

    /*
     * If the position is already full then kill the timer. Else increment the
     * progress bar.
     */
    if(iPos >= 100) {
      KillTimer(hwndDlg, PROGRESSBAR_TIMER_ID);
    } else {
      SendMessage(hwndProgressBar, PBM_SETPOS, iPos + 10, 0);
    }

    break;
  }
  case WM_CLOSE:
    EndDialog(hwndDlg, 0);
    break;
  default:
    return FALSE;
  }

  return TRUE;
}

BOOL LaunchGUI(HINSTANCE hInstance)
{
  return DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DialogFunc) == 0;
}

int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
  /*
   * Initialise the common controls DLL.
   */
  INITCOMMONCONTROLSEX iccex;
  iccex.dwSize = sizeof(iccex);
  iccex.dwICC  = ICC_PROGRESS_CLASS | ICC_STANDARD_CLASSES | ICC_WIN95_CLASSES;

  if(!InitCommonControlsEx(&iccex)) {
    MessageBox(NULL, L"Problem initialising common controls DLL.", NULL, MB_OK);
    return -1;
  }

  /*
   * Launches the main GUI window.
   */
  LaunchGUI(hInstance);
  return ERROR_SUCCESS;
}

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

  • Содержит одно диалоговое окно, содержащее один индикатор выполнения
  • Устанавливает таймер для срабатывания каждую секунду
  • Каждый раз, когда срабатывает таймер, сообщение таймера запускает индикатор выполнения для обновления

Графически это выглядит так:

Progress Bar

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

  • Загрузить / вставить DLL в цель
  • Пусть DLL экспортирует некоторую процедуру инициализации, позволяющую ей получать информацию о процессе инжектора / клиента.
  • Используйте GetProcAddress и CreateRemoteThread от клиента для вызова этой процедуры инициализации.
  • В DLL используйте полученную информацию для получения HWND клиента.

Конкретно, процедура инициализации будет выглядеть так:

HWND hwndClient = NULL;

BOOL CALLBACK EnumProc(HWND hwnd, LPARAM lParam)
{
  DWORD dwPID;

  GetWindowThreadProcessId(hwnd, &dwPID);
  if(dwPID == lParam) {
    hwndClient = hwnd;
  }
}

/*
 * This code assumes the client has only one window. Given a PID, it populates
 * a global to hold the window handle associated with the PID.
 */
DWORD WINAPI ReceiveClientPID(LPVOID dwPID)
{
  EnumWindows(EnumProc, (LPARAM)dwPID);
}

Код клиента может выглядеть примерно так:

/*
 * Depending on your method of injection, you should have a handle to the
 * target process as well as a HMODULE of the injected DLL.
 */
void InitDLL(HANDLE hProcess, HMODULE hModule)
{
  FARPROC lpInit  = GetProcAddress(hModule, "ReceiveClientPID");
  HANDLE  hThread = CreateRemoteThread(hProcess, NULL, NULL,
      (LPTHREAD_START_ROUTINE)lpInit, (LPVOID)GetCurrentProcessId(), NULL, NULL);

  if(hThread == NULL) {
    MessageBox(NULL, L"Problem calling init routine in DLL", NULL, MB_OK);
  } else {
    CloseHandle(hThread);
  }
}

Итак, теперь у вас есть HWND клиента в DLL и, следовательно, способ связи. Затем вы можете указать пользовательское сообщение в клиенте, чтобы изменить индикатор выполнения:

/*
 * The new progress position can be passed in wParam.
 */
#define WM_UPDATE_PROGRESS_BAR (WM_APP + 1)

Также добавьте соответствующий регистр в DialogFunc (мы можем удалить код WM_TIMER сейчас, потому что он был только там, чтобы продемонстрировать, как взаимодействовать с индикаторами выполнения):

case WM_UPDATE_PROGRESS_BAR:
  SendMessage(GetDlgItem(hwndDlg, IDC_PROGRESS1), PBM_SETPOS, wParam, 0);
  break;

А теперь, чтобы вызвать изменения в индикаторе выполнения клиента, DLL просто нужно сделать:

SendMessage(hwndClient, WM_UPDATE_PROGRESS_BAR, ..., 0);

Обратите внимание, что WM_UPDATE_PROGRESS_BAR также необходимо переопределить в DLL.

Чтобы все это соответствовало вашему текущему коду:

/*
 * Assumed progress is between 0 and 100. Otherwise it has to be
 * normalised so this is the case (or the range of the progress bar
 * in the client has to be changed).
 */
int my_progressor_callback(long progress)
{
     SendMessage(hwndClient, WM_UPDATE_PROGRESS_BAR, progress, 0);
     return 0;
}
2 голосов
/ 16 февраля 2012

Поскольку вы заявляете, что DLL не имеет графического интерфейса, а клиент обрабатывает все взаимодействия с пользователем, почему бы вам не отправить вместо этого информацию о ходе выполнения и показать ее там?

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...