[元旦礼物] 使用 TComTypeLib 调用 .Net DLL

首先感谢 2019 年许多的朋友对 QDAC 项目的支持,2019 年因为精力的原因,项目的更新和进度可能并不如大家的意。但是作者想说,我并没有放弃 QDAC 项目,只要大家还在, QDAC 项目就还在,就会继续维护。

下面进入正题。

【前置条件】

1、这个库是继于 COM 的 IDispatch 接口实现的,所以要求要调用的 DLL 需要编译为支持 COM 的版本,具体参考教程在 Delphi 中调用 .Net 的 DLL。不要对作者的能力报太高的期待,毕竟作者精力有限,深入研究 .NET DLL 的内容,反向编译出 PAS 单元实在是需要太多的时间。

2、这个库只支持 Windows ,需要客户机器上已经有 .NET 运行时。具体需要的版本取决于 DLL 实现要求的版本。

3、如果可以不用 TLB 支持也能实现,但我还是选择这个库需要 TLB 文件支持。如果不想用 TLB 支持,则无法实现运行时动态调用,只能在设计期就计划好代码。

【调用步骤】

1、在 uses 引用 comlib 单元。
2、调用 CoClassExists 函数,来判断你要调用的 .NET DLL 中的组件是否已经注册。如果没有注册,调用 RegisterClrLibrary 来注册 DLL 组件。如果不存在对应的 .TLB 文件,则在 regasm 调用注册组件成功时,会直接返回 true,如果存在,则会实际检查其中的类是否真正注册。
3、这里就要分叉了,两种选择:
(1)、如果不使用 TLB 支持,那么你就要用 OLE 方式调用。你需要支持要创建的对应的 CoClass 实例的 ClassId(GUID编码) 或者是 ProgId(类似于 AAA.BBB 格式) 。如果是 ClassId 调用 CreateComObject 创建实例,如果是 ProgId 调用 CreateOleObject 创建实例。下面的示例中以 ClassId 为例,ITestLib 就是对应的 .NET 中的一个 CoClass 实例 ID。要获取这个信息,可以用 Delphi 直接打开对应的 TLB 文件就可以看到。

var
  V: OleVariant;
const
  ITestLib: TGuid = '{EA2F140A-108F-47AE-BBD5-83EEE646CC0D}';
begin
  V := IDispatch(CreateComObject(ITestLib));
  ShowMessage(VarToStr(V.Add(1, 2)));
end;

关于这种调用方式简单,但 Delphi 实际上后台做了很多重复工作:
→ 编译时将调用改为对 System.Variants._DispInvoke 函数调用并完成参数转换
→ 调用 System.Variants.DispInvokeCore 函数
→ 调用 System.Win.ComObj.VarDispInvoke 函数
→ 调用 GetIDsOfNames 获取 Add 这个函数的 DispID
→ 调用 System.Win.ComObj. DispatchInvoke 函数
→ 调用 GetDispatchInvokeArgs 将参数列表从 TVarArgList 转换为 TDispParams 格式,以准备调用。
→ 调用 IDispatch 接口的 Invoke 函数完成实际的调用并返回。

你的每一次调用殾对应这些开销,在你方便的背后,编译器为你做了很多很多。

当然你也可以自己实现相关的逻辑,来完成运行时动态确定要调用的函数动态调用。

(2)如果使用 TLB 支持,则可以创建 TComTypeLib 实例,并用 LoadFromFile 来加载对应的 TLB 文件,然后上面的代码就简化为:

  var
    ATypeLib: TComTypeLib;
    AType: TComType;
    AFunc: TComFunction;
    AInst: IDispatch;
    AParams: TArray<TValue>;
  begin
      ATypeLib := TComTypeLib.Create;
      try
        ATypeLib.LoadFromFile(AFileName);
        if not ATypeLib.Registered then
          RegisterClrLibrary(FLibraryFile);
        AType := ATypeLib.FindType(类型的名称或ID);
        if not Assigned(AType) then
          raise Exception.Create('Class not found.');
        //创建实例
        AInst := IDispatch(AType.CreateInstance);
        //查找函数
        AFunc := AType.FindFunction('Add');
        if Assigned(AFunc) then
        begin
          SetLength(AParams, 2);
          //参数赋值
          AParams[0]:=1;
          AParams[1]:=2;
          Result:=AFunc.Invoke(AInst, AParams);
        end
      finally
        FreeAndNil(ATypeLib);
      end;

总结下需要做的工作:

→ 创建 TComTypeLib 实例并加载 TLB 文件
→ 查找类型和要调用的方法
→ 设置参数
→ 调用函数的 Invoke 方法

相比上一种方案,需要额外加载 TLB,但函数可以对象可以缓存重复调用,避免重复调用时的查询开销。

实际上,通过 TComTypeLib 你可以做的工作除了上面的工作外,还可以枚举出里面的函数和类型列表,以及各个参数的类型。更多的用法,请自己研究代码。comlib 单元在 QDAC 官方群中可以下载。

最后,祝大家新年工作顺利,事业进步。

分享到: