标签:style class blog http ext color
在本机代码中通过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)中,这样,所有的客户端都能找到它,就不必要在第一个目录下都复制一份。
在本机代码中通过 COM 使用 F#,布布扣,bubuko.com
标签:style class blog http ext color
原文地址:http://blog.csdn.net/hadstj/article/details/34441997