首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 呼叫 Win32 API 列舉區域網路共享印表機:EnumPrinters 完整實作

     編輯:關於C語言

前言

.NET Framework 的 System.Drawing.Printing.PrinterSettings.InstalledPrinters 僅能列舉本機安裝的印表機。若需掃描區域網路內所有共享印表機,必須透過 P/Invoke 呼叫 Win32 winspool.drv 的 EnumPrinters 函式。本文完整說明結構定義、參數解析、記憶體管理與完整可執行範例。

Win32 API 簽名與結構

[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 需預先配置非受控記憶體緩衝區。標準模式為:

  1. 第一次呼叫傳 IntPtr.Zero 與 cbBuf = 0,取得所需緩衝區大小 pcbNeeded。
  2. Marshal.AllocHGlobal 配置非受控記憶體。
  3. 第二次呼叫傳入指標與大小,填入結構陣列。
  4. 用 Marshal.PtrToStructure 逐筆轉換為 managed 結構。
  5. 最後 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) 替代手動運算
權限列舉網路印表機需具備網路存取權限,若在服務帳號下執行可能看不到

進階:非同步與快取

  • 列舉網路印表機可能耗時數秒,UI 應用請放入 Task.Run 執行。
  • 結果可快取並定期重整 (如每 5 分鐘),避免重複掃描造成網路廣播風暴。
  • 搭配 System.Drawing.Printing 取得印表機狀態 (線上/離線/缺紙) 需額外呼叫 OpenPrinter/GetPrinter (Level 2)。

結語

透過 P/Invoke 呼叫 EnumPrinters 是 .NET 列舉網路共享印表機的標準做法。掌握「兩次呼叫配置記憶體」模式與 Marshal 非受控記憶體操作,即可安全、高效地整合 Win32 列印功能至 C# 專案中。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved