延續上篇「VC 呼叫 Delphi COM」主題,本文詳述反向操作:在 Delphi 專案中消費 VC 6.0 (ATL) 開發的進程內 COM 伺服器。核心難點在於 IDL 到 Pascal 介面定義的手工轉換,以及 GUID/呼叫慣例的精確對應。
TestCom,介面選 Dual,執行緒模型 ApartmentITestCom 右鍵 → Add Method:ShowMsg()ShowMsg 顯示 MessageBoxMaAtl.dll、MaAtl.idl、MaAtl.tlb
// MaAtl.idl : IDL source for MaAtl.dll
import "oaidl.idl";
import "ocidl.idl";
[
object,
uuid(78313A6E-FBA7-11D5-8094-00E04C4EA60F),
dual,
helpstring("ITestCom Interface"),
pointer_default(unique)
]
interface ITestCom : IDispatch
{
[id(1), helpstring("method ShowMsg")] HRESULT ShowMsg();
};
[
uuid(78313A62-FBA7-11D5-8094-00E04C4EA60F),
version(1.0),
helpstring("MaAtl 1.0 Type Library")
]
library MAATLLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");
[
uuid(78313A6F-FBA7-11D5-8094-00E04C4EA60F),
helpstring("TestCom Class")
]
coclass TestCom
{
[default] interface ITestCom;
};
};
Delphi 6+ 內建 IDLtoPas.exe,舊版需手工轉換。對應規則:
| IDL 元素 | Pascal 對應 | 範例 |
|---|---|---|
| interface | interface | ITestCom = interface(IDispatch) |
| uuid | GUID 字串常數 | IID_ITestCom = '{78313A6E-FBA7-11D5-8094-00E04C4EA60F}' |
| coclass | CoClass 工廠類別 | CoTestCom = class ... Create: ITestCom |
| method | stdcall 函數 | function ShowMsg: HResult; stdcall; |
| HRESULT | HResult | 相容 |
| [id(n)] | DISPID 對應 | 自動處理 |
unit MaAtlDll_TLB;
interface
uses Windows, ActiveX, Classes, ComObj;
const
// TypeLibrary 版本
MaAtlMajorVersion = 1;
MaAtlMinorVersion = 0;
// GUID 定義(直接從 IDL 複製)
LIBID_MaAtl: TGUID = '{78313A62-FBA7-11D5-8094-00E04C4EA60F}';
IID_ITestCom: TGUID = '{78313A6E-FBA7-11D5-8094-00E04C4EA60F}';
CLASS_TestCom: TGUID = '{78313A6F-FBA7-11D5-8094-00E04C4EA60F}';
type
// 前向宣告
ITestCom = interface;
// CoClass 對應介面
TestCom = ITestCom;
// 介面定義:繼承 IDispatch,雙重介面
ITestCom = interface(IDispatch)
['{78313A6E-FBA7-11D5-8094-00E04C4EA60F}']
function ShowMsg: HResult; stdcall;
end;
// 類別工廠:提供 Create/CreateRemote
CoTestCom = class
class function Create: ITestCom;
class function CreateRemote(const MachineName: string): ITestCom;
end;
implementation
uses ComObj;
class function CoTestCom.Create: ITestCom;
begin
Result := CreateComObject(CLASS_TestCom) as ITestCom;
end;
class function CoTestCom.CreateRemote(const MachineName: string): ITestCom;
begin
Result := CreateRemoteComObject(MachineName, CLASS_TestCom) as ITestCom;
end;
end.
unit Unit1;
interface
uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, MaAtlDll_TLB; // 引用轉換後的介面單元
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
pS: ITestCom;
begin
// 1. 建立 COM 物件(自動處理 CoInitialize/CoUninitialize)
pS := CoTestCom.Create;
// 或跨機器:pS := CoTestCom.CreateRemote('ServerName');
// 2. 呼叫方法
pS.ShowMsg;
// 3. 釋放介面(介面計數歸零自動釋放)
pS := nil;
end;
end.
MaAtl.dll 複製到目標機器regsvr32 MaAtl.dll| 問題 | 原因 | 解決 |
|---|---|---|
| 編譯錯誤:Undeclared identifier 'CreateComObject' | 未引用 ComObj 單元 | uses 子句加入 ComObj |
| 執行錯誤:Class not registered | DLL 未註冊或路徑錯誤 | 確認 regsvr32 成功,檢查註冊表 HKCR\CLSID\{GUID} |
| 呼叫失敗:E_NOINTERFACE | IID 不匹配 | 核對 Pascal 端 GUID 與 IDL 完全一致,含大小寫與大括號 |
| 存取違規 | 呼叫慣例不匹配 | 確認所有方法使用 stdcall,IDL 預設即為 stdcall |
| 參數傳遞錯誤 | 資料型別對應錯誤 | 參考:BSTR↔WideString, SAFEARRAY↔PSafeArray, VARIANT↔OleVariant |
Delphi 6+ 支援直接匯入 .tlb 自動產生介面單元:
MaAtl.tlb 或註冊後的類型程式庫MaAtlLib_TLB.pas,功能等同手工轉換此法避免手工轉換錯誤,為現代 Delphi 開發推薦做法。
Delphi 消費 VC COM 的核心流程:VC ATL 產出 IDL/TLB → Delphi 匯入或手工轉換為 Pascal 介面 → 以 CreateComObject 建立物件 → 介面呼叫。掌握 IDL 與 Pascal 型別系統的對應關係,配合 stdcall 呼叫慣例與 GUID 精確匹配,即可實現穩定的跨語言 COM 互操作。