 | 级别: 中级 牛 亚文 (niuyawen@cn.ibm.com), 软件工程师, IBM 中国软件开发中心
2008 年 1 月 15 日
Lotus C API Toolkit 是 Notes/Domino 公布出来的一系列基于 Notes/Domino 的 API 编程接口和数据结构,用户可以用来开发自主应用程序来访问 Domino 的数据库,在 Domino 二次开发中被广泛的应用。Lotus C API 的内存管理机制是其核心内容之一,是开发高效实用的 Domino 应用程序的必备知识。
本文主要详细介绍了 Domino 数据库在内存中的存贮结构,以及 Lotus C API 提供的有关内存管理的常用数据结构和 API 接口,并通过实例说明如何遵循其内存管理机制进行高效实用的应用程序开发。
Domino 数据库模型
在介绍 Lotus C API 内存管理机制之前,首先来了解一下典型的 Domino 数据库的存贮模型(如图 1)。一个 Domino 的数据库由数据库的头信息和多个 note 数据结构组成。数据库头信息包括 title, categories, class, design class, ID, Replica info 等,用于描述该数据库的相关属性。头信息之后是多个 note 数据结构。note 是 Domino/Notes 用来存贮和各种信息的通用数据结构。note 可以分为 design notes (forms, views, folders, navigators, outlines, pages, framesets, agents, and resources), data documents, profiles documents, access control list 和 collections (indexes)。一个 note 又可以分为 note 头信息和多个 item。note 头信息包括 database handle, note ID, originator ID, note class, note flag 等。note 中的 item 由多个属性,如 item name, type, length 等,以及存贮的数据信息 content 组成。
如图 1 所示,Lotus C API Toolkit 使用了“容器”(container)模型来构造 Domino 的数据库存贮模型:一个 Domino 数据库可以包含多个 notes,每个 note 又包含多个 item,以此类推。关闭一个 container 对象的时候会自动地关闭和释放包含在该 container 内的所有对象。例如,如果你调用 Lotus C API 打开一个 database,并在该 database 中创建了一个 document,当关闭该 database 的时候,创建的 document 也会被关闭并释放其存贮空间。因此,特别注意的是,在关闭容器对象之前,一定要保存包含在该容器中的对象。此外,当调用 Lotus C API 函数 NotesTerm 结束一个 Notes session 时,所有在该 seesion 周期内分配的内存会全部释放。
图 1. Domino 数据库模型
note 结构是 Lotus C API 中存取 Domino 数据库元素的基本单位。例如,当程序员想读取一个 document 的某个属性的时候,首先必须将整个 document( 每个 document 都是一个 note) 读入内存。同理,当更新 document 的属性时,必须将整个内存中的 document 结构重新写回到硬盘。
BLOCKID—Domino 的“内存块”
除 note 结构之外,BLOCKID 是 Domino 管理内存的另一种数据结构,代表一个 Domino 的“内存块”。Lotus C API 提供了一系列的 API 函数(如 NSFItemInfo 等)可以将 Domino 数据库的数据读取到“内存块”中,再调用其它 API(如 NSFItemAppendByBLOCKID)对 block 中的数据进行操作。
BLOCKID 的定义如下:
typedef struct {
HANDLE pool; /* pool handle */
BLOCK block; /* block handle */
} BLOCKID;
|
其中,数据成员 pool 用于存贮分配的“内存块”的句柄。该 pool 指向的“内存块”可以被划分成多个“block”。成员 block 用于存贮该 BLOCKID 在 pool 指向的“内存块”中的偏移地址。
通过以下步骤可以将数据读取到“内存块”中,再将该 BLOCKID 作为输入传递给其它 Lotus C API 函数作后续处理:
- 声明一个
BLOCKID 类型的变量;
- 调用
OSMemAlloc 获得一个“内存块”句柄;
- 将获得的句柄存贮到
BLOCKID 的 pool 成员中;
- 将
BLOCKID 的 block 成员设成 NULLBLOCK;
- 调用
OSLockBlock 获得指向该 BLOCKID 的指针;
- 将数据写入指针指向的内存;
- 将该
BLOCKID 传递给其它 Lotus C API 函数之前调用 OSUnlockBlock 释放 BLOCKID。
相反,通过以下步骤可以将 Lotus C API 返回的数据结果写到“内存块”。
- 声明一个
BLOCKID 类型的变量;
- 调用相关的 Lotus C API 函数将指针传递给
BLOCKID;
- 调用
OSLockBlock 创建一个指向 BLOCKID 数据的指针;
- 使用刚创建的指针进行数据处理;
- 调用
OSUnlockBlock 释放 BLOCKID。
后面会有对“内存块”使用方式更详细的介绍,以及具体实例分析。
使用 note 数据结构进行内存管理
正如我们前面讲到的,note 是大多数 Lotus C API 使用的一种基本的 in-memory 数据结构,用于读 / 写 Domino 的数据库信息。下面我们将介绍 Lotus C API 如何使用 note 数据结构进行内存管理的机制。。
Lotus C API 提供了一系列的函数实现了基于 note 数据结构的内存管理方式。我们可以将这些函数分成三类 :
1. 基于 note 的内存分配函数集
Lotus C API 提供了一系列的基于 note 数据结构的内存分配函数,最常用的有 NSFNoteOpen, NSFNoteOpenExt, NSFNoteCreate 和 NSFNoteCopy 等。调用这类函数可以创建一个内存 note。
NSFNoteOpen 和 NSFNoteOpenExt 函数可以将一个 note 数据结构读入内存,并返回一个内存副本的句柄。它们之间的区别在于,NSFNoteOpen 仅支持 16 位的 WORD 参数(该参数在 OPEN_xxx 有描述),而 NSFNoteOpenExt 支持 32 位的 WORD 参数。
NSFNoteCreate 函数可以在内存中创建一个新的 note 结构。用 NSFNoteCreate 创建的 note 结构在其创建之初是空的,并且仅存在于内存中,没有硬盘数据库中的拷贝。开发人员需要调用 NSFItemAppend 一类的函数在该 note 结构中增加数据域,然后调用 NSFNoteUpdate 将其写入硬盘。最后调用 NSFNoteClose 关闭 note 句柄,释放 note 的内存空间。。
NSFNoteCopy 函数以一个 open 的 note 句柄为输入,在内存中创建一个该 note 的拷贝,并返回该拷贝的句柄。新创建的内存拷贝与作为输入的 note 有一些相同的 note 头信息,包括 Note ID, Originator ID 和 Database handle。在调用 NSFNoteUpdate 将该拷贝写回硬盘之前,必须生成并设置 Originator ID,并且将 Note ID 置 0.。
注意,所有的通过内存分配集中的函数创建的内存 note 都需要调用 NSFNoteUpdate 将其写回硬盘,并调用 NSFNoteClose 释放内存空间。。
2. 基于 note 的内存操所函数集
Lotus C API 提供了一系列的内存 note 操作函数。比如,NSFItemAppend 可以在一个 open 的内存 note 中增加一个 Item;NSFNoteAttachFile 可以将一个硬盘的文件 Attach 到内存 note。这类 note 内存操作函数有很多,分别使用不同的应用场合,用户可以参照 Lotus C API reference 文档获得内存操作函数集中的所需函数的用法。
3. 基于 note 的内存释放函数集
Lotus C API 提供了 NSFNoteClose 函数用于释放 note 占用的内存空间。由于 note 是由 note 头结构和多个叫做 item 的内存对象组成的,NSFNoteClose 将会释放所有这些成员的内存空间。
图 2 给出了一个使用 note 数据结构使用内存的典型设计流程:
图 2. 基于 note 的操作流程
使用“内存块”进行内存管理
“内存块”是 Lotus C API 管理内存的另外一种方式。与 note 数据结构相比,“内存块”提供了更加灵活的内存管理方式。Lotus C API 提供了很多基于“内存块”的内存访问函数。我们同样可以将这些函数分成 3 类:。
1. 基于“内存块”的内存分配函数集
Lotus C API 提供了基于“内存块”的内存分配函数 OSMemAlloc 和 OSMemoryAllocate,用于分配指定长度的一块内存。其中,OSMemAlloc 用于从 Notes/Domino 系统中分配一块内存。该函数类似于 C 语言函数 malloc。区别在于,OSMemAlloc 不仅分配一块内存,而且将创建一个该内存的句柄返回给 Lotus C API 函数。OSMemoryAllocate 与 OSMemAlloc 使用场合稍有不同。当 Notes/Domino 使用一个数据指针,并且设计者采用 Domino 的内存分配器来分配该数据的存贮空间时,应该使用 OSMemoryAllocate,而不是 OSMemAlloc。
2.“内存块”锁定函数集
“内存块”锁定函数可以锁定一个内存块,以防止其再分配。例如,OSLock 可以锁定一个内存对象并且返回该锁定的内存地址。一个内存对象被访问之前,必须对其锁定。一旦锁定,应用程序就可以使用该指针访问其内容。用 OSLock 锁定的内存需要调用其配对函数 OSUnLock 进行解锁。
与 OSLock 类似的一个函数是 OSLockObject。该函数用于锁定一个内存对象,防止其再分配,并返回该锁定的内存地址。访问一个内存对象之前必须调用该函数锁定这个内存对象。一旦一个内存对象被锁定,应用程序即可访问该对象的内容。
3.“内存块”释放函数集
内存访问函数分配的“内存块”必须调用配对的内存释放函数进行释放。例如,OSMemFree 用于释放 OSMemAlloc 分配的内存,OSMemoryFree 释放 OSMemoryAllocate 分配的内存。
图 3 给出了一个典型的基于“内存块”的内存管理流程。
图 3. 典型的基于“内存块”的内存管理流程
示例分析理
示例 1. 更新 Domino 数据库中 note 的各种类型域,并更新该文档。该程序执行成功之后会生成一个新的 document,查看生成的 document 的属性可以看到为其创建了 4 个域:Form,number,plain_text 和 text_list,如图 4。该程序向用户展示了如何使用 note 结构进行 Domino 的内存操作。
图 4. 典型的基于“内存块”的内存管理流程
清单 1. 示例 1
int main (int argc, char *argv[])
{
/* 初始化 Domino 运行时 */
NotesInitExtended (argc, argv);
/* 打开一个数据库 */
if (error = NSFDbOpen (path_name, &db_handle))
{ … }
/* 调用 NSFNoteCreate 创建一个数据 note,该 note 存在内存中 . */
if (error = NSFNoteCreate (db_handle, ¬e_handle))
{ … }
/* 写入 note 的 FORM 域,将其值设为 "SimpleDataForm" */
if (error = NSFItemSetText ( note_handle, "FORM", "SimpleDataForm", MAXWORD))
{ … }
/* 写入 note 的 PLAIN_TEXT 域,值为 "The quick brown fox jumped over the lazy dogs." */
if (error = NSFItemSetText ( note_handle, "PLAIN_TEXT",
"The quick brown fox jumped over the lazy dogs.", MAXWORD))
{ … }
/* 写入域名为 NUMBER 的数字域 . */
num_field = 125.007;
if (error = NSFItemSetNumber (note_handle, "NUMBER", &num_field))
{ … }
/* 获取当前的 time/date 并写入日期域 TIME_DATE */
OSCurrentTIMEDATE(&timedate);
if (error = NSFItemSetTime (note_handle, "TIME_DATE", &timedate))
{ … }
/* 创建一个 text-list 域并将其添加到 note */
if (error = NSFItemCreateTextList ( note_handle, "TEXT_LIST", "Charles", MAXWORD))
{ … }
/* 向创建的 text-list 域增加多个项 . */
if (error = NSFItemAppendTextList ( note_handle, "TEXT_LIST", "Janet", MAXWORD, TRUE))
{ … }
if (error = NSFItemAppendTextList ( note_handle, "TEXT_LIST", "Chuck", MAXWORD, TRUE))
{ … }
/* 将该内存中的 note 更新到硬盘 */
if (error = NSFNoteUpdate (note_handle, 0))
{ … }
/* 关闭 note,释放其内存空间 */
if (error = NSFNoteClose (note_handle))
{ … }
/* 关闭数据库 */
if (error = NSFDbClose (db_handle))
{ … }
/* 程序结束,结束 Domino 运行时 . */
NotesTerm();
return (0);
}
|
清单 2. 使用“内存块”的方式增加一个数据域
// 示例 2
OBJECT_DESCRIPTOR objLeftToDo;
BLOCKID bidLeftToDo;
DWORD dwItemSize;
void *p;
WORD w;
/*... steps missing ...*/
dwItemSize = (DWORD) (sizeof(WORD) + ODSLength(_OBJECT_DESCRIPTOR));
if (error = OSMemAlloc(0, dwItemSize, &(bidLeftToDo.pool)))
{
printf ("Error: unable to allocate %ld bytes for LeftToDo item.\n",
dwItemSize);
goto Exit1;
}
bidLeftToDo.block = NULLBLOCK;
p = OSLockBlock(void, bidLeftToDo);
w = TYPE_OBJECT;
memmove(p, &w, sizeof(WORD));
p += sizeof(WORD);
ODSWriteMemory(&p, _OBJECT_DESCRIPTOR, &objLeftToDo, 1);
OSUnlockBlock(bidLeftToDo);
if (error = NSFItemAppendObject(hMacro, ITEM_SUMMARY,
FILTER_LEFTTODO_ITEM,
sizeof(FILTER_LEFTTODO_ITEM)-1,
bidLeftToDo,
dwItemSize,
TRUE))
{
printf ("Error: unable to append %s item.\n", FILTER_LEFTTODO_ITEM);
OSMemFree(bidLeftToDo.pool);
goto Exit1;
}
|
更为详细的 Lotus C API 函数说明,可参阅 Lotus C API Toolkit 中的用户文档。
参考资料 学习
获得产品和技术
- 下载 IBM 软件产品试用版 以获得 DB2®, Lotus®, Rational®, Tivoli® 和 WebSphere® 工具和中间件的实际开发经验。
讨论
关于作者  | |  | 牛亚文,IBM CSDL WPLC Language Engineering and Client Platform 部门软件工程师,从事 Lotus C/C++ API Toolkit, NotesSQL 等产品的开发及维护。 |
对本文的评价
|  |