Mitch
Mitch

Reputation: 22251

How can I save and restore `PrinterSettings`?

I have an application which we want to save the PrinterSettings a user selects to the registry, then restore them when we are ready to print. Is there a way to serialize PrinterSettings?

Upvotes: 4

Views: 4087

Answers (1)

Mitch
Mitch

Reputation: 22251

The entirety of the PrinterSettings – including vendor specific options – can be stored by persisting the HDEVMODE structure received via PrinterSettings.GetHdevmode(), then restored via PrinterSettings.SetHdevmode(IntPtr).

The class below will add two extension methods to save and restore PrinterSettings to and from a byte array.

Caveat programmer: some printer drivers do not have backwards or forwards compatibility, and may crash if using persisted data from another version or architecture of the driver.

Example Use:

PrinterSettings CurrentSettings;
const string myAppKeyName = @"Software\MyApplicationName";
const string printerSettingValueName = "PrinterSettings"

// save
using (var sk = Registry.CurrentUser.CreateSubKey(myAppKeyName))
{
    sk.SetValue(printerSettingValueName, this.CurrentSettings.GetDevModeData(), RegistryValueKind.Binary);
}

// restore
using (var sk = Registry.CurrentUser.CreateSubKey(myAppKeyName))
{
    var data = sk.GetValue(printerSettingValueName, RegistryValueKind.Binary) as byte[];

    this.CurrentSettings = new PrinterSettings();
    if (data != null)
    {
        this.CurrentSettings.SetDevModeData(data);
    }
}

Implementation:

static class PrinterSettingsExtensions
{
    public static byte[] GetDevModeData(this PrinterSettings settings)
    {
        //Contract.Requires(settings != null);

        byte[] devModeData;
        RuntimeHelpers.PrepareConstrainedRegions();
        try
        {
            // cer since hDevMode is not a SafeHandle
        }
        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
            {
                Marshal.FreeHGlobal(hDevMode);
            }
        }
        return devModeData;
    }

    public static void SetDevModeData(this PrinterSettings settings, byte[] data)
    {
        //Contract.Requires(settings != null);
        //Contract.Requires(data != null);
        //Contract.Requires(data.Length >= Marshal.SizeOf(typeof(NativeMethods.DEVMODE)));

        RuntimeHelpers.PrepareConstrainedRegions();
        try
        {
            // cer since AllocHGlobal does not return SafeHandle
        }
        finally
        {
            var pDevMode = Marshal.AllocHGlobal(data.Length);
            try
            {
                // we don't have to worry about GlobalLock since AllocHGlobal only uses LMEM_FIXED
                Marshal.Copy(data, 0, pDevMode, data.Length);
                var devMode = (NativeMethods.DEVMODE)Marshal.PtrToStructure(
                        pDevMode, typeof(NativeMethods.DEVMODE));

                // The printer name must match the original printer, otherwise an AV will be thrown
                settings.PrinterName = devMode.dmDeviceName;

                // SetHDevmode creates a copy of the devmode, so we don't have to keep ours around
                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;
    }
}

Related:

Upvotes: 7

Related Questions