I have a program which prints a multi-page document. The first page is pre-printed paper, so it should print on the first side, and the rest of the pages should be duplexed.
My initial solution was to just print a blank sheet after the first page, but a lot (or maybe all) printers will use a different side of the pre-printed paper as the front (very bad), depending on whether it is set for duplexing or not.
So I'm stuck trying to convince the printer to change duplexing in the middle of the print job. I've been fighting with this without any luck, and using various code samples, this should theoretically work, but it doesn't.
After each EndPage call, if the duplexing needs to change, it gets the device context from the printer (using reflection on a private field of an internal class, yuck), it uses Marshal to fill in values on the DEVMODE structure, and then calls ResetDC() with the device context and DEVMODE pointer.
I retrieve the DEVMODE structure from the pointer for verification purposes only, I want to make sure I'm setting the correct fields. DEVMODE gets populated correctly and sent to ResetDC() and PrinterSettings.SetHdevmode(), but when I re-retrieve PrinterSettings.GetHdevmode(), the changes I just made are gone. And the printer keeps the old duplex setting.
Edit: I found some issues with code I posted before, such as the fact that OnStartPage calls ResetDC. So I need to be modifying the DEVMODE structure held by StandardPrintController. But it's still not working with these changes:
public class PrinterDuplexController : StandardPrintController
{
public PrinterDuplexController()
{
}
private static FieldInfo dcField = typeof(StandardPrintController)
.GetField("dc",
BindingFlags.GetField | BindingFlags.Instance | BindingFlags.NonPublic);
private static FieldInfo modeHandleField = typeof(StandardPrintController)
.GetField("modeHandle",
BindingFlags.GetField | BindingFlags.Instance | BindingFlags.NonPublic);
protected object dc
{
get
{
return dcField.GetValue(this);
}
}
protected IntPtr Hdc
{
get
{
var dc = this.dc;
return (IntPtr)(dc.GetType().GetProperty("Hdc").GetValue(dc, null));
}
}
protected IntPtr modeHandle
{
get
{
object result = modeHandleField.GetValue(this);
var field = result.GetType().GetField("handle", BindingFlags.GetField | BindingFlags.Instance | BindingFlags.NonPublic);
return (IntPtr)field.GetValue(result);
}
}
public override void OnEndPage(PrintDocument document, PrintPageEventArgs e)
{
base.OnEndPage(document, e);
IntPtr pDEVMODE = GlobalLock(modeHandle);
try
{
int[] flags = new int[1];
Marshal.Copy(
new IntPtr(40 + pDEVMODE.ToInt64()),
flags,
0,
1);
flags[0] |= (int)DM.Duplex;
Marshal.Copy(
flags,
0,
new IntPtr(40 + pDEVMODE.ToInt64()),
1);
Marshal.Copy(
new short[] { (short)e.PageSettings.PrinterSettings.Duplex },
0,
new IntPtr(62 + pDEVMODE.ToInt64()),
1);
var debugDevMode = (DEVMODE)Marshal.PtrToStructure(pDEVMODE, typeof(DEVMODE));
ResetDC(Hdc, pDEVMODE);
}
finally
{
GlobalUnlock(modeHandle);
}
}
[DllImport("gdi32.dll")]
//private static extern IntPtr ResetDC(IntPtr hdc, [In] ref DEVMODE lpInitData);
private static extern int ResetDC(IntPtr hdc, IntPtr DevMode);
[DllImport("gdi32.dll")]
public static extern int StartPage(IntPtr hdc);
[DllImport("gdi32.dll")]
public static extern int EndPage(IntPtr hdc);
[DllImport("kernel32.dll", ExactSpelling = true)]
private static extern IntPtr GlobalFree(IntPtr handle);
[DllImport("kernel32.dll", ExactSpelling = true)]
private static extern IntPtr GlobalLock(IntPtr handle);
[DllImport("kernel32.dll", ExactSpelling = true)]
private static extern IntPtr GlobalUnlock(IntPtr handle);
[Flags()]
internal enum DM : int
{
Orientation = 0x1,
PaperSize = 0x2,
PaperLength = 0x4,
PaperWidth = 0x8,
Scale = 0x10,
Position = 0x20,
NUP = 0x40,
DisplayOrientation = 0x80,
Copies = 0x100,
DefaultSource = 0x200,
PrintQuality = 0x400,
Color = 0x800,
Duplex = 0x1000,
YResolution = 0x2000,
TTOption = 0x4000,
Collate = 0x8000,
FormName = 0x10000,
LogPixels = 0x20000,
BitsPerPixel = 0x40000,
PelsWidth = 0x80000,
PelsHeight = 0x100000,
DisplayFlags = 0x200000,
DisplayFrequency = 0x400000,
ICMMethod = 0x800000,
ICMIntent = 0x1000000,
MediaType = 0x2000000,
DitherType = 0x4000000,
PanningWidth = 0x8000000,
PanningHeight = 0x10000000,
DisplayFixedOutput = 0x20000000
}
internal struct POINTL
{
public int x;
public int y;
}
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)]
internal struct DEVMODE
{
public const int CCHDEVICENAME = 32;
public const int CCHFORMNAME = 32;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)]
[FieldOffset(0)]
public string dmDeviceName;
[FieldOffset(32)]
public Int16 dmSpecVersion;
[FieldOffset(34)]
public Int16 dmDriverVersion;
[FieldOffset(36)]
public Int16 dmSize;
[FieldOffset(38)]
public Int16 dmDriverExtra;
[FieldOffset(40)]
public DM dmFields;
[FieldOffset(44)]
Int16 dmOrientation;
[FieldOffset(46)]
Int16 dmPaperSize;
[FieldOffset(48)]
Int16 dmPaperLength;
[FieldOffset(50)]
Int16 dmPaperWidth;
[FieldOffset(52)]
Int16 dmScale;
[FieldOffset(54)]
Int16 dmCopies;
[FieldOffset(56)]
Int16 dmDefaultSource;
[FieldOffset(58)]
Int16 dmPrintQuality;
[FieldOffset(44)]
public POINTL dmPosition;
[FieldOffset(52)]
public Int32 dmDisplayOrientation;
[FieldOffset(56)]
public Int32 dmDisplayFixedOutput;
[FieldOffset(60)]
public short dmColor;
[FieldOffset(62)]
public short dmDuplex;
[FieldOffset(64)]
public short dmYResolution;
[FieldOffset(66)]
public short dmTTOption;
[FieldOffset(68)]
public short dmCollate;
[FieldOffset(72)]
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHFORMNAME)]
public string dmFormName;
[FieldOffset(102)]
public Int16 dmLogPixels;
[FieldOffset(104)]
public Int32 dmBitsPerPel;
[FieldOffset(108)]
public Int32 dmPelsWidth;
[FieldOffset(112)]
public Int32 dmPelsHeight;
[FieldOffset(116)]
public Int32 dmDisplayFlags;
[FieldOffset(116)]
public Int32 dmNup;
[FieldOffset(120)]
public Int32 dmDisplayFrequency;
}
}
Try making two print jobs, each with the different duplex settings. Fiddle with priorities of the jobs to get them in order instead of hacking around internal fields.