Cocoa插件编程

Posted by CoderLeonidas on March 19, 2020

Cocoa插件编程

前言

对于希望构建模块化、可定制且易于扩展的应用程序的开发人员来说,插件体系结构是一个很有吸引力的解决方案。对于许多开发人员来说,一种允许第三方在不访问源代码的情况下向应用程序添加特性的聪明方法已经发展成为成熟的组件体系结构。Core Foundation插件使用Core Foundation bundle的基本代码加载工具为OS X应用程序提供标准的插件架构。虽然本节主要针对主应用程序开发人员,但是插件开发人员也需要。

本文档的组织结构

本节中的示例演示如何创建和使用CFPlugIn对象。为了清楚起见,已经删除了错误检查代码。在实践中,检查错误是非常重要的,因为将错误的参数传递到核心基础例程会导致应用程序崩溃。

这些文章讨论了插件体系结构及其工作方式:

  • 关于插件
  • 插件的架构
  • 插件的内部结构
  • 概念构建块
  • 插件的注册

这些文章包含了如何创建和使用插件的示例

  • 定义类型和接口
  • 插件的实现
  • 加载和使用插件

关于插件

将应用程序构造为设计良好的主框架和一组插件对应用程序开发人员有很多好处:

  • 您可以非常快地实现和合并应用程序功能。
  • 由于插件是具有明确定义的接口的独立模块,因此您可以快速隔离并解决问题。
  • 您可以创建应用程序的自定义版本,而无需修改源代码。
  • 第三方可以开发其他功能,而无需原始应用程序开发人员的任何努力。
  • 由于插件与语言无关,因此您可以重用旧版代码。

最终用户还可以从使用带有插件架构的应用程序中获益:

  • 他们可以为特定的工作流程自定义功能集。
  • 他们可以禁用不需要的功能,从而有可能简化应用程序的界面,减少内存占用并提高性能。

插件的架构

所有插件模型都需要两个基本实体:插件宿主和插件本身。宿主可以是应用程序、操作系统,甚至是另一个插件。插件宿主的代码的结构是这样的: 插件的外部代码模块可以提供某些定义良好的功能区域。插件的编写和编译完全独立于宿主,通常由另一个开发人员完成。在执行宿主代码时,它使用插件体系结构提供的任何机制来定位兼容的插件并加载它们,从而向宿主添加以前不可用的功能。

Figure 1 A CFPlugIn host with three plug-ins.

插件模型足够灵活,可以至少以两种完全不同的方式使用。 第一种方法是使用插件来支持主题特性,其中每个插件实现非常相似的功能并使用相同的接口。这种方法经常用于为图像编辑和音频编辑应用程序添加特殊的加工支持。

例如,音频编辑应用程序可能只附带几个简单的处理选项,如均衡和标准化。如果此应用程序具有插件架构,则第三方可以添加对附加处理功能的支持,如混响或翻边,而无需访问应用程序源代码。这是可能的,因为主应用程序的开发人员提供了一个定义明确的接口,所有的音频处理插件都必须使用这个接口。通过要求宿主和插件仅通过这个指定良好的接口进行通信,插件体系结构允许音频应用程序完全不知道处理的细节。

使用这种方法,主应用程序开发人员设计一个插件接口,该接口具有一个处理数据的功能。为了识别接口,主开发人员给它一个惟一的ID。接口还包含一个放置描述处理类型的字符串的位置,以便主应用程序能够区分实现接口的插件。

启动主应用程序时,它将搜索具有适当标识符的所有插件。对于找到的每个插件,主程序使用插件体系结构获取处理描述字符串和指向处理函数的指针。然后,主程序可以构造一个可用音频处理技术的菜单,并将它们呈现给用户。当用户从菜单中选择处理类型时,主程序调用相关的函数来完成工作。主程序不知道插件实现的细节,而插件也不知道应用程序实现的细节。任何一个都可能被完全重写,但是只要双方都遵循这个接口,一切都会继续工作。

插件模型还可以用作组件体系结构,其中每个组件(插件)实现非常不同的功能。在这种方法中,您可以将应用程序结构为一个插件外壳和一组插件,每个插件都负责应用程序功能、用户界面、文件系统交互、网络通信等主要领域。

此模型提供了与上述更直接的插件方法类似的好处。因为组件的实现细节对其他组件是隐藏的,所以只要组件接口继续按指定的方式运行,就可以随意修改它们。这种方法的另一个好处是可以在不同的应用程序之间轻松地共享组件。注意,您可以在单个应用程序中使用这两种方法。

插件和微软的COM

该插件模型与Microsoft的COM(组件对象模型)体系结构的基础兼容。 这意味着该插件接口是根据COM指南进行布局的,并且所有接口都必须继承自COM的IUnknown接口。 这些是插件与COM共享的唯一元素。 未映射其他COM概念,例如IClassFactory接口,聚合,进程外服务器和Windows注册表。本文档仅在必要时仅介绍COM概念,以解释它们在Core Foundation插件中的使用方式。 有关其他信息,建议您查找已经发布的有关COM的大量文档。 微软网站的COM区域(http://www.microsoft.com/com/tech/com.asp)是一个很好的起点。

插件的内部结构

在磁盘上,一个CFPlugIn像一个CFBundle一样以文件包的形式出现。CFPlugIn的特别之处在于它在插件的信息属性列表中增加了几个键。这些键在插件注册中被记录下来,因为PlugIn和插件在磁盘上共享相同的结构,尽管把CFPlugIn看作CFBundle是错误的。但是,在运行时,CFPlugIn有一个CFBundle这么说也是正确的。

CFPlugIn和CFBundle是成对出现的。每个CFPlugIn都有一个CFBundle,但是每个CFBundle(例如一个应用程序包或框架包)不一定对应于一个CFPlugIn。您不应该试图直接以CFBundle的形式访问插件。如果需要使用CFBundle API访问插件的资源,应该使用函数CFPlugInGetBundle

如果您的插件将由用户手动安装,则最好为插件定义OS X风格的创建者/类型代码(也可能是文件扩展名,尽管这不是必需的),以便它们显示文件系统视图中的相应图标。

概念构建块

在插件模型中,主应用程序定义一个或多个类型,每个类型由一个或多个接口组成。插件实现插件支持的每种类型的所有接口中的所有功能。插件还为它希望实现的每种类型提供了称为工厂的创建函数。当主应用程序加载插件时,每个插件类型的工厂函数都在宿主上注册。然后,宿主可以使用类型的工厂函数实例化类型并获得指向其IUnknown接口的指针。宿主使用IUnknown来查询其接口函数表的类型。函数表为宿主提供对插件接口实现的访问。图1说明了这些不同组件之间的关系。

Figure 1 Building blocks of the Core Foundation plug-in model.

以下部分详细描述了插件模型的各个方面。此外,本主题中的任务提供了如何构建简单插件宿主和插件的完整示例。

uuid(通用唯一标识符)

插件使用uuid惟一地标识类型、接口和工厂。在创建新类型时,主程序开发人员必须生成uuid来标识类型及其接口和工厂。

uuid(全局惟一标识符),也称为guid(全局惟一标识符)或iid(接口标识符),是保证惟一的128位值。UUID在空间和时间上都是唯一的,这是通过将生成它的计算机的一个惟一值(通常是以太网硬件地址)和一个表示从1582年10月15日00:00:00开始的100纳秒间隔数的值组合在一起实现的。

要为插件创建UUID,通常使用命令行实用工具uuidgen。如果需要以编程方式生成UUID,可以使用CFUUID.h中定义的函数来实现(请参阅Generating a UUID Programmatically)。用ASCII表示的uuid的标准格式是用连字符分隔的字符串,例如68753A44-4D6F-1226-9C60-0050E4C00067。用ASCII表示的uuid的标准格式是用连字符分隔的字符串,例如68753A44-4D6F-1226-9C60-0050E4C00067。十六进制表示形式与您所期望的一样,类似于以0x开头的数值列表。例如,0xD7, 0x36, 0x95, 0x0A, 0x4D, 0x6E, 0x12, 0x26, 0x80, 0x3A, 0x00, 0x50, 0xE4, 0xC0, 0x00, 0x67。为了使用UUID,只需创建它,然后将结果字符串复制到头文件和C语言源文件中。注意Core Foundation的UUID API只能在OS X上使用。

接口

接口是主程序用来定义插件开发人员要实现的功能区域的基本抽象。主程序和插件之间的所有交互都通过接口进行。在编程术语中,接口表示一个函数表,其中每个函数具有特定的语义。

接口构成了主程序和插件之间的契约。本约定包括:

  • 接口中函数的顺序
  • 接口函数的参数类型和返回类型
  • 每个函数的预期行为

一组函数不能被正确地认为是一个接口,除非有一个特定的定义,每个函数应该做什么。

例如,考虑一个具有一个函数的接口,该函数接受单个整数并返回一个整数。如果没有行为的定义,任何接受并返回整数的函数都可能被认为是该接口的实现。为了解决这种模糊性,接口中每个函数的预期行为也是该接口的一部分。插件主程序开发人员必须向潜在的插件开发人员提供一个头文件和文档,描述组成接口的函数,以及期望函数的行为方式。插件开发人员准确地了解正确实现接口所需的内容是至关重要的。

插件模型中的接口与Microsoft的COM体系结构定义的接口相同。 当用C ++实现时,所有插件接口都继承IUnknown接口,而在C中,所有插件接口函数表都必须包含IUnknown函数。 在运行时,主程序使用IUnknown接口来查找和获取对该插件实现的其他接口的访问。 有关IUnknown和实现插件接口的更多信息,请参见定义类型和接口。

类型

类型表示接口的聚合。 像接口一样,类型也由主程序定义,并由插件实现。 类型是一个完全抽象的实体,将相关的接口分组在一起,为该类型的组成接口所代表的不同API提供了概念上的保护。 类型可以实现要实现的功能或要解决的问题的高级表示。 接口表示您想要分解类型表示的问题的方式。 您可以将接口视为类型的实现。

例如,假设您正在开发一个名为ImageViewer的应用程序,并且希望允许第三方使用插件为不同类型的图像添加文件格式支持。您可以定义类型ImageAdaptorType,它将包含两个接口,ImageIOInterfaceImageSnifferInterfaceImageIOInterface可能包含两个函数。第一个函数将接受CFURLRef类型的参数,并返回由ImageViewer定义的特殊ImageViewerImage类型来表示图像。第二个函数将获取CFURLRefImageViewerImage类型的参数,并将图像写入URL所描述的位置。ImageSnifferInterface可以定义一个函数,该函数接受CFURLRef参数并返回一个布尔值,该值指示指向的文件是否可以被读取。

因为一个接口一旦发布就不能改变,所以类型必须支持多个接口。不需要更改类型的接口,只需使用更改的或附加的功能向类型添加新接口即可。通过这种方式,单个插件可能通过为每个版本实现不同的接口来支持主应用程序的不同版本。类型保持不变;由主应用程序来定位和使用适当的接口。

也可以为类型定义可选接口。例如,主应用程序可能定义一个类型,该类型具有特定于附加组件硬件的可选接口。主应用程序可以在运行时测试硬件是否存在,如果存在,主程序可以请求特定于硬件的插件接口。如果插件实现者不希望支持硬件特定的功能,他们不需要实现该接口。

工厂

工厂表示可以创建一个或多个类型实例的函数。调用类型的工厂函数类似于Java或c++中类上的new调用操作符。当插件宿主调用时,工厂函数为被请求类型的实例分配内存,为其接口设置函数表,并返回一个指向类型的 IUnknown接口的指针。然后,插件宿主可以使用IUnknown接口来搜索该类型支持的其他接口。

创建CFPlugin时,系统注册插件支持的所有类型及其相关的工厂函数。当插件宿主希望创建给定类型的实例时,它使用类型的 UUID来搜索任何已注册的工厂函数。然后,它可以使用工厂函数来创建类型的实例。

请注意,对于同一个类型,插件可能具有多个工厂函数。开发人员可以为不同的函数定义适当的用法。

实例

由于Core Foundation 1.3和更高版本的插件API使用COM模型,因此不再需要CFPlugInInstanceRefs。 在1.2版后的API中,实例不是可以直接操作的Core Foundation数据类型,而是一个通用术语,指的是插件用于实现类型的资源。

插件的注册

为了让插件宿主知道可用的类型,每个插件必须在宿主上注册。注册包括让主程序知道插件实现的类型及其相关的工厂函数。可以使用插件信息属性列表中的一些特殊键来静态地声明这些信息,也可以通过插件中的代码动态地注册这些信息。有关示例,请参见插件的实现。

当主程序找到插件时,插件使用插件信息属性列表中CFPlugInDynamicRegistration键的值来确定插件是需要静态注册还是动态注册。如果插件使用动态注册,则必须立即加载插件的代码,以便进行动态注册。如果插件使用静态注册,则在应用程序实际需要实例化类型之前无需加载其代码。因此,当没有使用动态注册的主要原因时,静态注册是首选的。

对于静态注册,信息属性列表包含CFPlugInFactories键,其值是一个字典(其键是工厂uuid(以标准字符串格式表示),其值是函数名)。创建插件的 CFBundle时,此字典中的每个键-值对注册一个工厂函数。信息属性列表还包含一个CFPlugInTypes键。这个键的值是一个字典(它的键是uuid类型,它的值是工厂uuid的数组)。对于每种类型,插件中都有一个可以创建该类型的工厂列表。有关示例插件的信息属性列表,请参见插件的实现。

代码实体还可以使用动态注册函数注册内置类型和工厂。具有内置类型的代码可以在内部使用插件模型,或者允许插件查询主程序的内置类型和接口。如果插件需要在使用之前发现有关主程序状态的一些信息,那么这可能是必要的。

由插件定义的信息属性列表键

本节中描述的键由插件定义,用于静态地注册插件支持的类型或定义插件的动态注册行为。

key description
kCFPlugInDynamicRegistration 用于确定插件所需的注册方法。它的值是一个字符串,对于动态注册是YES,对于静态注册是NO。
CFPlugInDynamicRegisterFunction 要调用来执行动态注册的自定义函数的名称。如果启用了动态注册,并且不存在此键,则调用函数CFPlugInDynamicRegister
CFPlugInUnloadFunction 要在卸载插件代码时调用的自定义函数的名称。
CFPlugInFactories 用于静态注册。它的值应该是一个字典,其键是工厂uuid(以标准字符串格式表示),其值是函数名。
CFPlugInTypes 用于静态注册。它的值应该是一个字典,其键是uuid类型,其值是工厂uuid的数组。

定义类型和接口

作为插件宿主开发人员,您的第一个任务是定义主程序支持的类型和接口。

要定义类型,您真正需要的只是一个UUID。要为插件创建UUID,通常使用命令行实用工具uuidgen。如果需要以编程方式生成UUID,可以使用CFUUID.h中定义的函数来实现(请参阅以编程方式生成UUID)。对于类型和工厂,您需要两种形式的UUID,即头文件的十六进制表示形式和信息属性列表的ASCII表示形式。用ASCII表示的uuid的标准格式是用连字符分隔的字符串,例如:d736950a-4d6e-1226-803a-00e4c00067。十六进制表示形式与您所期望的一样,类似于以0x开头的数值列表。例如,0xD7, 0x36, 0x95, 0x0A, 0x4D, 0x6E, 0x12, 0x26, 0x80, 0x3A, 0x00, 0x50, 0xE4, 0xC0, 0x00, 0x67。

除了它的UUID之外,关于类型的一个重要信息是类型需要实现哪些接口,以及哪些接口(如果有的话)是可选的。这些信息在运行时是不需要的,也不是在头文件中以代码的形式表示的,而是应该以注释的形式表示的。

要定义接口,需要接口的UUID和该接口的函数表的结构。这意味着您必须为接口中的每个函数定义函数指针和期望的行为。

清单1显示了声明类型的头文件的内容和用于实现类型的接口。该头文件通常由插件宿主开发人员创建,并提供给插件作者。

Listing 1 Defining a type and interface

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <CoreFoundation/CoreFoundation.h>
 
// Define the UUID for the type.
#define kTestTypeID (CFUUIDGetConstantUUIDWithBytes(NULL, 0xD7, 0x36, 0x95, 0x0A,
0x4D, 0x6E, 0x12, 0x26, 0x80, 0x3A, 0x00, 0x50, 0xE4, 0xC0, 0x00, 0x67))
 
