如何在FORCAL中添加对象
目 录
1 添加FcData可管理的对象 1.1 将要用到的一些函数 1.2 设计自己的对象MyObj1 1.3 创建一个dll工程:MyObj1 1.4 解析MyObj1.cpp 1.5 在OpenFc中进行演示 1.6 如何添加更多对象 1.7 效率测试 | 2 通过Forcal32.dll的输出函数添加对象 2.1 将要用到的一些函数 2.2 设计自己的对象MyObj2 2.3 创建一个dll工程:MyObj2 2.4 解析MyObj2.cpp 2.5 在OpenFc或其他Forcal程序中进行演示 2.6 如何添加更多对象 2.7 效率测试 |
开发者在软件中添加一些对象,然后通过脚本操作这些对象,这是许多程序员的梦想。像Matlab、VBScript、JScript、Lua、Python等脚本要实现这样的功能,好像是高级程序员才能完成的工作,甚至一些脚本根本就不支持此功能。我对这些脚本知之甚少,胡言乱语的依据是通过网上的资料,不当之处请指正。
在Forcal中添加对象,然后通过Forcal脚本操作这些对象,是很容易的一件事。看完本文后你可能忍不住要尝试一番,只要你用过像C/C++、Delphi等具有指针的语言。你不需要是一个资深的程序员,一个普通的编程者即可。本文将展示Forcal的强大的可扩充性和易扩充性,你大可不必被此句所吓倒,因为这是很容易实现的。
本文将使用VS2008 C++进行演示,但这些例子很容易移植到任何一个C++编译器中,虽然我对Delphi一知半解,但我相信转换成Delphi程序不会太难。为了方便,将通过动态库往Forcal中添加这些对象,实际上,通过主程序或任何一个模块添加这些对象都是可以的。如果你还未设计过动态库,需要先补上这一课,在windows系统中动态库是多么重要啊!
有两种方法可以将一个任意的对象添加到Forcal中,一种是添加FcData可管理的对象;另一种是通过Forcal32.dll的输出函数添加对象。
1 添加FcData可管理的对象 [返回页首]
在本例中,我们将用常量标识对象的类型,对象的成员也用相应的常量进行标识。本例子用整数表达式进行演示。
1.1 将要用到的一些函数 [返回页首]
本例子将要用到Forcal和FcData的几个函数,这里仅列出简单说明,详细说明请参考loadforcal.htm和fcdata.htm这两个文件,相信大家能找到这些函数的说明,看不懂也不要紧,可以通过后面的代码了解这些函数的具体应用。
Forcal输出函数:
1)测试FORCAL运行错误:int _stdcall TestRunErr(void);
如果检测到错误,我们就没有必要进行一些耗时较长的操作了。在向Forcal发送一个错误时,也需要检查有没有运行错误,若有,就没有必要发送了。
2)设置FORCAL运行错误:void _stdcall SetRunErr(int ErrType,char *FunName,int FunCode,int ForType,void *ForHandle);
有任何运行错误,都要通过该函数告诉Forcal。例如本例中,如果我们没有得到我们期望的对象,就调用该函数。
3)设置外部二级函数:int _stdcall SetFunction(fcINT FunType,char *FunName,void *Fun,fcINT ParaNum);
这是一个重要函数。我们注册的对象的所有操作都要通过一些函数来实现,这些函数要注册到Forcal中才能使用。这些函数就是通过SetFunction注册的。
4)设置常量:int _stdcall SetConst(fcINT ConstType,char *ConstName,void *ConstValue);
这是一个重要函数。为了方便,我们注册的对象类型要用一个标识符MyObj1表示,对象的两个成员分别用MyObj1_a和MyObj1_b表示(这种方法的优点是速度快,但标识符短时存在重名问题,除非像本例,用较长的标识符;另一种方法是用字符串标识成员名,可彻底解决重名问题,但速度慢,例子见后面),这些标识符标识的整数常量要通过SetConst注册到Forcal中才能使用。
5)删除常量或二级函数:void _stdcall DeleteConstOrFunction(fcINT Type,char *Name);
退出动态库之前要用该函数注销我们注册的常量和函数。
6)查找一个键:void * _stdcall SearchKey(char *KeyStr,fcINT ByteNum,fcINT KeyType);
我们将用该函数查找注册在Forcal中的函数RegFcDataType和IsFcData。这两个函数是FcData在加载时注册的。见下面的说明。
FcData输出函数:
7)注册FcData数据:fcINT _stdcall RegFcDataType(int Mode,void *NewFCD,void *DeleteFCD);
我们自己的对象就是通过该函数注册到Forcal的。但该函数是FcData提供的,因而我们的对象能由FcData进行管理。退出动态库之前,也要用该函数销毁注册的对象。
8)判断是否是FcData数据:fcINT _stdcall IsFcData(fcINT pFCD,fcINT &BasicType);
我们注册的对象是一个FcData数据,任何一个FcData数据都用一个指针表示,任何一个指针都是一个4字节整数。一个整数是不是我们要操作的对象,需要用这个函数进行判断。
1.2 设计自己的对象MyObj1 [返回页首]
对象可以是任意复杂的,但为了简单,我们定义的对象具有如下形式。
//定义自己的对象MyObj1,可以是任意复杂的对象
struct MyObj1
{
int MyObj1_a;
int MyObj1_b;
};
对象的两个成员用函数MyObj1::set和MyObj1::get进行存取。Forcal中的源代码看起来将是下面的样子:
Obj1=new(MyObj1); //申请对象Obj1
MyObj1::set(Obj1,MyObj1_a,11); //给对象Obj1的成员MyObj1_a赋值
MyObj1::get(Obj1,MyObj1_a); //获得对象Obj1的成员MyObj1_a的值
Obj1.MyObj1_b.MyObj1::set(22); //给对象Obj1的成员MyObj1_b赋值
Obj1.MyObj1_b.MyObj1::get(); //获得对象Obj1的成员MyObj1_b的值
delete(Obj1); //销毁对象Obj1
1.3 创建一个dll工程:MyObj1 [返回页首]
在VS2008 C++中创建->项目->Visual C++ ->win32->win32 项目->名称(输入MyObj1)->确定->下一步(不要点击完成)->应用程序类型(选Dll)->附加选项(选空项目)->完成。
好了,工程创建完毕,在VC++ 6.0中创建工程的方法与此类似,不赘述。接下来进行一些必要的设置:将活动解决方案配置为“Release”,项目属性中的字符集设置为“未设置”。
建立一个模块定义文件MyObj1.def,添加到工程中(项目->属性->链接器->输入->模块定义文件->MyObj1.def)。MyObj1.def的内容如下:
; MyObj1.def : Declares the module parameters for the DLL.
LIBRARY "MyObj1"
EXPORTS
; Explicit exports can go here
ForcalDll @1
该动态库仅一个输出函数ForcalDll:int _stdcall ForcalDll(HINSTANCE hFC,bool bInit);
添加头文件“forcal32.h”到工程中,这是编译Forcal程序所必须的。
添加头文件“mforcal32.h”到工程中,FcData的输出函数在该文件中定义。
建立一个C++源文件MyObj1.cpp添加到工程中。内容见下面。
生成解决方案,大功告成。
1.4 解析MyObj1.cpp [返回页首]
源程序由如下几部分组成:
1)必要的头文件。
2)一些全局变量,用以保存Forcal和FcData的输出函数。
3)对象MyObj1的定义。另外用IdMyObj1标识该对象,用IdMyObj1_a和IdMyObj1_b标识该对象的成员。需要注册到Forcal中才能使用。
4)5个函数定义。其中ForcalDll是Forcal扩展库必须要输出的函数,用于动态库的初始化和释放工作。NewMyObj1和DeleteMyObj1用于申请和销毁MyObj1对象,注册到FcData中由FcData调用。ifc_Set和ifc_Get两个函数可存取对象MyObj1的两个成员,注册到Forcal中由Forcal调用。这些函数在本程序中一个也没有调用,后面四个函数即windows设计中常说的回调函数。
1.5 在OpenFc中进行演示 [返回页首]
复制本例的源代码到C++编译器(如果用其他编译器需修改为相应的代码),编译生成MyObj1.dll。
将MyObj1.dll添加到OpenFc的工作区,加载MyObj1.dll(需要FcData32.dll的支持,FcData32.dll要在MyObj1.dll之前加载)。可演示如下源代码:
i:(::Obj1)= Obj1=new(MyObj1); //申请MyObj1对象,用模块变量Obj1传递对象指针
i:(::Obj1)= MyObj1::set(Obj1,MyObj1_a,11); //Forcal函数的一般调用格式
i:(::Obj1)= MyObj1::get(Obj1,MyObj1_a); //Forcal函数的一般调用格式
i:(::Obj1)= Obj1.MyObj1_b.MyObj1::set(22); //Forcal函数的对象成员运算符调用格式
i:(::Obj1)= Obj1.MyObj1_b.MyObj1::get(); //Forcal函数的对象成员运算符调用格式
i:(::Obj1)= delete(Obj1); //销毁对象Obj1
1.6 如何添加更多对象 [返回页首]
为每一个对象设计类似于NewMyObj1和DeleteMyObj1用于申请和销毁对象的函数,然后用函数RegFcDataType注册到FcData中就可以了。当然还要设计类似于ifc_Set和ifc_Get等操作对象的函数注册到Forcal中。
1.7 效率测试 [返回页首]
C++代码:
运行结果:
c= 395291538
time= 93 毫秒
在OpenFc中的Forcal代码:
运行结果:
i: 395291538
7.828 秒
Forcal的速度仅有C++速度的7.828/0.093=84.17204301075269分之一。可以看出,Forcal存取对象成员的效率是比较低的,故在Forcal代码中,凡是重复使用对象成员的地方,应该尽量用简单变量代替。再修改代码比较如下:
C++代码:
运行结果:
c= 395291538
time= 93 毫秒
在OpenFc中的Forcal代码:
运行结果:
i: 395291538
3.156 秒
Forcal的速度提高到C++速度的3.156/0.093=33.93548387096774分之一。
你也许会问,如果Forcal中有成千上万个对象,速度还这么快吗?是的,毫无疑问!不信?你可以测试。
2 通过Forcal32.dll的输出函数添加对象 [返回页首]
以这种方式添加的对象,仅需要Forcal32.dll的支持。与上面的例子不同,不需要用常量标识对象的类型,对象是用专用的函数申请的。对象的成员也不用常量标识,改用字符串标识,这是更普遍的方式,不存在重名问题,但对执行效率有影响。本例子用实数表达式进行演示。
2.1 将要用到的一些函数 [返回页首]
本例子将要用到Forcal的几个函数,这里仅列出简单说明,详细说明请参考loadforcal.htm文件,相信大家能找到这些函数的说明,看不懂也不要紧,可以通过后面的代码了解这些函数的具体应用。
Forcal输出函数:
1)测试FORCAL运行错误:int _stdcall TestRunErr(void);
如果检测到错误,我们就没有必要进行一些耗时较长的操作了。在向Forcal发送一个错误时,也需要检查有没有运行错误,若有,就没有必要发送了。
2)设置FORCAL运行错误:void _stdcall SetRunErr(int ErrType,char *FunName,int FunCode,int ForType,void *ForHandle);
有任何运行错误,都要通过该函数告诉Forcal。例如本例中,如果我们没有得到我们期望的对象,就调用该函数。
3)设置外部二级函数:int _stdcall SetFunction(fcINT FunType,char *FunName,void *Fun,fcINT ParaNum);
这是一个重要函数。我们注册的对象的所有操作都要通过一些函数来实现,这些函数要注册到Forcal中才能使用。这些函数就是通过SetFunction注册的。
4)删除常量或二级函数:void _stdcall DeleteConstOrFunction(fcINT Type,char *Name);
退出动态库之前要用该函数注销我们注册的函数。
5)获得表达式中的字符串:void _stdcall GetForStr(void *hFor,char *&ForStr,fcINT &StrMax);
由于对象的成员用字符串进行标识,因而将用到该函数。
6)查找一个键:void * _stdcall SearchKey(char *KeyStr,fcINT ByteNum,fcINT KeyType);
我们申请的对象注册到了Forcal中,在对一个对象操作前,需要用该函数判断对象是否存在。
7)插入一个键:int _stdcall InsertKey(char *KeyStr,fcINT ByteNum,fcINT KeyType,void *KeyValue,void (_stdcall *DelKey)(void *),void *&NowKey);
该函数可以将新的对象注册到Forcal中。
8)删除一个私有键:void _stdcall DeletePrivateKey(char *KeyStr,fcINT ByteNum,fcINT KeyType,void (_stdcall *DelKey)(void *));
该函数用于销毁一个对象。该函数太酷了,其他线程无法通过该函数销毁我们自己申请的对象。
9)锁定键的类型:int _stdcall LockKeyType(fcINT KeyType,void (_stdcall *DelKey)(void *),void (_stdcall *NullLock)(void));
要想用一种Forcal键值专门标识我们的对象类型,必须用该函数锁定该键值。
2.2 设计自己的对象MyObj2 [返回页首]
对象可以是任意复杂的,但为了简单,我们定义的对象具有如下形式。
//定义自己的对象MyObj2,可以是任意复杂的对象
struct MyObj2
{
double a;
double b;
};
对象的两个成员用函数MyObj2::set和MyObj2::get进行存取。Forcal中的源代码看起来将是下面的样子:
Obj2=NewMyObj2();//申请对象Obj2
MyObj2::set(Obj2,"a",11); //给对象Obj2的成员"a"赋值
MyObj2::get(Obj2,"a"); //获得对象Obj2的成员"a"的值
Obj2."b".MyObj2::set(22); //给对象Obj2的成员"b"赋值
Obj2."b".MyObj2::get(); //获得对象Obj2的成员"b"的值
DeleteMyObj2(Obj2);//销毁对象Obj2
2.3 创建一个dll工程:MyObj2 [返回页首]
在VS2008 C++中创建->项目->Visual C++ ->win32->win32 项目->名称(输入MyObj2)->确定->下一步(不要点击完成)->应用程序类型(选Dll)->附加选项(选空项目)->完成。
好了,工程创建完毕,在VC++ 6.0中创建工程的方法与此类似,不赘述。接下来进行一些必要的设置:将活动解决方案配置为“Release”,项目属性中的字符集设置为“未设置”。
建立一个模块定义文件MyObj2.def,添加到工程中(项目->属性->链接器->输入->模块定义文件->MyObj2.def)。MyObj2.def的内容如下:
; MyObj2.def : Declares the module parameters for the DLL.
LIBRARY "MyObj2"
EXPORTS
; Explicit exports can go here
ForcalDll @1
该动态库仅一个输出函数ForcalDll:int _stdcall ForcalDll(HINSTANCE hFC,bool bInit);
添加头文件“forcal32.h”到工程中,这是编译Forcal程序所必须的。
建立一个C++源文件MyObj2.cpp添加到工程中。内容见下面。
生成解决方案,大功告成。
2.4 解析MyObj2.cpp [返回页首]
源程序由如下几部分组成:
1)必要的头文件。
2)一些全局变量,用以保存Forcal的输出函数。
3)对象MyObj2的定义及标识该对象的私有键KeyMyObj2。
4)8个函数定义。其中ForcalDll是Forcal扩展库必须要输出的函数,用于动态库的初始化和释放工作。NullLock和DeleteMyObj2在向Forcal注册对象类型时将用到,由Forcal自动调用这两个函数。其余5个是向Forcal注册的二级函数,用于申请和销毁MyObj2对象及存取对象MyObj2的两个成员。这些函数在本程序中一个也没有调用,除ForcalDll之外的函数即windows设计中常说的回调函数。
注意:在实数表达式中,用一个实数的前4个字节保存一个对象指针。
2.5 在OpenFc或其他Forcal程序中进行演示 [返回页首]
复制本例的源代码到C++编译器(如果用其他编译器需修改为相应的代码),编译生成MyObj1.dll。
(1)将MyObj2.dll添加到OpenFc的工作区,加载MyObj2.dll。可演示如下源代码:
(::Obj2)= Obj2=NewMyObj2();//申请MyObj2对象,用模块变量Obj2传递对象指针
(::Obj2)= MyObj2::set(Obj2,"a",11); //给对象Obj2的成员"a"赋值,Forcal函数的一般调用格式
(::Obj2)= MyObj2::get(Obj2,"a"); //获得对象Obj2的成员"a"的值,Forcal函数的一般调用格式
(::Obj2)= Obj2."b".MyObj2::set(22); //给对象Obj2的成员"b"赋值,Forcal函数的对象成员运算符调用格式
(::Obj2)= Obj2."b".MyObj2::get(); //获得对象Obj2的成员"b"的值,Forcal函数的对象成员运算符调用格式
(::Obj2)= DeleteMyObj2(Obj2);//销毁对象Obj2
(2)用软件包forcal32.rar中的“Project1.exe”进行演示:先将“example.dll”改为其他名字,然后将“MyObj2.dll”改名为“example.dll”,用“Project1.exe”加载“example.dll”后就可以演示了。演示完要恢复原先“example.dll”的名字。演示源代码如下:
(::Obj2)= Obj2=NewMyObj2();
(::Obj2)= MyObj2::set(Obj2,"a",11);
(::Obj2)= MyObj2::get(Obj2,"a");
(::Obj2)= Obj2."b".MyObj2::set(22);
(::Obj2)= Obj2."b".MyObj2::get();
(::Obj2)= DeleteMyObj2(Obj2);
(3)用软件包mforcal32.rar中的文件夹“例子”中的“Project1.exe”、“SimpleOpenFC.exe”或“工程1.exe”进行演示:在这三个程序中加载“MyObj2.dll”即可进行演示。演示源代码如下:
(::Obj2)= Obj2=NewMyObj2();//申请MyObj2对象,用模块变量Obj2传递对象指针
(::Obj2)= MyObj2::set(Obj2,"a",11); //给对象Obj2的成员"a"赋值,Forcal函数的一般调用格式
(::Obj2)= MyObj2::get(Obj2,"a"); //获得对象Obj2的成员"a"的值,Forcal函数的一般调用格式
(::Obj2)= Obj2."b".MyObj2::set(22); //给对象Obj2的成员"b"赋值,Forcal函数的对象成员运算符调用格式
(::Obj2)= Obj2."b".MyObj2::get(); //获得对象Obj2的成员"b"的值,Forcal函数的对象成员运算符调用格式
(::Obj2)= DeleteMyObj2(Obj2);//销毁对象Obj2
2.6 如何添加更多对象 [返回页首]
(1)对每一个要添加的对象,用函数LockKeyType锁定一个键来进行标识,就像本例子中对象MyObj2的实现一样。
(2)创建一种可存储多个对象的结构:
struct MyObj
{
int ObjType; //标识对象的类型
void *Obj; //指向对象的指针
};
在该方式中,仅需用函数LockKeyType锁定一个键来标识MyObj。
无论哪种方式,都需要为每种对象设计一些专门的操作函数,同时需要将这些函数注册到Forcal中。
2.7 效率测试 [返回页首]
C++代码:
运行结果:
c= 1593979862.811235
time= 93 毫秒
在OpenFc中的Forcal代码:
运行结果:
1593979862.811235
58.359 秒
这次测试Forcal的速度不能令人满意,仅有C++速度的58.359/0.093=627.5161290322581分之一。可以看出,Forcal存取对象成员的效率是比较低的,故在Forcal代码中,凡是重复使用对象成员的地方,应该尽量用简单变量代替。再修改代码比较如下:
C++代码:
运行结果:
c= 1593979862.811235
time= 93 毫秒
在OpenFc中的Forcal代码:
运行结果:
1593979862.811235
13.375 秒
Forcal的速度提高到C++速度的13.375/0.093=143.8172043010753分之一,但仍不能令人满意。本例(MyObj2.dll)比上例(MyObj1.dll)速度慢的原因如下:
1)本例用字符串标识对象的成员,上例用常量进行标识。
2)本例验证对象指针时使用SearchKey函数,而上例验证对象指针时使用IsFcData函数。尽管SearchKey也足够快了,但IsFcData的内部进行了优化,故速度仍有差别。
你也许会问,如果Forcal中有成千上万个对象,速度还这么快吗?是的,毫无疑问!不信?你可以测试。
版权所有© Forcal数学软件 2002-2009,保留所有权利
E-mail: forcal@sina.com QQ:630715621
最近更新: <!--webbot bot="Timestamp" S-Type="EDITED" S-Format="%Y年%m月%d日" startspan -->2009年01月03日<!--webbot bot="Timestamp" i-checksum="1340" endspan -->