工具箱开发

本文档来源于 http://www.scilab.org/product/toolbox_guide/html/toolbox.html 页面中的英文文档,编写自己的工具箱,除了参考此文档,也可以参考一些成熟的工具箱的代码和代码组织方式。

工具箱结构

工具箱的根目录名为工具箱的名字(比如mytoolbox),它包含8个子目录:

  • macros: Scilab宏 (例如用Scilab代码写的,以.sci为文件名扩展名的函数),buldmacros宏和loadmacros宏脚本。
  • src: 源代码(所有的 .c 和 .f 文件),一个 buildsrc脚本
  • sci_gateway: 接口程序,和一个buildsci_gateway
  • help: 英语和法语帮助,其子目录明分别为eng和fr,里面包含.xml帮助文件,buildhelp和loadhelp脚本。
  • etc: .html, .pdf, .txt, .jpeg, ...
  • unit tests: .tst files (测试你的工具箱的脚本)
  • demos: 各种展示你的工具箱的例子。
  • includes: .h 文件。

和四个文件:

  • readme.txt: 工具箱描述和安装说明。
  • builder.sce: 主builder
  • loader.sce: 主loader
  • license.txt: 协议

子目录内的builder和loader

主builder和主loader脚本运行每个子目录(如macros,src, help等)里的子builder和子loader,来生成或者装载需要的库和帮助文件。

宏目录

为了方便,在宏目录里的builder和loader分别命名为buildmacros.sce和loadmacros.sce,当然您也可以命名为其他的名字。

一个宏(macros)是以Scilab语言写的一个函数(以.sci结尾)

假设目前我们的工具箱mytoolbox只包含一个.sci文件,函数foo1(见下面的代码)的功能如下:输入一个方阵A,这个函数返回向量XXA对角线上的正数元素。

foo1.sci

function [X]=foo1(A)
	//这个函数返回方阵A对角线上的正数元素
	
	// 检查A的类型和大小
	if  type(A)<>1 then
		error("type of input argument must be a double");
	end
	if  size(A,1)<>size(A,2) then
		error("input argument must be a square matrix");
	end
	//提取对角线上的正数
	X=[];
	for i=1:size(A,1)
		if A(i,i)>0 then
			X($+1)=A(i,i);
		end
	end
endfunction

builder宏

下面的builder宏从macros目录中的.sci文件中创建一个变量,(这儿的名字是mytoolboxlib= toolbox_name +'lib')并保存到库文件中。下面的builder宏代码是通用的,它做两步操作,第一步是找到buildmacros.sce所在的位置(详细内容查看get_absolute_file_path函数的帮助),第二步是生成库文件(详细内容查看genlib函数的帮助)。

buildmacros.sce

mode(-1)   //在这种模式下,下面执行的代码不会显示在屏幕上
toolboxname='mytoolbox'  //定义工具箱的名字
pathB=get_absolute_file_path('buildmacros.sce') //获取本文件的绝对路径
disp('Building macros  in ' +pathB)   //打印提示到屏幕
genlib(toolboxname+'lib',pathB,%t)  //生成宏库
clear pathB genlib toolboxname	//清除刚才定义的变量

请将字符串'mytoolbox'题换成你的工具箱的名字。

loader宏

这个loader宏将macros目录中的所有lib都装载到scilab。跟builder宏一样,它是一个通用的脚本。它第一步确定loadmacros.sce 文件的位置,第二步装载lib到Scilab(详细内容查看load函数的帮助)

loadmacros.sce

mode(-1)  //在这种模式下,下面执行的代码不会显示在屏幕上
pathL=get_absolute_file_path('loadmacros.sce') //获取本文件的绝对路径
disp('Loading macros  in ' +pathL) //打印提示到屏幕
load(pathL+'/lib')                           //装载lib到scilab
clear pathL                                     //清除刚才定义的变量

接口和src目录

