Plug-in Programming Topics

668 阅读25分钟

Plug-in Programming Topics

About Plug-ins

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

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

终端用户还可以从使用具有插件体系结构的应用程序中受益

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

Plug-in Architecture

所有插件模型都需要两个基本实体-插件主体和插件本身. 主体可以是应用程序, 操作系统, 甚至可以是其他插件. 插件主体的代码结构合理. 可以通过外部代码模块(插件)提供某些明确定义的函数区域. 插件的编写和编译完全独立于主体, 通常是由其他开发人员进行的. 执行主体代码时, 它将使用插件体系结构提供的任何机制来定位兼容的插件并加载它们, 从而为主体添加以前不可用的函数.

具有三个插件的CFPlugIn主体

A CFPlugIn host with three plug-ins

插件模型足够灵活, 可以至少以两种根本不同的方式使用. 第一种方法是使用插件来支持"主题上的变化"函数, 其中每个插件实现非常相似的函数并使用相同的接口. 该方法通常用于为图像编辑和音频编辑应用程序添加特殊处理支持.

例如, 音频编辑应用程序可能仅附带一些简单的处理选项, 例如均衡和标准化. 如果此应用程序具有插件体系结构, 则第三方可以在不访问应用程序源代码的情况下增加对其他处理函数(例如混响或镶边)的支持. 这是可能的, 因为主体应用程序的开发人员提供了一个明确定义的接口, 所有音频处理插件都必须使用该接口. 通过要求主体和插件仅通过该特定接口进行通信, 插件体系结构使音频应用程序完全不了解处理细节.

主体应用程序开发人员使用这种方法设计一种具有一个用于处理数据的函数的插件接口. 为了识别接口, 主体开发人员为其提供了唯一的ID. 该接口还包含放置描述处理类型的字符串的位置, 以便主体应用程序可以区分实现该接口的插件.

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

插件模型还可以用作组件体系结构, 其中每个组件(插件)实现非常不同的函数. 通过这种方法, 可以将应用程序构造为一个插件"外壳"和一组插件, 每个插件都相应负责应用程序函数的各个主要方面-用户接口, 文件系统交互, 网络通信等.

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

Plug-ins and Microsoft’s COM

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

Anatomy of a Plug-in

在磁盘上CFPlugInCFBundle一样作为文件包布置. CFPlugIn之所以与众不同是在插件的信息属性列表中添加了一些键. 这些键记录在"插件注册"中. 由于捆绑软件和插件在磁盘上共享相同的结构, 因此将CFPlugIn视为CFBundle是很诱人的(尽管不正确). 但是在运行时,正确地说CFPlugIn具有CFBundle

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

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

Conceptual Building Blocks

在插件模型中, 主体应用程序定义一种或多种类型, 每种类型由一个或多个接口组成. 插件针对插件支持的每种类型, 在所有接口中实现所有函数. 插件还为要实现的每种类型提供称为工厂的创建函数. 当主体应用程序加载插件时, 会针对每种插件类型向主体注册工厂函数. 然后, 主体可以使用类型的工厂函数实例化该类型, 并获取指向其IUnknown接口的指针, 主体使用IUnknown查询其接口函数表的类型. 函数表使主体可以访问插件的接口实现. 下图说明了这些各个组件之间的关系

Core Foundation插件模型的构建块

Building blocks of the Core Foundation plug-in model

以下各节详细描述了插件模型的各个方面。另外,本主题中的任务提供了有关如何构建简单的插件主体和插件的完整示例.

UUIDs (Universally Unique Identifiers)

插件使用UUID来唯一标识类型,接口和工厂. 创建新类型时, 主体开发人员必须生成UUID来标识该类型及其接口和工厂

UUID(通用唯一标识符), 也称为GUID(全局唯一标识符)或IID(接口标识符), 是保证唯一的128位值. 通过将UUID在其上生成的计算机的唯一值(通常是以太网硬件地址)与代表自1582101500:00:00以来100纳秒间隔数的值组合在一起,从而在空间和时间上使UUID唯一

要为插件创建UUID通常使用命令行实用程序uuidgen. 如果需要以编程方式生成UUID, 则可以使用CFUUID.h中定义的函数来生成(请参阅以编程方式生成UUID). 以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 FoundationUUID API仅在OS X上可用

Interfaces

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

接口构成主体和插件之间的协定. 该协定包括

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

除非明确定义每个接口的函数, 否则不能正确地将一组函数视为接口

例如考虑接口只有一个函数的, 该函数是一个整型参数, 整型返回值的函数. 如果没有行为的定义,则入参整数返回整数的任何函数都可以视为该接口的实现. 为了解决这种歧义, 接口中每个函数的预期行为也是该接口的"一部分". 插件主体开发人员必须向潜在的插件开发人员提供头文件和文档, 以描述构成接口的函数以及函数的预期行为. 至关重要的是插件开发人员必须确切知道正确实现接口所需的条件

插件模型中的接口与MicrosoftCOM体系结构定义的接口相同. 当用C++实现时, 所有插件接口都继承IUnknown接口, 而在C中, 所有插件接口函数表都必须包含IUnknown函数. 在运行时主体使用IUnknown接口来查找和获取对该插件实现的其他接口的访问.

Types

类型表示接口的聚合. 像接口一样类型也由主体定义并由插件实现, 类型是一个完全抽象的实体, 将相关接口组合在一起, 从而为该类型的组成接口所代表的不同API提供了概念上的保护. 类型高度呈现要实现的特性或者要解决的问题. 接口表示要解决类型描述的问题的解决方式. 可以将接口视为一种类型的实现

例如假设要开发一个名为ImageViewer的应用程序, 并且希望允许第三方使用插件为不同类型的图像添加文件格式支持. 假设定义了一个类型ImageAdaptorType, 由两个接口ImageIOInterfaceImageSnifferInterface组成. ImageIOInterface包含两个函数. 第一个函数将采用CFURLRef类型的参数, 并返回由ImageViewer定义的特殊ImageViewerImage类型来表示图像. 第二个函数将采用CFURLRef和ImageViewerImage类型的参数, 并将图像写入URL描述的位置

由于接口一旦发布就无法更改, 因此类型必须支持多个接口. 无需更改类型的接口, 只需将具有更改或附加函数的新接口添加到该类型. 这样单个插件可以通过为每个版本实现不同的接口来支持主体应用程序的不同版本. 类型将保持不变. 由主体应用程序来定位和使用适当的接口

也可以为类型定义可选接口. 例如, 主体应用程序可能会定义一种类型, 该类型具有特定于附加硬件的可选接口. 主体应用程序可以在运行时测试该硬件的存在, 如果存在, 则主体可以请求特定于硬件的插件接口. 如果插件实现者不希望支持特定于硬件的函数, 则无需实现该接口

Factories

工厂代表可以创建一个或多个类型的实例的函数. 调用类型的工厂函数类似于在JavaC++中对类的new调用操作符. 当由插件主体调用时, 工厂函数将为所请求类型的实例分配内存, 为其接口设置函数表, 并返回指向该类型的IUnknown接口的指针. 然后插件主体可以使用IUnknown接口搜索该类型支持的其他接口.

创建CFPlugin时, 系统会注册插件支持的所有类型及其关联的工厂函数. 插件主体要创建给定类型的实例时, 它将使用该类型的UUID搜索任何已注册的工厂函数. 然后可以使用工厂函数创建该类型的实例

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

Instances

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

Plug-in Registration

为了使插件主体知道可用的类型, 每个插件都必须向主体注册. 注册包括使主体了解插件实现的类型及其关联的工厂函数. 可以使用插件的信息属性列表中的几个特殊键来静态声明此信息, 也可以通过插件中的代码动态地注册该信息.

主体找到插件后, 插件将使用插件的信息属性列表中的CFPlugInDynamicRegistration键的值来确定插件是否需要静态或动态注册. 如果插件使用动态注册, 则必须立即加载插件代码. 以便进行动态注册. 如果插件使用静态注册, 则在应用程序实际需要实例化类型之前, 无需加载其代码. 因此在没有最主要的理由使用动态注册的情况下, 首选静态注册.

对于静态注册, 信息属性列表包含CFPlugInFactories键, 其值是一个字典, 字典中的键是工厂UUIDs(以标准字符串格式表示), 其值是函数名. 创建插件的CFBundle时, 此词典中的每个键值对都会注册一个工厂函数. 信息属性列表还包含CFPlugInTypes键, 此键的值是一个字典,字典中的键键是UUID类型,对应的值是工厂UUID的数组. 然后对于每种类型, 插件中都有可以创建该类型的工厂列表

代码实体还可以使用动态注册功能来注册内置类型和工厂. 然后, 具有内置类型的代码可以在内部使用插件模型, 或者允许插件向主体查询内置类型和接口. 如果插件需要在使用前查询有关主体状态的某些信息,则可能有必要动态注册内置类型的代码来进行查询

Information Property List Keys Defined by Plug-ins

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

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

Defining Types and Interfaces

作为插件主体开发人员的首要任务是定义主体支持的类型和接口

要定义类型, 真正需要的只是一个UUID, 要为插件创建UUID, 通常使用命令行实用程序uuidgen。如果需要以编程方式生成UUID, 则可以使用CFUUID.h中定义的函数来生成(请参阅以编程方式生成UUID). 对于类型和工厂, 需要两种形式的UUID, 即头文件的十六进制表示形式和信息属性列表的ASCII表示形式。以ASCII表示的UUID的标准格式是用连字符打点的字符串, 例如:D736950A-4D6E-1226-803A-0050E4C00067. 十六进制表示形式看起来像是一个以0x开头的数值列表, 例如0xD7、0x36、0x95、0x0A,0x4D,0x6E,0x12、0x26、0x80、0x3A,0x00、0x50、0xE4、0xC0、0x00、0x67

除了其UUID之外, 有关类型的重要信息还包括该类型应实现的接口以及可选的接口(如果有). 在运行时不需要此信息,也不必在标头中将其表示为代码,而应将其表示为注释

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

下列示例显示了声明类型的标头文件的内容以及用于实现该类型的接口. 此标头通常由插件主体开发人员创建并可供插件编写器使用

定义类型和接口

#include <CoreFoundation/CoreFoundation.h>
 
// Define the UUID for the type.
// 给类型定义UUID
#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.
// 给接口定义UUID
#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作为其第一个元素. 此宏(下面代码所示)扩展到COM IUnknown接口的结构体定义中. COM规范要求所有接口从IUnknown继承. 实际上, 这意味着每个接口函数表都必须从三个函数开始 - QueryInterface, AddRef, Release. 这也意味着任何接口都可以多态地视为IUnknown接口. 在运行时主体使用 QueryInterface查找和访问类型支持的所有其他接口. AddRefRelease用于引用计数

C语言实现的IUnknown

#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类派生接口类来实现. 下面示例显示了IUnknown接口在C++中的样子

C++中IUnknown的实现

interface IUnknown
{
   virtual HRESULT __stdcall QueryInterface(const IID& iid
                                   void **ppv) = 0;
   virtual ULONG __stdcall AddRef() = 0;
   virtual ULONG __stdcall Release() = 0;
}

最后请注意TestInterfaceStruct中的函数fooMethis参数作为其第一个参数. 这不是必需的, 但协助插件编写器是一件好事. 通过将this指针传递给每个接口函数, 可以允许插件编写器在C++中实现, 并在函数以任何语言执行时访问插件对象

Implementing a Plug-in

Registering Types and Interfaces

现在我们已经有一个类型和一些接口, 让我们来看看如何实现支持这种类型的插件. 首先考虑插件的信息属性列表

一个插件信息的Info.plist文件

<?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>

<!-- 
 @{
     @"CFBundleDevelopmentRegion" : @"English",
     @"CFBundleExecutable" :  @"CFTestPlugin",
     @"CFBundleIconFile" : @"",
     @"CFBundleIdentifier" : @"com.apple.yourcfbundle",
     @"CFBundleInfoDictionaryVersion" : @"6.0",
     @"CFBundlePackageType" : @"BNDL",
     @"CFBundleSignature" : @"???",
     @"CFBundleVersion" : @"1.0",
     @"CFPlugInDynamicRegisterFunction" : @"",
     @"CFPlugInDynamicRegistration" : @"NO", // 表明是静态注册
     @"CFPlugInFactories" : @{
         @"68753A44-4D6F-1226-9C60-0050E4C00067" : @"MyFactoryFunction", // UUID : MyFactoryFunction
     },
     @"CFPlugInTypes" : @{
         @"D736950A-4D6E-1226-803A-0050E4C00067" : @[
            @"68753A44-4D6F-1226-9C60-0050E4C00067"
         ];
     }, 
     @"CFPlugInUnloadFunction" : @"",
 }
-->

信息属性列表定义插件运行时行为的各个方面, 并包含插件支持的各种类型的可选静态注册信息. 有关静态和动态注册的详细信息, 请参之前的Plug-in Registration

在此示例中,CFBundleExecutable告诉CFBundle可执行文件的名称, 并由捆绑包的原始代码加载API使用. 其余键特定于插件模型.

CFPlugInDynamicRegistration键指示此插件是否需要动态注册. 在此示例中使用静态注册, 因此动态注册键设置为NO

CFPlugInFactories键用于静态注册工厂函数, CFPlugInTypes键用于静态注册可以创建每个支持类型的工厂

Implementing the Types, Factories, and Interfaces

实现插件时必须提供

  • 插件注册的任何工厂函数的实现
  • 插件支持的所有类型的所有接口中所有函数的实现
  • 实现的每个接口的接口函数表
  • 需要的COM函数, QueryInterface, AddRef, Release

示例包含实现类型kTestTypeID及其接口的插件的代码

插件的实现

#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;
}

实现插件的第一步是为要供应的工厂定义UUID, 这与信息属性列表中的CFPlugInFactories键中使用的UUID相同. 接下来定义TestType实现实例的数据结构

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

IUnknown函数之后,则是TestInterfacefooMe函数的实现. 在此示例中它只是打印一条消息. 接下来是实际TestInterface函数表的静态定义. 此表用IUnknown的函数和TestInterface函数填充

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

最后存在创建新实例并返回实例指针. 此指针也是指向IUnknown接口的指针. MyFactory函数必须符合CFPlugInFactory函数原型. 工厂函数使用allocatorsUUID作为参数

Loading and Using a Plug-in

下列示例显示了插件主体如何加载和使用插件. 它搜索已注册的插件, 以寻找实现测试接口的插件. 如果找到, 它将使用接口调用函数

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

加载和使用插件

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);
}

Generating a UUID Programmatically

示例演示如何使用CFUUID函数以编程方式生成UUID. 插件使用UUID来唯一地标识类型、接口和工厂

以编程方式生成UUID

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);

总结

使用插件流程

  1. 主体(应用程序, 操作系统, 其他插件)定义一个或多个类型(Type). 类型包含多个接口声明
  2. 插件向主体注册, 让主体知道插件支持的类型
  3. 插件实现支持的类型里接口所有的函数

理解如有错误 望指正 转载请说明出处