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

ActiveX 控制項動態載入 DLL 與函數指標封裝:POS 設備控制實戰案例

     編輯:關於C語言

場景:ActiveX 封裝舊有 C DLL (Police.dll) 供 Web/桌面調用

許多舊系統核心邏輯以 C DLL 形式存在 (如 POS 機、刷卡機、條碼掃描器驅動)。ActiveX 控制項作為**橋樑**,在瀏覽器或 VB/VC 容器中提供**自動化介面**,內部**動態載入 DLL (LoadLibrary)**、取得函數位址、管理裝置通訊埠 (COM 口)。

專案架構:ATL COM ActiveX (Lite Control)

  • 專案類型:**ATL COM AppWizard → DLL**,勾選 **Lite Control** (最小體積、無持久化、無連接點)。
  • 介面:**IPosCtrl** (自定義),方法對應業務流程。
  • 實作類:**CPosCtrl : public CComObjectRootEx, public IPosCtrl, public IOleObject...** (ATL 自動實作)。

介面設計 (IDL 節選)


interface IPosCtrl : IDispatch
{
    [id(1)] HRESULT Init([in] short nComPort);      // 開啟 COM 口、載入 DLL
    [id(2)] HRESULT Quit();                         // 關閉 COM 口、卸載 DLL
    [id(3)] HRESULT ModifyPosDT([in] BSTR bstrDT);  // 設定 POS 時間
    [id(4)] HRESULT EmptyPos();                     // 清空 POS 資料
};

CPosCtrl 核心實作

DLL 函數指標定義 (對應 Police.dll 導出)


// PosCtrl.cpp
#include "stdafx.h"
#include "PosActiveX.h"
#include "PosCtrl.h"

// Police.dll 導出函數指標型別
typedef HANDLE (__stdcall *PFN_Init_comm)(LPSTR strPort);      // 開啟通訊口
typedef BYTE (__stdcall *PFN_Exit)(HANDLE hCom);               // 關閉通訊口
typedef BYTE (__stdcall *PFN_Protocol)(HANDLE, BYTE, BYTE, BYTE*, BYTE*, BYTE*); // 通訊協議
typedef BYTE (__stdcall *PFN_Pipe_control)(HANDLE, BYTE);      // 多路控制
typedef BYTE (__stdcall *PFN_Dev_escape)(HANDLE);              // 解除多路

// 實際函數指標變數 (需跨實例共享?視 DLL 設計而定,此處為靜態)
#pragma data_seg("SHARED_DATA") // 若 DLL 狀態需進程共享
static HINSTANCE g_hDllInst = NULL;
static HANDLE g_hCom = NULL;
#pragma data_seg()
#pragma comment(linker, "/SECTION:SHARED_DATA,RWS")

// 函數指標
PFN_Init_comm g_pfnInitComm = NULL;
PFN_Exit g_pfnExit = NULL;
PFN_Protocol g_pfnProtocol = NULL;
PFN_Pipe_control g_pfnPipeControl = NULL;
PFN_Dev_escape g_pfnDevEscape = NULL;

// Police.dll 命令碼定義
#define CMD_INIT 0x87
#define CMD_EXIT 0x86
#define CMD_SETTIME 0x85
#define CMD_EMPTY 0x07
// ...

InitDll / ExitDll:動態載入與釋放


BOOL CPosCtrl::InitDll()
{
    if (g_hDllInst) return TRUE; // 已載入

    g_hDllInst = LoadLibrary(_T("Police.dll"));
    if (!g_hDllInst) return FALSE;

    g_pfnInitComm = (PFN_Init_comm)GetProcAddress(g_hDllInst, "Init_comm");
    g_pfnExit = (PFN_Exit)GetProcAddress(g_hDllInst, "Exit");
    g_pfnProtocol = (PFN_Protocol)GetProcAddress(g_hDllInst, "Protocol");
    g_pfnPipeControl = (PFN_Pipe_control)GetProcAddress(g_hDllInst, "Pipe_control");
    g_pfnDevEscape = (PFN_Dev_escape)GetProcAddress(g_hDllInst, "Dev_escape");

    if (!g_pfnInitComm || !g_pfnExit || !g_pfnProtocol) {
        ExitDll(); return FALSE;
    }
    return TRUE;
}

void CPosCtrl::ExitDll()
{
    if (g_hDllInst) {
        FreeLibrary(g_hDllInst);
        g_hDllInst = NULL;
        g_pfnInitComm = g_pfnExit = g_pfnProtocol = g_pfnPipeControl = g_pfnDevEscape = NULL;
    }
}

介面方法實作


