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

PHP 調用 C# DLL 完整實戰:從類庫創建、強簽名、COM 註冊到 GAC 部署與 PHP COM 調用

     編輯:PHP 相關知識

跨語言互操作是企業級系統集成的常見需求。PHP 通過 COM 組件模型可調用 .NET Framework 構建的 DLL。本文提供從零開始的完整流程:C# 類庫創建、COM 可見性配置、強名稱簽名、regasm 註冊、gacutil 全局程序集緩存部署,以及 PHP COM 類調用與異常處理。

環境要求

  • Windows Server / Windows 10+(COM 僅限 Windows)
  • .NET Framework 4.7.2+ 或 .NET 6+ (需啟用 COM 互操作)
  • PHP 7.4+ 已啟用 com_dotnet 擴展(php.ini: extension=com_dotnet)
  • Visual Studio 2019/2022 或 .NET SDK

步驟 1:創建 C# 類庫項目

# 使用 .NET CLI
dotnet new classlib -n HelloWorld -f net48
cd HelloWorld

項目文件配置

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net48</TargetFramework>
    <AssemblyName>HelloWorld</AssemblyName>
    <RootNamespace>HelloWorld</RootNamespace>
    <!-- 關鍵:啟用 COM 可見性 -->
    <ComVisible>true</ComVisible>
    <!-- 生成類型庫 -->
    <RegisterForComInterop>true</RegisterForComInterop>
  </PropertyGroup>
  
  <PropertyGroup Condition="'$(Configuration)'=='Release'">
    <SignAssembly>true</SignAssembly>
    <AssemblyOriginatorKeyFile>HelloWorld.snk</AssemblyOriginatorKeyFile>
  </PropertyGroup>
</Project>

業務類實現

// Hello.cs
using System;
using System.Runtime.InteropServices;

namespace HelloWorld
{
    // 介面定義(可選,但推薦:版本控制、類型安全)
    [ComVisible(true)]
    [Guid("D1A2B3C4-5E6F-7890-ABCD-EF1234567890")]
    [InterfaceType(ComInterfaceType.InterfaceIsDual)]
    public interface IHello
    {
        string Write();
        string Greet(string name);
        int Add(int a, int b);
        DateTime GetServerTime();
    }

    // 類實現
    [ComVisible(true)]
    [Guid("E2B3C4D5-6F78-90AB-CDEF-123456789012")]
    [ClassInterface(ClassInterfaceType.None)] // 只暴露介面
    [ProgId("HelloWorld.Hello")]             // COM ProgID,PHP 調用用此字符串
    public class Hello : IHello
    {
        public string Write()
        {
            return "Hello World from C# DLL!";
        }

        public string Greet(string name)
        {
            return $"Hello, {name}! Current time: {DateTime.Now:yyyy-MM-dd HH:mm:ss}";
        }

        public int Add(int a, int b)
        {
            return a + b;
        }

        public DateTime GetServerTime()
        {
            return DateTime.Now;
        }
    }
}

步驟 2:強名稱簽名

# 生成強名稱金鑰文件
sn -k HelloWorld.snk

# 或使用 Visual Studio:項目屬性 → 簽名 → 勾選「為程序集簽名」 → 選擇強名稱金鑰文件

步驟 3:編譯 Release 版本

dotnet build -c Release
# 輸出:bin/Release/net48/HelloWorld.dll

步驟 4:註冊 COM 組件 (regasm)

# 以管理員權限運行開發人員命令提示符
cd bin/Release/net48

# 註冊並生成類型庫
regasm HelloWorld.dll /tlb:HelloWorld.tlb /codebase

# 成功提示:
# Types registered successfully
# Assembly exported to '...\HelloWorld.tlb', and the type library was registered successfully

關鍵參數說明

  • /tlb:生成 .tlb 類型庫文件,供早期綁定使用
  • /codebase:在註冊表中記錄 DLL 絕對路徑,**非 GAC 部署時必須加**

步驟 5:安裝到全局程序集緩存 (GAC)

# 僅強名稱程序集可安裝 GAC
gacutil /i HelloWorld.dll

# 成功提示:
# Assembly successfully added to the cache

GAC vs Codebase 選擇指南

| 部署方式 | 適用場景 | 優點 | 缺點 | |----------|----------|------|------| | **GAC** | 生產環境、多應用共享、版本隔離 | 統一管理、版本策略、安全性高 | 需強名稱、需管理員權限、卸載麻煩 | | **Codebase** | 開發測試、單應用、無強名稱 | 簡單、無需 GAC、xcopy 部署 | 路徑硬編碼、版本衝突風險 |