primitive是指一个Scilab函数,它使用一个接口程序来调用以C或者Fortran代码写的函数。对于每个Scilabprimitive,我们必须为它在sci_gateway目录中创建对应的接口函数。

在使用C语言的写接口程序的情况下,当一个Scilab primitive被调用的时候,接口函数收件检查输入和输出的数值,类型和大小是否正确(使用CheckRhsCheckLhs 函数),并从Scilab内部堆栈中获取rhs参数的地址,传给接口函数。

我们这里并不展开描述接口函数的所有可能情况,更多的例子请参考SCI/examples目录的例程。

接口程序例程

下面一步一步的解释几个C语言的例子,理解了这个例子,可以帮助你写出其他的接口函数,并添加到你的工具箱。

例子a:
假定一个程序vectsum,它返回输入的两个向量的和。它的C语言函数名是sci_sumab,在Scilab中调用它的命令是sumab

为了方便,Scilab中的调用命令都用"sci_"开头。

下面描述了C语言函数vectsumsci_sumab,这两个函数当我们在Scilab窗口中输入--> Y=sumab(A,B )的时候被使用。

vectsum.c

void vectsum(int n, double * a, double * b, double * y)
{
	int k;
	for (k = 0; k < n; ++k) 
	y[k] = a[k] + b[k];
}

sci_sumab.c


#include "stack-c.h"
extern int vectsum(int n, double * a, double * b, double * y); 

void sci_sumab(char *fname){  
int l1, m1, n1, l2, m2, n2, l3, n;   
		
/* 1 - Check the number of inputs/outputs arguments  */  
int minlhs=1, maxlhs=1, minrhs=2, maxrhs=2; 
CheckRhs(minrhs,maxrhs) ; 
CheckLhs(minlhs,maxlhs) ; 

/* 2 - Check inputs arguments type, and get the size and the address 
          in the Scilab stack of the inputs arguments */  
GetRhsVar(1, "d", &m1, &n1, &l1);
GetRhsVar(2, "d", &m2, &n2, &l2);
		
/* 3 - Check that the inputs arguments have the same size */
/* it's possible to use the chekdims and getscalar functions to make these checks*/ 
n=m2*n2; 
if( n1!=n2 || m1!=m2) 
{
	cerro("inputs arguments must have the same size"); 
	return 0; 
}	
if(n1!=0 && m1!=0)    
	if(n1!=1 && m1!=1)   
	{
		cerro("inputs arguments must be vectors");    
		return(0); 
	}
		 
		
/* 4 - Create a new variable corresponding to the output argument */ 
CreateVar(3,"d",&m2,&n2,&l3);   

/* 5 -call vectsum routine: returns in stk(l3) the sum of a and b*/
vectsum(n,stk(l1),stk(l2),stk(l3));  
		
/* 6 - Specif ouput argument */  
LhsVar(1) = 3;  
return 0;
}

第一步: call CheckRhsVar(minrhs,maxrhs) and CheckLhsVar(minlhs,maxlhs) instructions

CheckRhsVar
function uses the arguments minrhs and maxrhs to check that: minrhs <= number of input arguments <= maxrhs The number of inputs and outputs arguments (respectively 2 and 1) of vectsum are constant, so minrhs=maxrhs=2 and minlhs=maxlhs=1, but for certains functions (see example2) they can be variable, in this case the variables minrhs/minlhs and maxrhs/maxlhs are different.
We can use directly the defined variables Rhs(=number of inputs) and Lhs(=number of outputs) instead of the functions CheckRhsVar and CheckLhsVar.

第二步: call GetRhsVar(1,"d",&m1,&n1,&l1) instruction
GetRhsVar function checks that the type of inputs arguments of sumab are correct, and gets their size and their address in the Scilab stack.

We describe below all arguments of GetRhsVar function:

  • 1 : corresponds to the position on the stack of the first input argument of sumab, i.e A, (2 corresponds to B,...)
  • m1 : gets the rows number of A (m2 for B)
  • n1 : gets the columns number of A (n2 for B)
  • l1 : gets the address of A in the Scilab stack (l2 for B)

