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)
- 可打印区域左上角X偏移:
- 单位换算适配:如果使用磅(
UnitPoint,1磅=1/72英寸)作为绘制单位,用DPI值做换算即可:- 水平DPI:
GetDeviceCaps(hdc, LOGPIXELSX) - 垂直DPI:
GetDeviceCaps(hdc, LOGPIXELSY) - 像素转磅公式:
磅值 = 像素值 * 72.0f / 对应方向DPI
嫌换算麻烦可以直接把GDI+ Graphics对象的绘制单位设为UnitPixel,直接用查询到的像素值定位即可。
- 水平DPI:
三个固定位置的定位逻辑
不要硬编码RectF坐标,按以下规则计算位置:
- 先调用
Graphics::MeasureString测量待绘制文本在当前字体下的实际宽高 - 顶部居中:文本X坐标 = (可打印区域宽度 - 文本宽度)/2,Y坐标 = 可打印区域顶部偏移 + 预留边距
- 页面正居中:文本X坐标 = (可打印区域宽度 - 文本宽度)/2,Y坐标 = (可打印区域高度 - 文本高度)/2
- 底部居中:文本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
相关产品推荐
相关产品推荐