// Define the UUID for the interface.
// TestType objects must implement TestInterface.
#define kTestInterfaceID (CFUUIDGetConstantUUIDWithBytes(NULL, 0x67, 0x66, 0xE9,
0x4A, 0x4D, 0x6F, 0x12, 0x26, 0x9E, 0x9D, 0x00, 0x50, 0xE4, 0xC0, 0x00, 0x67))
 
// The function table for the interface.
typedef struct TestInterfaceStruct
{
    IUNKNOWN_C_GUTS;
    void (*fooMe)( void *this, Boolean flag );
} TestInterfaceStruct;

请注意,定义接口函数表的结构包括IUNKNOWN_C_GUTS作为其第一个元素。 如清单2所示,该宏扩展了COM IUnknown接口的结构定义。 COM规范要求所有接口都从IUnknown继承。 实际上,这意味着每个接口函数表都必须以三个函数开头-QueryInterfaceAddRefRelease。这也意味着任何接口都可以被当作IUnknown接口来处理。在运行时,主程序使用QueryInterface来查找并获得对该类型支持的所有其他接口的访问权。AddRefRelease用于引用计数。

Listing 2 The IUnknown interface in C

1
2
3
4
5
6
 #define IUNKNOWN_C_GUTS \
 void *_reserved; \
 HRESULT (STDMETHODCALLTYPE *QueryInterface) \
            (void *thisPointer, REFIID iid, LPVOID *ppv); \
 ULONG (STDMETHODCALLTYPE *AddRef)(void *thisPointer); \
 ULONG (STDMETHODCALLTYPE *Release)(void *thisPointer)

在C ++中,使用支持COM的编译器,可以通过从IUnknown类派生您的接口类来实现。 清单3显示了C ++中IUnknown接口的外观。

Listing 3 The IUnknown interface in C++

1
2
3
4
5
6
7
interface IUnknown
{
    virtual HRESULT __stdcall QueryInterface(const IID& iid
                                    void **ppv) = 0;
    virtual ULONG __stdcall AddRef() = 0;
    virtual ULONG __stdcall Release() = 0;
}

最后,注意TestInterfaceStruct中的函数fooMe将this参数作为它的第一个参数。这并不是必需的,但是这对于帮助插件作者来说是一件很好的事情。通过将这个指针传递给每个接口函数,您允许插件编写器在c++中实现,并在函数以任何语言执行时访问插件对象。

插件的实现

本节详细介绍了实际实现插件所需的所有步骤,该插件支持在定义类型和接口时声明的类型

注册类型和接口

现在我们有了类型和一些接口,让我们来看看支持这种类型的插件是如何实现的。首先,考虑清单1中插件的信息属性列表

Listing 1 An Info.plist file for a plug-in

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>English</string>
    <key>CFBundleExecutable</key>
    <string>CFTestPlugin</string>
    <key>CFBundleIconFile</key>
    <string></string>
    <key>CFBundleIdentifier</key>
    <string>com.apple.yourcfbundle</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundlePackageType</key>
    <string>BNDL</string>
    <key>CFBundleSignature</key>
    <string>????</string>
    <key>CFBundleVersion</key>
    <string>1.0</string>
    <key>CFPlugInDynamicRegisterFunction</key>
    <string></string>
    <key>CFPlugInDynamicRegistration</key>
    <string>NO</string>
    <key>CFPlugInFactories</key>
    <dict>
        <key>68753A44-4D6F-1226-9C60-0050E4C00067</key>
        <string>MyFactoryFunction</string>
    </dict>
    <key>CFPlugInTypes</key>
    <dict>
        <key>D736950A-4D6E-1226-803A-0050E4C00067</key>
        <array>
            <string>68753A44-4D6F-1226-9C60-0050E4C00067</string>
        </array>
    </dict>
    <key>CFPlugInUnloadFunction</key>
    <string></string>
</dict>
</plist>

信息属性列表定义插件运行时行为的各个方面,并包含插件支持的各种类型的可选静态注册信息。有关静态和动态注册的更多信息,请参见插件的注册。

在本例中,CFBundleExecutable键告诉CFBundle可执行文件的名称,并由bundle的原始代码加载API使用。其余的键是特定于插件模型的。

CFPlugInDynamicRegistration键表示该插件是否需要动态注册。在本例中,使用了静态注册,因此将动态注册键设置为NO。

CFPlugInFactories键用于静态地注册工厂函数,而CFPlugInTypes键用于静态地注册能够创建每种受支持类型的工厂。

实现类型、工厂和接口

在实现插件时,您必须提供:

  • 为插件注册的任何工厂函数的实现
  • 插件支持的所有类型的所有接口中所有函数的实现
  • 您实现的每个接口的接口函数表
  • 所需的COM函数QueryInterfaceAddRefRelease

清单2包含实现kTestTypeID类型及其接口的插件的代码。

Listing 2 Example plug-in implementation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#include <CoreFoundation/CoreFoundation.h>
 #include "TestInterface.h"
 
// The UUID for the factory function.
#define kTestFactoryID (CFUUIDGetConstantUUIDWithBytes(NULL,
 0x68, 0x75, 0x3A, 0x44, 0x4D, 0x6F, 0x12, 0x26, 0x9C, 0x60,
0x00, 0x50, 0xE4, 0xC0, 0x00, 0x67))
 
// The layout for an instance of MyType.
typedef struct _MyType {
    TestInterfaceStruct *_testInterface;
    CFUUIDRef _factoryID;
    UInt32 _refCount;
 } MyType;
 
// Forward declaration for the IUnknown implementation.
static void _deallocMyType( MyType *this );
 
// Implementation of the IUnknown QueryInterface function.
static HRESULT myQueryInterface( void *this, REFIID iid, LPVOID *ppv )
{
    // Create a CoreFoundation UUIDRef for the requested interface.
    CFUUIDRef interfaceID = CFUUIDCreateFromUUIDBytes( NULL, iid );
 
    // Test the requested ID against the valid interfaces.
    if (CFEqual(interfaceID, kTestInterfaceID))
    {
        // If the TestInterface was requested, bump the ref count,
        // set the ppv parameter equal to the instance, and
        // return good status.
        ((MyType *)this)->_testInterface->AddRef( this );
        *ppv = this;
        CFRelease(interfaceID);
        return S_OK;
    }
 
    if (CFEqual(interfaceID, IUnknownUUID))
    {
        // If the IUnknown interface was requested, same as above.
        ((MyType *)this)->_testInterface->AddRef( this );
        *ppv = this;
        CFRelease( interfaceID );
        return S_OK;
    }
 
    // Requested interface unknown, bail with error.
    *ppv = NULL;
    CFRelease( interfaceID );
    return E_NOINTERFACE;
}
 
// Implementation of reference counting for this type.
// Whenever an interface is requested, bump the refCount for
// the instance. NOTE: returning the refcount is a convention
// but is not required so don’t rely on it.
static ULONG myAddRef( void *this )
{
    ((MyType *)this)->_refCount += 1;
    return ((MyType *)this)->_refCount;
}
 