第三步: call CreateVar(3,"d",&m2,&n2,&l3) instruction

CreateVar function creates in the Stack at the 3th position a variable which corresponds to the output argument of vectsum (here Y)

  • 3 : position of the created variable in the stack. This position (here 3) must follows the position of the last input argument (here2) of sumab
  • "d": sets the type of the created variable, here double
  • m2: sets the rows number of the created variable(here equal to the rows number of the second input argument B: m2)
  • n2: sets the columns number of the first created variable (here equal to the columns number of the second input argument B: n2)
  • l3: gets the address of the created variable in the Scilab stack

第四步: call vectsum(n,stk(l1),stk(l2),stk(l3)) instruction
The C function vectsum returns in stk(l3) the sum of stk(l1) and stk(l2) (i.e a and b)

第六步: call LhsVar(1) = 3 instruction
The first output argument (here Y) of sumab takes the value of the variable placed in the 3th position on the stack (i.e stk(l3))

例子b:

In the second example we describe the interface program named sci_fun of the Scilab primitive named fun.
This function call the C routines fun1 and fun2 and has 2 syntaxes which are:

First syntax:
--> [X, Y ]=fun(A);
Given a vector A, this function returns the positives components of A in a vector X and the sum of its positives components in a scalar Y.

Second syntax:
--> [X ]=fun(A);
Given a vector A, this function returns the positives components of A in a vector X.

The number of outputs arguments (i.e Lhs value) is variable: for the first syntax Lhs=1, for the second syntax Lhs=2. The number of intputs arguments (i.e Rhs value) is constant: Rhs=1 (first and second syntax).
So the interface program must check that: 1<=Lhs<=2 (set minlhs=1, maxlhs=2) and Rhs=1 (set minrhs=maxrhs=1)

fun1.c (the C function fun1 creates the vector X and the scalar Y. It calls the C function fun2 to get the needed size of X in order to allocate the corresponding memory place)


extern void  fun2(double *, int, int *);

void fun1(double * a,  int na, int * nx,  double ** x , double * y){
	int i, k1=0;
	*y=0;
	fun2(a, na, nx);
	*x=(double *)malloc((*nx)*sizeof(double));
	*y=0;
	for(i=0;i<na;i++)
	if(a[i]>0) {
		*(*x+k1)=a[i];
		*y += a[i];
		k1++;
	};
}

fun2.c

void  fun2(double * a, int na, int * nx)
{
	int i;
	*nx=0;
	for(i=0;i<na;i++)
	if (a[i]>0)  
		(*nx)++;
}

sci_fun.c

#include "stack-c.h"

extern void fun1(double * ,  int, int *, double **, double *);

int sci_fun(char *fname)
{
	int la, ma, na, m=1, nx, i, lx, ls;
	double * x, s;

	/* 1 - Check the number of inputs and outputs arguments */
	/* You can use the variables: Lhs and Rhs */
	int minlhs=1, maxlhs=2, minrhs=1, maxrhs=1;
	CheckRhs(minrhs,maxrhs) ;
	CheckLhs(minlhs,maxlhs) ;

	/* 2 - Check the rhs type, get the rows number (ma) and the columns number (na) of rhs, 
            and its address (la) in the Scilab stack (first position) */
	GetRhsVar(1, "d", &ma, &na, &la); 

	/* 3 - Check rhs is a vector */
	if(ma!=0 && na!=0 )
	{
		if(ma!=1 && na!=1)
		{
			cerro("input argument must be a vector");
			return(0);
		}
	}

	fun1(stk(la), na*ma, &nx, &x, &s);

	/* 4 - Create the place for the first output argument x ( a vector of doubles, size: 1*nx )
                  to the address lx in the Scilab stack (second position) */
	CreateVar(2, "d", &m, &nx, &lx);

	/* if there are two outputs variables then: Create the place for the second output 
            s ( a double, size 1*1)  to the address ls in the Scilab stack (third position) */ 
	/* get the value of s, and put it in the Scilab stack */
	if(Lhs==2)
	{
		CreateVar(3, "d", &m, &m, &ls);
		*stk(ls)=s;
	}

	/* get the components of x, and put them in the Scilab stack */
	for(i=0;i<nx;i++) 
		stk(lx)[i]=x[i];

	/* free memory */
	free(x);

	/* 5 - Specification of outputs variables */
	LhsVar(1) = 2;
	if(Lhs==2)
		LhsVar(2) = 3;
	return 0;
}

基本的builder

现在srcsci_gateway包含了所有为primitives funsumab创建builder所需要的文件(fun1.c, fun2.c, sci_fun.c, vectsum.c, sci_sumab.c) 。

我们需要撰写两个builder:

  • 一是在src目录中,builder(叫做buildersrc)需要创建一系列跟C语言函数对应的共享库(请参考ilib_for_link函数帮助)。
  • 另一个是在interface目录中, builder(叫做buildsci_gateway)创建新的共享库来连接已经编译好的C或者Fortran语言的接口,并生成loader(参考ilib_build函数帮助)。这个loader文件调用addinter函数,加载动态连接库到Scilab(参考addinter函数帮助)。

buildsrc.sce

ilib_for_link('mytoolboxsrc',['fun1.o' 'fun2.o','vectsum.o'],[],"c")

buildsci_gateway.sce

// must be run from this directory
ilib_name  = 'libmytoolbox'     // interface library name
files = ['sci_fun.o', 'sci_sumab.o'];  // objects files
libs  = ["../src/libmytoolboxsrc"]                 // other libs needed for linking
table = [ 'fun', 'sci_fun';
'sumab','sci_sumab'];        // table of (scilab_name,interface-name)
// do not modify below
ilib_build(ilib_name,table,files,libs)

The ilib_name value is the interface library name, the vector files contains all the object interface files,
the vector libs contains the libraries needed for linking (here the library included in the src directory),
the variable table contains the primitives names (first column) and the corresponding interfaces programs names (second column)

帮助目录

这个子目录里包含.xml文件,buildhelp脚本和loadhelp脚本。

在Unix/Linux系统中,你需要sabcmd来创建帮助手册,它是Sablotron软件包的一部分,是一个XML语言解析器。可以从这儿下载:http://www.scilab.org/download/index_download.php?page=related_tool.html

创建.xml文件

这儿是一个模板,用来展示如何撰写.xml帮助文件。你需要做的只是为你的函数的.xml文件填充各个项目的内容(如Langage,title,type,date, short description,……),并将他们放入help目录中。

<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
<!DOCTYPE MAN SYSTEM "/home/scilab/scilab-3.0/man/manrev.dtd">
<MAN>

<LANGUAGE>eng</LANGUAGE>
<TITLE>sumab</TITLE>
<TYPE>Scilab Function  </TYPE>
<DATE>20-Mar-2006</DATE>
<SHORT_DESCRIPTION name="add function name">  add short decription here</SHORT_DESCRIPTION>

<CALLING_SEQUENCE>
<CALLING_SEQUENCE_ITEM>add function syntax</CALLING_SEQUENCE_ITEM>
</CALLING_SEQUENCE>

<PARAM>
<PARAM_INDENT>
<PARAM_ITEM>
<PARAM_NAME>add param name</PARAM_NAME>

<PARAM_DESCRIPTION>
<SP>
: add here the parameter description
</SP>
</PARAM_DESCRIPTION>

<PARAM_ITEM>
<PARAM_NAME>add param name</PARAM_NAME>
<PARAM_DESCRIPTION>

<SP>
: add here the parameter description
</SP>
</PARAM_DESCRIPTION>
</PARAM_ITEM>
</PARAM_INDENT>
</PARAM>

