原文: http://sofia-sip.sourceforge.net/refdocs/programming.html
编写跨平台代码
大部分Sofia-SIP软件都是跨平台的。所有的核心模块(或最低要求)都是用ANSI C89加上一点ANSI C99标准编写。如果有特定平台代码,都会放到特定的c源文件,用wrapper方法跟其他代码分开。SU模块就是把平台相关的功能做了抽象处理,比如内存管理,sockets,线程和时间函数。
ANSI C99特性
Sofia-SIP代码使用了的ANSI C99特性:
- 定长整数
- va_copy() 和 snprintf() 函数
不能再Sofia-SIP代码中使用的 ANSI C99特性:
- 代码和变量定义交叉。(又翻译:再函数代码中间定义变量;即必须把定义放在函数开始)
定长整数类型
总所周知,整数长度取决于硬件,系统和编译器。这就意味着int或long不一定是32位。导致编码时,程序员难以确保int类型能保存目标值而没有溢出。
如果你能保证不溢出,使用int类型是没问题。最开始只有int类型的原因是效率考量,int是存储地最快(同时也是最大的长度)。
千万不要假设任何类型的长度,而是用sizeof()。
ANSI C99标准定义了以下定长整数
- int64_t
- uint64_t
- int32_t
- uint32_t
- int16_t
- uint16_t
- int8_t
- uint8_t
使用这些类型,你必须include <sofia-sip/su-types.h>,它处理好了正确的具体包含。如果su模块不可用,你必须在源码文件中,使用下面的代码段获取正确的include:
#if HAVE_STDINT_H
#include <stdint.h>
#elif HAVE_INTTYPES_H
#include <inttypes.h>
#else
#error Define HAVE_STDINT_H as 1 if you have <stdint.h>, \
or HAVE_INTTYPES_H if you have <inttypes.h>
#endif
字节序
主机字节序在不同平台不一样。当你只在本地处理数据,不需要考虑字节序。如果你要跟网络打交道,你就需要考虑了。
如果你想转换字节序,以下的函数可以调用:
- htonl() 主机 => 网络,参数:ulong
- htons() 主机 => 网络,参数:ushort
- ntohl() 网络 => 主机,参数:ulong
- ntohs() 网络 => 主机,参数:ushort
你需要include <netinet/in.h>
或 include <sofia-sip/su.h>
自定义类型对其(align)
默认情况下,编译会对齐结构体,以保证快速访问。这意味着大部分字段(field)都按32位对齐。如果你优化内存,你可能需要结构体(struct)对齐。
位了告诉编译器,你的一些字段仅需部分位(少于内建类型长度),你可以使用位域。
struct foo {
unsigned bar:5;
unsigned foo:2;
unsigned :0;
int something;
}
如果编译器决定pack这个结构体,bar,foo在开始的7位,something 从下一个32位开始。
位域有个问题:在ARM,不能访问非32位开始的32位字段,所以例子中 有个 :0
对齐的字段,警告:这个结构体在一些ARM gcc编译器上,可能初始化失败。(Kai Vehmanen 知道详细)
一个强制编译器对齐结构体的方式是,使用预编译指令 #pargma(pack)
. 这个指令是编译器自定的,所以如果要编写跨平台的代码,就不能使用它,即使在Sofia-SIP中的一些地方使用了它。另一个方案是编写特定函数去从32位字段中获取特定位,但不太方便,容易出错。
同样的对齐问题同样发生在转换char buffer 到 int32_t。在ARM平台,你只能从完整的32位数据中读int32_t。
一句话:使用位域和结构体对齐时,小心跨平台陷阱。如果不是必须,别用。
文件和目录结构
一个Sofia-SIP模块放在libsofia-sip-ua
目录下,并且包含了<modulename>.docs
(<modulename>
当然就是模块名)。
如果你想开发一个新模块,联系Sofia-SIP开发组,他们会帮你创建一个基本的模块
模块概览:
-
<modulename>.docs
文档 详细请查看 Module documentation in <module>.docs - pictures
文档中包含的图片,文件格式为GIF(html页面使用)和EPS(latex使用)。如果其他程序生成的图片,必须包含源文件。 - Makefile.am
详细请看 dealing with GNU Autotools - (可选)源代码和测试,源码同样可以放在子文件夹
编写面向对象代码
虽然C没有提供任何面向对象特性,仍能按照OO方式写代码。Sofia-SIP代码使用了很多OO特性。
数据隐藏(Data Hiding)
数据隐藏能是两个模块分离的很清晰。外部调用无法直接访问内部数据,但是能使用提供的方法。数据隐藏同样让两个对象之间的交互变得简单,所有交互都通过函数调用。
那C如何实现数据隐藏呢?最简单的方式是,在头文件中,只声明结构,不定义他们。Sofia-SIP中,我们在<sofia-sip/msg.h>
定义了typedef struct msg_s msg_t
, 但是类型的定义在 msg_internal.h
中。在msg模块外部就没法访问msg_t内部变量,但是能访问<sofia-sip/msg.h>
中声明的函数。msg_t的定义同样能够自由的改变,只要接口(<sofia-sip/msg.h>
中声明的函数)不变。
接口(Interface)
Sofia-SIP同样使用了抽象结构,消息头解析,定义在<sofia-sip/msg_types.h>
。消息头类型msg_header_t
是用两个C类型定义的: struct msg_common_s
和 struct msg_hclass_s
。
抽象结构使用在msg_hclass_t
定义的虚函数表实现的,非常像c++实现的抽象类和虚函数。为了实现每一个头,函数表定义了函数指针去实现具体功能。不像C++,对象类(msg_hclass_t
)是由真实结构表示的。
sip_contact_class[] =
{{
/* hc_hash: */ sip_contact_hash,
/* hc_parse: */ sip_contact_d,
/* hc_print: */ sip_contact_e,
/* hc_dxtra: */ sip_contact_dup_xtra,
/* hc_dup_one: */ sip_contact_dup_one,
/* hc_update: */ sip_contact_update,
/* hc_name: */ "Contact",
/* hc_len: */ sizeof("Contact") - 1,
/* hc_short: */ "m",
/* hc_size: */ MSG_ALIGN(sizeof(sip_contact_t), sizeof(void*)),
/* hc_params: */ offsetof(sip_contact_t, m_params),
/* hc_kind: */ msg_kind_append,
/* hc_critical: */ 0
}};
继承
Sofia较少的使用了继承。最常见的使用inheritance是su_home_t
。许多对象都继承至su_home_t
, 这就意味着他们能使用home-based内存管理函数,在<su_alloc.h>
声明。
这个场景中,继承意味着指向子类的指针能强转(cast)为指向基类的指针。也就是说,子类对象必须在开始处定义基类对象:
struct derived
{
struct base base[1];
int extra;
char *data;
};
因此有三种办法把指向子类的指针指向基类:
struct base *base1 = (struct base *)derived;
struct base *base2 = &derived->base;
struct base *base3 = derived->base;
第三个能够通过编译器是因为base是一个单元素数组。
模板
Sofia-SIP使用宏实现了模板的功能。包括:
- hash table
定义在<sofia-sip/htable.h>
- red-black tree
定义在<sofia-sip/rbtree.h>
内存管理
home-based 内存管理机制,在需要分配许多内存块的情况下非常有用。分配器是通过分配中心保存各个分配内存块的引用来实现的。当分配中心释放,所有它保持引用的内存块都会被释放。
请查看<sofia-sip/su_alloc.h>和memory managment tutorial查看更多内容。
上下文内存管理
典型的例子就是使用su_home_t
作为操作上下文的一部分
struct context {
su_home_t ctx_home[1]; /* memory home */
other_t *ctx_other_stuff; /* example of memory areas */
...
};
/* context pointer */
struct context *ctx;
/* Allocate memory for context structure and initialize memory home */
ctx = su_home_clone(NULL, sizeof (struct context));
/* Allocate memory and register it with memory home */
ctx->ctx_other_stuff = su_zalloc(ctx->ctx_home, sizeof(other_t));
... processing and allocating more memory ...
/* Release registered memory areas, home, and context structure */
su_home_zap(ctx->ctx_home);
Combining allocations
Another place where home-based memory management makes programmers life easier is case where a sub-procedure makes multiple memory allocations and, in case the sub-procedure fails, all the allocations must be released and, in case the sub-procedure is succesfull, all allocations must be controlled by upper level memory management.
int sub_procedure( su_home_t *top_home, ... )
{
su_home_t temphome[1] = { SU_HOME_INIT(temphome) };
... allocations and other processing ...
/* was processing successfull ? */
if (success) {
/* ok -> move registered allocated memory to upper level memory home */
su_home_move( top_home, temphome );
}
/* destroy temporary memory home (and registered allocations) */
/* Note than in case processing was succesfull the memory */
/* registrations were already moved to upper level home. */
su_home_deinit(temphome);
/* return ok/not-ok */
return success;
}
网友评论