EDN Admin
Well-known member
We have an application that produces print files via the Windows Spooler. When we have stress tested this application it appears that we are occassionally getting empty files being returned by the spooler.
We subsequently boiled the code down into a simply console app and the issue still occurs. We are at a loss to understand if this is our C# code, us missing something in the gdi32 usage, or something in the spooler.
The code we are using to test is below. In a nutshell it will open 5 threads, on each thread it will print 20 empty jobs (to file). What would appear to be happening is that when 2 threads are trying to print a the exact same time we get a empty file.
To recreate:
Create a folder c
rinttest
Create console app using the below code
Run console app and enter valid printer name (on your machine)
<br/>This will create a log for each thread, and the relevant print files. You will notice that some of these will be of zero size - this is what we are trying to get to the bottom of.
Thanks in advance,
Mark<br/>
<pre lang="x-c# using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace PrintTest
{
class Program
{
static void Main(string[] args)
{
string input = Console.ReadLine();
while (input != "q")
{
for (int i = 1; i <= 5; i++)
{
TestThread testThread = new TestThread(input, i);
testThread.Execute();
}
input = Console.ReadLine();
}
}
}
}<br/>[/code]
<pre lang="x-c# using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
namespace PrintTest
{
public struct DOCINFO
{
public int cbSize;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpszDocName;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpszOutput;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpszDatatype;
public int ftType;
}
public static class NativeMethods
{
[DllImport("gdi32.dll", CharSet = CharSet.Unicode, EntryPoint = "CreateDCW", SetLastError = true)]
public static extern IntPtr CreateDC(string lpszDriver, string lpszDevice, string lpszOutput, IntPtr lpInitData);
[DllImport("gdi32.dll", CharSet = CharSet.Auto, EntryPoint = "DeleteDC", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DeleteDC(IntPtr hdc);
[DllImport("gdi32.dll", CharSet = CharSet.Unicode, EntryPoint = "StartDocW", SetLastError = true)]
public static extern int StartDoc(IntPtr hdc, ref DOCINFO ipdi);
[DllImport("gdi32.dll", CharSet = CharSet.Auto, EntryPoint = "StartPage", SetLastError = true)]
public static extern int StartPage(IntPtr hdc);
[DllImport("gdi32.dll", CharSet = CharSet.Auto, EntryPoint = "EndPage", SetLastError = true)]
public static extern int EndPage(IntPtr hdc);
[DllImport("gdi32.dll", CharSet = CharSet.Auto, EntryPoint = "EndDoc", SetLastError = true)]
public static extern int EndDoc(IntPtr hdc);
}
public class TestThread
{
private string printerName;
private int id;
public TestThread(string printerName, int id)
{
this.printerName = printerName;
this.id = id;
}
public void Execute()
{
Thread thread = new Thread(Print);
thread.Start();
}
private void Print(object state)
{
using (StreamWriter sw = new StreamWriter(String.Format(@"c
rinttestsimplelog_{0}.txt", id)))
{
sw.WriteLine(String.Format("Printer Name: {0}", printerName));
sw.Flush();
IntPtr hdc = NativeMethods.CreateDC(null, this.printerName, null, IntPtr.Zero);
sw.WriteLine(String.Format("CreateDC returned: {0}", hdc));
sw.Flush();
if (hdc == IntPtr.Zero)
{
sw.WriteLine(String.Format("** ERROR CODE: " + Marshal.GetLastWin32Error()));
sw.Flush();
}
try
{
for (int i = 1; i <= 20; i++)
{
DOCINFO docInfo = new DOCINFO();
docInfo.cbSize = Marshal.SizeOf(typeof(DOCINFO));
docInfo.lpszDocName = String.Format("testDoc_{0}", this.id);
docInfo.lpszOutput = String.Format(@"c
rinttestsimple_{0}_{1}.prn", this.id, i);
docInfo.lpszDatatype = "RAW";
sw.WriteLine("***** PRINT START");
sw.WriteLine("lpszOutput set to: " + docInfo.lpszOutput);
sw.Flush();
int result = NativeMethods.StartDoc(hdc, ref docInfo);
sw.WriteLine(String.Format("StartDoc returned: {0}", result));
sw.Flush();
if (result == 0)
{
sw.WriteLine(String.Format("** ERROR CODE: " + Marshal.GetLastWin32Error()));
sw.Flush();
}
result = NativeMethods.StartPage(hdc);
sw.WriteLine(String.Format("StartPage returned: {0}", result));
sw.Flush();
if (result == 0)
{
sw.WriteLine(String.Format("** ERROR CODE: " + Marshal.GetLastWin32Error()));
sw.Flush();
}
result = NativeMethods.EndPage(hdc);
sw.WriteLine(String.Format("EndPage returned: {0}", result));
sw.Flush();
if (result == 0)
{
sw.WriteLine(String.Format("** ERROR CODE: " + Marshal.GetLastWin32Error()));
sw.Flush();
}
result = NativeMethods.EndDoc(hdc);
sw.WriteLine(String.Format("EndDoc returned: {0}", result));
sw.Flush();
if (result == 0)
{
sw.WriteLine(String.Format("** ERROR CODE: " + Marshal.GetLastWin32Error()));
sw.Flush();
}
sw.WriteLine("PRINT END *****");
}
}
finally
{
NativeMethods.DeleteDC(hdc);
}
}
}
}
}
[/code]
View the full article
We subsequently boiled the code down into a simply console app and the issue still occurs. We are at a loss to understand if this is our C# code, us missing something in the gdi32 usage, or something in the spooler.
The code we are using to test is below. In a nutshell it will open 5 threads, on each thread it will print 20 empty jobs (to file). What would appear to be happening is that when 2 threads are trying to print a the exact same time we get a empty file.
To recreate:
Create a folder c

Create console app using the below code
Run console app and enter valid printer name (on your machine)
<br/>This will create a log for each thread, and the relevant print files. You will notice that some of these will be of zero size - this is what we are trying to get to the bottom of.
Thanks in advance,
Mark<br/>
<pre lang="x-c# using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace PrintTest
{
class Program
{
static void Main(string[] args)
{
string input = Console.ReadLine();
while (input != "q")
{
for (int i = 1; i <= 5; i++)
{
TestThread testThread = new TestThread(input, i);
testThread.Execute();
}
input = Console.ReadLine();
}
}
}
}<br/>[/code]
<pre lang="x-c# using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
namespace PrintTest
{
public struct DOCINFO
{
public int cbSize;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpszDocName;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpszOutput;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpszDatatype;
public int ftType;
}
public static class NativeMethods
{
[DllImport("gdi32.dll", CharSet = CharSet.Unicode, EntryPoint = "CreateDCW", SetLastError = true)]
public static extern IntPtr CreateDC(string lpszDriver, string lpszDevice, string lpszOutput, IntPtr lpInitData);
[DllImport("gdi32.dll", CharSet = CharSet.Auto, EntryPoint = "DeleteDC", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DeleteDC(IntPtr hdc);
[DllImport("gdi32.dll", CharSet = CharSet.Unicode, EntryPoint = "StartDocW", SetLastError = true)]
public static extern int StartDoc(IntPtr hdc, ref DOCINFO ipdi);
[DllImport("gdi32.dll", CharSet = CharSet.Auto, EntryPoint = "StartPage", SetLastError = true)]
public static extern int StartPage(IntPtr hdc);
[DllImport("gdi32.dll", CharSet = CharSet.Auto, EntryPoint = "EndPage", SetLastError = true)]
public static extern int EndPage(IntPtr hdc);
[DllImport("gdi32.dll", CharSet = CharSet.Auto, EntryPoint = "EndDoc", SetLastError = true)]
public static extern int EndDoc(IntPtr hdc);
}
public class TestThread
{
private string printerName;
private int id;
public TestThread(string printerName, int id)
{
this.printerName = printerName;
this.id = id;
}
public void Execute()
{
Thread thread = new Thread(Print);
thread.Start();
}
private void Print(object state)
{
using (StreamWriter sw = new StreamWriter(String.Format(@"c

{
sw.WriteLine(String.Format("Printer Name: {0}", printerName));
sw.Flush();
IntPtr hdc = NativeMethods.CreateDC(null, this.printerName, null, IntPtr.Zero);
sw.WriteLine(String.Format("CreateDC returned: {0}", hdc));
sw.Flush();
if (hdc == IntPtr.Zero)
{
sw.WriteLine(String.Format("** ERROR CODE: " + Marshal.GetLastWin32Error()));
sw.Flush();
}
try
{
for (int i = 1; i <= 20; i++)
{
DOCINFO docInfo = new DOCINFO();
docInfo.cbSize = Marshal.SizeOf(typeof(DOCINFO));
docInfo.lpszDocName = String.Format("testDoc_{0}", this.id);
docInfo.lpszOutput = String.Format(@"c

docInfo.lpszDatatype = "RAW";
sw.WriteLine("***** PRINT START");
sw.WriteLine("lpszOutput set to: " + docInfo.lpszOutput);
sw.Flush();
int result = NativeMethods.StartDoc(hdc, ref docInfo);
sw.WriteLine(String.Format("StartDoc returned: {0}", result));
sw.Flush();
if (result == 0)
{
sw.WriteLine(String.Format("** ERROR CODE: " + Marshal.GetLastWin32Error()));
sw.Flush();
}
result = NativeMethods.StartPage(hdc);
sw.WriteLine(String.Format("StartPage returned: {0}", result));
sw.Flush();
if (result == 0)
{
sw.WriteLine(String.Format("** ERROR CODE: " + Marshal.GetLastWin32Error()));
sw.Flush();
}
result = NativeMethods.EndPage(hdc);
sw.WriteLine(String.Format("EndPage returned: {0}", result));
sw.Flush();
if (result == 0)
{
sw.WriteLine(String.Format("** ERROR CODE: " + Marshal.GetLastWin32Error()));
sw.Flush();
}
result = NativeMethods.EndDoc(hdc);
sw.WriteLine(String.Format("EndDoc returned: {0}", result));
sw.Flush();
if (result == 0)
{
sw.WriteLine(String.Format("** ERROR CODE: " + Marshal.GetLastWin32Error()));
sw.Flush();
}
sw.WriteLine("PRINT END *****");
}
}
finally
{
NativeMethods.DeleteDC(hdc);
}
}
}
}
}
[/code]
View the full article