By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
446,238 Members | 1,771 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 446,238 IT Pros & Developers. It's quick & easy.

Using SetPrinter in C# to set duplex option (in print prefs)

P: n/a
I was wondering if someone could lend me a hand with a C# problem I am having

I am trying to use the “setPrinter” api to change the duplex setting (under printing preferences on printer context menu) so that I can send a document to the printer in duplex mode

These are my declarations

[DllImport("kernel32.dll", EntryPoint="GetLastError", SetLastError=false
ExactSpelling=true, CallingConvention=CallingConvention.StdCall)
internal static extern Int32 GetLastError()

[DllImport("winspool.Drv", EntryPoint="ClosePrinter", SetLastError=true,
ExactSpelling=true, CallingConvention=CallingConvention.StdCall)
static extern bool ClosePrinter(IntPtr hPrinter)

[DllImport("winspool.Drv", EntryPoint="DocumentPropertiesA", SetLastError=true,
ExactSpelling=true, CallingConvention=CallingConvention.StdCall)
private static extern int DocumentProperties (IntPtr hwnd, IntPtr hPrinter,
[MarshalAs(UnmanagedType.LPStr)] string pDeviceNameg,
IntPtr pDevModeOutput, ref IntPtr pDevModeInput, int fMode)

[DllImport("winspool.Drv", EntryPoint="GetPrinterA", SetLastError=true, CharSet=CharSet.Ansi
ExactSpelling=true, CallingConvention=CallingConvention.StdCall)
private static extern bool GetPrinter(IntPtr hPrinter, Int32 dwLevel,
IntPtr pPrinter, Int32 dwBuf, out Int32 dwNeeded)

[DllImport("winspool.Drv", EntryPoint="OpenPrinterA", SetLastError=true, CharSet=CharSet.Ansi,
ExactSpelling=true, CallingConvention=CallingConvention.StdCall)
static extern bool OpenPrinter([MarshalAs(UnmanagedType.LPStr)] string szPrinter,
out IntPtr hPrinter, ref PRINTER_DEFAULTS pd)

[DllImport("winspool.drv", CharSet=CharSet.Auto, SetLastError=true)
private static extern bool SetPrinter(IntPtr hPrinter, int Level, IntPtr pPrinter, int Command)

// Wrapper for Win32 message formatter
[DllImport("kernel32.dll", CharSet=System.Runtime.InteropServices.CharSet.Aut o)
private unsafe static exter
int FormatMessage( int dwFlags
ref IntPtr pMessageSource
int dwMessageID
int dwLanguageID
ref string lpBuffer
int nSize
IntPtr* pArguments)

[StructLayout(LayoutKind.Sequential)
private struct PRINTER_DEFAULT

public int pDatatype
public int pDevMode
public int DesiredAccess
[StructLayout(LayoutKind.Sequential)
private struct PRINTER_INFO_

[MarshalAs(UnmanagedType.LPStr)] public string pServerName;
[MarshalAs(UnmanagedType.LPStr)] public string pPrinterName;
[MarshalAs(UnmanagedType.LPStr)] public string pShareName;
[MarshalAs(UnmanagedType.LPStr)] public string pPortName;
[MarshalAs(UnmanagedType.LPStr)] public string pDriverName;
[MarshalAs(UnmanagedType.LPStr)] public string pComment;
[MarshalAs(UnmanagedType.LPStr)] public string pLocation;
public IntPtr pDevMode;
[MarshalAs(UnmanagedType.LPStr)] public string pSepFile;
[MarshalAs(UnmanagedType.LPStr)] public string pPrintProcessor;
[MarshalAs(UnmanagedType.LPStr)] public string pDatatype;
[MarshalAs(UnmanagedType.LPStr)] public string pParameters;
public IntPtr pSecurityDescriptor;
public Int32 Attributes;
public Int32 Priority;
public Int32 DefaultPriority;
public Int32 StartTime;
public Int32 UntilTime;
public Int32 Status;
public Int32 cJobs;
public Int32 AveragePPM;
private const short CCDEVICENAME = 32
private const short CCFORMNAME = 32

[StructLayout(LayoutKind.Sequential)]
private struct DEVMODE
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCDEVICENAME)]
public string dmDeviceName

public short dmSpecVersion
public short dmDriverVersion
public short dmSize
public short dmDriverExtra
public int dmFields
public short dmOrientation
public short dmPaperSize
public short dmPaperLength
public short dmPaperWidth
public short dmScale
public short dmCopies
public short dmDefaultSource
public short dmPrintQuality
public short dmColor
public short dmDuplex
public short dmYResolution
public short dmTTOption;
public short dmCollate;

[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCFORMNAME)]
public string dmFormName;

public short dmUnusedPadding;
public short dmBitsPerPel;
public int dmPelsWidth;
public int dmPelsHeight;
public int dmDisplayFlags;
public int dmDisplayFrequency;
}

public const int DM_DUPLEX = 0x1000;
public const int DM_IN_BUFFER = 8;

public const int DM_OUT_BUFFER = 2;
public const int PRINTER_ACCESS_ADMINISTER = 0x4;
public const int PRINTER_ACCESS_USE = 0x8;
public const int STANDARD_RIGHTS_REQUIRED = 0xF0000;
public const int PRINTER_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | PRINTER_ACCESS_ADMINISTER | PRINTER_ACCESS_USE);

And here is my method:

public bool SetPrinterDuplex(string sPrinterName, int nDuplexSetting)
{
IntPtr hPrinter;
PRINTER_DEFAULTS pd = new PRINTER_DEFAULTS();
PRINTER_INFO_2 pinfo = new PRINTER_INFO_2();
DEVMODE dm;
IntPtr ptrDM;
IntPtr ptrPrinterInfo;
int sizeOfDevMode = 0;
int lastError;
byte[] yDevModeData;
byte[] yPInfoMemory;
int nBytesNeeded;
int nRet;
System.Int32 nJunk;

//On Error GoTo cleanup

if ((nDuplexSetting < 1) || (nDuplexSetting > 3) )
{
throw new ArgumentOutOfRangeException("nDuplexSetting","nDup lexSetting is incorrect.");
}
else
{
//if no printername provided, check if there is a default printer and use it instead
if (sPrinterName.Trim() == "")
{
PrintDocument printDocument1 = new PrintDocument();
sPrinterName = printDocument1.PrinterSettings.PrinterName;// + "\0";
}

//open the printer
pd.DesiredAccess = PRINTER_ALL_ACCESS;
nRet = Convert.ToInt32(OpenPrinter(sPrinterName, out hPrinter, ref pd));

if ((nRet == 0) || (hPrinter == IntPtr.Zero))
{
return false;
}

//get the size of the Printer Info structure
GetPrinter(hPrinter, 2, IntPtr.Zero, 0, out nBytesNeeded);
if (nBytesNeeded <= 0)
{
return false;
}

// Allocate enough space for PRINTER_INFO_2...
ptrPrinterInfo = Marshal.AllocCoTaskMem(nBytesNeeded);

// The second GetPrinter fills in all the current settings, so all you
// need to do is modify what you're interested in...
nRet = Convert.ToInt32(GetPrinter(hPrinter, 2, ptrPrinterInfo, nBytesNeeded, out nJunk));
if (nRet == 0)
{
return false;
}

pinfo = (PRINTER_INFO_2)Marshal.PtrToStructure(ptrPrinterI nfo, typeof(PRINTER_INFO_2));

if (pinfo.pDevMode == IntPtr.Zero)
{
// If GetPrinter didn't fill in the DEVMODE, try to get it by calling
// DocumentProperties...

IntPtr ptrZero = IntPtr.Zero;

//get the size of the devmode structure
sizeOfDevMode = DocumentProperties(IntPtr.Zero, hPrinter, sPrinterName, ptrZero, ref ptrZero, 0);
if (nRet <= 0)
{
return false;
}

ptrDM = Marshal.AllocCoTaskMem(sizeOfDevMode);

nRet = DocumentProperties(IntPtr.Zero, hPrinter, sPrinterName, ptrDM,
ref ptrZero, DM_OUT_BUFFER);
if ((nRet < 0) || (ptrDM == IntPtr.Zero))
{
//Cannot get the DEVMODE structure.
return false;
}

pinfo.pDevMode = ptrDM;

}

dm = (DEVMODE)Marshal.PtrToStructure(pinfo.pDevMode, typeof(DEVMODE));
if (!Convert.ToBoolean(dm.dmFields & DM_DUPLEX))
{
//You cannot modify the duplex flag for this printer
//because it does not support duplex or the driver does not support setting
//it from the Windows API.
return false;
}

//update fields
dm.dmDuplex = (short)nDuplexSetting;
dm.dmFields = DM_DUPLEX;

Marshal.StructureToPtr(dm,pinfo.pDevMode,true);

//pinfo.pDevMode = ptrDM;
pinfo.pSecurityDescriptor = IntPtr.Zero;

//update driver dependent part of the DEVMODE
nRet = DocumentProperties(IntPtr.Zero, hPrinter, sPrinterName, pinfo.pDevMode
, ref pinfo.pDevMode, (DM_IN_BUFFER | DM_OUT_BUFFER));
if (nRet < 0)
{
//Unable to set duplex setting to this printer.
return false;
}

Marshal.StructureToPtr(pinfo,ptrPrinterInfo,true);

lastError = Marshal.GetLastWin32Error();

nRet = Convert.ToInt16(SetPrinter(hPrinter, 2, ptrPrinterInfo, 0));
if (nRet == 0)
{
//Unable to set shared printer settings.
lastError = Marshal.GetLastWin32Error();
string myErrMsg = GetErrorMessage(lastError);

return false;
}
}
if (hPrinter != IntPtr.Zero)
ClosePrinter(hPrinter);

return Convert.ToBoolean(nRet);

}//End SetPrinterDuplex
It fails at the “setPrinter” with the following error "Error 1797: The printer driver is unknown.\r\n".

By the way, the code works in VB6 but I don’t want it to work in VB6, I want it to work in C#!!

Any help is appreciated.

Nov 15 '05 #1
Share this Question
Share on Google+
10 Replies


P: n/a

Hi joeeds,

I have reviewed your issue, I will spend some time to do some research on
this issue.

I will reply to you ASAP. Thanks for your understanding.

Best regards,
Jeffrey Tan
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "as is" with no warranties and confers no rights.

Nov 15 '05 #2

P: n/a
Joe,
Any reason not to use the Duplex setting in the PrinterSettings object?
I do that here implicitly by allowing the user to select Duplex in the
PrintDialog and it works fine. I don't force them to duplex as sometimes
that isn't appropriate for what we are doing.

Ron Allen
"Joe M" <jo****@microsoft.com> wrote in message
news:11**********************************@microsof t.com...
I was wondering if someone could lend me a hand with a C# problem I am having.
I am trying to use the "setPrinter" api to change the duplex setting (under printing preferences on printer context menu) so that I can send a
document to the printer in duplex mode.
These are my declarations:

[DllImport("kernel32.dll", EntryPoint="GetLastError", SetLastError=false,
ExactSpelling=true, CallingConvention=CallingConvention.StdCall)]
internal static extern Int32 GetLastError();

[DllImport("winspool.Drv", EntryPoint="ClosePrinter", SetLastError=true,
ExactSpelling=true, CallingConvention=CallingConvention.StdCall)]
static extern bool ClosePrinter(IntPtr hPrinter);

[DllImport("winspool.Drv", EntryPoint="DocumentPropertiesA", SetLastError=true, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)]
private static extern int DocumentProperties (IntPtr hwnd, IntPtr hPrinter, [MarshalAs(UnmanagedType.LPStr)] string pDeviceNameg,
IntPtr pDevModeOutput, ref IntPtr pDevModeInput, int fMode);

[DllImport("winspool.Drv", EntryPoint="GetPrinterA", SetLastError=true, CharSet=CharSet.Ansi, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)]
private static extern bool GetPrinter(IntPtr hPrinter, Int32 dwLevel,
IntPtr pPrinter, Int32 dwBuf, out Int32 dwNeeded);

[DllImport("winspool.Drv", EntryPoint="OpenPrinterA", SetLastError=true, CharSet=CharSet.Ansi, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)]
static extern bool OpenPrinter([MarshalAs(UnmanagedType.LPStr)] string szPrinter, out IntPtr hPrinter, ref PRINTER_DEFAULTS pd);

[DllImport("winspool.drv", CharSet=CharSet.Auto, SetLastError=true)]
private static extern bool SetPrinter(IntPtr hPrinter, int Level, IntPtr pPrinter, int Command);
// Wrapper for Win32 message formatter.
[DllImport("kernel32.dll", CharSet=System.Runtime.InteropServices.CharSet.Aut o)] private unsafe static extern
int FormatMessage( int dwFlags,
ref IntPtr pMessageSource,
int dwMessageID,
int dwLanguageID,
ref string lpBuffer,
int nSize,
IntPtr* pArguments);

[StructLayout(LayoutKind.Sequential)]
private struct PRINTER_DEFAULTS
{
public int pDatatype;
public int pDevMode;
public int DesiredAccess;
}

[StructLayout(LayoutKind.Sequential)]
private struct PRINTER_INFO_2
{
[MarshalAs(UnmanagedType.LPStr)] public string pServerName;
[MarshalAs(UnmanagedType.LPStr)] public string pPrinterName;
[MarshalAs(UnmanagedType.LPStr)] public string pShareName;
[MarshalAs(UnmanagedType.LPStr)] public string pPortName;
[MarshalAs(UnmanagedType.LPStr)] public string pDriverName;
[MarshalAs(UnmanagedType.LPStr)] public string pComment;
[MarshalAs(UnmanagedType.LPStr)] public string pLocation;
public IntPtr pDevMode;
[MarshalAs(UnmanagedType.LPStr)] public string pSepFile;
[MarshalAs(UnmanagedType.LPStr)] public string pPrintProcessor;
[MarshalAs(UnmanagedType.LPStr)] public string pDatatype;
[MarshalAs(UnmanagedType.LPStr)] public string pParameters;
public IntPtr pSecurityDescriptor;
public Int32 Attributes;
public Int32 Priority;
public Int32 DefaultPriority;
public Int32 StartTime;
public Int32 UntilTime;
public Int32 Status;
public Int32 cJobs;
public Int32 AveragePPM;
}

private const short CCDEVICENAME = 32;
private const short CCFORMNAME = 32;

[StructLayout(LayoutKind.Sequential)]
private struct DEVMODE
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCDEVICENAME)]
public string dmDeviceName;

public short dmSpecVersion;
public short dmDriverVersion;
public short dmSize;
public short dmDriverExtra;
public int dmFields;
public short dmOrientation;
public short dmPaperSize;
public short dmPaperLength;
public short dmPaperWidth;
public short dmScale;
public short dmCopies;
public short dmDefaultSource;
public short dmPrintQuality;
public short dmColor;
public short dmDuplex;
public short dmYResolution;
public short dmTTOption;
public short dmCollate;

[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCFORMNAME)]
public string dmFormName;

public short dmUnusedPadding;
public short dmBitsPerPel;
public int dmPelsWidth;
public int dmPelsHeight;
public int dmDisplayFlags;
public int dmDisplayFrequency;
}

public const int DM_DUPLEX = 0x1000;
public const int DM_IN_BUFFER = 8;

public const int DM_OUT_BUFFER = 2;
public const int PRINTER_ACCESS_ADMINISTER = 0x4;
public const int PRINTER_ACCESS_USE = 0x8;
public const int STANDARD_RIGHTS_REQUIRED = 0xF0000;
public const int PRINTER_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | PRINTER_ACCESS_ADMINISTER | PRINTER_ACCESS_USE);
And here is my method:

public bool SetPrinterDuplex(string sPrinterName, int nDuplexSetting)
{
IntPtr hPrinter;
PRINTER_DEFAULTS pd = new PRINTER_DEFAULTS();
PRINTER_INFO_2 pinfo = new PRINTER_INFO_2();
DEVMODE dm;
IntPtr ptrDM;
IntPtr ptrPrinterInfo;
int sizeOfDevMode = 0;
int lastError;
byte[] yDevModeData;
byte[] yPInfoMemory;
int nBytesNeeded;
int nRet;
System.Int32 nJunk;

//On Error GoTo cleanup

if ((nDuplexSetting < 1) || (nDuplexSetting > 3) )
{
throw new ArgumentOutOfRangeException("nDuplexSetting","nDup lexSetting is incorrect."); }
else
{
//if no printername provided, check if there is a default printer and use it instead if (sPrinterName.Trim() == "")
{
PrintDocument printDocument1 = new PrintDocument();
sPrinterName = printDocument1.PrinterSettings.PrinterName;// + "\0";
}

//open the printer
pd.DesiredAccess = PRINTER_ALL_ACCESS;
nRet = Convert.ToInt32(OpenPrinter(sPrinterName, out hPrinter, ref pd));

if ((nRet == 0) || (hPrinter == IntPtr.Zero))
{
return false;
}

//get the size of the Printer Info structure
GetPrinter(hPrinter, 2, IntPtr.Zero, 0, out nBytesNeeded);
if (nBytesNeeded <= 0)
{
return false;
}

// Allocate enough space for PRINTER_INFO_2...
ptrPrinterInfo = Marshal.AllocCoTaskMem(nBytesNeeded);

// The second GetPrinter fills in all the current settings, so all you
// need to do is modify what you're interested in...
nRet = Convert.ToInt32(GetPrinter(hPrinter, 2, ptrPrinterInfo, nBytesNeeded, out nJunk)); if (nRet == 0)
{
return false;
}

pinfo = (PRINTER_INFO_2)Marshal.PtrToStructure(ptrPrinterI nfo, typeof(PRINTER_INFO_2));
if (pinfo.pDevMode == IntPtr.Zero)
{
// If GetPrinter didn't fill in the DEVMODE, try to get it by calling
// DocumentProperties...

IntPtr ptrZero = IntPtr.Zero;

//get the size of the devmode structure
sizeOfDevMode = DocumentProperties(IntPtr.Zero, hPrinter, sPrinterName, ptrZero, ref ptrZero, 0); if (nRet <= 0)
{
return false;
}

ptrDM = Marshal.AllocCoTaskMem(sizeOfDevMode);

nRet = DocumentProperties(IntPtr.Zero, hPrinter, sPrinterName, ptrDM,
ref ptrZero, DM_OUT_BUFFER);
if ((nRet < 0) || (ptrDM == IntPtr.Zero))
{
//Cannot get the DEVMODE structure.
return false;
}

pinfo.pDevMode = ptrDM;

}

dm = (DEVMODE)Marshal.PtrToStructure(pinfo.pDevMode, typeof(DEVMODE));
if (!Convert.ToBoolean(dm.dmFields & DM_DUPLEX))
{
//You cannot modify the duplex flag for this printer
//because it does not support duplex or the driver does not support setting //it from the Windows API.
return false;
}

//update fields
dm.dmDuplex = (short)nDuplexSetting;
dm.dmFields = DM_DUPLEX;

Marshal.StructureToPtr(dm,pinfo.pDevMode,true);

//pinfo.pDevMode = ptrDM;
pinfo.pSecurityDescriptor = IntPtr.Zero;

//update driver dependent part of the DEVMODE
nRet = DocumentProperties(IntPtr.Zero, hPrinter, sPrinterName, pinfo.pDevMode , ref pinfo.pDevMode, (DM_IN_BUFFER | DM_OUT_BUFFER));
if (nRet < 0)
{
//Unable to set duplex setting to this printer.
return false;
}

Marshal.StructureToPtr(pinfo,ptrPrinterInfo,true);

lastError = Marshal.GetLastWin32Error();

nRet = Convert.ToInt16(SetPrinter(hPrinter, 2, ptrPrinterInfo, 0));
if (nRet == 0)
{
//Unable to set shared printer settings.
lastError = Marshal.GetLastWin32Error();
string myErrMsg = GetErrorMessage(lastError);

return false;
}
}
if (hPrinter != IntPtr.Zero)
ClosePrinter(hPrinter);

return Convert.ToBoolean(nRet);

}//End SetPrinterDuplex
It fails at the "setPrinter" with the following error "Error 1797: The printer driver is unknown.\r\n".
By the way, the code works in VB6 but I don't want it to work in VB6, I want it to work in C#!!
Any help is appreciated.

Nov 15 '05 #3

P: n/a
I've tried that already but for some reason the duplex setting does not take effect
It may work for that immediate PrintDocument however i need it to be a system change for the logged on session

Reason being is I am programatically sending a pdf to the printer using adobe acrobat. I can't find any acrobat documentation in their SDK about printing in duplex so I am trying to achieve it by changing the duplex session for the printer for that user/session

I know Word does a similar thing whereby you can set the duplex setting within Word which is different from the duplex setting that is default on the printer driver.
Nov 15 '05 #4

P: n/a

Hi joeeds,

I have reproduced out your problem, but I still did not figure out the
cause.

I will spend more time on it, Thanks for your understanding.

Best regards,
Jeffrey Tan
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "as is" with no warranties and confers no rights.

Nov 15 '05 #5

P: n/a
Thank you for your assistance Jeffrey

At the moment I have resorted to using the "c" code provided in the knowledge base article in msdn and calling it in my C# code via DllImport. Crude but works however I would still like to know what exactly I was doing wrong and ultimately to be able to replicate the same functionality in managed code
Nov 15 '05 #6

P: n/a

Hi joeeds,

Thanks very much for your feedback.

I am glad you have found a way to workaround this issue. Actually, we are
still doing research on this issue.

We will reply to you, once figure it out.

Thanks for your understanding.

Best regards,
Jeffrey Tan
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "as is" with no warranties and confers no rights.

Nov 15 '05 #7

P: n/a
Thanks for your persistence Jeffrey

I eagerly await any advice you can give me regarding this

Regards

Joseph
Nov 15 '05 #8

P: n/a

Hi Joseph,

I have added a reply to you, please refer to it, thanks

Best regards,
Jeffrey Tan
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "as is" with no warranties and confers no rights.

Nov 15 '05 #9

P: n/a

Hi Joseph,

Thanks very much for your feedback.

Yes, you can not view the attached files through IE in Microsoft site. You
should use Outlook Express to find the attached files.
Anyway, I will send the attached project through email to you.

Thanks

Best regards,
Jeffrey Tan
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "as is" with no warranties and confers no rights.

Nov 15 '05 #10

P: n/a
Hi Joseph,

Have you received my sample project?

Does it work for you? Please feel free to feedback. Thanks

Best regards,
Jeffrey Tan
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "as is" with no warranties and confers no rights.

Nov 15 '05 #11

This discussion thread is closed

Replies have been disabled for this discussion.