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

C# 結合 WQL 查詢並口基址實現 LPT 端口讀寫完整方案

     編輯:關於C#

在工業自動化、POS 系統開發中,並口(LPT)雖屬傳統介面,但因成本低、相容性好仍廣泛用於印表機、舊式儀器控制。.NET Framework 提供 SerialPort 類別支援序列埠,卻缺乏並口原生支援。本文介紹結合 inpout32.dll、Win32 API 與 WMI (WQL) 查詢動態取得並口基址,構建完整可靠的並口讀寫元件。

並口程式設計面臨的挑戰

  1. 無內建類別:.NET 無 ParallelPort 對應 SerialPort
  2. 基址不固定:傳統 0x378/0x379/0x37A 在多並口、PCIe 轉接卡環境下會變動
  3. 讀寫需不同機制:資料埠可用 Win32 WriteFile,狀態埠需 inpout32.dll 讀取
  4. 即時狀態監測:需背景執行緒輪詢狀態埠 (Base+1)

三種方案比較

方案優點缺點適用場景
Win32 API (CreateFile/WriteFile)無需第三方 DLL,系統原生無法讀取狀態埠資料單向輸出(如單純列印)
inpout32.dll (Inp32/Out32)可讀寫所有埠需知基址,需簽署驅動程式(x64)雙向通訊、狀態讀取
WQL 查詢 Win32_PortResource動態取得實際基址需 System.Management 參考,權限要求較高解決基址不固定問題

核心實作:ParallelPort 類別


using System;
using System.Management;
using System.Runtime.InteropServices;
using System.Threading;

public class ParallelPort : IDisposable
{
    // ===== inpout32.dll 匯入 =====
    [DllImport("inpout32.dll", EntryPoint = "Out32")]
    public static extern void Output(uint address, int value);

    [DllImport("inpout32.dll", EntryPoint = "Inp32")]
    public static extern int Input(uint address);

    // ===== Win32 API 匯入 =====
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess,
        int dwShareMode, IntPtr lpSecurityAttributes, int dwCreationDisposition,
        int dwFlagsAndAttributes, IntPtr hTemplateFile);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool WriteFile(IntPtr hFile, byte[] lpBuffer, int nNumberOfBytesToWrite,
        out int lpNumberOfBytesWritten, IntPtr lpOverlapped);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool CloseHandle(IntPtr hObject);

    private const uint GENERIC_WRITE = 0x40000000;
    private const uint OPEN_EXISTING = 3;
    private const int INVALID_HANDLE_VALUE = -1;

    private IntPtr _handle = (IntPtr)INVALID_HANDLE_VALUE;
    private uint _basePort = 0;
    private bool _isWorking = false;
    private Thread _monitorThread;
    private int _lastStatus = 0;

    public string PortName { get; private set; }
    public bool IsOpen => _handle != (IntPtr)INVALID_HANDLE_VALUE;

    public event EventHandler StatusChanged;

    public ParallelPort(string portName)
    {
        PortName = portName;
        DetectBasePortViaWQL();
    }

    // ===== WQL 動態偵測並口基址 =====
    private void DetectBasePortViaWQL()
    {
        // 查詢並口裝置關聯的資源
        string wql = $"ASSOCIATORS OF {{Win32_ParallelPort.DeviceID='{PortName}'}}";
        using (var searcher = new ManagementObjectSearcher(wql))
        {
            foreach (ManagementObject obj in searcher.Get())
            {
                if (obj.ClassPath.ClassName == "Win32_PortResource")
                {
                    _basePort = Convert.ToUInt32(obj.Properties["StartingAddress"].Value);
                    break;
                }
            }
        }
        if (_basePort == 0)
            throw new InvalidOperationException($"無法取得 {PortName} 的基址");
    }

    // ===== 開啟並口(使用 Win32 CreateFile 取得 handle)=====
    public void Open()
    {
        _handle = CreateFile(PortName, GENERIC_WRITE, 0, IntPtr.Zero,
            (int)OPEN_EXISTING, 0, IntPtr.Zero);

        if (_handle == (IntPtr)INVALID_HANDLE_VALUE)
            throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(),
                $"無法開啟 {PortName}");

        _isWorking = true;
        _monitorThread = new Thread(MonitorStatusPort) { IsBackground = true };
        _monitorThread.Start();
    }

    // ===== 寫入資料埠 (Base + 0) =====
    public bool Write(byte[] data)
    {
        if (!IsOpen) return false;
        int written;
        return WriteFile(_handle, data, data.Length, out written, IntPtr.Zero);
    }

    // ===== 讀取狀態埠 (Base + 1) 使用 inpout32 =====
    public byte[] ReadStatus(int length)
    {
        byte[] result = new byte[length];
        for (int i = 0; i < length; i++)
            result[i] = (byte)Input(_basePort + 1); // 狀態埠
        return result;
    }

    // ===== 背景執行緒監測狀態變化 =====
    private void MonitorStatusPort()
    {
        while (_isWorking)
        {
            int current = Input(_basePort + 1);
            if (current != _lastStatus)
            {
                _lastStatus = current;
                StatusChanged?.Invoke(this, new PortStatusEventArgs(current));
            }
            Thread.Sleep(500);
        }
    }

    public void Close()
    {
        _isWorking = false;
        _monitorThread?.Join(1000);
        if (_handle != (IntPtr)INVALID_HANDLE_VALUE)
        {
            CloseHandle(_handle);
            _handle = (IntPtr)INVALID_HANDLE_VALUE;
        }
    }

    public void Dispose() => Close();
}

