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 :)

viernes, 25 de marzo de 2022

Dibujando arbolitos fractales con la tortuga de Python

Éste fue un pequeño ejercicio para practicar recursividad. La idea es dibujar un arbolito fractal con tres ramitas. Ya antes había revisado el código para dibujar estos árboles, pero quise tener mi propia implementación. Después de un buen rato probando, dibujando en un papel cómo debían salir las ramitas, y dónde debía poner la bifurcación recursiva, terminé con este código:

import turtle as t

def drawit(x, y, angle, lenght, n):
    if n > 0:
        t.penup()
        t.goto(x, y)
        t.pendown()
        t.seth(angle)
        t.forward(lenght)
        cx = t.xcor()
        cy = t.ycor()
        drawit(cx, cy, angle + 30, lenght / 1.3, n-1)
        drawit(cx, cy, angle - 30, lenght / 1.3, n-1)
        drawit(cx, cy, angle, lenght / 1.3, n-1)
    else:
        t.hideturtle()
        return 0


drawit(0, -100, 90, 100, 7)
t.mainloop()


la variable n indica la profundidad hasta la que se dibujarán las ramas, otra forma es dibujar las ramas hasta que llegan a una longitud mínima. Las variables x, y, angle, lenght indican las coordenadas desde dónde se dibujarán las ramas, el ángulo y la longitud respectivamente. La función seth() de la tortuga setea el ángulo en el que se empezará a dibujar. Las funciones xcor() y ycor() capturan las coordenadas actuales de la tortuga.

La tortuga primero dibuja el tronco del árbol en la posición (0, -100) en un ángulo de 90 grados. A continuación se bifurca en tres ramas, cada una dibujando un "tronco" en el ángulo y la longitud indicadas a partir de las coordenadas donde la tortuga terminó de dibujar. Para evitar que se confunda, le ordeno ir a esas coordenadas, antes de empezar a dibujar, con la función goto().

El resultado es:


En esta web también dibujan un árbol fractal, y además le ponen color, voy a usar el mismo truco para colorear mi árbol:

import turtle as t
import math

def drawit(x, y, angle, lenght, n):
    if n > 0:
        t.penup()
        t.goto(x, y)
        t.pendown()
        t.seth(angle)

        t.pensize(math.log(lenght,2)/3)
        if n < 2:
            t.color('forest green')
        else:
            t.color('gray')

        t.forward(lenght)
        cx = t.xcor()
        cy = t.ycor()
        drawit(cx, cy, angle + 30, lenght / 1.3, n-1)
        drawit(cx, cy, angle - 30, lenght / 1.3, n-1)
        drawit(cx, cy, angle, lenght / 1.3, n-1)
    else:
        t.hideturtle()
        return 0


drawit(0, -100, 90, 100, 7)
t.mainloop()

Ahora mi árbol ya no parece muerto:


Luego me encontré con este otro arbolito, cuyo código es muy parecido al mío. En lugar de un contador, usa la longitud de las ramas para ponerle un límite a la profundidad de las recursiones. No fue difícil modificar mi código para dibujar un arbolito curvo, y después de probar un poco noté que en este caso es mejor usar una longitud mínima en lugar de la variable n para detener las recursiones. Al final, mi código es el siguiente:

import turtle as t

def drawit(x, y, angle, lenght, n):
    if lenght > 2:
        t.penup()
        t.goto(x, y)
        t.pendown()
        t.seth(angle)
        t.forward(lenght)
        cx = t.xcor()
        cy = t.ycor()
        drawit(cx, cy, angle + 25, lenght * 0.9, n-1)
        drawit(cx, cy, angle - 90, lenght * 0.45, n-1)
    else:
        t.hideturtle()
        return 0


drawit(200, -200, 90, 100, 10)
t.mainloop()


El resultado es:

Necesita algunos arreglos:

import turtle as t

def drawit(x, y, angle, lenght, n):
    if lenght > 2:
        t.penup()
        t.goto(x, y)

        if lenght < 10:
            t.color('gray')
        else:
            t.color('black')

        t.pensize(lenght/15)
        t.pendown()
        t.seth(angle)
        t.forward(lenght)
        cx = t.xcor()
        cy = t.ycor()
        drawit(cx, cy, angle + 25, lenght * 0.9, n-1)
        drawit(cx, cy, angle - 90, lenght * 0.45, n-1)
    else:
        t.hideturtle()
        return 0


drawit(180, -200, 90, 100, 10)
t.mainloop()


Finalmente queda así:

miércoles, 9 de marzo de 2022

Instalando Glassfish como servicio de Windows (versiones 4.1 y 5)

