从C ++ dll(windows)弹出进度条对话框的最简单方法

时间:2012-02-15 16:40:46

标签: winapi dll frameworks

我正在编写一个dll,它是另一个没有COM支持的dll(内部dll)的COM包装器。内部dll执行冗长的计算,并让外部dll通过回调函数知道进度如何。外部dll只是使函数在COM上可见。

但是,我需要外部dll弹出一个进度条对话框(我正在服务的COM客户端因各种原因无法自行完成)。那我该怎么做呢?到目前为止,我看到的所有示例都围绕着具有WinMain入口点的Win32应用程序;如果我们在需要对话时已经在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 个答案:

答案 0 :(得分:5)

我发布的新答案与您更新的问题更相关(并且有资格获得赏金)。首先考虑这个包含进度条的常规可执行文件的最小来源:

#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导出一些初始化例程,允许它接收有关注入/客户端进程的信息。
  • 使用客户端的GetProcAddressCreateRemoteThread来调用此初始化例程。
  • 在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);
  }
}

所以现在你在DLL中拥有了HWND客户端,因此可以进行通信。然后,您可以在客户端中指定自定义消息以更改进度条:

/*
 * 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;
}

答案 1 :(得分:2)

由于您声明DLL没有GUI且客户端处理所有用户交互,为什么不将进度信息发送到客户端并将其显示在那里?

如果要在DLL中显示对话框,则执行方式与在常规可执行文件中完全相同。绝对没有区别。如果您希望DLL在更新进度条时继续工作,您可以使用CreateThread启动新线程。

如果您显示一些代码,我们将能够更直接地为您提供帮助。