.NET Framework 的 System.Drawing.Printing.PrinterSettings.InstalledPrinters 僅能列舉本機安裝的印表機。若需掃描區域網路內所有共享印表機,必須透過 P/Invoke 呼叫 Win32 winspool.drv 的 EnumPrinters 函式。本文完整說明結構定義、參數解析、記憶體管理與完整可執行範例。
[DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool EnumPrinters(
PrinterEnumFlags flags,
string name,
int level,
IntPtr pPrinterEnum,
int cbBuf,
out int pcbNeeded,
out int pcReturned);
[Flags]
enum PrinterEnumFlags : uint
{
PRINTER_ENUM_DEFAULT = 0x00000001,
PRINTER_ENUM_LOCAL = 0x00000002,
PRINTER_ENUM_CONNECTIONS = 0x00000004,
PRINTER_ENUM_FAVORITE = 0x00000008,
PRINTER_ENUM_NETWORK = 0x00000040, // 列舉網路印表機
PRINTER_ENUM_REMOTE = 0x00000010, // 列舉遠端印表機
PRINTER_ENUM_SHARED = 0x00000020,
PRINTER_ENUM_NAME = 0x00000080,
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
struct PRINTER_INFO_1
{
public uint Flags;
public string pDescription;
public string pName;
public string pComment;
}
EnumPrinters 需預先配置非受控記憶體緩衝區。標準模式為:
IntPtr.Zero 與 cbBuf = 0,取得所需緩衝區大小 pcbNeeded。Marshal.AllocHGlobal 配置非受控記憶體。Marshal.PtrToStructure 逐筆轉換為 managed 結構。Marshal.FreeHGlobal 釋放記憶體。using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
public static class NetworkPrinterEnumerator
{
[DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool EnumPrinters(
PrinterEnumFlags flags,
string name,
int level,
IntPtr pPrinterEnum,
int cbBuf,
out int pcbNeeded,
out int pcReturned);
[Flags]
private enum PrinterEnumFlags : uint
{
PRINTER_ENUM_NETWORK = 0x00000040,
PRINTER_ENUM_LOCAL = 0x00000002,
PRINTER_ENUM_REMOTE = 0x00000010,
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct PRINTER_INFO_1
{
public uint Flags;
public string pDescription;
public string pName;
public string pComment;
}
///
/// 列舉網路/本機/遠端所有印表機
///
public static List GetAllPrinters()
{
var result = new List();
int cbNeeded = 0, nReturned = 0;
PrinterEnumFlags flags = PrinterEnumFlags.PRINTER_ENUM_NETWORK |
PrinterEnumFlags.PRINTER_ENUM_LOCAL |
PrinterEnumFlags.PRINTER_ENUM_REMOTE;
// 1. 取得所需緩衝區大小
bool ok = EnumPrinters(flags, null, 1, IntPtr.Zero, 0, out cbNeeded, out nReturned);
int lastError = Marshal.GetLastWin32Error();
if (!ok && lastError != 122) // ERROR_INSUFFICIENT_BUFFER = 122
throw new System.ComponentModel.Win32Exception(lastError);
// 2. 配置非受控記憶體
IntPtr pBuffer = Marshal.AllocHGlobal(cbNeeded);
try
{
// 3. 實際列舉
ok = EnumPrinters(flags, null, 1, pBuffer, cbNeeded, out cbNeeded, out nReturned);
if (!ok)
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
// 4. 解析結構陣列
IntPtr current = pBuffer;
int structSize = Marshal.SizeOf();
for (int i = 0; i < nReturned; i++)
{
PRINTER_INFO_1 info = Marshal.PtrToStructure(current);
result.Add(new PrinterInfo
{
Name = info.pName,
Description = info.pDescription,
Comment = info.pComment,
Flags = info.Flags
});
current = IntPtr.Add(current, structSize); // 移至下一個結構
}
}
finally
{
// 5. 釋放非受控記憶體
Marshal.FreeHGlobal(pBuffer);
}
return result;
}
}
public class PrinterInfo
{
public string Name { get; set; }
public string Description { get; set; }
public string Comment { get; set; }
public uint Flags { get; set; }
public override string ToString() => $"{Name} | {Description} | {Comment}";
}
class Program
{
static void Main()
{
try
{
var printers = NetworkPrinterEnumerator.GetAllPrinters();
Console.WriteLine($"共找到 {printers.Count} 台印表機:");
foreach (var p in printers)
Console.WriteLine($" {p}");
}
catch (Exception ex)
{
Console.WriteLine($"錯誤: {ex.Message}");
}
Console.ReadKey();
}
}
| 項目 | 說明 |
|---|---|
| CharSet.Auto | 自動依平台選 Unicode/ANSI,相容性最佳 |
| SetLastError=true | 允許 Marshal.GetLastWin32Error() 取得詳細錯誤碼 |
| Level = 1 | 對應 PRINTER_INFO_1,若需更多資訊 (驅動程式、連接埠等) 可用 Level 2/4/5,結構不同 |
| Flags 組合 | NETWORK | LOCAL | REMOTE 可涵蓋大多數情境;CONNECTIONS 僅列出已連線的網路印表機 |
| 記憶體對齊 | StructLayout.Sequential 確保欄位順序與大小符合 Win32 定義 |
| PtrToStructure 泛型 | C# 7.0+ 支援 Marshal.PtrToStructure<T>(ptr) 簡化轉型 |
| IntPtr.Add | .NET Core / 5+ 可用 IntPtr.Add(current, structSize) 替代手動運算 |
| 權限 | 列舉網路印表機需具備網路存取權限,若在服務帳號下執行可能看不到 |
Task.Run 執行。System.Drawing.Printing 取得印表機狀態 (線上/離線/缺紙) 需額外呼叫 OpenPrinter/GetPrinter (Level 2)。透過 P/Invoke 呼叫 EnumPrinters 是 .NET 列舉網路共享印表機的標準做法。掌握「兩次呼叫配置記憶體」模式與 Marshal 非受控記憶體操作,即可安全、高效地整合 Win32 列印功能至 C# 專案中。