Estos días han sido días de Glassfish en mi laptop. Jugando con las versiones 4.1 y 5, y rebuscando en internet, encontré que se puede instalar como un servicio de Windows. Como mis Glassfish son todos para pruebas, trasteo, más trasteo y depuración, mi dominio es el que se usa por defecto: domain1. Los puertos son también los que vienen por defecto: 4848 para la consola de administración y 8080 para la conexión http.

Dos buenos tutoriales han sido:

https://www.asesoriaensig.com.mx/instalar-glassfish-4-como-servicio-en-windows-2008/

https://www.luv2code.com/2013/11/13/install-glassfish-4-as-a-windows-service/

Lo que me sucedió es que con el comando asadmin start-domain me inicia el glassfish pero después de un rato tiende a detenerse. Este comando es para pruebas o depuración, no para producción.

Para producción en Windows, el Glassfish debe arrancarse como servicio. Para eso existe este comandito el cual debe ejecutarse como Administrador o pasará lo que se menciona aquí:

asadmin create-service

Lo que no ponen los tutoriales es lo siguiente: tengo un millón de servicios en Windows ¿cómo ubico el de Glassfish? Bueno, en Windows se crea como nombre-del-dominio Glassfish Server:

Lo malo: este servicio funciona, Glassfish 5 se ejecuta en background, pero Windows 10 no lo administra bien, reinicié mi laptop y el servicio estaba detenido así que tuve que iniciarlo de forma manual, además la información de si se está ejecutando o no no se muestra correctamente. Esto no me ocurrió con Glassfsih 4.1 corriendo en Windows Server 2013.

Volviendo a mi Glassfish 5 corriendo en Windows 10, no me permitía depurar webservices en Netbeans apuntando a ese mismo Glassfish 5. Como sólo es un Glassfish de pruebas, lo mejor en este caso es eliminar el servicio.

Deshabilité el servicio, ejecuté el Windows Powershell como administrador e intenté eliminar el servicio de glassfish con el comando:

sc domain1

No se eliminó, luego intenté con el comando:

sc delete "domain1 GlassFish Server"

Y tampoco se eliminó. Busqué en internet que estaba pasando y en dos segundos hallé la solución. El comando es:

sc.exe delete "domain1"

Lo que debe mostrar al ejecutarse es:


Luego se deben borrar los archivos que deja el servicio en esta ruta:

glassfish5\glassfish\domains\domain1\bin

Lo malo es que eliminar el servicio de glassfish no detiene los procesos de glassfish, para esto debemos ir al Task Manager (taskmgr.msc), hacer clic derecho en las cabeceras de las columnas y elegir "Línea de comandos". Luego se deben matar todos los procesos de Java que tengan "glassfish" en algún lado de su línea de comandos:

Suele haber más de uno, hay que matarlos todos. Después de hacer todo esto, ya pude debuggear mis webservices desde Netbeans usando Glassfish 5.

Por otro lado, el ejecutable asadmin está en esta ruta:

C:\glassfish5\glassfish\bin

miércoles, 9 de febrero de 2022

Arreglando el Error: "No appropriate protocol (protocol is disabled or cipher suites are inappropriate)" en Jdk 1.8.0_301

 Quise conectarme desde una aplicación chiquita de java a una base de datos en Sql Server 2017 via el driver jdbc sqljdbc4-2.0.jar. La aplicación la estaba depurando en Netbeans 11.1, corriendo en Windows 10. Todo estaba bien hasta que actualicé a la versión del Jdk a la 1.8.0_301, entonces en la línea:

Connection connection = DriverManager.getConnection(connectionString);

me arrojó esta excepción:

El controlador no pudo establecer una conexión segura con SQL Server con el cifrado de Capa de sockets seguros (SSL). Error: "No appropriate protocol (protocol is disabled or cipher suites are inappropriate)"

En inglés:

The driver could not establish a secure connection to SQL Server by using Secure Sockets Layer (SSL) encryption. Error: “No appropriate protocol (protocol is disabled or cipher suites are inappropriate)”.

Lueo de rebuscar en foros raruchos, hallé en éste la solución:


Es hora de ir a buscar ese archivo. Primero debo cerrar todas las aplicaciones de java: jars y Netbeans incluídos. En mi caso el archivo a modificar está en C:\Program Files\Java\jdk1.8.0_301\jre\lib\security\java.security. Las líneas que debo comentar son las 723, 724 y 725:


Debe quedar así:


En mi caso, basta volver a arrancar las aplicaciones de java (Netbeans incluídos) para resolver el problema y poder conectarme a mi base de datos. Puede ser que en algunos casos sea necesario reiniciar el sistema operativo.

jueves, 25 de noviembre de 2021

Resolviendo el proyecto Pagerank del curso "CS50’s Introduction to Artificial Intelligence with Python"

