从dll

时间:2018-12-07 17:32:38

标签: c++ windows dll shared-libraries gdi+

我想将GDI +与Pascal脚本一起使用,而Pascal脚本本身并不提供GDI +,但是我不知道为什么使用dll(共享)时,即使窗口被破坏,该过程也不会退出,我的意思是说我尽管没有任何窗口,它仍然可以从任务管理器中看到正在运行的进程。该过程保持空闲状态,即没有任何资源使用

在我的dll中,对于每个新的hwnd,我都钩住了自己的wndproc,并且在WM_Paint消息中,我正在绘制到目前为止需要绘制的指定对象

我正在导出DrawRectangle符号以用于32位的绘图和编译
我的dll是

#include <Windows.h>
#include <gdiplus.h>
using namespace Gdiplus;
#include <objidl.h>
#pragma comment(lib, "Gdiplus.lib")

#include <functional>
#include <map>
#include <memory>
#include <vector>

#define DLL_EXPORT(RETURN_TYPE)                                                \
  extern "C" __declspec(dllexport) RETURN_TYPE __stdcall

void msg(const char *str) { MessageBoxA(nullptr, str, "Message", 0); }
void msg(const wchar_t *str) { MessageBoxW(nullptr, str, L"Message", 0); }

class _GdiManager {
public:
  _GdiManager() {
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr);
  }
  ~_GdiManager() { GdiplusShutdown(gdiplusToken); }

private:
  GdiplusStartupInput gdiplusStartupInput;
  ULONG_PTR gdiplusToken;
} GdiManager;

class DrawableObject {
public:
  virtual void draw(Gdiplus::Graphics &Graphics) = 0;
  virtual ~DrawableObject() = default;
};

namespace DrawableObjects {
class Rectangle : public DrawableObject {
public:
  Rectangle(ARGB Color, int X, int Y, int Width, int Height)
      : m_X{X}, m_Y{Y}, m_Width{Width}, m_Height{Height}, m_Brush{Color} {}
  void draw(Gdiplus::Graphics &graphics) override {
    graphics.FillRectangle(&m_Brush, m_X, m_Y, m_Width, m_Height);
  }

private:
  int m_X, m_Y, m_Width, m_Height;
  Gdiplus::SolidBrush m_Brush;
};

} // namespace DrawableObjects

LRESULT MasterWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

class Painter {
public:
  Painter(HWND hWnd) : m_WindowHandle{hWnd}, m_Graphics{hWnd} {
    m_OriginalWindowProc = (WNDPROC)GetWindowLongW(m_WindowHandle, GWL_WNDPROC);
    SetWindowLongW(m_WindowHandle, GWL_WNDPROC, (LONG)MasterWindowProc);
  }

  LRESULT CallOriginalWndProc(HWND hwnd, UINT uMsg, WPARAM wParam,
                              LPARAM lParam) {
    return CallWindowProcW(m_OriginalWindowProc, hwnd, uMsg, wParam, lParam);
  }

  LRESULT Paint(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    if (uMsg == WM_PAINT) {
      for (auto &o : m_Objects)
        o->draw(m_Graphics);
    } else if (uMsg == WM_DESTROY) {
      PostQuitMessage(0);
    }
    return 0;
  }

  std::vector<std::unique_ptr<DrawableObject>> &Objects() { return m_Objects; }

private:
  HWND m_WindowHandle;
  Gdiplus::Graphics m_Graphics;
  WNDPROC m_OriginalWindowProc;
  std::vector<std::unique_ptr<DrawableObject>> m_Objects;
};

std::map<HWND, std::unique_ptr<Painter>> windowPaint;

LRESULT MasterWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
  auto &p = windowPaint[hwnd];
  auto r = p->CallOriginalWndProc(hwnd, uMsg, wParam, lParam);
  p->Paint(hwnd, uMsg, wParam, lParam);
  return r;
}

auto &insertPainter(HWND hwnd) {
  auto &my_painter = windowPaint[hwnd];
  if (!my_painter)
    my_painter = std::make_unique<Painter>(hwnd);
  return my_painter;
}

DLL_EXPORT(int)
DrawRectangle(HWND hwnd, ARGB LineColor, int startX, int startY, int width,
              int height) {
  auto &my_painter = insertPainter(hwnd);
  my_painter->Objects().push_back(std::make_unique<DrawableObjects::Rectangle>(
      LineColor, startX, startY, width, height));
  return 0;
}

主机程序:

//#include "gdi.cpp"
#include <ObjIdl.h>
#include <Windows.h>
#include <cassert>
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment(lib, "Gdiplus.lib")

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, PSTR, INT iCmdShow) {
  HWND hWnd;
  MSG msg;
  WNDCLASS wndClass;

  wndClass.style = CS_HREDRAW | CS_VREDRAW;
  wndClass.lpfnWndProc = WndProc;
  wndClass.cbClsExtra = 0;
  wndClass.cbWndExtra = 0;
  wndClass.hInstance = hInstance;
  wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
  wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
  wndClass.lpszMenuName = NULL;
  wndClass.lpszClassName = TEXT("GettingStarted");

  RegisterClass(&wndClass);

  hWnd = CreateWindow(TEXT("GettingStarted"),  // window class name
                      TEXT("Getting Started"), // window caption
                      WS_OVERLAPPEDWINDOW,     // window style
                      CW_USEDEFAULT,           // initial x position
                      CW_USEDEFAULT,           // initial y position
                      CW_USEDEFAULT,           // initial x size
                      CW_USEDEFAULT,           // initial y size
                      NULL,                    // parent window handle
                      NULL,                    // window menu handle
                      hInstance,               // program instance handle
                      NULL);                   // creation parameters

  ShowWindow(hWnd, iCmdShow);
  UpdateWindow(hWnd);

  auto dll = LoadLibraryW(L"isGDI.dll");
  assert(dll);
  auto DrawRectangle = (int(__stdcall *)(
      HWND, DWORD, int, int, int, int))GetProcAddress(dll, "DrawRectangle");
  assert(DrawRectangle);
  DrawRectangle(hWnd, 0xffff0000, 0, 0, 100, 100);

  while (GetMessage(&msg, NULL, 0, 0)) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  FreeLibrary(dll);

  return msg.wParam;
} // WinMain

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
                         LPARAM lParam) {

  return DefWindowProc(hWnd, message, wParam, lParam);
} // WndProc

此外,如果我直接通过源代码(不使用DLL)调用DrawRectangle,则程序将按预期运行

2 个答案:

答案 0 :(得分:1)

我发现您在dll中使用了全局对象GdiManager。这意味着它是从DLL_PROCESS_DETACH调用的析构函数,因此位于 LoaderLock 关键部分。在析构函数中,您调用GdiplusShutdown。当在{em> LoaderLock 内部调用的GdiplusShutdown和GDI +使用后台线程(suppressBackgroundThread = FALSE-这是您的情况)时,总是会导致死锁: GdiplusShutdown发出信号通知后台线程退出(设置Globals::ThreadQuitEvent),然后等待等待后台线程退出。线程退出时,请尝试进入 LoaderLock 并挂在这里-因为它由调用GdiplusShutdown的线程持有。因此,在进入 LoaderLock 关键部分时,主线程挂起,等待背景线程,而bacground线程挂起。

我们可以尝试使用suppressBackgroundThread = TRUE,但在这种情况下需要致电NotificationUnhook。如果在已经根据市场情况实现的DLL_PROCESS_DETACH UB上执行此操作,则它看起来可以正常运行,挂起或失败(例如在此调用DestroyWindow,这也是dll条目错误,如果进程也错误)分离将在其他线程上调用(比较dll附加)-因此将在另一线程的NotificationHook内部创建窗口)

正确的解决方案是从dll中导出2个附加函数,例如StartStop,并从第一次调用GdiplusStartup和第二次调用GdiplusShutdown中导出。 dll加载后立即调用Start,卸载前调用Stop

答案 1 :(得分:0)

您没有告诉当前线程(应用程序)退出。在与窗口关联的WndProc中使用PostQuitMessage

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
                         LPARAM lParam) {
    if (message == WM_DESTROY)
         PostQuitMessage(0);
    else
        return DefWindowProc(hWnd, message, wParam, lParam);
    return 0;
}