22/09/2019, 03:57:10 pm *
Bienvenido(a), Visitante. Por favor, ingresa o regístrate.
¿Perdiste tu email de activación?

Ingresar con nombre de usuario, contraseña y duración de la sesión
Noticias: Puedes practicar LATEX con el cómodo editor de Latex online
 
 
Páginas: [1]   Ir Abajo
  Imprimir  
Autor Tema: Interacción entre C y JavaScript+ActiveX (proyecto: usar navegador como consola)  (Leído 3800 veces)
0 Usuarios y 1 Visitante están viendo este tema.
argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« : 10/05/2013, 01:52:41 am »

El navegador como consola para programas hechos en C


Un programa GUI es todo aquel programa que abre una ventana en el escritorio de Windows, en general con fondo blanco, o mejor, del color que nosotros queramos, y aparecen allí diversos botones, cajas y listas con las cuales interactuar e intercambiar información. También puede que haya zonas donde editar texto, o tablas con celdas, o herramientas para hacer gráficos, insertar imágenes, sonidos, videos, enlaces a páginas web u otros archivos o programas, etc.

Los programas con interfase GUI son, por lo tanto, la mayoría de los programas que disfrutamos actualmente en nuestra computadora.
Uno de esos programas GUI son los navegadores de Internet más conocidos: Internet Explorer, Firefox, Opera, Chrome.

PERO cuando realizamos programas en C, en forma "común y corriente", no tenemos la comodidad de las interfases GUI.
El C "pelado" trabaja sólo con la consola de la línea de comandos.
Se nos abre entonces la ventana de comandos, que es ese rectángulo negro, feo, incómodo, cuyo tipo de letra es monoespaciado y anticuado. Es algo horrible, desagradable.

Sería muy deseable poder correr los programas que escribimos en C, usando una "alternativa" a la consola de comandos negra-fea-incómoda-horrible.

El primer problema que se nos presenta es que para hacer el más tonto programa con interfase GUI en Windows, tenemos que aprender a manejar alguna librería nueva y externa de C (como por ejemplo Tcl/Tk), o bien aprender a manejar las API de Windows, junto con técnicas de desarrollo de aplicaciones en C bajo Windows.

Lo ideal sería poder contar con una ventanita linda de Windows en la cual nuestros programas en C exhiban sus resultados, como si se tratara de la consola de comandos.
Se trataría de un emulador de la consola de entrada/salida estándar de C.

No sé si existen esa clase de emuladores,
pero aquí mostraremos cómo hacer para que el navegador funcione como consola de entrada/salida estándar para nuestros programas en C:sorprendido:

La idea esencial es que esta interacción entre C y el navegador sea "transparente" no sólo para el usuario de nuestros programas, sino también para el programador.  :guiño: :guiño: :guiño:

¿A qué me refiero?
Bueno, la interacción de entrada/salida en C se hace básicamente a través de las funciones printf y scanf del lenguaje C.
Lo que haremos nosotros es crear unas funciones que tengan un nombre parecido, y que en vez de enviar o tomar datos de la consola de entrada/salida estándar, lo hagan hacia y desde el navegador.

Así, el programador no tiene que aprenderse ninguna cosa complicada nueva, el problema se reduce a la inclusión de un par de archivos (una librería y una página HTML), y más aún, es totalmente sencillo modificar un programa para que envíe y/o tome datos ya sea de la consola estándar o del navegador, según sea nuestro deseo.
Esto último es útil si queremos distribuir nuestro programa para dárselo a personas que sólo desean seguir usando su consola estándar, como han hecho toda la vida.

En los próximos posts daremos detalles del proyecto, la planificación, y finalmente la implementación real y definitiva.

En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #1 : 10/05/2013, 01:32:42 pm »

Proyecto de consola C/JavaScript

Nuestro proyecto se llamará:

BrowCon

Versión: 1.00


(BrowCon = Browser Console = Consola del navegador).

Constará de dos archivos:

* Una librería en C llamada browcon01.00.h
* Un archivo HTML llamado browcon0100.html, es decir, un documento que puede abrirse y procesarse con un programa navegador.

La librería browcon01.00.h contendrá dos funciones, o bien macros funcionales, llamadas browcon_printf y browcon_scanf, que se podrán usar exactamente de la misma manera que las respectivas funciones printf y scanf de la librería estándar stdio.h.

Por ejemplo, supongamos que queremos calcular la suma de 2 números, pero que los datos se vean y/o carguen en el navegador. Escribiríamos estas sentencias:

float a, b;
browser_printf("Introducir dos números separados por un espacio: ", "");
browser_scanf("%f %f", a, b);
browser_printf("Suma: %f + %f = %f", a, b, a+b);

En el navegador se verá lo siguiente:

Introducir dos números separados por un espacio: 2.84   0.17
Suma: 2.84 + 0.17 = 3.01

Además, temporalmente aparecerá un recuadro para que el usuario pueda cargar ahí los números que desee sumar (he puesto 2.84 y 0.17 sólo como ejemplos de datos de entrada).



El archivo browcon01.00.html contendrá una sección de código en lenguaje JavaScript.
En dicha sección se definirán funciones que controlen las operaciones básicas de la "browser console", tales como escribir datos en la ventana (del navegador) o leer datos de allí y guardarlos en algún lugar intermedio de almacenaje de datos, y hacer esto cuidando que la apariencia y comportamiento sea prácticamente igual al de un prograna en C "normal".

Como HTML es un documento con cierta estructura y estilos, tendremos que tomar decisiones sobre ambos.
El diseño será totalmente simplificado: visualmente, browcon será una página HTML en blanco, pero con un par de bloques div destinados a mostrar datos (browcon_printf) o esperar entrada de datos del usuario (browcon_scanf).

Vamos a llamar browcon no sólo a los archivos de nuestro sistema, sino también a esa especie de consola simulada en el navegador.

Hay dos características visuales básicas que queremos tener en cuenta:

(1) A medida que el programador va enviando datos a la browcon, nos gustaría que quede siempre visible "lo último" que hemos enviado, empujando hacia arriba los datos más viejos.
Para esto tendremos que definir un punto especial en el documento HTML a modo de "ancla", que siempre estará debajo del bloque destinado a muestreo de datos, y que funcionará así:
Una vez que hayamos mostrado los datos, haremos que el navegador vaya hasta el ancla.

(2) Cuando el programador desee que el usuario envíe ciertos datos desde la browcon, introduciéndolos en una línea de texto finalizada con la tecla Enter (del mismo modo en que funciona scanf en la consola de comandos), nos gustaría que aparezca claramente una línea para edición, rodeada quizás de un rectángulo delimitador, pero que se abra inmediatamente debajo de la última línea de datos mostrada por previas llamadas a browcon_printf y browcon_scanf.

La manera de implementar esto de un modo sencillo sería así: poner el bloque de introducción de datos siempre debajo del bloque de muestreo de datos.
¡Pero dejarlo invisible!  :guiño:
Cuando el programador, desde su programa en C, envíe una solicitud de datos con browcon_scanf, lo único que habrá que hacer será hacer visible por un ratito al bloque de introducción de datos, hasta que el usuario termine de escribir la información solicitada, tras presionar la tecla Enter.



Para hacer que una página web reaccione y realice operaciones complejas sobre sus elementos, es necesario utilizar código JavaScript.
A fin de no lidiar con decenas de archivos, nosotros pondremos todo junto en un mismo archivo: el contenido HTML y los scripts de JavaScript, así como los formatos en CSS, de haberlos, etc.
Esto además tiene sentido en nuestro proyecto, porque nuestra página HTML browcon tendrá muy pocos y sencillos elementos HTML.
La complejidad estará en los scripts.

Sin embargo, JavaScript tiene restricciones de seguridad bien concretas: es imposible leer o escribir datos desde una página web hacia y desde una computadora, usando JavaScript.
¡Pero printf y scanf son operaciones básicas de lectura/escritura de datos! ¿Cómo resolveremos esto?

Por suerte existe una librería que amplía las posibilidades de JavaScript, llamada ActiveX, que funciona en el navegador Internet Explorer de Microsoft.

Esto nos obliga de entrada a tener que utilizar el Internet Explorer  :sorprendido: como único navegador admisible para nuestra browcon.

¡El proyecto browcon no funcionará en Firefox, Opera, Chrome, Netscape, ni ningún otro navegador!



Otro detalle importante de la implementación es que nuestra página HTML browcon tiene que estar siempre activa esperando alguna solicitud que pueda recibir externamente de mostrar o enviar información.

Lo más corriente sería intentar poner un ciclo del tipo "while", que ejecute siempre (hasta que se cierre el navegador), así:

while (true) {

   if ( Hay_Nueva_Solicitud() )
      Procesar_Solicitud();

}


¡Pero eso no funciona!

Aunque JavaScript cuenta con sentencias de tipo while, el efecto de poner un bucle while infinito (porque la condición es siempre "true" = "verdadera") en nuestra página HTML, es que el navegador quedará saturado, y nada funcionará.

Por lo tanto, la solución a este inconveniente tiene que tener otra "filosofía", a saber, la de "reaccionar ante interrupciones externas".
Sólo cuando desde el exterior se produce un cambio, nuestra browcon reaccionará y procesará una solicitud.
Para esto, lo que haremos será cargar un temporizador o cronómetro en el instante de inicio de nuestra página HTML browcon, y hacer que cada intervalos fijos de tiempo verifique si se han producido nuevas solicitudes de entrada/salida de datos.
En caso de detectar una solicitud, se la habrá de procesar, y si no, no se hace nada.

Esto es posible de hacer en JavaScript mediante la función setInterval, llamándola a través del atributo onload del elemento body de HTML.
 :¿eh?: :¿eh?: :¿eh?:

En castellano:

El contenido de nuestra página web estará encerrado entre etiquetas <body> </body>, como es usual.
El elemento body tiene atributos, por ejemplo, onload.
Cuando asignamos a onload un valor, se interpreta como una acción a realizar por JavaScript al momento en que se carga la página.
Por ejemplo, onload="alert('Estoy cargando, no me interrumpas!!!')" hará saltar un mensaje de alerta, al momento de iniciar la carga de la página HTML.

En vez de eso, quisiéramos poner a punto el cronómetro, de tal manera que repita cierta acción a una frecuencia de una vez cada 3000 milisegundos (o sea 3 segundos).
En ese caso, la función JavaScript que debemos invocar es setInterval.
A dicha función tendremos que pasarle como argumento un comando o función de JavaScript, que en nuestro caso será un procedimiento encargado de revisar si hay o no nuevas solicitudes de entrada/salida, y en caso de haberlas, realizar las tareas necesarias en la browcon.

Ese procedimiento lo inventaremos nosotros, y le daremos el nombre de RefreshBrowcon().
¿Por qué ese nombre? Porque se encargará de "refrescar" el estado de la browcon, verificando si hay nuevas solicitudes de entrada/salida, y tomando las acciones pertinentes.

Nuestro elemento body tendrá ahora este aspecto:

<body onload=setInverval("RefreshBrowcon();", 3000)>

   (elementos de la browcon...)

</body>

Nuestro procedimiento RefreshBrowcon() tendrá este aspecto:

<script type="text/javascript">
    
    function RefreshBrowcon() {
          
             // Aún no hacemos nada...

    }

</script>




Hay otras consideraciones a tener en cuenta.

Debido a que lo normal es que en C la información se envíe a archivos de texto con caracteres ASCII, o con cualquier otra codificación "antigua", puede resultar incompatible el intercambio de datos con JavaScript, si es que éste envía información en formato Unicode ("moderno").

Aunque lo ideal sería "modernizar todo", en realidad es más fácil configurar HTML para que elija un formato de codificación "antiguo". Así, elegiremos el conjunto de caracteres ISO-8859-1.

Así, del lado de HTML/JavaScript/ActiveX, nuestra browcon tiene por ahora este aspecto:


<!DOCTYPE HTML>
<!--
      BrowCon
      Version: 00.00
-->

<html>

<head>

    <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">

</head>

<body onload="setInterval('RefreshBrowser()', 3000);">

   <script type="text/javascript">

       function RefreshBrowser() {    

         // Aún no estamos haciendo nada aquí...

       }

    </script>

   <div>
       (Bloque para muestreo de datos con browcon_printf()...)
   </div>
  
   <div style="visibility:hidden">
       (Bloque para introducción de datos con solicitudes browcon_scanf()...)
    </div>

</body>

</html>



He agregado un comentario al principio, indicando el número de versión del proyecto.
Como todavía es sólo un esqueleto, la versión es 00.00.
Le pondremos 01.00 cuando esté lista la primera versión funcionando correctamente.

Salvemos ese documento con el nombre browcon00.00.html, y luego abrámoslo con el navegador Internet Explorer (recordemos que tiene que ser ése navegador, y no otro).

Como todavía no hemos introducido ninguna herramienta ActiveX, da lo mismo abrirlo con cualquier otro navegador, pero nuestro proyecto finalizado necesita ActiveX, así que mejor acostumbrémonos a usar Internet Explorer.

Puede que nos aparezcan algunos avisos de seguridad, indicándonos que nuestro documento HTML tiene scripts, o incluso elementos de ActiveX.
Para que todo funcione, habrá que hacer click en "Permitir los scripts..." o "Permitir ejecución de ActiveX...", etc.

El lector podrá objetar que la velocidad de "refresco" elegida, de 3 segundos, es muuuuuy lenta.
De hecho, es muy conveniente una velocidad de refresco lento en la etapa de diseño y depuración de un proyecto que trabaja con la función setInterval().

Esto es lógico, porque si tuviéramos un error de programación, el persistente refresco, si fuera rápido, no nos daría tiempo a cerrar el navegador, ni intentar cualquier otra escapatoria...

En este proyecto tendremos muchos errores todo el tiempo hasta que quede a punto.
Así que: cuidado.  :sorprendido: :sorprendido: :sorprendido:




Por último, observemos que el bloque destinado a introducción de datos por el usuario, está oculto mediante el atributo style="visibility:hidden".
En consecuencia, al cargar la página, ese bloque no se ve.

En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #2 : 10/05/2013, 06:51:39 pm »

(00.10) Mostrar u ocultar el bloque "scanf"

A partir de aquí vamos a dar nombres más concisos para las cosas que nos interesan.
Vamos a llamar output o bien consola (en modo) de entrada a las acciones de muestreo de datos, mediante funciones del tipo printf,
y llamaremos input o bien consola (en modo) de salida a las acciones de introducción de datos por parte del usuario, mediante funciones del tipo scanf.

Vemos que la consola de salida está siempre visible en la browcon, mientras que la consola de entrada aparece oculta.

Lo que haremos ahora es darle un identificador a cada una, denostando como divprintf a la consola de salida y con divscanf a la consola de entrada.

Además, definiremos en JavaScript una variable global show_divscanf, que tomará valores booleanos, es decir, true ó false (verdadero o falso), según que el bloque divscanf tenga que mostrarse o no.
Le daremos un valor inicial de false, de modo que al cargar la browcon, el bloque divscanf estará inicialmente oculto.

Ahora, haremos que la función de "refresco" RefreshConsole() verifique, cada vez que se la llama, es decir, paciente y repetidamente, cada 3 segundos, el estado de la variable show_divscanf.
Si tiene valor true (verdadero), entonces se hará visible el bloque divscanf,
y su tiene valor false (falso), el bloque divscanf se ocultará.

¿Cómo se logra esto de mostrar y ocultar?

Simplemente, hay que cambiar el valor del atributo style.visibility del elemento divscanf. El código para esto sería así:

       
              if (show_divscanf) {
                   document.getElementById('divscanf').style.visibility = "visible";
              } else {
                   document.getElementById('divscanf').style.visibility = "hidden";
              }


Lo que hacemos ahí es rastrear el elemento divscanf en nuestro documento HTML, mediante el método getElementById(), y modificamos su atributo de visibilidad, poniéndolo visible ("visible") u oculto ("hidden") según corresponda.
Ese código iría dentro de la función RefreshConsole(), como ya mencionamos.