STDMETHODIMP CPosCtrl::Init(short nComPort)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState()); // MFC 擴展 DLL 專用,ATL 專案通常不需

    if (!InitDll()) return E_FAIL;

    // 開啟 COM 口: "COM1", "COM2" ...
    char szPort[16];
    sprintf_s(szPort, "COM%d", nComPort);
    g_hCom = g_pfnInitComm(szPort);
    if (!g_hCom) return HRESULT_FROM_WIN32(GetLastError());

    // 初始化裝置 (發送 INIT 命令)
    BYTE bRet = g_pfnProtocol(g_hCom, CMD_INIT, 0, NULL, NULL, NULL);
    if (bRet != 0) { g_pfnExit(g_hCom); g_hCom = NULL; return E_FAIL; }

    return S_OK;
}

STDMETHODIMP CPosCtrl::Quit()
{
    if (g_hCom) {
        g_pfnExit(g_hCom);
        g_hCom = NULL;
    }
    ExitDll(); // 視架構決定是否立即卸載 DLL
    return S_OK;
}

STDMETHODIMP CPosCtrl::ModifyPosDT(BSTR bstrDT)
{
    if (!g_hCom) return E_UNEXPECTED;
    // BSTR → char* (YYYYMMDDHHMMSS 格式)
    USES_CONVERSION;
    char* pszDT = W2A(bstrDT);
    BYTE bRet = g_pfnProtocol(g_hCom, CMD_SETTIME, strlen(pszDT), (BYTE*)pszDT, NULL, NULL);
    return (bRet == 0) ? S_OK : E_FAIL;
}

STDMETHODIMP CPosCtrl::EmptyPos()
{
    if (!g_hCom) return E_UNEXPECTED;
    BYTE bRet = g_pfnProtocol(g_hCom, CMD_EMPTY, 0, NULL, NULL, NULL);
    return (bRet == 0) ? S_OK : E_FAIL;
}

關鍵技術點解析

技術點說明
LoadLibrary/GetProcAddress運行時動態連結,不需編譯期 .lib,支援 DLL 版本熱插拔、延遲載入。
函數指標型別定義嚴格對應 DLL 導出簽名 (__stdcall、參數型別、返回值),使用 typedef 提高可讀性。
呼叫慣例一致性DLL 導出 __stdcall (WINAPI),指標型別必須加 __stdcall,否則堆疊破壞。
字串轉換 (BSTR ↔ char*)自動化介面用 BSTR (Unicode),C DLL 多用 ANSI。用 USES_CONVERSION + W2A/A2W (ATL) 或 WideCharToMultiByte。
資源管理 (RAII)Init 獲取資源 (COM 口、DLL)、Quit 釋放。CPosCtrl 解構子應呼叫 Quit 確保不洩漏。
執行緒安全ActiveX 方法可能被多執行緒並發調用 (容器自由執行緒)。若 DLL 非執行緒安全,需 CCriticalSection 保護 g_hCom 及協議呼叫。
共享資料區段若多個控制項實例需共享同一 COM 口/DLL 狀態,用 #pragma data_seg + 連結器選項。否則每實例獨立載入。

部署與安全性

  • 代碼簽名:.OCX/.CAB 必須簽名 (Authenticode),IE 才允許安裝執行。
  • 標記為安全:實作 IObjectSafety 介面,SetInterfaceSafetyOptions 返回 S_OK,聲明可安全腳本初始化/存取。
  • CAB 封裝:CABARC.EXE 打包 .OCX、.INF、依賴 DLL (Police.dll) 為 .CAB,HTML CODEBASE 指向 .CAB。
  • 註冊:regsvr32 PosActiveX.ocx,安裝腳本自動處理。

現代替代方案

  • WebUSB / WebHID / Web Serial API:瀏覽器原生 API 直接存取 USB/串口設備,無需 ActiveX (僅 HTTPS、Chrome/Edge 支援)。
  • Native Messaging Host:Chrome 擴充功能與本地 Native App (C++/Go/Rust) 通訊,操作硬體。
  • Electron / Tauri / WebView2:桌面應用內嵌 Web,Node.js/C++ 直接調用 DLL。

總結

ActiveX 控制項動態載入 DLL 核心流程:**LoadLibrary → GetProcAddress → 函數指標型別轉換 → 狀態管理 (COM 口句柄、DLL 句柄) → 自動化介面封裝 → 資源釋放**。關鍵在於呼叫慣例匹配、字串編碼轉換、執行緒同步、RAII 資源管理、代碼簽名部署。雖 ActiveX 已為淘汰技術,但在舊系統遷移、特定 IE 環境維護中仍具參考價值。

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