<DESCRIPTION>
<DESCRIPTION_INDENT>

<DESCRIPTION_ITEM>
<P>
Add here a paragraph of the function description. 
Other paragraph can be added 
</P>
</DESCRIPTION_ITEM>
<DESCRIPTION_ITEM>
<P>
Add here a paragraph of the function description 
</P>
</DESCRIPTION_ITEM>

</DESCRIPTION_INDENT>
</DESCRIPTION>

<EXAMPLE><![CDATA[
Add here scilab instructions and comments
]]></EXAMPLE>

<SEE_ALSO>
<SEE_ALSO_ITEM> <LINK> add a key here</LINK> </SEE_ALSO_ITEM>

<SEE_ALSO_ITEM> <LINK> add a key here</LINK> </SEE_ALSO_ITEM>
</SEE_ALSO>

<BIBLIO>
<Add here the function bibliography if any 
</BIBLIO>

<AUTHORS>
<AUTHORS_ITEM label='enter here the author name'>
Add here the author  references
</AUTHORS_ITEM>
</AUTHORS>
<USED_FUNCTIONS>
Add here the used function name and  references
</USED_FUNCTIONS>
</MAN>

帮助builder

builder脚本(这儿是buildhelp.sce)创建一个whatis.htm文件,里面是所有函数的简短描述,并将.xml文件转换为.html文件(更多信息请查阅xmltohtml函数帮助)。
> function)

buildhelp.sce

mode(-1) //强制进入slient模式,不显示执行的代码在屏幕
path=get_absolute_file_path('builhelp.sce'); //得到这个文件的绝对路径
add_help_chapter("Title1",path);                  //将帮助添加到帮助章节中
xmltohtml(path,"Title1")                                //将xml转为html
clear path add_help_chapter get_absolute_file_path //清除无用的变量

帮助loader

帮助loader(这儿是loadhelp.sce)添加你的函数帮助文档到Scilab的帮助浏览器中。

loadhelp.sce

mode(-1) //强制进入slient模式,不显示执行的代码在屏幕
path=get_absolute_file_path('loadhelp.sce');//得到这个文件的绝对路径
add_help_chapter("Title1",path);//加载帮助
clear path add_help_chapter get_absolute_file_

总builder和总loader

总的builder和loader在顶层执行,他们去调用每个子builder和子loader,下面的是这两个脚本的例子:

builder.sce

mode(-1);
mainpathB=get_absolute_file_path('builder.sce');
chdir(mainpathB);
if isdir('src') then //如果存在src目录
chdir('src');         //进入src目录
exec('buildsrc.sce'); //执行src目录里的builder.sce
chdir('..');           //回到上一层目录
end                     //end if;下面类似
if isdir('sci_gateway') then 
chdir('sci_gateway');
exec('buildsci_gateway.sce');
chdir('..');
end
if isdir('macros') then
chdir('macros');
exec('buildmacros.sce');
chdir('..');
end
if isdir('help') then
chdir('help');
exec('buildhelp.sce');
chdir('..');
end
clear mainpathB

loader.sce

mode(-1);
mainpathL=get_absolute_file_path('loader.sce');
chdir(mainpathL);
if isdir('sci_gateway') then
chdir('sci_gateway');
exec('loader.sce');
chdir('..');
end
if isdir('macros') then
chdir('macros');
exec('loadmacros.sce');
chdir('..');
end
if isdir('help') then
chdir('help');
exec('loadhelp.sce');
chdir('..');
end
clear mainpathL

上传你的工具箱

另外,建议使用http://sourceforge.net或者其他类似的开源平台来管理工具箱代码。SourceForge.net网站提供了管理软件的版本控制系统(SVN和CVS),主页空间,发布系统,Bug管理系统。它不仅可以让你的软件管理更规范,而且会让更多的人知道你的工具箱,让更多的人受益于你的劳动成果。