Ahora, cada 3 segundos, JavaScript verifica si tiene que cambiar o no el mencionado atributo,
y eso produce el efecto de mostrar u ocultar el bloque divscanf.



El único problema es que no tenemos ninguna manera de hacer cambiar el estado de la variable show_divscanf.
Lo que necesitamos es que nuestra página reciba algún tipo de señal del exterior, y que reaccione cambiando enseguida el estado de la variable.

Por ahora no involucraremos acciones desde "afuera" de nuestro documento,
sino que nos conformamos por un rato con algún tipo de "simulación".
O sea, generaremos nosotros mismos la "señal de cambio de estado" haciendo clic en un botón que colocaremos en la parte superior de la página.

En tal caso, necesitaremos agregar un bloque HTML que contenga ese botón.
El botón se introduce mediante el tag input de HTML, seleccionando el tipo "button".

Al bloque o contenedor de este tipo de botones lo iremos modificando a lo largo de nuestro proyecto,
pero lo cierto es que siempre nos vendrá bien tener ahí un par de botones que nos permitan controlar un poco nuestra browcon.
Así, nos anticipamos a cualquier eventualidad futura, y le ponemos un identificador, digamos "divcontrol".

El botón tendrá un texto que informe en forma suscita que sirva para mostrar u ocultar el bloque divscanf.
Así que le pondremos el valor "Show/Hide divscanf".

Finalmente, necesitamos que el botón reaccione haciendo algo.
En concreto, queremos que nos sirva para cambiar el valor de la variable show_divscanf, y vaya alternando su valor de true a false y viceversa.
Esto se logra tomando la negación lógica del valor que previamente tenía, y asignando este nuevo valor a la variable.

Esta acción debe realizarse al momento que hagamos click sobre el botón.
Nuestro botón queda configurado así:


      <input
                 type="button"
                 value="Show/Hide divscanf"
                 onclick="show_divscanf = !show_divscanf;"
         />


Ya tenemos todas las modificaciones que deseamos agregar,
y nos queda lista entonces la versión 0.10 de nuestra browcon.

Cuando carguemos esta nueva versión en Internet Explorer,
seguramente estaremos tentados de hacer click en el nuevo botón que hemos creado.

¡Observaremos que la aparición o desaparición del bloque divscanf no es automático!

Esto es a propósito así, porque se parece a la situación real que manejaremos después,
mediante la cual JavaScript se entera de las señales enviadas desde el exterior sólo cada vez que se repite la llamada a RefreshConsole(), que en nuestro caso es cada 3 segundos.

Si cambiásemos la velocidad de refresco a 1 vez cada 1 milisegundo, entonces nos parecería que es automática la aparición o desaparición del bloque divscanf tras apretar el botón.
Pero no queremos este automatismo, porque puede traer conflictos al procesar señales así producidas desde el "exterior", de un modo más o menos impredecible.

El documento HTML tendrá ahora este contenido:

Nombre de archivo: browcon00.10.html

Contenido:


<!DOCTYPE HTML>
<!--
      BrowCon
      Version: 00.10
-->

<html>

     <head>
     
         <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
     
     </head>
     
     <body onload="setInterval('RefreshBrowser()', 3000);">
     
     
        <script type="text/javascript">
       
           show_divscanf = false;
     
     
           function RefreshBrowser() { 
             
              if (show_divscanf) {
                   document.getElementById('divscanf').style.visibility = "visible";
              } else {
                   document.getElementById('divscanf').style.visibility = "hidden";
              }
     
           }
       
        </script>
       
        <div id="divcontrol">
           
             <input type="button" value="Show/Hide divscanf" onclick="show_divscanf = !show_divscanf;" />
             
        </div>
     
        <div id="divprintf">
            (Bloque para muestreo de datos con browcon_printf()...)
        </div>
       
        <div id="divscanf" style="visibility:hidden">
            (Bloque para introducción de datos con solicitudes browcon_scanf()...)
        </div>
     
     </body>

</html>


He aquí una muestra de lo que se vería por ejemplo en Internet Explorer 10:


* browcon00.10.html (1.23 KB - descargado 65 veces.)
* browcon00.10.png (11.08 KB - descargado 188 veces.)
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #3 : 10/05/2013, 07:24:58 pm »

(00.11) Una pequeña mejora estética.

En la imagen del post anterior se puede apreciar que está todo muy pegado visualmente hablando.

Así que introduciremos un poco de separación entre los 3 bloques div de nuestra página, insertando simplemente una línea en blanco mediante el tag <br />.

Esto nos conduce a la versión 00.11 de la browcon, que pondré en Spoiler, porque es casi idéntica a la versión anterior, salvo por esta pequeña mejora estética.
Debajo, mostramos cómo luce la página visualmente tras el cambio.

Nombre de archivo: browcon00.11.html

Contenido:

Spoiler: (browcon00.11.html) (click para mostrar u ocultar)



* browcon00.11.html (1.24 KB - descargado 71 veces.)
* browcon00.11.png (13.56 KB - descargado 203 veces.)
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #4 : 10/05/2013, 09:30:40 pm »

(00.20) Agregando entrada/salida en forma manual.

En este post vamos a aprender muchas cosas, así que estemos atentos.  :sorprendido:

Como todavía no nos animamos a intercambiar datos entre C y JavaScript, vamos a seguir haciendo "simulaciones" de operaciones de entrada salida.
Tendremos el control manual de dichas operaciones.

Vamos a declarar una variable global en JavaScript llamada signal_printf, de tipo booleana,
que será true cuando haya nuevos datos para mostrar en el bloque divprintf,
y será false cuando no haya nuevos datos que mostrar.

Así, cada vez que nuestra browcon haga sus famosos "refrescos" cada 3 segundos, entrando en la rutina RefreshConsole(),
hará la consulta de si la variable signal_printf es true,
lo cual significará que habrá nuevos datos que mostrar.

En ese caso, se enviarán esos datos al bloque divprintf para ser "publicados" allí.
Haremos esto con una función que llamaremos post(),
que tomará como argumento una cadena de caracteres llamada str.
Así, la llamada post(str) agregará los nuevos datos al bloque divprintf.

Una vez hecho esto, es claro que los "nuevos" datos ya dejaron de ser "nuevos",
pues ya han sido "publicados".
Así que ya no hay nada nuevo para publicar, y debe retornarse a false el estado de la variable signal_printf.

Veamos cómo queda la función RefreshConsole() al agregarle esta nueva capacidad de reconocer cuándo publicar nuevos datos en divprintf:


           function RefreshBrowser() {   
             
              if (signal_divprintf) {
                   signal_divprintf = false;
                   post('Test divprintf. --- ');
              };
             
              if (show_divscanf) {
                   document.getElementById('divscanf').style.visibility = "visible";
              } else {
                   document.getElementById('divscanf').style.visibility = "hidden";
              }
     
           }


Vemos que está casi igual que antes, salvo que ahora le hemos agregado una sentencia "if" al principio, para manejar el caso de divprintf.

El texto que publicaremos es sólo una cadena de caracteres de testeo, que dice: "Test divprintf. --- ".

Aquí observamos algo intrigante, y es que allí
hacemos primero el cambio de estado de la variable signal_printf, poniéndolo a false,
antes que la publicación propiamente dicha de la cadena test.

¿Por qué hemos invertido el orden de estas sentencias?

En primer lugar, analizándolo fríamente, el resultado final de las operaciones será el mismo, sin importar el orden en que las efectuemos, porque la función post() no interferirá con la variable signal_printf, y viceversa.

Pero la cuestión va más allá.
Tenemos que analizar lo que ocurre a lo largo de la línea temporal en que está abierta nuestra página browcon.
Recordemos que cada 3 segundos la página intenta realizar persistentemente todas las tareas listadas en la función RefreshConsole().

¿Qué ocurriría si la función no ha terminado de completar sus tareas antes de que pasen los 3 segundos?
¡Se empezaría a ejecutar una nueva instancia de RefreshConsole() en simultáneo con la que todavía no terminó de ejecutarse!  :sorprendido:

Aquí comienza a enredarse todo.
Por ejemplo, si la nueva instancia de RefreshConsole() "ve" que el valor de la variable signal_divprintf todavía tiene valor true,
intentará publicar nuevamente los mismos datos que ya estaban siendo publicados por la instancia previa.
En resumen, se obtiene una duplicación en la salida de los datos, totalmente indeseable, por supuesto.

Para "defendernos" de esto, invertimos las sentencias,
de modo que el cambio de estado de la variable signal_divprintf se realice tan rápido como sea posible.
Lo más pronto que podemos hacer esto es en la primer sentencia dentro del bloque "if".

Aquí podemos hablar de "colisión de instancias", refiriéndonos a que un procedimiento colisiona con una nueva llamada de sí mismo, en superposición con una llamada anterior.

Si la velocidad de refresco fuese de 1 vez por cada milisegundo, entonces el riesgo de colisiones de instancias sería tan grande, que hasta podríamos tener decenas de instancias de RefreshConsole() corriendo al mismo tiempo.

Más tarde procuraremos corregir este problema, pero la idea básica es la misma que esbozamos aquí,
es decir, la de "informar prontísimo de un cambio de estado".
No hay muchas más opciones que esa en principio.

Tendremos que lidiar siempre con este problema de las "colisiones de instancias", de la función RefreshConsole().



Pasemos a definir la función de publicación post().
Tiene una expresión muy sencilla, que mostramos de inmediato:


           function post(str) {
                document.getElementById('divprintf').innerHTML += str;
           }


Lo que hace esta función es tomar el elemento HTML con identificador divprintf,
y allí le agrega la cadena de texto str al contenido previamente existente.

El contenido de un bloque div se puede obtener mediante el atributo innerHTML.
Al invocarlo, se obtiene una string (cadena de caracteres, cadena de texto), con el contenido completo que en ese momento hay dentro del div.
Si le cambiamos el valor a dicho atributo, automáticamente hacemos que cambie el contenido de todo el bloque div.

Por ejemplo, una sentencia como la siguiente dejaría el bloque en blanco:


             document.getElementById('divprintf').innerHTML = "";


Sin embargo, nosotros no queremos reemplazar lo que había antes en la browcon, sino agregar los nuevos datos, tal como ocurre en la consola estándar de Windows.
Para agregar datos a lo que ya estaba de antes, se usa el operador de asignación incremental +=, al cual le adosaremos la cadena str, que es la que supuestamente contiene los nuevos datos a visualizar en pantalla.



Ahora tenemos que ocuparnos de permitir al usuario ingresar una línea de datos en forma de texto, desde el teclado, como en la consola estándar de Windows.
Más aún, queremos que cuando el usuario presione la tecla Enter, los datos sean debidamente procesados.

Esto corresponde a una sentencia scanf en C.

Para esto, vamos a poner en el bloque divscanf un casillero rectangular que permita el ingreso de datos.
Esto se logra mediante un tag input, más o menos así:


        <div id="divscanf" style="visibility:hidden"> 

             <input type="text" value="(Ingrese datos)" />

        </div>


El casillero de texto es un tag input con tipo "text", como se puede apreciar.
Además, se le puede adjudicar un valor inicial con el atributo value.
Le hemos puesto el texto "(ingrese datos)", pero lo ideal es que ese campo esté vacío,
así que en el futuro pondremos value="".

Eso ha sido muy fácil, pero el problema es que ese casillero de texto no reacciona:llorando:
No hace nada aún.
Para que reaccione y sirva para enviar datos "hacia alguna parte", tendremos que cambiar la estructura del bloque divscanf, haciendo uso más bien de los formularios HTML.

El formulario puede contener uno o más botones, así como texto y otros objetos.
Pero lo importante es que es capaz de reaccionar ante determinadas acciones del usuario,
como por ejemplo, presionar la tecla Enter.

Nosotros utilizaremos un formulario cuyo único contenido es el casillero de texto arriba mencionado, para entrada de datos en forma de una línea de texto.

Además, a fin de poder interactuar con el formulario y con sus elementos internos, nos acostumbraremos a poner un identificador a todos estos elementos.

Por último, cuando el usuario presione la tecla Enter, el formulario reaccionará y podrá ejecutar una acción.
Le indicaremos a JavaScript que la acción a realizar será, en este caso, una función de JavaScript contenido dentro del mismo documento, encargada de procesar los datos introducidos por el usuario.
Dichos datos están almacenados en el atributo value del elemento con identificador inputscanf, que será el nombre que daremos a nuestros casillero de texto.
El procesamiento de estos datos se hará con una función que llamaremos senddata().
Y a fin de que el formulario haga el llamado apropiado a esta función, cuando el usuario presione la tecla Enter, tendremos que indicarlo dando al atributo action del formulario, el valor "javascript:sendata(document.getElementById("inputscanf").value)".

Nos quedaría lo siguiente:


        <div id="divscanf" style="visibility:hidden">
          <form id='formscanf' action='javascript:senddata(document.getElementById("inputscanf").value);'>   
             <input id='inputscanf' type="text" value="" />
          </form>
        </div>


¿Qué hace la función senddata()?
En el futuro, dicha función se encargará de exportar datos al "exterior" de la browcon.
Por ahora no hace gran cosa.

Sin embargo, dicha función devolverá el eco de los datos del usuario, reescribiéndolos en el bloque divprintf, o sea, publicándolos en la consola de salida de la browcon.

También, tiene que reajustar el valor de la variable show_divscanf a false, para que, una vez que el usuario ha culminado la entrada de datos, el casillero de texto correspondiente quede oculto.

Otra vez, nos preocupa el fenómeno de "colisión de instancias" de RefreshConsole(), así que cambiamos el estado de la variable show_divscanf tan pronto como sea posible.

El resultado final será esto:


           function senddata(str) {
                show_divscanf = false; 
                post(str+' ');
           }




En todo esto faltaría un detalle elemental, y es que necesitamos avisarle a la browcon cuándo hay una solicitud de datos (scanf).

Esto lo haremos manualmente haciendo click sobre un botón que diga algo como "Get data from divscanf".
En este caso, y sólo en este caso, se pondrá el valor de la variable show_divscanf a true,
logrando que en el próximo ciclo de 3 segundos, en la llamada a RefreshConsole(), aparezca mágicamente el casillero de texto para entrada de datos del usuario.


            <input type="button" value="Get data from divscanf" onclick="show_divscanf = true;" />




La puesta a punto del script requiere que demos valores iniciales a las variables.
Inicialmente no hay datos que mostrar, ni tampoco datos que exportar, así que inicializamos las variables a false.


           signal_divprintf = false;
           show_divscanf = false;




Ahora ya tenemos lista la interfase de nuestra browcon, funcionando correctamente los elementos encargados de la entrada y salida de datos.
Funciona bien, y podemos jugar libremente con los botones y el casillero de texto para ver qué pasa.
Pero recordemos que todavía funciona todo muy lentamente debido a que hay 3 segundos entre cada nueva acción.


Nombre de archivo: browcon00.20.html

Contenido:

Spoiler: (browcon00.20.html) (click para mostrar u ocultar)


He aquí una captura de pantalla de unos experimentos que yo mismo hice.
La "foto" la tomé justo en el momento en que terminaba de introducir datos por 3era vez, tal que el eco de esos datos fue publicado, mientras todavía estaba visible el casillero de texto del bloque divscanf, dado que aún no habían transcurrido los 3 segundos del ciclo.


* browcon00.20.html (1.91 KB - descargado 61 veces.)
* browcon00.20.png (16.15 KB - descargado 191 veces.)
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #5 : 10/05/2013, 10:45:56 pm »

(00.21) Mejorando detalles

Si bien nuestra browcon está bastante funcional, tiene varios defectos pequeños, que ahora procuraremos corregir.

Si jugamos con nuestra browcon para ingresar datos repetidas veces,
veremos que, al hacerse visible el bloque divscanf, el casillero de texto inputscanf aparece con el último texto que habíamos introducido antes.  :indeciso:

Esto es desprolijo, y nos gustaría que cada vez que se active el casillero inputscanf, aparezca vacío de contenido.
Para lograr esto, hay que tener en cuenta que el contenido de ese casillero está contenido en el atributo value del elemento inputscanf.
Así, antes de hacerlo visible, lo forzaríamos a que tenga el atributo value="".
Esto se haría, agregando la siguiente sentencia


      document.getElementById('inputscanf').value = '';


¿Dónde?   :¿eh?:

Esa acción debe realizarse en el momento que hacemos click en el botón "Get data...",
y justo antes de activar la variable show_divscanf.
Así que tendremos que agregar esto al atributo onclick del botón, así:


             <input type="button" value="Get data from divscanf" onclick="document.getElementById('inputscanf').value = ''; show_divscanf = true;" />


Notemos que el borrado del campo value se realiza justo antes de una nueva entrada de datos.
Mientras tanto, el valor antiguo que había, quedará ahí, aún en el caso de que el bloque divscanf quede oculto.

Podríamos también, por fanatismo, limpiar el campo value justo después de haber realizado el eco, de los datos ingresados, en el bloque dviprintf.
En tal caso, tendríamos que agregar otra vez la misma sentencia al final de la función senddata().


           function senddata(str) {
                show_divscanf = false; 
                post(str+' ');
                document.getElementById('inputscanf').value = '';
           }


Notemos que desde la versión 00.20 estoy enviando el eco de divscanf mediante la sentencia: post(str+' ':guiño:.
¿Cuál es la gracia de "sumar" el caracter espacio en blanco mediante +' '?
Eso lo hago para que haya un mínimo de separación entre sucesivos ecos...
Si no, quedaría muy desprolijo.

Como sea, ahora el casillero de texto se borrará inmediatamente tras publicarse el eco en divprintf.
Ya no habrá que esperar hasta la próxima entrada de datos.



Otro detalle que nos gustaría cambiar es la longitud del casillero de texto inputscanf.
Eso es sencillo, cambiando el atributo size.
Le pondremos por ejemplo el valor 127.

Esto no significa que la línea de texto que vamos a ingresar esté limitada en 127 caracteres,
sino que lo que veremos a un tiempo en el casillero serán 127 caracteres.
Pero podemos ingresar líneas de texto de tamaño arbitrario.


             <input id='inputscanf' type="text" value="" size=127 />




Nos agradaría también que el eco de un inputscanf quede separado del texto que viene a continuación.
Esto se arregla fácilmente, cambiando la llamada post(str + ' ':guiño: anterior por esta otra: post(str + '<br />':guiño:.
De ese modo, enviamos un salto de línea mediante el tag <br />.

¿Y no podríamos también dejar estos "ecos" más prolijos, agregando un salto de línea previo, o varios?

Como poder, se puede.
Pero dado que nos interesa imitar la consola estándar de Windows, vemos que esto no es necesario.
Incluso a veces tenemos situaciones como ésta:

Introduzca su año de nacimiento: 1977

En azul hemos marcado un mensaje publicado mediante printf, y con marrón el eco de los datos introducidos por el usuario mediante scanf.
Quisiéramos dejar abierta la posibilidad a que el programador pueda decidir colocar ambas informaciones en una misma línea en la browcon.



Por otra parte, ya es hora de que nos saquemos de encima el mensaje que dice: "

(Bloque para muestreo de datos con browcon_printf()...)"
.

Eso lo quitaremos del bloque divprintf, y punto.

Como resultado de esto, el bloque divprintf estará ahora inicialmente vacío.
Y sólo se irá rellenando a medida que le agreguemos texto al atributo innerHTML en sucesivas llamadas a post().



Por último, nos gustaría que visualmente se distingan un poco las publicaciones "normales" de aquellas producidas como el eco de la consola de entrada.

Pondremos en negritas y en color azul a los ecos.
Por ahora aceptaremos este formato, y más adelante veremos maneras de configurarlo a nuestro gusto.

Para lograrlo, hay que agregar el formato HTML mediante un tag span, con atributo color:DarkBlue, por ejemplo, y poniendo también un tag b, para activar las negritas.
Dentro de ese tag span ha de encerrarse el texto, pasado previamente en el parámetro str. La sentencia que logra el cometido es ésta:


                post('<span style="color:DarkBlue"><b>' + str + '</b></span>' + '<br />');


Hemos cambiado muchas veces esa línea, y por un largo rato la dejaremos así.
Pero podremos cambiarla en futuras versiones, para darnos gustos mayores.



Ya está.
Ahora podemos disfrutar de la versión 0.21 de browcon.
A continuación, el código y una foto:

Nombre de archivo: browcon00.21.html

Contenido:

Spoiler: (browcon00.21.html) (click para mostrar u ocultar)



En la imagen se ve que, aunque en la consola de entrada puse "Ingresando datos 1977", el casillero de texto quedó de inmediato en blanco tras aparecer el eco en pantalla.
También se aprecia que el eco resalta en azul y en negrita.


* browcon00.21.html (2.05 KB - descargado 60 veces.)
* browcon00.21.png (13.2 KB - descargado 192 veces.)
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #6 : 10/05/2013, 11:17:10 pm »

(00.22) Agregando un botón de información

Ciertamente que queda incómoda estar colgándole sentencias JavaScript al atributo onclick del botón "Get data...".

Para evitar esa desprolijidad, haremos allí una llamada a una sola función que llamaremos prepare_input(),
que se encargará de hacer los preparativos para que el usuario pueda entrar datos con el teclado.
Por ahora, esa función tendrá las únicas dos acciones que hasta ahora se activan clickeando el botón.

El botón ahora quedará definido así:


            <input type="button" value="Get data from divscanf" onclick="prepare_input();" />


Y la función prepare_input() dirá esto:


           function prepare_input() {
                document.getElementById('inputscanf').value = '';
                show_divscanf = true;           
           }


Así que, el funcionamiento será el mismo que antes, aunque con más protocolo de por medio.



Aprovechamos este momento de relax para agregar algún adorno a la browcon.
Le pondremos un botón extra en el bloque divcontrol, que muestre información de la versión actual de la browcon.

Para ello, definiremos una función showinfo(), que simplemente publicará en la consola de salida la información en cuestión.
Esta función será llamada al hacer click sobre el nuevo botón "Info".

Le pondremos un color marrón al texto, y también unos adecuados separadores en forma de línea horizontal,
que en HTML se generan con el tag hr.

El botón será éste:


             <input type="button" value="Info" onclick="show_info();" />


La función show_info() será así:


          function show_info() {
              var message = 
                 "<hr />"
                 + "<span style='color:brown'>"
                 + "<b>BrowCon</b><br />"
                 + "Version: " + version + "<br />"
                 + "Date: " + version_date + "<br />"
                 + "</span>"
                 + "<hr />";                                                                             
             
              post(message);
           }


He hecho uso de dos variables globales, con la intención de que esa información pueda modificarse fácilmente en futuras versiones, al principio del tag script.


           version = "00.22";
           version_date = "2013/May/10";




Pongo directamente el enlace a la descarga del archivo con la nueva versión modificada, y abajo una foto que tomé luego de clickear compulsivamente el botón "Info" varias veces:

browcon00.22.html


* browcon00.22.html (2.79 KB - descargado 66 veces.)
* browcon00.22.png (21.31 KB - descargado 193 veces.)
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #7 : 11/05/2013, 09:28:17 am »

(00.23) Haciendo scroll

Si experimentamos con la versión 00.22, veremos que las publicaciones se van acumulando por debajo, pero que seguimos viendo siempre las primeras, y sólo podemos ver los datos más "actualizados" sólo si manualmente hacemos scroll con la barra del lado derecho del navegador.

Queremos que nuestra browcon no sólo vaya mostrando los datos que le enviemos, sino que lo primero que veamos en pantalla sea lo último que enviamos.
También queremos tener acceso (en forma manual) a todo lo que se haya enviado previamente a la browcon.
Para ello, agregaremos al bloque divprintf una barra de scroll vertical, delimitaremos su espacio a unos 400 pixeles de alto en pantalla, y finalmente, al agregar nueva información, daremos la orden de que se produzca un scroll automático hasta la parte más inferior del bloque, empujando hacia arriba la información más antigua.

La consola de salida quedará ahora así:


        <div id="divprintf" style="height:400px; overflow-y:scroll">

        </div>


Los 400 píxeles de alto se han seleccionado, como claramente se ve, mediante el atributo style, y el subatributo height.
La barra de scroll vertical se ha colocado mediante el subatributo overflow-y, seleccionando la opción scroll.

Como uno puede inferir, el subatributo overflow-y se refiere a la acción que debe tomar el navegador cuando el bloque div debe mostrar más contenido que el le cabe normalmente por su tamaño.
Hemos seleccionado que esto se controle mediante una barra de scroll vertical bien visible.

Ahora bien, cada vez que invocamos la función post(), quisiéramos que el contenido antiguo sea empujado hacia arriba. Para esto, hay que modificar el atributo scrollTop del bloque divprintf, haciendo que tome el valor del atributo scrollHeight.
Con esto logramos que el tope superior del bloque se sitúe en la posición que coincide con la última línea, que, oh casualidad, es el valor de la altura total que actualmente tiene el bloque (contando toda la información contenida en él).

La funciòn post() se redefine, entonces, de la siguiente manera:


           function post(str) {
                document.getElementById('divprintf').innerHTML += str;
               
                var x = document.getElementById("divprintf");
                x.scrollTop = x.scrollHeight;
           }





Un detalle que me gustaría modificar en este mismo cambio de versión,
es la falta de "foco" cuando se hace visible la consola de entrada divscanf.
Creo que sería apropiado no sólo que se haga visible, sino que al mismo tiempo el cursor aparezca automáticamente allí, listo para que el usuario ingrese datos, y no como hasta ahora, que hay que ir hasta el casillero con el puntero del ratón y clickear allí para activarlo.

Para corregir este defecto, es necesario modificar el atributo de "foco" en la función Refresh_Console(),
justo después de la sentencia que hace visible la consola de entrada.
Agregamos la sentencia siguiente:


                   document.getElementById('inputscanf').focus();


La función luce ahora así:


           function RefreshBrowser() {   
             
              if (signal_divprintf) {
                   signal_divprintf = false;
                   post('Test divprintf. --- ');
              };
             
              if (show_divscanf) {                   
                   document.getElementById('divscanf').style.visibility = "visible";
                   document.getElementById('inputscanf').focus();
              } else {
                   document.getElementById('divscanf').style.visibility = "hidden";
              }
     
           }


Con esto, el cursor se posicionará ahora automáticamente en cada evento de entrada de datos.



Quitaré la información de la versión de los comentarios iniciales, y la pondré toda en la función show_info().
De ese modo no tendré luego que cambiar esta información en varios lugares distintos del documento.

Pero también me gustaría que dicha información sea lo primero que resalte al abrir el documento con un editor de texto, o un depurador.
Así que añadí una nueva sección script en la sección head del documento.
En esa sección pondré la función show_info() y las variables que informan de la versión de la browcon.



Archivo para descargar, y foto:

browcon00.23.html


* browcon00.23.html (3.06 KB - descargado 70 veces.)
* browcon00.23.png (36.34 KB - descargado 194 veces.)
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #8 : 11/05/2013, 12:08:34 pm »

(00.24) Fabulosos riesgos de seguridad

El modo en que estamos programando nuestra consola de entrada es muy riesgoso.
Básicamente, permite que el usuario tenga control del navegador, y puede llegar a hacer todo tipo de estragos.

Abrir el documento browcon00.23.html con Internet Explorer, y clickear en "Get data...".

En el casillero de entrada de datos, pegar el siguiente texto:

<form onmouseover="window.open('http://rinconmatematico.com','popUpWindow')">Te abro la página que yo quiero, jejejeje</form>

Al enviar el texto con Enter, se creará en nuestro documento un formulario con el texto "Te abro la página que quiero, jejejeje".
Y peor aún, moviendo el puntero del ratón sobre dicho texto, se nos abrirá una ventana del navegador con la web del rincón.

Esto puede hacerse con cualquier web, y prácticamente cualquier cosa.

He intentado ejecutar programas de Windows poniendo los comandos javascript adecuados, y aunque ActiveX lo permite, parece que hay restricciones de seguridad del propio Internet Explorer que lo impiden.
Pero creo que puedo burlar esas restricciones de seguridad...

Entonces aquí hay un agujero grave de seguridad, que tendremos que remediar.

En general, no es admisible que el usuario haga reaccionar un documento HTML, haciendo que lo convierta en un festín de acciones impredecibles y pintorescas.
(Pueden abrirse imágenes, cambiar formatos, enlazar a otras webs, ejecutar aplicaciones del sistema, etc.)



Para "remediar" esta simpática situación, vamos a desactivar  :llorando: todo tipo de reacciones en el texto ingresado por el usuario.

Esto se logrará reemplazando los caracteres "reactivos" en HTML (< > &) por sus códigos equivalentes "no reactivos": &lt; &gt; &amp;.
Definimos una función javascript llamada prevent_html(), que toma un parámetro str (una cadena de texto), y la transforma en una cadena "no reactiva", mediante el método replace().

Lo primero que se debe eliminar son los signos &, ya que en sucesivos reemplazos hay riesgo de mezclar apariciones originales de & con las que estamos introduciendo nosotros.  :sorprendido:


           function prevent_html(str) {
               return str.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
           }


Ahora usamos esa función cuando se llama a post() dentro de senddata():


         post('<span style="color:DarkBlue"><b>' + prevent_html(str) + '</b></span>' + '<br />');


Y listo, solucionado el problema.

Ahora adjuntamos la nueva versión, y mostramos una foto con el ejemplo que teníamos al principio (en que el usuario intenta entrar como cadena de texto el código que genera un formulario que a su vez abre casi automáticamente una página web).
Pero ahora se ha "neutralizado" todo intento dañino del usuario mediante la función prevent_html().

browcon00.24.html



 

* browcon00.24.html (3.22 KB - descargado 64 veces.)
* browcon00.24.png (20.06 KB - descargado 173 veces.)
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #9 : 11/05/2013, 04:09:21 pm »

(00.25) Reducción de autocolisiones

Dado que nuestra browcon funciona mediante "refrescos" (o mejor dicho, "rellamadas") a la función RefreshConsole() cada 3 segundos, y dado que esto abre la posibilidad a que varias instancias de esta función se vayan superponiendo, causando resultados inesperados e indeseables, me parece oportuno lidiar con esto ahora mismo, investigando el modo de solucionarlo.

Vamos a realizar varios tests para ver qué es lo que ocurre.
Esto implica agregar a nuestra browcon unas acciones superfluas, que no estarán presentes en la versión definitiva, pero que necesitamos ahora a fin de depurar el proyecto.

En primer lugar, vamos a definir una variable global que haga el recuento de cuántas instancias de la función RefreshConsole() se están ejecutando al mismo tiempo.
La llamaremos RCinst, y su valor inicial será, por supuesto, 0.

Cada vez que llamemos a RefreshConsole(), cada 3 religiosos segundos, según el cronómetro fijado al principio con setInterval(), haremos que se incremente en 1 el valor de RCinst, que entonces será un contador de instancias de RefreshConsole() activas.
A fin de que este contador muestre información fiable, tendrá que decrecer en 1 al finalizar todas las acciones en una llamada de RefreshConsole().

Además, mostraremos directamente el valor de la variable RCinst en la consola de salida, y por eso pondremos una llamada a post() informando ese valor.


           RCinst = 0;


           function RefreshBrowser() {   
             
              RCinst += 1;
              post('RCinst: '+RCinst.toString() + '<br />');
             
              // Aquí va el código que estaba en la versión 00.24...
             
              RCinst -= 1;
     
           }


Ahora bien.
Dado que las repeticiones se producen cada 3 segundos, es improbable que haya llamadas superpuestas a RefreshConsole().

Para que este fenómeno se ponga en evidencia, hay que reducir el tiempo entre repeticiones.

Hice la reducción a 1 milisegundo, y aún no hay colisiones visibles.
Así que las posibles autocolisiones de llamadas a RefreshConsole(), puede que se trate de un fenómeno esquivo.
Pero aún así lo tendré en cuenta.

Lo que voy a hacer acá es verificar si ya ha habido una llamada previa a RefreshConsole(),
y sólo si no hay instancias previas ejecutándose, ejecutaremos las sentencias que siguen en la función.

Entonces cambiaré las líneas anteriores, verificando solamente si hay o no una llamada a RefreshConsole() que está activa.
Reescribo la función así:


           RCactive = false;

           function RefreshBrowser() {   
             
                if (!RCactive) {
             
                        RCactive = true;
             
                        // Sentencias que estaban desde antes...               
                       
                        RCactive = false;
             
                 }
             
     
           }


Ahora bien.
No me parece que esto sea suficiente.

Hay dos maneras de interrumpir la paz de la browcon.
Una es con órdenes tipo printf (salida) y otra es con órdenes tipo scanf (entrada).

En cualquiera de los dos casos, que se activan de forma bastante independiente, queremos que RefreshConsole() no haga nada mientras esas tareas no han sido debidamente terminadas.

Agreguemos entonces otra variable más, llamada InputActive, que será true mientras el casillero de texto de divscanf está activo, y false en caso contrario.
Su valor inicial es false.

Se pone a true cuando se detecta una petición a la consola de entrada, y esto se efectuaría en la función prepare_input().

Debido a que nos interesa evitar las posibles autocolisiones tanto como sea posible,
no tenemos ningún apuro en poner InputActive a true.
Así, es una acción "demorada".
Por lo tanto, se coloca al final después de haber culminado las acciones de la función prepare_input(),
y más aún, después de haber puesto el foco (o el cursor) en el bloque divscanf.
Esto ha de ser así si queremos que se ejecute al menos 1 vez el bloque "if" asociado a la consola de entrada en RefreshConsole().

Finalmente, al final de las acciones en senddata() debe ponerse InputActive a false, indicando que ya no es necesario esperar a que el usuario introduzca datos en la consola de entrada.

Con este último cambio buscamos que no vuelva persistentemente el "foco" sobre la consola de entrada, y así, por ejemplo, podemos mover el ratón libremente por la página o fuera de ella, y así por ejemplo copiar y pegar porciones de texto en la consola de entrada en forma cómoda.

Al mismo tiempo, no se permite que la browcon acepte o emita datos mientras el casillero de texto esté visible.



Además, como ya me está empezando a cansar la lenta actualizaciòn cada 3 segundos, he cambiado a 500 milisegundos (0.5 segundos) la frecuencia del ciclo de "refresco".



Los cambios importantes se exhiben a continuación:


     <body onload="setInterval('RefreshBrowser()', 500);">
        <script type="text/javascript">
         
           RCactive = false; // RefreshConsole()'s is active or not 
           InputActive = false;     

          function senddata(str) {
                                         
                show_divscanf = false; 
                post('<span style="color:DarkBlue"><b>' + prevent_html(str) + '</b></span>' + '<br />');
                document.getElementById('inputscanf').value = '';

                InputActive = false;
           }
     
           function RefreshBrowser() {   
             
             if (!RCactive && !InputActive) {
             
                        RCactive = true;
             
                        if (signal_divprintf) {
                            signal_divprintf = false;
                            post('Test divprintf. --- ');
                        };
                       
                        if (show_divscanf) {                   
                            document.getElementById('divscanf').style.visibility = "visible";
                            document.getElementById('inputscanf').focus();
                 
                            InputActive = true;
                        } else {
                            document.getElementById('divscanf').style.visibility = "hidden";
                        }                 
                       
                        RCactive = false;
             
              }
           }
 


Archivo adjunto:

browcon00.25.html

* browcon00.25.html (3.81 KB - descargado 66 veces.)
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #10 : 11/05/2013, 08:31:38 pm »

(00.30) Procesando archivos con datos reales

Vamos a agregar aquí la capacidad de leer y escribir archivos de texto.

El modo más sencillo de interactuar con otras aplicaciones es tomando datos almacenados en archivos del disco rígido. Esto es así porque todos los lenguajes de programación incluyen algún modo de leer y escribir archivos.
En cambio, es más complicado intercambiar datos almacenados en la memoria, o en registros del sistema operativo, o variables del entorno.

Un modo rápido de intercambiar información entre C y JavaScript podría ser el uso de variables del entorno del sistema operativo.
Pero me he encontrado con dificultades. Por ejemplo, dichas variables se almacenan temporalmente en sesiones locales de cada programa, y no se pueden intercambiar.

Tras varias vueltas, me decidí por la lectura y escritura de archivos de texto en disco, que es una operación clara y segura, que tanto C como JavaScript pueden llevar a cabo... Eso sí, en JavaScript necesitamos la ayuda de la librería ActiveX, pues de otro modo es imposible que JavaScript realice acciones de lectura/escritura en disco.



Habiendo decidido esto, es natural definir funciones genéricas que se encarguen de leer el contenido completo de un archivo de texto y guardarlo en una cadena de texto, y también escribir una cadena de texto en un archivo.

Primero definimos una función auxiliar que pregunta si un determinado archivo está vacío.
En caso contrario, podremos leer su contenido, si no, devolvemos la cadena vacía.
Esto puede ser necesario de verificar en el caso de que, por ejemplo, estemos intentando leer un archivo que no existe.
Así, evitamos problemas en las operaciones de lectura.


            function file_isempty(filename) {
                return ((new ActiveXObject("Scripting.FileSystemObject")).GetFile(filename).Size == 0);
            }

            function ReadTextFile(filename) {
                var content = "";
                var f = (new ActiveXObject("Scripting.FileSystemObject")).OpenTextFile(filename, 1, true, false);
                if (!file_isempty(filename)) {
                     content = f.ReadAll();
                }
                f.Close();
             
                return content;
            }
           
            function WriteTextFile(filename, sData) {
                var f = (new ActiveXObject("Scripting.FileSystemObject")).CreateTextFile(filename, true, false);
                f.write(sData);
                f.Close();
            }


Las tres funciones toman como parámetro una cadena de texto filename que contendrá el nombre de archivo.
La función file_isempty() devuelve un valor booleano true, si el archivo está vacío, y false en otro caso.
La función ReadTextFile() lee el contenido completo de un archivo de texto en una cadena de texto, la cual retorna como resultado de la operación.
La función WriteTextFile() abre un archivo de texto con nombre filename, y le escribe encima los datos de la cadena de texto sData. Si había datos previamente en el archivo, éstos se pierden indefectiblemente.

En todos los casos se respeta la recomendación general, en todo lenguaje de programación, de cerrar los archivos una vez completadas las operaciones que se han hecho sobre ellos.
Esto se lleva a cabo mediante el método Close().



Ahora bien.
Necesitamos que sea el lenguaje C quien envíe la petición de escribir o leer archivos.
Pero como todavía no abordaremos esto, seguiremos con los botones que simulan peticiones ficticias de entrada/salida.

Pero ahora vamos a leer y escribir datos en archivos reales.

Primero que nada, decidamos en qué carpeta del disco rígido vamos a alojar estos archivos.
Lo haremos en la misma carpeta en que se encuentra la browcon.
No interesa cuál es esta carpeta, porque con JavaScript/ActiveX podemos obtener esta información,
y así no nos veremos obligados a modificar nuestro código cada vez que se nos ocurra cambiar de carpeta nuestro proyecto.

La ubicación de la página web (en nuestro caso, la browcon) puede obtenerse leyendo el atributo window.location.href.
Dado que la browcon la estamos alojando en nuestra computadora, aparecerá antepuesto a la dirección URL el prefijo 'file:///'.
Simplemente quitaremos esa parte de la dirección invocando el método replace('file:///','').
El resultado lo alojaremos en una variable temporal (tipo string) temp, y procuraremos dejar sólo el directorio base, quitándole el nombre del documento actual.
¿Cómo logramos esto?
Bueno, notemos que la dirección URL de nuestra browcon contiene todo el camino de carpetas separadas por '/' a las que es necesario acceder hasta llegar a la última, que contiene el archivo browcon00.30.html.

Así, buscamos la última aparición del caracter '/', y borramos lo que sea que haya a su derecha.
O bien, nos quedamos con todo lo que hay a su izquierda.
Las siguientes sentencias hacen el trabajo:


            temp = window.location.href.replace("file:///","");
            base_dir = temp.slice(0,1 + temp.lastIndexOf("/"));


El método slice() lo usamos para tomar la porción de la cadena de texto que nos interesa guardar.



Elegimos nombres para los archivos de texto que usaremos a modo de consolas de entrada y salida:


            browcon_output = base_dir + 'browcon.output.txt';
            browcon_input  = base_dir + 'browcon.input.txt';


Observamos que los archivos quedarán almacenados en la misma carpeta en que tenemos alojada la browcon, pues les hemos antepuesto dicha carpeta base al nombre de archivo.

Cuando la browcon reciba una señal de que debe imprimir datos en la consola de salida,
en vez de enviar la cadena test que usábamos hasta ahora, enviaremos el contenido completo del archivo de texto browcon_output.
Para esto, leemos el contenido del archivo con la función ReadTextFile(), y lo enviamos al bloque divprintf con la función post().

Toda esa explicación para una sentencia sencilla:


                        if (signal_divprintf) {
                            signal_divprintf = false;
                            post(ReadTextFile(browcon_output));
                        };


Vemos que sólo ha cambiado la tercer línea, la de post(...).



Cuando la browcon recibe una señal de petición de entrada de datos, sólo se hace visible el casillero de texto correspondiente, y nada más.
Se espera luego a que el usuario ingrese algún dato, y sólo cuando ha presionado Enter es que se llamaba a la función senddata().
Es en este momento, dentro de la función senddata(), que haremos el envío de datos al archivo browcon_input.

Lo haremos antes del respectivo eco que enviamos a la "pantalla".
Así sólo agregamos una línea de código, y la función senddata() queda ahora así:


           function senddata(str) {                                         
                show_divscanf = false;
               
                WriteTextFile(browcon_input, str);
                 
                post('<span style="color:DarkBlue"><b>' + prevent_html(str) + '</b></span>' + '<br />');
                document.getElementById('inputscanf').value = '';

                InputActive = false;
           }


El contenido del archivo browcon.output.txt tendremos que ponerlo a mano por ahora.
Pacientemente, abriendo un editor de texto, escribimos el texto que más nos guste.
Y tras clickear el botón "Send data..." de nuestra browcon, el contenido del archivo aparecerá en el navegador como por arte de magia.

Para que esto funcione siempre bien, hay que recordar "guardar" el archivo de texto en el editor.
Yo puse el siguiente texto en browcon.output.txt:


Esto es un texto cualquiera,
escrito en el archivo browcon.output.txt.
Esto tiene que visualizarse correctamente en la browcon,
tras recibir una petición de muestreo de datos.
También se puede enviar código HTML activo, por ejemplo:
¿Formateado? <span style='color:red'><b>¡ROJO CON NEGRITA!</b></span>


Tras presionar varias veces el botón "Send data...",
y tras enviar el texto "¡¡Hola mundo!!" mediante el botón "Get data...",
obtuve la imagen de la foto, y además el archivo browcon.input.txt fue modificado correctamente por la browcon para alojar el texto "¡¡Hola mundo!!":




Observamos que todo funciona muy bien, pero que en particular los "saltos de línea" son interpretados como un caracter " " (espacio en blanco).
Esto es así debido al modo particular en que HTML interpreta y acomoda los espacios en blanco.
Digamos que, básicamente, ignora los saltos de línea, y también ignora los blancos excesivos.

Para tener un control más directo sobre estos caracteres, tendremos que hilar más fino en nuestras habilidades de programación.
No es tanto un problema técnico (que puede resolverse fácilmente tanto desde C como desde JavaScript), como de decisión de diseño.
Lo discutiremos después.

(Por ahora, digo Fernández, lo importante es que ande).



Finalmente, adjuntamos la nueva versión de browcon, para descarga.

browcon00.30.html


* browcon00.30.html (5 KB - descargado 75 veces.)
* browcon00.30.png (54.2 KB - descargado 160 veces.)
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #11 : 12/05/2013, 01:26:58 am »

(00.31) Interacción manual con C

Ya es tiempo de que programemos un poco en C.

Como es natural, necesitaremos algunas funciones parecidas a las de entrada/salida que ya hemos definido en JavaScript.

Debido a que vamos a correr simultáneamente dos procesos, uno en JavaScript y otro en C, y ambos van a intentar leer o escribir sobre los mismos archivos, hay que anticiparse a este posible conflicto, tomando algunas precauciones.

(1ero) Cuidaremos que C y JavaScript no escriban datos en los mismos archivos.

Como la operación de escritura de un archivo cambia el contenido de éste, puede darse un resultado impredecible si hay más de un proceso poniendo información ahí.
Así que diseñaremos nuestras rutinas de manera que no exista esta superposición.
En particular, nos las tendremos que ingeniar para compartir información entre C y JavaScript, sin que ambos "se toquen".

(2do) Se tendrá especial cuidado en la apertura de archivos, para evitar conflictos.

Debido a que probablemente más de un proceso a la vez esté intentando acceder a un mismo archivo,
es probable que se produzcan constantemente errores al intentar abrir esos archivos en C.
Por eso es necesario tomar medidas profilácticas, asegurando que no se realizan operaciones de lectura/escritura de archivos hasta que C ha podido abrir el archivo sin problemas.

Puede ocurrir que C esté intentando escribir en un archivo que JavaScript está leyendo, o viceversa.
Esto no acarrearía problemas, creo yo, debido a que las operaciones de lectura "son inofensivas", por decirlo de algún modo.
Además, quisiéramos confiar en que el sistema operativo se encargará de gestionar las operaciones de lectura y escritura de manera que no superpongan de modos indeseables.

Pero no podemos jugarnos a que el sistema operativo gestiona todo bellamente a favor de nuestros deseos.
Vamos a tener que tomar precauciones extra para que no haya conflicto alguno entre C y JavaScript.



Pero todos esos problemas no aparecerán por ahora.
Así que sólo ocupémonos de las tareas más básicas de lectura/escritura de datos en C.

Primero, definimos funciones WriteTextFile() y ReadTextFile(), que se encargan de escribir datos, y de leer datos, respectivamente, de archivos dados, en forma de texto plano. El comportamiento de las funciones será el siguiente:


int WriteTextFile(char* filename, char* sData);


La función espera que le pasemos como parámetros un nombre de archivo de tipo char* (una string) en la variable filename, y un trozo de texto plano en forma de cadena de caracteres en la variable sData de tipo char*.
El valor de retorno será un número entero n, que será EOF si hubo algún error de escritura, o bien un número no negativo si todo salió perfecto.
Este valor de retorno es el mismo que enviaría la función fputs() de la librería estándar stdio.h.

char* ReadTextFile(char* filename, char* buffer);

La función espera que le pasemos como parámetros un nombre de archivo de tipo char* (una string) en la variable filename, y una variable char* en la que se almacenarán los datos leídos del archivo.
Dicha variable tiene que tener espacio suficiente para albergar al menos MAXLONGSTR caracteres,
que es una constante que inicialmente definiremos como igual a 1023.
El valor de retorno de la función será esta misma cadena de caracteres leída del archivo.
Este valor de retorno es el mismo que enviaría la función fgets() de la librería estándar stdio.h.

Para que todo esto marche bien, es necesario invocar ciertas funciones de la librería stdio.h.
Así que ya vamos escribiendo este código:


#include <stdio.h>

#define MAXLONGSTR 1023

int WriteTextFile(char* filename, char* sData) {
    FILE* F;
    while ( !(F = fopen(filename, "w")) )
      ;
    // F != NULL, file 'filename' open for writing
    int n = fputs(sData, F);
    while ( fclose(F) == EOF)
      ;
    return n;
}

char* ReadTextFile(char* filename, char* buffer) {
    // size(buffer) >= MAXLONGSTR
    FILE* F;
    while ( ! (F = fopen(filename, "r")) )
      ;
    // F != NULL, file 'filename' open for reading 
    fgets(buffer, MAXLONGSTR, F);
    while (fclose(F) == EOF)
      ;   
    return buffer;
}


Como puede observarse, en ambas funciones hemos puesto un bucle while que, mientras la apertura del archivo filename para escritura ("w") o lectura ("r") da el resultado NULL (que significa error al intentar el archivo), no hace nada, y procede a repetir el intento de apertura del archivo.

Lo mismo hicimos para fclose(), aunque esta vez hay que comparar con EOF.

A continuación se usan directamente las funciones estándar fputs y fgets.
Se cierra el archivo, y por último se retorna el valor que hayamos colectado en las llamadas a fputs o fgets, respectivamente.



Nos interesa mantener los mismos nombres de archivo para las consolas de entrada y salida que hemos establecido ya en nuestro código JavaScript. Así que definimos estos nombres como constantes al principio de todo:


#define BROWCON_OUTPUT_FILENAME "browcon.output.txt"
#define BROWCON_INPUT_FILENAME  "browcon.input.txt"


Además, necesitamos definir variables en las que almacenaremos los nombres de nuestros archivos, así como el directorio base de todos ellos:

char browcon_base_dir[MAXLONGSTR] = "";
char browcon_output_file[MAXLONGSTR] = "";
char browcon_input_file[MAXLONGSTR] = "";


Inicialmente tienen un valor "cadena vacía", pero luego serán redefinidos a los valores deseados.
¿Cuándo y cómo?
Lo haremos con una función encargada de inicializar valores y variables, y que más tarde nos puede servir para dar arranque a algunas otras operaciones.

Vamos a necesitar la librería string.h de C.

He aquí el código:


#include <string.h>

void browcon_start(char* base_dir) {
    strcpy(browcon_base_dir, base_dir);   
    strcat(strcpy(browcon_output_file, base_dir), BROWCON_OUTPUT_FILENAME);   
    strcat(strcpy(browcon_input_file, base_dir), BROWCON_INPUT_FILENAME);
}


Como se ve, a fin de inicializar el directorio base de la browcon, en el lado de C, habrá que pasarle dicho directorio base en forma explícita como parámetro a la función browcon_start().

Obtener alegremente este directorio base, como hicimos en JavaScript, no es tan fácil, a menos que usemos librerías especializadas para ello.
Estudiaremos varias alternativas más adelante.
Por ahora, indicaremos este directorio en forma manual, poniéndolo directamente en el código C.



Ya estamos en condiciones de imitar a las funciones puts() y gets() de la librería estándar stdio.h de C.
Definiremos funciones browcon_puts() y browcon_gets() que harán tareas análogas, pero esta vez escribiendo y leyendo datos de los archivos browcon.output.txt y browcon.input.txt.


int browcon_puts(char* s) {
    return WriteTextFile(browcon_output_file, s);
}

char* browcon_gets(char* s) {
    return ReadTextFile(browcon_input_file, s);
}


La función browcon_puts() toma los caracteres del parámetro s y los envía al archivo browcon_output_file,
mientras que la función browcon_gets() toma los caracteres del archivo browcon_input_file, y los almacena en s,
que tiene que tener espacio suficiente para albergar los datos.



Las funciones ReadTextFile() y browcon_gets() leen solamente la primer línea de texto del archivo, ignorando el resto.
Esto tiene sentido porque la consola de entrada será siempre una simple línea de texto, y nada más.
No obstante, siempre hay tiempo de modificar este comportamiento, si alguna vez hiciera falta.

Sin embargo, cabe notar aquí que el nombre ReadTextFile para la función es engañoso, porque sólo podremos leer, por ahora, la primer línea de texto del archivo.



Vamos a necesitar variables en las que almacenar temporalmente los datos leídos o escritos de la browcon.
También puede que nos haga falta una variable en la cual almacenar el número entero devuelto por las llamadas a fgets().

Serán variables globales (por ahora), definidas como:


char browcon_output[MAXLONGSTR] = "";
char browcon_input[MAXLONGSTR] = "";
int  browcon_errn = 0;




Por fin, vamos a definir unas macros que emulen el comportamiento de las funciones printf y scanf.
Se llamarán browcon_printf() y browcon_scanf().

La macro browcon_printf() se puede usar exactamente de la misma manera que printf(), aceptando un número variable de argumentos de cualquier tipo, y cuyos tipos son interpretados con la típica cadena de formato que todos los programadores de C conocen de memoria.
Además, retorna un valor entero, con el mismo significado que el de printf() (más precisamente, que el de fputs()).

De modo análogo, la macro browcon_scanf() se puede usar del mismo modo que scanf(), también con número variable de argumentos, y mediante las mismas vicisitudes y reglas que usaríamos para dar valores a variables mediante una llamada a scanf().
Su valor de retorno es análogo al de scanf() (más precisamente, al de sscanf()).

¿Cómo logramos esa magia?

Lo hacemos con la ayuda de las funciones sprintf() y sscanf(), que se encargan de tomar una cadena de texto, y tratarla como si fuera la consola de entrada/salida de datos.
Las macros tienen, pues, una sencilla definición:


#define browcon_printf(fmt, ...) ( \
    sprintf(browcon_output, (char*)fmt, __VA_ARGS__),    \
    browcon_errn = browcon_puts(browcon_output) \
    )


#define browcon_scanf(fmt, ...) \
    (browcon_errn = sscanf(browcon_gets(browcon_input), fmt, __VA_ARGS__))
 


La última macro está muy compacta, y tiene unos trucos extraños en C.
La función sscanf() requiere que su primer argumento sea un buffer en donde almacenar caracteres.
¿Le hemos pasado eso como primer parámetro?
De hecho, sí, porque la función browcon_gets() devuelve un puntero al primer caracter de una cadena de caracteres, y ése sería el buffer que vamos a utilizar.
Más aún, la dirección de memoria de ese puntero coincide con la de browcon_input.
Apuntan a la misma cadena.



Pongamos todo esto más o menos en orden.
Obtendremos nuestra primer librería en C para manejar nuestra browcon.
La llamaremos browcon00.31.h, aludiendo a la versión 00.31 de nuestro proyecto.

Lo ponemos en Spoiler:

Spoiler: (browcon00.31.h) (click para mostrar u ocultar)



El archivo browcon00.31.html será idéntico, por ahora, a la versión 00.30, a fin de no confundir al auditorio.
Igual lo incluimos aquí:

browcon00.31.html



Pero esta historia aún no acaba.
Tenemos que poner a prueba nuestra librería.
Lo haremos con un programa de prueba, llamado testbc01.c,
que intentará enviar y recibir datos desde los archivos de la browcon.


 
#include <stdio.h>
#include "browcon00.31.h"

int main(void) {
   
    printf("Estableciendo ruta y archivos...\nPresione Enter para continuar en cada paso.\n\n");

    browcon_start("C:\\programming\\C\\browcon\\");   
    getchar();   
   
    printf("Enviando...\n");         
    browcon_printf("Enviando una cadena de texto común y corriente.", "");   
    getchar();
   
    printf(" Ahora...\n");   
    browcon_printf(" Ahora enviamos datos formateados: %i %i %s %i.", 1, 22, "hello!", 333);   
    getchar();
 
    int a, b, c; char s[100];
    printf("(Llamada a scanf...)\n");
    getchar();
    browcon_scanf("%i %i %s %i", &a, &b, s, &c);   

    printf("Datos leídos: %i %i %s %i\n", a, b, s, c);   
    getchar();
}



Lo que hace nuestro programa es inicializar el directorio base y los archivos de la browcon, para entrada y salida.
Luego espera a que presionemos Enter para comenzar.
A continuación envía una cadena de texto.
Otra vez hay que presionar Enter.
Sigue otra cadena de texto, pero esta vez formateada para enviar datos de diversos tipos.
Presionar Enter.
Luego se definen unas variables en las que se alojarán valores, al leer datos desde la browcon.
Presionar Enter.
Se muestran los datos leídos.

Para que este experimento funcione, tenemos que poner mucho de nuestra parte, llevando un control manual de todas las operaciones.
Hagamos esto:

1. Abrir el archivo browcon00.31.html con Internet Explorer.
2. A cada pregunta de autorización que pida el navegador, decir que sí.
3. Compilar y correr el programa testbc01.c.
4. Presionar Enter.
5. En la consola estándar de C, se mostrará un mensaje que indica que la primer cadena se ha enviado.
6. Ir entonces al navegador, y clickear en "Send data...", lo cual simulará el envío de datos de C a JavaScript.
7. Los datos de esa cadena de caracteres aparecerán en la browcon (en el navegador).
8. Volver a la consola de C, y presionar Enter para que el programa continúe.
9. Aparecerá la cabecera del segundo envío de datos a la browcon.
10. Ir de nuevo al navegador, y hacer click otra vez en "Send data...".
11. El 2do envío de datos aparecerá ahora en el navegador.
12. Volver a la consola estándar de C, y presionar Enter para continuar.
13. Aparecerá el mensaje indicando que se hará una petición de datos a la browcon.
14. Aprovechar para ir al navegador, y efectuar esta petición de datos manualmente, clickeando el botón "Get data...".
15. Aparecerá el casillero para entrar datos.
16. Aquí hay que ser muy cuidadosos, porque el programa en C espera que le enviemos exactamente 4 datos, separados por espacios: el 1er dato es un número entero, el 2do dato otro número entero, el 3er dato es una palabra, y el 4to dato otro número entero. Entrar datos cualesquiera, respetando estas especificaciones.
17. Presionar Enter (en el navegador). Aparecerá el eco de los datos ingresados.
18. Volver a la consola de C, y presionar Enter para continuar.
19. El programa leerá los datos de la consola de entrada de la browcon.
20. Luego mostrará, en la consola estándar, los datos leídos.
21. Presionar Enter de nuevo para terminar.

Realizando estos pasos, podremos verificar si todo funciona bien.
Yo seguí esos pasos, y obtuve respectivamente, en el navegador, y en la consola estándar de C, lo que muestro en la foto:


* browcon00.31.html (5.04 KB - descargado 62 veces.)
* browcon00.31.png (32.08 KB - descargado 144 veces.)
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #12 : 12/05/2013, 02:04:18 am »

(00.32) Una pequeña mentira

En el mensaje anterior les dije una pequeña mentira.

El uso de browcon_printf() y browcon_scanf() no es "exactamente" el mismo que printf() y scanf().

Cuando printf() o scanf() toman un solo parámetro, es decir, el primero, que es la cadena de formato, el programa compila sin problemas.

Pero no sucede lo mismo con browcon_printf() y browcon_scanf().
Estas macros necesiten al menos un segundo parámetro.
Es por eso que en el programa testbc01.c, les puse como 2do parámetro una cadena en blanco: "".

Este es el precio que tendríamos que pagar a cambio de poder definir ambas macros de la manera sencilla y escueta en que lo hicimos.

El estándar C99 no permite quitar este parámetro extra en las macros con número variable de parámetros.

De paso, expliquemos un poco cómo es que las macros funcionan internamente.
Hemos usado el poder de las macros con número variable de parámetros, que le dan mucha potencia al lenguaje C,
permitiendo extenderlo del modo en que nosotros lo acabamos de hacer, redefiniendo funciones de la biblioteca estándar stdio.h.
En la cabecera de la macro, se indica con puntos suspensivos que allí se espera un número variable de argumentos.
Y luego, para poder referirnos a esa lista de argumentos, debemos usar el identificador __VA_ARGS__,
que instruye al preprocesador para que en ese lugar reemplace por la lista de argumentos pasados como parámetro a la macro.

Cuando se invocan este tipo de macros, hace falta que en __VA_ARGS__ "haya algo", no puede ser una lista vacía.
Y esto obliga a que exista al menos un parámetro.

¿Se puede remediar esto?
La verdad es que sí se puede, siempre y cuando no necesitemos realizar ninguna operación especial sobre la cadena de formato.
Tendremos que tener esto en cuenta para el futuro, y dar rodeos en caso de que algo nos obligue a operar con la cadena de formato.

La solución consiste en definir las macros sin argumentos explícitos, poniendo solamente puntos suspensivos.

El resultado será algo como esto:



#define browcon_printf(...) ( \
    sprintf(browcon_output, __VA_ARGS__),    \
    browcon_errn = browcon_puts(browcon_output) \
    )


#define browcon_scanf(...) \
    (browcon_errn = sscanf(browcon_gets(browcon_input), __VA_ARGS__))
   


Con este cambio, podemos ahora sí considerar a browcon_printf() y browcon_scanf() como análogas a las conocidas printf() y scanf().

Llamaremos versión 00.32 a la obtenida con esta pequeña modificación.

En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #13 : 12/05/2013, 02:59:52 pm »

(00.33) Ocultación de la interfase de C

Ahora queremos dar a nuestra librería en Ç un formato más compacto,
de manera que las variables globales que allí pusimos, queden ocultas al programador.
Queremos que el programador solicite en forma explícita el acceso a dichas variables, sin saber sus nombrres.

Una vez que las variables han sido obtenidos, el programador podrá cambiarle los valores a su antojo, por ejemplo.

La razón de ocultar los nombres de las variables es que no queremos saturar al programador con variables ya declaradas, que puedan entrar en conflicto con otras que él tenga previamente, ya sea en otras librerías, o en su propio programa.

Aún así, lo ideal sería impedir que el programador pueda cambiar su contenido.
Esta cuestión la abordaremos más adelante...



La "ocultación" de las variables no ofrece dificultad:
Se colocan las variables deseadas en el interior de una función, que llamaremos browcon().
Para que esas variables puedan accederse como tales, y para que sigan funcionando como antes, es decir, manteniendo su valor en forma accesible para cualquier otra función, sin que se pierdan datos en el camino,
tendremos que declararlas como static.

Consideremos por ejemplo el caso de la variable browcon_output.
Al declararla dentro de la función browcon(), logramos que sea "invisible" para el programador.
Al declararla como static, logramos que tanto su valor como la posición que ocupa en memoria, no cambien a lo largo de todo el programa.
A fin de recuperar el valor de esa variable, habrá que solicitarle a la función browcon() que nos dé acceso a ella.
Esto sólo se puede hacer si dentro de la misma función browcon() hay definido algún mecanismo que devuelva este dato.
Suponiendo que daremos un tal mecanismo, la devolución se hace mediante una sentencia return.

Dado que browcon_output es un array fijo de caracteres, su "valor" en realidad es una dirección de memoria.
Si fuese de tipo char* (puntero a char), esta dirección de memoria podría cambiarse, dando lugar a que el programador haga verdaderos estragos.
Esto lo evitamos definiendo la variable como de tipo char[], que es sutilmente distinto: es un dato compatible con char*, pero cuyo valor (dirección de memoria) no puede modificarse.

En términos técnicos, se dice en C que no es un lvalue, o sea, no puede ponerse del lado izquierdo de un operador de asignación.

Sin embargo, el valor de los caracteres individuales de la variable sí que pueden modificarse.
Ya veremos cómo sucede esto.



Definiremos una función browcon() que albergará variables con la información básica que queremos sostener a lo largo del programa.
Estas variables son las que teníamos antes, sólo que ahora las escondemos en la función:


char* browcon( /* parámetros... */ ) {
   
    static char browcon_output[MAXLONGSTR] = "";
    static char browcon_input[MAXLONGSTR] = "";
   
    static char browcon_base_dir[MAXLONGSTR] = "";
    static char browcon_output_file[MAXLONGSTR] = "";
    static char browcon_input_file[MAXLONGSTR] = "";

    // Sentencias de browcon()...
   
}


La función browcon() retornará cada una de esas variables, si se lo pedimos.
Para esto, usaremos parámetros que indican a la función qué tiene que hacer.
Un primer parámetro se usará para indicarle el tipo de solicitud, y un 2do parámetro servirá para elegir la variable sobre la cual se efectuará la solicitud, o bien para anexar información adicional.

Esto se llevará a cabo con una sintaxis (sencilla) que definiremos nosotros, dentro de la misma función browcon().
¡Estamos creando un mini-mini-lenguaje!

Las solicitudes se indicarán como a través de una cadena de texto, un parámetro char* que llamaremos request. El 2do parámetro será también de tipo char*, llamado param, y contendrá los detalles de la solicitud.

(Podría hacerse todo a través de un solo parámetro, con cierta sintaxis interna más compleja, pero esto complicará innecesariamente la definición de la función browcon()).

Haremos que la solicitud "get var" retorne una variable específica de las declaradas como static en el interior de browcon(). Así por ejemplo, para obtener la variable browcon_output, haríamos una solicitud como ésta:

char* cadena = browcon("get var", "browcon_output");

Así, la dirección de la variable cadena será ahora igual a la de browcon_output.
Esto nos da acceso a la variabla browcon_output, que estaba "oculta".
Y de paso vemos que es posible modificar su contenido.

Por ejemplo, para poner la cadena "Hola mundo!" en browcon_output, bastaría hacer ahora:


char* cadena = browcon("get var", "browcon_output");
strcpy(cadena, "Hola mundo!");


Si dentro de browcon() imprimiéramos el valor de browcon_output, en pantalla nos aparecería "Hola mundo!".
¡Hemos modificado el valor de una variable interna a la función, desde afuera de ella!
 :sonrisa_amplia:

Sin embargo, observemos que no es importante declarar una variable llamada cadena para poder modificar el contenido de browcon_output.
De hecho, para C, los caracteres individuales de browcon_output son lvalues, y por lo tanto pueden modificarse.

Podemos entonces lograr el mismo efecto escribiendo una sentencia más compacta como ésta:


strcpy(browcon("get var", "browcon_output"), "Hola mundo!");


A todos los efectos, podemos pensar que browcon("get var", "browcon_output") es la variable browcon_output.
La única diferencia es que no podemos escribir en nuestro programa en forma explícita el nombre de la variable, y tenemos que acceder a ella de esta forma "indirecta".

Pero funcionan igual a nivel de programación. Son lo mismo.

Otra cuestión aquí es que, dado que el manejo de estas variables lo hacemos nosotros, no es necesario que el programador sepa exactamente cuál es el valor exacto de la variable internamente.
Nosotros podemos darle un nombre cualquiera dentro de la función, y en cambio, que se identifique de modo distinto en los parámetros de la solicitud. O sea, en vez de pasar la cadena de caracteres "browcon_output" como parámetro, pasaríamos cualquier otra cosa, como "dame la variable esa que no recuerdo cómo se llamaba, pero que decía algo de OUT, creo...". La solicitud sería ahora:


browcon("get var", "dame la variable esa que no recuerdo cómo se llamaba, pero que decía algo de OUT, creo...")


Eso no sirve de mucho.
Lo que interesa es que la solicitud sea breve (para no cansar al programador de tanto tipear),
y que sea informativa (para que el programador tenga más o menos claro qué ocurre con los datos que está manipulando).
Me parece a mí que sería interesante que el programador recuerde o sepa que la variable browcon_output es de tipo char[].

Por brevedad, a la variable browcon_output la identificaremos, externamente, con un nombre como bout.
en realidad, esto no importa, pues lo que interesa es cómo se ejecuta la solicitud, y no los parámetros con que se la invoca.
Este último aspecto, más estético, es, como dijimos, para ayudar al programador.
Las cosas funcionan igual.

Así que nuestra función va tomando esta forma:


char* browcon(char* request, char* param) {

    static char browcon_output[MAXLONGSTR] = "";
    static char browcon_input[MAXLONGSTR] = "";
   
    static char browcon_base_dir[MAXLONGSTR] = "";
    static char browcon_output_file[MAXLONGSTR] = "";
    static char browcon_input_file[MAXLONGSTR] = "";
   
    if (strcmp(request, "get var") == 0)
                if (strcmp(param, "char bout[]") == 0)
                   return browcon_output;
                else if (strcmp(param, "char bin[]") == 0)
                   return browcon_input;
                else if (strcmp(param, "char basedir[]") == 0)
                   return browcon_base_dir;
                else if (strcmp(param, "char bfout[]") == 0)
                   return browcon_output_file;
                else if (strcmp(param, "char bfin[]") == 0)
                   return browcon_input_file;
                else
                   return NULL;
    else
       return NULL;
}


Como se ve, no hay ningún misterio en la función.
Si el primer parámetro es igual a "get var", se retorna alguna de las variables static "ocultas", según qué haya pasado como parámetro el programador.

Hemos hecho corresponder las solicitudes de la siguiente manera:

"char bout[]"  ----->  retorna browcon_output.
"char bin[]"  ----->  retorna browcon_input.
"char basedir[]"  ----->  retorna browcon_base_dir.
"char bfout[]"  ----->  retorna browcon_output_file.
"char bfin[]"  ----->  retorna browcon_input_file.

La "b" es por "browcon" y la "f" es por "file", cuando corresponda.

Como se ve, el programador no tendrá ni idea ahora de cuál es el verdadero nombre de las variables internamente en la función browcon(). Esto nos permite cambiar dichos nombres en el futuro, si nos place, sin cambiar el formato de la solicitud,
y asimismo, cambiar el formato de la solicitud sin tener que cambiar los nombres de las variables.



Nos está quedando afuera algo "colgada" la variable browcon_errn, que es de tipo int, y no encaja en el esquema anterior, porque las otras variables son de tipo char*.

En principio no hay problemas en tener una variable interna static int browcon_errn;
El problema está a la hora de querer tener acceso a esa variable desde el exterior,
porque esas solicitudes las estamos resolviendo a través de una devolución con return,
y el tipo del valor de retorno es siempre char[], no int.

Para que haya compatibilidad en el retorno, al menos browcon_err tiene que ser de tipo puntero o array.
Entonces, la definiremos más bien como un array de tipo int, así:


    static int  browcon_errn[1];


Es un array de tamaño "1", porque nos interesa alojar sólo un número entero.
No necesitábamos más que eso.
Para acceder al valor de ese número entero, ahora tendríamos que escribir browcon_err[0], o bien *browcon_err, como es usual.

Pero ahora tenemos el problema de que el valor de retorno de la función, cuando hagamos

return browcon_errn;

será un [b]int*[/b] en vez de un char*.

Para poder manejar retornos a distintos tipos de variables, vamos a cambiar el tipo de retorno a la forma genérica void* (puntero a void).
Este puntero genérico se usa, como sabemos, para albergar direcciones de memoria de datos del que no interesa saber su tipo.
Es el programador ahora quien tiene que ser muy cuidadoso a la hora de trabajar estos valores como si fueran de un tipo u otro.

Es la versión C del "polimorfismo": un mismo objeto se puede comportar de modos diferentes, según la situación.

Además, si bien no es estrictamente necesario, en las sentencias return haremos un cast a (void*) para dejar claro que estamos convirtiendo los char[] y los int[] a void*.

Luego, cuando el programador utilice estos valores de retorno, tiene que volver a hacer un cast al tipo que corresponde, por ejemplo a (char*) o a (int*).
Veamos cómo queda la versión modificada:


void* browcon(char* request, char* param) {
   
    static int  browcon_errn[1] = {0};
    static char browcon_output[MAXLONGSTR] = "";
    static char browcon_input[MAXLONGSTR] = "";
   
    static char browcon_base_dir[MAXLONGSTR] = "";
    static char browcon_output_file[MAXLONGSTR] = "";
    static char browcon_input_file[MAXLONGSTR] = "";
   
    if (strcmp(request, "get var") == 0)
                if (strcmp(param, "int*berrn") == 0)
                   return (void*)browcon_errn;
                else if (strcmp(param, "char*bout") == 0)
                   return (void*)browcon_output;
                else if (strcmp(param, "char*bin") == 0)
                   return (void*)browcon_input;
                else if (strcmp(param, "char*basedir") == 0)
                   return (void*)browcon_base_dir;
                else if (strcmp(param, "char*bfout") == 0)
                   return (void*)browcon_output_file;
                else if (strcmp(param, "char*bfin") == 0)
                   return (void*)browcon_input_file;
                else
                   return NULL;
    else
       return NULL;     
}



A todas las variables les hemos asignado un valor inicial.
Esa inicialización se hace una sola vez, en la primer ocasión que se llama a browcon().
El adjetivo static instruye para que no se vuelva a hacer esa inicialización en futuras llamadas a la función.

¿Cómo tenemos que hacer si queremos modificar el valor de browcon_err[0]?
Ahora se nos ha complicado la cuestión.
Una forma sería ésta:

int *n = (int*) browcon("get var", "berrn");
n[0] = 3;

O bien:

*n = 3;

Pero si por alguna razón no queremos definir variables, tendríamos que hacer esto:

((int*) browcon("get var", "berrn"))[0] = 3;

Allí accedemos al primer (y único) elemento del array browcon_errn,
y le ponemos el valor 3.
Para ello, previamente hemos tenido que convertir el valor de retorno de browcon(), que era (void*), al tipo (int*),
para poder asignarle un entero. Si no, podemos tener resultados indeseados.

También podemos acceder al 0-ésimo elemento del array a través del operador de indirección *, como de costumbre.
Aunque nos quedaría algo engorroso, así:

* ((int*) browcon("get var", "berrn")) = 3;

Y de hecho, ésta es la manera en que lo voy a utilizar.



La misma función browcon() será utilizada para fijar valores para las variables (al menos algunas de ellas).
Esto se hará con otros tipos de solicitud.
A fin de indicar que, por ejemplo, la variable browcon_base_dir tiene que tener la cadena "C:\\programming\\C\\browcon\\",
haríamos una llamada así:

browcon("set basedir", "C:\\programming\\C\\browcon\\");

En este tipo de solicitudes, no hace falta que browcon() retorne algún valor.
Podría retornar un puntero nulo (NULL), o bien podría retornar simplemente a la variable misma, para poder ser usada y aprovechada en llamadas anidadas con otras funciones.
Elegiré este último enfoque.

Además, agregaremos una solicitud "start", con la intención de inicializar de algún modo los objetos dentro de browcon().
Como esta inicialización se hace ya al declarar las variables, no hay por ahora nada especial que hacer.
Así que dicha solicitud no hará nada, y simplemente retornará NULL.
Pero en realidad sí que hace algo.
Si hacemos siempre una primera llamada a browcon() con la solicitud "start", por ser la 1er llamada a la función, los valores de las variables quedarán allí inicializados. Algo es algo.

No tengo claro ahora si las variables static se inicializan cuando arranca el programa, o sólo cuando se llama a la función por primera vez.  :¿eh?:
Me queda pendiente de tarea...



La función completa nos queda ahora así:


void* browcon(char* request, char* param) {
   
    static int  browcon_errn[1] = {0};
    static char browcon_output[MAXLONGSTR] = "";
    static char browcon_input[MAXLONGSTR] = "";
   
    static char browcon_base_dir[MAXLONGSTR] = "";
    static char browcon_output_file[MAXLONGSTR] = "";
    static char browcon_input_file[MAXLONGSTR] = "";
   
    if (strcmp(request,"start") == 0)
       return NULL;
    else if (strcmp(request, "get var") == 0)
                if (strcmp(param, "int berrn[]") == 0)
                   return (void*)browcon_errn;
                else if (strcmp(param, "char bout[]") == 0)
                   return (void*)browcon_output;
                else if (strcmp(param, "char bin[]") == 0)
                   return (void*)browcon_input;
                else if (strcmp(param, "char basedir[]") == 0)
                   return (void*)browcon_base_dir;
                else if (strcmp(param, "char bfout[]") == 0)
                   return (void*)browcon_output_file;
                else if (strcmp(param, "char bfin[]") == 0)
                   return (void*)browcon_input_file;
                else
                   return NULL;
    else if (strcmp(request, "set basedir") == 0)
       return (void*)strcpy(browcon_base_dir, param);
    else if (strcmp(request, "set bfout") == 0)
       return (void*)strcat(strcpy(browcon_output_file, browcon_base_dir), param);
    else if (strcmp(request, "set bfin") == 0)
       return (void*)strcat(strcpy(browcon_input_file, browcon_base_dir), param);
    else
       return NULL;     
}


Observemos que, por defecto, la función retorna NULL.
Es importante que siempre haya un valor de retorno concreto cuando se está trabajando con punteros,
porque si no pueden producirse accesos imprevistos a lugares indeseados de la memoria RAM, haciendo que el programa se bloquee.



Queremos ahora ocultar al programador también las operaciones de escritura y lectura de archivos.
Lo haremos con una nueva función "controladora y ocultadora" como la anterior.
Se llamará fbrowcon() (la "f" es por "file" = "archivo"), y actuará respondiendo a solicitudes, igual que hicimos con browcon():


#define BROWCON_OUTPUT_FILENAME "browcon.output.txt"
#define BROWCON_INPUT_FILENAME  "browcon.input.txt"

void* fbrowcon(char* request, char* filename, char* buffer) {
    if (strcmp(request,"start") == 0) {
        browcon("start",NULL);
        browcon("set basedir",  filename);
        browcon("set bfout", BROWCON_OUTPUT_FILENAME);
        browcon("set bfin",  BROWCON_INPUT_FILENAME);
        return NULL;   
    } else if (strcmp(request,"write plain text line") == 0) {
        FILE* F;   
        while ( !(F = fopen(filename, "w")) )
          ;
        // F != NULL, file 'filename' open for writing
        int* n_ = (int*)browcon("get var", "int berrn[]");
        *n_ = fputs(buffer, F);
        while (fclose(F) == EOF)
          ;
       
        return n_;
    } else if (strcmp(request,"read plain text line") == 0) {
        FILE* F;   
        // size(buffer) >= MAXLONGSTR
        while ( ! (F = fopen(filename, "r")) )
          ;
        // F != NULL, file 'filename' open for reading 
        fgets(buffer, MAXLONGSTR, F);
        while (fclose(F) == EOF)
          ;
       
        return (void*) buffer;
    } else
       return NULL;
}


El primer parámetro de fbrowcon() es de una cadena de texto, char* request, en la que se informa el tipo de solicitud.
Hemos definido las solicitudes "start", "write plain text line", "read plain text line".

El 2do parámetro se usa para informar el nombre de archivo sobre el que se procesará la solicitud.
En el caso de lectura/escritura de archivos, es claro que esto significa el nombre del archivo que leeremos o escribiremos.
Pero en el caso de la solicitud "start", lo que se espera en el parámetro char* filename es la ruta del directorio base en el que se va a trabajar.

Así, es importante que en nuestro programa llamemos a esta función al principio de todo, para inicializar correctamente dicha ruta.
Si no, nada funcionará bien.


El 3er parámetro se refiere a la información misma que se procesará en el archivo.
En el caso de una solicitud de escritura de datos, el parámetro char* buffer contendrá una cadena de caracteres que será enviada al archivo "console.output.txt".
Esta cadena puede desecharse luego de haber enviado los datos para escritura.

En el caso de una solicitud de lectura de datos, el parámetro char* buffer necesita recibir una variable externa con espacio suficiente para almacenar datos allí. Esta vez buffer no puede desecharse.

Finalmente, en el caso de una solicitud "start", no importa qué se haya pasado como parámetro en buffer,
pero lo recomendable (digo yo  :indeciso: ) en este caso es llamar a la función con ese parámetro puesto a NULL.

Notemos que, otra vez, el valor de retorno de fbrowcon() es de tipo void*,
y que esto nos obliga a hacer conversiones y desconversiones, según los casos, hacia/desde (char*) ó (int*).



Para mayor brevedad, voy a cambiar aquí los nombres de las funciones browcon_puts() y browcon_gets() a bcputs() y bcgets(), respectivamente.
Ahora estas funciones hacen solicitudes a fbrowcon(). Quedan así:


int bcputs(char* s) {
    return *((int*) fbrowcon("write plain text line", (char*)browcon("get var", "char bfout[]"), s));
}

char* bcgets(char* s) {
    return (char*) fbrowcon("read plain text line", (char*)browcon("get var", "char bfin[]"), s);
}


Observemos cómo ahora, mediante un cast *((int*) ...) hemos podido lograr el objetivo de enviar un humilde y sencillo int como valor de retorno de bcputs(), a fin de que actúe de forma análoga a la función estándar puts() de stdio.h.



Las macros ahora se llamarán bcprintf() y bcscanf(), para más brevedad.
Estas macros las hemos definido de manera que "retornen" un valor, que es un int en ambos casos,
con el mismo significado que el valor retornado por printf() y scanf().

Como toda la macro es una "expresión" que evalúa a un int, no podemos hacer ciertas declaraciones allí adentro.
En particular, no podemos declarar variables.

Y por eso nos vemos obligados a hacer manipulaciones extrañas con casts y con indirecciones.
El resultado es esto:


#define bcprintf(...) ( \
    sprintf(browcon("get var", "char bout[]"), __VA_ARGS__),    \
    *( (int*) browcon("get var", "int berrn[]") ) = bcputs(browcon("get var", "char bout[]")) \
    )


#define bcscanf(...) \
    (*((int*) browcon("get var", "int berrn[]") ) = sscanf(bcgets(browcon("get var", "char bin[]")), __VA_ARGS__))
   





Hemos completado las modificaciones, y esto constituye la versión 00.33 de la browcon.
De nuevo, no hacemos modificaciones a la parte de JavaScript.

El código completo, en spoiler:

Spoiler: (browcon00.33.h) (click para mostrar u ocultar)



Para testear esta librería, podemos hacerlo con un programa de testeo adecuado, el siguiente:


// testbc02.c

#include <stdio.h>
#include "browcon00.33.h"

int main(void) {
   
    printf("Estableciendo ruta y archivos...\nPresione Enter para continuar en cada paso.\n\n");

    fbrowcon("start","C:\\programming\\C\\browcon\\",NULL);   
    printf(browcon("get var","char basedir[]"));
    printf("\n"); 
    printf(browcon("get var","char bfout[]"));
    printf("\n"); 
    printf(browcon("get var","char bfin[]"));
    printf("\n"); 
   
    getchar();     
   
    printf("Enviando...\n");         
    bcprintf("Enviando una cadena de texto común y corriente.");   

   
    printf(" Ahora...\n");   
    bcprintf(" Ahora enviamos datos formateados: %i %i %s %i.", 1, 22, "hello!", 333);   
    getchar();
 
    int a, b, c; char s[100];
    printf("(Llamada a scanf...)\n");
    getchar();
    bcscanf("%i %i %s %i", &a, &b, s, &c);   

    printf("Datos leídos: %i %i %s %i\n", a, b, s, c);   
    getchar();
}






