该版本可以使用共享内存来存储zengl脚本的编译缓存。在config.zl配置文件中增加了和共享内存相关的配置,当前版本还新增了magick模块,可以进行图像相关的操作...

    页面导航: 项目下载地址:

    zenglServer源代码的相关地址:https://github.com/zenglong/zenglServer  当前版本对应的tag标签为:v0.11.0

zenglServer v0.11.0:

    该版本可以使用共享内存来存储zengl脚本的编译缓存。在config.zl配置文件中增加了和共享内存相关的配置:

def TRUE 1;
def FALSE 0;
def KBYTE 1024;

....................................................

zengl_cache_enable = TRUE; // 是否开启zengl脚本的编译缓存,默认为FALSE即不开启,设置为TRUE可以开启编译缓存

shm_enable = TRUE; // 是否将zengl脚本的编译缓存放入共享内存
shm_min_size = 300 * KBYTE; // 需要放进共享内存的缓存的最小大小,只有超过这个大小的缓存才放入共享内存中,以字节为单位


    通过将shm_enable设置为TRUE(前提是zengl_cache_enable也设置为了TRUE),可以开启共享内存。同时,还有一个shm_min_size的配置,用于表示需要放进共享内存的编译缓存的最小大小,只有超过这个大小的缓存才放入共享内存中,以字节为单位,默认是300K字节,也就是当编译缓存的大小超过300K时,才会放入共享内存,小于300K的还是使用普通的文件缓存的方式。

    在main.c文件中,可以看到和共享内存相关的代码:

....................................................
#define SHM_MIN_SIZE (300 * 1024) // 如果配置文件中没有设置shm_min_size时,就使用该宏的值作为需要放进共享内存的缓存的最小大小(以字节为单位)
....................................................

static long config_shm_enable; // 是否将zengl脚本的编译缓存放入共享内存
static long config_shm_min_size; // 需要放进共享内存的缓存的最小大小,只有超过这个大小的缓存才放入共享内存中,以字节为单位

....................................................

/**
 * zenglServer启动时会执行的入口函数
 */
int main(int argc, char * argv[])
{
	// 获取配置文件中设置的shm_enable即是否开启共享内存来存储编译缓存
	if(zenglApi_GetValueAsInt(VM,"shm_enable", &config_shm_enable) < 0)
		config_shm_enable = ZL_EXP_FALSE;

	// 获取配置文件中设置的shm_min_size的值,也就是开启共享内存的情况下,需要放进共享内存的缓存的最小大小,只有超过这个大小的缓存才放入共享内存中,以字节为单位
	if(zenglApi_GetValueAsInt(VM,"shm_min_size", &config_shm_min_size) < 0)
		config_shm_min_size = SHM_MIN_SIZE;

	....................................................

	// 将远程调试相关的配置,以及是否开启zengl脚本的编译缓存的配置,记录到日志中
	write_to_server_log_pipe(WRITE_TO_LOG, "remote_debug_enable: %s remote_debugger_ip: %s remote_debugger_port: %ld"
			" zengl_cache_enable: %s shm_enable: %s shm_min_size: %ld\n",
			config_remote_debug_enable ? "True" : "False",
			config_remote_debugger_ip,
			config_remote_debugger_port,
			config_zengl_cache_enable ? "True" : "False",
			config_shm_enable ? "True" : "False",
			config_shm_min_size);
	....................................................
}


    上面代码中,在zenglServer启动的入口函数里,会先获取配置文件中的shm_enable和shm_min_size的值,并将它们分别赋值给config_shm_enable和config_shm_min_size静态全局变量,这样,其他函数中需要获取和共享内存相关的配置时,就可以直接通过这两个变量来获取到了。

    在重利用编译缓存时,就会根据上面那两个配置来处理需要使用共享内存的情况,下面的代码也位于main.c中:

/**
 * 尝试重利用full_path脚本文件对应的缓存数据,cache_path表示缓存数据所在的文件路径
 * 如果缓存文件不存在,则会重新生成缓存文件,如果full_path脚本文件内容发生了改变或者其加载的脚本文件内容发生了改变,也会重新生成缓存
 * 外部调用者通过is_reuse_cache变量的值来判断是否需要生成缓存文件,如果is_reuse_cache为ZL_EXP_FALSE,就表示没有重利用缓存,则需要生成缓存文件
 * 如果is_reuse_cache为ZL_EXP_TRUE,则说明重利用了缓存,不需要再生成缓存文件了
 */
