在本机代码中通过 COM 使用 F#

728 篇文章 1 订阅
86 篇文章 0 订阅

在本机代码中通过COM 使用 F#

 

虽然大多数情况下,我们可能希望从 F# 代码调用本机代码,但是,在某些情况下,也可能想从本机代码中调用 F# 库函数。例如,假设我们有一个大型的程序是用 C++ 写的,有可能希望用户界面保持用 C++,而把一些逻辑,比如执行复杂数学计算的部分迁移到 F#,以方便维护。在这种情况下,我们就要从本机代码中调用 F# 了。简单的方法是借用.NET 提供的工具,为我们的 F# 程序集创建 COM 包装;然后,使用COM 运行时从 C++ 中调用 F# 函数。

要通过 COM 公开函数,需要用特殊方法进行开发。首先,必须定义接口,为函数指定契约,接口的成员必须使用命名参数(参见本章前面的“C# 中调用 F# 库”一节),接口本身使用 System.Runtime.InteropServices.Guid特性来标记;然后,必须提供一个类来实现这个接口,这要用System.Runtime.InteropServices.Guid 和System.Runtime.InteropServices.ClassInterface 特性进行标记,还应该总是把 ClassInterfaceType.None枚举成员传递给 ClassInterface 特性的构造函数,说明没有接口应该自动生成。

我们来看一下示例是如何做的。假设我们想公开两个函数Add 和 Sub 给非托管的客户端,需要在命名空间Strangelights 下创建接口IMath,然后,创建类 Math 实现这个接口,还需要保证类和接口用适当的特性进行标记。最后的代码可能像这样:

 

namespaceStrangelights

open System

open System.Runtime.InteropServices

 

// define an interface(since all COM classes must

// have a seperateinterface)

// mark it with afreshly generated Guid

[<Guid("6180B9DF-2BA7-4a9f-8B67-AD43D4EE0563")>]

type IMath =

  abstract Add : x: int* y: int -> int

  abstract Sub : x: int* y: int -> int

 

// implement theinterface, the class must:

// - have an emptyconstuctor

// - be marked with itsown guid

// - be marked with theClassInterface attribute

[<Guid("B040B134-734B-4a57-8B46-9090B41F0D62");

ClassInterface(ClassInterfaceType.None)>]

  typeMath() =

  interface IMath with

    memberthis.Add(x, y) = x + y

    memberthis.Sub(x, y) = x - y

 

 

函数 Add 和 Sub很简单,因此,直接在 Math 类的主体中实现,自然没有问题;但是,如果需要把它们拆分到类之外的其他助理函数,也不会有问题,可以用自己觉得合适的任何方法实现类成员,都行,只需要提供接口和类,这样,COM 运行时就能够在代码中有一个入口点。

下面是这个过程中公开的最复杂的部分,注册程序集,使 COM 运行时能找到它。这是通过使用工具RegAsm.exe 实现的。假设我们把前面的示例代码编译成的 .NET .dll 通过OM  需要保证类的s 叫 ComLibrary.dll,那么,需要调用 RegAsm.exe两次,使用下面的命令:

 

regasm comlibrary.dll /tlb:comlibrary.tlb

regasm comlibrary.dll

 

第一次创建类型库文件 .tlb,它是能够用于开发的 C++ 项目中的;第二次是注册程序集,使 COM 运行时能够找到它;这两个步骤还需要在分发程序集的机器上运行。

C++ 调用 Add 函数是这样的,开发环境以及如何设置 C++ 编译器都会对代码的编译产生影响。在这里,我们创建的Visual Studio 项目,选择了控制台应用程序模板,并启用了 ATL。注意,下面是有关源代码的描述:


#import 命令告诉编译器需要导入我们自己的类型库,可能需要使用文件(.tlb)的完整路径;编译器还将自动生成一个头文件,在这里是comlibrary.tlh,文件的位置在debug 或 release 目录下。它的作用是让我们知道类型库中可用的函数和标识符;

然后,需要初始化 COM 运行时,这是通过调用CoInitialize 函数实现的;

接着,需要声明一个指针,指向我们创建的接口 IMath,这是通过代码comlibrary::IMathPtr pDotNetCOMPtr; 完成的。注意,命名空间是来自库的名字,而不是 .NET 的命名空间;

下一步,需要创建 Math 类的实例,这是通过调用CreateInstance 方法完成的,把 Math 类的GUID 传递给它。幸运的是,为此目的,有一个常量定义;

如果这些都成功了,就能名调用 Add 函数了。注意,函数的返回返回dd 目的,有一个常量这 r pDotNetCOMPtr; 实际上是 HRESULT,这个值告诉我们调用是否已经成功,而实际的实际结果是通过一个输出参数传出来的。

 

// !!! C++ Source !!!

#include "stdafx.h"

// import the meta data about out.NET/COM library

#import "..\ComLibrary\ComLibrary.tlb"named_guids raw_interfaces_only

 

// the applications main entry point

int _tmain(int argc, _TCHAR* argv[])

{

  // initialize the COM runtime

 CoInitialize(NULL);

  // a pointer to our COM class

 comlibrary::IMathPtr pDotNetCOMPtr;

 

  // create a new instance of the Math class

  HRESULT hRes =pDotNetCOMPtr.CreateInstance(comlibrary::CLSID_Math);

  // check it was created okay

  if (hRes == S_OK)

  {

    // define a local to hold the result

    long res = 0L;

    // call the Add function

    hRes =pDotNetCOMPtr->Add(1, 2, &res);

    // check Add was called okay

    if (hRes == S_OK)

    {

      // print the result

      printf("The result was: %ld", res);

    }

    // release the pointer to the math COM class

   pDotNetCOMPtr.Release();

  }

  // uninitialise the COM runtime

  CoUninitialize();

}

 

示例的运行结果如下:

 

The result was: 3

 

当我们运行最后的程序时,必须保证ComLibrary.dll 与程序在同样的目录中,否则,COM 运行时会找不到文件。如果打算让这个库被多个客户端使用,那么,我强烈建议对程序集签名,并放在全局程序集缓存(Global Assembly Cache,GAC)中,这样,所有的客户端都能找到它,就不必要在第一个目录下都复制一份。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值