Defining COM Interfaces

所有的COM接口都从IUnknown 派生出来。你可以为自定义接口接口生成一个类型库(type library),让客户端可以访问方法信息。接口可以用MIDL描述。

创建一个自定义的COM接口的步骤:

  • Decide how you want to provide marshaling support for your interface; either with type-library–driven marshaling or with a proxy/stub DLL. Even in-process interfaces must be marshaled if they are to be used across apartment boundaries. It is a good idea to build marshaling support into every COM interface, even if you don’t think you will need it. See Interface Marshaling for more information.
  • Describe the interface or interfaces in an interface definition (IDL) file. In addition, you can specify certain local aspects of your interface in an application configuration file (ACF). If you are using type-library–driven marshaling, add a library statement that references the interfaces for which you want to generate type information.
  • Use the MIDL compiler to generate a type library file and header file, or C-language proxy/stub files, interface identifier file, DLL data file and header file. See MIDL Compilation for more information.
  • Depending on the marshaling method you chose, write a module definition (DEF) file, compile and link all the MIDL-generated files into a single proxy DLL, and register the interface in the system registry, or register the type library. See Loading and Registering a Type Library and Building and Registering a Proxy DLL for more information.

Interface Marshaling

如果你的接口会被跨apartment、进程、线程使用,那么就必须提供编组机制。提供编组支持的几种方式:

  • Write your own proxy/stub code that calls the COM channel, which in turn calls the RPC run-time libraries. Theoretically, it is possible to do this, but in practice it is almost impossible to do without a significant amount of effort.
  • Describe your interfaces in an interface definition language (IDL) file and use the MIDL compiler to generate a proxy/stub DLL. This method provides the best performance and the most flexibility in terms of acceptable data types. Using MIDL-generated proxy stubs, you can control not only memory management but even the marshaling and unmarshaling of complex data types across different platforms.
  • Use MIDL to generate a type library that the system uses to provide marshaling support at run time. This is the easiest way to implement marshaling support. All you have to do is generate a type library and register it. Your interfaces must be Automation-compatible (either oleautomation or dual), which places some restrictions on the kinds of data types you can use as method parameters. However, in most cases, the advantage of having your interfaces accessible to programs written in other languages, such as Microsoft Visual Basic and Java, outweighs the limitations on data types.

Anatomy of an IDL File

本章节提供了两个例子:

MIDL.exe编译IDL文件之后会生成若干个相应的文件,以上面的Example2.idl为例,会生成:

  • Example2.h 包含接口成员的定义
  • Example2_p.c 包含proxy/stub
  • Example2_i.c 包含接口的ID
  • Example2.tlb 接口成员的类型信息
  • Dlldata.c 包含创建proxy/stub DLL相关的信息

关于MIDL编译器的选项,可以参考MIDL Compiler Options

Oleaut32.dll ( Automation dynamic link library)提供了若干函数,可以用来注册和加载类型库信息,下面是一个例子:

ITypeLib *pTypeLib;
HRESULT hr;
hr = LoadTypeLibEx("example.tlb", REGKIND_REGISTER, &pTypeLib);
if(SUCCEEDED(hr))
{
    pTypeLib->Release();
} else {
    exit(0); // Handle errors here.
}

Building and Registering a Proxy DLL

本章介绍了如何编写Proxy DLL的定义文件,如果编写构建文件以便生成自动的注册和解除函数。以及在不使用自动生成的Proxy的情况下,如何修改注册表添加自定义的DLL以支持默认方式以外的编组方式。

Interface Design Rules

COM接口必须是直接或者间接从IUnknown继承出来,并且遵循下列规则

  • They must have a unique interface identifier (IID).
  • They must be immutable. Once they are created and published, no part of their definition may change.
  • All interface methods must return an HRESULT value so that the portions of the system that handle - remote processing can report RPC errors.
  • All string parameters in interface methods must be Unicode. -Your data types must be remotable. If you cannot convert a data type to a remotable type, you will have to create your own marshaling and unmarshaling routines. Also, LPVOID, or void *, has no meaning on a remote computer. Use a pointer to IUnknown, if necessary.

设计COM接口的时候有一些其他的注意事项

  • 使用指针的时候必须小心,指针所指向的内存必须有明确的大小,这样才可以在远端重新分配相同大小的内存。
  • 使用指针的时候必须初始化,否则编组后传递给远端的,可能是根本无效的地址。
  • 如果想用指针别名,最好早MIDL中定义
  • 分配和释放内存的时候必须注意。除非特别指出,否则跨进程的调用结束后,远端会释放相应的内存。
  • 定义HRESULT的值范围的时候,不要和既有的重合了。比如COM定义的值FACILITY_ITF 范围从0x0000 到0x01FF 。任何时候,跟单个函数调用有关的信息,最好放在输出参数中处理,而不是在HRESULT中。

随着DCOM的引入,对接口有了新的要求,必须是remotable的。在使用MIDL描述接口的时候要注意这一点。基本是同进程使用的COM服务端,也要考虑到这一点。一个COM服务端如果没有声明自己的线程模型,那么默认就是单线程的。单线程的COM服务端需要通过主Apartment来提供服务(也就是第一个调用 CoInitialize 或者CoInitializeEx的Apartment。所以即便单线程环境下,也可能有跨线程的需求。

Using a COM Interface包含一个COM接口使用的例子。

其他参考

(本篇完)