El curso se encuentra en este enlace: https://cs50.harvard.edu/ai/2020/

Aquí se deben hacer dos implementaciones para calcular el Pagerank:
  • La primera implementación es el Modelo del Surfista Aleatorio (Random Surfer Model). En resumen, el algoritmo debe iterar a través de todas las páginas, escoger una de las páginas enlazadas, y contar cuántas veces se escogió cada página. Las reglas de cómo se debe escoger cada página están en la función transition_model, que devuelve la probabilidad con la que se debe escoger cada página. Los valores devueltos por la función transition_model deben sumar 1, para esto se tienen dos conjuntos de valores, las páginas enlazadas desde una página determinada (el conjunto de páginas que se está evaluando), y todas las otras páginas. Si una página pertenece al conjunto de páginas que estamos evaluando, la distribución de probabilidad es el factor damping_factor/Número de páginas en el conjunto. Si una página no está en el conjunto, la probabilidad es de (1 - el factor damping_factor)/el total de páginas que no pertenecen al conjunto. Lo que debe devolver esta función es una proporción, por ello al final se dividen los resultados entre el total de páginas.

La función transition_model es:

def transition_model(corpus, page, damping_factor):
    """
    Return a probability distribution over which page to visit next,
    given a current page.

    With probability `damping_factor`, choose a link at random
    linked to by `page`. With probability `1 - damping_factor`, choose
    a link at random chosen from all pages in the corpus.
    """
    N = len(corpus[page])
    M = len(corpus)
    distr = { }

    if N == 0:
        distr = { c: 1 / M for c in corpus.keys() }
    else:
        for p in corpus.keys():
            if p in corpus[page]:
                distr[p] = damping_factor / N
            else:
                distr[p] = (1 - damping_factor) / (M - N)

    return distr


La función para esta primera implementación es:

def sample_pagerank(corpus, damping_factor, n):
    """
    Return PageRank values for each page by sampling `n` pages
    according to transition model, starting with a page at random.

    Return a dictionary where keys are page names, and values are
    their estimated PageRank value (a value between 0 and 1). All
    PageRank values should sum to 1.
    """    
    pageranks = {p : 0 for p in corpus.keys()}
    page = random.choice(list(corpus.keys()))
    trans = transition_model(corpus, page, damping_factor)
    pageranks[page] = 1

    for _ in range(1, n):
        links = []
        weights_ = []

        for p in trans.keys():
            links.append(p)
            weights_.append(trans[p])

        page = random.choices(links, weights = weights_, k = 1)
        pageranks[page[0]] += 1
        trans = transition_model(corpus, page[0], damping_factor)

       
    return {p : pageranks[p] / n for p in pageranks.keys()}


  • La segunda implementación es el Método Iterativo, el cual escoge una página cualquiera para empezar a calcular el valor de Page_rank mediante la fórmula:


La iteración se detiene cuando la diferencia entre PR(p) calculado en la iteración actual (variable pagerank) y el PR(p) calculado en la iteración anterior (variable pagerank2) es muy pequeña. La variable temp almacena el valor dentro de la sumatoria (cantidad de páginas enlazadas desde la página p). Si el valor resulta cero (no hay páginas enlazadas) se le asigna el valor 1/N.

La función en este caso es iterative_pagerank:

def iterate_pagerank(corpus, damping_factor):
    """
    Return PageRank values for each page by iteratively updating
    PageRank values until convergence.

    Return a dictionary where keys are page names, and values are
    their estimated PageRank value (a value between 0 and 1). All
    PageRank values should sum to 1.
    """
    N = len(corpus)
    pagerank = { p : 1 / N for p in corpus.keys() }
    pagerank2 = { p : 0 for p in corpus.keys() }

    stop = False

    while not stop:
        for p in corpus.keys():
            temp = 0

            for i in corpus:
                if p in corpus[i]:
                    temp += pagerank[i] / len(corpus[i])

            if temp == 0:
                temp = 1 / N

            pagerank[p] = (1 - damping_factor) / N + temp * damping_factor

        stop = True
        for p in corpus.keys():
            if abs(pagerank[p] - pagerank2[p]) > 0.001:
                stop = False
                break

        pagerank2 = copy.deepcopy(pagerank)

    return pagerank



jueves, 14 de octubre de 2021

Configurando Sql Server 2017 para que escuche en el puerto por defecto 1433

No me podía conectar desde Java ejecutándose en Windows 10 a mi Sql Server ejecutándose en localhost a pesar de poner todos los parámetros correctos como indican en esta web: https://www.codejava.net/java-se/jdbc/connect-to-microsoft-sql-server-via-jdbc

