如何以编程方式为 Windows 创建空打印机?
Posted
技术标签:
【中文标题】如何以编程方式为 Windows 创建空打印机?【英文标题】:How do I programmatically create a null printer for Windows? 【发布时间】:2016-09-26 21:55:33 【问题描述】:我将提出并回答我自己的问题。我原以为需要创建一个空打印机并不是一件罕见的事情,但是在我找到一个可行的解决方案之前,我花了很长时间进行谷歌搜索和组装。我找到了很多关于通过 Windows GUI 创建空打印机的答案,但有关以编程方式执行此操作的信息相对稀缺且分散。希望我的回答或它引出的更好建议能为其他一些可怜的 shmo 节省一些时间。
【问题讨论】:
虽然在这里提出并回答您自己的问题是完全可以接受的,但您提出的问题必须符合与任何其他问题相同的质量标准。这没有。请edit 清楚地解释您要解决的问题并提出明确说明的问题,然后您发布的答案可以解决。 我很抱歉。我认为标题本身清楚地说明了这个问题。也许我离它太近了,看不到它是怎么做到的?给我一些具体的批评,我会尽力改进它。 删除问题的所有内容并添加需要创建“空打印机”的原因。目前帖子只包含“搜索了很多”和“我很棒”的文字,没有明确的理由为什么会这样做。 (您的问题在确定后可能会成为主题)。 我给了你具体的批评:清楚地解释问题 - 它应该解释得足够清楚,以便在这里对未来的读者有所帮助;他们应该能够确定这是否与他们试图解决的问题相同。我不知道 problem 是什么,也不知道您为什么认为 null 打印机 可以解决它。为什么需要null 打印机?当没有人知道您要解决的问题时,您希望它如何获得更好的建议? 好吧,你想知道的不是什么我想做什么,而是为什么我需要(或认为我需要)去做。我没有意识到动机进入了它,我认为感兴趣的只是“我该怎么做X”。我会重新编写和更新。谢谢你对我的耐心。 【参考方案1】:这“对我有用”。我想有一种更优雅的方法可以实现这一点,并且我希望有很多改进/更正代码的建议,但我无法找到一个简洁完整的答案来解决我认为相对普遍的需求。我有一个相当具体的要求,显然这段代码可以泛化,可以添加适当的错误处理等等。
//pd is a PrintDocument. used like:
PrintController printController = new StandardPrintController();
pd.PrintController = printController;
NullPrinter np = new NullPrinter();
if (!np.NullPortExists())
np.CreateNullPort();
if (!np.NullPrinterExists())
np.CreateNullPrinter();
pd.PrinterSettings.PrinterName = "NUL_PRINTER";
/*********************************************/
using System;
using System.Management; // This must also be added as a reference
using System.Drawing.Printing;
using System.Runtime.InteropServices;
namespace YourFavoriteNamespace
//
// This helper class has methods to determine whether a 'Nul Printer' exists,
// and to create a null port and null printer if it does not.
//
public class NullPrinter
// Printer port management via Windows GUI (Windows 7, probably same in other versions):
//
// Go to printers & devices
// Select any printer
// Click on Print server properties
// Select Ports tab
// Add or remove (local) port
// To remove a local port, if "in use", stop and restart the print spooler service.
// It seems that the most recently used port will be "in use" until the system restarts,
// or until another port is used.
// A port may also be added when adding a printer.
// Valid names for a Null port appear to be NUL, NULL, NUL: - all case insensitive.
public bool NullPortExists()
for (int i = 0; i < PrinterSettings.InstalledPrinters.Count; i++)
string printerName = PrinterSettings.InstalledPrinters[i];
string query = string.Format("SELECT * from Win32_Printer WHERE Name LIKE '%0'", printerName);
ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);
ManagementObjectCollection coll = searcher.Get();
foreach (ManagementObject printer in coll)
string pName = printer["PortName"].ToString();
if (pName.Equals("NULL", StringComparison.InvariantCultureIgnoreCase) ||
pName.Equals("NUL", StringComparison.InvariantCultureIgnoreCase) ||
pName.Equals("NUL:", StringComparison.InvariantCultureIgnoreCase))
return true;
return false;
// The application that uses this requires a printer specifically named "NUL_PRINTER"
public bool NullPrinterExists()
for (int i = 0; i < PrinterSettings.InstalledPrinters.Count; i++)
if (PrinterSettings.InstalledPrinters[i] == "NUL_PRINTER")
return true;
return false;
public bool CreateNullPort()
return Winspool.AddLocalPort("NUL") == 0 ? true : false;
public void CreateNullPrinter()
Winspool.AddPrinter("NUL_PRINTER");
/*********************************************************/
//
// This Winspool class was mostly borrowed and adapted
// from several different people's blog posts,
// the links to which I have lost.
// Thank you, whoever you are.
//
public static class Winspool
[StructLayout(LayoutKind.Sequential)]
private class PRINTER_DEFAULTS
public string pDatatype;
public IntPtr pDevMode;
public int DesiredAccess;
[DllImport("winspool.drv", CharSet = CharSet.Auto)]
static extern IntPtr AddPrinter(string pName, uint Level, [In] ref PRINTER_INFO_2 pPrinter);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
struct PRINTER_INFO_2
public string pServerName,
pPrinterName,
pShareName,
pPortName,
pDriverName,
pComment,
pLocation;
public IntPtr pDevMode;
public string pSepFile,
pPrintProcessor,
pDatatype,
pParameters;
public IntPtr pSecurityDescriptor;
public uint Attributes,
Priority,
DefaultPriority,
StartTime,
UntilTime,
Status,
cJobs,
AveragePPM;
[DllImport("winspool.drv", EntryPoint = "XcvDataW", SetLastError = true)]
private static extern bool XcvData(
IntPtr hXcv,
[MarshalAs(UnmanagedType.LPWStr)] string pszDataName,
IntPtr pInputData,
uint cbInputData,
IntPtr pOutputData,
uint cbOutputData,
out uint pcbOutputNeeded,
out uint pwdStatus);
[DllImport("winspool.drv", EntryPoint = "OpenPrinterA", SetLastError = true)]
private static extern int OpenPrinter(
string pPrinterName,
ref IntPtr phprinter,
PRINTER_DEFAULTS pDefault);
[DllImport("winspool.drv", EntryPoint = "ClosePrinter")]
private static extern int ClosePrinter(IntPtr hPrinter);
public static int AddLocalPort(string portName)
PRINTER_DEFAULTS def = new PRINTER_DEFAULTS();
def.pDatatype = null;
def.pDevMode = IntPtr.Zero;
def.DesiredAccess = 1; //Server Access Administer
IntPtr hPrinter = IntPtr.Zero;
int n = OpenPrinter(",XcvMonitor Local Port", ref hPrinter, def);
if (n == 0)
return Marshal.GetLastWin32Error();
if (!portName.EndsWith("\0"))
portName += "\0"; // Must be a null terminated string
// Must get the size in bytes. .NET strings are formed by 2-byte characters
uint size = (uint)(portName.Length * 2);
// Alloc memory in HGlobal to set the portName
IntPtr portPtr = Marshal.AllocHGlobal((int)size);
Marshal.Copy(portName.ToCharArray(), 0, portPtr, portName.Length);
uint NotUsedByUs;
uint xcvResult;
XcvData(hPrinter, "AddPort", portPtr, size, IntPtr.Zero, 0, out NotUsedByUs, out xcvResult);
ClosePrinter(hPrinter);
Marshal.FreeHGlobal(portPtr);
return (int)xcvResult;
public static void AddPrinter(string PrinterName)
IntPtr mystrptr = new IntPtr(0);
IntPtr mysend2;
PRINTER_INFO_2 pi = new PRINTER_INFO_2();
pi.pServerName = "";
pi.pPrinterName = PrinterName;
pi.pShareName = "NUL";
pi.pPortName = "NUL";
pi.pDriverName = "Generic / Text Only";
pi.pComment = "No Comment";
pi.pLocation = "Local";
pi.pDevMode = mystrptr;
pi.pSepFile = "";
pi.pPrintProcessor = "WinPrint";
pi.pDatatype = "RAW";
pi.pParameters = "";
pi.pSecurityDescriptor = mystrptr;
mysend2 = AddPrinter(null,2, ref pi);
【讨论】:
嘿@mickeyf,你有没有想出任何其他方法来做到这一点或改进你的代码? @jnewbie 我有很多可能的盘子,结果证明这对于这种情况“足够好”,所以我没有再花时间在上面了。我希望它对你有一些帮助。我不会质疑你的动机。 哈哈哈,谢谢!您是否有将数据发送到创建的打印机的代码? @jnewbie 只需将其发送到任何其他打印机即可。它当然最终会进入位桶,但与此同时 Windows 会创建每个页面。您可以在为 PrintDocument.BeginPrint、PrintDocument.PrintPage 等事件编写的方法中对此进行监控。这样做的一个用途是,您可以确定实际需要的页数,即使在数据非常多变的情况下,以某种其他方式预先计算它会非常尴尬,比如网格可能包含在某些情况下的数据case 换行,因此不能依赖行高始终相同。 我试过了,但是没有用!请给我一些示例,说明如何读取发送到创建的打印机的任何数据?以上是关于如何以编程方式为 Windows 创建空打印机?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Windows 10 附带的 Microsoft Print To PDF 打印机以编程方式打印到 PDF 文件而不提示 C# 中的文件名