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

C++Builder 實作 I/O 端口讀寫的兩種核心技術詳解

     編輯:關於C++

背景動機

C++Builder (BCB) 移除了傳統 Turbo C/Borland C++ 中的 inportb/outportb 等直接 I/O 函式,開發者在需要控制並行埠、序列埠或自訂硬體卡時,必須自行實作底層存取。本文詳細解析兩種主流做法:內嵌組合語言 與 __emit__ 機器碼嵌入,並提供可直接套用的封裝函式與範例專案。

方法一:內嵌組合語言 (Inline Assembly)

BCB 使用 asm { ... } 區塊包裹組語指令,編譯器會將其直接轉換為機器碼嵌入目的檔,無需外部組譯器。

輸出函式 OutPort

void OutPort(unsigned short port, unsigned char value) {
    asm {
        mov  dx, port   ; 端口位址 → DX
        mov  al, value  ; 8-bit 資料 → AL
        out  dx, al     ; AL → [DX] 端口
    }
}

輸入函式 InPort

unsigned char InPort(unsigned short port) {
    unsigned char value;
    asm {
        mov  dx, port   ; 端口位址 → DX
        in   al, dx     ; [DX] 端口 → AL
        mov  value, al  ; AL → 區域變數
    }
    return value;
}

優點:可讀性高、易除錯、支援標註註解。
缺點:僅限 32 位元編譯器;64 位元 BCB 不支援內嵌組語。

方法二:__emit__ 機器碼直接嵌入

__emit__ 為 BCB 專屬內建函式,接受一連串機器碼位元組,編譯時直接寫入目的碼,完全繞過組譯器。需熟悉 x86 指令編碼,稍有錯誤即導致當機。

對應機器碼解析

指令機器碼說明
MOV EDX, [port]8B 95 [offset]將區域變數 port 位址載入 EDX
MOV AL, [value]8A 85 [offset]將區域變數 value 載入 AL
OUT DX, AL66 EE16-bit 端口輸出 (66h 前綴覆蓋操作數大小)
IN AL, DX66 EC16-bit 端口輸入
MOV [value], AL88 85 [offset]AL 存回區域變數

實作程式碼

void OutPort(unsigned short port, unsigned char value) {
    __emit__(0x8B, 0x95, &port);  // MOV EDX, port
    __emit__(0x8A, 0x85, &value); // MOV AL, value
    __emit__(0x66, 0xEE);         // OUT DX, AL
}

unsigned char InPort(unsigned short port) {
    unsigned char value;
    __emit__(0x8B, 0x95, &port);  // MOV EDX, port
    __emit__(0x66, 0xEC);         // IN AL, DX
    __emit__(0x88, 0x85, &value); // MOV value, AL
    return value;
}

優點:理論上可用於 64 位元編譯器 (需調整指令編碼)。
缺點:極難維護、無型別檢查、錯誤機器碼會導致系統崩潰。

實戰範例:控制並行埠 (LPT1) LED 顯示

建立 VCL Forms 專案,放置兩個 TButton,分別命名 btnWrite、btnRead,Caption 設為「寫端口」/「讀端口」。於表單 .h 宣告兩個 public 方法,.cpp 實作事件處理:

void __fastcall TForm1::btnWriteClick(TObject *Sender) {
    OutPort(0x378, 0x00);  // 資料埠輸出 0
    OutPort(0x379, 0x00);  // 狀態埠清除
}

void __fastcall TForm1::btnReadClick(TObject *Sender) {
    int val = InPort(0x37A);  // 讀取控制埠
    ShowMessage("讀取值: " + IntToHex(val, 2));
}

編譯執行後,若接上並行埠 LED 測試卡,即可觀察輸出效果。若需切換為 __emit__ 版本,只需替換表單類別中的兩個函式實作即可,呼叫端完全不變。

進階議題與替代方案

  • Windows NT/2000/XP 以後:應用層禁止直接 I/O,需撰寫核心模式驅動程式 (KMDF/WDM) 或使用 inpout32.sys、WinRing0 等第三方驅動封裝。
  • 64 位元 BCB:內嵌組語不支援,若維護舊專案請改用 __emit__ (需重新計算 64-bit 指令編碼) 或改用驅動程式介面。
  • USB/PCIe 裝置:現代周邊多非 ISA 總線,直接 I/O 已不適用,請改用 SetupAPI + DeviceIoControl 流程。

總結

內嵌組語與 __emit__ 均能在 BCB 32 位元環境下實現 I/O 端口存取,前者適合快速原型與教學,後者適合極度精簡或需避開組譯器限制的場合。實務上建議封裝為獨立單元 (PortIO.cpp/h),並以條件編譯切換實作,隔離平台差異。

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