Acerca de:

Este blog contiene los códigos, ejemplos y bases de datos que he usado cuando aprendía acerca de algún tema específico. En lugar de borrarlos (una vez dominado ya el tema), he decidido publicarlos :)

martes, 25 de junio de 2024

Tutorial viejito: Creando una dll en Borland C++ para luego usarla en Visual Basic 6

 Otro tutorial de los viejitos, de hace casi 20 años! Enjoy!

Visual Basic es un lenguaje de programación con claras ventajas: Es fácil y rápido de programar, cuenta con abundante documentación, recursos y herramientas gratuitos botados por toda la Internet, permite redimensionar arrays, jalar datos desde archivos en Office, se lleva bien con las Apis, etc, etc. 

Pero también tiene sus desventajas: sus aplicaciones tienden a ser un tanto más lentas y pesadas que las programadas en otros lenguajes, depende de librerías que no todo el mundo tiene, es un compilador propietario de Microsoft, no maneja sentencias en asm, no permite declarar variables binarias tipo word o byte y manipularlas bit por bit, etc, etc.  

Es decir: todas esas cosas que tiene el lenguaje C++ pero que le faltan al Visual Basic. No digo que uno sea mejor que el otro, sino que uno tiene lo que al otro le falta. Si tuviera que elegir, haría las GUIs de mis programas en Visual, pero la parte del manejo de arrays, bucles, cálculo matemático, lectura/escritura en el puerto paralelo, entre otras cosas más, se lo dejaría al C++. Al Visual Basic le dejaría las Apis y el puerto serial, además de intercambiar datos con archivos de Office o leer imágenes desde el disco duro. 

En realidad, existe una manera de combinar las bondades de ambos lenguajes: hacer las GUIs del programa en Visual Basic y ordenarle que jale las funciones que necesita desde Dlls hechas y compiladas en C++. El uso de Dlls tiene claras ventajas: si todo el código está metido en un exe éste será más pesado y, a la hora de ejecutarlo, se cargará todo en la memoria RAM. Pero si hace llamadas a Dlls, éstas sólo se cargarán en la RAM en el momento de ser usadas, lo cual liberará espacio en memoria y hará que el programa corra más rápido.  

1. Cómo hacer la Dll:

Primero hay que bajar el compilador gratuito que ofrece Borland.  Incluye un ide, pero este tutorial está hecho para la versión original sin ide. La carpeta Borland se debe copiar a la partición C:, por lo que la ruta del ejecutable que realiza la compilación (el bcc32.exe) queda en C:\Borland\BCC55\Bin\.

Luego se debe ir a la carpeta C:\Borland\BCC55\Lib\ y copiar todos los archivos (excepto la carpeta PSDK) a la carpeta Bin, donde está bcc32.exe. Esto es porque a la hora de compilar, el compilador busca los archivos obj de la carpeta Lib, si no los copiamos a la misma carpeta donde está bcc32.exe nos botará error. 

En la misma carpeta Bin, desde el block de notas, se debe crear el archivo que será nuestra Dll, debe tener extensión cpp y el nombre que llevará la dll. Yo la llamaré inicio.cpp. El código es:

// inicio.cpp

#include <windows.h>
#include "inicio.h"


BOOL WINAPI DllEntryPoint(HINSTANCE, DWORD, LPVOID)
{
return TRUE;
}

extern "C"
int __declspec (dllexport) WINAPI suma (int a, int b) {
return a+ b;
}

La primera función WINAPI es "el puntero de entrada" que necesita Visual Basic para poder usar la Dll. Borland C++ permite declarar esta función como "BOOL WINAPI dllMain(HINSTANCE, DWORD, LPVOID)", pero en este caso Visual Basic generará error al no hallar el puntero de entrada. (Hay que notar que no existe una función main o winMain en la dll)

De la forma que sea, esta función DllEntryPoint deberá retornar siempre el valor de TRUE o se generará error.  

