許多舊系統核心邏輯以 C DLL 形式存在 (如 POS 機、刷卡機、條碼掃描器驅動)。ActiveX 控制項作為**橋樑**,在瀏覽器或 VB/VC 容器中提供**自動化介面**,內部**動態載入 DLL (LoadLibrary)**、取得函數位址、管理裝置通訊埠 (COM 口)。
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 資料
};
// 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
// ...
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 + 連結器選項。否則每實例獨立載入。 |
IObjectSafety 介面,SetInterfaceSafetyOptions 返回 S_OK,聲明可安全腳本初始化/存取。CABARC.EXE 打包 .OCX、.INF、依賴 DLL (Police.dll) 為 .CAB,HTML CODEBASE 指向 .CAB。regsvr32 PosActiveX.ocx,安裝腳本自動處理。ActiveX 控制項動態載入 DLL 核心流程:**LoadLibrary → GetProcAddress → 函數指標型別轉換 → 狀態管理 (COM 口句柄、DLL 句柄) → 自動化介面封裝 → 資源釋放**。關鍵在於呼叫慣例匹配、字串編碼轉換、執行緒同步、RAII 資源管理、代碼簽名部署。雖 ActiveX 已為淘汰技術,但在舊系統遷移、特定 IE 環境維護中仍具參考價值。