XPIDL

**XPIDL** 是一种接口描述语言,用于指定 XPCOM 接口类。

接口描述语言 (IDL) 用于以语言和机器无关的方式描述接口。IDL 使定义接口成为可能,然后可以通过工具处理这些接口来自动生成与语言相关的接口规范。

xpidl 文件本质上只是一系列声明。在顶层,我们可以定义类型定义、本机类型或接口。接口可能进一步包含类型定义、本机类型、方法、常量或属性。大多数声明可以应用属性。

类型

有三种方法可以创建类型:类型定义、本机类型或接口。此外,还有一些内置的本机类型。内置本机类型是在上面 type_spec 产生式下列出的那些。以下是对应表

IDL 类型

Javascript 类型

C++ 输入参数

C++ 输出参数

Rust 输入参数

Rust 输出参数

boolean

boolean

bool

bool*

bool

*mut bool

char

string

char

char*

c_char

*mut c_char

double

number

double

double*

f64

*mut f64

float

number

float

float*

f32

*mut f32

long

number

int32_t

int32_t*

i32

*mut i32

long long

number

int64_t

int64_t*

i64

*mut i64

octet

number

uint8_t

uint8_t*

u8

*mut u8

short

number

uint16_t

uint16_t*

u16

*mut u16

string [1]

string

const char*

char**

*const c_char

*mut *mut c_char

unsigned long

number

uint32_t

uint32_t*

u32

*mut u32

unsigned long long

number

uint64_t

uint64_t*

u64

*mut u64

unsigned short

number

uint16_t

uint16_t*

u16

*mut u16

wchar

string

char16_t

char16_t*

i16

*mut i16

wstring [1]

string

const char16_t*

char16_t**

*const i16

*mut *mut i16

MozExternalRefCountType

number

MozExternalRefCountType

MozExternalRefCountType*

u32

*mut u32

Array<T> [2]

array

const nsTArray<T>&

nsTArray<T>&

*const ThinVec<T>

*mut ThinVec<T>

除了此列表之外,几乎每个 IDL 文件都以某种方式包含 nsrootidl.idl,它还定义了以下类型

IDL 类型

Javascript 类型

C++ 输入参数

C++ 输出参数

Rust 输入参数

Rust 输出参数

PRTime

number

uint64_t

uint64_t*

u64

*mut u64

nsresult

number

nsresult

nsresult*

u32 [3]

*mut u32

size_t

number

uint32_t

uint32_t*

u32

*mut u32

voidPtr

N/A

void*

void**

*mut c_void

*mut *mut c_void

charPtr

N/A

char*

char**

*mut c_char

*mut *mut c_char

unicharPtr

N/A

char16_t*

char16_t**

*mut i16

*mut *mut i16

nsIDRef

ID 对象

const nsID&

nsID*

*const nsID

*mut nsID

nsIIDRef

ID 对象

const nsIID&

nsIID*

*const nsIID

*mut nsIID

nsCIDRef

ID 对象

const nsCID&

nsCID*

*const nsCID

*mut nsCID

nsIDPtr

ID 对象

const nsID*

nsID**

*const nsID

*mut *mut nsID

nsIIDPtr

ID 对象

const nsIID*

nsIID**

*const nsIID

*mut *mut nsIID

nsCIDPtr

ID 对象

const nsCID*

nsCID**

*const nsCID

*mut *mut nsCID

nsID

N/A

nsID

nsID*

N/A

N/A

nsIID

N/A

nsIID

nsIID*

N/A

N/A

nsCID

N/A

nsCID

nsCID*

N/A

N/A

nsQIResult

object

void*

void**

*mut c_void

*mut *mut c_void

AUTF8String [4]

string

const nsACString&

nsACString&

*const nsACString

*mut nsACString

ACString [4]

string

const nsACString&

nsACString&

*const nsACString

*mut nsACString

AString [4]

string

const nsAString&

nsAString&

*const nsAString

*mut nsAString

jsval

any

HandleValue

MutableHandleValue

N/A

N/A

jsid

N/A

jsid

jsid*

N/A