La siguiente función es la que programé y hace una simple sumatoria de dos enteros. La sintaxis en general es:

extern "C"
tipo __declspec (dllexport) WINAPI nombre_funcion (argumentos) { ... }

Hay que observar que antes de "declspec" pueden ir uno o dos guiones largos. 

Se debe notar que el archivo "inicio.cpp" también llama a un archivo "inicio.h", para crear la dll ambos son necesarios. El código de "inicio.h" es:

#ifndef _INICIO_H
#define _INICIO_H

#ifdef BUILD_DLL // en la construcción de la librería
#define EXPORT __declspec(dllexport)
#else // en la construcción del ejecutable
#define EXPORT __declspec(dllimport)
#endif

#ifdef __cplusplus /* if in a C++ program */
extern "C"
#endif

int __declspec(dllexport) WINAPI suma(int a, int b);

#endif 

Lo importante aquí está en la penúltima línea de código: la declaración de la función que se "exporta" desde la dll y que devolverá los valores al programa principal. 

La Compilación:

Ambos archivos, "inicio.cpp" e "inicio.h" deberán estar en la misma carpeta donde está el ejecutable compilador, la carpeta deberá tener estos archivos:


Luego se va a Inicio->Ejecutar->cmd (para windows 2000 hacia arriba, para windows 98 es Inicio->Ejecutar->Command), nos situamos en la carpeta del compilador y se ingresa el siguiente comando (va todo seguido):


El compilador necesita varios parámetros para hacer lo que queremos, ahora los voy a explicar:

"-einicio": todo lo que va después de "-e" y sin espacios, será el nombre de la dll.

-Q: Es para que nos dé la información de la compilación y si se generó algún error.

-I: este comando, seguido de una ruta o varias rutas separadas por punto y coma sin espacios, le indica al compilador dónde buscar las cabeceras que se están usando. Como "windows.h" está en la carpeta Include, debemos decirle que busque allí o botará error. 

-WD: es el comando para generar una dll y no un exe. 

Después de todo esto, le decimos qué archivo se quiere compilar.

Para más información, recomiendo revisar el archivo de ayuda que viene con el compilador y que está en la carpeta Help.

Si todo sale bien, deberá aparecer:


Después de esto, la dll deberá aparecer en la carpeta donde tenemos los archivos "inicio¡.cpp" e "inicio.h" con el nombre de "inicio.dll". Esta dll ya podemos copiarla a donde la necesitemos para poder usarla (se recomienda ponerla en la carpeta system32).

 

Probando la dll:

Abrimos el visual con un proyecto Exe Estándar. Le añadimos un módulo .bas nuevo y le ponemos este código:

Public Declare Function suma Lib "inicio.dll" (ByVal a As Integer, ByVal b As Integer) As Integer

Dentro de la Dll está la función "suma" (pueden ser varias funciones, entonces tenemos que jalarlas una por una), con los dos argumentos tipo Entero. La función devuelve un entero, por ello se la declara "As Integer". Si la función en C++ fuera del tipo "void" no se pone al final el tipo de dato pues no devuelve ningún valor (incluso en algunas webs vi que las funciones tipo "void" de C++ eran llamadas como "Sub" en lugar de "Function" desde Visual Basic, pero esto no lo he probado).

En el formulario, el único código que puse para probar si devolvía un valor correcto fue:

Private Sub Form_Load()
    Dim c As Integer
    c = suma(8, 4)
    Form1.Caption = c
End Sub

Y lo que salió fue:

Ya funciona :)

Y ahora, una web donde explican un poco más esto de las Dlls generadas con el Borland C++:

http://www.zator.com/Cpp/E1_4_4b2a.htm

lunes, 29 de abril de 2024

El objeto RestTemplate devuelve nulo al testearlo con Mockito

Tuve el mismo problema que ponen en esta pregunta.

En la pregunta se tiene este método en una clase:

public CustomerResponse someMethod() {
    CustomerResponse response = restTemplate.exchange(url, 
    HttpMethod.GET, null, CustomerRes.class).getBody();
    return response;
}

Y se testea con este código:

public void checkResponseIsNotNull() {
    CustomerResponse customerResponseMock = 
    mock(CustomerResponse.class);
    when(restTemplate.exchange(url, HttpMethod.GET, null
    CustomerResponse.class).getBody()).
    thenReturn(customerResponseMock);
    CustomerResponse cr = service.someMethod();
    Assert.assertNotNull(cr);
}

Lo malo es que el objeto restTemplate devuelve nulo al llamar a su método exchange(), luego comprobé que también devuelve nulo en el caso de que se llame a su método postForEntity(). En otros casos el objeto restTemplate devuelve un response nulo.

La solución está aquí. Como escribí en mi comentario, para "mockear" un objeto restTemplate se deben cumplir dos condiciones: 

1. Los parámetros que se le pasan al mock del objeto restTemplate deben ser del mismo tipo que los que se le pasan a sus funciones exchange() o postForEntity().

2. Se debe mockear también el objeto response.

Un código de ejemplo está a continuación:

Class MyClass{
    RestTemplate restTemplate;
    ... some other code...
    
    MyClass(RestTemplate restTemplate){
        this.restTemplate = restTemplate;
    }
    
    public
MyResponseClass funcion(Uri uripath, SomeClass clase) {

    ... add some headers ...

HttpEntity<SomeClass> requestEntity = new HttpEntity<>(clase, headers);

    ResponseEntity<MyResponseClass> responseEntity = this.restTemplate.postForEntity(uripath, requestEntity, MyResponseClass.class);
        return responseEntity.getBody();
    }
}


Test:

@Mock
private RestTemplate restTemplate;

@Test
void Testit{

    SomeClass clase = new SomeClass();
    
    MyResponseClass response = new MyResponseClass();
    response.setValue("OMG");
    ResponseEntity reMock = mock(ResponseEntity.class);
    
    when(reMock.getBody()).thenReturn(response);
    when(this.restTemplate.postForEntity(any(URI.class), any(RequestEntity.class), eq(MyResponseClass.class))).thenReturn(reMock);

    MyClass otherClass = new MyClass(restTemplate);
    MyResponseClass finalResponse = otherClass.funcion(someuri, clase);
    assertThat(finalResponse, is(notNullValue()));
    assertThat(
finalResponse.getValue(), is("OMG"));
}


lunes, 26 de febrero de 2024

La diferencia entre un ejercicio de regresión y uno de clasificación con Tensorflow

 Estaba revisando estos dos códigos:
The Hello World of Deep Learning
Beyond Hello World: A computer Vision Example


El tutorial no lo dice, al menos no con la claridad que yo desearía, pero el primero es un problema de regresión y el otro es un problema de clasificación.

En el caso de la regresión, Tensorflow predice un valor en función a una entrada. En el caso de un problema de clasificación, Tensorflow devuelve el índice con mayor probabilidad de corresponder a la etiqueta (o clasificación) de una entrada.

Ambos códigos son casi idénticos, voy a copiar debajo los que tengo en mi máquina y con los que he estado jugando:

Éste es el de regresión:

import tensorflow as tf
import numpy as np
from tensorflow import keras

# Build a simple Sequential model
model = tf.keras.Sequential([keras.layers.Dense(units=1, input_shape=[1])])
# Compile the model
model.compile(optimizer='sgd', loss='mean_squared_error')
 
# Declare model inputs and outputs for training
xs = np.array([-1.0,  0.0, 1.0, 2.0, 3.0, 4.0], dtype=float)
ys = np.array([-3.0, -1.0, 1.0, 3.0, 5.0, 7.0], dtype=float)
 
# Train the model
model.fit(xs, ys, epochs=100)

