Crear nuestra propia Run Time Library (Segunda Parte)
Primero que nada vamos a crear el directorio CLIB donde queramos y pasar los archivos que creamos en el primer tutorial ahi (C_ALLOC.CPP, C_MAIN.CPP, C_STR.CPP y C_WMAIN.CPP).
Yo uso una función con mucha frecuencia que simplemente muestra un número en pantalla con un MessageBox:
void SayLong(int number)
{
char Data[10];
ltoa(number,Data,10);
MessageBox(0,Data,"El Numero",0);
}
No tenemos acceso a ltoa al ser una función de la Run Time Library y por supuesto la vamos a implementar:
C_LTOA.CPP
#include <string.h>
extern "C" char *ltoa(long value,char * string,int radix)
{
char *ptr;
unsigned digval;
ptr = string;
do
{
digval = (unsigned)(value % radix);
value /= radix;
*ptr++ = (char)(digval + 48);
} while (value > 0);
*ptr-- = ' ';
strrev(string);
return string;
}
C_STRREV.CPP
extern "C" char *strrev (char *string)
{
char *start = string;
char *left = string;
char ch;
while (*string++);
string -= 2;
while (left < string)
{
ch = *left;
*left++ = *string;
*string-- = ch;
}
return(start);
}
Tanto ltoa como strrev son funciones muy básicas que trabajan con cadenas de caracteres, vamos a crear nuestra libreria de esta forma:
cl /c *.cpp
lib *.obj /out:clib.lib
En el final de la primera parte les dije que ibamos a ver como crear los operadores new y delete, bueno vamos a intentar crear este más que simple programa y de paso probar atol y strrev:
SIMPLE.CPP (lo creamos en un directorio diferente al de la libreria)
#include <windows.h>
void SayLong(int number)
{
char Data[10];
ltoa(number,Data,10);
MessageBox(0,Data,"El Numero",0);
}
class Man
{
public:
Man()
{
SayLong(66);
}
~Man()
{
SayLong(99);
}
};
int main()
{
Man *yo = new Man;
return 0;
}
Con nuestra Run Time Library:
cl /c simple.cpp
link simple.obj /nodefaultlib clibclib.lib user32.lib kernel32.lib
Como algunos ya se habrán imaginado esto no es posible, todavía, porque no hemos creado el operador new.
Entonces el linker se queja:
simple.obj : error LNK2001: unresolved external symbol "void * __cdecl operator new(unsigned int)" (??2@YAPAXI@Z)Que es realmente lo que realiza el operador new? Es fundamental saber esto antes de intentar emular su funcionamiento; pese a los mitos que dominan el mundo de la informática el operador new no hace practicamente nada, el mismo se limita a llamar a malloc y devolver el puntero asignado por esta función.
Eso es todo lo que realiza new, pero no hay que pasar por alto algo muy importante, el compilador llama al constructor del objeto si usamos new. Por eso siempre que usemos objetos dinámicos debemos usar new por lógica.
Entonces vamos a crear el C_NEW.CPP teniendo en cuenta lo que ya sabemos:
#include <stdlib.h>
#include <malloc.h>
void *operator new(unsigned int cb) { return malloc(cb); }
En este código simplemente llamamos a malloc (nuestro malloc, el que creamos en C_ALLOC.CPP).
Recompilamos nuestra libreria:
cl /c *.cpp
lib *.obj /out:clib.lib
Volvemos a linkear (no hace falta volver a compilar) el programa anterior con nuestra Run Time Library así:
cl /c simple.cpp
link simple.obj /nodefaultlib clib.lib user32.lib kernel32.lib
Al ejecutar el programa simple.exe vemos el número 66 en pantalla, eso quiere decir que nuestro constructor fue llamado y como sabemos que nuestro código de new es una linea y no llama a ningún constructor tenemos la prueba fehaciente de que los constructores son llamados por el compilador y no por new.
Con este código estamos dejando memoria sin liberar, el objeto creado no está siendo destruido en ninguna parte, así que vamos a deshacer el error:
SIMPLE.CPP
#include <windows.h>
void SayLong(int number)
{
char Data[10];
ltoa(number,Data,10);
MessageBox(0,Data,"El Numero",0);
}
class Man
{
public:
Man()
{
SayLong(66);
}
~Man()
{
SayLong(99);
}
};
int main()
{
Man *yo = new Man;
delete yo;
return 0;
}
Intentamos compilarlo con nuestra linea de comandos de siempre
cl /c simple.cpp
link simple.obj /nodefaultlib clib.lib user32.lib kernel32.lib
Y nos encontramos con el siguiente error:
simple.obj : error LNK2001: unresolved external symbol "void __cdecl operator delete(void *)" (??3@YAXPAX@Z)Si van siguiendo atentamente este tutorial ya deben saber que delete es simplemente una llamada a free y el compilador implementa la llamada al destructor, vamos a comprobarlo implementando nuestra versión del operador delete:
C_DELETE.CPP
#include <stdlib.h>
#include <malloc.h>
void operator delete(void *p) { free(p); }
Creamos la libreria con la linea de comandos pertinente y volvemos a linkear (no hace falta volver a compilar) el programa con nuestra linea de comandos de siempre, como esperabamos el resultado obtenido es un mensaje con el número 66 y otro con el 99, demostrando nuevamente como los constructores y destructores son llamados por el compilador.
Vamos a incluir un poco más de complejidad en nuestra clase para probarla:
SIMPLE.CPP
#include <windows.h>
void SayLong(int number)
{
char Data[10];
ltoa(number,Data,10);
MessageBox(0,Data,"El Numero",0);
}
class Man
{
public:
int edad;
int pais;
};
int main()
{
Man *yo = new Man;
Man *john = new Man;
Man *pedro = new Man;
yo->edad = 20;
yo->pais = 54; //argentina
john->edad = 25;
john->pais = 1; //usa
pedro->edad = 14;
pedro->pais = 34; //españa
SayLong(yo->edad);
SayLong(john->edad);
SayLong(pedro->edad);
delete yo;
delete john;
delete pedro;
return 0;
}
Volvemos a compilar, linkear y comprobar que el resultado es el esperado, los numeros que salen en pantalla son 20, 25 y 14, en ese mismo orden.
Generalmente las aplicaciones de consola usan parametros introducidos por el usuario en la linea de comandos, es una convención que el primer parametro sea el nombre de la aplicación.
Como podemos emular el funcionamiento de VC++ que no sólo nos pasa como parametros a nuestra función main, argc y argv sino que también los mantiene como variables estaticas?
De esta forma, vamos a reemplazar nuestro C_MAIN.CPP que no hacía nada por este otro que hace bastantes cosas:
#include <malloc.h>
#include <string.h>
extern "C" int __argc = 1;
extern "C" char** __argv = 0;
extern "C" void __stdcall ExitProcess(unsigned long uExitCode);
extern "C" char* __stdcall GetCommandLineA(void);
extern int main(int argc, char *argv[],char *env[]);
extern "C" void mainCRTStartup()
{
char *parametros = GetCommandLineA();
char *temp = (char*)malloc(2048);
memset(temp,0,2048);
__argc = 0;
__argv = (char**)malloc(4);
char Except = false;
while(*parametros)
{
if (*parametros == 34)
{
if (Except == false)
{
Except = true;
}
else
{
Except = false;
if (strlen(temp) > 0)
{
__argv = (char**)realloc(__argv,4 * (__argc+1));
__argv[__argc] = (char*)malloc(strlen(temp) + 1);
memset(__argv[__argc],0,strlen(temp) + 1);
strcpy(__argv[__argc],temp);
strcpy(temp,"");
__argc++;
}
}
parametros++;
continue;
}
if ( (*parametros == 32) && (Except == false) )
{
if (strlen(temp) > 0)
{
__argv = (char**)realloc(__argv,4 * (__argc+1));
__argv[__argc] = (char*)malloc(strlen(temp) + 1);
memset(__argv[__argc],0,strlen(temp) + 1);
strcpy(__argv[__argc],temp);
strcpy(temp,"");
__argc++;
}
}
else
{
unsigned long pos = strlen(temp);
temp[pos] = *parametros;
temp[pos+1] = 0;
}
parametros++;
}
if (strlen(temp) > 0)
{
__argv = (char**)realloc(__argv,4 * (__argc+1));
__argv[__argc] = (char*)malloc(strlen(temp) + 1);
memset(__argv[__argc],0,strlen(temp) + 1);
strcpy(__argv[__argc],temp);
strcpy(temp,"");
__argc++;
}
free(temp);
main(__argc,__argv,0);
for (int y = 0;y < __argc;y++) { free(__argv[y]); }
free(__argv);
ExitProcess(0);
}
Si intentaramos construir la libreria en este momento no tendríamos ningún problema, pero cada vez que quisieramos utilizarla nos pediría las funciones _realloc y _memset por lo tanto sería una libreria completamente inútil.
realloc lo usamos porque no sabemos cuantos elementos va a tener la linea de comandos y memset para dejar un buffer lleno de ceros y no tener datos 'basura'.
C_REALLOC.CPP
#include <windows.h>
void *realloc(void *block, size_t size) { return HeapReAlloc(GetProcessHeap(),NULL,block,size); }
C_MEMSET.CPP
extern "C" void *memset(void *s,int c,size_t n)
{
char *t = (char*)s;
while(n--)
{
*t++ = c;
}
return s;
}
Ahora si podemos crear la libreria con los parametros de siempre.
Este código consigue pasarle como parametros argc y argv a la función main utilizando la función GetCommandLine de la API de Windows, usa nuestra implementación de memoria dinámica para guardar los parametros y los libera cuando termina la función main (que es llamada con main(__argc,__argv,0)
finalmente llama a la función ExitProcess de la API de Windows.
De esta forma teniendo __argc y __argv emulamos perfectamente el funcionamiento de VC++, vamos a comprobarlo con este pequeño código:
CMDLINE.CPP
#include <windows.h>
extern int __argc;
extern char **__argv;
void SayLong(int number)
{
char Data[10];
ltoa(number,Data,10);
MessageBox(0,Data,"El Numero",0);
}
void main(int argc,char *argv[])
{
SayLong(argc);
for (int g = 0;g < argc;g++)
{
MessageBox(0,argv[g],argv[g],0);
}
SayLong(__argc);
for (int h = 0;h < __argc;h++)
{
MessageBox(0,__argv[h],__argv[h],0);
}
}
Compilamos y linkeamos con:
cl /c cmdline.cpp
link cmdline.obj /nodefaultlib clib.lib user32.lib kernel32.libEl programa funciona perfectamente, argc y __argc coinciden y argv y __argv también, prueben a compilarlo sin nuestra Run Time Library y obtendran los mismos resultados en cuanto a funcionamiento.
Bueno gente, eso es todo por ahora, en la tercera parte vamos a ver como implementar constructores y destructores para objetos estáticos.
Saludos,
Mariano.
Autor: Mariano Ventaja
http://www.c0d3rz.com.arDescarga:
http://www.c0d3rz.com.ar/foro/viewtopic.php?t=94