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

C# 開發 ActiveX 控制項完整指南:從建立專案到部署與網頁嵌入

     編輯:關於C#

適用情境

B/S 架構需呼叫客戶端本地資源 (USB Key、印表機、掃描儀、硬體加密裝置) 時,ActiveX 為傳統但有效的解決方案。本文示範以 C# Windows Forms 控制項庫開發、簽署、部署 ActiveX。

開發環境需求

  • Visual Studio 2019/2022
  • .NET Framework 4.7.2+ (建議 4.8,客戶端較普及)
  • 專案類型:Windows Forms 控制項庫

專案關鍵設定 (必要步驟)

  1. 設定組件 COM 可見:
    • 專案右鍵 → Properties → Application → Assembly Information
    • 勾選 Make assembly COM-Visible
  2. 啟用 COM Interop 註冊:
    • Properties → Build → 勾選 Register for COM interop
    • 注意:Debug/Release 兩種組態皆需設定
  3. AssemblyInfo.cs 加入允許部分信任呼叫者:
    
    using System.Security;
    [assembly: AllowPartiallyTrustedCallers()] // 關鍵:允許網頁沙箱呼叫
    [assembly: ComVisible(true)]
            
  4. 控制項類別加上 Guid 與介面實作:
    
    [Guid("YOUR-GUID-HERE")] // 使用工具 → Create GUID 產生
    [ClassInterface(ClassInterfaceType.AutoDual)] // 支援晚期繫結
    [ComVisible(true)]
    public partial class MyActiveXControl : UserControl
    {
        // 必須實作 IObjectSafety 讓 IE 信任腳本存取
        // 介面定義見下方完整程式碼
    }
            

IObjectSafety 介面完整實作 (必要,否則 IE 封鎖)


using System;
using System.Runtime.InteropServices;