# Make a prediction
print(model.predict([10.0]))
 

Éste es el código del problema de clasificación:

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
 
 
# Load the training and test split of the Fashion MNIST dataset
(training_images, training_labels), (test_images, test_labels) = fmnist.load_data()

print(len(training_images))
print(len(test_images))
print(training_labels[0:10])
 

# You can put between 0 to 59999 here
index = 562

# Set number of characters per row when printing
np.set_printoptions(linewidth=128)

# Print the label and image
print(f'LABEL: {training_labels[index]}')
print(f'\nIMAGE PIXEL ARRAY:\n {training_images[index]}')

# Visualize the image
plt.imshow(training_images[index])

# Normalize the pixel values of the train and test images
training_images  = training_images / 255.0
test_images = test_images / 255.0
 
# Build the classification model
model = tf.keras.models.Sequential([tf.keras.layers.Flatten(),
                                    tf.keras.layers.Dense(256, activation=tf.nn.relu),
                                    tf.keras.layers.Dense(10, activation=tf.nn.softmax)])
 

model.compile(optimizer = tf.optimizers.Adam(),
              loss = 'sparse_categorical_crossentropy',
              metrics=['accuracy'])

model.fit(training_images, training_labels, epochs=15)
 
# Evaluate the model on unseen data
model.evaluate(test_images, test_labels)


Aparte de las diferencias al construir la red neuronal o al predecir o evaluar un valor, o la data que se está usando, la diferencia principal es la función de pérdida o Loss Function, ésta es la que define si la red neuronal que estamos construyendo se usará para clasificar o predecir. Durante el entrenamiento de la red, esta función mide cuánto se están acercando los valores calculador por la red a los valores verdaderos (usualmente usando el cuadrado de la diferencia entre ambos valores, pero hay funciones más complejas), esta información se usa en el siguiente bucle para ajustar los parámetros de la red y disminuir el valor de la función de pérdida. La red neuronal hace estos ajustes usando otra función, la de optimización, usualmente la del gradiente descendente. En el caso de las funciones cóncavas, cuando su gradiente se hace cero, se ha alcanzado el mínimo. Lo que hace la función de optimización es minimizar a la de pérdida usando este principio.

Una lista de distintas funciones de pérdida está en este enlace.

miércoles, 20 de diciembre de 2023

Entrada antigua: Acerca de la Recursividad

Este es un artículo que escribí en mi vieja web allá por los dosmiles (¿2007?) y me ha traído nostalgia: es de cuando batallaba intentando entender la recursividad.

Se me hace curioso leerme después de tantos años, He recordado cuando, debugueando, intentaba infructuosamente entender línea por línea cómo iba eso de la recursividad, y de cómo sabía el procesador, la máquina virtual o el runtime, cómo volver a llamar a la función para llegar a la respuesta correcta.

He terminado sonriendo, pues éste fue el inicio de lo que luego sería esto: http://programacionamartillazos.blogspot.com/2023/06/cuadrados-recursivos-con-la-tortuga-de.html

o ésto: https://programacionamartillazos.blogspot.com/2016/01/listar-los-archivo-en-una-carpeta-y-sus.html

Aquí el artículo, Enjoy!

 

"Para entender la recursividad, primero hay que entender la recursividad"

 

En mis ratos libres me gusta leer blogs sobre programación. Así me topé con http://www.variablenotfound.com/2007/02/dnde-se-han-ido-los-programadores.html y con http://www.codinghorror.com/blog/archives/000781.html donde dice "If you can successfully write a loop that goes from 1 to 10 in every language on your resume, can do simple arithmetic without a calculator, and can use recursion to solve a real problem, you’re already ahead of the pack!".

Entonces me faltaba la recursión o recursividad. Ya había leído sobre ella antes, incluso bajado tutoriales, pero mi error fue intentar entenderla.
Como es una función que se llama a sí misma, no hay por dónde "cogerla", lógicamente es algo autocontenido y que debería generar un bucle infinito:

El límite, o parada, sucede cuando "i" llega a 10. Si "i" es igual o mayor a 10 (partiendo de un valor inferior), simplemente sale de la función y retorna el resultado de la suma total. No se debe perder tiempo intentando entender la recursión basta saberla usar. Si se intenta entender o hallar la secuencia lógica de la función de arriba no se llegará a ninguna parte. La variable "i" recibe la respuesta de una función donde está la propia variable "i" (y lo peor: en este caso la operación que debe realizar la función sumaR está donde recibe los parámetros).

¿hay alguna manera de escapar de este bucle infernal?
Sí, en el momento en que "i" llega al valor donde se debe retornar cero (ya no se llama a la función sumaR).

Recién llegué a comprender la recursión cuando dejé de intentar comprenderla. Suena raro, pero así fue.

jueves, 9 de noviembre de 2023

Hilos con C#, corregido

Continuando con mi entrada anterior, el detalle que hay que corregir es el siguiente: no se puede modificar la interfaz gráfica desde otro hilo. Las interfaces gráficas de .Net y Java no son thread-safe, incluso estos frameworks arrojarán una excepción si detectan que se está intentando modificar la interfaz gráfica desde otro hilo que no haya sido el que la creó.

Como ejemplo, voy a añadir un par de líneas al final de las funciones Hilo1() e Hilo2() en el código que dejé como ejemplo:






En muchas aplicaciones que modifican la interfaz gráfica es necesario llamar a las funciones Refresh() de los controles. Después de este cambio, la aplicación arroja esta excepción:



La manera correcta, en C#, de modificar la interfaz gráfica desde otro hilo es usando las funciones Invoke() o BeginInvoke() de la plataforma .Net. 

Por otro lado, es una mala práctica llamar a la función Abort() de un hilo. Si se desea que un hilo finalice junto a la aplicación que lo creó se debe declarar como hilo de background, eso se hace en una línea de código:

p2.IsBackground = true;

El código corregido es:

using System;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

using System.Threading; 


namespace WindowsApplication1

{

    public partial class Form1 : Form

    {

        Thread p1;

        Thread p2;

        byte r, g;

        bool b1, b2;

        

        public Form1()

        {

            InitializeComponent();

        }


        private void Form1_Load(object sender, EventArgs e)

        {

            r = 0; g = 255; b1 = false; b2 = true; 


            p1 = new Thread(new ThreadStart(Hilo1));

            p2 = new Thread(new ThreadStart(Hilo2));


            p1.IsBackground = true;

            p2.IsBackground = true;


            p1.Start();

            p2.Start(); 

        }

        

        public void Hilo1() {

            while (true)

            {


                Thread.Sleep(10);


                if (r >= 0 && r <= 255 && b1 == false)

                {

                    r++;

                    if (r == 255)

                        b1 = true;

                }


                if (r >= 0 && r <= 255 && b1 == true)

                {

                    r--;

                    if (r == 0)

                        b1 = false;

                }


                if (this.InvokeRequired)

                {

                    this.BeginInvoke((Action)(() =>

                    {

                        pictureBox1.BackColor = Color.FromArgb(r, 80, 100);

                        pictureBox1.Refresh();

                    }));

                }

            }

       }


        public void Hilo2() {

            while (true)

            {

                Thread.Sleep(10);


                if (g >= 0 && g <= 255 && b2 == false)

                {

                    g++;


                    if (g == 255)

                        b2 = true;

                }


                if (g >= 0 && g <= 255 && b2 == true)

                {

                    g--;


                    if (g == 0)

                        b2 = false;

                }


                if (this.InvokeRequired)

                {

                    this.BeginInvoke((Action)(() =>

                    {

                        pictureBox2.BackColor = Color.FromArgb(100, g, 80);

                        pictureBox2.Refresh();

                    }));

                }

            }

        }