static void main_try_to_reuse_zengl_cache(ZL_EXP_VOID * VM, char * cache_path, char * full_path, ZL_EXP_BOOL * is_reuse_cache)
{
	FILE * ptr_fp;
	ZL_EXP_VOID * cachePoint = NULL;
	ZENGL_EXPORT_API_CACHE_TYPE * api_cache;
	ZL_EXP_LONG offset, cache_mtime, file_mtime;
	ZL_EXP_BYTE * mempoolPtr;
	ZL_EXP_CHAR ** filenames, * filename;
	ZL_EXP_INT cacheSize, i;
	struct stat stat_result;
	(* is_reuse_cache) = ZL_EXP_FALSE;
	if(stat(cache_path, &stat_result)==0) { // 获取缓存文件的修改时间
		cache_mtime = (ZL_EXP_LONG)stat_result.st_mtime;
	}
	else { // 获取文件的状态信息失败,可能缓存文件不存在,需要重新编译生成缓存,直接返回
		write_to_server_log_pipe(WRITE_TO_PIPE, "can not stat cache file: \"%s\", maybe no such cache file [recompile]\n", cache_path);
		return ;
	}
	if(stat(full_path, &stat_result)==0) { // 获取主执行脚本的修改时间
		file_mtime = (ZL_EXP_LONG)stat_result.st_mtime;
		if(file_mtime >= cache_mtime) { // 如果主执行脚本的修改时间大于等于缓存数据的修改时间,则说明主执行脚本的内容发生了改变,需要重新编译生成新的缓存
			write_to_server_log_pipe(WRITE_TO_PIPE, "\"%s\" mtime:%ld [changed] [recompile]\n", full_path, file_mtime);
			return;
		}
	}
	else { // 主执行脚本不存在,直接返回
		write_to_server_log_pipe(WRITE_TO_PIPE, "warning stat script file: \"%s\" failed, maybe no such file! [recompile]\n", full_path);
		return ;
	}
	// 打开缓存文件
	if((ptr_fp = fopen(cache_path, "rb")) == NULL) {
		write_to_server_log_pipe(WRITE_TO_PIPE, "no cache file: \"%s\" [recompile]\n", cache_path);
		return ;
	}
	flock(ptr_fp->_fileno, LOCK_SH); // 加文件共享锁,如果有进程在修改缓存内容的话,所有读缓存的进程都会等待写入完成,再执行读操作
	fstat(ptr_fp->_fileno, &stat_result);
	cacheSize = stat_result.st_size;
	ZL_EXP_BYTE is_create = ZL_EXP_FALSE; // 判断是否是新创建的共享内存,如果是新建的,则需要将缓存数据读取到共享内存中,如果共享内存已经存在,则无需再读取
	// is_use_shm用于判断是否使用共享内存,如果配置中启用了共享内存,同时编译缓存的大小超过了配置文件中shm_min_size的值,则表示当前编译缓存需要放到共享内存中
	ZL_EXP_BYTE is_use_shm = config_shm_enable ? ((cacheSize > config_shm_min_size) ? ZL_EXP_TRUE : ZL_EXP_FALSE) : ZL_EXP_FALSE;
	int shm_id = -1;
	key_t share_mem_key;
	if(is_use_shm) { // 如果使用共享内存,则先将缓存路径转为共享内存key,再通过该key来获取已存在的共享内存,如果共享内存不存在时,则新建一个共享内存
		share_mem_key = ftok(cache_path, 1);
		shm_id = shmget(share_mem_key, cacheSize, 0666);
		if(shm_id == -1) {
			if(errno == ENOENT) { // 不存在,则新建一个共享内存
				shm_id = shmget(share_mem_key, cacheSize, IPC_CREAT | 0666);
				is_create = ZL_EXP_TRUE;
			}
			else { // 获取共享内存失败,则将is_use_shm设为FALSE,表示使用普通的文件缓存方式
				write_to_server_log_pipe(WRITE_TO_PIPE, "shmget <key: 0x%x size: %d cache_path: %s> failed [%d] %s [read from cache file]\n",
						share_mem_key, cacheSize, cache_path, errno, strerror(errno));
				is_use_shm = ZL_EXP_FALSE;
			}
		}
	}
	if(is_use_shm) { // 如果使用共享内存,则通过shmat库函数,将共享内存映射到当前进程的线性地址空间,从而得到当前进程可以访问的内存地址
		cachePoint = shmat(shm_id, NULL, 0);
		if(cachePoint == ((ZL_EXP_VOID *)-1)) { // 映射失败,记录错误,并使用原始的文件缓存方式
			write_to_server_log_pipe(WRITE_TO_PIPE, "shmat <id: %d cache_path: %s> failed [%d] %s [read from cache file]\n",
						shm_id, cache_path, errno, strerror(errno));
			is_use_shm = ZL_EXP_FALSE;
		}
	}
	if(!is_use_shm || is_create) {
		if(!is_use_shm) { // 如果不使用共享内存,则在根据缓存大小,新建一个堆内存空间,编译缓存会读取到该堆内存中
			cachePoint = malloc(cacheSize);
		}
		if(fread(cachePoint, cacheSize, 1, ptr_fp) != 1) { // 读取编译缓存数据到堆内存(普通文件缓存方式),或者读取到新创建的共享内存中
			write_to_server_log_pipe(WRITE_TO_PIPE, "read cache file \"%s\" failed [recompile]\n", cache_path);
			goto end;
		}
	}
	api_cache = (ZENGL_EXPORT_API_CACHE_TYPE *)cachePoint;
	if(api_cache->signer != ZL_EXP_API_CACHE_SIGNER) { // 根据缓存签名判断是否是有效的缓存数据
		write_to_server_log_pipe(WRITE_TO_PIPE, "invalid cache file \"%s\" [recompile]\n", cache_path);
		goto end;
	}
	mempoolPtr = ((ZL_EXP_BYTE *)cachePoint + api_cache->mempoolOffset);
	offset = (ZL_EXP_LONG)api_cache->filenames;
	filenames = (ZL_EXP_CHAR **)(mempoolPtr + offset - 1);
	if(api_cache->filenames_count > 0) {
		// 循环判断加载的脚本文件的内容是否发生了改变,如果改变了,则需要重新编译生成新的缓存
		for(i=0; i < api_cache->filenames_count; i++) {
			offset = (ZL_EXP_LONG)(filenames[i]);
			filename = (ZL_EXP_CHAR *)(mempoolPtr + offset - 1);
			if(stat(filename, &stat_result)==0) {
				file_mtime = (ZL_EXP_LONG)stat_result.st_mtime;
				if(file_mtime >= cache_mtime){
					write_to_server_log_pipe(WRITE_TO_PIPE, "\"%s\" mtime:%ld [changed] [recompile]\n", filename, file_mtime);
					goto end;
				}
			}
			else {
				write_to_server_log_pipe(WRITE_TO_PIPE, " stat \"%s\" failed [recompile]\n", filename);
				goto end;
			}
		}
	}
	// 通过zenglApi_ReUseCacheMemData接口函数,将编译好的缓存数据加载到编译器和解释器中,这样就可以跳过编译过程,直接运行
	if(zenglApi_ReUseCacheMemData(VM, cachePoint, cacheSize) == -1) {
		if(is_use_shm)
			write_to_server_log_pipe(WRITE_TO_PIPE, "[shm:0x%x] reuse cache file \"%s\" failed: %s [recompile]\n", share_mem_key, cache_path, zenglApi_GetErrorString(VM));
		else
			write_to_server_log_pipe(WRITE_TO_PIPE, "reuse cache file \"%s\" failed: %s [recompile]\n", cache_path, zenglApi_GetErrorString(VM));
		goto end;
	}
	(* is_reuse_cache) = ZL_EXP_TRUE;
	if(is_use_shm)
		write_to_server_log_pipe(WRITE_TO_PIPE, "[shm:0x%x] reuse cache file: \"%s\" mtime:%ld\n", share_mem_key, cache_path, cache_mtime);
	else
		write_to_server_log_pipe(WRITE_TO_PIPE, "reuse cache file: \"%s\" mtime:%ld\n", cache_path, cache_mtime);
end:
	fclose(ptr_fp);
	flock(ptr_fp->_fileno, LOCK_UN); // 解锁
	if(cachePoint != NULL) {
		if(is_use_shm) // 如果使用了共享内存,则通过shmdt来解除映射
			shmdt(cachePoint);
		else // 如果是普通的文件缓存方式,则将之前创建的堆内存释放掉
			free(cachePoint);
	}
}


    上面函数里,会先检测是否需要使用共享内存,如果配置中通过shm_enable开启了共享内存,并且当前脚本的编译缓存的大小超过了配置中shm_min_size的值,如果对应的共享内存不存在,则通过shmget库函数以及编译缓存的大小来创建一个共享内存,并通过shmat库函数将共享内存映射到当前进程的地址空间,再将编译缓存数据读取到共享内存中。如果共享内存已经存在了,则直接使用共享内存中的编译缓存。对于不需要使用共享内存的编译缓存(主要是缓存大小没达到要求),则继续使用普通的文件缓存方式。

    当zengl脚本的内容发生了变化需要更新编译缓存时,zenglServer会自动移除掉相应的共享内存,这样下一次重利用缓存时,就会生成新的共享内存,并将新的缓存数据写入共享内存,相关代码如下(同样位于main.c文件中):

/**
 * 在编译执行结束后,生成缓存数据并写入缓存文件
 */
static void main_write_zengl_cache_to_file(ZL_EXP_VOID * VM, char * cache_path)
{
	FILE * ptr_fp;
	ZL_EXP_VOID * cachePoint;
	ZL_EXP_INT cacheSize;
	// 通过zenglApi_CacheMemData接口函数,将编译器和解释器中的主要的内存数据缓存到cachePoint对应的内存中
	if(zenglApi_CacheMemData(VM, &cachePoint, &cacheSize) == -1) {
		write_to_server_log_pipe(WRITE_TO_PIPE, "write zengl cache to file \"%s\" failed: %s\n", cache_path,zenglApi_GetErrorString(VM));
		return;
	}

	// 打开cache_path对应的缓存文件
	if((ptr_fp = fopen(cache_path, "wb")) == NULL) {
		write_to_server_log_pipe(WRITE_TO_PIPE, "write zengl cache to file \"%s\" failed: open failed\n", cache_path);
		return;
	}
	flock(ptr_fp->_fileno, LOCK_EX); // 写入缓存数据之前,先加入互斥锁,让所有读进程等待写入完成
	struct stat stat_result;
	fstat(ptr_fp->_fileno, &stat_result);
	ZL_EXP_INT cachefileSize = stat_result.st_size;
	key_t share_mem_key = ftok(cache_path, 1);
	int shm_id = shmget(share_mem_key, cachefileSize, 0666);
	if(shm_id != -1) { // 由于生成了新的编译缓存数据,因此,如果存在对应的共享内存,则将共享内存移除掉,下次读缓存时,就会创建一个新的共享内存,并将新的缓存数据写入共享内存
		shmctl(shm_id, IPC_RMID, NULL);
		write_to_server_log_pipe(WRITE_TO_PIPE, "remove shm key: 0x%x, shm id: %d, ", share_mem_key, shm_id);
	}
	// 将缓存数据写入缓存文件
	if( fwrite(cachePoint, cacheSize, 1, ptr_fp) != 1)
		write_to_server_log_pipe(WRITE_TO_PIPE, "write zengl cache to file \"%s\" failed: write failed\n", cache_path);
	else
		write_to_server_log_pipe(WRITE_TO_PIPE, "write zengl cache to file \"%s\" success \n", cache_path);
	fclose(ptr_fp);
	flock(ptr_fp->_fileno, LOCK_UN); // 解锁
}


    当zenglServer主进程退出时,会自动清理共享内存,相关C代码如下(还是位于main.c文件中):

