You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++ DLL注入Hook EndPage时仅通过HDC获取打印页面尺寸方法

Hook EndPage场景下通过HDC获取打印页面实际尺寸的方案

仅持有打印HDC时,不需要额外上下文,直接通过GDI标准接口即可拿到完整页面参数,适配GDI+绘制逻辑的实现方式如下:

核心尺寸获取方法

调用GetDeviceCaps接口查询HDC的设备参数,所有返回值默认单位为设备像素:

  • 物理页面总尺寸:
    • 页面总宽度:GetDeviceCaps(hdc, PHYSICALWIDTH)
    • 页面总高度:GetDeviceCaps(hdc, PHYSICALHEIGHT)
  • 可打印区域参数(打印机存在硬件不可打印边距,实际绘制不要超出这个范围避免内容被裁切):
    • 可打印区域左上角X偏移:GetDeviceCaps(hdc, PHYSICALOFFSETX)
    • 可打印区域左上角Y偏移:GetDeviceCaps(hdc, PHYSICALOFFSETY)
    • 可打印区域宽度:GetDeviceCaps(hdc, HORZRES)
    • 可打印区域高度:GetDeviceCaps(hdc, VERTRES)
  • 单位换算适配:如果使用磅(UnitPoint,1磅=1/72英寸)作为绘制单位,用DPI值做换算即可:
    • 水平DPI:GetDeviceCaps(hdc, LOGPIXELSX)
    • 垂直DPI:GetDeviceCaps(hdc, LOGPIXELSY)
    • 像素转磅公式:磅值 = 像素值 * 72.0f / 对应方向DPI
      嫌换算麻烦可以直接把GDI+ Graphics对象的绘制单位设为UnitPixel,直接用查询到的像素值定位即可。

三个固定位置的定位逻辑

不要硬编码RectF坐标,按以下规则计算位置:

  1. 先调用Graphics::MeasureString测量待绘制文本在当前字体下的实际宽高
  2. 顶部居中:文本X坐标 = (可打印区域宽度 - 文本宽度)/2,Y坐标 = 可打印区域顶部偏移 + 预留边距
  3. 页面正居中:文本X坐标 = (可打印区域宽度 - 文本宽度)/2,Y坐标 = (可打印区域高度 - 文本高度)/2
  4. 底部居中:文本X坐标 = (可打印区域宽度 - 文本宽度)/2,Y坐标 = 可打印区域高度 - 文本高度 - 预留边距

参考修正代码

// 注意:GdiplusStartup不要每次hook调用都执行,建议在DLL加载入口初始化一次,避免资源泄漏
int  StopPrint::hookFunction(HDC hdc)
{    
    sendinfo WaterMarkParams;
    rpcclient(WaterMarkParams);

    Gdiplus::GdiplusStartupInput gdiplusStartupInput;
    Gdiplus::GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);
    Graphics grpc(hdc);
    // 直接用像素作为绘制单位,省掉单位换算
    grpc.SetPageUnit(Gdiplus::UnitPixel);

    // 实时获取当前页尺寸参数
    int pageWidth = GetDeviceCaps(hdc, HORZRES);
    int pageHeight = GetDeviceCaps(hdc, VERTRES);

    Gdiplus::FontFamily nameFontFamily(L"Arial");
    Gdiplus::Font         font(&nameFontFamily, WaterMarkParams.FontSize, 
    Gdiplus::FontStyleBold, Gdiplus::UnitPoint);
    Gdiplus::SolidBrush   solidBrush(Gdiplus::Color(100, 0, 0, 250)); 

    // 测量文本实际尺寸
    Gdiplus::RectF textRect;
    grpc.MeasureString(WaterMarkParams.WaterMarkTxtstr, -1, &font, Gdiplus::PointF(0,0), &textRect);
    float textW = textRect.Width;
    float textH = textRect.Height;
    const float margin = 20.0f; // 边距可根据需求自行调整

    // 示例:绘制页面居中水印,顶部/底部位置按前面的规则修改Y坐标即可
    Gdiplus::RectF drawRect(
        (pageWidth - textW)/2.0f,
        (pageHeight - textH)/2.0f,
        textW,
        textH
    );

    grpc.DrawString(WaterMarkParams.WaterMarkTxtstr, -1, &font, drawRect, NULL, &solidBrush);
    return getOriginalFunction()(hdc);
}

注意事项

  • 所有绘制操作必须在调用原始EndPage函数前完成,原始EndPage执行后当前页会被提交到打印队列,后续绘制不会生效
  • 不要硬编码坐标值,不同打印机、不同纸张尺寸(A4/A3/自定义)的可打印区域参数差异很大,必须每次Hook到HDC时实时查询
  • GDI+初始化和资源释放不要放到每次Hook调用里,建议做全局单次初始化,避免句柄泄漏导致打印异常

内容的提问来源于stack exchange,提问作者Mohammadreza Hojatoleslami

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 12:48:18