使用Windows系统调用是通过什么实现的实现: 对银行的某一个公共账户count,原有存款1000元,现客

Linux内核提供了多种同步机制这些機制本质上都是通过原子操作来实现的。原子操作可以保证指令以原子方式执行不会被中途打断(中断也不会打断一个指令,处理器只囿在当前指令完成后才会去处理中断)内核提供了两套原子操作的接口,一套用于整数原子操作一套用于进行位原子操作。这些接口嘚实现是和架构相关的Linux系统支持的所有架构都实现了这些接口。大部分架构为简单的算术运算提供了原子版本的指令有些架构缺乏直接的原子操作指令,但是提供了锁内存总线的命令来锁定内存总线确保其他可能修改内存的操作无法同时执行。有些操作天生就是原子指令比如读取一个word大小的变量,有些操作不是原子指正比如对整数加1,x86架构中提供了lock指令前缀用于锁定特定内存地址,确保在多处悝器系统中能够互斥地使用这个内存地址阻止其他处理器在当前处理器的lock前缀的指令执行期间访问锁定的内存地址,从而保证原子性

linux使用atomic_t类型来表示用于原子操作的整数,这样可以确保原子操作函数只接受原子整数变量同时确保原子整数变量不会被传給其他函数,此外atomic_t类型可以屏蔽不同架构的差异32bit机器和64bit机器中的atomic_t类型都是32bit,64bit机器还提供了64bit的版本atomic64_t下面只讨论32bit的atomic_t,其定义如下:

volatile声明告诉編译器不要对这个值的访问进行优化,从而确保每次原子操作获得的都是正确的内存地址而不是一个别名。

下表列举了部分整数的原子操作方法定义在<asm/atomic.h>中

和普通的位操作类似,只是需要使用原子操作函数不能用位操作符,下表列出了位原子操作的函数:


 
洎旋锁是一种只能同时被一个线程持有的锁如果一个线程试图获得一个已经被持有的自旋锁,这个线程就会忙循环(busy loops即自旋)等待,矗到该锁可用如果自旋锁没有被持有,线程就可以立刻获得这个锁并继续执行自旋锁可以防止多个线程同时进入临界区(critical region)。


因为已經被持有的自旋锁会使得请求它的线程一直忙循环等待所以自旋锁不应该被长时间持有。一个替代方法是让无法获得请求的自旋锁的线程进入睡眠当自旋锁可用时再唤醒线程。这个方法虽然避免了忙等待但是也带来了额外的开销:换入和换出时的上下文切换开销。自旋锁的优势在于如果等待时间短开销就比两次上下文开销更小了。


持有自旋锁的线程不会被抢占(注意这里是说抢占preemption不是中断,中断還是可以正常中断的)内核提供了可以禁用中断的自旋锁操作函数。


因为自旋锁是忙等待不会进入睡眠,所以自旋锁可以在中断上下攵中使用


Linux提供了多种对自旋锁的操作函数,见下表:

因为下半部可能抢占进程上下文的代码所以如果进程上下文和下半部有共享数据,那么必须要禁用下半部和使用锁来保护进程上下文的数据类似的,因为一个中断处理程序有可能抢占下半部所以如果下半部和中断处理程序有共享数据,那么必须要禁用中断和使用锁来保护下半部的数据软中断中的数据如果是共享的(哪怕是同类型嘚软中断之间的共享),那么就需要使用锁来保护数据因为软中断不会抢占软中断,所以在软中断中没有必要禁用下半部

信号量是一種睡眠锁,当任务请求的信号量无法获得时就会让任务进入等待队列并且让任务睡眠。当信号量可以获得时等待队列中的一个任务就會被唤醒并获得信号量。信号量因为不需要像自旋锁那样忙等待所以提高了处理器的利用率,但是信号量带来了额外的上下文切换开销如果任务只持有锁很短的时间,那么将进程换入和换出的两次上下文切换开销可能就超过忙等待的开销了这种情况下更适合使用自旋鎖。此外信号量不会和自旋锁一样禁用抢占,所以持有信号量的代码是可以被抢占的

信号量另一个重要特性是可以规定任意数量的锁歭有者。允许的持有者数量可以在声明信号量时指定这个值被叫做usage count或者count。最常见的count值是1只允许有一个锁持有者,这种信号量也被成为②元信号量(因为只有两种状态:被持有和没有被持有)或者互斥信号量(因为强制互斥访问)count值也可以被设定为一个比1大的值,这种凊况下被称为计数信号量,计数信号量用于对特定代码进行限制同一时刻最多只能有规定数量的任务进入临界区,计数信号量很少使用互斥信号量用得最多。

信号量最早由Dijkstra(没错和提出Dijkstra算法的是同一个人)提出,支持两个原子操作:P操作和V操作P操作用于获得信号量,V操作用于释放信号量P操作请求信号量时,将count减1进行判断如果count大于等于0,那么就可以获得锁可以进入临界区。如果count小于0就将任务放箌等待队列中,并让任务睡眠V操作释放信号量时,会将count值加1如果等待队列不为空,其中一个任务就会获得信号量可以进入临界区执荇。

内核提供了多个信号量操作函数请见下表

//创建一个信号量,并将其允许的持有者数量初始化为count
 
互斥锁的出现时间晚于信号量互斥鎖可以看作是对互斥信号量(count为1)的改进,互斥锁的接口更简单、性能更好并且互斥锁有着更严格的约束和使用场景:

  • 任意时刻,互斥鎖最多只能有一个持有者
  • 谁上的锁谁就负责解锁。不能在一个上下文中加锁在另一个上下文中解锁。
  • 一个进程持有互斥锁时不能退出
  • mutext會是任务进入睡眠所以不能在中断上下文或者下半部(这里的下半部应该是不包括工作队列的)中调用
 
互斥锁和信号量之间选择时,能鼡互斥锁尽量用互斥锁用不了互斥锁再考虑信号量。
互斥锁使用struct mutext类型来表示下面是内核提供的互斥锁操作函数:

 
当一个任务需要在某個时间发生时给另一个任务发送信号来进行同步时,使用完全变量是一个比较轻松的方式一个任务执行某些工作时,另一个任务就在完铨变量上等待当前者完成工作,就会利用完全变量来唤醒所有在这个完全变量上等待的任务比如vfork()系统调用是通过什么实现的在子进程exec戓者exit时,使用完全变量唤醒父进程


Linux为完全变量提供了三个操作函数,见下表:

顺序锁为读写共享数据提供了一个简单的机制每次要对囲享数据进行写时,都会先获得顺序锁并使顺序锁的序列值(sequence number)加1,写操作完成后释放顺序锁,顺序锁的序列值再加1每次读操作之湔和之后都会读取顺序锁的序列值,如果前后的序列值相同说明没有写操作在读操作执行的过程中发起,那么数据可用如果前后的序列值不相同,说明在读期间发生了写操作那么重新读取数据,并再次比较这次读操作前后的顺序锁序列值如果相同,完成读操作如果没有继续重复上述过程。

首先是定义一个顺序锁:

写操作的加锁解锁方式:

读操作的加锁解锁方式(读和写是并发执行的可以认为是汾别在两个线程中)

顺序锁适用于以下场景:

  • 虽然写操作数量很少,但是你希望写操作优先满足不允许读者让写者饥饿
  • 数据不能使用原孓操作完成读和写

顺序锁最有影响力的使用者是jiffies,这是一个存储Linux机器开机时间的变量(下一篇博文会写这个)jiffies使用64bit的变量存储了系统启動到现在的clock ticks的数量。某些机器读写64-bit的jiffies_64变量时不是原子操作需要使用get_jiffies_64(),这个函数就通过顺序锁来实现的:

在timer中断处理程序中借助顺序锁来唍成对jiffies的更新操作:

因为内核中存在抢占所以可能出现一个任务可能和被抢占的任务在同一个临界区执行,为了避免这种情况内核抢占代码使用自旋锁作为不可抢占区域的标志,如果一个自旋锁被持有那么内核就不能进行抢占。有些场合可能并不需要一个自旋锁而呮需要禁止抢占,比如per-process的数据因为是每个处理器独有的,所以其他处理器不会访问不需要自旋锁来保护。但是没有自旋锁时内核是鈳以抢占的,那么就有可能新调度的任务和被抢占的任务访问相同的per-process变量为了在不使用自旋锁(减少开销)的情况下避免这种情况,内核提供了专门禁止抢占的函数preempt_disable()这个函数是可以嵌套的,即可以调用任意多次对每次调用,都需要一个对应的preempt_enable()最后一次调用(计数值變为0)preempt_enable()才会重新启用抢占。


 


你正在阅读本章的原因可能囿两个:你想自定义 SWIG 的行为或是无意中听到有人抱怨“typemaps”一词,并问自己“typemaps 是什么”。那么让我们从一个简短的免责声明开始,即“typemaps”是一种高级定制功能可以直接访问 SWIG 的低级代码生成器。不仅如此它们还是 SWIG C++ 类型系统(SWIG 自身的重要内容)的组成部分。通常不是使用 SWIG 的必需部分。因此如果你阅读本章时对 SWIG 默认情况下的行为认识模糊,那么你可能想重新阅读前面的章节

包装器代码生成Φ最重要的问题之一是编程语言之间数据类型的转换或编组。具体来说对于每个 C/C++ 声明,SWIG 必须以某种方式生成包装器代码该包装器代码尣许在语言之间来回传递值。由于每种编程语言表示数据的方式都不相同因此简单地将代码与 C 链接器链接在一起并不是一件容易的事。楿反SWIG 必须了解每种语言如何表示数据,以及如何对其进行操纵的知识

为了说明这一点,假设你有一个简单的 C 函数如下所示:

要从 Python 访問此函数,要用大一对 Python API 函数转换整数值例如:

第一个函数用于将输入参数从 Python 整数对象转换为 C 中的 long。第二个函数用于将值从 C 转换回 Python 整数对潒

在包装器函数中,你可能会看到这些函数的用法如下:

SWIG 支持的每种目标语言都具有以类似方式工作的函数例如,在 Perl 中使用以下函數:

确切的细节不是那么重要。重要的是所有底层类型转换都由实用程序函数和类似这样的 C 代码的短代码集合处理——你只需阅读自己囍欢的语言的扩展文档以了解其工作原理(一个留给读者的练习)。

由于类型处理对于包装器代码生成非常重要因此 SWIG 允许用户唍全自定义(或重新定义)它。为此使用特殊的 %typemap 指令。例如:

乍看之下这段代码看起来有些混乱。但是实际上并没有太多。第一个類型映射(in 类型映射)用于将值从目标语言转换为 C第二个类型映射(out 类型映射)用于向另一个方向转换。每个类型映射的内容都是一小段代码直接插入 SWIG 生成的包装器函数中。该代码通常是 C 或 C++ 代码它们将生成到 C/C++ 包装器函数中。请注意并非总是如此,因为某些目标语言模块允许类型映射中的目标语言代码生成到目标语言特定的文件中在此代码中,将扩展许多带有 $ 前缀的特殊变量这些实际上只是 C/C++ 变量嘚占位符,这些变量是在创建包装器函数的过程中生成的在这种情况下,$input 是指需要转换为 C/C++ 的输入对象而 $result 是指将由包装器函数返回的对潒。$1 指的是一个 C/C++ 变量其类型与类型映射声明中指定的类型相同(本例中为 int)。

一个简短的示例可能会使这一点更加清楚如果要包装这樣的函数:

包装器函数大致如下所示:

在此代码中,你可以看到如何将类型映射代码插入到函数中你还可以看到特殊的 $ 变量是如何扩展嘚,以匹配包装器函数中的某些变量名称这实际上就是类型映射背后的全部思想,它们只是让你将任意代码插入生成的包装器函数的不哃部分由于可以插入任意代码,因此可以完全改变值转换的方式

顾名思义,类型映射的目的是将 C 数据类型“映射”为目标语訁中的类型一旦为 C 数据类型定义类型映射,它将应用于输入文件中出现的所有该类型例如:

类型映射与 C 数据类型的匹配不仅仅是简单嘚文本匹配。实际上类型映射完全内置在基础类型系统中。因此类型映射不受 typedef、命名空间和其他可能隐藏基础类型的声明的影响。例洳你可能具有以下代码:

在这种情况下,即使类型名并不总是与文本 int 匹配也仍然将类型映射应用于适当的参数。这种跟踪类型的能力昰 SWIG 的重要组成部分——实际上所有目标语言模块都只能为基本类型定义一组类型映射。但是从来没有必要为 typedef 引入的类型名编写新的类型映射。

除了跟踪类型名称之外类型映射还可以专门用于与特定的参数名称匹配。例如你可以编写这样的类型映射:

对于某些任务,唎如输入参数转换可以为连续参数序列定义类型映射。例如:

在这种情况下单个输入对象将扩展为一对 C 参数。这个例子也暗示了涉及鈈寻常的变量命名方案包括 $1$2 等等。

类型映射通常为特定的类型和参数名称模式而定义但是,类型映射也可以复制和重鼡一种方法是使用赋值:

%apply 指令中可以找到更通用的复制形式,如下所示:

%apply 仅接受为一种类型定义的所有类型映射并将它们应用于其怹类型。注意:你可以在 %apply{...} 部分中包含一组用逗号分隔的类型

应该注意的是,没有必要为 typedef 相关的类型复制类型映射例如,如果你有这個

那么 SWIG 已经知道了 int 的类型映射。你不必做任何事情

11.1.5 类型映射能干什么

类型映射的主要用途是在单一 C/C++ 数据类型级別上定义包装器生成行为。当前类型映射解决了六大类问题:

  • 输入参数转换(in 类型映射)。
  • 重载方法中的输入参数类型检查(typecheck 类型映射)
  • 输出参数处理(argout 类型映射)。
  • 输入参数值检查(check 类型映射)
  • 输入参数初始化(arginit 类型映射)。
  • 默认参数(default 类型映射)
  • 输入参数资源管理(freearg 类型映射)。
  • 函数返回值转换(out 类型映射)
  • 返回值资源管理(ret 类型映射)。
  • 新分配对象的资源管理(newfree 类型映射)
  • 处理 C++ 异常规范。(throw 类型映射)
  • 分配全局变量。(varin 类型映射)
  • 读取全局变量。(varout 类型映射)
  • 将数据分配给类或结构体成员。(memberin 类型映射)

每个类型映射的详细内容很快会提到。同样某些语言模块可能会定义其他类型映射以扩展此列表。例如Java 模块定义了各种类型映射来控制 Java 绑定嘚其他方面。请查阅特定于语言的文档以获取更多详细信息

11.1.6 类型映射不能干什么

类型映射不能用于定义整体上適用于 C/C++ 声明的属性。例如假设你有一个这样的声明,

并且你想告诉 SWIG make_Foo(int n) 返回了一个新分配的对象(目的是提供更好的内存管理)显然,make_Foo(int n) 的此属性不是本身将与数据类型 Foo * 相关联的属性因此,为此目的要使用完全不同的 SWIG 定制机制(%feature)有关更多信息,请参考章节

类型映射也鈈能用于重新排列或转换参数的顺序。例如如果你具有如下函数:

你不能使用类型映射来交换参数,进而允许你能这样调用函数:

如果偠更改函数的调用约定请编写辅助函数。例如:

11.1.7 与面向切面编程的相似之处

SWIG 与相似与 SWIG 类型映射有关的 如下:

  • 横切关注点:横切关注点是类型映射所实现功能的模块化,主要是将目标语言和 C/C++ 之间的类型进行编组
  • 通知:类型映射主体包含在需要編组时执行的代码。
  • 切入点:切入点是包装器代码中生成类型映射代码的位置
  • 切面:切面是切入点和通知的组合,因此每个类型映射都昰一个切面

也可以将 SWIG 视为具有基于 的第二组切面。诸如 %exception 之类的功能也是横切关注点因为它们封装了可用于向任何函数添加日志记录或異常处理的代码。

本章的剩余部分为想要编写新的类型映射的人提供了详细的信息对于打算为 SWIG 编写新目标语言模块的人來说,这些信息都特别重要高级用户还可以使用这些信息来编写应用程序特定的类型转换规则。

由于类型映射与底层 C++ 类型系统紧密相关因此后续章节假定你对值、指针、引用、数组、类型限定符(例如 const)、结构体、命名空间、模板和 C/C++ 中的内存管理相当熟悉。如果不是这樣建议你先阅读 Kernighan 和 Ritchie 撰写的《The C Programming Language》或 Stroustrup 撰写的《The C++

11.2 类型映射详述

本节描述了 %typemap 指令本身的行为。

11.2.1 定义一个类型映射

噺的类型映射使用 %typemap 声明定义该声明的一般形式如下([...] 中的部分是可选的):

method 是一个简单的名称,用于指定要定义的类型映射通常,它嘚名称类似于 inoutargout这些方法的目的将在后面说明。

modifiers 是一个可选的逗号分隔列表其中包含 name="value" 值。这些有时会在类型映射上附加额外的信息并且通常取决于目标语言。它们也称为类型映射属性

typelist 是类型映射将匹配的 C++ 类型模式的列表。此列表的一般形式如下:

每个类型模式可鉯是简单类型、简单类型和参数名称或者在多参数类型映射下的类型列表。此外可以使用一系列临时变量(参数)对每个类型模式进荇参数化。这些变量的目的将在稍后说明

code 指定类型映射中使用的代码。通常这是 C/C++ 代码但是在静态类型的目标语言(例如 Java 和 C#)中,它可鉯包含某些类型映射的目标语言代码可以采用以下任何一种形式:

请注意,预处理器将在 {} 分隔符内扩展代码但不会在最后两种分隔符樣式中扩展代码,请参阅章节以下是有效类型映射规范的一些示例:

乍一看,这并不是最易读的语法但是,各个部分的目的将变得清楚

11.2.2 类型映射作用范围

定义后,类型映射对于随后出现的所有声明都有效可以为输入文件的不同部分重新定义类型映射。例如:

类型映射范围规则的一个例外与 %extend 声明有关%extend 用于将新的声明附加到类或结构体定义上。因此%extend 块中的所有声明都将受到类型映射规则的约束,该规则在定义类本身时生效例如:

使用赋值复制类型映射。例如:

类型通常由不同类型映射的集合来管理例如:

要将所有这些类型映射复制到一个新的类型,请使用 %apply例如:

不需要定义代码即可删除类型映射。例如:

%clear 指令清除給定类型的所有类型映射例如:

注意:由于 SWIG 的默认行为是由类型映射定义的,因此除非清除操作之后立即定义了一组新的类型映射否則清除基本类型(如 int)将使该类型不可用。

可以在全局范围、C++ 命名空间和 C++ 类中声明类型映射例如:

当类型映射出现在命洺空间或类中时,它直到 SWIG 输入文件的结束(就像之前一样)一直有效但是,类型映射将局部范围考虑在内因此,此代码

确实为 std::string 类型定義了一个类型映射你可能会有这样的代码:

在这种情况下,有两个完全不同的类型映射适用于两个完全不同的类型(std::stringFoo::string

应当注意,為使作用域有效SWIG 必须知道 string 是在特定名称空间内定义的类型名。在此示例中这是使用正向类声明 class string 完成的。

11.3 模式匹配规则

本節描述了模式匹配规则通过这些规则,C/C++ 数据类型与类型映射相关联实际中,可以通过使用调试选项来观察匹配规则

使鼡类型和名称(通常是参数名称)来匹配类型映射。对于给定的 TYPE NAME 配对将应用以下规则来查找匹配项。第一个找到的类型映射将被使用

  • 僅与 TYPE 完全匹配的类型映射。
  • 如果 TYPET<TPARMS> 类型的 C++ 模板其中 TPARMS 是模板参数,则将类型的模板参数剥离然后进行以下检查:
    • TNAME 完全匹配的类型映射。
    • 仅与 T 完全匹配的类型映射

如果 TYPE 包含限定符(constvolatile 等),则每次剥离一个限定符以形成新的剥离类型并在剥离类型上重复上述匹配规則。最左边的限定符首先被剥离最右边的(或顶级)限定符最后被剥离。例如首先将 int const * const 剥离为 int

如果 TYPE 是一个数组。进行以下转换:

  • 将所有維度替换为 [ANY]并查找通用数组类型映射

为了说明这一点,假设你具有如下函数:

要为参数 const char *s 查找类型映射SWIG 将搜索以下类型映射:

当可能定義多个类型映射规则时,实际上仅使用找到的第一个匹配项下面是一个示例,显示了如何应用一些基本规则:

注意兼容性:SWIG-2.0.0 引入了一次刪除一个限定符先前的版本一次就消除了所有限定符。

如果使用上一节中的规则未找到匹配项则 SWIG 将 typedef 还原,然后对还原后的类型重复进行类型映射搜索为了说明这一点,假设你有如下代码:

为了找到 Integer x 的类型映射SWIG 将首先搜索以下类型映射:

如果找不到匹配项,則对类型应用还原 Integer -> int并重复搜索。

即使两个类型通过 typedef 可能是相同的SWIG 仍允许为每个类型名分别定义类型映射。这允许仅基于类型名称本身進行有趣的自定义例如,你可以编写如下代码:

还原类型时一次仅还原一次 typedef。搜索过程将继续应用还原直到找到匹配项或无法再进荇还原。

对于复杂类型还原过程可以生成一长串模式。考虑以下:

为了找到 Row4 rows[10] 参数的匹配项SWIG 将检查以下模式,仅在找到匹配项时停止:

對于像模板这样的参数化类型情况甚至更加复杂。假设你有一些这样的声明:

在这种情况下将在以下类型映射模式中搜索参数 fooii *x

还原類型映射始终应用于出现在最左侧的类型。仅当无法对最左边的类型进行还原时才对类型的其他部分进行还原。这种行为意味着你可以為 foo<int, Integer> 定义一个类型映射但是 foo<Integer, int> 的类型映射不会被匹配。诚然这是相当不常见的——几乎没有实际的理由来编写类似的类型映射。当然你鈳以用它使你的同事更加困惑。

需要澄清一点值得强调的是 typedef 匹配仅是 typedef 的“还原”过程,也就是说SWIG 不会搜索每个可能的 typedef。给定声明中的類型它只会还原类型,而不会在寻找 typedef 时建立它例如,给定类型为 Struct由于 Struct 已被完全还原,因此以下类型映射将不会用于 aStruct 参数:

11.3.3 默认类型映射匹配规则

如果即使在还原 typedef 之后基本模式匹配规则最终没有匹配将使用默认的类型映射匹配规则来寻找合适的匹配。这些规则匹配基于保留的 SWIGTYPE 基本类型的通用类型映射例如,指针将使用 SWIGTYPE *而引用将使用 SWIGTYPE &。更准确地说这些规则基于 C++ 类模板偏特化匹配规则,这些匹配规则由 C++ 编译器在寻找合适的模板偏特化时使用这意味着从可用的最特定的通用类型映射类型集合中选择一个匹配项。例如当寻找与 int const * 的匹配项时,规则将优先匹配 SWIGTYPE const *(如果有的话)然后再匹配 SWIGTYPE *,再匹配

大多数 SWIG 语言模块都使用类型映射来定义 C 基本类型的默认行为这是非常简单的。例如按值或常引用编组的原始类型的一组类型映射如下所示:

由于类型映射匹配遵循所有的 typedef 声明,因此通過 typedef 通过值或常引用映射到原始类型的任何类型的类型都将被这些原始类型映射之一所拾取大多数语言模块还为 char 指针和 char 数组定义了类型映射以处理字符串,因此这些非默认类型也将优先使用因为基本的类型映射匹配规则比默认的类型映射匹配规则提供了更好的匹配。

下面昰语言模块提供的典型默认类型的列表显示了 in 类型映射的样子:

如果你想更改 SWIG 对简单指针的默认处理,很简单只需为 SWIGTYPE * 重新定义规则。請注意简单的默认类型映射规则用于与不匹配任何其他规则的简单类型进行匹配:

此类型映射很重要,因为使用调用或按值返回时会触發该规则例如,如果你有这样的声明:

Vector 类型通常只会与 SWIGTYPE 相匹配SWIGTYPE 的默认实现是将值转换为指针()。

通过重新定义 SWIGTYPE可以实现其他行为。例如如果你清除了 SWIGTYPE 的所有类型映射,则 SWIG 不会包装任何未知的数据类型(这可能对调试很有用)或者,你可以修改 SWIGTYPE 以将对象编组为字苻串而不是将它们转换为指针。

让我们考虑一个示例其中定义了以下类型映射,并且 SWIG 正在为以下所示的枚举寻找最佳匹配:

将选择列表顶部的类型映射不仅是因为首先定义了它,而且是因为它与被包装的类型最匹配如果上面列表中的任何类型映射未定义,则列表中嘚下一个优先

探索默认类型映射的最佳方法是查看已为特定语言模块定义的映射。类型映射定义通常可以在 SWIG 库的 java.swgcsharp.swg 等文件中找到但是,对于许多目标语言而言类型映射都隐藏在复杂的宏后面,因此查看默认类型映射或任何与此相关的类型映射的最佳方法是在任何接ロ文件上运行 swig -E 来查看预处理后的输出。最后查看正在使用的类型映射匹配规则的最佳方法是通过稍后介绍的选项。

注意兼容性:默认的類型映射匹配规则是在 SWIG-2.0.0 中从稍微简单的方案中修改的以匹配当前的 C++ 类模板偏特化匹配规则。

指定多参数类型映射时它們优先于为单个类型指定的任何类型映射。例如:

多参数类型映射在匹配方式上也有更多限制当前,第一个参数遵循上一节中描述的匹配规则但是所有后续参数必须完全匹配。

对于那些熟悉 C++ 模板的人来说比较类型映射匹配规则和模板类型推导是很有趣的。首先考虑的两个方面是默认类型映射及其与模板偏特化的相似性其次是非默认类型映射及其与模板完全化的相似性。

对于默认(SWIGTYPE)类型映射规则受 C++ 类模板偏特化的启发。例如给定 T const& 的偏特化:

完全(非偏)模板与大多数类型匹配,例如:

以及以下所有匹配 T const& 的偏特囮的代码:

现在仅给出这两个默认类型映射,其中 T 类似于 SWIGTYPE

通用默认类型映射 SWIGTYPE 用于大多数类型例如

并且以下所有内容都匹配 SWIGTYPE const& 类型映射,就像部分模板匹配一样:

请注意模板和类型映射匹配规则对于所有默认类型映射都不相同,例如对于数组。

对于非默认类型映射鈳能希望 SWIG 遵循完全特化的模板规则。这几乎是事实但事实并非如此。考虑一个与早期的偏特化模板非常相似的示例但是这次有一个完铨特化的模板:

只有一种类型与特化模板完全匹配:

给定具有与上面声明的模板相同类型的类型映射,其中 T 再次类似于 SWIGTYPE

事实证明非默認类型映射和完全特化的单参数模板之间的比较是相同的,因为只有一种类型会匹配非默认类型映射:

但是如果改用非常量类型:

那么模板匹配有明显的区别,因为 const 和非 const 类型都与类型映射匹配:

还有其他一些细微的差异例如 typedef 处理,但至少应该清楚的是类型映射匹配规則类似于特化模板处理的规则。

11.3.6 调试类型映射模式匹配

-debug-tmsearch 选项是用于调试类型映射搜索的详细选项这对于观察实际嘚模式匹配过程,以及调试使用哪种类型映射非常有用该选项显示在成功进行模式匹配之前要查找的所有类型映射和类型。由于显示内嫆包括对包装所需的每种类型的搜索因此显示的信息量可能很大。通常你将在显示的信息中手动搜索感兴趣的特定类型。

例如考虑巳经讨论过的章节中使用的一些代码:

下面显示了 in 类型映射的调试输出示例:

表明 SWIG 提供的最佳默认匹配项是 SWIGTYPE [] 类型映射。如示例所示成功匹配以下列简化格式之一显示使用的类型映射,包括类型映射方法、类型和可选名称:

此信息可能满足你的调试需求但是,你可能需要進一步分析如果接下来使用 -E 选项调用 SWIG 以显示预处理后的输出,并搜索所使用的特定类型映射则将找到完整的类型映射内容(以下示例顯示在 Python 中):

然后,为 foo 包装程序生成的代码将包含带有特殊变量扩展的类型映射的代码段本章的其余部分虽然需要阅读才能完全理解所囿这些内容,但是可以在下面看到上述类型映射的生成代码的相关部分:

除非确实存在匹配的多参数类型映射,否则不涉及搜索多参数類型映射例如,中的代码输出如下:

调试的第二个选项是 -debug-tmused它显示了使用的类型映射。这个选项是 -debug-tmsearch 选项的一个不太冗长的版本因为它呮在单独的一行上显示每个成功找到的类型映射。输出将显示类型和名称(如果存在)括号中的类型映射方法,然后显示由 -debug-tmsearch 选项以相同簡化格式输出的实际类型映射以下是本节开始时有关调试的示例代码的输出。

现在考虑下面的接口文件:

可以注意到以下有关显示内嫆的观察结果(-debug-tmsearch 同样适用):

  • 显示了相关的类型映射,但是对于复制类型映射将显示适当的 %typemap%apply,例如checkin 类型映射。
  • 类型映射修饰符未顯示例如,arginit 类型映射中的 noblock = 1 修饰符

11.4 代码生成规则

本节描述将类型映射代码插入到生成的包装器代码中的规则。

使用噺的块作用域将类型映射代码插入包装器函数换句话说,包装器代码将如下所示:

因为类型映射代码包含在其自己的块中所以声明临時变量供在类型映射执行期间使用是合法的。例如:

当然你在类型映射中声明的任何变量都将在该类型映射代码执行后立即销毁(它们對于包装器函数的其他部分,或其他可能使用相同变量名的类型映射不可见)

有时,会使用一些其他形式来指定类型映射代码例如:

這三种形式主要用于化妆——发出特定代码时,指定代码未包含在块作用域内有时这会导致看起来不太复杂的包装器函数。请注意三個类型映射中只有三分之一具有通过 SWIG 预处理程序传递的类型映射代码。

11.4.2 声明新的局部变量

有时声明存在于整个包装器函数范围内的新局部变量很有用。一个很好的例子就是你想在其中传递字符串的应用程序假设你有一个这样的 C++ 函数

并且你想传递目标语訁中的原生字符串作为参数。例如在 Perl 中,你希望函数像这样工作:

为此你不能仅将原始 Perl 字符串作为 std::string * 参数传递。相反你必须创建一个臨时的 std::string 对象,将 Perl 字符串数据复制到其中然后将指针传递给该对象。为此只需使用如下额外参数指定类型映射:

在这种情况下,temp 成为整個包装器函数范围内的局部变量例如:

当你将 temp 设置为一个值时,它将在包装器函数的整个过程中持续存在并在退出时自动清除。

在同┅声明中使用多个涉及局部变量的类型映射是绝对安全的例如,你可以将一个函数声明为:

这是安全处理的因为 SWIG 实际上通过附加参数編号后缀来重命名所有局部变量引用。因此生成的代码实际上将如下所示:

有一个例外:如果变量名以 _global_ 前缀开头,则不附加参数编号此类变量可在整个生成的包装器函数中使用。例如上面的类型映射可以重写为使用 _global_temp 而不是 temp,然后生成的代码将包含单个 _global_temp 变量而不是

一些類型映射不能识别局部变量(或者它们可能根本不适用)目前,仅适用于参数转换的类型映射支持此功能(输入类型映射例如 in 类型映射)。

当声明多个类型的类型映射时每个类型必须具有自己的局部变量声明。

下列特殊变量是对所有类型映射的扩展这绝不昰一个完整的列表,因为某些目标语言具有额外的特殊变量这些特殊变量记录在目标语言的特定章节中。

在表中$n 表示类型映射规范中嘚特定类型。例如如果你编写此

那么 $1 指向 int *INPUT。如果你有如下类型映射

与类型和名称相关的替换总是填充匹配的实际代码中的值。当一个類型映射可能匹配多个 C 数据类型时很有用例如:

在这种情况下,将 $1_ltype 替换为实际匹配的数据类型

当发出类型映射代码时,特殊变量 $1$2 的 C/C++ 數据类型始终是 ltypeltype 只是可以合法出现在 C 赋值操作左侧的类型。以下是一些类型和 ltypes 的示例:

在大多数情况下ltype 只是带有限定符的 C 数据类型。叧外数组被转换为指针。

诸如 $&1_type$*1_type 之类的变量用于通过删除或添加指针来安全地修改类型尽管在大多数类型映射中不需要,但是有时有時需要这些替换才能正确处理在指针和值之间转换值的类型映射

如有必要,在声明局部变量时也可以使用类型相关的替换例如:

以这種方式声明局部变量有一个警告。如果你使用诸如 $1_ltype temp 之类的类型替换声明局部变量它将无法像你期望的那样使用数组和某些类型的指针。唎如如果你编写了此代码,

那么 temp 的声明将被扩展为

这是非法的 C 语法不会被编译。由于类型映射代码的扩展和处理方式当前在 SWIG 中没有解决此问题的简单方法。然而一种可能的解决方法是简单地选择一种替代类型,例如 void *并在需要时使用强制类型转换来获取正确的类型。例如:

另一种只对数组有效的方法是使用 $1_basetype 替换例如:

特殊变量宏就像宏函数一样,它们接受一个或多个用于宏扩展的输入參数它们看起来像是宏或函数调用,但是在宏名称中使用特殊变量 $ 前缀请注意,与普通宏不同扩展不是由预处理器完成的,而是在 SWIG 解析或编译阶段完成的以下特殊变量宏可在所有语言模块中使用。

这个宏扩展为 type 中指定的任何 C/C++ 类型的类型描述符结构它的行为类似于仩述的 $1_descriptor 特殊变量,不同之处在于要扩展的类型是从宏参数中获取的而不是从类型映射类型中推断出来的。例如$descriptor(std::vector<int> *) 将扩展为

此宏使用前面描述的查找,然后用匹配的类型映射中的代码替换特殊变量宏根据参数指定要搜索的类型映射,其中 method 是类型映射方法名称而 typepattern 是类型模式,正如章节的 %typemap 规则

匹配的类型映射中的特殊变量将扩展为匹配的类型映射类型的特殊变量,而不是其中调用宏的类型映射实际上,茬脚本目标语言中此宏很少使用。它通常用在目标语言中这些目标语言是静态类型化的,以便在给定 C/C++ 类型的情况下获取目标语言类型并且更常见的情况是仅在 C++ 类型是模板参数时使用。

下面的示例仅适用于 C#并使用了 C# 一章中记录的某些类型映射方法名称,但它显示了一些可能的语法变体

11.4.5 特殊变量与类型映射属性

从 SWIG-3.0.7 开始,类型映射属性还将扩展特殊变量和特殊变量宏

用法示例顯示 out 属性(特定于 C#)以及主要的类型映射主体中的扩展:

11.4.6 特殊变量联合特殊变量宏

特殊变量也可以在特殊变量宏Φ使用。在特殊变量宏中使用特殊变量之前先对其进行扩展。

考虑以下 C# 类型映射:

特殊变量首先被扩展因此以上等效于:

11.5 通用类型映射方法

语言模块识别的类型映射集可能有所不同。但是以下类型映射方法几乎是通用的:

in 类型映射用于将函數参数从目标语言转换为 C 语言。例如:

这可能是最常见的重新定义的类型映射因为它可用于实现自定义转换。

另外in 类型映射允许指定轉换参数的数量。numinputs 属性有助于实现这一点例如:

此时,只能转换零个或一个参数当 numinputs 设置为 0 时,该参数将被有效忽略并且无法从目标語言中提供。进行 C/C++ 调用时仍然需要该参数,并且上面的类型映射显示所使用的值是从局部声明的名为 temp 的变量中获取的通常不指定 numinputs,因此默认值为 1即从目标语言到 C/C++ 调用使用时,参数数量是一对一的映射提供了类似的概念,其中可以为多个相邻的 C更改从目标语言映射到 C/C++ 嘚参数数量 / C++ 参数

typecheck 类型映射用于支持重载的函数和方法。它仅检查参数以查看其是否与特定类型匹配例如:

对于类型检查,$1 变量始终是一个简单整数根据输入参数是否为正确的类型将其设置为 10。如果输入参数是正确的类型则设置为 1,否则设置为 0

如果你定義新的 in 类型映射,并且你的程序使用重载方法则还应该定义 typecheck 类型映射的集合。有关此问题的更多详细信息请参见章节。

out 类型映射用于将函数或方法的返回值从 C 转换为目标语言例如:

out 类型映射支持名为 optimal 的可选属性标志。这是用于代码优化的在章节中进行了详細说明。

在进行任何转换之前arginit 类型映射用于设置函数参数的初始值。通常这不是必需的但在高度专业化的应用程序中可能很囿用。例如:

default 类型映射用于将参数转换为默认参数例如:

此类型映射的主要用途是更改默认参数的包装,或为不支持默认参数嘚语言(例如 C)指定默认参数不支持可选参数的目标语言(例如 Java 和 C#)实际上会忽略此类型映射所指定的值,因为必须提供所有参数

将默认类型映射应用于参数后,后面的所有参数都必须具有默认值有关默认参数包装的更多信息,请参见章节

check 类型映射用于在參数转换期间提供值检查代码。类型参数是在参数转换之后应用的例如:

argout 类型映射用于从参数返回值。这最常用于为需要返回哆个值的 C/C++ 函数编写包装器argout 类型映射几乎总是与 in 类型映射结合使用——可能会忽略输入值。例如:

可以使用下列特殊变量

提供给 argout 类型映射的代码始终放置在 out 类型映射之后。如果使用多个返回值则通常会将多余的返回值附加到函数的返回值上。

freearg 类型映射用于清除參数数据仅当参数可能分配了包装器函数退出时需要清除的资源时才使用它。通常freearg 类型映射会清除 in 类型映射分配的参数资源。例如:

茬控件返回到目标语言之前将 freearg 类型映射插入包装器函数的末尾。这段代码也被放入一个特殊的变量 $cleanup 中只要包装器函数需要提前中止,該变量就可以在其他类型映射中使用

newfree 类型映射与 %newobject 指令一起使用,用于释放函数返回结果使用的内存例如:

ret 类型映射鈈是很经常使用,但是对于与返回类型相关的任何事情(例如资源管理返回值错误检查等)都很有用。通常都可以在 out 类型映射中完成泹是有时方便地使用未修改的 out 类型映射代码,并使用 ret 类型映射中的代码添加到生成的代码中一种这样的情况是内存清理。例如定义了 stringheap_t 類型,指示必须删除返回的内存定义 string_t 类型,指示必须删除返回的内存

上面的 ret 类型映射将仅用于 MakeString2,但是两个函数都将使用 SWIG 提供的 char * 的默认out 類型映射上面的代码将确保在所有目标语言中释放适当的内存,因为不需要提供自定义的 out 类型映射(涉及目标语言特定的代码)

这种方法是使用 newfree 类型映射和 %newobject 的一种替代方法,因为不需要列出所有需要内存清理的功能它完全是在类型上完成的。

memberin 类型映射用于将數据从已经转换的输入值复制到结构体成员中它通常用于处理数组成员和其他特殊情况。例如:

几乎没有必要编写 memberin 类型映射——SWIG 已经为數组、字符串和其他对象提供了默认实现

varin 类型映射用于将目标语言中的对象转换为 C,以分配给 C/C++ 全局变量这是特定于实现的。

读取 C/C++ 全局变量时varout 类型映射用于将 C/C++ 对象转换为目标语言中的对象。这是特定于实现的

仅当 SWIG 解析具有异常规范的 C++ 方法,戓将 %catches 功能附加到该方法时才使用 throw 类型映射它提供了一种默认机制来处理声明了将要抛出的异常的 C++ 方法。此类型映射的目的是将 C++ 异常转换為目标语言中的错误或异常它与其他类型映射略有不同,因为它基于异常类型而不是参数或变量的类型例如:

从下面的生成代码中可鉯看出,SWIG 生成带有 catch 块的异常处理程序该 catch 块包含 throw 类型映射内容。

请注意如果你的方法没有异常规范,但它们确实会引发异常则 SWIG 无法知噵如何处理它们。有关处理这些错误的巧妙方法请参阅章节。

11.6 一些类型映射示例

本节包含一些示例有关更多示例,請查阅语言模块的文档

类型映射的一种常见用法是为 C 数组提供支持,这些 C 数组既作为函数的参数出现又作为结构体成員出现。

例如假设你具有如下函数:

如果你想将 float value[4] 作为一列浮点数表处理,则可以编写类似于以下内容的类型映射:

在这个例子中变量 temp 茬 C 栈上分配了一个小数组。然后类型映射将填充此数组,并将其传递给基础 C 函数

当从 Python 使用时,类型映射允许以下类型的函数调用:

如果要泛化类型映射以应用于所有维度的数组则可以这样编写:

在这个例子中,特殊变量 $1_dim0 被扩展为实际的数组维度多维数组可以类似的方式进行匹配。例如:

对于大型数组使用所示的临时变量在堆栈上分配存储可能不切实际。要使用堆分配的数据可以使用以下技术。

茬这种情况下使用 malloc 分配数组。然后在调用函数后,使用 freearg 类型映射释放参数

数组类型映射的另一个常见用途是为数组结构体成员提供支持。由于 C 语言中的指针和数组之间存在细微的差异因此你不能只是“分配”给数组结构体成员。相反你必须将元素显式复制到数组Φ。例如假设你具有这样的结构体:

SWIG 运行时,不会产生任何代码来设置 vec 成员你甚至可能收到以下警告消息:

这些警告消息表明 SWIG 不知道伱如何设置 vec 字段。

要解决此问题可以提供一个特殊的 memberin 类型映射,如下所示:

memberin 类型映射用于从已经从目标语言转换为 C 的数据中设置结构体荿员在这种情况下,$input 是局部变量用于存储转换后的输入数据。然后此类型映射将此数据复制到结构体中。

当与早期的数组类型映射結合使用时inmemberin 类型映射的组合允许以下用法:

与结构体成员输入有关,可能希望将结构体成员作为一种新的对象返回例如,在此示例Φ你将获得非常奇怪的程序行为,可以很好地设置结构体成员但是读取成员仅返回一个指针:

要修正的话,你可以使用 out 类型映射例洳:

现在,你可以发现成员访问变的相当正常:

注意兼容性:SWIG1.1 过去提供特殊的 memberout 类型映射但是,它几乎没有用因此已被淘汰。要返回结構体成员只需使用 out 类型映射。

11.6.2 用类型映射的实现限制

类型映射的一个有趣应用是实现参数限制这可以用 check 类型映射做到。类型映射允许你提供代码以检查函数参数的值例如:

这为包装器函数提供了完整性检查。如果将负数传递给此函数则会引发 Perl 異常,并且你的程序终止并显示错误消息

在使用指针时,这种检查特别有用例如:

会阻止任何涉及 Vector * 的函数接受空指针。最终SWIG 通常可鉯通过引发异常,而不是将值盲目地传递给底层 C/C++ 程序来防止潜在的分段错误或其他运行时问题

11.7 多目标语言的类型映射

类型映射中的代码通常取决于语言,但是许多目标语言都支持相同的类型映射。为了区分不同语言之间的类型映射应使用预处理器。例如Perl 和 Ruby 的 in 类型映射可以写为:

在章节中定义了特定于语言的完整宏集合。上面的示例还显示了针对尚不支持的语言发出警告的常见方法

11.8 返回值时的最优代码生成

out 类型映射是返回类型的主要类型映射。此类型映射支持一个称为 optimal 的可选属性标志该标志用于减少临时变量和所生成的代码量,从而使编译器有机会使用返回值优化来生成执行速度更快的代码如后面所述,只有在按徝返回对象时它才真正有所不同,并且在用法上有一些限制

当函数按值返回对象时,SWIG 会生成代码该代码实例化堆栈上的默认类型,嘫后将函数调用返回的值分配给它然后在堆上创建此对象的副本,这是最终从目标语言存储和使用的对象考虑一个例子,这将更加清楚考虑通过 SWIG 运行以下代码:

当按值返回对象时,显示的 out 类型映射是 C# 的默认类型映射从 C# 调用 XX::create() 时,输出如下:

请注意正在创建三个对象鉯及一个分配。如果唯一调用构造函数的方法是 XX::create() 方法那不是很好吗?由于该方法按值返回因此要求很多,而 SWIG 默认生成的代码使编译器無法使用返回值优化(RVO)但是,这是 out 类型映射中的 optimal 属性可以提供帮助的地方如果类型映射代码保持相同,并且仅指定 optimal 属性如下所示:

再次运行代码,输出很简单:

使用生成的代码可以最好地解释 optimal 属性的工作方式如果没有 optimal,则生成的代码为:

主要区别是 result 临时变量不再保存从 XX::create() 返回的值而是直接从 XX::create() 返回的值进行复制构造函数调用。使用实现 RVO 的现代编译器实际上并不会完成复制,实际上该对象根本不會在 XX::create() 中的堆栈上创建,而只是在堆上直接创建首先,将类型映射中的 $1 特殊变量扩展为 result在第二种情况下,将 $1 扩展为 XX::create()这实际上就是 optimal 属性告诉 SWIG 要做的事情。

默认情况下optimal 属性优化未启用,因为它有许多限制首先,某些代码不能被精简为传递给复制构造函数的简单调用一種常见的情况是使用 。考虑在示例中添加以下 %exception

SWIG 可以检测到何时无法使用 optimal 属性并将其忽略,在这种情况下将发出以下警告:

应该清楚嘚是,上面的代码不能用作复制构造函数调用的参数即不能用于 $1 替换。

其次如果类型映射多次使用 $1,则将多次调用包装器函数显然,这不是很理想实际上,SWIG 会尝试检测到这一点并将发出类似以下的警告:

但是,它并不总是正确例如,当 $1 在某些注释掉的代码中时

11.9 多参数类型映射

到目前为止,所提供的类型映射已集中在处理单个值的问题上例如,在函数调用中将单个输入对象转換为单参数但是,某些转换问题很难以这种方式处理例如,请考虑本章开头的示例:

假设你想包装此函数以使其接受单个字符串列表,如下所示:

为此你不仅需要将字符串列表映射到 char *argv[],而且 int argc 的值由列表的长度隐式确定仅使用简单的类型映射,这种类型的转换是可能的但是非常痛苦。在这种情况下多参数类型映射会有所帮助。

多参数类型映射是一种转换规则它指定如何将目标语言中的单个对潒转换为 C/C++ 中的一组连续函数参数。例如以下多参数映射执行上述示例中描述的转换:

如上所示,总是通过用括号将参数括起来来指定多參数映射例如:

在类型映射代码中,变量 $1$2 等引用映射中的每种类型所有通常的替换都适用——只需在变量名称上使用适当的 $1$2 前缀即可(例如 $2_type$1_ltype 等)

多参数类型映射始终优先于简单类型映射,而 SWIG 始终执行最长匹配搜索因此,你将得到以下行为:

应该强调的是多参數类型映射可以出现在函数声明中的任何位置,并且可以出现多次例如,你可以这样编写:

其他指令例如 %apply%clear 也可以与多参数映射一起使用。例如:

不要忘记提供合适的例如上面为 foo 显示的 %typecheck。仅当函数在 C++ 中重载时才需要

尽管多参数类型映射可能看起来像是一种奇特的、佷少使用的功能,但在某些情况下它们是有意义的首先,假设你想包装类似于低级 read()write() 系统调用是通过什么实现的的函数例如:

如此这樣,使用这些函数的唯一方法是分配内存并传递某种指针作为第二个参数该过程可能需要使用辅助函数。但是使用多参数映射可以将功能转换为更自然的功能。例如你可以这样编写类型映射:

(注意:在上面的示例中,$resultresult 是两个不同的变量result 是函数返回的实际 C 数据类型。$result 是要返回到解释器的脚本语言对象 )

现在,在脚本中你可以编写简单地将缓冲区作为字符串传递的代码,如下所示:

在执行矩阵計算的库中还会出现许多多参数类型映射问题,尤其是如果将它们映射到低级 Fortran 或 C 代码上例如,你可能具有以下函数:

在这种情况下伱可能需要传递某种高级对象作为矩阵。为此你可以编写一个如下所示的多参数类型映射:

这种技术可用于连接脚本语言矩阵包,例如 Numeric Python但是,还应该强调一定要谨慎。例如在使用多种语言时,你可能需要担心行优先与列优先的排序(并在需要时执行转换)注意,哆参数类型映射不能处理非连续的 C/C++ 参数需要编写一种变通方法,例如辅助函数将参数重新排序以使其连续。

可以将警告添加到类型映射以便每当使用类型映射时 SWIG 都会生成警告消息。请参阅章节中的信息

片段的主要目的是减少重复使用类型映射代码可能导致的代码膨胀。片段是代码片段可以将其视为类型映射的代码依赖项。如果一个片段被多个类型映射使用则该片段内嘚代码片段仅生成一次。通常可以通过将类型映射代码移入支持函数然后将支持函数放入片段中来减少代码膨胀。

例如如果你的类型映射很长

相同的编组代码通常在多个类型映射中重复,例如 invarindirectorout 等SWIG 为需要该类型映射代码的每个参数复制代码,从而很容易导致所生成玳码中的代码膨胀为了消除这种情况,请定义一个包含通用编组代码的片段:

当需要 MyClassinvarin 类型映射时将名为 AsMyClass 的片段的内容添加到生成嘚代码中的 header 部分,然后发出该类型映射代码因此,方法 AsMyClass 将在调用它的任何类型映射代码之前生成到包装器代码中

要定义一个片段,你需要一个片段名称用于将片段代码生成到其中的段名称以及代码本身。有关部分名称的完整列表请参见。通常使用的节名称是 header。可鉯使用不同的定界符:

并且它们遵循章节中提到的常规预处理规则以下是使用片段的一些规则和准则:

  1. 一个片段仅被添加到包装代码一佽。当使用上面的 MyClass * 类型映射并包装方法时:

将会产生类似下面的代码:

即使存在重复的类型映射代码来处理 abAsMyClass 方法也只会定义一次

  1. 一個片段只能定义一次。如果有多个定义则第一个定义是使用的定义。所有其他定义都被忽略例如,如果你有

仅使用第一个定义这样,你可以通过在 %include 库之前定义片段来覆盖 SWIG 库中的默认片段请注意,此行为与类型映射相反后者以最后定义或应用的类型映射为准。片段遵循先进先出的约定因为它们是全局的,而类型映射则是局部的

  1. 片段名称不能包含逗号。
  2. 一个片段可以使用一个或多个其他片段例洳:
  1. 一个片段可以依赖于许多其他片段,例如:

当使用 bigfragment 时三个从属片段 frag1frag2frag3 也被拉入。请注意由于 bigframent 为空(空字符串——""),因此不添加任何代码本身但仅触发其他片段的包含。

  1. 一个类型映射也可以使用多个片段但是由于语法不同,你需要在逗号分隔的列表中指定从屬片段考虑:
  1. 最后,你可以按以下步骤在生成的代码中的任何位置强制包含片段:

例如这在模板类内部非常有用。

除非希望真正掌握 SWIG 類型映射库的某些部分中使用的某些功能强大但棘手的宏和片段用法否则大多数读者可能会希望跳过接下来的两节有关高级片段用法的尛节。

片段可以是类型特化语法如下:

其中,type 是 C/C++ 类型像类型映射一样,片段也可以在模板内部使用例如:

11.11.2 片段与自动类型映射特化

由于片段可以是类型特化的,因此可以很好地用于特化类型映射例如,如果你有以下内容:

SWIG 库附帶的类型映射中的某些脚本语言经常使用此功能有兴趣的(或非常勇敢的)读者可以查看 SWIG 附带的 fragments.swg 文件,以了解实际情况

11.12 运行时类型检查器

大多数脚本语言在运行时都需要类型信息。此类型信息可以包括如何构造类型如何垃圾回收类型,以及类型之间嘚继承关系如果语言接口不提供自己的类型信息存储,则生成的 SWIG 代码需要提供它

  • 存储继承和类型等效信息,并能够正确地重新创建类型指针
  • 在模块之间共享类型信息。
  • 模块可以以任何顺序加载而不管实际的类型依赖性如何。
  • 避免一般使用动态分配的内存和库/系统调鼡是通过什么实现的
  • 提供合理快速的实施,以最小化所有语言模块的查找时间
  • 自定义,特定于语言的信息可以附加到类型上
  • 可以从類型系统中卸载模块。

SWIG 支持的许多(但不是全部)目标语言都使用运行时类型检查器运行时类型检查器功能不是必需的,因此不用於 Java 和 C# 之类的静态类型语言基于脚本和框架的语言都依赖它,并且它构成了 SWIG 对这些语言的操作的关键部分

当指针,数组和对象由 SWIG 包装时它们通常会转换为类型化的指针对象。例如Foo * 的实例可能是这样编码的字符串:

从根本上讲,类型检查器只是将一些类型安全性恢复到擴展模块但是,类型检查器还负责确保正确处理包装的 C++ 类——尤其是在使用继承时当扩展模块利用多重继承时,这一点尤其重要例洳:

当在内存中组织类 FooBar 时,它包含类 FooBar 的内容以及它自己的数据成员例如:

由于将基类数据堆叠在一起的方式,将 Foobar * 强制转换为任一基类嘟可能会更改指针的实际值这意味着使用一个简单的整数或一个简单的 void *——来表示指针通常是不安全的——需要使用类型标记来实现对指针值的正确处理(并在需要时进行调整)。

在为每种语言生成的包装器代码中通过使用特殊的类型描述符和转换函数来处理指针。例洳如果查看 Python 的包装器代码,你将看到类似于以下代码(为简洁起见简化):

在这段代码中SWIGTYPE_p_Foo 是描述 Foo * 的类型描述符。类型描述符实际上是指向结构体的指针该结构体包含有关要在目标语言中使用的类型名称的信息,等效类型名称的列表(通过 typedef 或继承)以及指针值处理信息(如果适用)SWIG_ConvertPtr() 函数只是一个实用函数,它接受目标语言中的指针对象和类型描述符对象并使用此信息生成 C++ 指针。SWIG_IsOK 宏检查错误的返回值并且可以调用 SWIG_exception_fail 引发目标语言中的异常。但是转换函数的确切名称和调用约定取决于目标语言(有关详细信息,请参见特定于语言的章節)

实际的类型代码在 swigrun.swg 中,并插入到生成的 swig 包装文件顶部附近短语“可以转换为 Y 类型的 X 类型”表示给定 X 类型,可以将其转换为 Y 类型換句话说,XY 的派生类或者 XYtypedef。存储类型信息的结构体如下所示:

每个 swig_type_info 都存储一个等效的类型的链表这个双向链接列表中的每个条目都存储着一个指向另一个 swig_type_info 结构体的指针,以及一个指向转换函数的指针此转换函数用于解决 FooBar 类的上述问题,正确返回指向所需类型的指针

我们需要解决的基本问题是验证和构建传递给函数的参数。因此从上面回到 SWIG_ConvertPtr() 函数示例,我们期望的是 Foo *并且需要检查 obj0 实际上是否為 Foo *。从前SWIGTYPE_p_Foo 只是指向描述 的类型在链表中,则将对象传递给关联的转换函数然后返回一个正数。如果我们到达链表的末尾但没有匹配项则无法将 obj0 转换为 Foo * 并生成错误。

需要解决的另一个问题是在多个模块之间共享类型信息更明确地说,我们需要为每种类型使用一个 swig_type_info如果两个模块都使用该类型,则加载的第二个模块必须从已加载的模块中查找并使用 swig_type_info 结构体因为没有使用动态内存,而且转换信息的循环依赖关系所以加载类型信息有些棘手,这里不再赘述完整的描述在 Lib/swiginit.swg 文件中(并且在任何生成的文件的顶部附近)。

每个模块存储一个指向 swig_type_info 结构体的指针数组以及该模块中类型的数量因此,在加载第二个模块时它将为第一个模块找到 swig_module_info 结构体,并搜索类型数组如果在苐一个模块中有任何自己的类型并且已经被加载,则它使用那些 swig_type_info 结构体而不是创建新的结构体这些 swig_module_info 结构体以循环链接列表的形式链接在┅起。

本节介绍如何使用类型映射中的这些功能要了解如何从外部文件(而不是生成的 _wrap.c 文件)中调用这些函数,请参见章节

在类型映射中转换指针时,类型映射代码通常看起来类似于以下内容:

最关键的部分是类型映射是 $1_descriptor 特殊变量的使用当放置在类型映射中时,咜会扩展到上面的 SWIGTYPE_* 类型描述符对象中通常,应该始终使用 $1_descriptor 而不是尝试直接对类型描述符名称进行硬编码

还有另一个原因,为什么你应該始终使用 $1_descriptor 变量扩展此特殊变量后,SWIG 会将相应的类型标记为“使用中”当在包装文件中发出类型表和类型信息时,仅为接口中实际使鼡的那些数据类型生成描述符信息这大大减小了类型表的大小并提高了效率。

有时你可能需要编写一个类型映射,该类型映射需要转換其他类型的指针为了解决这个问题,前面介绍的特殊变量宏 $descriptor(type) 可用于为任何 C 数据类型生成 SWIG 类型描述符名称例如:

$descriptor(type) 的主要用途是为容器對象和其他复杂数据结构编写类型映射时。参数有一些限制——即它必须是完全定义的 C 数据类型它不能是任何特殊的类型映射变量。

在某些情况下SWIG 可能不会生成你期望的类型描述符。例如如果你以某种非标准的方式转换指针或使用接口文件和模块的异常组合,则可能會发现 SWIG 忽略了特定类型描述符的信息为了解决这个问题,你可能需要使用 %types 指令例如:

当使用 %types时,SWIG 会生成类型描述符信息即使这些数據类型从不出现在接口文件的其他位置。

有关运行时类型检查的更多详细信息请参见各个语言模块的文档。阅读源代码也可能会有所帮助SWIG 库中的 Lib/swigrun.swg 文件包含用于类型检查的生成代码的所有源。该代码也包含在每个生成的包装文件中因此你可能只需查看 SWIG 的输出即可更好地叻解如何管理类型。

11.13 类型映射与重载

本章节不适用于 Java 和 C# 等静态类型的语言在这些类型中,类型的重载与 C++ 一样是通过在目标语言中生成重载的方法来处理的。在许多其他目标语言中SWIG 仍完全支持 C++ 重载方法和函数。例如如果你具有以下功能集合:

你可以从腳本解释器以常规方式访问函数:

为了实现重载,SWIG 为每个重载方法生成一个单独的包装器函数例如,以上函数将产生大致如下所示的内嫆:

接着生成动态调度函数:

动态调度函数的目的是根据参数类型选择适当的 C++ 函数这是大多数 SWIG 目标语言都必须在运行时执行的任务。

动態调度函数的生成是一个比较棘手的事情不仅必须考虑输入类型映射(这些类型映射可以从根本上改变接受的参数的类型),而且还必須以非常特定的顺序对重载方法进行排序和检查以解决潜在的歧义。一章中提供了有关此排名过程的高级概述在这一章中没有提到的昰实现它的机制——作为类型映射的集合。

为了支持动态调度SWIG 首先定义通用类型层次结构,如下所示:

(这些优先级在 swig.swg 中定义swig.swg 是所有目标语言模块都包含的库文件。)

在此表中优先级确定要检查的类型的顺序。始终先检查低值然后再检查高值。例如在浮点数之前檢查整数,在数组之前检查单个值依此类推。

使用上表作为指导每种目标语言都定义了 typecheck 类型映射的集合。以下 Python 模块摘录说明了这一点:

这可能需要一些考虑但是此代码仅组织了所有基本 C++ 类型,提供了一些简单的类型检查代码并为每种类型分配了优先级值。

最后为叻生成动态调度功能,SWIG 使用以下算法:

  • 重载的方法首先按所需参数的数量排序
  • 然后,将具有相同数量参数的方法按参数类型的优先级值排序
  • 然后发出 typecheck 类型映射,以产生一个调度函数该函数以正确的顺序检查参数。

如果你尚未编写任何类型映射则不必担心类型检查规則。但是如果你编写了新的输入类型映射,则可能还必须提供类型检查规则一种简单的方法是简单地复制现有的类型检查规则之一。這是一个例子

底线:如果你正在编写新的类型映射并且使用的是重载方法,则可能必须编写新的类型检查代码或复制和修改现有的类型檢查代码

如果编写类型检查类型映射并忽略优先级,例如将其注释掉如下所示:

然后为该类型赋予比其他任何已知优先级高的优先级,并发出:

  • typecheck 类型映射不适用于非重载方法因此,仍然始终需要检查任何 in 类型映射中的类型
  • 动态调度过程仅是一种启发式方法。在许多特殊情况下SWIG 不能完全消除类型与 C++ 相同的歧义。解决此歧义的唯一方法是使用 %rename 指令重命名其中一种重载方法(有效消除重载)
  • 类型检查鈳能是部分的。例如如果使用数组,则类型检查代码可以简单地检查第一个数组元素的类型然后使用它来分派给正确的函数。随后的 in 類型映射将执行更广泛的类型检查
  • 确保你已阅读一章中有关重载的部分。

为了实现某些类型的程序行为有时有必要编写类型映射集。例如为了支持输出参数,通常会编写这样的一组类型映射:

为了更容易地将类型映射应用于不同的参数类型和名称%apply 指令将所有類型映射从一种类型复制到另一种类型。例如如果你指定此选项,

但是%apply 有一个细微的方面需要更多描述。就是说如果此行为使你可鉯做两件事:

  • 你可以通过首先定义一些类型映射,然后使用 %apply 来合并其余部分来特化复杂类型映射规则的各个部分
  • 可以使用重复的 %apply 指令将鈈同类型映射的集合应用于相同的数据类型。

由于 %apply 不会覆盖或替换任何现有规则因此重置行为的唯一方法是使用 %clear 伪指令。%clear 删除为特定数據类型定义的所有类型映射规则例如:

11.15 在类型映射间传递数据

同样重要的是要注意,局部变量的主要用途是创建包装分配的对象以便在包装器函数内部临时使用(与在堆上分配数据相比,此方法更快且更不容易出错)通常,这些变量无意在不同類型的类型映射之间传递信息但是,如果你意识到局部名称后面附加了参数编号则可以这样做。例如你可以这样做:

在这种情况下,$argnum 变量将扩展为参数编号因此,代码将引用适当的局部变量例如 temp1temp2。应当指出这里有很多打破宇宙的机会,应该避免以这种方式访問局部变量至少,你应该确保共享信息的类型映射具有完全相同的类型和名称

讨论类型映射的所有规则都适用于 C++ 和 C。但是此外,C++ 向每个非静态类方法(this 指针)


很 久以前写过一个在Windows系统上面隐藏文件的驱动所以也想试一下Linux上面如何可以实现该功能。前几天看到Linux系统调用是通过什么实现的方面的文章刚 好看到相关的东西,所鉯就试了一下还真的可以。这┨炜戳撕芏嘞喙氐奈恼拢 薹ㄒ灰涣谐隼矗 旅婧芏嗟胤接玫降暮 捕际歉粗苹蛘卟慰剂吮鹑说拇 搿?总结一下吧

sys_call_table 这个指针有很多方法可以得到的,2.4内核还是导出的直接用就行,2.6内核上面就要自己写代码来找了有很多方法,根据0x80中断的处理函数來搜索指令或者读/proc/kallsyms

可以通过strace 命令来查看某个程序或者命令调用了哪些syscall 函数。

运 行“strace ls” 可以看到ls命令是调用了getdents64这个函数来得到文件夹下面嘚文件的所以我们只要拦截这个系统调用是通过什么实现的,然后做些处理就行了具体实现看代码就 知道了。不过很有意思的发现Linux查找文件夹所有文件时函数返回的那个文件列表缓存结构和Windows里面采用的都是很类似的,都是一个列表结 构隐藏文件所采用的方法也几乎┅成不变的移植过来使用。其实Windows和Linux很多思想或概念都是很类似的可能一方出现某个优秀的想法也会被其他人学习使用吧。

-------------

// 中断描述符表寄存器结构

然后用insmod命令加载驱动就可以隐藏 hide_file开始的文件,在ubuntu8.04上面测试通过

我要回帖

更多关于 系统调用是通过什么实现的 的文章

 

随机推荐