Notemos la llamada a fbrowcon("start", ...) para inicializar el directorio base antes de las operaciones.

En algún momento futuro, intentaremos que esa llamada sea innecesaria.

Lo más gracioso de todo esto... es que ¡funciona!

 :sonrisa_amplia:

'Ta lindo...
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #14 : 14/05/2013, 05:35:25 am »

Coordinación y comunicación entre procesos independientes

Aquí analizaremos el punto más delicado del proyecto browcon.
Tenemos dos procesos, uno en C y otro en JavaScript, que deben comunicarse entre sí, y tienen que estar coordinados.

En general la mecánica es que el programador, desde C, envía una sentencia bcprintf() o bcscanf(), y JavaScript recibe la orden y la procesa.
Para que esto funcione, C tiene que enviar varias informaciones a JavaScript: el tipo de operación solicitada (si es de escritura bcprintf() o de lectura bcscanf()), los datos a procesar (sólo en el caso de bcprintf(), que envía texto a imprimir en la browcon), y una señal de que todos los datos están correctamente preparados.
Esta "señal" es lo último que C tiene que enviar, porque si C envía la "señal" a JavaScript antes de tener los demás datos listos... es que no están listos aún, y la información que procesará JavaScript será, por supuesto, incompleta o errónea.

Cuando JavaScript recibe la "señal" de que debe actuar, puede comenzar a procesar los datos e interpretar la orden recibida, actuando de uno u otro modo en el navegador.

Mientras esto ocurre, el programa en C tiene que interrumpir su flujo normal de proceso, a la espera de que JavaScript termine de actuar, porque si no, si C continuara sin freno alguno, podría producir enseguida una nueva orden de lectura o escritura, que se sobrepondría a la anterior, confundiendo a JavaScript.
El resultado de esto, es que habría órdenes que quedarían sin procesar, o que se mezclarían.

Para que ninguna orden de lectura/escritura se pierda en el camino, C y JavaScript tienen que avisarse mutuamente de lo que van haciendo, y esperarse el uno al otro, como en una danza de procesos, a fin de ir coordinados.
Mientras uno de ellos realizar una tarea, el otro espera, y viceversa.

______________

Supongamos que nos interesa una orden de escritura, bcprintf().
En C ponemos bcprintf("Hola mundo"); , que escribe la orden "bcprintf()" en browcon.crequest.txt y "Hola mundo" en el archivo browcon.output.txt, y luego pone una señal de que todo está listo escribiendo un "1" en browcon.flagc.txt.

Aquí el C espera una respuesta de JavaScript, una señal de que ya ha terminado de escribir "Hola mundo" en el navegador.
Es JavaScript quien debe ahora generar una señal informando esto.

Nótese que no queda más remedio que informar mediante estas señales, porque ni C puede adivinar lo que ocurre en el navegador, ni JavaScript puede adivinar lo que ocurre en el flujo de ejecución de datos del programa en C.

Ahora JavaScript ingresa, cada 500 milisegundos, en el proceso RefreshConsole().
Ahí verifica si en los últimos 500 milisegundos se ha generado una señal "1" en el archivo browcon.flagc.txt.
Cuando la encontró, lee el archivo browcon.crequest.c para ver qué diablos quiere C.
Dado que ahí verá una orden "bcprintf", buscará datos leyendo el archivo browcon.output.txt.
Encuentra entonces el texto "Hola mundo", y lo imprime en el bloque divprintf en la ventana del navegador.
Ahí terminó de procesar la orden.
Entonces JavaScript envía una señal, poniendo "1" en browcon.flagj.txt, de que JavaScript está listo para recibir y procesar una nueva orden.

Mientras tanto C estaba a la espera. ¿De qué?
Bueno, C tiene que ir leyendo el archivo browcon.flagj.txt, y verificar que allí hay o no hay un "1".
Cuando lo encuentra, quiere decir que ya puede continuar con el flujo del programa, y quizás enviar a JavaScript una nueva orden de lectura/escritura.

____________________

Pareciera que esto tiene que funcionar, pero hay algunos inconvenientes en la lógica, que tienen que ver con los valores mismos de "flag" que usamos.
Veamos la situación con más detenimiento.

Tanto el "flagc" como el "flagj" pueden valer "0" ó "1", para indicar "aún no estoy listo" y "ya estoy listo", respectivamente.
Hemos dejado claro cuándo estos flags tienen que valer "1".
Pero, ¿cuál es el momento correcto para ponerlos a "0" otra vez?

Si dejáramos que valgan "1", sin cuidado, entonces, o bien JavaScript repetirá la misma orden varias veces, o bien C seguirá adelante sin freno alguno, sobreescribiendo órdenes, que JavaScript no tendría tiempo de procesar.

Además, los flags tienen que tener algún valor inicial. ¿Cuál conviene?

Bereshit ("En el principio...", Génesis 1.1) necesitamos que JavaScript esté listo para recibir órdenes, pero también queremos que JavaScript no haga nada hasta que C explícitamente diga que tiene una orden lista.

Así que el valor inicial de los flags debe ser:

flagc == "0"
flagj == "1"

Notará el lector cómo es que a estas alturas ya no me importa nada, y estoy tratando a los archivos como "variables".
Son como "variables" que pueden compartir los dos procesos C y JavaScript. Si no, ¿de qué otro modo puede compartir información?

Ahora C envía una orden bcprintf() y espera, mientras que JavaScript hace lo suyo.

¿Cómo es la espera de C? Tiene que preguntar, con una condición en un while, si JavaScript está listo o no lo está.
Mientras JavaScript "no está listo", C tiene que seguir esperando.
La sentencia sería "más o menos" así:

while (flagj == "0")
   ; // nada...

Sólo cuando JavaScript avise poniendo flagj = "1", allí C continúa.
Pero esto no funciona, porque flagj ya valía "1" en un principio, y así C continuará sin freno con el flujo del programa.

Así que, cuando JavaScript recibe la señal flagc == "1", indicando que debe procesar una nueva orden,
inmediatamente (rapidísimo), tiene que poner flagj = "0", para indicar que por un rato no estará disponible para nuevas órdenes, y que C debe esperar.
Ahora sí, al terminar su tarea, JavaScript enviará una señal poniendo flagj = "1".

El esquema sería así:

(1) JavaScript inicializa flagj = "1" e inicia el temporizador que busca órdenes de C cada 500 milisegundos.
(2) C inicializa flagc = "0"
(3) C elabora una orden bcprintf()
(4) C envía la señal flagc = "1" y se pone en espera de que flagj sea "1" de nuevo
(5) JavaScript detecta la señal y pone flagj = "0"
(6) JavaScript procesa la orden
(7) JavaScript envía la señal flagj = "1" indicando que terminó
(8) C detecta la señal flagj == "1" y continúa

A propósito escribí el esquema de pasos, para poner en evidencia que hay un obstáculo lógico entre los pasos (4) y (5).
Observemos que C tiene que enviar la señal primero, y que sólo después de esto JavaScript puede detectarla y cambiar en consecuencia el valor de flagj a "0".
Después de esto es que C tendría que ponerse en espera, preguntando si aún flagj == "0"...

Pero no hay manera de controlar el orden en que se hacen estas operaciones.
Luego de que C envía la señal flagc = "1", no hay manera de decirle que espere a JavaScript hasta que JavaScript detecte la señal, y tenga tiempo de ponera flagj = "0".
En ese breve lapso de tiempo, C tiene oportunidad de verificar si flagj == "0", y le dará falso, porque aún flagj no ha podido cambiarse a "0", sino que viene valiendo "1" desde "antes".

Sólo la suerte puede ayudarnos aquí, rezando para que el controlador de multitarea del sistema operativo elija darle prioridad al proceso de JavaScript, postergando al de C lo bastante como para que la primera vez que C pregunte en su bucle while por el valor de flagj, "vea" un valor "0".
Ciertamente, no podemos confiarnos a las oraciones, y tendremos que remediarlo con lógica de programación.

Aquí la idea es imitar un poco el proceso de comunicación por radio, en el que la gente se avisa si ha recibido o no el último mensaje.
Esto es como un "eco" del mensaje, cuando los radiohablantes dicen "copiado" o "recibido", y luego dicen "cambio", para dar lugar a que el otro hable.

Vamos a agregar nuevos flags, que sirvan para dar estos avisos en la comunicación.
En el archivo browcon.flagc.echoj.txt, el proceso JavaScript informará el "eco" de la señal recibida por parte de C,
y en browcon.flagj.echoc.txt, el proceso C informará el "eco" de la señal recibida por parte de JavaScript.
Brevemente diré flagc.echoj y flagj.echoc.

Lo bueno de estas nuevas "variables" o "flags", es que pueden inicializarse con valores distintos, y puede preguntarse por sus valores en momentos independientes a los de flagc y flagj.

Dado que, en el momento en que C envía la señal flagc = "1", nos interesa que C quede inmediatamente frenado,
necesitamos que C esté informado de que JavaScript aún no ha recibido la señal, y por lo tanto el eco de la misma tiene que ser inicialmente "0".
Así, ponemos flagc.echoj = "0" al inicio del proceso JavaScript.

Ahora JavaScript puede tomarse las cosas más relajadamente, porque sabe que C no hará nada hasta recibir el eco de la señal.
Una vez que JavaScript recibe la señal flagc = "1",
cambia el valor de flagj a "0", y sólo después, con total tranquilidad, envía el eco flagc.echoj = "1".
O sea, el envío del echo no tiene por qué ser inmediato.
Más bien tiene que ser "oportuno".

Ahora, cuando C reciba el eco de la señal, recién ahora es que comenzará a preguntar si flagj == "0".
En este momento, por el orden en que hemos hecho las cosas, C encontrará que flagj == "0" en las primeras iteraciones del bucle while.
Hemos conseguido coordinar ambos procesos satisfactoriamente.

Mas, ahora, se nos presenta el problema de que necesitamos que flagc.echoj valga de nuevo "0" antes de la siguiente llamada a bcprintf o bcscanf.
¿Cómo logramos que JavaScript cambie este valor en el momento menos inoportuno?

Bueno, tengamos en cuenta que ya el valor de flagc ha sido utilizado, y no necesitamos que siga valiendo "1".
De hecho, sería una buena oportunidad ponerlo a "0" una vez que JavaScript ya ha enviado el eco de la señal.
Así que ponemos flagc = "0".

Podríamos hacer que JavaScript espere todavía un poco más en este momento a C, a que cambie el valor del flag, y sólo después de esta breve espera es que procesaría, por fin, la orden bcprintf.
Esto funcionaría como un "eco del eco". ¡Por Dios!
Y es en este punto, en que el valor de flagc.echoj ya ha sido usado, y que C está detenido esperando a que flagj == "1", que tenemos tiempo, en JavaScript, de poner flagc.echoj = "0".

Veamos, entonces, el esquema de las operaciones.
En lo que sigue, voy a usar la abreviatura "J" para referirme a JavaScript.

(0) J inicializa flagj = "1", flagc.echoj = "0"
(1) J espera a que flagc == "1"
(2) C inicializa flagc = "0"
(3) C prepara una orden bcprintf
(4) C envía la señal flagc = "1"
(5) C espera a que flagc.echoj == "1"
(6) J detecta la señal flagc == "1"
(7) J cambia su estado a flagj = "0"
(8) J envía el eco de la señal flagc.echoj = "1"
(9) J espera el eco del eco, indicado con flagc == "0"
(10) C detecta el eco flagc.echoj == "1"
(11) C cambia el valor flagc = "0" (que sirve como eco del eco)
(12) C espera a que flagj == "1"
(13) J detecta el eco del eco flagc == "0"
(14) J procesa la petición bcprintf
(15) J envía la señal flagj = "1"

Bueno, esta historia no ha terminado.
Ahora que JavaScript es quien ha enviado su señal de "1", la recepción e intercambio de ecos con C tiene que repetirse como en los pasos (4) a (13), o al menos con las mismas precauciones, aunque ahora intercambiando los papeles.
Una vez que ambos procesos hayan coordinado bien sus pasos, podrán continuar cada uno por su lado, sin mayores novedades.

Lo importante es que, antes de terminar todas las operaciones, los estados de los flags tienen que volver a sus valores originales, para que todos los pasos funcionen correctamente en la próxima sentencia bcprintf o bcscanf.

Necesitamos, pues, un nuevo flag, que servirá para que C envíe sus ecos a JavaScript.
Será el archivo browcon.flacj.echoc.txt, que brevemente escribiré como flagj.echoc.

Veamos cómo sigue la historia:

(16) J reinicia flagc.flagj = "0"
(17) J espera a que flagj.echoc == "1"
(18) C detecta la señal flagj == "1"
(19) C envía el eco de la señal flagj.echoc = "1"
(20) C continúa con el flujo del programa (ya no le importa más nada de JavaScript)
(21) J detecta el eco flagj.echoc == "1"
(22) J continúa por su lado (ya no le importa más nada de C)