/**
 * 当主进程接收到SIGINT或者SIGTERM终止信号时,会触发的信号处理函数
 */
void sig_terminate_master_callback()
{
	..........................................................

	// 删除共享内存
	DIR * dp;
	struct dirent * ep;
	char * path = "zengl/caches";
	char filename[SESSION_FILEPATH_MAX_LEN];
	int path_dir_len = strlen(path);
	int ep_name_len, left_len, del_shm_num = 0;
	key_t share_mem_key;
	struct stat ep_stat;
	strncpy(filename, path, path_dir_len);
	filename[path_dir_len] = '/';
	left_len = SESSION_FILEPATH_MAX_LEN - path_dir_len - 2;

	dp = opendir(path);
	if (dp != NULL) // 循环根据编译缓存的文件名,得到相应的共享内存key,并根据key将已存在的共享内存移除掉
	{
		int cpy_len;
		while((ep = readdir(dp)))
		{
			ep_name_len = strlen(ep->d_name);
			if(ep_name_len > 20) {
				cpy_len = (ep_name_len <= left_len) ?  ep_name_len : left_len;
				strncpy(filename + path_dir_len + 1, ep->d_name, cpy_len);
				filename[path_dir_len + 1 + cpy_len] = '\0';
				if(stat(filename, &ep_stat) == 0) {
					if(ep_stat.st_size > config_shm_min_size) {
						share_mem_key = ftok(filename, 1);
						int shm_id = shmget(share_mem_key, ep_stat.st_size, 0666);
						if(shm_id != -1) {
							shmctl(shm_id, IPC_RMID, NULL);
							write_to_server_log_pipe(WRITE_TO_LOG, "************ remove shm key: 0x%x [cache_file: %s]\n", share_mem_key, ep->d_name);
							del_shm_num++;
						}
						else if(errno != ENOENT) {
							write_to_server_log_pipe(WRITE_TO_LOG, "!!!******!!! remove shm key: 0x%x failed [%d] %s\n", share_mem_key, errno, strerror(errno));
						}
					}
				}
				else
					write_to_server_log_pipe(WRITE_TO_LOG, "!!!******!!! remove shm cache_path: \"%s\" failed [%d] %s\n", filename, errno, strerror(errno));
			}
		}
		closedir(dp);
	}
	else {
		write_to_server_log_pipe(WRITE_TO_LOG, "!!!******!!! opendir \"%s\" failed [%d] %s\n", path, errno, strerror(errno));
	}
	write_to_server_log_pipe(WRITE_TO_LOG, "------------ remove shm number: %d\n", del_shm_num);

	// 如果所有子进程都退出了,就释放相关资源,并退出主进程,子进程和主进程都退出后,整个程序也就退出了
	sem_unlink("accept_sem");
	sem_close(my_thread_lock.accept_sem);
	write_to_server_log_pipe(WRITE_TO_LOG, "closed accept_sem\n");
	shutdown(server_socket_fd, SHUT_RDWR);
	write_to_server_log_pipe(WRITE_TO_LOG, "shutdowned server socket\n");
	close(server_socket_fd);
	write_to_server_log_pipe(WRITE_TO_LOG, "closed server socket\n===================================\n\n");
	free(server_log_pipe_string.str);
	exit(0);
}


    当前版本的crustache模板中,{{ var }} 默认会进行html转义,{{{ val }}} 不做任何处理,直接原样输出,{{& val }} 会执行反转义,将转义后的字符串恢复为原始的html。可以参考my_webroot/v0_7_0/test.zl的测试脚本,该测试脚本在当前版本中做了如下调整:

use builtin;

data['title'] = 'mustache模板测试';

data["val"] = "my world!";
data["zl"] = "<b>welcome to zengl!&lt;span style=&quot;color:green&quot;&gt;大家好&lt;&#47;span&gt;</b>";
................................................
print bltMustacheFileRender("test.tpl",data);


    该脚本调整了data["zl"]的值,并且依旧使用test.tpl来输出data数组中的值:

{{> header.tpl}}
<b>hello {{val}}!</b>
<br/>
<h3>{{ zl }}</h3>
<h3>{{{ zl }}}</h3>
<h3>{{& zl }}</h3>

..................................................


    上面用{{ zl }},{{{ zl }}},{{& zl }}三种标签来输出zl的值,在之前的版本中,这三个标签输出的结果是一样的。但是在当前版本中,第一个{{ zl }}会对zl的值进行html转义,第二个{{{ zl }}}会直接输出zl的原始值,不对其做任何处理,最后一个{{& zl }}会对zl的值进行反转义,将zl中的转义后的字符串恢复为相应的html,当前版本的执行结果如下:



图1:crustache模板转义

    当前版本还新增了magick模块,可以进行图像相关的操作,例如:缩放图像等。要开启magick模块,在编译时,需要在make命令后面加入USE_MAGICK=6,由于操作图像使用的是ImageMagick,而ImageMagick有6.x和7.x的版本,目前只支持6.x的版本,因此USE_MAGICK后面跟随的是数字6,如果以后支持7.x的话,还可以跟随7。

    当然,要使用ImageMagick,前提是系统中安装了底层的开发库。

    如果是ubuntu系统,可以通过sudo apt-get install imagemagick libmagickcore-dev libmagickwand-dev来安装ImageMagick相关的库和开发头文件。

    如果是centos系统,则可以通过yum install ImageMagick ImageMagick-devel来安装相关的底层库。可以通过convert --version命令来查看系统安装的是哪个版本的ImageMagick。

    要同时使用mysql和magick模块,可以使用make USE_MYSQL=yes USE_MAGICK=6命令:

[email protected]:~/zenglServer$ make USE_MYSQL=yes USE_MAGICK=6
cd zengl/linux && make libzengl.a
make[1]: Entering directory `/home/zengl/zenglServer/zengl/linux'
gcc -D ZL_LANG_EN_WITH_CH -g3 -ggdb -O0 -std=c99 -fvisibility=hidden -fPIC -c zengl_main.c zengl_parser.c zengl_symbol.c zengl_locals.c zengl_assemble.c zengl_ld.c zenglrun_main.c zenglrun_func.c zenglrun_hash_array.c zenglApi.c zenglApi_BltModFuns.c zenglDebug.c
ar rc libzengl.a zengl_main.o zengl_parser.o zengl_symbol.o zengl_locals.o zengl_assemble.o zengl_ld.o zenglrun_main.o zenglrun_func.o zenglrun_hash_array.o zenglApi.o zenglApi_BltModFuns.o zenglDebug.o
make[1]: Leaving directory `/home/zengl/zenglServer/zengl/linux'
cd crustache && make libcrustache.a
make[1]: Entering directory `/home/zengl/zenglServer/crustache'
gcc -g3 -ggdb -O0 -std=c99 -fvisibility=hidden -fPIC -c buffer.c crustache.c houdini_html.c stack.c
ar rc libcrustache.a buffer.o crustache.o houdini_html.o stack.o
make[1]: Leaving directory `/home/zengl/zenglServer/crustache'
gcc -g3 -ggdb -O0 -std=c99 main.c http_parser.c module_request.c module_builtin.c module_session.c dynamic_string.c multipart_parser.c resources.c client_socket_list.c json.c randutils.c md5.c debug.c main.h http_parser.h common_header.h module_request.h module_builtin.h module_session.h dynamic_string.h multipart_parser.h resources.h client_socket_list.h json.h randutils.h md5.h debug.h module_mysql.c module_mysql.h  module_magick.c module_magick.h zengl/linux/zengl_exportfuns.h  -o zenglServer zengl/linux/libzengl.a crustache/libcrustache.a -lpthread -lm -DUSE_MYSQL `mysql_config --cflags --libs`  -D USE_MAGICK=6 `pkg-config --cflags --libs Wand`

mysql module is enabled!!!
magick module is enabled!!!
[email protected]:~/zenglServer$ 


    和magick模块相关的C文件为module_magick.c和module_magick.h,在module_magick.c中存在magickNewWand等和图像操作相关的模块函数:

/*
 * module_magick.c
 *
 *  Created on: May 27, 2018
 *      Author: zengl
 */

#include "main.h"
#include "module_magick.h"
#include <string.h>
/**
 * zenglServer使用ImageMagick来操作jpg,png,gif之类的图像
 * ImageMagick的官方网站:www.imagemagick.org
 * 并使用MagickWand封装的API来操作ImageMagick
 * MagickWand对应的网站地址:https://www.imagemagick.org/script/magick-wand.php
 * 目前只支持imagemagick 6的版本,暂不支持imagemagick 7的版本
 */
#include <wand/MagickWand.h>

/**
 * 根据当前执行脚本的目录路径,加上filename文件名,来生成可以被fopen等C库函数使用的路径,定义在module_builtin.c文件中
 */
void builtin_make_fullpath(char * full_path, char * filename, MAIN_DATA * my_data);

static __thread ZL_EXP_BOOL st_is_magick_genesis = ZL_EXP_FALSE;

/**
 * 通过MagickWandGenesis初始化MagickWand环境
 * 在调用MagickWand其他接口之前,需要先使用MagickWandGenesis来初始化环境
 * zenglServer会根据需要自动初始化该环境,因此,脚本中无需手动执行初始化操作
 */
static ZL_EXP_BOOL st_magick_wand_genesis()
{
	if(st_is_magick_genesis == ZL_EXP_FALSE) {
		MagickWandGenesis();
		st_is_magick_genesis = ZL_EXP_TRUE;
		write_to_server_log_pipe(WRITE_TO_PIPE, "[debug] MagickWandGenesis \n"); // debug
		return ZL_EXP_TRUE;
	}
	else
		return ZL_EXP_FALSE;
}

/**
 * 这是一个和MagickWand相关的资源释放回调函数,由于MagickWand操作图像时,会先通过NewMagickWand得到一个MagickWand实例,
 * 再由该实例去执行各种图像操作,例如,加载图像,调整图像大小等,该实例在创建和使用过程中,会分配内存资源
 * 如果脚本中没有通过magickDestroyWand模块函数手动释放掉这些实例资源的话,zenglServer会在脚本执行结束时,自动通过下面这个回调函数,
 * 以及DestroyMagickWand接口来释放掉NewMagickWand所分配的实例,以防止内存泄露
 * 每个NewMagickWand创建的实例的指针都会存储到zenglServer的资源列表中,这样就可以在脚本执行结束时,检测是否有没有释放掉的实例
 */
static void st_magick_destroy_wand_callback(ZL_EXP_VOID * VM_ARG, void * ptr)
{
	MagickWand * magick_wand = (MagickWand *)ptr;
	DestroyMagickWand(magick_wand);
	write_to_server_log_pipe(WRITE_TO_PIPE, "[debug] DestroyMagickWand: %x\n", magick_wand); // debug
}

/**
 * 在使用某个MagickWand实例指针执行相关图像操作前,会先通过下面这个函数来检测指针是否是一个有效的实例指针
 * 由于每个新建的实例的指针都会存储到资源列表中,因此,如果在资源列表中找得到该指针,则说明是一个有效的实例指针
 */
static ZL_EXP_BOOL st_is_valid_magick_wand(RESOURCE_LIST * resource_list, void * magick_wand)
{
	int ret = resource_list_get_ptr_idx(resource_list, magick_wand, st_magick_destroy_wand_callback);
	if(ret >= 0)
		return ZL_EXP_TRUE;
	else
		return ZL_EXP_FALSE;
}

/**
 * 模块函数会通过下面这个函数来检查提供的指针参数是否是有效的实例指针,如果不是有效的实例指针,则抛出错误
 * 该函数又会通过st_is_valid_magick_wand来进行基础的检测,如果st_is_valid_magick_wand返回0,则抛出错误
 */
static MAIN_DATA * st_assert_magick_wand(ZL_EXP_VOID * VM_ARG, void * magick_wand, const char * module_fun_name)
{
	MAIN_DATA * my_data = zenglApi_GetExtraData(VM_ARG, "my_data");
	if(!st_is_valid_magick_wand(&(my_data->resource_list), magick_wand)) {
		zenglApi_Exit(VM_ARG,"%s runtime error: invalid magick_wand", module_fun_name);
	}
	return my_data;
}

/**
 * 如果使用了MagickWandGenesis初始化MagickWand环境
 * 则在结束时,需要使用MagickWandTerminus来终止MagickWand环境
 * zenglServer会在脚本执行结束时,自动调用下面这个函数来执行终止环境的操作
 */
void export_magick_terminus()
{
	if(st_is_magick_genesis == ZL_EXP_TRUE) {
		MagickWandTerminus();
		st_is_magick_genesis = ZL_EXP_FALSE;
		write_to_server_log_pipe(WRITE_TO_PIPE, "[debug] MagickWandTerminus \n"); // debug
	}
}

/**
 * magickWandGenesis模块函数,初始化MagickWand环境
 * 在zengl脚本中无需手动调用该模块函数来执行初始化操作,因为,zenglServer会根据需要自动执行初始化操作
 * 当然也可以手动通过该模块函数来执行初始化,如果手动执行过,则zenglServer就不会再重复执行初始化操作了
 * 因为一旦初始化后,会设置st_is_magick_genesis静态全局变量,如果该变量的值被设置了,就说明已经初始化过了,可以防止重复的初始化操作
 */
ZL_EXP_VOID module_magick_wand_genesis(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	int retval = (int)st_magick_wand_genesis();
	zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_INT, ZL_EXP_NULL, retval, 0);
}

/**
 * magickNewWand模块函数,新建一个MagickWand实例,并将该实例的指针返回
 * 在执行具体的图像操作之前,需要先新建一个MagickWand实例,因为,大部分图像操作接口都需要接受一个实例指针作为参数
 * 该模块函数在创建了一个实例指针后,还会将该指针存储到资源列表中,这样,其他的图像操作函数在接受到一个实例指针时,
 * 就可以根据该指针是否存在于资源列表中来判断是否是一个有效的实例指针了,并且如果脚本中没有手动释放掉这些实例指针的话,
 * zenglServer还可以从资源列表中将未释放掉的实例指针给自动释放掉
 *
 * magickNewWand模块函数在调用时,不需要提供任何参数,它会将新建好的实例指针以整数的形式作为结果返回
 */
ZL_EXP_VOID module_magick_new_wand(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	st_magick_wand_genesis();
	MagickWand * magick_wand = NewMagickWand();
	write_to_server_log_pipe(WRITE_TO_PIPE, "[debug] NewMagickWand: %x\n", magick_wand); // debug
	zenglApi_SetRetVal(VM_ARG,ZL_EXP_FAT_INT, ZL_EXP_NULL, (ZL_EXP_LONG)magick_wand, 0);
	MAIN_DATA * my_data = zenglApi_GetExtraData(VM_ARG, "my_data");
	int ret_code = resource_list_set_member(&(my_data->resource_list), magick_wand, st_magick_destroy_wand_callback);
	if(ret_code != 0) {
		zenglApi_Exit(VM_ARG, "magickNewWand add resource to resource_list failed, resource_list_set_member error code:%d", ret_code);
	}
}

/**
 * magickReadImage模块函数,将指定的图像文件加载到MagickWand实例
 * 该模块函数的第一个参数magick_wand需要是一个有效的MagickWand实例指针,第二个参数filename表示需要加载的图像的文件名,该文件名是相对于当前执行脚本的相对路径
 * 例如:
 * use builtin, magick;
 * fun exit(err)
 *	print err;
 *	bltExit();
 * endfun
 * wand = magickNewWand();
 * if(!magickReadImage(wand, 'king.png'))
 *	exit('read king.png failed');
 * endif
 * 上面脚本先通过magickNewWand新建了一个wand实例,接着通过magickReadImage将king.png图像加载到wand实例
 * 接着就可以通过wand实例来操作图像了
 * magickReadImage模块函数在执行成功后会返回1,执行失败会返回0,通常执行失败的原因可能是图像文件不存在,或者加载的文件内容不是一个有效的图像格式,执行失败的具体原因会记录在日志中
 */
ZL_EXP_VOID module_magick_read_image(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}};
	if(argcount < 2)
		zenglApi_Exit(VM_ARG,"usage: magickReadImage(magick_wand, filename): integer");
	MagickBooleanType status;
	zenglApi_GetFunArg(VM_ARG,1,&arg);
	if(arg.type != ZL_EXP_FAT_INT) {
		zenglApi_Exit(VM_ARG,"the first argument [magick_wand] of magickReadImage must be integer");
	}
	MagickWand * magick_wand = (MagickWand *)arg.val.integer;
	MAIN_DATA * my_data = st_assert_magick_wand(VM_ARG, magick_wand, "magickReadImage");
	zenglApi_GetFunArg(VM_ARG,2,&arg);
	if(arg.type != ZL_EXP_FAT_STR) {
		zenglApi_Exit(VM_ARG,"the second argument [filename] of magickReadImage must be string");
	}
	char full_path[FULL_PATH_SIZE];
	char * filename = arg.val.str;
	builtin_make_fullpath(full_path, filename, my_data);
	status = MagickReadImage(magick_wand, full_path);
	if(status == MagickFalse) {
		ExceptionType severity;
		char * description=MagickGetException(magick_wand, &severity);
		write_to_server_log_pipe(WRITE_TO_PIPE, "MagickReadImage failed: %s\n", description);
		description=(char *) MagickRelinquishMemory(description);
		zenglApi_SetRetVal(VM_ARG,ZL_EXP_FAT_INT, ZL_EXP_NULL, ZL_EXP_FALSE, 0);
	}
	else
		zenglApi_SetRetVal(VM_ARG,ZL_EXP_FAT_INT, ZL_EXP_NULL, ZL_EXP_TRUE, 0);
}

/**
 * magickGetImageFormat模块函数,返回MagickWand实例所加载的图像的格式
 * 该模块函数的第一个参数magick_wand需要是一个有效的MagickWand实例指针
 * 例如:
 * use builtin, magick;
 * fun exit(err)
 *	print err;
 *	bltExit();
 * endfun
 * wand = magickNewWand();
 * if(!magickReadImage(wand, 'king.png'))
 *	exit('read king.png failed');
 * endif
 * print 'format: ' + magickGetImageFormat(wand) + '<br/>';
 * 上面脚本的执行结果如下:
 * format: PNG
 * 模块函数会将图像的格式以字符串的形式返回,上面png图像就返回了PNG,如果是jpg图像会返回JPEG等
 */
ZL_EXP_VOID module_magick_get_image_format(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}};
	if(argcount < 1)
		zenglApi_Exit(VM_ARG,"usage: magickGetImageFormat(magick_wand): integer or string");
	zenglApi_GetFunArg(VM_ARG,1,&arg);
	if(arg.type != ZL_EXP_FAT_INT) {
		zenglApi_Exit(VM_ARG,"the first argument [magick_wand] of magickGetImageFormat must be integer");
	}
	MagickWand * magick_wand = (MagickWand *)arg.val.integer;
	st_assert_magick_wand(VM_ARG, magick_wand, "magickGetImageFormat");
	char * format = MagickGetImageFormat(magick_wand);
	if(format == NULL)
		zenglApi_SetRetVal(VM_ARG,ZL_EXP_FAT_INT, ZL_EXP_NULL, 0, 0);
	else {
		zenglApi_SetRetVal(VM_ARG,ZL_EXP_FAT_STR, format, 0, 0);
		MagickRelinquishMemory(format);
	}
}

/**
 * magickGetImageWidth模块函数,返回图像的宽度
 * 该模块函数的第一个参数magick_wand需要是一个有效的MagickWand实例指针
 * 例如:
 * use builtin, magick;
 * fun exit(err)
 *	print err;
 *	bltExit();
 * endfun
 * wand = magickNewWand();
 * if(!magickReadImage(wand, 'king.png'))
 *	exit('read king.png failed');
 * endif
 * print 'width: ' + magickGetImageWidth(wand) + '<br/>';
 * 上面脚本的执行结果如下:
 * width: 450
 * 执行结果说明king.png图像的宽度是450像素
 */
ZL_EXP_VOID module_magick_get_image_width(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}};
	if(argcount < 1)
		zenglApi_Exit(VM_ARG,"usage: magickGetImageWidth(magick_wand): integer");
	zenglApi_GetFunArg(VM_ARG,1,&arg);
	if(arg.type != ZL_EXP_FAT_INT) {
		zenglApi_Exit(VM_ARG,"the first argument [magick_wand] of magickGetImageWidth must be integer");
	}
	MagickWand * magick_wand = (MagickWand *)arg.val.integer;
	st_assert_magick_wand(VM_ARG, magick_wand, "magickGetImageWidth");
	ZL_EXP_LONG width = MagickGetImageWidth(magick_wand);
	zenglApi_SetRetVal(VM_ARG,ZL_EXP_FAT_INT, ZL_EXP_NULL, width, 0);
}

/**
 * magickGetImageHeight模块函数,返回图像的高度
 * 该模块函数的第一个参数magick_wand需要是一个有效的MagickWand实例指针
 * 例如:
 * use builtin, magick;
 * fun exit(err)
 *	print err;
 *	bltExit();
 * endfun
 * wand = magickNewWand();
 * if(!magickReadImage(wand, 'king.png'))
 *	exit('read king.png failed');
 * endif
 * print 'height: ' + magickGetImageHeight(wand) + '<br/>';
 * 上面脚本的执行结果如下:
 * height: 332
 * 执行结果说明king.png图像的高度是332像素
 */
ZL_EXP_VOID module_magick_get_image_height(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}};
	if(argcount < 1)
		zenglApi_Exit(VM_ARG,"usage: magickGetImageHeight(magick_wand): integer");
	zenglApi_GetFunArg(VM_ARG,1,&arg);
	if(arg.type != ZL_EXP_FAT_INT) {
		zenglApi_Exit(VM_ARG,"the first argument [magick_wand] of magickGetImageHeight must be integer");
	}
	MagickWand * magick_wand = (MagickWand *)arg.val.integer;
	st_assert_magick_wand(VM_ARG, magick_wand, "magickGetImageHeight");
	ZL_EXP_LONG height = MagickGetImageHeight(magick_wand);
	zenglApi_SetRetVal(VM_ARG,ZL_EXP_FAT_INT, ZL_EXP_NULL, height, 0);
}

/**
 * magickResizeImage模块函数,将图像缩放到所需的尺寸
 * 该模块函数的第一个参数magick_wand需要是一个有效的MagickWand实例指针,第二个参数width表示需要缩放的宽度,第三个参数height表示需要缩放的高度,
 * 第四个参数filter_type表示缩放操作时,需要使用的滤镜类型,不同的滤镜生成的图像质量和图像体积大小会有所区别
 * 例如:
 * use builtin, magick;
 * fun exit(err)
 *	print err;
 *	bltExit();
 * endfun
 * wand = magickNewWand();
 * if(!magickReadImage(wand, 'king.png'))
 *	exit('read king.png failed');
 * endif
 * if(!magickResizeImage(wand, 200, 150, "LanczosFilter"))
 *	exit('resize king.png failed');
 * endif
 * 上面脚本在执行时,会使用LanczosFilter滤镜将wand加载的图像缩放到200x150尺寸
 * 第四个参数可以是字符串形式的滤镜类型,底层会将字符串映射为同名的enum类型的滤镜值,也可以直接传enum对应的整数值,但是,不同的6.x版本中的enum类型对应的整数值是不同的
 * 传错了整数值,可能会发生段错误,因此,传字符串要保险点。字符串目前只支持通用滤镜类型,所谓通用滤镜类型,是指从6.2的低版本到6.9的高版本中都存在的滤镜类型,高版本中
 * 新增了很多低版本中没有的滤镜类型,要使用这些新增的非通用的滤镜类型,只能传整数值过来
 * 模块函数执行成功会返回整数1,失败则返回整数0,失败的原因会记录到日志中
 */
ZL_EXP_VOID module_magick_resize_image(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}};
	if(argcount < 4)
		zenglApi_Exit(VM_ARG,"usage: magickResizeImage(magick_wand, width, height, filter_type): integer");
	zenglApi_GetFunArg(VM_ARG,1,&arg);
	if(arg.type != ZL_EXP_FAT_INT) {
		zenglApi_Exit(VM_ARG,"the first argument [magick_wand] of magickResizeImage must be integer");
	}
	MagickWand * magick_wand = (MagickWand *)arg.val.integer;
	st_assert_magick_wand(VM_ARG, magick_wand, "magickResizeImage");
	zenglApi_GetFunArg(VM_ARG,2,&arg);
	if(arg.type != ZL_EXP_FAT_INT) {
		zenglApi_Exit(VM_ARG,"the second argument [width] of magickResizeImage must be integer");
	}
	ZL_EXP_LONG width = arg.val.integer;
	zenglApi_GetFunArg(VM_ARG,3,&arg);
	if(arg.type != ZL_EXP_FAT_INT) {
		zenglApi_Exit(VM_ARG,"the third argument [height] of magickResizeImage must be integer");
	}
	ZL_EXP_LONG height = arg.val.integer;
	char * filter_types_str[] = {
			"UndefinedFilter", "PointFilter", "BoxFilter", "TriangleFilter", "HermiteFilter", "HanningFilter",
			"HammingFilter", "BlackmanFilter", "GaussianFilter", "QuadraticFilter", "CubicFilter", "CatromFilter",
			"MitchellFilter", "LanczosFilter", "BesselFilter", "SincFilter"
	};
	int filter_types_str_len = sizeof(filter_types_str)/sizeof(filter_types_str[0]);
	FilterTypes filter_types_enum[] = {
			UndefinedFilter, PointFilter, BoxFilter, TriangleFilter, HermiteFilter, HanningFilter,
			HammingFilter, BlackmanFilter, GaussianFilter, QuadraticFilter, CubicFilter, CatromFilter,
			MitchellFilter, LanczosFilter, BesselFilter, SincFilter
	};
	FilterTypes filter_type = LanczosFilter; // 默认值
	zenglApi_GetFunArg(VM_ARG,4,&arg);
	if(arg.type == ZL_EXP_FAT_STR) {
		for(int i=0; i < filter_types_str_len; i++) {
			if(filter_types_str[i][0] == arg.val.str[0] &&
				strlen(filter_types_str[i]) == strlen(arg.val.str) &&
				strcmp(filter_types_str[i], arg.val.str) == 0) {
				filter_type = (FilterTypes)filter_types_enum[i];
				break;
			}
		}
	}
	else if(arg.type == ZL_EXP_FAT_INT) {
		filter_type = (FilterTypes)arg.val.integer;
	}
	write_to_server_log_pipe(WRITE_TO_PIPE, "[debug] filter type: %d\n", filter_type); // debug
	MagickBooleanType status = MagickResizeImage(magick_wand, width, height,filter_type,1.0);
	if(status == MagickFalse) {
		ExceptionType severity;
		char * description=MagickGetException(magick_wand, &severity);
		write_to_server_log_pipe(WRITE_TO_PIPE, "MagickResizeImage failed: %s\n", description);
		description=(char *) MagickRelinquishMemory(description);
		zenglApi_SetRetVal(VM_ARG,ZL_EXP_FAT_INT, ZL_EXP_NULL, ZL_EXP_FALSE, 0);
	}
	else
		zenglApi_SetRetVal(VM_ARG,ZL_EXP_FAT_INT, ZL_EXP_NULL, ZL_EXP_TRUE, 0);
}

/**
 * magickWriteImage模块函数,将MagickWand实例中存储的图像写入到指定的文件中
 * 该模块函数的第一个参数magick_wand需要是一个有效的MagickWand实例指针,第二个参数filename表示需要写入的文件名,该文件名是相对于当前执行脚本的路径
 * 例如:
 * use builtin, magick;
 * fun exit(err)
 *	print err;
 *	bltExit();
 * endfun
 * wand = magickNewWand();
 * if(!magickReadImage(wand, 'king.png'))
 *	exit('read king.png failed');
 * endif
 * if(!magickResizeImage(wand, 200, 150, "LanczosFilter"))
 *	exit('resize king.png failed');
 * endif
 * if(!magickWriteImage(wand, 'thumb.jpg'))
 *	exit('write to thumb.jpg failed');
 * endif
 * 上面在执行完缩放操作后,将wand中缩放以后的图像写入到thumb.jpg文件中,从而生成了king.png的缩略图
 * 该模块函数执行成功会返回整数1,执行失败返回整数0,并将失败的原因记录到日志中
 */
ZL_EXP_VOID module_magick_write_image(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}};
	if(argcount < 2)
		zenglApi_Exit(VM_ARG,"usage: magickWriteImage(magick_wand, filename): integer");
	zenglApi_GetFunArg(VM_ARG,1,&arg);
	if(arg.type != ZL_EXP_FAT_INT) {
		zenglApi_Exit(VM_ARG,"the first argument [magick_wand] of magickWriteImage must be integer");
	}
	MagickWand * magick_wand = (MagickWand *)arg.val.integer;
	MAIN_DATA * my_data = st_assert_magick_wand(VM_ARG, magick_wand, "magickWriteImage");
	zenglApi_GetFunArg(VM_ARG,2,&arg);
	if(arg.type != ZL_EXP_FAT_STR) {
		zenglApi_Exit(VM_ARG,"the second argument [filename] of magickWriteImage must be string");
	}
	char full_path[FULL_PATH_SIZE];
	char * filename = arg.val.str;
	builtin_make_fullpath(full_path, filename, my_data);
	MagickBooleanType status = MagickWriteImages(magick_wand, full_path, MagickTrue);
	if(status == MagickFalse) {
		ExceptionType severity;
		char * description=MagickGetException(magick_wand, &severity);
		write_to_server_log_pipe(WRITE_TO_PIPE, "magickWriteImage failed: %s\n", description);
		description=(char *) MagickRelinquishMemory(description);
		zenglApi_SetRetVal(VM_ARG,ZL_EXP_FAT_INT, ZL_EXP_NULL, ZL_EXP_FALSE, 0);
	}
	else
		zenglApi_SetRetVal(VM_ARG,ZL_EXP_FAT_INT, ZL_EXP_NULL, ZL_EXP_TRUE, 0);
}

/**
 * magickDestroyWand模块函数,注销掉不再使用的MagickWand实例,并释放该实例所占用的内存资源
 * 该模块函数的第一个参数magick_wand需要是一个有效的MagickWand实例指针
 * 当某个实例指针不再需要使用时,可以手动将其释放掉,如果忘了手动调用该模块函数执行释放操作的话,则在脚本结束时,zenglServer会自动根据资源列表将没释放掉的实例指针给释放掉。
 */
ZL_EXP_VOID module_magick_destroy_wand(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}};
	if(argcount < 1)
		zenglApi_Exit(VM_ARG,"usage: magickDestroyWand(magick_wand): integer");
	zenglApi_GetFunArg(VM_ARG,1,&arg);
	if(arg.type != ZL_EXP_FAT_INT) {
		zenglApi_Exit(VM_ARG,"the first argument [magick_wand] of magickDestroyWand must be integer");
	}
	MagickWand * magick_wand = (MagickWand *)arg.val.integer;
	MAIN_DATA * my_data = st_assert_magick_wand(VM_ARG, magick_wand, "magickDestroyWand");
	MagickWand * retval = DestroyMagickWand(magick_wand);
	write_to_server_log_pipe(WRITE_TO_PIPE, "[debug] DestroyMagickWand: %x\n", magick_wand); // debug
	zenglApi_SetRetVal(VM_ARG,ZL_EXP_FAT_INT, ZL_EXP_NULL, (ZL_EXP_LONG)retval, 0);
	int ret_code = resource_list_remove_member(&(my_data->resource_list), magick_wand); // 将释放掉的实例指针从资源列表中移除
	if(ret_code != 0) {
		zenglApi_Exit(VM_ARG, "magickDestroyWand remove resource from resource_list failed, resource_list_remove_member error code:%d", ret_code);
	}
}

/**
 * magickWandTerminus模块函数,使用MagickWandTerminus接口来终止MagickWand环境
 * 如果初始化过MagickWand环境,那么在结束时,都需要使用MagickWandTerminus接口来终止环境,
 * 如果在脚本中忘了手动调用该模块函数的话,zenglServer会在脚本执行结束时,自动通过上面的export_magick_terminus()函数来执行终止操作
 * 如果模块函数执行了终止操作,则返回1,如果没有执行终止操作,则返回0。没执行终止操作的可能原因是没有初始化过,不需要终止,或者是资源列表中还有没有释放掉的实例指针
 */
ZL_EXP_VOID module_magick_wand_terminus(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	MAIN_DATA * my_data = zenglApi_GetExtraData(VM_ARG, "my_data");
	int res_count = resource_list_get_count_by_callback(&(my_data->resource_list), st_magick_destroy_wand_callback);
	if(st_is_magick_genesis == ZL_EXP_TRUE && res_count == 0) {
		MagickWandTerminus();
		st_is_magick_genesis = ZL_EXP_FALSE;
		write_to_server_log_pipe(WRITE_TO_PIPE, "[debug] MagickWandTerminus \n"); // debug
		zenglApi_SetRetVal(VM_ARG,ZL_EXP_FAT_INT, ZL_EXP_NULL, 1, 0);
		return;
	}
	if(res_count > 0) {
		write_to_server_log_pipe(WRITE_TO_PIPE, "[debug] have %d wand resource in resource_list, skip MagickWandTerminus \n", res_count); // debug
	}
	else if(!st_is_magick_genesis) {
		write_to_server_log_pipe(WRITE_TO_PIPE, "[debug] MagickWand not init, skip MagickWandTerminus \n", res_count); // debug
	}
	zenglApi_SetRetVal(VM_ARG,ZL_EXP_FAT_INT, ZL_EXP_NULL, 0, 0);
}

/**
 * magick模块的初始化函数,里面设置了与该模块相关的各个模块函数及其相关的处理句柄
 */
ZL_EXP_VOID module_magick_init(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT moduleID)
{
	zenglApi_SetModFunHandle(VM_ARG,moduleID,"magickWandGenesis",module_magick_wand_genesis);
	zenglApi_SetModFunHandle(VM_ARG,moduleID,"magickNewWand",module_magick_new_wand);
	zenglApi_SetModFunHandle(VM_ARG,moduleID,"magickReadImage",module_magick_read_image);
	zenglApi_SetModFunHandle(VM_ARG,moduleID,"magickGetImageFormat",module_magick_get_image_format);
	zenglApi_SetModFunHandle(VM_ARG,moduleID,"magickGetImageWidth",module_magick_get_image_width);
	zenglApi_SetModFunHandle(VM_ARG,moduleID,"magickGetImageHeight",module_magick_get_image_height);
	zenglApi_SetModFunHandle(VM_ARG,moduleID,"magickResizeImage",module_magick_resize_image);
	zenglApi_SetModFunHandle(VM_ARG,moduleID,"magickWriteImage",module_magick_write_image);
	zenglApi_SetModFunHandle(VM_ARG,moduleID,"magickDestroyWand",module_magick_destroy_wand);
	zenglApi_SetModFunHandle(VM_ARG,moduleID,"magickWandTerminus",module_magick_wand_terminus);
}


    在my_webroot/v0_11_0目录内新增了test.zl,该脚本主要用于测试magick模块中和图像操作相关的模块函数,该脚本的内容如下:

use builtin, magick;

fun exit(err)
	print err;
	print '</body></html>';
	bltExit();
endfun

print '<!Doctype html>
	<html>
	<head>
		<meta http-equiv="content-type" content="text/html;charset=utf-8" />
		<title>测试magick模块</title>
	</head>
	<body>';

// magickWandGenesis();
wand = magickNewWand();
if(!magickReadImage(wand, 'king.png'))
	exit('read king.png failed');
endif
print '<img src="king.png"><br/>';
print 'format: ' + magickGetImageFormat(wand) + '<br/>';
print 'width: ' + magickGetImageWidth(wand) + '<br/>';
print 'height: ' + magickGetImageHeight(wand) + '<br/>';
if(!magickResizeImage(wand, 200, 150, "LanczosFilter"))
	exit('resize king.png failed');
endif
if(!magickWriteImage(wand, 'thumb.jpg'))
	exit('write to thumb.jpg failed');
endif
wand = magickNewWand();
if(!magickReadImage(wand, 'thumb.jpg'))
	exit('read thumb.jpg failed');
endif
print '===============<br/>the thumb.jpg:<br/><img src="thumb.jpg"><br/>';
print 'format: ' + magickGetImageFormat(wand) + '<br/>';
print 'width: ' + magickGetImageWidth(wand) + '<br/>';
print 'height: ' + magickGetImageHeight(wand) + '<br/>';
print 'end';
// magickDestroyWand(wand);
// magickWandTerminus();

print '</body></html>';


    该脚本的执行结果如下:



图2:test.zl测试magick模块函数

    通过magick模块函数,访问test.zl脚本,就生成了king.png的缩略图。

    当前版本还在内建模块中增加了bltDate,bltMkdir,bltUnlink,以及bltFileExists模块函数,这些模块函数的C源码都位于module_builtin.c文件中:

/**
 * bltDate模块函数,将时间戳转为字符串格式返回
 * 第一个参数format表示需要生成的字符串格式,第二个可选参数timestamp表示需要进行转换的时间戳,如果没有提供第二个参数,则默认使用当前时间对应的时间戳
 * 例如:
 * print bltDate('%Y-%m-%d %H:%M:%S') + '<br/>';
 * print bltDate('%Y-%m-%d %H:%M:%S', 574210255)+ '<br/>';
 * 执行结果如下:
 * 2018-06-18 10:53:28
 * 1988-03-13 06:50:55
 * 上面第一个语句中,没有提供第二个参数,则使用当前时间戳。第二个语句中第二个参数574210255时间戳对应的日期时间是:1988-03-13 06:50:55
 * 由于该模块函数底层是使用strftime来生成格式化字符串的,因此,具体的格式是由strftime来决定的,例如:%Y表示年,%m表示月等
 * 可以使用man strftime来查看具体有哪些格式
 */
ZL_EXP_VOID module_builtin_date(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}};
	if(argcount < 1)
		zenglApi_Exit(VM_ARG,"usage: bltDate(format[, timestamp])");
	zenglApi_GetFunArg(VM_ARG,1,&arg);
	if(arg.type != ZL_EXP_FAT_STR) {
		zenglApi_Exit(VM_ARG,"the first argument [format] of bltDate must be string");
	}
	char * format = arg.val.str;
	time_t rawtime;
	if(argcount > 1) {
		zenglApi_GetFunArg(VM_ARG,2,&arg);
		if(arg.type != ZL_EXP_FAT_INT) {
			zenglApi_Exit(VM_ARG,"the second argument [timestamp] of bltDate must be integer");
		}
		rawtime = (time_t)arg.val.integer;
	}
	else {
		time(&rawtime);
	}
	struct tm * timeinfo;
	char buffer[128];
	timeinfo = localtime(&rawtime);
	size_t ret = strftime (buffer,sizeof(buffer), format,timeinfo);
	if(ret == 0) {
		zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_STR, "", 0, 0);
	}
	else {
		zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_STR, buffer, 0, 0);
	}
}

/**
 * bltMkdir模块函数,根据指定的路径,创建目录
 * 第一个参数path表示相对于当前执行脚本的路径,该模块函数将根据该路径来创建目录,第二个可选参数file_mode表示创建目录的读写执行权限
 * 例如:
 * use builtin;
 * def TRUE 1;
 * def FALSE 0;
 * path = 'tmpdir';
 * if(bltMkdir(path, 0e777) == TRUE)
 *	print 'mkdir ' + path + ' success!' + '<br/>';
 * else
 *	print 'the ' + path + ' exists, no need real mkdir' + '<br/>';
 * endif
 * 上面这段脚本在执行后,将在当前执行脚本的目录中创建一个名为tmpdir的子目录,如果tmpdir已经存在,则bltMkdir模块函数会返回0
 * 上面脚本中bltMkdir的第二个参数0e777是一个八进制值,表示需要创建的目录的读写执行权限是rwxrwxrwx,也就是所有用户都可以操作该目录
 */
ZL_EXP_VOID module_builtin_mkdir(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}};
	if(argcount < 1)
		zenglApi_Exit(VM_ARG,"usage: bltMkdir(path[, file_mode])");
	zenglApi_GetFunArg(VM_ARG,1,&arg);
	if(arg.type != ZL_EXP_FAT_STR) {
		zenglApi_Exit(VM_ARG,"the first argument [path] of bltMkdir must be string");
	}
	char full_path[FULL_PATH_SIZE];
	char * filename = arg.val.str;
	mode_t file_mode;
	if(argcount > 1) {
		zenglApi_GetFunArg(VM_ARG,2,&arg);
		if(arg.type != ZL_EXP_FAT_INT) {
			zenglApi_Exit(VM_ARG,"the second argument [file_mode] of bltMkdir must be integer");
		}
		file_mode = (mode_t)arg.val.integer;
	}
	else {
		file_mode = 0755;
	}
	MAIN_DATA * my_data = zenglApi_GetExtraData(VM_ARG, "my_data");
	builtin_make_fullpath(full_path, filename, my_data);
	struct stat st = {0};
	if (stat(full_path, &st) == -1) {
		if(mkdir(full_path, file_mode) != 0) {
			zenglApi_Exit(VM_ARG,"bltMkdir <%s> failed [%d] %s", full_path, errno, strerror(errno));
		}
		zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_INT, ZL_EXP_NULL, ZL_EXP_TRUE, 0);
	}
	else
		zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_INT, ZL_EXP_NULL, ZL_EXP_FALSE, 0);
}

/**
 * bltUnlink模块函数,删除指定路径对应的文件
 * 该模块函数的第一个参数path表示相对于当前执行脚本的路径,如果path对应的文件存在,且具有权限,则模块函数会将该文件给删除掉
 * 例如:
 * bltUnlink('thumb.jpg');
 * 上面脚本中,如果thumb.jpg存在,则将其删除,如果文件不存在则直接返回0
 * 该模块函数只能用于删除常规文件,不可以删除目录
 */
ZL_EXP_VOID module_builtin_unlink(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}};
	if(argcount < 1)
		zenglApi_Exit(VM_ARG,"usage: bltUnlink(path)");
	zenglApi_GetFunArg(VM_ARG,1,&arg);
	if(arg.type != ZL_EXP_FAT_STR) {
		zenglApi_Exit(VM_ARG,"the first argument [path] of bltUnlink must be string");
	}
	char full_path[FULL_PATH_SIZE];
	char * filename = arg.val.str;
	MAIN_DATA * my_data = zenglApi_GetExtraData(VM_ARG, "my_data");
	builtin_make_fullpath(full_path, filename, my_data);
	struct stat st = {0};
	if (stat(full_path, &st) == -1)
		zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_INT, ZL_EXP_NULL, ZL_EXP_FALSE, 0);
	else {
		if(unlink(full_path) != 0) {
			zenglApi_Exit(VM_ARG,"bltUnlink <%s> failed [%d] %s", full_path, errno, strerror(errno));
		}
		zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_INT, ZL_EXP_NULL, ZL_EXP_TRUE, 0);
	}
}

/**
 * bltFileExists模块函数,检测指定路径的文件是否存在
 * 该模块函数的第一个参数path表示相对于当前执行脚本的路径,如果path对应的文件存在,则返回1,否则返回0
 * 例如:
 * file = 'thumb.jpg';
 * if(bltFileExists(file))
 *	bltUnlink(file);
 *	print 'unlink ' + file + ' success!' + '<br/>';
 * else
 *	print file + ' not exists, no need real unlink' + '<br/>';
 * endif
 * 上面脚本在执行时,如果bltFileExists模块函数检测到thumb.jpg文件存在,则会将该文件删除,并提示unlink thumb.jpg success!
 * 如果文件不存在,则bltFileExists模块函数会返回0,并提示thumb.jpg not exists, no need real unlink
 * bltFileExists模块函数还可以检测目录是否存在
 */
ZL_EXP_VOID module_builtin_file_exists(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}};
	if(argcount < 1)
		zenglApi_Exit(VM_ARG,"usage: bltFileExists(path)");
	zenglApi_GetFunArg(VM_ARG,1,&arg);
	if(arg.type != ZL_EXP_FAT_STR) {
		zenglApi_Exit(VM_ARG,"the first argument [path] of bltFileExists must be string");
	}
	char full_path[FULL_PATH_SIZE];
	char * filename = arg.val.str;
	MAIN_DATA * my_data = zenglApi_GetExtraData(VM_ARG, "my_data");
	builtin_make_fullpath(full_path, filename, my_data);
	struct stat st = {0};
	int retval = stat(full_path, &st);
	if (retval == -1) {
		if(errno == ENOENT)
			zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_INT, ZL_EXP_NULL, ZL_EXP_FALSE, 0);
		else
			zenglApi_Exit(VM_ARG,"bltFileExists <%s> failed [%d] %s", full_path, errno, strerror(errno));
	}
	else if(retval == 0)
		zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_INT, ZL_EXP_NULL, ZL_EXP_TRUE, 0);
	else
		zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_INT, ZL_EXP_NULL, ZL_EXP_FALSE, 0);
}


    在my_webroot/v0_11_0目录内还有一个test2.zl的测试脚本,该脚本主要用于测试上面这些新增的内建模块函数的使用,该脚本的内容如下:

use builtin;
def TRUE 1;
def FALSE 0;

print '<!Doctype html>
	<html>
	<head>
		<meta http-equiv="content-type" content="text/html;charset=utf-8" />
		<title>测试bltDate, bltMkdir, bltFileExists, bltUnlink模块函数</title>
	</head>
	<body>';

print bltDate('%Y-%m-%d %H:%M:%S') + '<br/>';
print bltDate('%Y-%m-%d %H:%M:%S', 574210255)+ '<br/>';
path = 'tmpdir';

if(bltFileExists(path)) // bltFileExists还可以检测目录是否存在
	print path + ' dir exists<br/>';
else
	print path + ' dir not exists<br/>';
endif

if(bltMkdir(path, 0e777) == TRUE)
	print 'mkdir ' + path + ' success!' + '<br/>';
else
	print 'the ' + path + ' exists, no need real mkdir' + '<br/>';
endif

file = 'thumb.jpg';
if(bltFileExists(file))
	bltUnlink(file);
	print 'unlink ' + file + ' success!' + '<br/>';
else
	print file + ' not exists, no need real unlink' + '<br/>';
endif

print '</body></html>';


    该脚本的执行结果如下:



图3:test2.zl测试新增的内建模块函数

    以上就是当前版本相关的内容。OK,就到这里,休息,休息一下 o(∩_∩)o~~

结束语:

    只有弱小的人,才懂得力量的可贵

——  《美国队长》
 
上下篇

下一篇: zenglServer v0.12.0 图形验证码, 新增模块函数

上一篇: zenglServer v0.10.1 添加bltInt,bltFloat,bltHtmlEscape模块函数,使用v1.8.1版本的zengl语言库

相关文章

zenglServer v0.6.0 session会话

zenglServer v0.10.1 添加bltInt,bltFloat,bltHtmlEscape模块函数,使用v1.8.1版本的zengl语言库

zenglServer v0.13.0 目录入口文件以及模板路径调整

zenglServer v0.3.0 mysql模块

zenglServer v0.12.0 图形验证码, 新增模块函数

zenglServer v0.5.0 设置响应头, 获取cookie