@import url(http://www.blogjava.net/CuteSoft_Client/CuteEditor/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css);
本次通过loadRunner录制SQL Server介绍一下如何测试一个sql语句或存储过程的执行性能。
主要分如下几个步骤完成:
第一步、测试准备
第二步、配置ODBC数据源
第三步、录制SQL语句在Sql Server查询分析器中的运行过程
第四步、优化录制脚本,设置事务
第五步、改变查询数量级查看SQL语句的性能
第六步、在controller中运行脚本
下面开始具体的介绍:
测试准备阶段我们首先要确认测试数据库服务器:我们可以在本地安装SQL SERVER数据库服务端及客户端,也可以确定一台装好的SQL SERVER服务器。
接下来,准备测试数据:对数据库测试时我们要考虑的不是SQL语句是否能够正确执行,而是在某数量级的情况下SQL语句的执行效率及数据库服务的运行情况,所以我们分别准备不同数量级的测试数据,即根据实际的业务情况预估数据库中的记录数,在本次讲解中我们不考虑业务逻辑也不考虑数据表之间的关系,我们只建立一张表,并向此表中加入不同数量级的数据,如分别加入1000条、10000条、50000条、100000条数据查看某SQL语句的执行效率。
在查询分析器中运行如下脚本:
--创建测试数据库
create database loadrunner_test;
use loadrunner_test
--创建测试数据表
create table test_table
(username varchar(50),sex int,age int,address varchar(100),post int)
--通过一段程序插入不同数量级的记录,具体的语法在这里就不多说了
declare @i int
set @i=0
while @i<1000 //循环1000次,可以根据测试数据情况改变插入条数
begin
BEGIN TRAN T1
insert into test_table (username,sex,age,address,post) values ('户瑞海'+cast(@i as varchar),@i-1,@i+1,'北京市和平里'+cast(@i as varchar)+'号',123456);
IF @@ERROR <> 0
begin
rollback;
select @@error
end
else
begin
commit;
set @i = @i+1
end
end
|
好了,执行完上述语句后,建立的数据表中已经有1000条记录了,下面进行第二步的操作,配置ODBC数据源,为了能让loadrunner能够通过ODBC协议连接到我们建立的SQL SERVER数据路,我们需要在本机上建立ODBC数据源,建立方法如下:
控制面板—性能和维护—管理工具—数据源(ODBC)--添加,在列表中选择SQL SERVER点击完成,根据向导输入数据源名称,链接的服务器,下一步,输入链接数据库的用户名和密码,更改链接的数据库,完成ODBC的配置,如果配置正确的话,在最后一步点击“测试数据源”,会弹出测试成功的提示。
配置好ODBC数据源后就要录制SQL语句在查询分析器中的执行过程了:
1、 打开loadrunner,选择ODBC协议
2、 在start recording中的application type 选择win32 application;program to record中录入SQL SERVER查询分析器的路径“..\安装目录\isqlw.exe”
3、 开始录制,首先通过查询分析器登录SQL SERVER,在打开的查询分析器窗口中输入要测试的SQL语句,如“select * from test_table;”
4、 在查询分析器中执行该语句,执行完成后,结束录制
好了,现在就可以看到loadrunner生成的脚本了(由于脚本过长,在这里就不粘贴了,有需要的朋友可以加我QQ,我把脚本发给你们),通过这些语句,我们可以看出,登录数据库的过程、执行SQL语句的过程。
接下来,我们来优化脚本,我们分别为数据库登录部分和执行SQL语句的部分加一个事物,在增加一个double的变量获取事务执行时间,简单内容如下:
Action()
{ double trans_time; //定义一个double型变量用来保存事务执行时间
lr_start_transaction("sqserver_login"); //设置登录事务的开始
lrd_init(&InitInfo, DBTypeVersion); //初始化链接(下面的都是loadrunner生成的脚本了,大家可以通过帮助查到每个函数的意思)
lrd_open_context(&Ctx1, LRD_DBTYPE_ODBC, 0, 0, 0);
lrd_db_option(Ctx1, OT_ODBC_OV_ODBC3, 0, 0);
lrd_alloc_connection(&Con1, LRD_DBTYPE_ODBC, Ctx1, 0 /*Unused*/, 0);
………………
trans_time=lr_get_transaction_duration( "sqserver_login" ); //获得登录数据库的时间
lr_output_message("sqserver_login事务耗时 %f 秒", trans_time); //输出该时间
lr_end_transaction("sqserver_login", LR_AUTO); //结束登录事务
lr_start_transaction("start_select");//开始查询事务
lrd_cancel(0, Csr2, 0 /*Unused*/, 0);
lrd_stmt(Csr2, "select * from test_table;\r\n", -1, 1, 0 /*None*/, 0);//此句为执行的SQL
lrd_bind_cols(Csr2, BCInfo_D42, 0);
lrd_fetch(Csr2, -10, 1, 0, PrintRow24, 0);
……………..
trans_time=lr_get_transaction_duration( "start_select" ); //获得该SQL的执行时间
lr_output_message("start_select事务耗时 %f 秒", trans_time); //输出该时间
lr_end_transaction("start_select", LR_AUTO); //结束查询事务
|
优化后,在执行上述脚本后,就可以得到登录到数据库的时间及运行select * from test_table这条语句的时间了,当然我们也可以根据实际情况对该条语句进行参数化,可以测试多条语句的执行时间,也可以将该语句改为调用存储过程的语句来测试存储过程的运行时间。
接下来把该脚本在controller中运行,设置虚拟用户数,设置集合点,这些操作我就不说了,但是值得注意的是,没有Mercury 授权的SQL SERVER用户license,在运行该脚本时回报错,提示“You do not have a license for this Vuser type.
Please contact Mercury Interactive to renew your license.”我们公司穷啊买不起loadrunner,所以我也无法继续试验,希望有license朋友们监控一下运行结果!
最起码在VUGen中运行该脚本我们可以得到任意一个SQL语句及存储过程的执行时间,如果我们测试的B/S结构的程序,我们也可以通过HTML协议录制的脚本在CONTROLLER中监控SQL SERVER服务器的性能情况,这样两方面结合起来就可以对数据库性能做一个完整的监控了。
本人对LOADRUNNER也是在摸索中,如果文章有写的不对,或理解错误的地方请指出,不甚感激。(以上言论仅代表作者的个人观点,不代表51Testing观点)
/*需要的表结构如下
CREATE TABLE `test_data` (
`order_id` BIGINT UNSIGNED NOT NULL COMMENT 'Order numbers. Must be unique.',
`status` BOOL NOT NULL DEFAULT '0' COMMENT 'Whether data has been used or not. A value of 0 means FALSE.',
`date_used` DATETIME NULL COMMENT 'Date/time that the data was used.',
UNIQUE (
`order_id`
)
) ENGINE = innodb COMMENT = 'LoadRunner test data';
*/
Action()
{
int rc;
int db_connection; // 数据库连接
int query_result; // 查询结果集 MYSQL_RES
char** result_row; // 查询的数据衕
char *server = "localhost";
char *user = "root";
char *password = "123456";
char *database = "test";
int port = 3306;
int unix_socket = NULL;
int flags = 0;
// 找到libmysql.dll的所在位置.
rc = lr_load_dll("C:\\Program Files\\MySQL\\MySQL Server 5.1\\bin\\libmysql.dll");
if (rc != 0) {
lr_error_message("Could not load libmysql.dll");
lr_abort();
}
// 创建MySQL对象
db_connection = mysql_init(NULL);
if (db_connection == NULL) {
lr_error_message("Insufficient memory");
lr_abort();
}
// 连接到MySQL数据库
rc = mysql_real_connect(db_connection, server, user, password, database, port, unix_socket, flags);
if (rc == NULL) {
lr_error_message("%s", mysql_error(db_connection));
mysql_close(db_connection);
lr_abort();
}
// 向数据库插入数据
// 此处的 {ORDER_ID} 是一个参数,简单测试时可以用一个常数代替
lr_save_string (lr_eval_string("INSERT INTO test_data (order_id) VALUES ({ORDER_ID})"),"paramInsertQuery");
rc = mysql_query(db_connection, lr_eval_string("{paramInsertQuery}"));
if (rc != 0) {
lr_error_message("%s", mysql_error(db_connection));
mysql_close(db_connection);
lr_abort();
}
// 从数据库读取一个数据并显示
rc = mysql_query(db_connection, "SELECT order_id FROM test_data WHERE status IS FALSE LIMIT 1");
if (rc != 0) {
lr_error_message("%s", mysql_error(db_connection));
mysql_close(db_connection);
lr_abort();
}
query_result = mysql_use_result(db_connection);
if (query_result == NULL) {
lr_error_message("%s", mysql_error(db_connection));
mysql_free_result(query_result);
mysql_close(db_connection);
lr_abort();
}
// 如果结果集包含多行数据,需要多次调用 mysql_fetch_row 直到返回NULL
result_row = (char **)mysql_fetch_row(query_result);
if (result_row == NULL) {
lr_error_message("Did not expect the result set to be empty");
mysql_free_result(query_result);
mysql_close(db_connection);
lr_abort();
}
// 保存参数,用于删除这行数据
lr_save_string(result_row[0], "paramOrderID");
lr_output_message("Order ID is: %s", lr_eval_string("{paramOrderID}"));
mysql_free_result(query_result);
// 在事务里更新一行数据,需要用InnoDB引擎
rc = mysql_query(db_connection, "BEGIN"); //启动事务
if (rc != 0) {
lr_error_message("%s", mysql_error(db_connection));
mysql_close(db_connection);
lr_abort();
}
// 使用 "FOR UPDATE" 锁住要更新的数据行
rc = mysql_query(db_connection, "SELECT order_id FROM test_data WHERE status IS FALSE LIMIT 1 FOR UPDATE");
if (rc != 0) {
lr_error_message("%s", mysql_error(db_connection));
mysql_close(db_connection);
lr_abort();
}
query_result = mysql_use_result(db_connection);
if (query_result == NULL) {
lr_error_message("%s", mysql_error(db_connection));
mysql_free_result(query_result);
mysql_close(db_connection);
lr_abort();
}
result_row = (char **)mysql_fetch_row(query_result);
if (result_row == NULL) {
lr_error_message("没有查询到结果");
mysql_free_result(query_result);
mysql_close(db_connection);
lr_abort();
}
lr_save_string(result_row[0], "paramOrderID");
lr_output_message("Order ID is: %s", lr_eval_string("{paramOrderID}"));
mysql_free_result(query_result);
lr_save_string(lr_eval_string("UPDATE test_data SET status=TRUE, date_used=NOW() WHERE order_id='{paramOrderID}'"),"paramUpdateQuery");
rc = mysql_query(db_connection, lr_eval_string("{paramUpdateQuery}"));
if (rc != 0) {
lr_error_message("%s", mysql_error(db_connection));
mysql_close(db_connection);
lr_abort();
}
rc = mysql_query(db_connection, "COMMIT"); // 提交事务
if (rc != 0) {
lr_error_message("%s", mysql_error(db_connection));
mysql_close(db_connection);
lr_abort();
}
// 再次查找数据,应该为空了,因为前面的事务更新了标志
rc = mysql_query(db_connection, "SELECT order_id FROM test_data WHERE status IS FALSE LIMIT 1");
if (rc != 0) {
lr_error_message("%s", mysql_error(db_connection));
mysql_close(db_connection);
lr_abort();
}
query_result = mysql_use_result(db_connection);
if (query_result == NULL) {
lr_error_message("%s", mysql_error(db_connection));
mysql_free_result(query_result);
mysql_close(db_connection);
lr_abort();
}
result_row = (char **)mysql_fetch_row(query_result);
if (result_row == NULL) {
lr_output_message("Result set is empty as expected");
mysql_free_result(query_result);
} else {
lr_error_message("Did not expect the result set to contain any rows");
mysql_free_result(query_result);
mysql_close(db_connection);
lr_abort();
}
// 删除数据
lr_save_string(lr_eval_string("DELETE FROM test_data WHERE order_id = '{paramOrderID}'"),"paramDeleteQuery");
rc = mysql_query(db_connection, lr_eval_string("{paramDeleteQuery}"));
if (rc != 0) {
lr_error_message("%s", mysql_error(db_connection));
mysql_close(db_connection);
lr_abort();
}
// 释放MySQL资源
mysql_close(db_connection);
return 0;
}
本文章来源于西盟软件站【www.zmke.com】详细地址:http://www.zmke.com/article/175/data/2009/2009070819880.html
(转)LoadRunner测试过程中调用dll文件的制作与使用
上一篇 / 下一篇 2009-01-17 16:35:36 / 个人分类:LoadRunner
在Windows操作系统中使用DLL有很多优点,最主要的一点是多个应用程序、甚至是不同语言编写的应用程序可以共享一个DLL文件,真正实现了资源"共享",大大缩小了应用程序的执行代码,更加有效的利用了内存;使用DLL的另一个优点是DLL文件作为一个单独的程序模块,封装性、独立性好,在软件需要升级的时候,开发人员只需要修改相应的DLL文件就可以了,而且,当DLL中的函数改变后,只要不是参数的改变,程序代码并不需要重新编译。这在编程时十分有用,大大提高了软件开发和维护的效率。
在LR下也可以直接调用动态链接库文件,针对一些使用LR脚本编写比较烦琐的方法可以考虑此方法(比如各种加解密算法的实现,数据库的操作等),并能够使测试脚本简单明了。鉴于此,本人初次尝试了如何制作DLL文件并在LR中使用。
1、dll文件的制作
在Visual C++6.0开发环境下,打开File-New-Project选项,可以选择Win32 Dynamic-Link Library来创建一个名为dllfortest的空的dll工程(这只是方法之一)。
在该项目中新建一个dllfortest.h和dllfortest.cpp文件,文件的内容如下:
//dllfortest.h
extern "C" _declspec(dllexport) int Max(int a, int b, int c);
extern "C" _declspec(dllexport) int Min(int a, int b, int c);
//dllfortest.cpp 包含一个计算三个整数中最大值和最小值的方法
#include"dllfortest.h"
int Max(int a, int b, int c)
{
int Res;
Res = (a>b? a:b)>c? (a>b? a:b):c;
return Res;
}
int Min(int a, int b, int c)
{
int Res;
Res = (a>b? b:a)>c? c:(a>b? b:a);
return Res;
}
该动态链接库编译成功后,打开dllfortest工程目录下的debug目录下,可以看到生成了一个dllfortest.dll文件,这就是我们想要的文件。
2、dll文件在LR中的调用
打开LR VU Generator,选择C Vuser协议(或其他支持C的协议),进入编辑界面。Action部分如下所示:
Action()
{
lr_load_dll("dllfortest.dll");
lr_message("Max Result is %d",Max(100,200,67));
lr_message("Min Result is %d",Min(55,97,63));
return 0;
}
将dllfortest.dll文件复制到脚本所在的目录(如果不复制的话,lr_load_dll的参数应该写成dll文件的绝对路径),如果编译没有报错的话,就可以直接运行了。
运行结果如下:
Virtual User scrīpt started
Starting action vuser_init.
Ending action vuser_init.
Running Vuser...
Starting iteration 1.
Starting action Action.
Max Result is 200
Min Result is 55
Ending action Action.
Ending iteration 1.
Ending Vuser...
Starting action vuser_end.
Ending action vuser_end.
Vuser Terminated.
如果在脚本中用到了DLL文件中并不存在的方法名时,编译也能通过,但是运行时LR会报错,提示不存在该方法
比如我们在脚本中添加一句:
lr_message("Min Result is %d",Sum(55,97,63)); //Sum方法并不存在
运行时错误信息如下:
Action.c(7): Error: C interpreter run time error: Action.c (7): Error -- Unresolved symbol : Sum.
Action.c(7): Notify: CCI trace: Compiled_code(0): Action()
.
这个例子并没有体现出DLL文件调用的优点,因为调用的方法在LR中用C直接就可以完成,但涉及到LR中完不成的任务时,或许dll文件的作用就体现出来了
example:
LoadRunner提供了功能强大的API集合,足够应付大多数性能测试的需求。但在某些情况下,这些API仍然有覆盖不到的地方。例如,我们有一个WEB应用,该应用有一个页面输入用户的信息,为了安全起见,用户输入的信息在提交之前都要先进行加密处理,加密处理通过本地的COM组件实现。
对这个要求而言,LoadRunner的现有API不能提供直接支持,因为LoadRunner在录制脚本时只录制数据交互,因此,COM的加密处理过程是不能录制下来的。在LoadRunner的脚本中,可能只有类似以下的语句描述了这个过程:
…………
web_url("userinfo",
"URL=http://testweb/userinfo.aspx",
"TargetFrame=",
"Resource=0",
"Referer=",
LAST);
web_submit_form("login ",
"Snapshot=t4.inf",
ITEMDATA,
"Name=username", "Value=4e92Sh6d394g", ENDITEM,
"Name=password", "Value=932A2hf34U18", ENDITEM,
LAST);
…………
从脚本可以看到,输入的数据是加密后的数据,但LR没有录制到加密过程。
假设加密函数所在的DLL名为security.dll,用于加密的函数名为encode,则一种可能的对脚本的修改方法如下代码所示。
…………
char *encode_username, *encode_password, *orgin_username, *orgin_password;
char* uservalue, passvalue;
int ret;
……
web_url("userinfo",
"URL=http://testweb/userinfo.aspx",
"TargetFrame=",
"Resource=0",
"Referer=",
LAST);
orgin_username = lr_eval_string(“{ username }”); //获取参数的值
orgin_password = lr_eval_string(“{ password }”);
ret = lr_load_dll(“security.dll”); //加载DLL库
encode(origin_username, encode_username); //调用encode函数
encode(origin_password, encode_password);
sprintf(uservalue, “Value=%s”, encode_username);
sprintf(passvalue, “value=%s”, encode_password);
web_submit_form("login ",
"Snapshot=t4.inf",
ITEMDATA,
"Name=username", uservalue, ENDITEM,
"Name=password", passvalue, ENDITEM,
LAST);
注意:有些脚本录制需要相应的patch的支持,如录制DotNet编写的应用程序你需要把lr78安装盘\ Patches\Trap_for_.net_patch文件夹中trpfnc32.32dll拷贝到loadrunner\bin路径下,才能正常工作。
场景介绍
最近在做类似于QQ的通信工具的性能测试时发现了一些问题,现总结出来与大家分享一下。希望大家在使用LoadRunner时不仅仅停在只是录制/播放脚本,而全面提升脚本的编程技术,解决复杂场景。
本次测试中碰到的问题是这样的,在消息的传送过程中遇到了DEC加密的过程,LoadRunner录制到的全是加密的消息,比如我录制了某一个用户的登陆,发送消息,退出,但由于是加密的,只能单个用户使用,但如果我想并发多少个用户就存在很多问题,最直接的一个问题就是用户名是加密的,密码是加密的,当然你可以说让程序那里注掉加密的代码进行明码的测试,当然也是一种办法。但程序组提出了要使用更真实的方法来模拟,这时就必需使用下面介绍的方法。
一开始是直接把API移植到LoadRunner中来,不过由于加密算法异常复杂,有几层循环,而脚本是解释执行的,进行一次加密运算可能需要好几分钟,当然在脚本里可以把脚本本身运行的时间去掉,但这样做显然没有直接调用DLL来的效率高。由于程序组比较忙,所以无法提供DLL给测试,所以测试组完成了DLL的编写,并在LoadRunner中调用成功,高效的完成了用户信息加密,参数关联,成功的完成了测试。
动态链接库的编写
在Visual C++6.0开发环境下,打开FileNewProject选项,可以选择Win32 Dynamic-Link Library建立一个空的DLL工程。
Win32 Dynamic-Link Library方式创建Non-MFC DLL动态链接库
每一个DLL必须有一个入口点,这就象我们用C编写的应用程序一样,必须有一个WINMAIN函数一样。在Non-MFC DLL中DllMain是一个缺省的入口函数,你不需要编写自己的DLL入口函数,用这个缺省的入口函数就能使动态链接库被调用时得到正确的初始化。如果应用程序的DLL需要分配额外的内存或资源时,或者说需要对每个进程或线程初始化和清除操作时,需要在相应的DLL工程的.CPP文件中对DllMain()函数按照下面的格式书写。
BOOL APIENTRY DllMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)
{
switch( ul_reason_for_call )
{
case DLL_PROCESS_ATTACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
default:
break;
}
return TRUE;
}
参数中,hMoudle是动态库被调用时所传递来的一个指向自己的句柄(实际上,它是指向_DGROUP段的一个选择符);ul_reason_for_call是一个说明动态库被调原因的标志,当进程或线程装入或卸载动态链接库的时候,操作系统调用入口函数,并说明动态链接库被调用的原因,它所有的可能值为:DLL_PROCESS_ATTACH: 进程被调用、DLL_THREAD_ATTACH: 线程被调用、DLL_PROCESS_DETACH: 进程被停止、DLL_THREAD_DETACH: 线程被停止;lpReserved为保留参数。到此为止,DLL的入口函数已经写了,剩下部分的实现也不难,你可以在DLL工程中加入你所想要输出的函数或变量了。
我们已经知道DLL是包含若干个函数的库文件,应用程序使用DLL中的函数之前,应该先导出这些函数,以便供给应用程序使用。要导出这些函数有两种方法,一是在定义函数时使用导出关键字_declspec(dllexport),另外一种方法是在创建DLL文件时使用模块定义文件.Def。需要读者注意的是在使用第一种方法的时候,不能使用DEF文件。下面通过两个例子来说明如何使用这两种方法创建DLL文件。
1)使用导出函数关键字_declspec(dllexport)创建MyDll.dll,该动态链接库中有两个函数,分别用来实现得到两个数的最大和最小数。在MyDll.h和MyDLL.cpp文件中分别输入如下原代码:
//MyDLL.h
extern "C" _declspec(dllexport) int desinit(int mode);
extern "C" _declspec(dllexport) void desdone(void);
extern "C" _declspec(dllexport) void des_setkey(char *subkey, char *key);
extern "C" _declspec(dllexport) void endes(char *block, char *subkey);
extern "C" _declspec(dllexport) void dedes(char *block, char *subkey);
//MyDll.cpp
#include"MyDll.h"
//这里我用了比较大小的函数代替了我要实现的函数
int desinit(int a, int b)
{
if(a>="b)return" a;
else
return b;
}
int desdone(int a, int b)
{
if(a>="b)return" b;
else
return a;
}
该动态链接库编译成功后,打开MyDll工程中的debug目录,可以看到MyDll.dll、MyDll.lib两个文件。LIB文件中包含DLL文件名和DLL文件中的函数名等,该LIB文件只是对应该DLL文件的"映像文件",与DLL文件中,LIB文件的长度要小的多,在进行隐式链接DLL时要用到它。读者可能已经注意到在MyDll.h中有关键字"extern C",它可以使其他编程语言访问你编写的DLL中的函数。
LoadRunner调用动态链接库
上面完成动态链接库开发后,下面就介绍动态链接库如何被LoadRunner进行调用,其实也是很简单的。在LoadRunner中的DLL调用有局部调用与全局调用,下面介绍局部调用。
首先把你编译的DLL放在脚本路径下面,这里是MyDll.dll,MyDll.lib.然后在Action中使用lr_load_dll("MYDll.dll"),此函数可以把DLL加载进来,让你调用DLL里面的函数,而DLL中的运算是编译级的,所以效率极高,代码样例如下:
#include "lrs.h"
Action()
{
//
int nRet = 6;
char srckey[129];
memset(srckey, 'a', 128);
lr_message(lr_eval_string(srckey));
lr_load_dll("MyDLL.dll");
nRet = desinit(5,8);
lr_message("比较的结果为%d",nRet);
return 0;
}
运行结果
比较的结果为8
全局的动态链接库的调用则需要修改mdrv.dat,路径在LoadRunner的安装目录下面(LoadRunner/dat directory);在里面修改如例:
[WinSock]
ExtPriorityType="protocol"
WINNT_EXT_LIBS="wsrun32.dll"
WIN95_EXT_LIBS="wsrun32.dll"
LINUX_EXT_LIBS="liblrs.so"
SOLARIS_EXT_LIBS="liblrs.so"
HPUX_EXT_LIBS="liblrs.sl"
AIX_EXT_LIBS="liblrs.so"
LibCfgFunc="winsock_exten_conf"
UtilityExt="lrun_api"
ExtMessageQueue="0"
ExtCmdLineOverwrite="-WinInet" No
ExtCmdLineConc="-UsingWinInet" No
WINNT_DLLS="user_dll1.dll," user_dll2.dll, ...
//最后一行是加载你需要的DLL
这样你就可以在LR中随意的调用程序员写的API函数,进行一些复杂的数据加密,准备的一些操作,进行复杂的测试。同时如果你觉的有大量高复杂的运算也可以放在DLL中进行封装,以提高效率。
|