步驟 6:PHP COM 調用

<?php
// com_client.php
try {
    // 實例化 COM 對象(使用 ProgID)
    $com = new COM("HelloWorld.Hello");
    
    // 或者使用 CLSID(更穩定,不依賴註冊表 ProgID 映射)
    // $com = new COM("{E2B3C4D5-6F78-90AB-CDEF-123456789012}");

    // 調用方法
    echo "Write(): " . $com->Write() . PHP_EOL;
    echo "Greet('PHP'): " . $com->Greet('PHP') . PHP_EOL;
    echo "Add(10, 20): " . $com->Add(10, 20) . PHP_EOL;
    
    // 日期時間類型自動轉換為 PHP DateTime 或字符串
    $time = $com->GetServerTime();
    echo "GetServerTime(): " . $time->format('Y-m-d H:i:s') . PHP_EOL;

    // 釋放 COM 對象(重要:避免內存洩漏)
    $com = null;
    unset($com);

} catch (com_exception $e) {
    echo "COM Error: {$e->getMessage()}" . PHP_EOL;
    echo "Code: {$e->getCode()}" . PHP_EOL;
} catch (Throwable $e) {
    echo "General Error: {$e->getMessage()}" . PHP_EOL;
}

進階:異常處理與類型映射

C# 異常 → PHP com_exception

// C# 端
public string RiskyMethod()
{
    throw new ArgumentException("參數無效", nameof(param));
}

// PHP 端自動捕獲為 com_exception
// $e->getMessage() 包含 "參數無效"
// $e->getCode() 為 HRESULT (0x80070057 = E_INVALIDARG)

類型映射表

| C# 類型 | PHP 類型 | 備註 | |---------|----------|------| | string | string | UTF-16 → UTF-8 自動轉換 | | int/long | int | 64位長整型在 32位 PHP 可能截斷 | | double/decimal | float | 精度注意 | | DateTime | variant/DateTime | PHP 7.4+ 返回 DateTime 對象 | | bool | bool | | | Array/List | variant/SAFEARRAY | 需 foreach 遍歷 | | 物件 | COM 物件 | 可鏈式調用 |

常見問題排查

| 錯誤現象 | 原因 | 解決方案 | |----------|------|----------| | Class 'COM' not found | PHP 未啟用 com_dotnet | php.ini 啟用 extension=com_dotnet,重啟 PHP | | Failed to create COM object | DLL 未註冊/註冊失敗 | 管理員運行 regasm /codebase,檢查註冊表 HKCR\HelloWorld.Hello | | Access denied | DCOM 權限不足 | dcomcnfg → 組件服務 → 計算機 → 我的電腦 → 屬性 → COM 安全性 → 啟動和激活權限 → 編輯限制 → 添加 IIS_IUSRS/PHP 用戶 → 允許本地啟動/激活 | | 參數類型不匹配 | PHP 弱類型傳遞 | 顯式轉換:(int)$val, (string)$val | | 內存洩漏 | 未釋放 COM 對象 | 必須 $com = null; unset($com); 或使用 com_release() |

生產部署檢查清單

  • ✅ C# 類庫編譯為 Release x64(匹配 PHP 進程位數)
  • ✅ 強名稱簽名 + GAC 安裝
  • ✅ DCOM 權限配置:PHP 運行用戶擁有「本地啟動/激活」權限
  • ✅ PHP com.allow_dcom = true (php.ini)
  • ✅ 異常處理:捕獲 com_exception、記錄日誌、返回友好錯誤
  • ✅ 資源釋放:所有代碼路徑釋放 COM 對象
  • ✅ 版本管理:AssemblyVersion 遵循語義化版本,GAC 支持並行版本

替代方案對比

| 方案 | 跨平台 | 性能 | 複雜度 | 維護性 | |------|--------|------|--------|--------| | **PHP COM + C# DLL** | ❌ 僅 Windows | 中等 | 高 | 中等 | | **HTTP/gRPC 微服務** | ✅ 全平台 | 高 | 中 | 高 | | **命令行調用** | ✅ 全平台 | 低 | 低 | 低 | | **共享內存/管道** | ⚠️ 平台相關 | 高 | 高 | 低 |

總結

PHP 調用 C# DLL 是 Windows 平台特有的緊耦合集成方案,適合「既有 .NET 庫、無法重寫、性能要求中等、部署環境可控」的場景。關鍵成功因素:強名稱簽名、GAC 部署、DCOM 權限、COM 對象釋放。現代架構建議優先考慮 HTTP API 或 gRPC 微服務化,實現跨平台、松耦合、可獨立演進。

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