控制台应用生成EMF元文件尺寸异常 与WinForm运行结果不符
问题根因
两类应用的核心差异是默认DPI感知等级不同:
- Windows Forms 从 .NET Core 3.0、.NET Framework 4.7 版本开始默认开启Per-Monitor DPI感知,进程不会被系统执行DPI虚拟化,通过GDI/GDI+接口读取到的显示设备DPI与显示器实际缩放比例完全匹配。
- 控制台应用默认不声明任何DPI感知等级,系统会强制对其开启DPI虚拟化:所有GDI接口返回的设备DPI固定为虚拟值96,与显示器实际缩放比例无关。但GDI+创建元文件时,底层元文件子系统会绕过虚拟化层读取物理显示器的真实DPI做帧尺寸换算,两层逻辑使用的DPI系数不匹配,最终生成的EMF帧尺寸会按照系统缩放比例等比放大,和观测到的200%缩放尺寸翻倍、150%缩放尺寸为预期1.5倍的现象完全吻合。
之前尝试用毫米作为页面/帧单位仍存在5%左右偏差,是因为代码中仍先通过虚拟96DPI做了毫米到像素的换算,再传入元文件构造函数,没有绕开DPI系数错位的问题。
修复方案
共两种可落地的修复方式,第二种稳定性更高,不受运行环境DPI设置影响:
- 为控制台应用开启DPI感知
可以通过添加应用程序清单文件,或在进程启动初期调用Win32 DPI相关API,将进程设置为Per-Monitor DPI感知模式,让GDI+读取到的DPI值与元文件子系统使用的DPI值保持一致,原有代码逻辑即可正常运行。注意这种方案在用户更换不同缩放比例的显示器时,仍可能出现适配问题。 - 直接使用物理单位定义EMF帧,完全跳过像素换算
创建元文件时直接指定帧单位为毫米,传入以毫米为单位的帧尺寸,不要自行做毫米到像素的转换,从根源上避免DPI系数不匹配的问题,同时记得释放获取的HDC资源避免泄漏。修正后的代码如下:
using System; using System.Drawing; using System.Drawing.Imaging; namespace MetafileDotNot { internal class Program { static void Main() { using (Graphics offScreenGraphics = Graphics.FromHwndInternal(IntPtr.Zero)) { IntPtr hdc = offScreenGraphics.GetHdc(); try { // 直接指定帧单位为毫米,传入200mm×100mm的帧尺寸 using (Metafile metaFile = new Metafile( "test.emf", hdc, new RectangleF(0, 0, 200, 100), MetafileFrameUnit.Millimeter, EmfType.EmfPlusDual )) using (Graphics graphics = Graphics.FromImage(metaFile)) { // 页面单位也设置为毫米,直接传入毫米坐标绘制 graphics.PageUnit = GraphicsUnit.Millimeter; graphics.FillRectangle(Brushes.DarkBlue, new RectangleF(10, 10, 180, 80)); } } finally { offScreenGraphics.ReleaseHdc(hdc); } } } } }
这种写法生成的EMF尺寸完全不依赖当前显示器的缩放比例,在任何DPI环境下插入Word都能得到准确的200mm×100mm尺寸,不会出现比例偏差。
内容的提问来源于stack exchange,提问作者Codo
相关产品推荐
相关产品推荐

