虽然 AIX 和 IBM i 构建在共同的处理器 PowerPC 架构之上,并且 IBM i 上的 PASE 应用是以 AIX 模式运行的,但两者内存管理和地址空间分配有较大差异。在很多情况下,PASE 上的应用需要调用 IBM i 操作系统 Integrated Language Environment® (ILE) 中的程序(PGM),服务程序(Service Program)或者命令行程序(CL)获取系统信息。同样也存在 IBM i 程序使用 PASE 提供的功能这样的情况。这样的互相调用方式跨越了 PASE 和 ILE 两种运行环境,程序中需要特殊的代码来完成。本文以下章节将分别介绍如何处理这两种情况。
在讨论跨越运行环境进行调用之前,本节将简要介绍这两种环境。
ILE 是 Integrated Language Environment 的缩写,意思为集成语言环境。ILE 是被设计用来提升 IBM i 上程序开发的一系列工具和相关的系统支持。在 IBM i 平台 ILE 环境下,C/C++、CL、RPG、COBOL 语言编写的程序可以编译成 Module Object。Module Object 可以直接链接成可执行的 Program Object。
ILE 中有如下几个重要概念:
- Procedure
一段高级语言编写的源代码,它可以详细地完成某项任务,然后返回给调用者。对 C 语言而言,Procedure 就是一个函数;对 CL 而言,Procedure 其实就是一个 CL 程序的源代码。
- Module
Module 是由 ILE 编译器编译出来的,不可以执行的对象。Module 包含有一个或多个 Procedure 和一些数据信息,以及在这个 Module 中一些 Procedure 和数据导出导入信息。当然,有些 Module 还包含调试信息,这取决于如何编译程序。
- Program
Program 是 ILE 链接器把一个或者多个 Module 对象绑定链接而成的可以执行的程序。即 ILE 应用程序。
- Service Program
Service Program 是 ILE 下的外部可调用历程(函数或过程)的封装。可供其它的 ILE 程序或 Service Program 引用和链接。
IBM i Portable Application Solutions Environment(PASE) 是为运行在 IBM i 操作系统上的 AIX 应用而开发的一个集成运行时环境,该运行时环境在 IBM i 操作系统的 Licensed Internal Code 内核之上。引入 PASE 的目的是不改变或只需要微小的改动就可以将已有的 AIX 程序移植到 IBM i 操作系统上运行。PASE 支持 AIX 的 application binary interface (ABI),支持标准的 C 和 C++ 运行时和 pthreads 线程库,很大程度上能够兼容 AIX 的共享库,脚本,实用程序和系统调用。PASE 的应用以 IBM i job 方式运行和管理,并且可以使用 IBM i 操作系统提供的功能,如文件服务,安全和 sockets 通信机制。
我们可以将 PASE 应用程序集成到已有的 ILE 系统中,这样用户就可以重用已有的 ILE 代码,使 PASE 应用获得在 ILE 系统中才有的特性。因为只有 ILE 才能充分发挥 IBM i 操作系统的独特优势。
我们可以通过以下几种方式在 ILE 端调用 PASE 应用程序:
-
QSH CMD('pase_command parameters')
pase_command 为 PASE 端的可执行应用程序,它可以是该可执行应用程序的绝对路径,
也可以是当前 session 环境变量 PATH 下的可执行的应用程序。
清单 1. 使用 QSH CMDQSH CMD('ps -ef') QSH CMD('/QOpenSys/usr/bin/ls') -
CALL QP2SHELL PARM('pase_command''parameters)
pase_command 为 PASE 端的可执行应用程序的绝对路径 , parameters 为该命令的命令行
参数。例如 :
清单 2. 在命令行使用 QP2SHELLCALL QP2SHELL PARM('/QOpenSys/usr/bin/ls' '/QOpenSys/QIBM')
在 ILE 程序中调用 PASE 端可执行应用程序 , 我们可以使用以下三个 API 实现在 ILE 程序中调用 PASE 端应用程序。
- QP2SHELL
- QP2SHELL2
QP2SHELL 和 QP2SHELL2 这两个 API 提供了一个非常简单的使用 IBM i PASE 程序的
接口,直接使用要运行的 IBM i PASE 程序名字和输入参数作为 API 的输入,就可以调用 PASE 应用。
这两个 API 参数相同,差别在于 QP2SHELL()函数会创建新的激活组并在其中运行 PASE 程序。而 QP2SHELL2()函数会在调用者相同的激活组中运行 PASE 程序。这两个 API 都没有指示错误状态的返回值,也不会返回 PASE 程序的返回值。
程序示例:
清单 3. 在程序中使用 QP2SHELL
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <qp2usr.h>
#include <qp2shell.h>
void main(int argc, char* argv[])
{
char* parm = “/”;
char* pasePgm = “/QOpenSys/bin/ls”;
QP2SHELL(pasePGM, parm);
}
|
- Qp2RunPase
Qp2RunPase 允许用户提供更多的参数来对 PASE 程序的运行进行控制。它可以指定程
序名和运行参数,还可以定义 PASE 程序运行环境变量,以及 PASE 程序初始运行的 CCSID。Qp2RunPase 的返回值可以得到 PASE 程序执行的状态。PASE 程序结束返回时,导致 Qp2RunPase 返回并将控制权返还给 ILE 代码。下面是 Qp2RunPase 函数的原型:
清单 4. Qp2RunPase 函数声明
int Qp2RunPase( const char* pathname, const char* symbolName, const void* symbolData, unsigned int symbolDataLen, int ccsid, const char* const* argv, const char* const* envp); |
参数说明:
- pathName 输入参数:
表明 PASE 程序在 IFS 文件系统中的路径,可以使绝对路径,也可以是相对于作业当前路径的相对路径。
- symbolName、symbolData、symbolDataLen 输入参数
symbolName 参数必须定义为为一个空指针; symbolData 和 symbolDataLen 参数通常被忽略。
- CCSID 输入参数:
这个参数确定 PASE 程序初始运行的 CCSID。系统会将参数数据从作业默认的 CCSID 转换为对应的编码。例如,CCSID 设定为 1208,表明使用 UTF-8 编码。
- argv 参数:
argv 指向一个指针数组,该指针数组为 IBM i PASE 程序的参数表。第一个参数是 PASE 程序的路径名,该参数的值与参数 pathName 的值相同。
- envp 参数:
envp 参数指向一个指针数组,该指针数组用来定义 IBM i PASE 程序的环境变量。
Qp2RunPase 的返回值能够提供更多的信息,以便用户对 PASE 程序的运行状态进行处理。通常情况下,“0”表示正常返回,非零值表明错误返回,如“QP2RUNPASE_ERROR(-1)”或“QP2RUNPASE_RETURN_NOEXIT(-2)”这两个值表明函数本身的异常返回。IBM i 还提供了一些宏来处理返回值,如下表所示:
表 1. 使用宏处理 Qp2RunPase 返回值
| 宏 | 含义 |
|---|---|
| WIFEXITED(stat_val) | 如果返回非零值,表明 Qp2RunPase 函数的执行过程有错误,需要调用下面的宏获得错误的细节信息 |
| WEXITSTATUS(stat_val) | 当宏 WIFEXITED(stat_val) 返回非零值时,表明执行过程有错误。该宏会返回 PASE 程序的返回值 |
| WIFSIGNALED(stat_val) | 检查 PASE 程序是否被 signal 异常终止 |
| WTERMSIG(stat_val) | 当宏 WIFSIGNALED(stat_val) 返回非零值时,表明执行过程被 signal 终止。这个宏确认具体收到的 signal number |
程序示例:
清单 5. Qp2RunPase 函数使用实例
int rc = Qp2RunPase(“program/path/in/PASE”, NULL, NULL, 0, 819, (char**)&argv, NULL);
if (rc == QP2RUNPASE_ERROR || rc == QP2RUNPASE_RETURN_NOEXIT)
{
printf (“internal error”);
}
if (WIFEXITED(rc) != 0)
{
// Command exited normally, check its return code
paseRC = WEXITSTATUS(rc);
printf (“pase program exit with none zero return: %d”, paseRC);
}
|
以上介绍了在 ILE 中调用 PASE 环境中的应用程序 IBM i 还提供了一组支持直接调用 PASE 共享库的 API。调用过程需要用到以下函数:
- Qp2ptrsize: 获取 PASE 的指针长度。PASE 支持 32 位模式和 64 位模式。
- Qp2dlopen: 类似于 UNIX 下的 dlopen() 函数,用于打开 PASE 共享库。
- Qp2dlsym: 类似于 UNIX 下的 dlsym() 函数,用于查找对应的函数入口。
- Qp2dlclose: 类似于 UNIX 下的 dlclose() 函数,用于关闭 PASE 共享库。
- Qp2dlerror: 如果 Qp2dlopen、Qp2dlsym 或 Qp2dlclose 函数在执行过程出错,用户可以使用这个函数获取错误信息。
- Qp2malloc: 在 PASE 中分配一块内存,共 ILE 与 PASE 之间进行数据交互。
- Qp2free: 用于释放 Qp2mallloc 函数分配的内存。
Qp2CallPase
Qp2CallPase 使得用户可以在 ILE 程序中调用 PASE 共享库中的函数。Qp2CallPase 不会产生新的任务,只会在当前任务中执行 PASE 函数。
清单 6. Qp2CallPase 函数原型
#include <qp2user.h> int Qp2CallPase( const void* target, const void* arglist, const QP2_arg_type_t* signature, QP2_result_type_t result_type, void* result); |
参数说明:
- target
PASE 函数的描述符。
- arglist:
IBM i PASE 函数的参数列表。
- signature
参数的具体类型。QP2_ARG_END 表示 Signature 列表的结尾,QP2_ARG_WORD 表示 4-byte 数据,QP2_ARG_FLOAT32 表示 4-byte 浮点型数据,QP2_ARG_FLOAT64 表示 8-byte 浮点型数据。
- result_type
调用的 PASE 函数的返回值的类型,在头文件 qp2user.h 中定义。QP2_RESULT_VOID 表明 PASE 函数没有返回值,QP2_RESULT_WORD 表示 4-byte 数据,QP2_RESULT_DWORD 表示 8-byte 数据,QP2_RESULT_FLOAT64 表示 8-byte 浮点型数据。
- result
PASE 函数的返回值。
下面介绍 Qp2CallPase 函数的使用方法和步骤:
- 初始化:调用 IBM i PASE 环境之前必须对其进行初始化。IBM i PASE 环境提供了两个工具用来完成初始化,一个是 /usr/bin/start32,用来激活 32 位 IBM i PASE 环境,另一个是 /usr/bin/start64,用来激活 64 位 IBM i PASE 环境。每个 IBM i 进程必须且只能运行一个独立的 IBM i PASE 环境。
- 检查:调用 Qp2ptrsize() 来确定 IBM i PASE 环境是否正常运行。返回值:
0,表示没有完成初始化。
4,表示 PASE 环境运行在 32 位模式下
8,表示 PASE 环境运行在 64 位模式下。 - 加载库:用 Qp2dlopen 和 Qp2dlsym 将被调用的库加载至内存中,然后就可以定位被调用函数的入口。
- 调用 Qp2CallPase:使用 Qp2CallPase() API 激活 PASE 共享库函数,被调用的函数参数列表以指针数组的方式传递给 Qp2CallPase。ILE 代码还需要提供一种方式来描述参数列表的类型,通常称为 Signature。用户可以在头文件 qsysinc/qp2usr.h 中找到这些类型定义。
- 终止 IBM i PASE 进程:需要先调用 Qp2dlclose() 来卸载 IBM i PASE 共享库,然后调用 Qp2EndPase() 函数来终止 start64 或 start32 程序启动的 PASE 环境。
清单 7. Qp2CallPase 函数使用实例
#include <stdio.h>
#include <qp2shell2.h>
#include <qp2user.h>
#define JOB_CCSID 0
int main(int argc, char* argv[])
{
QP2_ptr64_t id;
void* getpid_pase;
const QP2_arg_type_t signature[] = {QP2_ARG_END};
QP2_word_t result;
QP2SHELL2(“/usr/lib/start32”);
id = Qp2dlopen(NULL, QP2_RTLD_NOW, JOB_CCSID);
getpid_pase = Qp2dlsym(id, “getpid”, JOB_CCSID, NULL);
int rc = Qp2CallPase(getpid_pase,
NULL,// no argument list
signature,
QP2_RESULT_WORD,
&result)
printf (“IBM i PASE getpid() = &i\n”, result);
if (result == -1)
printf (“IBM i errno = %i\n”, *Qp2errnop());
Qp2dlclose(id);
Qp2EndPase();
return 0;
}
|
在 ILE 端我们通常通过 CRTCMOD(如果源程序文件是由 ILE C 语言编写)或者 CRTCPPMOD(如果源程序文件是由 ILE C++ 语言编写)命令来将 ILE 端的程序编译成 MODULE,然后用 CRTPGM 来将编译好的 MODULE(s) 以及 Service Program 链接生成可执行的 PGM。
清单 8. 在 ILE 编译和链接程序
CRTCMOD MODULE(QYOURLIB/YOURMOD) SRCFILE(QYOURLIB/QCSRC)
或者:÷ CRTCPPMOD MODULE(QYOURLIB/YOURMOD)
SRCSTMF('/qopensys/myprogram/myprogram.cpp') INCDIR('/qopensys/myprogram/include')
CRTPGM PGM(QYOURLIB/YOURPGM) MODULE(QYOURLIB/YOURMOD)
BNDSVRPGM(QSYS/QP2USER)
|
ILE 命令行是指在 IBM i 的终端窗口内使用的命令,类似 AIX 的 shell 命令。例如: CRTUSRPRF USRPRF(USER1) 用来创建一个 IBM i 系统账号。如果希望在 PASE 应用中调用上面的命令行提供创建账号功能,就需要使用 systemCL API 来完成。systemCL 提供了 PASE 程序运行 ILE Commnad Line 命令的功能。函数声明:
清单 9. systemCL 声明
#include <as400_protos.h> int systemCL(const char *command, int lags); |
<as400_protos.h> 头文件是 PASE 专有的,包含了支持 PASE 调用 ILE 的 API。
清单 10. 使用 systemCL 调用 ILE 命令
int retval = systemCL("CRTUSRPRF USRPRF(USER1)", SYSTEMCL_MSG_STDOUT);
|
Program 是 ILE 下的可执行程序,对象类型为 *PGM。 例如通过 WRKOBJ OBJ(QSYS/*ALL) OBJTYPE(*PGM) 来查看 QSYS 库下的所有 Program。Program 可以在 ILE 的终端下使用 CALL 命令直接引用。
由于要在 PASE 中使用 ILE 对象涉及到地址转换,先介绍 PASE 中的 ILE 对象指针也就是地址,即 ILEpointer 结构 :
清单 11. ILEpointer 对象声明
typedef union _ILEpointer {
#if !(defined(_AIX) || defined(KERNEL))
#pragma pack(1,16,_ILEpointer) /* Force sCC quadword alignment */
#endif
long double align; /* Force xlc quadword alignment
(with -qldbl128 -qalign=natural) */
#ifndef _AIX
void *openPtr; /* MI open pointer (tagged quadword) */
#endif
struct {
char filler[16-sizeof(uint64)];
address64_t addr;/* (PASE) memory address */
} s;
} ILEpointer;
|
可以看出 ILE 下对象指针长度是 16 字节,而 PASE 的地址保存在 8 字节的 addr 中。
调用 PGM 对象使用到的函数:
清单 12. _RSLOBJ 和 _RSLOBJ2 函数声明
#include <as400_protos.h>
int _RSLOBJ(ILEpointer *sysptr
const char *path,
char *objtype);
int _RSLOBJ2(ILEpointer *sysptr,
unsigned short type_subtype,
const char *objname,
const char *libname);
|
这两个函数用来解析和提取 ILE 下 Program 对象的地址,接收参数为对象路径或对象名库名返回 16 字节指向 Program 的系统指针。
清单 13. _PGMCALL 函数声明
int _PGMCALL(const ILEpointer *target,
void **argv,
unsigned flags);
|
_PGMCALL 函数调用指定的 Program。参数为 _RSLOBJ() 和 _RSLOBJ2() 函数返回的对象指针。参数 flags 指定如何控制被调的 ILE 程序,通常情况下,如果调用者希望 _PGMCALL 通过返回值的方式来判断调用出错,可以使用 PGMCALL_EXCP_NOSIGNAL,否则 _PGMCALL 将出错转化为 PASE 的信号,在未安装信号处理函数的情况下,这可能导致程序直接退出。
例子:在 PASE 下调用 ILE 的 TESTLIB 库文件系统 TESTPGM(*PGM 类型 )
清单 14. _PGMCALL 使用实例
ILEpointer PGM_pointer;
const char* arglist[] = { "parm1", "parm2",NULL};
int retval1 = _RSLOBJ2(&PGM_pointer, RSLOBJ_TS_PGM, "TESTLIB", "TESTPGM");
int retval2 = _PGMCALL(&PGM_pointer,(void**)arglist, PGMCALL_EXCP_NOSIGNAL);
|
ILE 下的 Service Program 类似于共享库或者动态链接库,包含一系列的函数或过程为其他 Service Program 或程序使用。Service Program 区别于 PGM 的地方是没有程序入口函数即 main 函数。通常这些函数被一个或多个应用频繁使用,将这些函数打包到 Service Program 可以提高重用性,简化维护开销和减少内存消耗。Service Program 可以做到运行时绑定。
调用 Service Program 需要使用到的函数:
清单 15. _ILELOAD 函数声明
unsigned long long _ILELOADX(const void *id, unsigned int flags); int _ILELOAD(const void *id, unsigned int flags); |
上面两个函数加载 ILE 的 Service Program,返回 Service Program 的唯一激活标识。
清单 16. _ILESYMX 和 _ILESYM 函数声明
int _ILESYMX(ILEpointer *export,
unsigned long long actmark,
const char *symbol);
int _ILESYM(ILEpointer *export,
int actmark,
const char *symbol);
|
这两个函数使用 _ILELOADX() 和 _ILELOAD() 返回的激活标识作为输入参数,在 Service Program 寻找被调用函数或过程的符号,对返回指向该函数的 16 字节指针。
清单 17. _ILECALLX 和 _ILECALL 函数声明
int _ILECALLX(const ILEpointer *target,
ILEarglist_base *ILEarglist,
const arg_type_t *signature,
result_type_t result_type,
int flags);
int _ILECALL(const ILEpointer *target,
ILEarglist_base *ILEarglist,
const arg_type_t *signature,
result_type_t result_type);
|
以上两个函数调用 Service Program,传递 16 字节的指向被调 ILE 函数指针,以及参数列表,参数类型列表等。由于 ILE 的程序要求参数有一定的对齐方式,PASE 提供了两个函数帮助构造参数列表。
清单 18. size_ILEarglist 函数声明
size_t size_ILEarglist(const arg_type_t *signature); |
size_ILEarglist() 用来计算参数字节数。由于参数按字节对齐,参数的顺序将影响参数空间的大小。
清单 19. build_ILEarglist 函数声明
int build_ILEarglist(ILEarglist_base *ILEarglist,
const void *PASEarglist,
const arg_type_t *signature);
|
build_ILEarglist() 根据参数类型构造参数列表。
调用 Service Program 程序示例:
TESTLIB 库下的 TESTSRV Service Program 里的 TESTSRV 过程,声明如下:
清单 20. TESTSRV 函数声明
int TESTSRV(int a, float b,char c); |
需要定义一个和被调函数有同样参数的转换函数,调用过程封装在转换函数中完成。对 PASE 开发者来说这个转换函数是必要的,因为编译器会根据转换函数的参数类型和顺序自动构造参数列表并进行参数字节对齐,Service Program 可以直接使用编译器生成的参数列表。否则开发人员需要自己实现参数的字节对齐。
清单 21. PASE 下调用 ILE Service Program 实例
int TESTSRVWrapper(int arg1, double arg2, char arg3)
{
// 加载 Service Program
int actmark = _ILELOAD("TESTLIB/TESTSRV", ILELOAD_LIBOBJ);
ILEPointer* ILEtarget = (ILEPointer*)malloc(sizeof(ILEPointer));
// 定位被调过程的符号
int rc = _ILESYMX(ILEtarget, actmark, "TESTSRV");
// 指定调用参数类型列表
static short signature[] =
{
ARG_INT32,
ARG_FLOAT64,
ARG_UINT8,
ARG_END
};
ILEarglist_base *ILEarglist;
// 计算参数列表空间
ILEarglist = (ILEarglist_base*)malloc( size_ILEarglist(signature) );
// 构造参数列表,只需第一个参数的地址
build_ILEarglist(ILEarglist, &arg1, signature);
// 传入参数并调用
_ILECALL(ILEtarget, ILEarglist, signature, RESULT_INT32);
free(ILEarglist);
free(ILEtarget);
|
}
PASE 支持 AIX 的编译器 IBM XL C/C++ for AIX,该编译器是单独提供的。PASE 也包含了许多 AIX 的开发工具,如 ld,ar,make,yacc。XLC 编译器要注意的问题是指针的处理。因为 ILEpointer 类型代表了访问底层机器接口的指针,在结构体中它要求 16 字节对齐,XLC 通过编译选项 -qlngdbl128 和 -qalign=natural 为 long double 类型的数据保持 16 字节对齐提供了有限的支持。
编译命令示例:
清单 22. PASE 下进行编译
xlC_r -o test -qlngdbl128 -qalign=natural – H16 – lc128 -qrtti=dyna -qlanglvl=newexcp -Wl,,-brtl -Wl,-bhalt:8 -bI:/usr/lib/as400_libc.exp test.cpp |
xlC_r 是编译命令,用来编译多线程程序以确保和线程安全库链接。
-qrtti 可以为 dynamic_cast 产生运行时类型识别信息。
-qlanglvl 指定 new 或 new[] 在不能分配请求的内存时抛出异常
-Wl 提供了一组链接选项:-brtl 默认值会先搜寻 .so 类型的文件进行链接,再搜寻 .a 类型的文件;-bhalt 确保当链接程序返回的错误值大于指定值时链接器挂起。
编译命令会将 -bI 后面的参数传递给链接器 ld,as400_libc.exp 文件包含了来自 libc.a 的 IBM i 系统特有的接口。
如果生成链接文件而非可执行程序则需使用 -qmkshrobj 参数。
通常,PASE 应用可以利用 IBM i 操作系统环境提供的服务,如安全,消息处理,通信,备份和恢复,同时也可以使用继承自 AIX 的应用接口。运行在 PASE 下的应用可以集成 ILE 文件系统和 DB2 for IBM i,也可以调用 Java 和 ILE 应用。Java 和 ILE 应用也可以调用 PASE 应用。这种相互调用方便了开发人员进行跨平台程序移植,提高了资源使用率并有助于节约投资和维护成本。
学习
-
参考 i5/OS PASE Callable Program APIs,了解更多运行 PASE 程序的方法。
-
查看 i5/OS PASE ILE Procedure APIs,获取关于 ILE 程序和 PASE 交互的更多信息。
-
查看 Runtime Functions For Use by i5/OS PASE Programs,查找更多 PASE 运行时接口。
-
IBM developerWorks 中国 IBM i 专区:为 IBM i 的开发人员准备的技术信息和资料。这里提供产品下载、how-to 信息、支持资源以及免费技术库,包含 2000 多份技术文章、教程、最佳实践、IBM Redbook 和在线产品手册。
讨论
- 加入 developerWorks 中文社区,developerWorks 社区是一个面向全球 IT 专业人员,可以提供博客、书签、wiki、群组、联系、共享和协作等社区功能的专业社交网络社区。
- 加入 IBM i 中国开发团队 Blog,参与在线交流。