// When an interface is released, decrement the refCount.
// If the refCount goes to zero, deallocate the instance.
static ULONG myRelease( void *this )
{
    ((MyType *)this)->_refCount -= 1;
    if (((MyType *)this)->_refCount == 0)
    {
        _deallocMyType( (MyType *)this );
        return 0;
    }
    return ((MyType *)this)->_refCount;
}
 
// The implementation of the TestInterface function.
static void myFooMe( void *this, Boolean flag )
{
    printf("myFooMe: instance 0x%x: I've been fooed.  %s\n",
            (unsigned)this, (flag ? "YES" : "NOPE"));
}
 
// The TestInterface function table.
static TestInterfaceStruct testInterfaceFtbl =
{
        NULL,               // Required padding for COM
        myQueryInterface,   // These three are the required COM functions
        myAddRef,
        myRelease,
        myFooMe
}; // Interface implementation
 
// Utility function that allocates a new instance.
static MyType *_allocMyType( CFUUIDRef factoryID )
{
    // Allocate memory for the new instance.
    MyType *newOne = (MyType *)malloc( sizeof(MyType) );
 
    // Point to the function table
    newOne->_testInterface = &testInterfaceFtbl;
 
    // Retain and keep an open instance refcount
    // for each factory.
    newOne->_factoryID = CFRetain( factoryID );
    CFPlugInAddInstanceForFactory( factoryID );
 
    // This function returns the IUnknown interface
    // so set the refCount to one.
    newOne->_refCount = 1;
    return newOne;
}
 
// Utility function that deallocates the instance
// when the refCount goes to zero.
static void _deallocMyType( MyType *this )
{
    CFUUIDRef factoryID = this->_factoryID;
    free(this);
    if (factoryID)
    {
        CFPlugInRemoveInstanceForFactory( factoryID );
        CFRelease( factoryID );
    }
}
 
// Implementation of the factory function for this type.
void *MyFactory(CFAllocatorRef allocator, CFUUIDRef typeID)
{
    // If correct type is being requested, allocate an
    // instance of TestType and return the IUnknown interface.
    if (CFEqual(typeID, kTestTypeID))
    {
        MyType *result = _allocMyType( kTestFactoryID );
        return result;
    }
    // If the requested type is incorrect, return NULL.
    return NULL;
}

如清单2所示,实现插件的第一步是为将要提供的工厂定义UUID。这与信息属性列表中的CFPlugInFactories键使用的UUID相同。接下来,定义TestType实现实例的数据结构。

在定义实例结构之后,您将实现每个插件所需的IUnknown接口函数。在本例中,QueryInterface依赖于实例结构中的第一个指针是接口这一事实,因此,返回指向MyType结构的指针与返回指向TestInterface指针的指针是相同的。实现多个接口的类型将更加复杂。在c++中,这可以通过使用多重继承和静态类型转换来实现。在C语言中,您必须手动跟踪接口指针。

在IUnknown函数之后,是来自TestInterface的fooMe函数的实现。在本例中,它只是打印一条消息。接下来是实际TestInterface函数表的静态定义。这个表由IUnknown和TestInterface函数填充。

在函数表之后是两个实用函数,它们允许轻松地创建和释放MyType结构。分配器填充指向接口函数表的指针,并将初始引用计数设置为1。它还负责向工厂注册实例,以便插件知道在实例仍然处于活动状态时不要卸载插件的代码。deallocator函数释放MyType的内存并从工厂注销实例。

最后,实际的工厂函数创建一个新实例并返回一个指向它的指针。这个指针也是一个指向IUnknown接口的指针。MyFactory函数必须符合CFPlugInFactoryFunction原型。工厂函数以分配器和类型uuid作为参数。

清单2包含许多胶水代码,对于使用内置支持生成COM接口布局的编译器的C ++开发人员而言,这是不必要的。 如果希望用C ++实现CFPlugIns,请参考Microsoft和其他公司提供的大量COM文档。

加载和使用插件