N/A

Promise

Promise 对象

dom::Promise*

dom::Promise**

N/A

N/A

IDL 中的类型定义基本上与 C 或 C++ 中的类型定义相同:您首先定义要引用的类型,然后定义类型的名称。类型当然可以是基本类型之一,也可以是通过类型定义、接口或本机类型声明的任何其他类型。

本机类型是对应于给定 C++ 类型的类型。大多数本机类型不可脚本化:如果它不在上面的列表中,那么它肯定不可脚本化(上面的一些,特别是 jsid,不可脚本化)。

本机类型声明的括号内容(尽管没有括号的本机声明是可以解析的,但我不能保证 xpidl 处理程序能正确处理它们)是一个等效于 C++ 类型的字符串。XPIDL 本身不会解释此字符串,它只是在任何使用本机类型的地方都直接粘贴它。可以通过在本机声明上使用 [ptr][ref] 属性来修改类型的解释。其他属性仅用于 nsrootidl.idl 中。

WebIDL 接口

WebIDL 接口也是有效的 XPIDL 类型。要在 XPIDL 中声明 WebIDL 接口,请编写

webidl InterfaceName;

当用作输入参数时,WebIDL 类型将作为 mozilla::dom::InterfaceName* 传递,当用作输出或输入/输出参数时,将作为 mozilla::dom::InterfaceName** 传递,当用作数组元素时,将作为 RefPtr<mozilla::dom::InterfaceName> 传递。

注意

其他 WebIDL 类型(例如字典、枚举和联合)当前不支持。

常量和 CEnums

常量必须附加到接口。唯一支持的常量是那些编译成源代码时成为整数类型的常量;字符串常量和浮点数常量当前不支持。

常量通常用于描述一组枚举值。在这种情况下,可以使用 cenum 构造来将常量组合在一起。在 cenum 中组合的常量将反映为它们是在接口上直接声明的一样,在 Rust 和 Javascript 代码中。

cenum MyCEnum : 8 {
  eSomeValue,  // starts at 0
  eSomeOtherValue,
};

枚举名称后的数字,例如上面示例中的 : 8,定义了具有给定类型的枚举值的宽度。cenum 的类型可以在 xpidl 中引用为 nsIInterfaceName_MyCEnum

接口

接口本质上是常量、方法和属性的集合。接口可以继承自其他接口,并且每个接口最终都必须继承自nsISupports

接口属性

接口可以具有以下属性

uuid

接口的内部唯一标识符。它必须是唯一的,并且在创建接口时必须生成 uuid。之后,无需再更改它。

mach gen-uuid,一个类似于uuidgen的 CLI 工具,或一个在线工具,例如https://mozilla.pettay.fi/uuidgen.html,可以帮助为新接口生成 UUID。

builtinclass

禁止 JavaScript 类实现此接口。所有子接口也必须使用此属性进行标记。

function

此接口的 JavaScript 实现可能是一个在属性调用时调用的函数,而不是具有给定属性的对象。

scriptable

此接口可供 JavaScript 类使用。必须继承自scriptable接口。

rust_sync

此接口可以安全地从多个线程并发使用。所有子接口也必须使用此属性进行标记。以这种方式标记的接口必须是非可脚本化的或builtinclass,并且必须使用线程安全的引用计数。

标记为rust_sync的接口将在 Rust 中实现Sync特征。有关这意味着什么的更多详细信息,请阅读特征的文档:https://doc.rust-lang.net.cn/nightly/std/marker/trait.Sync.html

方法和属性

接口声明一系列属性和方法。IDL 中的属性类似于 JavaScript 属性,因为它们是一对 getter 和(可选)setter。在 JavaScript 上下文中,属性作为常规属性访问公开,而本机代码将属性视为 Get 方法和可能存在的 Set 方法。

属性可以声明为只读,在这种情况下,在脚本上下文中设置属性会导致抛出错误,并且本机上下文缺少 Set 方法,方法是使用readonly关键字。

对于本机代码,声明为attribute type foo;的属性是两个方法type getFoo();void setFoo(in type foo);的声明的语法糖。如果foo被声明为只读,则后者方法将不存在。属性支持所有方法的属性,除了optional_argc,因为这对属性没有意义。

属性命名有一些特殊规则。由于 MSVC++ 编译器进行的 vtable 转换,禁止使用名为IID的属性。与方法类似,如果属性的第一个字符在 IDL 中是小写,则仅在本机代码中将其大写。

方法定义一个返回类型和一系列输入和输出参数。当从 JavaScript 上下文调用时,调用在大多数情况下看起来与声明的方式相同;某些参数属性可以调整代码的外观。在本地上下文中,调用会更加复杂。

方法和属性的一个重要属性是可脚本性。如果方法或属性在scriptable接口中声明,并且缺少noscriptnotxpcom属性,则该方法或属性是可脚本化的。任何不可脚本化的方法只能由本机代码访问。但是,scriptable方法必须包含可以转换为脚本的参数和返回类型:除了nsrootidl.idl中声明的一些类型(见上文)之外,任何本机类型都不能在可脚本化的方法或属性中使用。上述规则的一个例外是,如果nsQIResult参数具有iid_is属性(某些类似 QueryInterface 的操作的特殊情况)。

方法和属性在转换为本机代码时会被转换。如果方法声明为notxpcom,则会阻止返回类型的转换,因此它大多按其外观进行调用。否则,本机方法的返回类型为nsresult,如果返回类型不是void,则其充当最终的输出参数。名称会被转换,以便第一个字符无条件地大写;后续字符不受影响。但是,binaryname属性的存在允许用户选择另一个在本地代码中使用的名称(以避免与其他函数冲突)。例如,方法[binaryname(foo)] void bar();在本机代码中变为nsresult Foo()(请注意,仍然应用了大写)。但是,在将binaryname与属性一起使用时,不会应用大写;即,[binaryname(foo)] readonly attribute Quux bar;在本机代码中变为Getfoo(Quux**)

implicit_jscontextoptional_argc参数是帮助本地代码实现确定从脚本中如何进行调用的属性。如果implicit_jscontext存在于方法中,则会在常规列表之后添加一个额外的JSContext* cx参数,该参数接收调用者的上下文。如果存在optional_argc,则会在末尾添加一个额外的uint8_t _argc参数,该参数接收实际使用的可选参数的数量(显然,您首先需要有一个可选参数)。请注意,如果这两个属性都设置了,则首先添加JSContext* cx,然后是uint8_t _argc,最后是返回值参数。最后,作为对所有已提及内容的例外,对于属性 getter 和 setter,JSContext *cx位于任何其他参数之前。

另一个仅限本机的属性是nostdcall。通常,在 Windows 上以 stdcall ABI 声明接口,以与 COM 接口保持 ABI 兼容。任何具有nostdcall的不可脚本化方法或属性都使用thiscall ABI 约定。没有此属性的方法通常在其声明中使用NS_IMETHOD,在其定义中使用NS_IMETHODIMP,以便在必要的编译器上自动添加 stdcall 声明说明符;使用此方法的方法可以使用普通的nsresult代替。

另一个属性infallible仅用于属性。当存在时,它会导致为属性生成一个可靠的 C++ getter 函数定义,以及正常的易错 C++ getter 声明。仅当易错 getter 在实践中是可靠的(即始终返回NS_OK)对于所有可能的实现时才应使用它。此可靠的 getter 包含调用易错 getter、断言成功并直接返回获取值的代码。使用此属性的目的是使 C++ 代码更友好 - 对可靠的 getter 的调用比对易错 getter 的调用更简洁易读。此属性只能用于具有内置类型或接口类型的属性,以及标记为builtinclass的类中。后者的限制是因为可以审核易错 getter 的 C++ 实现以确定其是否可靠,但 JS 实现始终可以抛出异常(例如,由于内存不足)。

如果方法调用或属性 get/set 的结果应始终(或通常)进行检查,则must_use属性很有用。(例如,打开文件的程序方法几乎肯定需要检查其结果。)此属性会导致将[[nodiscard]]添加到生成的函数声明中,这意味着某些编译器(例如 clang 和 GCC)将在不使用这些结果时报告错误。

方法参数

每个方法参数都可以以三种模式之一指定:inoutinoutout参数本质上是一个辅助返回值,尽管从脚本上下文中使用这些参数比较麻烦,因此如果合理,应避免使用它们。inout参数是一个输入参数,其值可能因方法的结果而改变;这些参数使用起来相当麻烦,并且通常应尽量避免使用。

outinout参数会反映为具有.value属性的对象,该属性包含参数的真实值;对于out参数,缺少value属性,并且对于inout参数,将其初始化为传入的值。脚本代码需要设置此属性以将值赋给参数。常规in参数或多或少地正常反映,其中所有数字类型都表示数字,布尔值表示truefalse,各种字符串(包括AString等)表示 JavaScript 字符串,而nsID类型表示Components.ID实例。此外,jsval类型被转换为适当的 JavaScript 值(因为jsval是所有 JavaScript 值的内部表示),并且具有nsIVeriant接口的参数会根据需要自动装箱和拆箱。

所有 IDL 类型在本机代码中的等效表示在前面的表格中给出;类型为inout的参数遵循其out形式。本机代码应特别注意不要为输出参数传递空值(尽管已知代码库的某些部分违反了此规则,但在 JS<->native 屏障处严格执行此规则)。

类型的表示还取决于它们可能具有的许多类型的属性中的一些。array属性将参数转换为数组;参数还必须具有相应的size_is属性,其参数是具有数组大小的参数。在本机代码中,类型获得另一个指针间接寻址,并且 JavaScript 数组在脚本代码中使用。脚本代码调用者可以忽略数组参数的值,但实现者仍然必须适当地设置值。

注意

对于新代码,建议使用Array<T>内置类型而不是[array]属性。它更易于从 JS 和 C++ 中使用。将来,可能弃用并删除[array]

constshared属性对本机代码是特殊的。顾名思义,const属性使其对应的参数变为constshared属性仅对outinout参数有意义,并且表示调用者不应释放指针值。只有简单的本机指针类型,如stringwstringoctetPtr,可以声明为共享。shared 属性还会使其对应的参数变为 const。

retval属性指示参数实际上充当返回值,并且只是需要为参数赋值属性才导致将其指定为参数。它对本机代码没有影响,但脚本代码将其用作常规返回值。自然地,包含retval参数的方法必须声明为void,参数本身必须是out参数并且是最后一个参数。

其他属性是optionaliid_is属性。optional属性表示脚本代码可以省略属性而不会出现问题;所有后续参数必须是可选的或 retval 参数。请注意,可选的输出参数仍然为参数传递一个变量,但其值将被忽略。iid_is参数表示可以在相应的参数中找到nsQIResult参数的真实 IID,以允许脚本代码自动拆箱类型。

并非所有类型组合都是可能的。具有各种字符串属性的原生类型都被禁止用作inout参数或array参数。此外,具有nsid属性但缺少ptrref属性的原生类型是被禁止的,除非该方法是notxpcom并且用作in参数。

所有权规则

对于引用堆分配数据的类型(字符串、数组、接口指针等),必须遵循 XPIDL 数据所有权约定,以避免内存损坏和安全漏洞。

  • 对于in参数,调用方分配并释放所有数据。如果被调用方需要在调用完成后使用该数据,则必须创建数据的私有副本,或者在接口指针的情况下,AddRef它。

  • 对于out参数,被调用方创建数据,并将所有权转移给调用方。对于缓冲区,被调用方使用malloc分配缓冲区,调用方使用free释放缓冲区。对于接口指针,被调用方代表调用方执行AddRef操作,调用方必须调用Release。此手动引用/内存管理应使用新代码中的getter_AddRefsgetter_Transfers帮助器执行。

  • 对于inout参数,如果被调用方选择替换旧数据,则必须清理旧数据。缓冲区必须使用free释放,接口指针必须Release。之后,将应用上述out规则。

  • shared out 参数不应被释放,因为它们旨在引用常量字符串文字。