El Blog de JuDelCo

Programación, Música, Arte y chorradas varias!

Programa Videojuegos, no Motores

Publicado por JuDelCo el 13 septiembre 2012
Publicado en: Programación. 1 comentario

Para empezar, el término «motor» (en su definición vinculada al mundo del desarrollo) no posee una definición concreta, por lo tanto, para que nadie se pierda, intentaré definirlo con mis palabras para utilizarlo en el artículo como busco hacerlo. Un «motor» es una colección de subsistemas bien definidos y reutilizables (generalmente librerías y herramientas) diseñados para facilitar el desarrollo de un videojuego adaptándolos a una serie de requisitos que sean necesarios por el juego en cuestión (estos requisitos pueden ser de muchas categorías bien definidas: Renderizado, sonido, físicas, etc…). Aquellos motores ambiciosos que permiten configurar requisitos de distintas categorías tienden a ser conocidos como «motor de videojuego» en vez de simplemente «motor gráfico» o «motor de físicas» (por ejemplo).

Ahora que nos entendemos, vayámonos con el caso real: «Como programar el motor de un videojuego».

Por alguna razón, muchos desarrolladores neófitos de videojuegos (y a veces experimentados) tienden a creer que para crear un videojuego necesitan crear primero un motor (ya que piensan que es una parte crítica del mismo). Ellos ven el juego como un coche (un coche sin motor simplemente no funcionará), sin embargo, esa analogía mecánica empieza a venirse abajo cuando nos damos cuenta de la realidad de la situación: un motor está constituido SOLAMENTE por componentes reutilizables. Al igual que puedes construir un coche con piezas reutilizables de otros coches puedes hacerlo con piezas hechas a medida y personalizadas, ¿no es verdad? El hecho de que tengas que tener un motor para programar un videojuego es mentira. Esa leyenda ha sido extendida a lo largo del tiempo por desarrolladores que no sabían hacer nada mejor.

Sin embargo, alguien puede decidir que ha llegado el momento de programar su propio motor, pensando así que entonces podrán crear juegos realmente buenos. Estas personas suelen realizar preguntas solicitando recursos y dudas sobre el diseño y el proceso de desarrollo de estos motores (de hecho si estas leyendo esto es porque probablemente buscabas crear algo similar así que déjame destruir tus sueños con un poco de esfuerzo por mi parte). Continúa leyendo

Mi consejo: Si estás tratando de programar un motor… NO LO HAGAS. No importa cuales sean tus razones (no importa si estas programando el motor para que puedas crear el juego de tus sueños, o si piensas que es una buena técnica de aprendizaje, o cualquier número de razones similares, ya que TODAS son una pérdida de tiempo. Puedes sentarte y escribir un videojuego sin tener un motor creado antes, de hecho es (normalmente) la mejor propuesta por la que puedes optar con diferencia. El proceso de desarrollo de un videojuego es MUCHO más sencillo que el de un motor ya que los requisitos para crear un videojuego están mucho más definidos y clarificados. Además, un videojuego es MUCHO más útil y recompensado que un motor (que suele acabar en un simple tech-demo).

La mayoría de los desarrolladores que «finalizan» un «motor» que fue diseñado y programado aisladamente de la creación de un juego definido (con el objetivo de tener el motor primero)… ¡ no pueden realmente ni usarlos ! (ni serán útiles para terceros). Ya que el proyecto no tuvo ningún fin, ningún límite o utilidad tangible tal proyecto no será de utilidad de cara a un proyecto futuro del cual tomar como base el finalizado, pues una base sin unos buenos pilares no tiene estabilidad ni futuro.Esos «motores» que crean como resultado tienden a ser un poco más que una mezcla de funcionalidad aleatoria inconsistente que no es práctica, robusta ni reutilizable. Además, el resultado «visible» de los motores suelen ser un pequeño mapa en el que puedes caminar y observar pequeños modelos renderizados con ciertos efectos básicos de iluminación, normal mapping, etc. Puede que también hayan implementado cierto sistema de animación o detección de colisiones. Estos proyectos son simplemente demos técnicas y ninguna es visualmente «irresistible» en particular.

La solución, aun si tu realmente quieres programar un motor, es crear EL JUEGO en su lugar. Estoy seguro que pondrás en duda este consejo, pero espera un momento. El juego NO tiene que ser una producción épica. NO tiene que ser GTA5, Quake 17, el próximo Elder Scrolls o el nuevo World of Warcraft. Solo tiene que ser un JUEGO con una jugabilidad bien definida. Es mucho más fácil definir los requerimientos necesarios para crear un juego ! (y además los pondrás en práctica). Una vez hayas hecho un juego, haz otro. Y luego otro (y luego otro,…. y otro… más y más). Cada vez que empieces un nuevo proyecto, identificarás nuevas funcionalidades que podrían ser usadas genéricamente en todos los proyectos para reunirla en una librería de código base. Probablemente tendrás que refactorizar parte de tu código para eliminar las dependencias explícitas del juego en cuestión, pero eso es algo bueno! Te ayudará a generalizar qué es generalizable de una forma en la cual podrás probar la funcionalidad genérica con tu juego acabado y confirmar que funciona (es obvio que tendrás que modificar ciertos aspectos específicos del juego para adaptarlo a código genérico).

Si repites estos pasos las veces suficientes, después de unos pocos juegos tendrás el comienzo de una colección sólida de funcionalidad reutilizable que ha sido PROBADA para tener aplicaciones prácticas en el desarrollo de un videojuego. Además, tendrás un conjunto como resultado de juegos interesantes que podrán demostrar la solidez de tu librería. Este método de crecimiento del motor (en vez de crear uno de golpe desde la nada) es infinitamente mejor ya que te ayuda a definir tus metas, te fuerza a pensar sobre los problemas que realmente tendrás a la hora de crear un videojuego y las implementaciones que necesitarás hacer. También te forzará a pensar sobre la necesaria separación entre lo específico del juego y la funcionalidad lógica del motor. Esto hará que aprendas de tus errores en vez de pretender que no los tendrás.

¿A qué esperas? ¡Deja de divagar y ponte hacer un videojuego! ¡TU videojuego!

Basado en: Write games, not engines

Comparte esto !

  • Haz clic para compartir en Twitter (Se abre en una ventana nueva)
  • Haz clic para compartir en Facebook (Se abre en una ventana nueva)

Me gusta esto:

Me gusta Cargando...

[PC] Slaves to Armok II: Dwarf Fortress

Publicado por JuDelCo el 29 enero 2012
Publicado en: Noticias, Videojuegos. 13 comentarios

No os voy a mentir, esto es un clon (actualizado y ampliado) de la antigua entrada que hice de Dwarf Fortress pero con motivo de ACTUALIZAR el enorme pack que hice con utilidades, skins, enlaces, y un sin fin de cosas. Así que:

¿De qué va Dwarf Fortress?

Es una mezcla de todo, elementos de juegos de estrategia, de rol, de construcción, enanitos locos, Sims, dungeon crawlers… Único.
Tu propósito es levantar una fortaleza enana en un mundo que cambia a cada partida y en el que TODO puede pasar, desde un ataque goblin, a que tus enanos se mueran ahogados en su propia mierda, se suiciden, exploten experimentando, se depriman, se cabreen con vosotros y os destruyan el castillo… como he dicho antes, algo único, TAN unico que algunos fans del juego han hecho fortalezas increíbles completamente gestionadas hasta un mismísimo ordenador funcional con todos sus componentes de hardware hecho a mano (increíble obra de ingeniería, en serio) con los componentes de construcción del propio juego. El juego tiene un sistema de físicas muy complejo, incluyendo gravedad (cuidado con cavar cuevas muy anchas sin columnas!) sistema de hidráulicas, presiones, magma, volcanes, erosión, temperatura, química, y paro porque no acabaría.

Review:

Bueno, lo primero es lo primero, aspectos positivos y negativos del juego.

Positivo:

  • Experiencia de juego muy distinta a los típicos juegos comerciales o indie.
  • Da para horas y horas y horas y horas (y horas) de probar cosas distintas, el juego ademas anima a ello. Si te encuentras atascado, visita la wiki!
  • Situaciones (o eventos, como se llama en el juego) que cambian siempre, ninguna partida es como la de otra persona, pueden ocurrir cosas similares, pero vuestras historias jamas se parecerán.
  • No hay ningún juego tan profundo como este
  • Pica muchísimo, es bastante adictivo.
  • Gratuito y multiplataforma

Negativo:

  • Aspecto gráfico bastante ausente, empezó siendo solo con caracteres ASCII, pero ya tiene sprites decentes en la versión estándar, no obstante puedes jugar a cualquier versión, tanto ASCII, como básica, como otras que hay por ahí, algunas realmente buenas con gráficos en isométrico. Así que se puede decir que está subsanado en parte.
  • Dificultad MUY elevada en muchas ocasiones (dependiendo de como juguemos claro!)
  • Curva de aprendizaje jodida, tienes que echarle una tarde para controlar los menús y saber como va esto (muy por encima!). TODO se maneja con controles de teclado.
  • Esta en inglés y, aunque yo no he tenido mayor problema, no obstante el juego tiene una gran riqueza en su vocabulario por lo que si no sabes muy bien inglés te recomiendo tener al lado Google Translate para traducir palabras sueltas.

¿Y ahora… qué?

Hay tutoriales completos en ingles, simplemente con visitar la Wiki oficial (es enorme) del juego o realizando una sencilla búsqueda en Youtube. También los hay en español, aunque sean un poco antiguos y no profundicen tanto como los ingleses, pero es lo que hay, ya que el juego está íntegramente en inglés y tendremos que jugarlo así.
Por mi parte, os he recopilado un pack actualizado con todas las herramientas / utilidades más útiles del juego, packs de texturas, enlaces de interés a webs relacionadas (como el archivo de mapas, un repositorio de más utilidades, wikis, tutoriales a punta pala, etc larguisimo). Aparte de estos enlaces, también están descargados varios tutoriales en PDF así como el propio juego actualizado y comics e imágenes, y todo ello categorizado en carpetas y con sus correspondientes enlaces para que podáis visitar la fuente del recurso (para que podáis descargar sus actualizaciones en caso de que las sufran).

 

Sin más, os dejo aquí el pack, pero esta vez en MEDIAFIRE, ya que Megaupload ha muerto llevándose consigo a la tumba el anterior pack (aunque no es una pérdida muy grave, ya que estaba desactualizado y este trae lo que tenía el anterior más miles de cosas más, lo veréis por el peso del mismo):

 

Dwarf Fortress Pack Parte 1/3 [Mediafire]
Dwarf Fortress Pack Parte 2/3 [Mediafire]
Dwarf Fortress Pack Parte 3/3 [Mediafire]

 
Saludos !

Comparte esto !

  • Haz clic para compartir en Twitter (Se abre en una ventana nueva)
  • Haz clic para compartir en Facebook (Se abre en una ventana nueva)

Me gusta esto:

Me gusta Cargando...

[Opinión personal] El nacimiento de un videojuego

Publicado por JuDelCo el 3 enero 2012
Publicado en: Programación, Videojuegos. 7 comentarios

Aunque esto va más orientado realmente a la creación del proyecto de un videojuego, ¡mejor aún!

Veamos, quiero hablar sobre aquellos proyectos en los que personas (generalmente jóvenes o neófitos en el tema con muchas ganas y poca experiencia) crean un «llamamiento» (ya sean diseñadores, programadores, etc) para llevar a cabo un proyecto el cual está (generalmente) vagamente diseñado. Por ejemplo, típica persona que se apunta a un foro en el que sabe que pulula al menos gente interesada en el tema, y crea un hilo en el que busca a un número de personas (cuantas más siempre mejor, ¡claro!) a las que clasifica en categorías (va poniendo una lista categorizándolas, un grupo para los diseñadores, otro para los programadores, etc…), usando como reclamo un par de frases descriptivas de lo que quiere hacer o algún boceto, ya sea un juego shooter, un plataformas, un clonico del pacman, etc. Bien, esta persona (o personas) casi nunca cuentan con nada hecho, como mucho un par de bocetos o requisitos del juego de una forma mas o menos poco detallada. Y claro, imagino que habréis visto en qué acaba la mayoría de estos proyectos…

Creo que el que más lejos llegó fue uno en el que crearon una web y un foro con registro cerrado para esas personas a las que «reclutaron». Llegaron a crear: Por parte de los diseñadores, un par de bocetos o tiras de sprites de calidad discutible. Por parte de los programadores discusiones y discusiones sobre qué lenguaje/librerias/programas/herramientas usar, con screens de esos programas para ver qué se podría llegar a hacer, y, por parte del que lo ha «organizado» todo, un par de detalles del juego de qué podría tener o algún boceto más sobre como podría ser un nivel del mismo. NADA MAS, hasta ahí he visto yo. Una vez se llega a este punto el 80% mínimo de la gente «reclutada» ha abandonado o ha desaparecido, y el resto resultante está en un caos de organización tan masivo y con tan poca documentación en la que basar su trabajo, que no pueden hacer más que esperar o intentar liar más el rizo.

Pues no, esto así no se hace. Aparte de la insistencia de aquellas personas a las cuales se ha de reclutar (y deben de ser de confianza o demostrar experiencia previa en otros proyectos), NO se puede crear software entre varios programadores desde cero. Es decir, reunir a un grupo de programadores (con una estructura jerarquizada o no, eso da igual) y empezar a hacer algo del cual no tienen un análisis previo hecho, es imposible, totalmente imposible.

Para que un proyecto salga adelante, PRIMERO debe de haberse realizado un análisis previo y definido unos requisitos del mismo lo más detallados posibles (es decir, unos documentos donde recogen qué hará el software, que no hará, y de que forma lo hará, documentar en lo posible toda la funcionalidad y capacidad del mismo). Una vez se tienen hechos (este paso SI se puede hacer entre varios, pero para ello debe haber una gran comunicación y experiencia previa por parte de los analistas involucrados), entonces hay dos salidas: O bien utilizar un engine existente (como unreal engine, cryengine, game maker, allegro, c4 engine, rpg maker, unity, o un etc muy largo…) desde el cual ya se puede comenzar con un grupo existente de personas para repartir el trabajo (ya que la base está hecha) O BIEN plantearse el diseñar el engine del videojuego desde cero, partiendo de librerías como OpenGL+SDL, XNA, o derivados (todas estarán basadas en DirectX o OpenGL, TODAS). Si se opta por lo segundo, no se puede meter a varios programadores a programar en un código inexistente desde 0 (por muy bonito que tengáis configurado el SVN o GIT). Por ello es inútil el molestarse en buscar a esas personas que te harán los gráficos, sonidos, niveles o la propia IA de los enemigos ANTES de tener creado el engine del videojuego, ya que sino estarán de brazos cruzados hasta que se tenga hecho (ya que sin el no pueden comenzar), y esto ocasionará que huyan del proyecto ya que no se les dará trabajo para hacer (si, empezarán con muchas ganas, pero a la semana se les habrá olvidado que les «reclutaste»).

Resumiendo, los pasos correctos (a MI parecer, tened en mente que es una opinión personal) para que un proyecto salga adelante en condiciones y no se abandone a la primera de cambio son (partiendo de que eres tu el que vas a organizarlo, si eres de los que reclutan esto no tiene mucho sentido, al menos puede servirte para detectar si el barco se va a pique o no):

  • Primero, si no tienes conocimientos previos de como realizar el proyecto, estudia, practica, haz mini-ejemplos (prototipos) básicos de lo que pensabas hacer, lee MUCHO, aprende inglés si es que no sabes ya (o estas perdido en cuanto a búsqueda de información en internet), y sobre todo no esperes que alguien haga todo el trabajo por ti y luego te lleves el reconomiento. Eso nunca pasará.
  • Segundo, no vale pensar en ese proyecto 5 min y hacer un txt o un boceto con 4 cosas que podría tener como «concepto», NO. Pasa todas las horas que puedas creando documentación que recoja los requisitos del videojuego, qué niveles tendrá y como, su historia o argumento (si tiene), enemigos, menús, sistema de juego, HUD, nº de jugadores, etc. Todo dato que puedas aportar detallando más y más será de utilidad. (Obviamente hazlo documentándolo propiamente, nada de lenguaje «hoygan» en un txt plano.
  • Decide antes las herramientas a utilizar, recursos, webs útiles y lo más importante, el lenguaje de programación y librerías a usar (o engine). Este será el pilar #1 del juego, así que mucho cuidado con lo que elijes. Mi recomendación personal es aprender C/C++ y usar SDL+OpenGL (dejad un comentario para preguntar más detalles).
  • Una vez hecho el paso anterior, diseña y construye TU SOLO la base del engine para el juego (por eso dije lo que dije en el primer paso). No digo que creas el juego entero por ti mismo, sino que el sistema de entidades en el mapa funcione, tengas funciones para la carga de recursos (como imágenes y sonido), el bucle principal del juego, entre otras cosas y todo muy bien estructurado, ya que en esto se basará TODO. El objetivo es conseguir un engine orientado al videojuego que quieras crear Y que esté MODULARIZADO. Es decir, que si alguien modifica el código de sonido o de IA de algún enemigo, NO se tenga que modificar nada más para que el juego siga funcionando. De esta forma varios programadores podrán repartirse la tarea sin que el trabajo de uno afecte al trabajo de otro (algo fundamental).
  • Una vez tengas el engine creado, funcionando y tengas un prototipo FUNCIONAL, entonces es el momento de buscar y reclutar a gente. Muéstrale lo que tienes hecho, dí a donde quieres llegar, crea algunas demos técnicas de tu engine para que la gente se anime y se apunte al proyecto (te aseguro que teniendo esto hecho vas a notar una diferencia brutal en cuanto a la cantidad de gente dispuesta a unirse al proyecto en comparación si no hubieras tenido casi nada hecho).
  • Reparte tareas, ¿recuerdas la documentación que te dije que hicieras del juego? Gracias a ella sabrás que hace falta por hacer y podrás repartir las tareas fácilmente. Comparte esa documentación con todo el que esté involucrado en el proyecto. Así sabrán como debería ser el juego una vez acabado y podrán aportar sus trabajos pensando en ello y dando su toque personal, y NO a ciegas. Mantén a la gente motivada y todo debería acabar bien.

Bueno, y esto es básicamente como se debería comenzar un proyecto en condiciones. Como he dicho, es una opinión personal basada en la lógica y en la experiencia (de lo que he visto, yo no he ido por ahí reclutando a todo un foro para terminar haciendo un hello world xD). Si queréis ahondar más en el tema, hace tiempo publiqué una entrada en la que hablaba de los motivos por los que acaban los proyectos orientados a videojuegos (haced click y leedla, es bastante interesante -> Por qué acaban los proyectos )

Por último, os dejo con un cuento de NightFox que no tiene desperdicio, con moraleja y todo. Aquí va:

—————————————————-

«El cuento del programador y la lechera»

Erase una vez un chavalin que soñaba con ser programador y crear sus propios videojuegos. Había escrito algún programa y estaba lleno de ilusión. Quiso empezar con algo simple, un pong y empezó a ello. Cuando tenia algunas lineas de código escritas, pensó, un pong es muy simple, yo quiero destacar, así que se dijo, bueno, un pong quizás es demasiado fácil, que tal si hago algo mas vistoso, si, un mario, así que empezó a programar, a dibujar algunos fondos, algún sprite y cuando ya tenia eso en pantalla dijo, el mario esta muy visto, que tal si le pongo una ametralladora y que pueda disparar a los Koopas. Empezó sobre el papel a hacer diseños y pensar que estaría bien. Mientras pensó, pero claro, un toque de rol molaría, como el Pokemon, poder subir de nivel, personalizar el personaje y empezó a buscar gráficos sonidos para su homebrew. Cuando tenia mucho material reunido, su programa mostraba (solo) la pantalla de titulo y un menú pensó, bueno, como ya esta empezado, hagamos un mundo mas abierto, como GTA.

Entonces le visito un amigo suyo y le pregunto: «oye, hay un concurso de programación de videojuegos, ¿vas a presentar tu juego?». Nuestro chavalin le contesta: «¡si! Pero lo he cambiado, ahora no es un pong, es un GTA + RPG + MARIO + POKEMON + ARCADE». El amigo sonríe y le dice: «Se que has aprendido mucho, pero, en dos meses, crees que seras capaz de aprender todo lo que te falta, dibujar toda esa cantidad de fondos, sprites, hacer las músicas, sonidos…»

¿Me dices que TU que hace 2 meses que programas (con suerte) harás el mismo trabajo que un equipo experto de 30 personas en 1 año?
En ese momento el castillo megachachi mágico que se había montado nuestro colega en su cabecita se vino abajo y vio que lo que pretendía era una salvajada, imposible de realizar.

Moraleja del cuento: Si alguien se cree que el solo o un grupo de 4 o 5, harán el mismo trabajo que un equipo de profesionales en una décima parte del tiempo que lo hacen ellos, no se, cuando toquen de pies en el suelo igual se fracturan las piernas de la ostia que se pegan. Así que, sed realistas, calculad que posibilidades REALES tenéis de llevar a cabo un proyecto en el tiempo que os marquéis, hacedlo y no os desviéis de lo que tenéis proyectado. Así estaréis en ese 5% de proyectos que se terminan.

—————————————————-

¡ Hasta la próxima entrada !

Comparte esto !

  • Haz clic para compartir en Twitter (Se abre en una ventana nueva)
  • Haz clic para compartir en Facebook (Se abre en una ventana nueva)

Me gusta esto:

Me gusta Cargando...

Introducción al lenguaje ensamblador IL de .NET [Parte 4]

Publicado por JuDelCo el 18 diciembre 2011
Publicado en: Programación. 3 comentarios

[ <– Introducción al lenguaje ensamblador IL de .NET [Parte 3] ]

Definiendo Métodos

Una vez hemos aprendido acerca de las condiciones (saltos o ramificaciones), bucles y variables, es hora de ver en profundidad cómo son declarados los métodos en ILAsm. La forma de declarar métodos en ILAsm es muy parecida a la usada para hacerlo en lenguajes como C# o C++, excepto algunos pequeños detalles. Bien, veamos entonces un código de ejemplo para analizar en detalle qué hemos hecho:

//Metodos.il
//Pruebas creando métodos

.assembly extern mscorlib {}

.assembly Metodos
{
    .ver 1:0:1:0
}
.module Metodos.exe

.method static void main() cil managed
{
    .maxstack 2
    .entrypoint

    ldc.i4 10
    ldc.i4 20
    call int32 RealizarSuma(int32, int32)
    call void EscribirSuma(int32)
    ret
}

.method public static int32 RealizarSuma(int32, int32) cil managed
{
    .maxstack 2    

    ldarg.0
    ldarg.1
    add

    ret
}
.method public static void EscribirSuma(int32) cil managed
{
    .maxstack 2
    ldstr "El resultado es: "
    call void [mscorlib]System.Console::Write(string)

    ldarg.0
    call void [mscorlib]System.Console::Write(int32)

    ret
}
Figura 1.8 – Metodos.il, Definir y llamar a nuestros propios métodos

Este simple programa sumará dos números (predefinidos, para simplificar el código) y mostrará el resultado. Hemos definido dos métodos aquí. Toma nota que ambos métodos son estáticos por lo que podemos acceder a ellos directamente sin crear ninguna instancia de ellos. Primero hemos cargado dos números al stack y llamado al primer método al que hemos denominado ‘RealizarSuma‘, el cual estará esperando dos valores int32 al principio del stack. Si miramos al cuerpo del método (RealizarSuma), veremos que su declaración luce igual que el método main tal y como hemos visto en ocasiones anteriores, de nuevo hemos definido la directiva .maxstack pero date cuenta que esta vez no hemos incluido la directiva .entrypoint ya que un programa únicamente puede tener un punto de entrada y, en el caso de arriba, hemos declarado ese punto en el método main (como se suele hacer). Las instrucciones ldarg.0 y ldarg.1 cargan los valores de los argumentos del método al stack. Entonces simplemente hemos usado la instrucción de suma (add) y por ultimo el método finaliza retornando al punto donde fue invocado. Date cuenta de que el método devuelve un valor de tipo int32, por lo que, ¿qué valor habrá devuelto?. Por supuesto, ese valor fue devuelto y ahora está disponible en el stack de evaluación una vez terminó el método su trabajo. A partir de aquí el método main vuelve a tener el control para llamar esta vez al método ‘EscribirSuma‘. Este método requiere también de un parámetro de tipo int32 y, ya que nuestra anterior llamada a ‘RealizarSuma‘ ya nos devolvió ese valor, ‘EscribirSuma‘ lo utilizará como parámetro. Una vez llamado al nuevo método, este escribirá en pantalla el resultado de la suma usando ldarg.0 para cargar el argumento y usar su valor.

El código analizado demuestra que crear y usar métodos no es difícil en ILAsm y bueno, de momento lo es, pero los métodos también pueden recibir valores por referencia, por lo que en la siguiente sección hablaremos de ello.

 

Pasando variables por Referencia

ILAsm soporta también el paso de parámetros por referencia ya que lo soportan también lenguajes de alto nivel en .NET. Cuando pasamos cualquier variable por referencia, no le pasamos su valor sino su dirección de memoria (puntero) para que el método pueda trabajar con ella directamente. Veamos un ejemplo de como funciona:

.method static void main() cil managed
{
    .maxstack 2
    .entrypoint
    .locals init (int32, int32)

    ldc.i4 10
    stloc.0
    ldc.i4 20
    stloc.1

    //Cargamos la dirección de memoria de la 1era variable
    ldloca 0
    //Cargamos el valor de la 2ª variable
    ldloc.1
    call void RealizarSuma(int32 &, int32)
    ldloc.0
    //Cargamos la 1era variable de nuevo, pero esta vez su valor
    call void [mscorlib]System.Console::WriteLine(int32)
    ret 
} 
.method    public static void RealizarSuma(int32 &, int32) cil managed 
{ 
    .maxstack 2 
    .locals    init (int32)    
    //Buscamos en memoria y copiamos su valor 
    ldarg.0 
    ldind.i4 
    stloc.0
    ldloc.0
    //Realizamos la suma
    ldarg.1
    add
    stloc.0

    ldarg.0
    ldloc.0
    stind.i4

    ret
}
Figura 1.9 – VariablesPorRef.il, Pasando variables por referencia

El punto interesante en el código es el uso de algunas nuevas instrucciones como ldloca, la cual carga la dirección de memoria de una variable, en vez de su valor, al stack. En el método principal (main), hemos declarado dos variables (locales) y asignado unos valores (10 y 20 respectivamente). Entonces hemos cargado la dirección de memoria de la primera variable en la memoria y el valor de la segunda, para luego invocar el método ‘RealizarSuma‘. Notarás que se ha usado ‘&’ con el primer parametro int32, lo cual significa que el valor contendrá su referencia en memoria y no su valor para poder pasar tal variable por referencia. De la misma manera, en la definición del método ‘RealizarSuma’ encontraremos también el mismo símbolo ‘&’. Por lo que, tenemos una variable que es pasada por referencia y una segunda variable por valor. Ahora el problema está en que tenemos que acceder a esa dirección de memoria para poder acceder a su valor y modificarlo si fuese necesario. Para solucionar esto, hemos almacenado al stack el primer argumento (el cual contiene la dirección de memoria) y utilizado la instrucción ldind.i4, la cual carga el valor de un entero (32 bit) al stack utilizando una dirección en memoria dada. Hemos almacenado ese valor en una variable local para que podamos reutilizarla fácilmente (o tendríamos que hacer este paso varias veces). Entonces hemos cargado esa variable local y el segundo argumento (el cual ya va por valor) al stack, sumados ambos y guardados en la misma variable local. Por ultimo tenemos algo interesante, y es modificar el valor de esa variable pasada por referencia con el valor del resultado. Para ello hemos usado la instrucción ldind.i4, la cual modifica el valor de una variable en la dirección de memoria indicada en el stack (es decir, lee qué dirección de memoria tiene que acceder y escribe un valor). Una vez hecho esto, para testear que todo ha ido bien, no devolveremos el resultado con el método, en vez de esto, ya que hemos almacenado el resultado en la variable pasada por referencia, usaremos en el método main esa misma variable para escribir el resultado utilizando el método ‘WriteLine‘.

Esta es la forma en la que en ILAsm se utilizan variables por referencia. Hasta aquí, hemos visto formas de declarar variables, condiciones, bucles y por último métodos (con ambos tipos de parámetros). En la próxima parte entraremos de lleno con los espacios de nombre (namespaces a partir de ahora) y las clases, es decir, con la parte orientada a objetos de ILAsm.

¡Hasta la próxima entrega!

Comparte esto !

  • Haz clic para compartir en Twitter (Se abre en una ventana nueva)
  • Haz clic para compartir en Facebook (Se abre en una ventana nueva)

Me gusta esto:

Me gusta Cargando...

Introducción al lenguaje ensamblador IL de .NET [Parte 3]

Publicado por JuDelCo el 17 diciembre 2011
Publicado en: Programación. 2 comentarios

[ <– Introducción al lenguaje ensamblador IL de .NET [Parte 2] ]

Declaración de Variables

Las variables es uno de los pilares de cualquier lenguaje de programación y por tanto, ILAsm nos permite declararlas y usarlas. Aunque no es una tarea tan sencilla como en los lenguajes de alto nivel, usando la directiva .locals podremos definir variables. Esta directiva debería normalmente usarse al principio de cualquier método, aunque puedes poner la declaración en cualquier sitio, obviamente, antes de usarlas. Aquí tenemos un ejemplo en el que declaramos una variable, guardamos un valor en ella y luego la usamos para imprimir su valor.

.locals init (int32, string)
ldc.i4 34
stloc.0
ldstr "Texto de ejemplo para probar la variable local"
stloc.1
ldloc.0
call void [mscorlib]System.Console::WriteLine(int32)
ldloc.1
call void [mscorlib]System.Console::WriteLine(string)
Figura 1.5 – Variables Locales

Hemos declarado dos variables usando la directiva .locals, una del tipo int32 y la otra del tipo string. Luego hemos cargado el número 34 del tipo int32 en memoria y se la hemos asignado a la variable local 0, la cual es nuestra primera variable local. Toma nota que en ILAsm los índices empiezan por cero. Luego hemos cargado en memoria una segunda variable y se la hemos asignado a esa segunda variable local. Finalmente hemos mostrado por pantalla ambas variables. Os preguntareis para qué sirve la instrucción ldloc, pues bien, puede ser usada para cargar cualquier tipo de dato en memoria (enteros, coma flotante, objetos, etc…).

En este caso no hemos usado nombres para nuestras variables. Ya que son locales y no tenemos intención de utilizarlas fuera de nuestro método. Eso no significa que no se puedan declarar variables por su nombre. Para declarar esas variables, simplemente hazlo al igual que en otro lenguaje de alto nivel como por ejemplo .locals init (int32 x, int32 y). Después podrás leer o guardar valores en esas variables usando las mismas instrucciones, pero usando sus nombres como ‘stloc x‘ y ‘ldloc y‘. Aun así, aunque tengan sus propios nombres, podrás seguir accediendo a ellas mediante su índice. Nota: A lo largo de todos los tutoriales usaré variables sin nombre, para que te acostumbres a pensar en qué indice están guardadas tus variables.

Bien, ahora tienes una idea de cómo manipular variables y stacks. Por favor revisa los códigos si aún no tienes muy claro algún concepto y que, a partir de ahora, estaremos manipulando ambos con bastante frecuencia. Por ello es necesario que te familiarices con este procedimiento (definir/inicializar, guardar y leer sus valores).

 

Condiciones en ILAsm

Las condiciones son otros de los pilares de cualquier lenguaje de programación. En los lenguajes de bajo nivel, como cualquier otro lenguaje ensamblador, las condiciones están hechas usando saltos (o ramificaciones). Tomemos un ejemplo de esto:

br SaltarAqui //También puedes usar br.s si lo deseas

//El código que haya aquí será saltado después de la instrucción anterior

SaltarAqui:
//En cambio, estas instrucciones serán ejecutadas

Compara esa instrucción con la instrucción ‘goto‘ de un lenguaje de alto nivel. ¿ Funciona igual verdad ? La instrucción saltará el punto de ejecución a la etiqueta indicada. Aquí, en ILAsm, en vez de usar goto usaremos br. Es instrucción también puede ser usada utilizando br.s si estás seguro de que el destino está entre los -128 y +127 bytes anteriores-posteriores a este, ya que usará un int8 en vez de un int32 para el offset. El ejemplo de arriba ha sido una ramificación (salto) incondicional ya que no hubo ninguna condición anterior de la instrucción que indicase lo contrario. Tomemos pues un ejemplo en el que sí contemplaremos un ejemplo con un salto condicional:

//Saltando.il
.method static void main() cil managed
{
    .maxstack 2
    .entrypoint
    //Cogemos el primer valor del usuario
    ldstr "Introduce el Primer número: "
    call void [mscorlib]System.Console::WriteLine (string)

    call string [mscorlib]System.Console::ReadLine ()
    call int32 [mscorlib]System.Int32::Parse(string)

    //Cogemos el segundo valor del usuario
    ldstr "Introduce el Segundo número: "
    call void [mscorlib]System.Console::WriteLine (string)

    call string [mscorlib]System.Console::ReadLine ()
    call int32 [mscorlib]System.Int32::Parse(string)

    ble Menor
        ldstr "El segundo número es menor que el primero."
        call void [mscorlib]System.Console::WriteLine (string)

    br Salida

Menor:
    ldstr "El primer número es menor que el segundo."
    call void [mscorlib]System.Console::WriteLine (string)
Salida:
    ret
}
Figura 1.6 – Saltando.il (solamente el método main)

El código de arriba toma dos valores del usuario y entonces consulta cual de ellos es el más pequeño. La instrucción la cual requiere nuestra atención es «ble Menor«, la cual indica al JIT a mirar si el primer valor en el stack es menor o igual que el segundo, si así es el caso, entonces saltará a la etiqueta ‘Menor‘. En el caso contrario la ejecución del programa continuará por la siguiente instrucción, sin realizar el salto, sin embargo luego se encontrará con un salto incondicional, ya que si no estuviera ahí, el programa continuaría y ejecutaría las instrucciones contenidas en la etiqueta ‘Menor‘. Por ello hemos creado una nueva etiqueta llamada ‘Salida‘ y hemos llamado a la instrucción ‘br Salida‘ la cual obligará al programa a saltar a la etiqueta ‘Salida‘ y ejecutar la instrucción ret, para finalizar el método.

Las otras instrucciones condicionales que podemos encontrar son beq (==), bne (!= ), bge (>= ), bgt (>), ble (<= ), blt (<), además de brfalse y brtrue (para comprobar si el primer elemento del stack es cero o no respectivamente). Puedes usar cualquiera de ellas para ejecutar alguna parte de tu código y evitar (saltar) otro. Como dije antes, no tenemos la facilidad que tenemos en los lenguajes de alto nivel, por ello todo debe de ser realizado por ti mismo si tienes pensado escribir o modificar código en ILAsm.

 

Bucles en ILAsm

Los bucles son otra parte fundamental de un lenguaje de programación. Un bucle no es otra cosa que la repetición del mismo bloque de código una vez y otra vez. Para ello la repetición del bloque dependerá de una variable a la que llamaremos índice del bucle. En el siguiente fragmento de código mostraré un ejemplo de cómo funciona un bucle básico, así que dedicale un poco de tiempo a entender cómo funcionan los bucles:

 .method static void main() cil managed
{
    //Definimos dos variables locales
    variables .locals init (int32, int32)
    .maxstack 2
    .entrypoint

    ldc.i4 4
    stloc.0        //Tope, el límite del bucle. Haremos 5 repeticiones
    ldc.i4 0
    stloc.1        //Inicializamos el índice a cero 

Comienzo:
    //Comprobamos si el contador del índice ha superado el límite
    ldloc.1
    ldloc.0
    bgt Salida //Si la segunda variable supera a la primera, entonces saltamos

    ldloc.1
    call void [mscorlib]System.Console::WriteLine(int32)

    //Incrementamos el contador
    ldc.i4 1
    ldloc.1
    add
    stloc.1
    br Comienzo
Salida:
    ret
}
Figura 1.7 – Bucles.il (solamente el método main)

En C#, este código se vería de esta manera:

for (temp = 0; temp < 5; temp++)
            System.Console.WriteLine (temp);

Bien, examinemos el código. Primero, hemos declarado dos variables locales e inicializado la primera con un valor de 4 y la segunda con valor 0. El bucle real empieza con la etiqueta ‘Comienzo‘, desde donde primero comprobaremos si el contador del indice del bucle (la variable nº2, ldloc 1) sobrepasa el límite impuesto por nuestra primera variable (ldloc 0), si ese fuera el caso, el programa saltaría a la etiqueta ‘Salida‘ con lo cual nuestro programa terminaría. Si no es el caso, entonces el valor será escrito por pantalla e incrementaremos ese valor para luego saltar de nuevo a la etiqueta ‘Comienzo‘ para comprobar de nuevo si el contador ha excedido o no el límite impuesto. Así funcionan los bucles en ILAsm (y en la mayoría de los otros lenguajes ensamblador).

En la próxima entrega veremos cómo definir nuestros propios métodos y utilizarlos, además de ver los dos tipos de parámetros soportados. Por valor y por referencia.

 

[ Introducción al lenguaje ensamblador IL de .NET [Parte 4] –> ]

Comparte esto !

  • Haz clic para compartir en Twitter (Se abre en una ventana nueva)
  • Haz clic para compartir en Facebook (Se abre en una ventana nueva)

Me gusta esto:

Me gusta Cargando...

Introducción al lenguaje ensamblador IL de .NET [Parte 2]

Publicado por JuDelCo el 16 diciembre 2011
Publicado en: Programación. 2 comentarios

[ <– Introducción al lenguaje ensamblador IL de .NET [Parte 1] ]

El Stack de evaluación

El stack de evaluación puede ser considerado como un stack normal como otro cualquiera, sin embargo este stack es únicamente usado para guardar información justo antes de ejecutar una instrucción. Sabemos que la información guardada en memoria cuando podemos tener acceso a realizar alguna operación sobre ella. Lo mismo pasa si movemos esa información a los registros en un lenguaje ensamblador antes de invocar alguna instrucción o interrupción. De la misma manera que tenemos que mover la información (en el caso del primer tutorial fue una cadena de texto) al stack antes de procesarla (para mostrar esa cadena en pantalla). Al principio de nuestro método ‘main’ (véase figura 1.1 en la anterior entrada), nos damos cuenta que tuvimos que almacenar información a lo largo de nuestro método. Por ello usamos la directiva .maxstack, si hubiéramos indicado la directiva con un número tres (.maxstack 3), entonces el JIT hubiera creado un stack con 3 celdas para su uso posterior en el método. Ojo, esto no significa que únicamente podemos cargar 3 valores en la vida de nuestro método, simplemente significa que podemos albergar 3 valores a la vez como máximo. Una vez que el método finaliza, el stack y sus valores se eliminan. Así es como funciona el recolector de Basura en .NET. Además, no hay limitación en cuanto al tipo de dato que podemos usar para almacenar en el stack. Podremos mover cualquier tipo de dato (cadenas, enteros, objetos, etc) al stack siempre que queramos.

Tomemos otro ejemplo que nos aclarará el concepto del stack de evaluación:

//Sumar.il
//Suma dos números

.assembly extern mscorlib {}

.assembly Sumar
{
    .ver 1:0:1:0
}
.module Sumar.exe

.method static void main() cil managed
{
    .maxstack 2
    .entrypoint

    ldstr "La suma de 50 y 30 es = "
    call void [mscorlib]System.Console::Write (string)

    ldc.i4.s 50
    ldc.i4 30
    add
    call void [mscorlib]System.Console::Write (int32)
    ret
}
Figura 1.3 – Sumar.il, sumando dos números predefinidos

El trozo de código encima de nuestro método main es el mismo que en el ejemplo anterior, el cual ya explicamos. Únicamente ha cambiado el nombre del módulo. En este código lo importante a discutir es el .maxstack 2, el cual indica al JIT a preparar suficiente espacio en memoria para que podamos guardar dos valores. Entonces cargamos la cadena en el stack y la imprimimos. Lo próximo que hacemos es cargar dos enteros en memoria a la vez (usando las instrucciones ldc.i4.s y ldc.i4), usar la instrucción de suma y finalmente mostrar por pantalla el resultado entero que hemos obtenido. La instrucción de suma usada (add) tomará del stack de evaluación dos valores (siempre que sean encontrados), los sumará y guardará el resultado al comienzo del stack (es decir, un ‘push’). Justo después de la instrucción de suma, encontramos otra instrucción llamada Write que escribirá algo por pantalla. Este método requiere que haya un valor guardado en el stack. En este caso requerirá que sea del tipo entero. Si lo encuentra, entonces escribirá su valor por pantalla o en caso contrario levantará un error.

No os liéis con ldc.i4.s y ldc.i4, ambos representan un tipo de dato entero (integer), pero el primero es un único byte y el segundo son cuatro bytes.

Espero que hayáis entendido la forma de usar el stack de evaluación y como funciona. Ahora veremos los tipos de datos del lenguaje ILAsm como una parte fundamental del mismo.

Tipos de Datos ILAsm

 

Al igual que cuando aprendes cualquier otro lenguaje, hay que hablar sobre sus tipos de datos soportados. Este caso es igual. Tómate un tiempo para observar la tabla de abajo (figura 1.4) para aprender cuales son los tipos de datos más comunes en este lenguaje, pero antes, me gustaría añadir una cosa. No hay consistencia en la definición de los tipos de datos en los diferentes lenguajes de .NET. Por ejemplo para indicar un tipo de dato entero (32 bit) en VB.NET usamos ‘Integer‘ pero en C# y en VC++ no es así, aunque luego ambos casos se representen usando System.Int32. Además, necesitamos tener en cuenta si es un tipo básico con la especificación del Lenguaje común (CLS, Common Language Specification) o no. Como por ejemplo el tipo de dato UInt32 no está reconocido por VB.NET por ello no es básico CLS.

Bien, empecemos por aprender la tabla que nos proporciona los nuevos nombres para los tipos de datos de ILAsm:

Nombre en IL Tipo Base en .NET Significado (Descripción) Básico CLS
Void Sin valor, solo usado como tipo de retorno No
Bool System.Boolean Booleano No
Char System.Char Carácter (16-bit unicode) No
int8 System.SByte Entero 1-byte (con signo) No
int16 System.Int16 Entero 2-byte (con signo) No
int32 System.Int32 Entero 4-byte (con signo) Si
int64 System.64 Entero 8-byte (con signo) Si
native int System.IntPtr Entero (con signo) Si
unsigned int8 System.Byte Entero 1-byte (sin signo) Si
unsigned int16 System.UInt16 Entero 2-byte (sin signo) Si
unsigned int32 System.UInt32 Entero 4-byte (sin signo) No
unsigned int64 System.UInt64 Entero 8-byte (sin signo) Si
native unsigned int System.UIntPtr Entero (sin signo) Si
Float32 System.Single Coma flotante de 4-byte No
Float64 System.Double Coma flotante de 8-byte No
object System.Object Tipo objeto Si
& Puntero administrado Si
* System.IntPtr Puntero no administrado Si
typedef System.Typed Reference Tipo especial que contiene datos y explícitamente indica su tipo Si
Array System.Array Array Si
string System.String Cadena de texto Si
Figura 1.4 – Tipos de Datos en ILAsm

También tenemos abreviaciones para los tipos de datos en ILAsm como por ejemplo .i4, .i4.s, .u4, etc como usamos en el ejemplo anterior. Los tipos de datos mencionados en la tabla de arriba muestran los tipos de datos son reconocidos en ILAsm y además menciona cuales son tipos básicos CLS y cuales no. Entonces, teniendo en mente esa información, podremos llamar a cualquier función como esta:

call int32 FuncionEjemplo (string, int32, float64) 

Lo cual significa, que la función ‘FuncionEjemplo‘ devuelve un valor del tipo int32 (System.Int32) y toma tres valores como parámetros que son del tipo string (System.String), int32 (System.Int32) y float64 (System.Double) respectivamente. Toma nota que estos son datos básicos en el CLR y en ILAsm. Cuando estés interesado en tratar tipos de datos que no son básicos (definidos por el usuario) entonces el código podrá lucir como esto:

//Código en C#
ColorTranslator.ToHtml(Color);

//Código en ILAsm
call instance string [System.Drawing]System.Drawing.ColorTranslator::ToHtml(valuetype [System.Drawing]System.Drawing.Color)

Toma nota que hemos definido explícitamente los tipos de dato en los parámetros y incluyendo además el espacio de nombre (namespace) donde el tipo de dato reside y una palabra clave que indicará al JIT que estamos referenciando a un tipo de dato no básico (valuetype).

Esto lo dejaré más claro en la próxima entrega del tutorial, donde trataremos más intensamente con los tipos de datos. También veremos una de las bases de cualquier lenguaje: Condiciones y bucles.

 

[ Introducción al lenguaje ensamblador IL de .NET [Parte 3] –> ]

Comparte esto !

  • Haz clic para compartir en Twitter (Se abre en una ventana nueva)
  • Haz clic para compartir en Facebook (Se abre en una ventana nueva)

Me gusta esto:

Me gusta Cargando...

Introducción al lenguaje ensamblador IL de .NET [Parte 1]

Publicado por JuDelCo el 15 diciembre 2011
Publicado en: Programación. 2 comentarios

Introducción a los Tutoriales

Lo que pretendo con esto es realizar una serie de tutoriales consecutivos sobre este tema, por lo que los próximos tutoriales se basaran en los anteriores. No pretendo que lleguéis a ser maestros escribiendo código ILAsm, más bien que obtengáis la base necesaria para que podáis desenvolveros sin mucha dificultad con el y sigáis aprendiendo por vuestra cuenta.

Con estos tutoriales aprenderéis la base del lenguaje ensamblador IL (tambien llamado CIL, o Common Intermediate Language) para que podáis debuguear código en bajo nivel .NET ya compilado (desde cualquier lenguaje de alto nivel, como VB .NET, C# y otros) y así corregir fallos, añadir, quitar o cambiar características y más. Es decir, podrás editar libremente cualquier programa o librería una vez compilada de .NET. Además, usando estos conocimientos, tendrás la base para poder empezar a escribir tu propio compilador para crear un nuevo lenguaje .NET (aunque no vamos a llegar tan lejos).

Siempre que compilas tu código en .NET, independientemente del lenguaje que estés usando, será convertido al Lenguaje Intermedio (IL, Intermediate Language) el cual es también conocido como Lenguaje Intermedio de Microsoft (MSIL) o Lenguaje Intermedio Común (CIL). Puedes pensar que IL es como el Byte Code generado por el lenguaje Java. Si estás interesado en entender cómo .NET funciona con sus tipos de datos y cómo el código que escribes es convertido a código IL, entonces estos tutoriales sobre ILAsm te van a resultar muy útiles dándote grandes ventajas.

Entre estas ventajas encontramos entender qué es lo que devuelve el compilador .NET cuando compila, por lo tanto, podrás examinar el codigo traducido por el compilador y realizar los cambios que sean necesarios (aunque no es necesario en la mayoría de los casos). Además, puedes cambiar el código IL para hacer cambios que tu lenguaje de alto nivel no permitiría para aumentar el rendimiento de tu código, y también te ayudará a debuggear tu código en bajo nivel. Y como dije antes, si estás planeando escribir un compilador para .NET, entonces necesitarás entender el código ILAsm.

IL es el formato binario el cual el compilador devuelve una vez termina su tarea, es decir, no puede ser leido por humanos, pero al igual que otros binarios (ejecutables) tienen su lenguaje ensamblador, IL tiene tambien el suyo propio conocido como lenguaje ensamblador IL (ILAsm). El lenguaje ensamblador IL (ILAsm a partir de ahora) tiene sus propias instrucciones de la misma manera que cualquier otro lenguaje ensamblador nativo pueda tener. Por ejemplo, para sumar dos números, tienes la instrucción ‘add’, para restar dos números, tienes la instrucción ‘sub’, etc. Es obvio que el compilador en tiempo de ejecución de .NET (el JIT) no puede ejecutar código ILAsm directamente. Si tu tienes escrito código en ILAsm entonces tendrás primero que compilarlo a código IL y entonces el JIT podrá ejecutar el código.

NOTA: Por favor ten en cuenta que IL y ILAsm son cosas distintas. Siempre que hablaremos sobre IL, nos referiremos al código binario devuelto por el compilador de .NET mientras que cuando referenciemos a ILAsm, nos referiremos al lenguaje ensamblador de este código el cual no está en su forma binaria.

NOTA2: Ten en cuenta que espero que estés bastante familiarizado con .NET (incluyendo algún lenguaje de alto nivel como C# o VB.NET). En estos tutoriales no profundizaremos en todos los detalles, sino únicamente en aquellos que realmente necesitan ser explicados. Si algo te confunde, por favor házmelo saber para aclarártelo.

 

Introducción al Lenguaje Ensamblador IL

Bien, empecemos con el propósito de estos tutoriales, la introducción al lenguaje ensamblador IL. Ten en cuenta que puedes escribir código en ILAsm en cualquier editor de texto (como notepad) y luego usar la linea de comandos (cmd) para compilar el código. El compilador en este caso es el ILAsm.exe, el cual está distribuido junto con el SDK de .NET Framework. ILAsm es un lenguaje algo duro de aprender para aquellos que siempre han programado en lenguajes de alto nivel, pero para aquellos que han programado en C o en C++ no les debería resultar demasiado complicado. En ILAsm necesitamos hacer todas las cosas manualmente, como añadir un valor al stack, administrar la memoria, etc. Piensa en ILAsm de la misma manera que cualquier otro lenguaje ensamblador con la diferencia de que en otro lenguaje estarás tratando con ejecutables nativos para Windows y con este lenguaje (ILAsm) estarás tratando con ejecutables .NET y además, este lenguaje es un poco más fácil (que otros lenguajes ensamblador) y además está orientado a objetos.

Empecemos con este lenguaje con nuestro primer programa de ejemplo que escribirá una frase en pantalla (en la consola). Es una tradición que en el comienzo de cada lenguaje se use un «hola mundo» así que vamos a hacer lo mismo:

//Ejemplo.IL 
//Un simple programa que imprime una cadena de texto por consola

.assembly extern mscorlib {}

.assembly Ejemplo
{
    .ver 1:0:1:0
}
.module ejemplo.exe

.method static void main() cil managed
{
    .maxstack 1
    .entrypoint

    ldstr "Hola mundo! Also, The Game !!!"

    call void [mscorlib]System.Console::WriteLine (string)
    ret
}
 Figura 1.1 – Un programa de ejemplo en ILAsm

 

Una vez escrito el codigo de arriba (o figura 1.1) en un simple editor de texto como notepad, guárdalo como «Ejemplo.il«. Ahora realizaremos nuestra primera compilación y ejecutaremos nuestro codigo. Una vez hecho esto entraremos en detalles y explicaré qué es lo que hace cada instrucción. Para compilar el código, escribe lo siguiente en una ventana de comandos (cmd):

 Figura 1.2 – Salida del programa de ejemplo. Aqui puedes ver el comando usado para compilar el código

 

ILAsm.exe es una herramienta utilizada por linea de comando la cual está incluida con el Framework SDK de .NET y puede ser encontrada en la carpeta «<carpeta_windows>\Microsoft.NET\Framework\<version>«. Si quieres puedes incluir esta ruta a la variable de entorno Path de tu SO (por ello no he tenido que escribir la ruta completa en la figura 1.2 como podéis ver). Cuando has terminado de compilar tu archivo .IL, tendrás listo tu ejecutable (.exe) con el mismo nombre que tu archivo .IL. Puedes especificar el nombre de salida utilizando «/OutPut=<filename>» (sin comillas). Para ejecutar tu recien compilado programa, simplemente escribe el nombre de tu progama y presiona Enter. La salida del programa aparecerá ante ti. Bien, ahora nos tomaremos un tiempo para entender que es lo que hemos escrito en el código. Ten en mente que me estoy refiriendo al código de la figura 1.1

  • Las dos primeras lineas (que empiezan por //) son comentarios. En ILAsm puedes escribir comentarios de la misma manera que en C# o C++. Para comentar múltiples líneas o un trozo de línea, puedes usar /* … */
  • Lo siguiente que hemos hecho a sido indicar al compilador que importe la librería llamada mscorlib (.assembly extern mscorlib {}). En ILAsm, cada linea comenzada por un punto (.) indica que la línea es una instrucción especial o una directiva. Entonces la directiva .assembly que tenemos aquí dice que vamos a usar una librería externa (la cual no está escrita por nosotros en este código, está pre-compilada).
  • La siguiente directiva .assembly define la información de nuestro archivo (.assembly Ejemplo ….). En nuestro caso, hemos incluido «Ejemplo» como el nombre del ensamblado y entre llaves hemos incluido informacion sobre el ensamblado de salida (nuestro .exe), en este caso hemos incluido la información de versión de nuestro programa. Podemos incluir más información sobre el ensamblado en este bloque, como la clave pública, etc.
  • La siguiente directiva indica el nombre del módulo de nuestro ensamblado (.module Ejemplo.exe). Como ya sabemos debe haber al menos un módulo en cada ensamblado.
  • Si seguimos hacia adelante, nos encontramos con (.method static void main() cil managed), la directiva .method indica que estamos definiendo un método, el cual es estático (usando la misma definición que en C#) y no retorna nada (void). Además, el nombre del método es ‘main’ y no toma ningún parámetro (ya que no hay nada dentro de los paréntesis). La instrucción final ‘cil managed‘ indica al compilador que el código que vamos a compilar estará gestionado por nosotros (nuestro código).
  • Moviéndonos dentro del método, la primera directiva es .maxstack (.maxstack 1). Esta directiva es importante la cual anuncia el número máximo de objetos que podremos cargar en memoria (en el stack de evaluación en realidad). Discutiremos sobre esto extensamente en el próximo capítulo, ignóralo por el momento.
  • La directiva .entrypoint le indica al compilador que este método será el punto de entrada para nuestra aplicación, es decir, la primera función que será llamada una vez el programa se ejecute (que suele ser el método main).
  • La siguiente línea (ldstr «Hola mundo! Also, The Game !!!») contiene la instrucción ldstr. La instrucción ldstr es usada para cargar una cadena en la memoria (o stack de evaluación). Es necesario escribir valores en el stack de evaluación antes de que puedan ser utilizados. Como he dicho antes, discutiremos sobre ello extensamente en el siguiente capítulo.
  • La siguiente instrucción (call void [mscorlib]System.Console::WriteLine (string)) llama (invoca) a un método que reside en la librería mscorlib. Toma nota que hemos indicado la ruta completa de este método incluyendo el tipo de retorno, los tipos de parámetros y en qué librería reside. Con esto hemos pasado la cadena de texto como parámetro, la cual no es una variable, es un tipo de dato. La instrucción anterior (ldstr «Hola m….») cargó la cadena al stack y este método la está usando para imprimirla.
  • La instrucción final (ret), aunque no debería ser necesario explicarla, implica retornar (terminar) el método.

Leyendo las lineas de arriba, ahora deberías tener una idea de como escribir código en ILAsm, además, deberías darte cuenta de que ILAsm no es como un lenguaje .NET de alto nivel (como C#, VB, etc). De todas formas, todo el codigo que tu escribas, lo harás siguiendo una estructura de este estilo (o con pequeños cambios, cuando trabajemos con clases).

Para la siguiente parte del tutorial hablaremos sobre el stack de evaluación y los tipos de datos más comunes que nos encontraremos.

[ Introducción al lenguaje ensamblador IL de .NET [Parte 2] –> ]

Comparte esto !

  • Haz clic para compartir en Twitter (Se abre en una ventana nueva)
  • Haz clic para compartir en Facebook (Se abre en una ventana nueva)

Me gusta esto:

Me gusta Cargando...

SpaceChem – Más que un juego

Publicado por JuDelCo el 14 octubre 2011
Publicado en: Noticias, Videojuegos, Videos. 5 comentarios

Source original: http://blog.darkhogg.es/2011/07/spacechem-mas-que-un-juego/

Cualquiera que sepa algo de Zachtronics Industries sabe de qué hablo cuando digo que sus juegos no son exactamente para toda la familia. Zach tiene por costumbre hacer juegos de puzzle poco convencionales, más enfocados a la resolución de problemas en general que con encontrar una solución concreta a un nivel concreto. SpaceChem es todo eso y más.

(Que no te engañe la imagen, el juego tiene una curva de dificultad fácil de tomar)

En SpaceChem debemos transformar una serie de átomos y moléculas en otros, usando para ellos reactores. Estos reactores contienen dos elementos mecánicos que nos permiten mover, rotar, unir, separar, etc. los elementos de entrada para transformarlos. Nuestros ‘waldos’ se desplazan por el escenario siguiendo un camino concreto que nosotros mismos hacemos, ejecutando órdenes a su paso. En ciertos niveles podemos unir más de un reactor de este tipo para formar una cadena de producción.

Como una imagen vale más que mil palabras, digo yo que un vídeo valdrá bastante más:

 

Lo que hace a SpaceChem diferente de otros juegos de puzzle es la infinidad de soluciones válidas a las que se pueden llegar en un mismo nivel. Tanto que el calificativo de puzzle se le queda corto: En los puzzles típicamente existe una única solución correcta, o variaciones de dicha solución, que el diseñador oculta de alguna forma y nos deja la tarea de encontrarla. SpaceChem es más que eso, es un juego de resolución de problemas: Dadas unas herramientas (los reactores) y unos materiales (las entradas al reactor), hemos de apañárnoslas para conseguir unos productos (las salidas del reactor). No existe ninguna pista de cómo hemos de hacerlo, eso es justamente lo que hemos de conseguir nosotros. Nuestro objetivo no es descubrir la intrincada solución que pensó el diseñador, sino idear una nosotros mismos, que puede ser o no similar a la que dicho diseñador pensó en un principio (si es que pensó alguna). A su vez éste tipo de juegos nos da la posibilidad de medir de alguna manera cómo es nuestra solución de buena, según el número de ciclos que tarda o de símbolos usados, por ejemplo.

No es secreto que Zach diseña lo que él mismo llama juegos para ingenieros. Uno de sus juegos, KOHCTPYKTOP, nos introduce de lleno en la piel de un ingeniero electrónico. Por suerte para todos, los siguientes juegos que hizo no eran tanto para ingenieros, aunque conservaran ese ambiente ingenieril que define a Zach. Estoy hablando de The Codex of Alchemical Engineering y SpaceChem (y un poco en menor medida, de The Bureau of Steam Engineering). SpaceChem es, en realidad, la evolución lógica de The Codex, tintado de química en lugar de alquimia y cambiando la mecánica fundamental de brazos mecánicos fijos a brazos mecánicos completamente móviles.

The Codex trabaja con un modelo de bucles repetitivos, donde el escenario en sí no repercute de ninguna manera en cómo se comportan los brazos. SpaceChem va un paso más allá permitiendo puntos de sincronización, toma de decisiones basadas en datos externos, sensores e incluso una cierta memoria. A todo ello sumémosle la capacidad de procesamiento paralelo que introduce el tener varios reactores funcionando al mismo tiempo, capacidad que estaba presente en The Codex hasta cierto punto, pero no de forma tan marcada.

A estas alturas cualquiera con un mínimo de conocimientos de programación debería saber a dónde quiero llegar. Y quien no, probablemente esa última frase le haya abierto la mente. Resulta que hay decenas de conceptos de la programación que pueden aplicarse a SpaceChem, desde cuestiones muy básicas hasta conceptos de concurrencia bastante más complejos:

  • Ejecución secuencial: Tanto en SpaceChem como en la programación, las instrucciones se ejecutan una a una, en un orden determinado y una detrás de otra.
  • Bucles: En SpaceChem, el objetivo principal es producir un número concreto de moléculas o átomos. Generalmente las soluciones se construyen de forma que el sistema funciona correctamente de forma indefinida (no siempre es así, luego hablaré de ello). En programación igualmente existen bucles que nos permiten realizar un cierto proceso de forma indefinida. Los videojuegos y los sistemas operativos en realidad no son más que un bucle casi infinito (puesto que se puede terminar) que ejecuta las instrucciones adecuadas.
  • Modularización de la solución: En muchísimas ocasiones nos encontramos niveles en los que se nos presenta una tarea muy complicada que podemos resolver usando tareas más sencillas. En programación esto es el día a día, y dudo que nadie sea capaz de programar bien sin realizar una buena división de tareas.
  • Entrada y salida con bloqueo: Cuando en SpaceChem tratamos de ejecutar una instrucción INPUT y no hay nada disponible, o un OUTPUT y la tubería de salida está llena, automáticamente el waldo espera a poder realizar la acción. Las operaciones de entrada y salida en programación se comportan exactamente igual en la mayoría de situaciones:bloquean el proceso hasta que existan datos de entrada o sitio a la salida. Lo cual nos lleva al siguiente punto:
  • Bloqueo mutuo: Conocido generalmente como deadlock, se trata de una situación en la que dos o más procesos quedan a la espera de recursos que están siendo utilizados por otro de los procesos que forman parte del bloqueo. El ejemplo en SpaceChem se puede encontrar fácilmente en el nivel No Thanks Necessary. Una de las entradas nos aporta N2 y O2 con probabilidades de 25% y 75% respectivamente, es decir, 3 átomos de O por cada átomo de N, con objetivo de conseguir NO3. La solución típica consiste en diseñar un reactor que tome como entrada O2/N2 y envíe átomos de O por la salida superior y de N por la inferior. Este reactor se conecta a un segundo que, a partir de átomos sencillos de O y N, forma el NO3. El problema es que la entrada, aunque a lo largo del tiempo tiende a la uniformidad, es aleatoria, y no podemos asumir que los elementos llegarán exactamente como pensamos (tres O por cada N). En ocasiones podemos llegar a tener 8 O y ningún N, o incluso 4 N y ningún O. Sin espaciar adecuadamente las tuberías, es muy normal que el primer reactor quede a la espera para enviar un O que ya no cabe a la vez que el segundo queda a la espera de un átomo de O que no hay. Ninguno de los dos reactores avanzará, por lo que ambos esperarán indefinidamente. En el mundo de la programación el problema es algo más complicado y generalmente ocurre porque dos procesos distintos tratan de acceder a varios recursos a la vez y en orden inverso, como dos ficheros. La Wikipedia lo explica muy bien, mejor que yo de hecho.

Estos son unos pocos ejemplos, pero hay más cosas que podemos relacionar. Por ejemplo, un reactor puede verse como un proceso con dos hilos (los waldos) que se conecta con el resto de procesos del sistema mediante colas (FIFOs, las tuberías).

Con todo esto, SpaceChem y muchos otros juegos del estilo, son algo más que juegos. Son juegos en cierta manera educativos. Te obligan a pensar de forma muy productiva, puesto que el reto que se te presenta es resolver un problema, con una serie de herramientas, sin ningún tipo de guía, algo a lo que se supone que nos enseñan a los ingenieros en general, y a los informáticos en particular.


Comparte esto !

  • Haz clic para compartir en Twitter (Se abre en una ventana nueva)
  • Haz clic para compartir en Facebook (Se abre en una ventana nueva)

Me gusta esto:

Me gusta Cargando...

【東方】Tear of Girl (Cirno’s Theme Remix)

Publicado por JuDelCo el 8 septiembre 2011
Publicado en: Música, Touhou, Videos. 4 comentarios

Bueno, he aquí una canción de touhou que yo mismo he subido en HD (no la encontraba en Youtube)

Comparte esto !

  • Haz clic para compartir en Twitter (Se abre en una ventana nueva)
  • Haz clic para compartir en Facebook (Se abre en una ventana nueva)

Me gusta esto:

Me gusta Cargando...

Organización del código fuente en C++

Publicado por JuDelCo el 23 agosto 2011
Publicado en: Programación. 6 comentarios

Introducción

Si bien muchos programas simples caben en un solo archivo C o Cpp, cualquier proyecto serio va a necesitar dividir el código en varios ficheros con el fin de ser manejable. Sin embargo, muchos principiantes no se dan cuenta hasta que punto esto es importante (Sobre todo por que muchos lo han intentado y les ha dado más problemas que soluciones y decidieron que no merecía la pena el esfuerzo). En este artículo intentaré explicar porqué hacerlo y cómo hacerlo correctamente. Cuando sea necesario, doy une breve explicación de como trabajan los compiladores y enlazadores para ayudar a entender porque se tienen que hacer las cosas de una manera determinada.

Terminología

En este artículo voy a llamar a los ficheros estándar de C y C++ (normalmente con extensión .c o .cpp) “ficheros fuentes”. Esto será para distinguirlos de los “archivos de cabecera” (normalmente con extensión .h o .hpp). Esta terminología es la usada por Visual C++ y la mayoría de los libros. Ten en cuenta que esta diferencia es puramente conceptual, ambos son archivos de texto con código en el interior. Sin embargo, como se muestra en el resto del artículo, la diferencia de como se trata a unos archivos u otros es muy importante.

¿Por qué dividir el código en varios archivos?

La primera pregunta que los programadores novatos se hacen cuando ven un directorio lleno de archivos de código es ¿Por qué no un solo archivo? No entienden el sentido de separar el código en varios ficheros.

Dividir cualquier proyecto de tamaño razonable tiene algunas ventajas, siendo las más significativas las siguientes:

  • Acelerar la compilación. La mayoría de compiladores trabajan en un solo archivo a la vez. Así que si tenemos 10.000 líneas de código en un solo archivo y cambiamos una línea para compilar nuestro programa necesitamos volver a procesar y compilar 10.000 líneas de código. Sin embargo, si 10.000 líneas de código se distribuyen uniformemente en 10 archivos, si cambiamos una línea solo tendremos que volver a compilar 1000 líneas de código, ahorrando tener que compilar las 9000 líneas restantes del resto de ficheros.
  • Aumenta la organización. Dividir el código en una línea lógica hará el trabajo más fácil para ti (O cualquier programador del proyecto) para encontrar variables, clases, estructuras, funciones, etc. Imagina que usas la función de búsqueda de tu editor para encontrar una parte del código, si tienes un fichero llamado juego.cpp tendrás que buscar a lo largo de todo el código, sin embargo, si tienes tu juego dividido en 4 ficheros del tipo graphics.cpp, main.cpp, audio.cpp e input.cpp, si quieres buscar algo relacionado con el sonido sabrás donde buscar reduciendo en 3/4 tu búsqueda.
  • Facilita reutilizar código. Si el código está cuidadosamente dividido y los archivos son independientes unos de otros te permite reutilizarlos en otros proyecto ahorrándote mucho trabajo en el futuro. Hay muchas más cosas necesarias para poder reutilizar el código, pero sin una buena organización es difícil saber que partes van a trabajar juntas y cuales no. Por lo tanto, poner los subsistemas y clases en ficheros independientes ayuda a portar el código a otros proyectos fácilmente.
  • Compartir código entre proyectos. En principio es el mismo tema que la reutilización, pero hay ocasiones que el mismo archivo se usa en varios proyectos en vez de simplemente copiar y pegar, esto facilita que si se corrigen errores o se cambia algo no se tenga que modificar cada copia del archivo y te aseguras de que ambos proyectos utilizan la última versión del código.
  • Dividir las tareas entre los programadores. En proyectos grandes donde trabajan varios programadores no es factible trabajar todos en el mismo fichero, la separación de código facilita que cada uno pueda dedicarse a una parte sin interferir en las demás. Por supuesto, puede darse el caso de que dos quieran modificar el mismo archivo a la vez, para ello existen el software de control de versiones como SVN, GIT, Mercurial…

Todo lo anterior son aspectos de modularidad, un elemento clave tanto en el diseño estructurado como orientado a objetos.

Como hacerlo. Los Fundamentos

Es probable que te estés convenciendo de que hay que dividir el código, ahora la cuestión es cómo hacerlo. Aunque algunas de las decisiones que tome serán razonablemente arbitrarias hay algunas reglas básicas que debería seguir para asegurar el trabajo.

En primer lugar, mirar como dividir el código en secciones. A menudo se separa en subsistemas o “módulos” como el sonido, música, gráficos, manejo de ficheros, etc. Crea nuevos ficheros con nombres significativos para saber a simple vista que contiene, a continuación mueva todo el código que pertenece al módulo a ese archivo. A veces no está claro que pertenece a cada módulo (Algunos te dirían que es por un mal diseño de su software).

Una vez dividido el código en archivos fuentes el siguiente paso es considerar que meter en el archivo de cabecera, en un nivel simple, lo que suele estar en la parte superior del código es un serio candidato a ir al archivo de cabecera, de ahí a que se llamen así.

Los archivos de cabecera suelen incluir algunas o todas de las siguientes cosas:

  • Definición de estructuras y clases.
  • Definición de tipos (typedef).
  • Prototipos de funciones.
  • Variables Globales (ver más adelante).
  • Constantes
  • Macros #define.
  • Directivas #pragma.

(Además, deberías incluir las plantillas y las funciones en línea. Esto debería quedar claro al terminar este artículo).

Por ejemplo, mira cualquier biblioteca estándar que venga con el compilador que uses. Stdlib.h es un buen ejemplo (ver aquí). Te darás cuenta que tiene algunas de las cosas mencionadas en la lista de antes. Del mismo modo puedes ver que las variables globales están precedidas por el modificador extern, esto es importante, pero lo veremos más adelante.

En general será necesario tener un archivo de cabecera para cara archivo de código fuente, si tienes un sound.cpp tendrás un sound.h, si tienes un sprite.cpp tendrás un sprite.h. Mantener siempre el mismo orden es importante. Saber lo que va en el archivo de cabecera y lo que va en el archivo fuente.

Estos archivos de cabecera se convertirán en la interfaz entre los subsistemas. Al incluir (#include) un archivo de cabecera en otro archivo tendrás acceso a todas las funciones, clases y tipos declarados en ella. Por lo tanto en todos los archivos que uses sprintes probablemente tengas que incluir sprite.h en cada uno de ellos. Recuerda usar #include “sprite.h” y no #include <sprite.h> para que busque en el directorio del proyecto y no en la biblioteca estándar.

Recuerda que, en lo que al compilador se refiere, no existe ninguna diferencia entre un archivo de cabecera y un archivo de código fuente, para el compilador ambos son archivos de texto que contienen código y tratarán igual, la diferencia es puramente conceptual y para los programadores. Los archivos de cabecera deben contener la interfaz, es decir, la definición de lo que contienen los archivos de código fuentes. Estos son los que contienen las funciones, clases y demás. Esto significa que un archivo fuente utiliza otros archivos fuentes gracias a los archivos de cabecera, estos hacen de enlace entre unos y otros.

Dificultades potenciales

Las reglas anteriores son bastante vagas y sólo sirven como punto de partida para organizar el código. En casos sencillos, se puede producir por completo los programas de trabajo siguiendo las directrices. Sin embargo, hay algunos detalles más que han de tenerse en cuenta, y es a menudo estos detalles los que causan a los programadores noveles tanto dolor, cuando comienzan a dividir su código hasta en los archivos de cabecera y los archivos normales.

En mi opinión hay cuatro errores básicos de los que entran en el turbio mundo de los archivos de cabeceras definidos por el usuario:

  • La fuente de los archivos ya no compila ya que no pueden encontrar las funciones o variables que necesitan. (Esto a menudo se manifiesta en forma de algo parecido a un “error C2065: ‘MyStruct’: identificador no declarado”. En Visual C++, aunque esto puede producir cualquier número de mensajes de error diferentes en función de exactamente lo que están tratando de referencia)
  • Dependencias cíclicas. Cuando dos encabezados tienen la necesidad de incluirse entre sí. Por ejemplo la clase sprite incluye un puntero a la criatura que representa y la clase de la criatura necesita un puntero a la clase sprite. No importa como lo hagas ambas deben ser declaradas antes de usarse, pero a la fuerza cuando una se está declarando la otra no ha sido declarada aún.
  • Duplicar las definiciones. Imagina que tienes un archivo llamado map.h que incluye los archivos sprite.h y hero.h, a su vez hero.h incluye sprite.h por lo que sprite.h se incluiría dos veces en map.h, provocando un error.
  • Duplicado las instancias de los objetos dentro del código que compila bien. Este es un error vincular, a menudo difícil de entender.

Entonces, ¿cómo solucionar estos problemas?

Solución problema 1

Afortunadamente, estos problemas son fáciles de arreglar, y aún más fácil de evitar, una vez que las entienda.

El primer error, en un archivo de origen se niega a compilar porque uno de los identificadores fue declarado, es fácil de resolver. Simplemente incluye (#include) el archivo que contiene la definición del identificador que usted necesita. Si sus archivos de cabecera están organizados de forma lógica y llamado así, esto debe ser fácil. Si es necesario utilizar la estructura Sprite, entonces es probable que tenga que incluir “sprite.h” en todos los archivos que lo hace. Un error que los programadores suelen tener es asumir que es un archivo ha sido incluido porque está incluido en otro archivo ya incluido, un ejemplo.

/* Header1.h */
#include "header2.h"
class ClassOne { ... };

/* Header2.h */
class ClassTwo { ... };

/* File1.cpp */
#include "Header1.h"
ClassOne myClassOne_instance;
ClassTwo myClassTwo_instance;

En este caso File1.cpp compila bien ya que incluye a Header1.h e indirectamente (a través de Header1.h) también incluye a Header2.h, pero en el futuro alguien puede decidir que Header1.h no necesita incluir a Header2.h. Esto hará que se rompa File1.cpp la próxima vez que compiles al no tener la declareción de ClassTwo.

La solución está en incluir siempre los archivos necesarios explícitamente en el archivo que son necesarios y no confiar en que otros archivos incluidos ya lo incluyen, esto además ayuda a entender y documentar el código pues se ve que archivos necesita ese fichero en concreto para funcionar.

Solución Problema 2

Las dependencias cíclicas son un problema común en la ingeniería de software. A menudo se necesita que una clase o estuctura conozca la existencia de otra y esta de la primera, esto termina con el siguiente aspecto.

/* Parent.h */
#include "child.h"
class Parent
{
    Child* theChild;
};

/* Child.h */
#include "parent.h"
class Child
{
    Parent* theParent;
};

Dado que uno de ellos tiene que ser compilado en primer lugar, necesita alguna manera de romper el ciclo. En este caso, de hecho es bastante trivial. La estructura de Parent en realidad no necesita saber los detalles de la clase del Child, ya que sólo se almacena un puntero a una. Los punteros son casi lo mismo no importa lo que apuntan, por lo tanto no es necesario que la definición de la estructura o la clase con el fin de almacenar un puntero a una instancia de esa estructura o clase. Así que la línea #include no es necesario. Sin embargo, sólo tiene que sacarla para tener un “identificador no declarado” error cuando se encuentra con la palabra Child, así que tienes que dejar que el compilador saber que Child es una clase a la que desea apuntar. Esto se hace con una declaración adelantada (forward declaration), tomando la forma de una definición de clase o de clase sin un cuerpo. ejemplo:

/* Parent.h */
class Child; /* Forward declaration of Child; */
class Parent
{
    Child* theChild;
};

Observa como sustituimos la línea #include por la declaración adelantada, igualmente podemos hacerlo en el archivo Parent.h, esto, además, ahorra tiempo de compilación ya que es un #include menos que tratar. Como solo estamos utilizando un puntero y no el tipo en sí no necesita la declaración entera. En el 99% de los casos esto se puede aplicar a uno o ambos archivos para romper las dependencias cíclicas.

Por supuesto, en los archivos fuentes, habrá funciones que se aplican a Parent que modifican también a Child. Por lo tanto es probable que haya que incluir Parent.h y Child.h tanto en Parent.cpp como en Child.cpp

Tenga en cuenta que ser capaz de eliminar completamente la dependencia no siempre es posible. Muchas de las clases y las estructuras están compuestas de otras clases y las estructuras, que es una dependencia no se puede evitar. Sin embargo, siempre que esta dependencia es de un solo sentido, el orden de compilación será fijo y no debería haber ningún problema.

Solución Problema 3

Duplicar definiciones en tiempo de compilación significa que un archivo de cabecera terminó incluyéndose más de una vez en un archivo en particular. Esto lleva a que una clase o estructura se define más de una vez, causando error. Lo primero que debes hacer es asegurarte de que se incluyen solo los archivos necesarios para ese código fuente en particular y quitar todo lo que no utilice.

Lamentablemente, esto rara vez es suficiente, ya que algunas cabeceras se incluyen otras cabeceras. Vamos a revisar un ejemplo de las anteriores, con ligeras modificaciones:

/* Header1.h */
#include "header3.h"
class ClassOne { ... };

/* Header2.h */
#include "header3.h"
class ClassTwo { ... };

/* File1.cpp */
#include "Header1.h"
#include "Header2.h"
ClassOne myClassOne_instance;
ClassTwo myClassTwo_instance;

Por alguna razón tanto Header1.h como Header2.h incluyen a Header3.h, quizás ClassOne y ClassTwo se compenen con funciones de Header3.h. La razón no es importante, pero muchas veces sucede casos como estos en los que al final en File1.cpp se acaba incluyendo dos veces el mismo archivo incluso sin haber una #include a él en el mismo archivo, recuerda que la directiva #include lo que hace es, antes de compilar, copiar todo el contenido del archivo incluido en el archivo actual. así que File1.cpp quedaría de la siguiente manera.

A los efectos de la compilación, File1.cpp termina con copias de Header1.h y Header2.h, los cuales incluyen sus propias copias de Header3.h. El archivo resultante, con todas las cabeceras de expansión en línea en su archivo original, se conoce como una unidad de traducción. Debido a esta expansión en línea, todo lo declarado en Header3.h va a aparecer dos veces en esta unidad de traducción, causando un error.

Así que, ¿qué hacer? No se puede hacer sin Header1.h o Header2.h, ya que se necesita para acceder a las estructuras declaradas en su interior lo que necesita alguna forma de asegurar que, no importa qué, Header3.h no va a aparecer dos veces en el File1.cpp cuando se compila.

Si se miraba stdlib.h antes, te habrás dado cuenta las líneas en la parte superior similar a lo siguiente:

#ifndef _INC_STDLIB
#define _INC_STDLIB

Y en la parte inferior del archivo, algo así como:

#endif  /* _INC_STDLIB */

Esto es lo que se conoce como un “inclusion guard”. Viene a decir que si no está definido _INC_STDLIB defínelo, sino, ve a #endif sería similar al código cuando quieres que algo se ejecute solo una vez.

static bool done = false;
if (!done)
{
    /* Do something */
    done = true;
}

Durante la compilación de File1.cpp, la primera vez que pide que se incluya el archivo stdlib.h, llega a la línea #ifndef y continúa porque “_INC_STDLIB” aún no está definida. La siguiente línea define ese símbolo y lleva a cabo la lectura en stdlib.h. Si hay otra “#include” durante la compilación de File1.cpp, leerá el cheque #ifndef y luego salta a #endif al final del archivo. Esto se debe a todo lo que entre el #ifndef y #endif se ejecuta sólo si “_INC_STDLIB” no está definido, y que se definió la primera vez que lo incluye. De esta manera, se garantiza que las definiciones en stdlib.h sólo son cada vez incluye una vez al ponerlos dentro de #ifndef / #endif.

Esto es trivial para aplicar a sus propios proyectos. Al comienzo de cada archivo de cabecera que usted escribe, escribe lo siguiente:

#ifndef INC_FILENAME_H
#define INC_FILENAME_H

Tenga en cuenta que el símbolo (en este caso, “INC_FILENAME_H”) tiene que ser único a través de su proyecto. Es por esto que es una buena idea de incorporar el nombre del archivo en el símbolo. No agregue un guión bajo al principio como stdlib.h tiene como identificadores precedidos por un guión bajo se supone que son reservados para “la aplicación” (es decir, el compilador, las librerías estándar, y así sucesivamente). Luego se agrega el #endif / * INC_FILENAME_H * / al final del archivo. El comentario no es necesario, pero le ayudará a recordar a que pertenece ese #endif.

Solución Problema 4

Cuando el enlazador trata de crear un archivo ejecutable (o biblioteca) de su código lo que hace es meterlo todo en un archivo objeto (.obj o .o), uno por cada archivo de código fuente y los une. El trabajo principal del enlazador es resolver los identificadores (básicamente, las variables o los nombres de funciones) y convertirlas en direcciones máquina en el archivo final. El problema surge cuando el enlazador en cuentra dos intancias o más de ese identificador en los archivos objetos, entonces no se puede determinar cual es el “correcto” para su uso. El identificador debe ser único para evitar cualquier ambigüedad, Así que ¿Cómo es que el compilador no ve que hay un identificador duplicado y el enlazador si lo ve?

Imagina el siguiente código:

/* Header.h */
#ifndef INC_HEADER_H
#define INC_HEADER_H
int my_global;
#endif /* INC_HEADER_H */

/* code1.cpp */
#include "header1.h"
void DoSomething()
{
    ++my_global;
}

/* code2.cpp */
#include "header1.h"
void DoSomethingElse()
{
    --my_global;
}

La primera se compila en dos archivos objeto, probablemente llamado code1.obj y code2.obj. Recuerde que una unidad de traducción contiene una copia completa de todos los encabezados incluidos en el archivo que está compilando. Finalmente, los archivos de objetos se combinan para producir el archivo final.

Aquí hay una representación visual de la forma en que estos archivos (y su contenido) se combinan:

Note que hay dos copias de “my_global” en ese bloque final. Aunque “my_global” fue único para cada unidad de traducción (esto sería garantizada por el uso de los guardias de inclusión), que combina los archivos objeto generados por cada unidad de traducción se traduciría en que haya más de una instancia de my_global en el archivo. Esto se marca como un error, ya que el enlazador no tiene manera de saber si estos dos identificadores son en realidad una misma, o si uno de ellos era mal llamado justa y que se supone en realidad que es de 2 variables independientes. Así que hay que arreglarlo.

La respuesta no consiste en definir las variables o funciones en los archivos de cabecera en lugar de los archivos fuentes, donde estás seguro que se compilan solo una vez. Esto trae un nuevo problema, ¿Cómo hacer las funciones y variables visibles globalmente si no se encuentra en un archivo de cabecera? ¿De qué manera la pueden “ver” otros archivos? La respuesta es declararlas en los archivos de cabecera, pero no definirlas. Esto permite al compilador saber que la función o variable existe, pero delega el acto de de asignarle una dirección al enlazador.

Para hacer esto para una variable, se añade ‘extern‘ la palabra clave antes de su nombre:

extern int my_global;

El especificador ‘extern‘ es como decirle al compilador que esperar hasta el tiempo de enlace para resolver la “conexión”. Y para una función, simplemente hay que poner el prototipo de función:

int SomeFunction(int parameter);

Las funciones se consideran “extern” por defecto por lo que se acostumbra a omitir el ‘extern’ en un prototipo de función.

Por supuesto, estas son sólo las declaraciones de que my_global y SomeFunction existen en alguna parte. En realidad, no los creas. Todavía tiene que hacer esto en uno de los archivos de origen, de lo contrario aparecerá un error de vinculador nuevo cuando se descubre que no puede resolver uno de los identificadores a una dirección real. Así que para este ejemplo, tendría que añadir “int my_global” a cualquiera de Code1.cpp o Code2.cpp, y todo debería funcionar bien. Si se trata de una función, deberá añadir la función como su cuerpo (es decir, el código de la función) en uno de los archivos de origen.

La regla aquí es recordar que los archivos de cabecera define una interfaz, no una implementación. En ellas se indica que las funciones, variables y objetos existen, pero no se hace responsable de su creación. Ellos pueden decir lo que una estructura o clase debe contener, pero en realidad no debe crear instancias de esa estructura o clase. Se puede especificar los parámetros de una función y se lo devuelve, pero no cómo se obtiene el resultado. Y así sucesivamente. Esta es la razón por la que la lista de lo que puede entrar en un fichero de cabecera al principio de este artículo es importante.

Hay dos notables excepciones a no incluir el cuerpo de las funciones en los archivos de cabecera:

La primera excepción es la de funciones de la plantilla. La mayoría de los compiladores y enlazadores no puede manejar plantillas se definen en archivos diferentes a la que se utilizan en, por lo que las plantillas casi siempre es necesario definir en un encabezado para que la definición se puede incluir en todos los archivos que necesita para usarlo. Debido a la forma de plantillas se crea una instancia en el código, esto no conduce a los mismos errores que se podrían obtener mediante la definición de una función normal en un encabezado. Esto es así porque las plantillas no se compilan en el lugar de la definición, sino que se compilan, ya que son utilizados por el código en otros lugares.

La segunda excepción es las funciones en línea, mencionó brevemente antes. Una función en línea se ha compilado en el código, en vez de llamadas de la forma habitual. Esto significa que cualquier unidad de traducción que ‘llama “el código de una función en línea tiene que ser capaz de ver el funcionamiento interno (es decir, la puesta en práctica) de esa función con el fin de insertar el código de esa función directamente. Esto significa que un prototipo de función sencilla no es suficiente para llamar a la función en línea, lo que significa que donde quiera que normalmente sólo tiene que utilizar un prototipo de función, se necesita el cuerpo de la función general de una función en línea. Al igual que con las plantillas, esto no causa errores de vinculador como la función en línea no es realmente compilado en el lugar de la definición, pero se inserta en el lugar de la llamada.

Otras Consideraciones

Por lo tanto, el código está muy bien dividido en varios archivos, que le da todos los beneficios mencionados en el inicio como la velocidad de compilación de una mayor y mejor organización. ¿Hay algo más que necesitas saber?

En primer lugar, si estás usando la biblioteca estándar de C++ (STL) o cualquier otra biblioteca que utilice espacios de nombres, no utilice la se sentencia “using” en los archivos de cabecera pues reducirás la utilidad de los espacios de nombres casi por completo, es mejor usar el identificador del espacio de nombre en cada función u objeto que uses, así tienes la ventaja de saber a que biblioteca pertenece ese código y no tendrás problemas de superponer nombres de funciones propias que te hagan rectificar el código.

En segundo lugar, el uso de macros debe ser cuidadosamente controlado. Los programadores de C tienen que confiar en las macros de un montón de funcionalidades, pero los programadores de C++ debe evitarlos siempre que sea posible. Si desea una constante en C + +, utilice la palabra clave ‘const’. Si desea una función en línea en C++, utilice la palabra clave ‘inline’. Si desea una función que opera sobre los diferentes tipos utilice plantillas o sobrecarga. Pero si necesitas utilizar una macro, por alguna razón, y lo coloca en un fichero de cabecera, trate de no escribir macros que podrían interferir con el código en los archivos que se incluyen. Cuando se producen errores extraños de compilación, no quiero tener que buscar a través de todos los archivos de cabecera para ver si alguien utiliza un # define para cambiar inadvertidamente su función o sus parámetros a otra cosa. Así que siempre que sea posible, mantener las macros de cabeceras a menos que usted puede estar seguro de que no les importa que afecta a todo en el proyecto.

Sources:

http://razonartificial.com/2011/07/organizacion-del-codigo-fuente-en-cpp/
http://www.gamedev.net/page/reference/index.html/_/reference/programming/languages/c-and-c/organizing-code-files-in-c-and-c-r1798

Comparte esto !

  • Haz clic para compartir en Twitter (Se abre en una ventana nueva)
  • Haz clic para compartir en Facebook (Se abre en una ventana nueva)

Me gusta esto:

Me gusta Cargando...

Ir a las entradas

← Entradas Antiguas
  • Acerca de…


    Youtube: www.youtube.com/judelco
    Twitter: www.twitter.com/judelco

    RSS Feed   Suscribir RSS - Entradas
  • Estadísticas

    • 42.096 visitas
  • Calendario

    agosto 2022
    L M X J V S D
    1234567
    891011121314
    15161718192021
    22232425262728
    293031  
    « Sep    
  • Buscar en el Blog

  • Categorías

    • Chorradas (4)
    • Música (1)
    • Noticias (12)
    • Programación (16)
    • Touhou (2)
    • Videojuegos (13)
    • Videos (2)
  • Últimos comentarios

    JuDelCo en [PC] Slaves to Armok II: Dwarf…
    DarkSoulsAle en [PC] Slaves to Armok II: Dwarf…
    JuDelCo en Programa Videojuegos, no …
    Hamo en [PC] Slaves to Armok II: Dwarf…
    JuDelCo en Introducción al lenguaje ensam…
  • Suscríbete al blog para recibir notificaciones de nuevas entradas por e-mail !

    Únete a 235 seguidores más
Crea un blog o un sitio web gratuitos con WordPress.com.
Privacidad y cookies: este sitio utiliza cookies. Al continuar utilizando esta web, aceptas su uso.
Para obtener más información, incluido cómo controlar las cookies, consulta aquí: Política de cookies
  • Seguir Siguiendo
    • El Blog de JuDelCo
    • Únete a 235 seguidores más
    • ¿Ya tienes una cuenta de WordPress.com? Accede ahora.
    • El Blog de JuDelCo
    • Personalizar
    • Seguir Siguiendo
    • Regístrate
    • Acceder
    • Denunciar este contenido
    • Ver sitio web en el Lector
    • Gestionar las suscripciones
    • Contraer esta barra
 

Cargando comentarios...
 

    A %d blogueros les gusta esto: