要克服编写带有不可移植 C/C++ 代码并基于 XML 的应用程序方面的限制,可以使用可嵌入的 C/C++ 解释器。
XML 正在改变信息共享和信息交换的世界。XML 标准允许用户通过开放的、独立于平台、供应商和语言的方式为电子数据交换、数据管理和发布等任务定义他们自己的数据和文档。其自描述的灵活标记(用于标记相关数据块的开头和结尾)构造了一个相关数据对象(称作元素—可能是数据库、网页片段(链接、数字、元数据、文本和图像)或电子表格的内容)的层次结构。由于 XML 使用内容封装分隔内容和表示,因此该结构使数据具有可重用性、简单的可导出性以及可重新配置性。由于 XML 是一个格式良好的标记语言,因此需要使用编程技术来执行与处理相关的任务,如分析、生成、操作和验证 XML 数据。为此,通常使用 C 和 C++ 编写基于 XML 的应用程序。但这两种语言都存在一定的问题—尽管 C/C++ 代码在理论上是可移植的,而关联的编译和链接过程是不可移植的。因此,大多数 C/C++ 代码需要在不同的平台上通过不同的方法进行编译和链接。真正可移植的 C/C++ 代码并非通过动态方式生成和执行;因此,在这些情况下,通常使用 Java 和 Java 虚拟机处理 XML 数据。
本文将介绍和演示 Ch 语言环境(下载)和 Ch Package for Oracle C/C++ XML Developer's Kit (XDK)(下载)用于跨平台脚本编制、shell 编程、数值计算、网络计算和嵌入式脚本编制的可嵌入的 C/C++ 解释器。Ch XML Package 用于集成可移植的 XML 数据和可移植的 C/C++ 代码;它使大量基于 C/C++ 的现有应用程序和技术与 XML 文档进行无缝工作。
特性
与 C 兼容的 Ch 语言环境是一个用于跨平台 C/C++ 脚本编制的可嵌入的解释器。Ch 支持 ISO 1990 C 标准中的所有功能以及 ISO C99 中的大多数新增功能,如复杂数字、可变长度数组 (VLA)、二进制常数以及 IEEE 754 浮点算法。该语言还支持 C++ 中的类、对象和封装以实现基于对象的编程。主要的特性包括:
- 解释性:可以在 Ch 中执行 C 程序而不必进行乏味的编译/链接/执行/调试循环。
- 交互性:用户可以交互式运行 C 代码,并逐行输入代码。因此,Ch 可轻松用于测试新函数。它同时还是一个优秀的实时交互计算环境。
- 可嵌入性:Ch 可以嵌入到其他应用程序、硬件以及手持设备中,使用户不必在不同的平台中开发和维护专有脚本编制语言。
- 数值计算:除支持所有 C 类型(如 char、int、float、double 以及新型 complex 和 VLA)以外,Ch 还将计算数组作为一级对象进行处理。许多高级数值函数(如微分方程求解、集成、 傅立叶分析以及二维/三维绘图)使 Ch 成为一个用于解决工程和科学问题的强大语言环境。
- 超高级:Ch 弥补了低级语言与超高级语言 (VHLL) 之间的差距。作为 C 的超集,Ch 包含低级语言功能。但作为 VHLL,它使程序能够轻松地在不同平台之间进行移植。
- 基于对象:Ch 支持 C++ 中的类、对象和封装,使您能够使用数据抽象和信息隐藏以及简化的 I/O 处理进行基于对象的编程。
- 文本处理:Ch 具有高级文本处理功能,如内置的字符串数据类型和 foreach-loop。这些功能对于系统管理、shell 编程和基于 Web 的应用程序尤其有用。该功能还可用于开发可移植代码来处理可移植数据。
- 跨平台 shell:为便于用户使用,Ch 提供了一个通用 shell。它可以用作登录命令 shell,类似于 Unix 中的 C-shell、Bourne shell、Bash 或 Korn Shell 以及 Windows 中的 MS-DOS shell。
- 安全的网络计算:安全 Ch 是采用不同安全层(如沙箱、程序员/管理控制、隐藏的指针、受限函数、字符串类型的自动内存管理以及自动数组绑定检查)的全新设计,可以有效地解决网络计算的安全问题。
- 可移植性:Ch 程序可以在不同平台(包括 Windows 和 Unix)中运行。程序员可以在一台计算机上开发和维护程序,然后将这些程序部署到 Ch 支持的所有平台。
- 广泛的库选择:所有现有 C 库和模块都可以是 Ch 库的一部分。因此,Ch 库的潜力几乎是无限的。例如,Ch 支持 POSIX、TCP/IP 套接字、Winsock、Win32、X11/Motif、GTK+、OpenGL、ODBC、LAPACK、LDAP、NAG 统计库、Intel OpenCV(用于计算机版本和图像处理)、National Instrument 的 NI-DAQ 和 NI-Motion 等。
- 启用 Web:通过用于 Web 服务器的开发模块(如公用网关接口 (CGI) 的类),Ch 允许对基于 Web 的应用程序和服务进行快速的开发和部署。
结构
Ch XML Package for Oracle XDK 的内容(可用于 Windows)包括:
- choraxml/demos—用 C/C++ 编写的原始 Oracle XDK 演示代码,可以在 Ch 中运行
- choraxml/dl—Ch 动态加载库
- choraxml/lib—Ch 函数
- choraxml/include—Ch 主文件
- choraxml/bin—Oracle XDK 动态库和命令
- choraxml/src—用于开发 Ch XML 程序包的源代码。
要安装 Ch XML for Oracle XDK,请执行以下步骤。
- 运行 Ch XML 需要安装 Ch。如果计算机中未安装 Ch,请从 http://www.softintegration.com 中下载和安装 Ch。
- 启动 Ch。
-
从 http://iel.ucdavis.edu/projects/chxml/ 下载文件 choraxml_v1.0.0.Window.tar.gz。使用以下命令在 Ch 命令 shell 中解压缩 zip 文件。
gzip -cd choraxml_v1.0.0.Window.tar.gz | tar -xvf -
- 遵循自述文件中的说明安装 choraxml 包并设置环境变量 ORA_NLS33 和 ORA_XML_MESG(这两个变量是 Oracle XDK 所需的变量)。
- 转到 demos 的目录,并键入文件名(如 DOMNamespace.c)以便通过解释的方式运行程序。
集成 Oracle XDK 与 Ch
Ch 最重要的功能之一是可以将二进制静态或动态 C/C++ 库与语言环境轻松地集成在一起而不必进行重新编译。例如,使用 Ch XML Package for Oracle C/C++ XDK,可以通过解释的方式跨平台执行使用 Oracle XML 库的 C/C++ 应用程序。还可以通过互联网执行它们。
与 DOM API 集成
XML 文档对象模型 (DOM) API 在内存中创建了一个树结构来存储 XML 文档的数据。通常情况下,基于 DOM 的 XML C/C++ 应用程序没有回调函数。但在 Oracle XDK 的 DOM API 创建 Ch 绑定相对比较简单。
图 1 演示了 Oracle XDK 如何与 Ch 集成。该体系结构包含三层:顶层是用户的应用程序,它们是使用 C/C++ XML 库的现有应用程序。中间层是 Ch 包装程序,它是二进制函数与基于本文的解释函数之间的中间件。我们开发了用于 Oracle XDK 的 Ch 包装程序或 Ch 绑定;在分发中还提供了用于开发 Ch 包装程序的源代码。此开放源可用于将 Ch 绑定移植到不同的平台和不同版本的 XDK。底层是 XDK 提供的原始 C/C++ 二进制代码。
通过 Oracle XDK 的 Ch 绑定,可以通过解释的方式执行用户的应用程序而不必进行跨平台编译:当 XML 应用程序调用 XML 函数时,程序将调用 XML-Ch 函数。XML-Ch 函数通常调用 XML Ch 动态加载库 (CHDL) 函数,该函数随后调用 Oracle XDK 中相应的二进制函数。例如,在 DOMNamespace.c 的应用程序中,将调用 xmlparse() 函数。当应用程序 DOMNamespace.c 启动时将使用 xmlparse.chf。xmlparse.chf 通过动态加载库(该库调用二进制函数 xmlparse())调用二进制函数 xmlparse_chdl()。以下列出了 xmlparse.chf 的示例代码。
uword xmlparse(xmlctx *ctx, oratext *uri, oratext *incoding, ub4 flags) {void *fptr;uword retval;fptr = dlsym(_ChOraxml_handle, "xmlparse_chdl");if(fptr == NULL) {fprintf(_stderr, "Error:sdlsym():%s/n", __func__, dlerror());return -1; }dlrunfun(fptr, &retval, xmlparse, ctx, uri, incoding, flags);return retval;}
其中,_ChOraxml_handle 是 XML Ch 动态加载库的文件句柄。函数 dlsym() 获取动态加载库中的 xmlparse_chdl() 函数的地址。函数 dlrunfun() 按照该地址调用 xmlparse_chdl()。
以下是 XML Ch 动态加载库内部的 xmlparse_chdl() 函数的源代码。
EXPORTCH uword xmlparse_chdl(void *varg) {va_list ap;xmlctx *ctx;const oratext *uri;const oratext *incoding;ub4 flags;uword retval;//获取所有传递到二进制函数的参数Ch_VaStart(ap, varg);ctx = Ch_VaArg(ap, xmlctx *);uri = Ch_VaArg(ap, const oratext *);incoding = Ch_VaArg(ap, const oratext *);flags = Ch_VaArg(ap, ub4);//调用二进制 xmlparse() 函数retval = xmlparse(ctx, uri, incoding, flags);Ch_VaEnd(ap);return retval;}
函数 xmlparse_chdl() 通过函数 Ch_VaArg() 从用户应用程序层中的 xmlparse() 函数获取它的参数值。它随后调用二进制函数 xmlparse() 并返回它的值。
使用回调函数与 SAX API 集成
Simple API for XML (SAX) 使用基于事件的模型处理 XML 文档;基于 SAX 的分析器在遇到标记(如开始标记或结束标记)时调用 C++ 中的方法或 C 中的函数。SAX 应用程序为 XML 文档定义回调函数。Ch 包装程序为此类回调函数提供注册(如图 2 所示)当应用程序调用某个函数(带有指向用户空间中定义的函数(回调函数)的参数)时,应用程序将回调函数的地址传递给 Ch 函数作为该函数的参数。但回调函数应在 Ch 包装程序中注册。Ch 函数将该地址传递到 CHDL 函数,以便在 Ch 包装程序中注册该回调函数。当遇到事件时,XML 二进制库函数将最终调用该回调函数。
例如,SAX API 包含函数 xslsetoutputsax(xslctx *xslSSctx, xmlsaxcb *s)。类型“xmlsaxcb *”的第二个参数(即结构指针)包含函数指针的成员字段。函数文件 xslsetoutputsax.chf 类似于上一节中的 xmlparse.chf。下面列出了函数 xslsetoutputsax_chdl() 的源代码。
......static xmlsaxcb saxcb_ch; ....static sword startDocument_chdl_funarg(void *ctx);static void * startDocument_chdl_funptr = NULL;....EXPORTCH uword xslsetoutputsax_chdl(void *varg) {va_list ap;xslctx *xslSSctx;xmlsaxcb *saxcb;xmlsaxcb *saxcb_tmp;uword retval; ....saxcb = Ch_VaArg(ap, xmlsaxcb *); ....if(saxcb != NULL) { ....startDocument_chdl_funptr = (void *)saxcb->startDocument;if(saxcb->startDocument != NULL)saxcb_ch.startDocument = startDocument_chdl_funarg; .... }saxcb_tmp = &saxcb_ch;retval = xslsetoutputsax(xslSSctx, saxcb_tmp);Ch_VaEnd(ap);return retval;}static sword startDocument_chdl_funarg(void *ctx){sword retval = 0;Ch_CallFuncByAddr(NULL, startDocument_chdl_funptr, &retval, ctx);return retval;}
函数 startDocument_chdl_funarg() 用于注册回调函数,当遇到事件“启动文档”时将调用该回调函数 。startDocument_chdl_funptr 通过 Ch_VaArg() 获取用户定义的回调函数的指针。函数 Ch_CallFuncByAddr() 调用用户应用层中的该回调函数。
使用 ODBC 与 Oracle 数据库集成
使用 XML 的应用程序经常需要访问数据库,如 Microsoft Access 或 Oracle 数据库。该任务可以使用 Ch ODBC 工具包(如图 3 所示)轻松完成。应用程序可以调用 XML 的 API 进行文档处理,以及调用 OBDC 进行直接和解释性的数据库访问。
例如,以下 Ch 代码使用 ODBC 连接到数据库。它插入并删除记录。
/************************************************************************* 这是在 Ch 中使用 ODBC API 的示例。* 运行该示例前,必须 * 先在 Microsoft Access 中创建一个数据库,然后 * 在 Windows ODBC 驱动程序管理器* 中将该数据库注册为“test.mdb”。 *************************************************************************/ #include <windows.h>#include <sql.h>#include <sqlext.h>#define TEST_LEN 10#define NAME_LEN 30 int main() {HENV henv;HDBC hdbc;UCHAR szDSN[SQL_MAX_DSN_LENGTH+1];UCHAR szDescription[255];SWORD cbDSN;SWORD cbDescription;HSTMT hstmt;RETCODE rc;UCHAR szTest[TEST_LEN+1];UCHAR ** szGetData;SDWORD iNumRows = 0;SDWORD cbTest = SQL_NTS;SDWORD Age = 29;UCHAR szName[NAME_LEN];SDWORD sAge;SDWORD cbName, cbAge;SQLAllocEnv(&henv);SQLAllocConnect(henv, &hdbc); rc = SQLDataSources(henv, SQL_FETCH_FIRST, szDSN, sizeof(szDSN),&cbDSN, szDescription, sizeof(szDescription),&cbDescription); printf(" %s /n", szDescription);printf(" %s /n", szDSN);// 连接到数据源 rc = SQLConnect(hdbc, "test", 5, NULL, SQL_NTS, NULL, SQL_NTS);if( rc != SQL_SUCCESS)printf(" invalid handle, connect failed /n");rc = SQLAllocStmt(hdbc, &hstmt);if( rc != SQL_SUCCESS)printf(" invalid handle, Allocate statement failed /n");// 插入一个记录rc = SQLPrepare(hstmt, "INSERT INTO Info (Name, Age) VALUES (?, ?)",SQL_NTS);if( rc != SQL_SUCCESS)printf(" invalid handle, insert failed /n");// 绑定将作为输入的数据rc = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, TEST_LEN, 0, szTest, 0, &cbTest); // 将 szTest 绑定到 Name rc = SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_USHORT, SQL_NUMERIC, 10, 0, &Age, 0, &cbTest); // 将变量 Age 绑定到记录 Age if( rc != SQL_SUCCESS)printf(" invalid handle, bind failed /n");strcpy(szTest, "You");rc = SQLExecute(hstmt);if( rc != SQL_SUCCESS)printf(" invalid handle, Execute failed /n");// 将语句连接到原始数据库rc = SQLAllocStmt(hdbc, &hstmt);// 删除名称为“You”的记录rc = SQLPrepare(hstmt, "DELETE FROM Info WHERE Name = 'You'", SQL_NTS);if( rc != SQL_SUCCESS)printf(" invalid handle, delete failed /n");rc = SQLExecute(hstmt);if( rc != SQL_SUCCESS)printf(" invalid handle, Execute failed /n"); SQLFreeStmt(hstmt, SQL_CLOSE);SQLDisconnect(hdbc);SQLFreeConnect(hdbc);SQLFreeEnv(henv); }
应用程序示例
Oracle XDK 包含多个用于处理 XML 文档的 C/C++ 演示程序;使用 Ch Package,所有这些 C/C++ 程序可以通过解释的方式执行而不必进行编译。例如,图 4 显示了 C 程序 DOMNamespace.c 在 Ch 命令 shell 中的交互式执行。在命令 shell 中键入文件名时,将执行该程序并显示如图 4 中所示的输出。程序 DOMNamespace.c 也可以从 Ch 的 IDE 中执行。多个 IDE(例如,UltraEdit 和 SlickEdit)可以轻松地用于编辑和运行 Ch 程序。
如果将 Ch 嵌套为应用程序中的脚本引擎,该程序将能够使用该应用程序动态控制的 C/C++ 脚本处理 XML 文档。这时,程序将作为 C 脚本处理。
XML 文档广泛用于基于 Web 的应用程序和集成。与 Perl、Python 或 PHP 一样,解释性的 C/C++ 脚本可用于在 Ch 中创建动态网页。Ch CGI 工具包包含四个类—Request、Response、Server 和 Cookie—其中的 API 类似于 ActiveServer Pages 和 JavaServer Pages。对于基于 Web 的应用程序,Ch CGI 脚本通常拥有文件扩展名 .ch。例如,可以通过单击 Web 浏览器中的超链接启动程序 DOMNamespace.ch。随后,它可以打印并在 Web 浏览器内部显示程序结果。程序 DOMNamespace.ch 基于程序 DOMNamespace.c 进行修改,并包含以下更改:
#include <cgi.h>.... //与 DOMNamespace.c 相同int main(){xmlctx *ctx;class CResponse Response;Response.setContentType( "text/plain" );Response.begin();... //与 DOMNamespace.c 相同 ...Response.end();
Ch 专业版和工具包下载 |
类 Response 封装 HTTP 样式响应。Response::begin() 开始发送输出。CResponse::end() 结束标准输出。
图 5 显示了一个使用 Ch XML Package 的基于 Web 的应用程序的演示页面。“函数”区域包含到 CGI 演示程序链接;当单击某个链接(如“DOMNAmespace.ch”)时,将执行相应的 CGI 程序。
无论是在 Ch 命令 shell 中执行(如图 4 所示)还是在 IDE 中执行(如图 6 所示),输出均相同。
结论
集成可移植 XML 数据与可移植 C/C++ 代码在有效处理文档和数据方面具有极好的潜力。使用 C/C++ 解释器 Ch 和 Ch XML 包,可以通过解释的方式执行使用 Oracle C/C++ XDK 的应用程序而不必进行乏味的编译/链接/执行/调试循环。此外,由于 Ch 是一个可嵌入的 C/C++ 解释器,因此应用程序可以将 Ch 嵌入为脚本编制引擎来处理使用 C/C++ 脚本的 XML 文档。
Zhaoqing Wang (zqwang@iel.ucdavis.edu) 是一位在戴维斯加州大学的集成工程实验室工作的博士后。他专门从事网络计算和开放式体系结构软件集成。
Harry H. Cheng 是戴维斯加州大学机械与航空工程部门的集成工程实验室的教授与主任。