清单1中的代码示例展示了插件宿主如何加载和使用插件。它搜索已注册的插件,寻找实现测试接口的插件。如果找到,它将使用接口调用函数。

  • CFPlugInFindFactoriesForPlugInType搜索所有已注册的插件,并返回所有能够创建所请求类型的工厂的数组。
  • 对于每个工厂,CFPlugInInstanceCreate创建一个测试类型的实例,并获得一个指向它的IUnknown接口的指针(如果存在的话)。
  • 测试IUnknown接口以确定它是否是测试接口。如果是,则调用它的一个函数并设置一个标志来停止搜索。

Listing 1 Loading and using a plug-in

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
Boolean foundInterface = false;
 
// Create a URL that points to the plug-in using a CFString path 'aPath'.
CFURLRef url =
    CFURLCreateWithFileSystemPath(NULL, aPath, kCFURLPOSIXPathStyle, TRUE);
 
// Create a CFPlugin using the URL.
// This causes the plug-in's types and factories to be registered with the system.
// The plug-in's code is not loaded unless it is using dynamic registration.
CFPlugInRef plugin = CFPlugInCreate(NULL, url);
 
if (!plugin)
{
    printf("Could not create CFPluginRef.\n");
}
else
{
    // Test whether this plug-in implements the Test type.
    CFArrayRef factories = CFPlugInFindFactoriesForPlugInType(kTestTypeID);
 
    // If there are factories for the requested type, attempt to
    // get the IUnknown interface.
    if (factories != NULL)
    {
        CFIndex factoryCount = CFArrayGetCount(factories);
 
        if (factoryCount <= 0)
        {
            printf("Could not find any factories.\n");
        }
        else
        {
            CFIndex index;
 
            for (index = 0;
                (index < factoryCount) && (!foundInterface);
                index++)
            {
                // Get the factory ID at the current index.
                CFUUIDRef factoryID =
                            CFArrayGetValueAtIndex(factories, index);
                // Use the factory ID to get an IUnknown interface.
                // The code for the PlugIn is loaded here.
                IUnknownVTbl **iunknown =
                    CFPlugInInstanceCreate(NULL, factoryID, kTestTypeID);
                // If this is an IUnknown interface,
                // query for the Test interface.
                if (iunknown)
                {
                    TestInterfaceStruct **interface = NULL;
                    (*iunknown)->QueryInterface(iunknown,
                                CFUUIDGetUUIDBytes(kTestInterfaceID),
                                (LPVOID *)(&interface));
                    // Finished with IUnknown.
                    (*iunknown)->Release(iunknown);
 
                    // If this is a Test interface, try to call its function.
                    if (interface)
                    {
                        (*interface)->fooMe(interface, TRUE);
                        (*interface)->fooMe(interface, FALSE);
                        // Finished with test interface.
                        // This causes the plug-in's code to be unloaded.
                        (*interface)->Release(interface);
 
                        foundInterface = true;
                    }
                }
                // end of iunknown
            }
            // end of index loop
        }
        // factoryCount > 0
 
        // Release the factories array.
        CFRelease(factories);
    }
    if (!foundInterface)
    {
        printf("Failed to get interface.\n");
    }
    // Release the CFPlugin -- memory for the plug-in is deallocated here.
    CFRelease(plugin);
}

以编程方式生成UUID

清单1展示了如何使用CFUUID函数以编程方式生成UUID。插件使用uuid惟一地标识类型、接口和工厂。

Listing 1 Generating a UUID programmatically

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CFUUIDRef     myUUID;
CFStringRef   myUUIDString;
char          strBuffer[100];
 
myUUID = CFUUIDCreate(kCFAllocatorDefault);
myUUIDString = CFUUIDCreateString(kCFAllocatorDefault, myUUID);
 
// This is the safest way to obtain a C string from a CFString.
CFStringGetCString(myUUIDString, strBuffer, 100, kCFStringEncodingASCII);
 
CFStringRef outputString =
    CFStringCreateWithFormat(kCFAllocatorDefault,
                             NULL,
                             CFSTR("My UUID is: %s!\n"),
                             strBuffer);
CFShow(outputString);