Lo peor es que se trata de una instalación fresca de Sql Server, además el SQL Server Management Studio no tiene problemas en conectarse. Pero al intentar conectarme desde una aplicación en Java, siempre me arrojaba la siguiente excepción:

com.microsoft.sqlserver.jdbc.SQLServerException: The TCP/IP connection to the host localhost, port 1433 has failed. Error: "Connection refused: connect. Verify the connection properties. Make sure that an instance of SQL Server is running on the host and accepting TCP/IP connections at the port. Make sure that TCP connections to the port are not blocked by a firewall.

El error también puede decir que se ha agotado el tiempo de espera.

Luego de comprobar que no fuera el firewall (activándolo y desactivándolo), reinicié los servicios que aparecen listados en Services.msc, eso tampoco resolvió el problema:

 

En el caso de Sql Server Express, no interesa si el SQL Server Agent no se ejecuta, por ahí leí que la versión Express no soporta el SQL Server Agent, de modo que se detiene apenas se inicia. Esto no interesa.

Pasa saber qué etá pasando, se debe revisar si hay alguna aplicación escuchando el puerto 1433; para esto se debe abrir el Windows Powershell o el cmd e ingresar el comando netstat -a que listará todos los puertos en escucha:



Es una lista bastante larga, pero el puerto 1433 debe aparecer por algún lado. En mi caso no aparece, para resolverlo se deben seguir las instrucciones de https://edywerder.ch/sql-server-remote-port/

Primero se debe abrir el SQL Server Configuration Manager, si no se tiene el acceso directo, hay que ir a Inicio y ejecutar el archivo respectivo dado en este enlace https://docs.microsoft.com/en-us/sql/relational-databases/sql-server-configuration-manager?view=sql-server-ver15 

Esta es la lista para cada versión de SQL Server: 

VersionPath
SQL Server 2019 C:\Windows\SysWOW64\SQLServerManager15.msc
SQL Server 2017 C:\Windows\SysWOW64\SQLServerManager14.msc
SQL Server 2016 C:\Windows\SysWOW64\SQLServerManager13.msc
SQL Server 2014 (12.x) C:\Windows\SysWOW64\SQLServerManager12.msc
SQL Server 2012 (11.x)C:\Windows\SysWOW64\SQLServerManager11.msc

No hay que poner la ruta completa, basta el nombre del archivo msc respectivo. En mi caso es SQLServerManager14.msc

A continuación se debe ir a Protocolos de SQLEXPRESS (el nombre de mi Instancia) y hacer click derecho a TCP/IP->Propiedades:


Las opciones que se deben configurar son IPV3 e IPV4, se debe borrar el cero en TCP Dinamic Ports y poner Enabled en Yes, la opción TCP Port se deba en blanco:

 

 La última opción es IPALL donde se debe ingresar el puerto 1433 en TCP Port:


Luego se deben reiniciar todos los servicios de SQL Server en Services.msc. Se debe comprobar que el puerto 1433 aparece listado al ejecutar el comando netstat -a:


Esto me resolvió el problema de conexión desde Java.

viernes, 10 de septiembre de 2021

Resolviendo el proyecto Knights del curso "CS50’s Introduction to Artificial Intelligence with Python"

El curso es accesible libremente en este link: https://cs50.harvard.edu/ai/2020/

El proyecto está en este link: https://cs50.harvard.edu/ai/2020/projects/1/knights/

En resumen, el proyecto trata de personajes que pueden ser Caballeros (Knights) o Truhanes (Knaves) pero no ambos a la vez. Los Caballeros siempre dicen la verdad, y los Truhanes siempre mienten. El objetivo es, dada cierta información, averiguar si los personajes son Caballeros o Truhanes.

El primer problema dice:

# Puzzle 0
# A says "I am both a knight and a knave."

Mi solución en este caso es:

knowledge0 = And(
    Or(AKnightAKnave),
    Not(And(AKnightAKnave)),
    Implication(Not(And(AKnightAKnave)), AKnave)
)

La afirmación de lo que dice A es falsa ("Soy ambos, Caballero y Truhán"). Como es falsa, implica que A es un Truhán.

El segundo problema es:

# Puzzle 1
# A says "We are both knaves."
# B says nothing.

Luego de analizar las condiciones del problema, hallé dos bicondicionales: Si un personaje miente, es un Truhán, y si es un Truhán, entonces miente. Si un personaje dice la verdad ,es un Caballero, y si es un Caballero, debe decir la verdad.

Mi solución:

knowledge1 = And(
    Or(AKnightAKnave),
    Not(And(AKnightAKnave)),
    Or(BKnightBKnave),
    Not(And(BKnightBKnave)),
    Implication(Not(And(AKnaveBKnave)), AKnave),
    Biconditional(And(Or(AKnaveBKnave), Not(And(AKnaveBKnave))), AKnave)
)

La implicación significa que A y B no pueden ser Truhanes, porque en este caso A estaría diciendo la verdad y sería un Caballero. Tampoco A puede ser un Caballero, porque estaría mintiendo.

El bicondicional indica que uno de los dos es un Truhán, pero no pueden serlo ambos, sí y sólo si A es Truhán.

El tercer problema es:

# Puzzle 2
# A says "We are the same kind."
# B says "We are of different kinds."

Mi solución:

knowledge2 = And(
    Or(AKnightAKnave),
    Not(And(AKnightAKnave)),
    Or(BKnightBKnave),
    Not(And(BKnightBKnave)),
    Biconditional(And(AKnightBKnight), AKnight),
    Biconditional(And(AKnaveBKnight), AKnave),
    Biconditional(And(AKnightBKnight), BKnave),
    Biconditional(And(AKnaveBKnight), BKnight)
)

Aquí agoto las cuatro opciones, todas como bicondicionales. Si ambos son caballeros, A dice la verdad y viceversa. En este caso B estaría mintiendo y sería un Truhán, y viceversa. Si A es un Caballero, B es necesariamente un Truhán.

El último problema es:

# Puzzle 3
# A says either "I am a knight." or "I am a knave.", but you don't know which.
# B says "A said 'I am a knave'."
# B says "C is a knave."
# C says "A is a knight."

Aquí recibí un poco de ayuda de: https://cs50.stackexchange.com/questions/41635/help-with-cs50ai-project-1-knights-puzzle-3

Mi solución es:

knowledge3 = And(
    Or(AKnightAKnave),
    Not(And(AKnightAKnave)),
    Or(BKnightBKnave),
    Not(And(BKnightBKnave)),
    Or(CKnightCKnave),
    Not(And(CKnightCKnave)),
    Biconditional(Or(AKnightAKnave), AKnight),
    Biconditional(CKnaveBKnight),
    Biconditional(CKnightBKnave),
    Biconditional(AKnightCKnight),
    Biconditional(AKnaveCKnave)

Lo que podría decir A está en el primer bicondicional. La última bicondicional codifica lo que sería A en caso de que C sea un Truhán, la penúltima codifica loq ue será A si C fuera un Caballero. Las otras dos bicondicionales codifican lo que sería C según lo que dice B en caso de ser Caballero o Truhán.

Para resolver este problema tuve que hacer una tabla de verdad con lo que pueden ser A, B, C (Caballero y Truhán) y lo que están diciendo, y también verificar que mi respuesta era la correcta.

Encontré este otro código https://github.com/iron8kid/Knights/blob/main/puzzle.py donde el autor no usa el bicondicional, haciendo que sus soluciones sean mucho más complejas que las mías.

Los archivos del proyecto con mis soluciones, una copia de las soluciones de iron8kid (en el archivo puzzle2.py) y los ejemplos dados en el curso (subcarpeta src) se pueden descargar de aquí.

IDE: Visual Studio Code 1.60 con Python 3.8

miércoles, 18 de agosto de 2021

Modificando los Atributos de los Archivos con Visual Basic 6

Otra entrada migrada de la vieja web Electrónica a Martillazos, porque desde hace una década no hago más que programar y programar, asi que de electrónica ya nada. Y de paso me deshago de la fuente Comic Sans :D


De vez en cuando los virus infectan las computadoras de la facultad. Hubo uno muy molesto que modificaba los atributos de los archivos de tal forma que no pudieran "desocultarse":

Desde la ventana de "propiedades" es imposible "desocultar" el archivo. Intenté hallar cómo modificar esta propiedad, rebusqué hasta en el registro y nada.

Hasta que me topé con esta web: http://www.recursosvisualbasic.com.ar/htm/tutoriales/tutorial-basico7.htm#getattr-setattr

Dado que desde Visual Basic es muy pero muy fácil modificar los atributos de los archivos decidí hacerme mi propio programa para quitar las modificaciones que hizo el condenado virus:

Se abre el Visual y se carga un proyecto exe estándar. Luego, a la barra de controles, se le carga el control Conmon Dialog. Cómo hacerlo está acá: http://www.recursosvisualbasic.com.ar/htm/tutoriales/control-commondialog.htm

Éste control servirá para elegir el archivo cuyos atributos se van a modificar, ésta es la ventana que creé:

 


Ésta es la lista de los controles:

- Un Label para ver la ruta del archivo: label1

- Un Conmon Dialog: cmd

- Dos botones, uno para llamar al Conmon Dialog y escoger el archivo, y el otro para modificar sus atributos: Command2 y Command1 respectivamente.

- Seis OptionButton para escoger los atributos que se deseen modificar, sus nombres, de arriba a abajo y muy explícitos, son:

normal, lectura, oculto, sistema, s_oculto, s_oculto_lectura.

Cada OptionButton asigna un número a una variable tipo Integer llamada "attr" ya que los atributos están definidos por números, acá la lista de todos los que pude encontrar:

0 - Archivo Normal

1 - Archivo de Sólo Lectura

2 - Archivo Oculto

4 - Archivo de Sistema

16 - Directorio o Carpeta

32 - Archivo Modificado

38 - Archivo de Sistema Oculto

39 - Archivo de Sistema Oculto y Sólo Lectura

18 - Carpeta Oculta

6 - Oculto (en la caja ventana de propiedades no está el check en "archivo")

7 - Archivo? de sistema oculto y sólo lectura (en la caja ventana de propiedades no está el check en "archivo", es posible que sea aplicable para carpetas, no lo he comprobado)

Y el código fuente es (un par de líneas comentadas se colaron por ahí):

Option Explicit

Dim ruta As String
Dim ret As Long
Dim attr As Integer

Private Sub Command1_Click()
'cmd.Action = 1

If ruta <> "" Then
SetAttr ruta, attr
End If

End Sub

Private Sub Command2_Click()

cmd.DialogTitle = "Seleccione un archivo"
cmd.ShowOpen

If cmd.FileName <> "" Then
ruta = cmd.FileName
Label1 = ruta
End If

End Sub

Private Sub Form_Load()

'ret = GetAttr("c:\IO.SYS")
Me.Top = Screen.Height / 2 - Me.Height / 2
Me.Left = Screen.Width / 2 - Me.Width / 2
attr = 0

End Sub

Private Sub lectura_Click()
attr = 1
End Sub

Private Sub normal_Click()
attr = 0
End Sub

Private Sub oculto_Click()
attr = 2
End Sub

Private Sub s_oculto_Click()
attr = 38
End Sub

Private Sub s_oculto_lectura_Click()
attr = 39
End Sub

Private Sub sistema_Click()
attr = 4
End Sub

 

Y para quienes no quieren programarse esto, les dejo el ejecutable que me llevó 15 minutos de mi vida hacerlo.

miércoles, 16 de junio de 2021

Las Apis Copyfile y Shellexecute, y sus Equivalentes en .Net Con C#

Otra entrada migrada de la vieja web :)

Después de mucho programar Apis en Visual Basic 6, se me hizo difícil dejar esa costumbre al pasar al C Sharp. Con la plataforma .Net,  ya no son necesarias muchas de las llamadas a Apis, pero dado que el Visual Basic 6 está tan bien documentado suele ser más sencillo hallar en internet cómo hacer algo usando una Api que usando .Net. Lo malo: llamar a las apis hace depender nuestras aplicaciones del sistema operativo Windows.  

Con ShellExecute para C Sharp:

En .Net tenemos la clase Process que hace todo lo que hace la api ShellExecute así que no tendríamos porqué necesitarla más. Por ejemplo, para ejecutar un programa, se pone:

Process p = new Process();
p.StartInfo.FileName = "iexplore.exe";
p.StartInfo.Arguments = "http://www.microsoft.com";
p.Start();


o abrir el Notepad:

Process p = new Process();
p.StartInfo.FileName = "notepad.exe";
p.Start();


Si quiero abrir un archivo el código sería:

Process pr = new Process();
try
{
    pr.StartInfo.FileName = @"C:\un_archivo_cualquiera";
    pr.Start();
}
catch (NullReferenceException ex)
{
    MessageBox.Show(ex.Message, "Error");
}


Que reemplaza a este otro código (dentro de una clase):

class executeIt{

/*
Public Declare Function ShellExecute _
Lib "shell32.dll" _
Alias "ShellExecuteA" ( _
ByVal hwnd As Long, _
ByVal lpOperation As String, _
ByVal lpFile As String, _
ByVal lpParameters As String, _
ByVal lpDirectory As String, _
ByVal nShowCmd As Long) _
As Long
*/

    [System.Runtime.InteropServices.DllImport("shell32.dll", EntryPoint = "ShellExecute")]
    public static extern int ShellExecuteA(int hwnd, string lpOperation, string lpFile, string lpParameters, string lpDirectory,
    int nShowCmd);
}



Lo que está comentado es la llamada a la api para Visual Basic 6, debajo está la "traducción" para C Sharp. Así se puede ir deduciendo cómo "traducir" el código que llama apis para VB6 a C Sharp.

Para usar la api se hace:

executeIt.ShellExecuteA(0, "OPEN","E:\\mundodisco_1-27.zip","", "", 0);
executeIt.ShellExecuteA(0, "OPEN","F:\\Músi-k","", "", 1);


La primera línea abre mis libros zipeados del Mundodisco de Terry Pratchett. La segunda abre mi carpeta con mis mp3 descargados (y de paso dejo información sobre las particiones de mi disco duro).

Si en Visual Basic 6 las variables se declaraban como Long, en C Sharp su equivalente es Int. Esto es debido a cuántos bytes ocupan estas variables en cada lenguaje. En esta página se puede ver una comparación de los tipos de variables, su tamaño y otras características.

Usando la api CopyFile:

Si se compila el siguiente código:

using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        /* Public Declare Function CopyFile Lib "kernel32.dll" Alias
        * "CopyFileA" (ByVal lpExistingFileName As String,
        * ByVal lpNewFileName As String, ByVal bFailIfExists As Long) As Long
        */


        [System.Runtime.InteropServices.DllImport("kernel32.dll", EntryPoint = "CopyFile")]
        public static extern int CopyFileA(string lpExistingFileName, string
        lpNewFileName, int bFailIfExists);

        private void replicar()
        {
            int count, rad;
            string copia = " ";
            string nombre_archivo = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;

            // Fuente: http://blogs.msdn.com/brada/archive/2004/04/14/113620.aspx  por Luc Cluitmans

            count = 0;
            System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo(@"C:\");
            foreach (System.IO.FileInfo file in dir.GetFiles("CopyIt*.exe"))
                count++; // si es cero es que no existe el archivo.

            for (int i = count; i <= count * 2+1; i++)
            {
                copia = String.Concat("C:\\", "CopyIt", i.ToString(), ".exe");
                rad = CopyFileA(nombre_archivo, copia, 0);
            }
        }
    
        static void Main(string[] args)
        {
            Program pp = new Program();
            pp.replicar();
        }
    }
}


se tendrá un programa que simplemente busca cuántas copias existen en C:\ (los archivitos "CopyIt*.exe") y luego con un bucle se copia el doble de veces más uno. No interesa cómo se llame el programa original o dónde se encuentre, siempre sus copias terminarán en C:\ y siempre contará cuántas de ellas existen. Así, después de unos pocos clicks, el disco duro (poniendo el ícono apropiado) se verá así:


Y como todos son el mismo programa, se puede ejecutar cualquiera de ellos y harán lo mismo: copiarse y copiarse.