Aquí se nos presentan dos problemas adicionales.
Tenemos que inicializar adecuadamente el valor de flag.echoc, que lógicamente, tiene que ser "0" para que tenga sentido el paso (17). Así que modificamos el paso (2) así:

(2) C inicializa flagc = "0", flagj.echoc = "0"

Pero también tenemos que devolver el estado flagj.echo = "0" en algún momento entre los pasos (3) a (23).
¿Cuándo es esto oportuno?
Bueno, la respuesta es sencilla: en el mismo paso (2), sin más.

Lo que tenemos que tener en cuenta aquí es que los pasos (1) a (22) se repetirán siempre, en cada sentencia bcprintf o bcscanf.
Sólo el paso (0) es el que se ejecuta por primera y única vez, y depende de JavaScript, no de C.
Esto asegura que, en el paso (17), cuando JavaScript pregunte si flagj.echoc == "0" ó "1", el valor de ese flag estará correctamente inicializado a "0" previamente a ese momento.

Con esto, me parece a mí que la lógica del algoritmo bi-proceso funcionará correctamente.

En el próximo post vamos a implementar este algoritmo, y ver si realmente funciona, o si aparecen nuevas dificultades.

En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #15 : 14/05/2013, 01:53:01 pm »

Coordinación y comunicación entre procesos independientes (contiinuación)

Siguiendo la filosofía del post anterior, diseñemos ahora un algoritmo bi-proceso para manejar las órdenes bcscanf.

(0) J inicializa flagj = "1", flagc.echoj = "0"
(1) J espera a que flagc == "1"
(2) C inicializa flagc = "0"
(3) C prepara una orden bcscanf
(4) C envía la señal flagc = "1"
(5) C espera a que flagc.echoj == "1"
(6) J detecta la señal flagc == "1"
(7) J cambia su estado a flagj = "0"
(8) J envía el eco de la señal flagc.echoj = "1"
(9) J espera el eco del eco, indicado con flagc == "0"
(10) C detecta el eco flagc.echoj == "1"
(11) C cambia el valor flagc = "0" (que sirve como eco del eco)
(12) C espera a que flagj == "1"
(13) J detecta el eco del eco flagc == "0"
(14a) J procesa la petición bcscanf, haciendo visible el casillero de entrada de datos del formulario
(14b) J espera a que el usuario introduzca datos y los envíe a través del formulario
(14c) El usuario ingresa datos en el casillero de entrada y presiona Enter
(14d) J dispara la función senddata() de proceso del formulario
(14e) J envía el texto del formulario a browcon.input.txt
(14f) J envía el eco del texto del formulario a divprintf
(15) J envía la señal flagj = "1"
(16) J reinicia flagc.flagj = "0"
(17) J espera a que flagj.echoc == "1"
(18) C detecta la señal flagj == "1"
(18*) C busca datos en browcon.input.txt, los lee y procesa
(19) C envía el eco de la señal flagj.echoc = "1"
(20) C continúa con el flujo del programa (ya no le importa más nada de JavaScript)
(21) J detecta el eco flagj.echoc == "1"
(22) J continúa por su lado (ya no le importa más nada de C)

Notemos que, en este caso, hemos tenido que desarrollar en más detalle el paso (14), a fin de procesar correctamente la orden bcscanf.

En el paso (18*) tengo la duda de si conviene ponerlo ahí, o como paso (19*).
Al ponerlo entre (18) y (19) lo que ocurre es que se posterga el envío del eco desde C a JavaScript.
Eso sería como mentirle un poco a JavaScript, imponiendo una demora, para darnos tiempo en C a leer los datos de browcon.input.txt y procesarlos.
Pero podemos interpretar que el eco, en este caso, significa no sólo un "eco del flag", sino un "eco de toda la operación input".

Al poner la lectura de browcon.input.txt después de (19), sólo estaría enviando un eco de la señal.
Esto podría dar lugar a algún tipo de descoordinación entre los procesos... o no.
Veamos. Una vez que C envía este eco, se desentiende de JavaScript, y sigue su curso, hasta que una nueva sentencia bcprintf o bcscanf aparece.
En cambio JavaScript queda esperando hasta recibir este eco, y después recién se "libera", desentendiéndose de C.

Si ponemos la lectura de datos en (18*), como hicimos, las operaciones de envío del eco desde C, y de recepción de este mismo eco desde JavaScript, suceden en instantes de tiempo más cercanas, casi en simultáneo, y creo yo que es más conveniente lograr esta coordinación.
(Sobretodo, porque en futuras versiones el algoritmo puede llegar a agregar nuevas complicaciones, y mejor tener desde un principio una base sólida).


En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #16 : 14/05/2013, 04:00:46 pm »

Manejos del temporizador en JavaScript

Antes de proseguir, es importante analizar el temporizador en JavaScript.

La experiencia me ha indicado que no puedo usar bucles while en JavaScript a fin de "esperar" a que por ejemplo C envíe una señal, cambiando el contenido de un archivo.
Lo que me ha ocurrido es que JavaScript "no suelta" el procesador hasta que el ciclo while haya terminado.
Entonces no da tiempo a que C haga una porción de sus tareas.

En cambio, haciendo que JavaScript actúe cada 500 milisegundos, en el entretiempo el sistema operativo cede una ventana temporal para que C ejecute instrucciones.

De hecho, el problema que tenemos es más profundo, porque no sabemos a ciencia cierta (al menos yo no sé), cómo funciona la gestión de procesos en un sistema operativa multitarea, como es el caso de Windows.
Tenemos dos procesos, uno en C y otro en JavaScript, que corren "al mismo tiempo", pero en realidad lo que ocurre es que Windows va cediendo en forma organizada y más o menos equidistribuida, ventanas de tiempo para que cada proceso siga su curso en forma independiente.

Esto no nos asegura, entonces, que el C no adolezca del mismo problema con los ciclos while que hallamos en JavaScript.
Yo estoy suponiendo que estos problemas no se dan en la práctica...
Pero una manera más segura de desarrollar estos algoritmos biproceso sería, en mi opinión, evitar los bucles while que hemos colocado en las librerías, y tratar de usar también en C un "temporizador", tal como en JavaScript.



En fin, por ahora sólo nos interesa JavaScript.

El método setInterval() se usa, como hemos visto, para ejecutar una tarea cada un cierto intervalo fijo de tiempo.
Este intervalo de tiempo por ahora lo vamos a ajustar manualmente.
Quizás más adelante podamos investigar un ajuste automatizado y más fino. Será interesante...

El método setInterval tiene este formato de llamada:

setInterval(func(), ms);

en donde func() es una función o método JavaScript, y ms es un número que indica la cantidad de milisegundos de espera entre cada llamada a func().
Si ponemos

setInterval(RefreshConsole(), 500);

lograremos que JavaScript ejecute la función RefereshConsole() cada 500 milisegundos.



Una función importante es clearInterval(), que tiene la gran virtud de parar el molesto ciclo repetitivo.

El modo de uso es el siguiente:

Cuando se invoca el método setInterval(), a uno de los objetos de HTML (en nuestro caso, directamente a toda la ventana),
el resultado de la operación es un objeto quien es el encargado de quedar activo repitiendo la acción indicada cada ms milisegundos.
Ese objeto tendremos que cargarlo en una variable de JavaScript.
Cuando queramos suspender las repeticiones, llamamos al método clearInterval() de este último objeto.

Una vez cumplidas ciertas tareas, se puede volver a cargar el temporizador con setInterval(), si hiciera falta.

Así, es conveniente trabajar siempre con el par setInterval(), clearInterval(), en la medida de lo posible.
Un ejemplo:


tempobj = window.setInterval(RefreshConsole(), 500);

tareas varias
tareas varias
tareas varias

window.clearInterval(tempobj);

tempobj = window.setInterval(post("Hola Bart. <br />"), 1500);


Ahora tendríamos que separar las diversas tareas de (0) a (22), que corresponden a JavaScript, y planificar los procesos adecuadamente.

El paso (0) es de inicialización, y se debe hacer en el momento de carga de la página.

El paso (1), de "espera" de JavaScript, se realizaría con el temporizador.
De hecho, las únicas tareas asociadas al temporizador serán las de "espera", pues son las que reemplazan a las sentencias while, que no podemos utilizar...

Al mismo tiempo, hay que crear un objeto asociado al temporizador, y hacemos entonces esto:

temp1 = window.setInterval(Waiting1(), 500);

La función Waiting1() verificará si flagc sea igual a "1" o "0".

Una vez que flagc == "1", (paso (6)), en ella misma hacemos las siguientes acciones:

* Paramos el objeto temp1 mediante una llamada a window.clearInterval(temp1).
* Ponemos flagj = "0" (paso (7)).
* Ponemos flagc.echoj = "1" (paso (8)).
* Activamos una nueva espera, mediante:

temp2 = window.setInterval(Waiting2(), 500);

La función Waiting2() verifica cada 500 milisegundos si flagc == "0" (paso (9)).
Una vez que encuentra que flagc == "0", realiza estos pasos:

* Parar el temporizador temp2 mediante: window.clearInterval(temp2) (paso (13)).
* Verificar si la orden enviada por C es bcprintf o bcscanf.
* En caso de que la orden sea bcprintf, hace la llamada a post(), y luego continúa con el paso (15).

* En caso de que la orden sea bcscanf, activar el casillero de entrada de datos (divscanf.visibility = "visible") (paso (14a)).
*** Ahora la browcon no haría nada, ni siquiera verificar el temporizador cada 500 milisegundos. Sólo reaccionará cuando el usuario envíe una cadena de texto por el formulario.
*** Cuando esto ocurra, se abre senddata() (paso 14(d)).
*** Tras terminar todas las acciones (envío de datos a browcon.input.txt, paso (14e), y eco de datos hacia divprintf, paso (14f)), se continúa con el paso (15).

* El paso (15) se implementa con el paso de control a una función, que llamaremos middle_step(). No es muy explicativa, pero se encarga de poner flagj = "1", flagc.echoj = "0", y lanzar una nueva espera.
* Se lanza esta nueva espera mediante

temp3 = window.setInterval(Waiting3(), 500);

La función Waiting3() verificará cada 500 milisegundos si flagj.echoc == "1".

Una vez que esto ocurre, Waiting3() realiza estos pasos:

* Parar el temporizador temp3 mediante window.clearInterval(temp3).
* Iniciar un nueva espera mediante

temp4 = window.setInterval(Waiting4(), 500);

Esta vez, la función Waiting4() verifica (paso 21) si flagj.echoc == "1".
Una vez que esto ocurre, realiza estos pasos:

* Para el temporizador temp4 mediante window.clearInterval(temp4).
* Reinicia el ciclo de tareas desde el principio (o sea, yendo al paso (1), no al (0)), otra vez haciendo:

temp1 = window.setInterval(Waiting1(), 500);



En el momento en que esta llamada a setInterval() ocurre, en que se activa de nuevo Waiting1() cada 500 milisegundos, y asignado a la misma variable temp1 que al principio, nos encontramos conque el "estado" de los flags de JavaScript es el mismo que tenían al principio del programa.
Es como si todo hubiera comenzado desde cero otra vez.

Es decir, tenemos que asegurarnos que la lógica del algoritmo nos deja en el paso (22) siempre el siguiente estado de cosas:

flagj = "1", flagc.echoj = "0".
flagc = "0", flagj.echoc = "0"

Los flags que controla C, no podemos controlarlos completamente desde JavaScript.
Desde JavaScript tenemos que confiar que la contraparte de C está actuando con una lógica compartida, obedeciendo un algoritmo bi-proceso, en que C hace su parte.
Si se respetan los pasos (1) a (22), como antes, los flags controlados por C estarán de nuevo a "0" en el paso (22),
y entonces verdaderamente estaremos tal como al principio de todo.



Notemos también que ya hemos eliminado nuestra función RefreshConsole() en todo esto.
Habrá desaparecido, y ha sido reemplazada por sucesivos temporizadores, que a su vez están encadenados unos a otros:

temp1 = window.setInterval(Waiting1(), 500); // Al finalizar la espera,


Waiting2() {
   if (fin-de-la-espera)  {
        // tareas varias   

     temp3 = window.setInterval(Waiting3(), 500);
   }

}



Waiting3() {
   if (fin-de-la-espera)  {
        // tareas varias   

     temp4 = window.setInterval(Waiting4(), 500);
   }

}


Waiting4() {
   if (fin-de-la-espera)  {
        // tareas varias   

     temp1 = window.setInterval(Waiting1(), 500);
   }

}


Con esto, se genera un ciclo de llamadas Waiting1(), Waiting2(), Waiting3(), Waiting4(), y de nuevo Waiting1(), recomenzando el ciclo.



En cierto modo, lo que hemos hecho, es manejar el tránsito de dos calles que se cruzan, poniendo algo así como semáforos, o inspectores de tránsito.

Notemos que hay un momento, cuando se procesa una petición bcscanf, en que JavaScript no hace nada, ni siquiera una mísera verificación cada 500 milisegundos.
Estamos a merced del usuario, y su deseo o no de ingresar datos en el casillero de texto del formulario.

Si el usuario cierra el navegador, o si cancela la ejecución del programa en C, no hay algoritmo que valga.
No soy Dios para poder controlarlo todo.

De cualquier manera, hay buenas razones por las cuales el usuario pueda desear cerrar el navegador.
Por ejemplo, porque ha habido algún bloqueo de memoria, o porque algo no ha funcionado como esperaba, y desea reiniciar el programa.

Ocurre que es posible reiniciar fácilmente el programa en C, pero al hacer esto no se reinician automáticamente los flags de JavaScript, ni mucho menos lograremos que JavaScript se ponga en su "estado inicial".

Cuando queramos reiniciar la ejecución del programa en C, acompañado de un reinicio en el navegador, tendremos que hacer ambas cosas manualmente, siguiendo estos pasos:

* Cerrar el programa en C.
* Recargar la página con el botón de recarga del navegador.
* Volver a correr el programa en C.

Eso arreglará todo.

Inclusive, podemos agregar botones en nuestra browcon que explícitamente la reinicien a su estado inicial.



Otra observación que me gustaría hacer respecto el algoritmo explicado en estos últimos 3 posts, es que , si bien hemos necesitado usar varios flags para una sincronización efectiva y sin errores entre C y JavaScript, también ocurre que hemos podido solucionar la cuestión usando sólo 2 flags por cada una de ellos (2 para C y 2 para JavaScript).

Es importante tener en cuenta que los flags no tienen que multiplicarse indefinidamente, y que debemos lograr nuestros objetivos de efectividad y buena sincronización, usando la mínima cantidad posible de flags.
Hemos también combinado estos flags con 2 posibles acciones de muy distinta naturaleza: bcprintf y bcscanf.

Estos cuidados son importantes. Por un lado, hay pasos del algoritmo que son los mismos para ambos tipos de tareas, mientras que otros (como el 14 o el 18), no lo son.
Sin embargo, hemos procurado que el trabajo con los flags sea prácticamente el mismo en ambos casos, logrando así separar las tareas propiamente dichas de lectura/escritura, de las tareas de sincronización entre procesos.

Hemos respetado también el requisito de que C y JavaScript no puedan escribir sobre los mismos archivos, jamás, aunque sí puede que quieran acceder ambos a la vez a un mismo archivo, aunque mediante otras posibilidades (ambos leen, o uno escribe y el otro lee).
Esto lo logramos mediante el uso de "ecos de los flags". Su función es esa: la de evitar que C y JavaScript intenten escribir en el mismo archivo, al mismo tiempo.

En el próximo post pondremos el código propiamente dicho del algoritmo que hemos bosquejado hasta aquí.
En línea

Páginas: [1]   Ir Arriba
  Imprimir  
 
Ir a:  

Impulsado por MySQL Impulsado por PHP Powered by SMF 1.1.4 | SMF © 2006, Simple Machines LLC XHTML 1.0 válido! CSS válido!