public class PortStatusEventArgs : EventArgs
{
    public int StatusByte { get; }
    public PortStatusEventArgs(int status) => StatusByte = status;
}

關鍵技術點解析

WQL 查詢並口資源

ASSOCIATORS OF {Win32_ParallelPort.DeviceID='LPT1'} 回傳該並口關聯的所有資源物件,篩選 Win32_PortResource 類別即可取得 StartingAddress(基址)。此法解決多並口、PCIe 轉接卡導致基址漂移問題。

混合使用 Win32 與 inpout32

  • 資料輸出:用 CreateFile("LPT1") + WriteFile,支援緩衝、重疊 I/O,相容性佳
  • 狀態讀取:Win32 ReadFile 讀 LPT 較不穩定,改用 inpout32.dll 的 Inp32(Base+1) 直接讀取狀態埠

x64 環境部署注意

  1. inpout32.dll 必須使用簽署版本(如 inpoutx64.dll),否則載入失敗
  2. 專案設定 Prefer 32-bit 取消勾選,或準備 x86/x64 兩版 DLL 並透過 DllImport 條件編譯
  3. 需管理員權限執行(存取埠硬體資源)

使用範例


var port = new ParallelPort("LPT1");
port.StatusChanged += (s, e) => 
    Console.WriteLine($"狀態變更: 0x{e.StatusByte:X2}");

port.Open();
port.Write(new byte[] { 0x1B, 0x40 }); // ESC @ 初始化印表機
port.Write(System.Text.Encoding.GetEncoding(950).GetBytes("測試列印內容\n\n"));
Thread.Sleep(2000);
port.Close();

常見問題排查

現象可能原因解決方案
CreateFile 回傳 -1埠被佔用/不存在/權限不足檢查裝置管理員、以管理員執行、確認 PortName 正確
inpout32.dll 載入失敗x64 程序載入 x86 DLL / 未簽署使用簽署版 inpoutx64.dll 或啟用 Prefer 32-bit
WQL 查無結果WMI 服務未啟動/權限不足啟動 Winmgmt 服務、加入 Administrators 群組
列印亂碼編碼不匹配確認印表機代碼頁,使用正確 Encoding (Big5/UTF-8)

總結

透過 WQL 動態查詢並口基址,結合 Win32 API 處理資料輸出、inpout32.dll 處理狀態讀取,並以背景執行緒監測狀態變化,即可構建穩健、可部署於現代 Windows 環境的並口控制元件。此架構已在多套 POS 系統、儀器控制專案中驗證可靠。

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