[ComImport, Guid("CB5BDC81-93C1-11CF-8F20-00805F2CD064")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IObjectSafety
{
    [PreserveSig]
    int GetInterfaceSafetyOptions(ref Guid riid, 
        [MarshalAs(UnmanagedType.U4)] ref int pdwSupportedOptions, 
        [MarshalAs(UnmanagedType.U4)] ref int pdwEnabledOptions);

    [PreserveSig]
    int SetInterfaceSafetyOptions(ref Guid riid, 
        [MarshalAs(UnmanagedType.U4)] int dwOptionSetMask, 
        [MarshalAs(UnmanagedType.U4)] int dwEnabledOptions);
}

// 在控制項類別中實作
#region IObjectSafety 實作
private const string IID_IDispatch = "{00020400-0000-0000-C000-000000000046}";
private const string IID_IDispatchEx = "{A6EF9860-C720-11D0-9337-00A0C90DCAA9}";
private const string IID_IPersistStorage = "{0000010A-0000-0000-C000-000000000046}";
private const string IID_IPersistStream = "{00000109-0000-0000-C000-000000000046}";
private const string IID_IPersistPropertyBag = "{37D84F60-42CB-11CE-8135-00AA004BB851}";

private const int INTERFACESAFE_FOR_UNTRUSTED_CALLER = 0x00000001;
private const int INTERFACESAFE_FOR_UNTRUSTED_DATA = 0x00000002;
private const int S_OK = 0;
private const int E_FAIL = unchecked((int)0x80004005);
private const int E_NOINTERFACE = unchecked((int)0x80004002);

private bool _safeForScripting = true;
private bool _safeForInitializing = true;

public int GetInterfaceSafetyOptions(ref Guid riid, ref int pdwSupportedOptions, ref int pdwEnabledOptions)
{
    int hr = E_FAIL;
    string strGuid = riid.ToString("B");
    pdwSupportedOptions = INTERFACESAFE_FOR_UNTRUSTED_CALLER | INTERFACESAFE_FOR_UNTRUSTED_DATA;

    switch (strGuid)
    {
        case IID_IDispatch:
        case IID_IDispatchEx:
            hr = S_OK;
            pdwEnabledOptions = _safeForScripting ? INTERFACESAFE_FOR_UNTRUSTED_CALLER : 0;
            break;
        case IID_IPersistStorage:
        case IID_IPersistStream:
        case IID_IPersistPropertyBag:
            hr = S_OK;
            pdwEnabledOptions = _safeForInitializing ? INTERFACESAFE_FOR_UNTRUSTED_DATA : 0;
            break;
        default:
            hr = E_NOINTERFACE;
            break;
    }
    return hr;
}

public int SetInterfaceSafetyOptions(ref Guid riid, int dwOptionSetMask, int dwEnabledOptions)
{
    int hr = E_FAIL;
    string strGuid = riid.ToString("B");

    switch (strGuid)
    {
        case IID_IDispatch:
        case IID_IDispatchEx:
            if (((dwEnabledOptions & dwOptionSetMask) == INTERFACESAFE_FOR_UNTRUSTED_CALLER) && _safeForScripting)
                hr = S_OK;
            break;
        case IID_IPersistStorage:
        case IID_IPersistStream:
        case IID_IPersistPropertyBag:
            if (((dwEnabledOptions & dwOptionSetMask) == INTERFACESAFE_FOR_UNTRUSTED_DATA) && _safeForInitializing)
                hr = S_OK;
            break;
        default:
            hr = E_NOINTERFACE;
            break;
    }
    return hr;
}
#endregion

業務功能範例:讀取 USB Key 憑證


public partial class MyActiveXControl : UserControl
{
    // 供網頁 JavaScript 呼叫的公共方法
    public string ReadUsbKeyCertificate()
    {
        try
        {
            // 實際邏輯:操作 CryptoAPI、PKCS#11 等
            // 這裡僅示範回傳字串
            return "SUCCESS|Certificate data here...";
        }
        catch (Exception ex)
        {
            return $"ERROR|{ex.Message}";
        }
    }

    // UI 互動範例:按鈕點擊觸發
    private void btnReadCert_Click(object sender, EventArgs e)
    {
        string result = ReadUsbKeyCertificate();
        lblStatus.Text = result;
        // 也可觸發事件通知網頁
        OnCertificateRead(result);
    }

    // 定義事件供網頁訂閱
    [ComVisible(true)]
    public delegate void CertificateReadHandler(string result);
    public event CertificateReadHandler CertificateRead;

    protected virtual void OnCertificateRead(string result)
    {
        CertificateRead?.Invoke(result);
    }
}

部署專案建立 (Setup Project)

  1. 方案加入新專案:Setup Project (需安裝 Microsoft Visual Studio Installer Projects 擴充套件)。
  2. 右鍵部署專案 → Add → Project Output → 選擇控制項庫專案 → Primary Output。
  3. 選取 Primary Output → 屬性視窗 → Register = vsdrpCOM (關鍵:註冊為 COM 元件)。
  4. 建置部署專案產出 `setup.exe` 與 `.msi`。

網頁端嵌入與呼叫




    





常見問題排查

現象原因解決方案
網頁顯示 "無法建立物件" / 空白CLSID 不符、未註冊、架構不符 (x86/x64)確認 classid 指定正確 GUID、以管理員執行 `regasm /codebase Your.dll`、目標 IE 位元數與 DLL 一致
JavaScript 呼叫方法報 "存取被拒絕"未實作 IObjectSafety 或 AllowPartiallyTrustedCallers 缺失檢查 AssemblyInfo.cs 及介面實作完整性
部署專案建置錯誤 regcap.exeVS 2015/2017 已知 Bug用 VS 2019+ 或複製 VS 2022 的 regcap.exe 覆蓋
客戶端安裝需 .NET FrameworkActiveX 依賴 CLR安裝包勾選 .NET Framework 4.8 必要條件,或引導使用者安裝

現代替代方案建議

  • WebAssembly (Blazor WASM):瀏覽器沙箱內執行 .NET,無法直接存取硬體。
  • Native Messaging + Chrome Extension:瀏覽器擴充功能與本地主機程式通訊,支援 Chrome/Edge/Firefox。
  • WebUSB / WebHID / Web Serial API:現代瀏覽器標準 API,直接存取 USB/HID/序列埠裝置 (需 HTTPS、使用者授權)。
  • 本地服務 + REST/WebSocket:客戶端安裝 Windows Service / Tray App,網頁透過 localhost API 呼叫。
⚠️ ActiveX 僅支援 IE/Edge IE 模式,現代專案強烈建議採用上述替代方案。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved