C# .NET Core 6读写打印机DEVMODE属性保存还原失效问题
问题定位
你遇到的DEVMODE还原后系统配置不生效问题,核心原因是当前代码仅修改了进程内PrinterSettings实例的内存配置,未将配置写入系统持久化存储。
调用SetHdevmode方法只会更新当前.NET运行时中PrinterSettings对象持有的DEVMODE内存句柄,不会触发打印后台服务的配置更新逻辑:应用内唤起的打印机属性对话框读取的是当前进程内存中的DEVMODE数据,因此显示为修改后的值;Windows系统设置页面读取的是打印服务存储的全局/用户级持久化配置,因此显示为未修改的旧值。
现有代码存在的明确问题
- 句柄释放逻辑错误:
GetHdevmode返回的HGLOBAL内存句柄必须通过GlobalFree接口释放,现有代码使用的Marshal.FreeHGlobal对应本地内存释放接口,在64位Windows 11环境下会造成内存泄漏,偶发句柄访问异常。 - DEVMODE结构截断:现有代码定义的DEVMODE结构体仅包含公共基础字段,未预留打印机驱动私有扩展区域的访问能力。多数厂商打印机会将核心配置(如纸盒选择、色彩模式、装订规则)存储在
dmDriverExtra指向的驱动私有内存段,直接按固定结构体长度拷贝会出现配置数据丢失。 - 缺少系统配置写入逻辑:现有代码完全没有调用打印服务相关的持久化API,仅做了进程内内存赋值,不可能实现系统层面的配置还原。
正确实现逻辑
要实现全局生效的打印机配置备份还原,按以下逻辑调整实现:
- 备份配置时,直接通过打印API打开目标打印机句柄,读取打印服务存储的完整DEVMODE结构(包含全部驱动私有扩展数据),按
dmSize + dmDriverExtra的实际长度拷贝二进制数据做持久化存储。 - 还原配置前,先调用文档属性接口校验备份的DEVMODE数据与当前打印机驱动版本兼容,避免驱动升级后旧配置数据导致崩溃。
- 校验通过后,调用打印机配置设置接口将DEVMODE数据写入打印服务的持久化存储,完成后广播系统设置变更消息,通知所有已打开的系统面板、应用刷新打印机配置缓存。
- 所有Win32接口获取的全局内存句柄,统一使用
GlobalFree释放,禁止混用本地内存释放接口。
权限说明:修改打印机全局默认配置需要对应打印机的管理权限,标准用户账号仅能修改当前用户上下文下的打印机默认配置,无法修改全局配置。
以下是测试代码参考(已标注问题点):
public static class PrinterSettingsExtensions { public static byte[] GetDevModeData(this PrinterSettings settings) { byte[] devModeData; RuntimeHelpers.PrepareConstrainedRegions(); try { } finally { var hDevMode = settings.GetHdevmode(); try { IntPtr pDevMode = NativeMethods.GlobalLock(hDevMode); try { var devMode = (NativeMethods.DEVMODE)Marshal.PtrToStructure( pDevMode, typeof(NativeMethods.DEVMODE)); var devModeSize = devMode.dmSize + devMode.dmDriverExtra; devModeData = new byte[devModeSize]; Marshal.Copy(pDevMode, devModeData, 0, devModeSize); } finally { NativeMethods.GlobalUnlock(hDevMode); } } finally { // 错误:此处应调用GlobalFree释放hDevMode,而非Marshal.FreeHGlobal Marshal.FreeHGlobal(hDevMode); } } return devModeData; } public static void SetDevModeData(this PrinterSettings settings, byte[] data) { RuntimeHelpers.PrepareConstrainedRegions(); try { } finally { var pDevMode = Marshal.AllocHGlobal(data.Length); try { Marshal.Copy(data, 0, pDevMode, data.Length); var devMode = (NativeMethods.DEVMODE)Marshal.PtrToStructure( pDevMode, typeof(NativeMethods.DEVMODE)); settings.PrinterName = devMode.dmDeviceName; // 仅修改进程内PrinterSettings的配置,未写入系统打印服务 settings.SetHdevmode(pDevMode); } finally { Marshal.FreeHGlobal(pDevMode); } } } } static class NativeMethods { private const string Kernel32 = "kernel32.dll"; [DllImport(Kernel32, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto)] public static extern IntPtr GlobalLock(IntPtr handle); [DllImport(Kernel32, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto)] public static extern bool GlobalUnlock(IntPtr handle); [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Auto)] public struct DEVMODE { private const int CCHDEVICENAME = 32; private const int CCHFORMNAME = 32; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)] public string dmDeviceName; public short dmSpecVersion; public short dmDriverVersion; public short dmSize; public short dmDriverExtra; public int dmFields; public int dmPositionX; public int dmPositionY; public int dmDisplayOrientation; public int dmDisplayFixedOutput; public short dmColor; public short dmDuplex; public short dmYResolution; public short dmTTOption; public short dmCollate; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHFORMNAME)] public string dmFormName; public short dmLogPixels; public int dmBitsPerPel; public int dmPelsWidth; public int dmPelsHeight; public int dmDisplayFlags; public int dmDisplayFrequency; public int dmICMMethod; public int dmICMIntent; public int dmMediaType; public int dmDitherType; public int dmReserved1; public int dmReserved2; public int dmPanningWidth; public int dmPanningHeight; } }
运行环境参考:.NET Core 6、Windows 11 22000.795
内容的提问来源于stack exchange,提问作者Leon H
相关产品推荐
相关产品推荐

