Anuncios

Bienvenidos sean a este post, continuando con nuestro post anterior pasaremos a un ejemplo mas complejo donde construiremos un contenedor para llamar funcion de Lua usando la aplicacion vararg en C.

Anuncios

Nuestra funcion contenedora (llamemosla llamar_va) toma el nombre de la funcion a ser llamada, una cadena describiendo los tipos de argumentos y resultados, luego la lista de argumentos y por ultimo una lista de apuntadores a variables para almacenar los resultados, maneja todos los detalles de la API, con esta funcion podriamos escribir nuestro ejemplo del post anterior de la siguiente manera:

llamar_va("f","dd>d",x, y, &z);
Anuncios

Donde la cadena “dd>d” significa “dos argumentos de tipo doble, un resultado de tipo doble”, este descriptor puede usar las letras ‘d’ para doble, ‘i’ para enteros (integer) y ‘s’ para cadenas (strings), un ‘>’ separa los argumentos del resultado, si la funcion no tiene resultados el ‘>’ es opcional, veamos el siguiente codigo:

#include <stdarg.h>

void llamar_va(const char *func, const char *sig, ...)
{
	va_list v1;
	int narg, nres;
	va_start(v1, sig);
	lua_getglobal(L, func);

	< empujando argumentos, en un proximo codigo >

	nres = strlen(sig);

	if (lua_pcall(L, narg, nres, 0) != 0)
		error(L, "error llamando: '%s': %s", func, lua_tostring(L, -1));
	
	< recuperando resultados, en otro proximo codigo >

	va_end(v1);
}
Anuncios

Este codigo muestra la implementacion de la funcion llamar_va, mas alla de su generalidad esta funcion sigue los mismos pasos de nuestro primer ejemplo, primero empuja la funcion a la pila, veamos el siguiente codigo:

for (narg = 0; *sig; narg++)
{
	luaL_checkstack(L, 1, "demasiados argumentos");
	
	switch(*sig++)
	{
		case 'd':
			lua_pushnumber(L, va_arg(v1, double));
			break;
		case 'i':
			lua_pushinteger(L, va_arg(v1, int));
			break;
		case 's':
			lua_pushstring(L, v1, char *));
			break;
		case '>':
			goto endargs;
		default:
			error(L, "opcion invalida (%c)", *(sig-1));
	}
}
endargs:
Anuncios

Este codigo se encarga de empujar los argumentos, en este caso primero chequeamos la pila tenemos un mensaje de error en caso de que nos excedamos de los argumentos, nuestro siguiente paso sera un switch para chequear al apuntador de sig, en caso de que sea d, es decir un numero doble, empujara a la pila este valor, en el siguiente caso de que sea i, es decir un numero entero, tambien lo empuja a la pila, nuestro siguiente paso sera para las cadenas, tambien tenemos uno para el operador que delimita al argumento de los resultados, lo que hablamos antes, y por ultimo un default que nos notificara en caso de que no encaje con ninguno de los parametros anteriores, hacemos la llamada, luego recuperamos los resultados con el siguiente codigo:

nres = -nres;

while(*sig)
{
	switch(*sig++)
	{
		case 'd':
			if (!lua_isnumber(L, nres))
				error(L, "tipo de resultado erroneo");
			*va_arg(v1, double *) = lua_tonumber(L, nres);
			break;
		case 'i':
			if (!lua_isnumber(L, nres))
				error(L, "tipo de resultado erroneo");
			*va_arg(v1, int *) = lua_tonumber(L, nres);
			break;
		case 's':
			if (!lua_isstring(L, nres))
				error(L, "tipo de resultado erroneo");
			*va_arg(v1, const char **) = lua_tostring(L, nres);
			break;
		default:
			error(L, "opcion invalida (%c)", *(sig-1));
	}
	nres++;
}
Anuncios
Anuncios

Con este codigo obtenemos los resultados, en este caso chequea el resultado que debe recuperar pero al igual que en el caso anterior dependiendo del identificador, aunque en todo los casos es igual primero va a chequear si el tipo coincide con el identificador si falla devuelve un error sino lo recupera de la pila y lo asigna a la variable, en caso de no que no coincida con ninguna devuelve un error como en el codigo anterior, la mayoria de su codigo es sencilla pero hay otras sutilezas:

  • primero no necesita chequear si func es una funcion ya que lua_pcall disparara cualquier error
  • segundo porque empuja un numero arbitrario de argumentos, debe chequear el espacio de la pila
  • tercero, la funcion puede retornar cadenas y llamar_va no puede quitar los resultados de la pila y esto depende del llamador para quitarlos

Despues lo finaliza usando resultados de cadena ocasionales o despues de copiarlos a otros buffers, antes de terminar veamos como quedo el codigo final:

#include <stdarg.h>

void llamar_va(const char *func, const char *sig, ...)
{
	va_list v1;
	int narg, nres;
	va_start(v1, sig);
	lua_getglobal(L, func);

	for (narg = 0; *sig; narg++)
	{
		luaL_checkstack(L, 1, "demasiados argumentos");
		
		switch(*sig++)
		{
			case 'd':
				lua_pushnumber(L, va_arg(v1, double));
				break;
			case 'i':
				lua_pushinteger(L, va_arg(v1, int));
				break;
			case 's':
				lua_pushstring(L, v1, char *));
				break;
			case '>':
				goto endargs;
			default:
				error(L, "opcion invalida (%c)", *(sig-1));
		}
	}
	
	endargs:
	nres = strlen(sig);

	if (lua_pcall(L, narg, nres, 0) != 0)
		error(L, "error llamando: '%s': %s", func, lua_tostring(L, -1));
	
	nres = -nres;

	while(*sig)
	{
		switch(*sig++)
		{
			case 'd':
				if (!lua_isnumber(L, nres))
					error(L, "tipo de resultado erroneo");
				*va_arg(v1, double *) = lua_tonumber(L, nres);
				break;
			case 'i':
				if (!lua_isnumber(L, nres))
					error(L, "tipo de resultado erroneo");
				*va_arg(v1, int *) = lua_tonumber(L, nres);
				break;
			case 's':
				if (!lua_isstring(L, nres))
					error(L, "tipo de resultado erroneo");
				*va_arg(v1, const char **) = lua_tostring(L, nres);
				break;
			default:
				error(L, "opcion invalida (%c)", *(sig-1));
		}
		nres++;
	}
	
	va_end(v1);
}
Anuncios

En resumen, hoy hemos visto como es un llamador de funciones generico, que nos permite trabajar con distintos tipos, vimos como subirlos y como recuperarlos, espero les haya sido util sigueme en tumblr, Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.

Anuncios

Tengo un Patreon donde podes acceder de manera exclusiva a material para este blog antes de ser publicado, sigue los pasos del link para saber como.

Tambien podes donar

Es para mantenimiento del sitio, gracias!

$1.00