        private void Form1_FormClosed(object sender, FormClosedEventArgs e)

        {

        }

    }

}

 

El nuevo ejemplo se puede descargar de aquí.

viernes, 29 de septiembre de 2023

Hilos con C# (C Sharp)

Otro tutorial viejito, muy viejito, hay un detalle qué arreglar en el código, pero eso lo voy a explicar en una siguiente entrada:

¡Hilos! Visual Basic 6 no los permite, y programarlos en Borland C++ es muy engorroso (personalmente odio tener que estar programando archivos .h y el Borland los necesita para poder usar los hilos). En este tutorial voy a usar C#.

Para el programita que voy a mostrar a continuación hay que abrir el Visual C# (yo uso el 2005), crear un nuevo proyecto tipo Windows Application y en el formulario que aparece, ponerle dos Pictureboxes: pictureBox1 y pictureBox2. Al final, debe verse así:

 

Y luego se le mete este código:

using System;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

using System.Threading;

 

namespace WindowsApplication1

{

    public partial class Form1 : Form

    {

        Thread p1;

        Thread p2;

        byte r, g;

        bool b1, b2;

       

        public Form1()

        {

            InitializeComponent();

        }

 

        private void Form1_Load(object sender, EventArgs e)

        {

            r = 0; g = 255; b1 = false; b2 = true;

            p1 = new Thread(new ThreadStart(Hilo1));

            p2 = new Thread(new ThreadStart(Hilo2));

            p1.Start();

            p2.Start();

        }

       

        public void Hilo1() {

            while (true)

            {

                Thread.Sleep(10);

 

                if (r >= 0 && r <= 255 && b1 == false)

                {

                    r++;

                    if (r == 255)

                        b1 = true;                   

                }

 

                if (r >= 0 && r <= 255 && b1 == true)

                {

                    r--;

                    if (r == 0)

                        b1 = false;

                }

                pictureBox1.BackColor = Color.FromArgb(r, 80, 100);

            }

       }

 

        public void Hilo2() {

            while (true)

            {

                Thread.Sleep(10);

 

                if (g >= 0 && g <= 255 && b2 == false)

                {

                    g++;

 

                    if (g == 255)

                        b2 = true;

                }

 

                if (g >= 0 && g <= 255 && b2 == true)

                {

                    g--;

 

                    if (g == 0)

                        b2 = false;

                }     

                pictureBox2.BackColor = Color.FromArgb(100, g, 80); 

            }                  

        }

 

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)

        {

            p1.Abort();

            p2.Abort();

        }

    }

}

 

Lo que se debe entender primero es que existen dos hilos: "p1" y "p2" (o dos objetos de la clase "Thread", en inglés "Hilo") cada uno asociado a un método: A "p1" le corresponde "Hilo1" y a "p2" le corresponde "Hilo2". Lo que "p1" y "p2" permiten es que ambos métodos (Hilo1 e Hilo2) se ejecuten de manera simultánea.

Todas las otras variables en el programa son de control. Los hilos empiezan a ejecutarse al llamar al método "Start" y se terminan con "Abort". El C# no permite llamar de nuevo a "Start" para un hilo que ya se está ejecutando.

Lo que hace el programa simplemente es variar el color de ambos PictureBoxes en un bucle que jamás termina, para ello se usa el comando CualquierPictureBox.BackColor = Color.FromArgb(100, g, 80);

También uso el método Thread.Sleep que detiene la ejecución del hilo durante el tiempo en milisegundos que va entre paréntesis. Esto es para que los colores varíen despacio y se puedan apreciar.

Cada vez que dentro de un método se usa la palabra "Thread" ésta se refiere al hilo que está asociado a él.

Una buena página para aprender hilos en C# es ésta: https://www.albahari.com/

Y acá se puede descargar mi ejemplo de los PictureBoxes.