Y ahora, su equivalente para .Net:

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace ConsoleApplication1
{
    class Program
    {
        private void replicar()
        {
            int count;
            string copia = " ";
            string nombre_archivo = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
            // Fuente: http://blogs.msdn.com/brada/archive/2004/04/14/113620.aspx  por Luc Cluitmans
            count = 0;

            DirectoryInfo dir = new DirectoryInfo(@"C:\");
           
            foreach (FileInfo file in dir.GetFiles("CopyIt*.exe"))
                count++; // si es cero es que no existe el archivo.

            for (int i = count; i <= count * 2+1; i++)
            {
                copia = String.Concat("C:\\", "CopyIt", i.ToString(), ".exe");
                File.Copy(nombre_archivo, copia);
            }
        }

        static void Main(string[] args)
        {
            Program pp = new Program();
            pp.replicar();
        }
    }
}


Si se usan las rutas al estilo Visual basic en C Sharp se debe poner una arroba antes para indicarle que es una ruta de directorio, pues el formato de las rutas a los directorios es:

Rutas de archivos en Visual Basic: por ejemplo: "c:\mi archivo"
Rutas de archivos en C, C++ y C Sharp: por ejemplo: "c:\\mi archivo"

miércoles, 28 de abril de 2021

Resolviendo el error "PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target" en Glassfish

La solución la dan en este enlace, (está mejor explicado aquí). En mi caso, se trataba de un webservice en glassfish que se conectaba a otra web para descargar archivos. Todo ejecutandose en Windows 10.

Este error ocurre cuando uno de nuestros webservices se conecta a una web https con un certificado que no está en el repositorio de glassfish. Felizmente esto no es complicado de resolver.

Como estamos en Windows, y para hacernos la vida más fácil, primero descargamos e instalamos el Keystore-Explorer

Al ejecutarlo se ve así:

 

Luego tenemos que abrir el repositorio de certificados de glassfish, es un archivo llamado cacerts.jks. En mi caso está ubicado en:

C:\glassfish5\glassfish\domains\domain1\config

Ya había abierto este archivo antes, no es la primera vez que tengo este problema.
 
A continuación nos pedirá la contraseña, esta es "changeit".
 
Luego con el Edge (sí, leyeron bien: Edge), nos vamos a la web que está dando problemas. Lo que sigue a continuación no lo encontré ni en Firefox ni en Chrome.
 
Ya en la web, primero le hacemos clik al ícono del candado que aparece a la izquierda de la dirección url:
 

Hacemos click en "Ver certificado", en el lado derecho aparecerán las propiedades del certificado:


Le hacemos click a "Exportar a archivo", nos pedirá guardar un archivo con extensión crt.

Nos vamos al KeyStore Explorer.

Ya dentro, hacemos click derecho y elegimos "Import Trusted Certificate":

Importamos el archivo crt que nos acabamos de descargar, si aparecen mensajes de advertencia, le hacemos click en Aceptar y OK a todos.

Cerramos el KeyStore Explorer. Eso es todo para resolver el problema.

miércoles, 24 de febrero de 2021

Llamando a una dll en C++ desde Java mediante JNI

Ya había escuchado antes acerca de JNI (Java Native Interface) que permite acceder a código en C o C++ desde java, así que ya era hora de probarlo:

Primero, abro mi Visual Studio 2019, elijo Crear un Proyecto->C++->Biblioteca de Vínculos Dinámicos:

Mi proyecto se llama JNISample y está ubicado en D:. Lo que suelo hacer con mis dlls es crear un nuevo archivo de encabezado, en este caso se llamará jniSample.h:


Luego se hace click derecho al nombre del proyeccto, nos vamos a General->C++->Directorios de Inclusión Adicionales, e incluímos las carpetas Include de nuestra instalación de JDK tal y como indica esta web:


En mi caso las rutas son C:\Program Files\Java\jdk1.8.0_231\include y C:\Program Files\Java\jdk1.8.0_231\include\win32

En el archivo dllmain.cpp añadimos la siguiente función que simplemente toma dos cadenas de caracteres y suma sus longitudes:

La explicación del uso de la variable env está en esta web. En resumen es para indicar que se desea copiar la cadena de caracteres enviada por java a la memoria del código nativo en C++, y al final liberar esta memoria.

En el archivo .h se copia la declaración de la función:

Y se compila. El código dentro de los #ifdef es para que el compilador no decore el nombre de la función.

Ahora hay que crear la aplicación java. Yo estoy usando Netbeans 11.1. La aplicación de ejemplo es de tipo Ant y se llama JNISample1; la voy a ubicar dentro de la carpeta del proyecto de C++, JNISample.

Luego copio la dll compilada a la carpeta donde está el proyecto en Netbeans, en mi caso es D:\JNISample\JNISample1:

 

Siguiendo las instrucciones de esta web y esta otra, se añade el código necesario para cargar la dll y usarla desde java, debe quedar así:

 

Si arroja algún error, puede deberse a que no se ha escrito correctamente el nombre de la librería, éste es el nombre del archivo dll sin la extensión. Más información en este enlace.

Es preferible que la función que llama a la dll sea pública, así podremos usar este jar desde otro. Yo prefiero tener un jar intermedio entre la aplicación principal y la dll debido a los pasos que siguen a continuación:

Para enlazar la dll a la aplicación de java se debe crear el verdadero archivo .h con el que se debe recompilar la dll. Esto se hace invocando al ejecutable javah que existe dentro de la instalación del JDK (no he probado con el JRE). Para este paso hay que seguir las instrucciones de esta web, que se resumen en abrir la consola en la ubicación de la dll e ingresar el siguiente comando:

"C:\Program Files\Java\jdk1.8.0_231\bin\javah" -o jniSample.h -jni -classpath "D:\JNISample\JNISample1\build\classes" "jnisample1.JNISample1"

De forma más genérica tiene la siguiente forma:

"ruta a mi instalacion de java donde está javah" -o nombre_de_la_dll.h -jni -classpath "ruta de mi proyecto\build\classes" "paquete.clase"

Al presionar Enter, y si todo sale bien no debe decirnos nada, sólo debe mostrarnos mensajes en caso de un error.

Y con esto ya tenemos nuestro archivo de cabecera .h el cual se debe reemplazar en el proyecto de Visual Studio. En mi caso javah me generó esto:

 

Simplemente copio todo este contenido y lo reemplazo en mi archivo jniSample.h de mi proyecto de Visual Studio.

A continuación se le cambia el nombre a la función original en dllmain.cpp.

Inicialmente estaba así:

Y ahora debo modificarla cambiándole el nombre que aparece en el archivo .h:

Luego compilo todo y reemplazo la dll resultante por la que está en la carpeta del proyecto de Netbeans.

Y eso es todo:


Los dos proyectos se pueden descargar de este enlace. El proyecto de Visual Studio es para la versión 2019.