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

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.

lunes, 25 de enero de 2021

Compilando las librerías Opencv 4.2 y Opencv_contrib con CMake y Visual Studio 2019 para Java

Continuando con el tutorial de OpenCV, esta vez toca generar el jar para poder usarlo desde java.

Yo tengo instalado JDK 8. Mi variable de entorno JAVA_HOME está seteada como C:\Program Files\Java\jdk1.8.0_231. Esta variable de entorno es importante para que CMake pueda hallar la ruta de instalación de java.

Al crear el jar para OpenCV, la diferencia está en algunas opciones al compilar con CMake. Lo bueno de Opencv es que se puede recompilar tantas veces como queramos, y tener una compilación distinta en carpetas diferentes. En este ejemplo, estoy enviando mi compilación para Java a la carpeta D:\opencv2.

Primero: descargar y descomprimir Apache Ant. Ant no se instala, sólo se descomprime. OpenCV lo necesita para generar el jar.

Añadir a la variable de entorno Path la ruta a donde hemos descomprimido Ant, en mi caso es C:\apache-ant-1.10.9\bin

 



Reiniciar el sistema. 

Luego de configurar  CMake, debe de poder reconocer las rutas de java y de ant:


 

A continuación, se deben escoger las siguientes opciones en CMake:

Build_Java:


BUILD_FAT_JAVA_LIB:


Si se desea compilar con opencv-contrib se deben elegir estas opciones:

Y se presiona Generate. Esto creará los proyectos de Visual Studio para crear las librerías y el jar. Se debe compilar como Release los proyectos All_Build e Install como explico en el tutorial anterior.

Y me pasó que no me generó el jar.

Debía aparecer en la carpeta D:\opencv2\bin o en D:\opencv2\install\java. En este caso se debe buscar los proyectos gen_opencv_java_source y opencv_java_jar, y compilarlos primero gen_opencv_java_source y luego opencv_java_jar, ambos como release:


 Necesité más de un intento para poder generar mi jar.

El jar y los archivos lib y dll deben estar en la misma carpeta, yo prefiero ponerlos en la carpeta de mi proyecto. Para usar este jar basta añadirlo como librería (yo uso netbeans):


martes, 22 de diciembre de 2020

Compilando las librerías Opencv 4.2 y Opencv_contrib con CMake y Visual Studio 2019

Opencv viene de dos formas: la librería ya compilada que contiene funciones libres y probadas de visión por computadora, y aparte las funciones de opencv_contrib, las cuales no tienen licencias 100% libres o aún son experimentales. Por ello opencv_contrib sólo se distribuye en código fuente que debe compilarse para poder usarlas.

El mejor tutorial que he encontrado para generar los binarios de opencv_contrib es este.

Primero nos vamos a https://opencv.org/releases/ y bajamos las fuentes "sources" de la versión que queremos, yo escogí la 4.2 por ninguna razón en especial.

Luego nos vamos a https://github.com/opencv/opencv_contrib/releases/tag/4.2.0 y descargamos las fuentes de opencv_contrib (las versiones de ambos deben ser las mismas). La lista de versiones de opencv_contrib está aquí.

Luego descomprimimos ambos archivos en la misma carpeta, yo escogí D:

Además hay que crear una carpeta a dónde irán nuestras librerías ya compiladas, yo la llamé opencv.


Luego descargamos e instalamos la última versión de CMake. Yo escogí el instalador msi para Windows.

Ejecutamos CMake, ingresamos la carpeta donde están los códigos fuente opencv (en mi caso es opencv-4.2.0) y la carpeta de destino a donde irán los binarios, luego presionamos el botón Configure:

Aquí podemos escoger si quremos que se genere la solución para Visual Studio. Yo escogí la versión 2019, a continuación se presiona Finish. Yo suelo presionar una vez más Configure después de este paso, pero puede que no sea necesario. Una vez configurado, Cmake nos mostrará las opciones de configuración:


Para compilar con opencv_contrib buscamos las siguientes opciones:

 

OPENCV_ENABLE_NONFREE debe estar seleccionado, y OPENCV_EXTRA_MODULES_PATH debe tener la ruta de la carpeta modules dentro de la carpeta donde hemos descomprimido opencv_contrib.

A continuación presionamos Generate. Este paso puede tomar algunos minutos. En la carpeta definida como el destino de los binarios aparecerá un archivo llamado OpenCV.sln que debemos abrir con la versión de Visual Studio elegida:


En Visual Studio elegimos Release, la plataforma a la que queremos compilar (yo puse x64), y tal y como dice el tutorial, compilar primero el proyecto donde dice BUILD y luego INSTALL:

Esta parte puede demorar un buen rato.

En mi caso los binarios se fueron a las carpetas D:\opencv\install\x64\vc16\bin para las dlls y D:\opencv\install\x64\vc16\lib para las librerías estáticas. Antes de poder usarlas en nuestros proyectos de Visual Studio hay que setearlas en la variable de entorno Path e INCLUDE.

En Path van las ubicaciones de las librerías:


INCLUDE debe crearse en caso de no existir, contiene la ruta a donde están los archivos de cabecera .h y .hpp. En mi caso se fueron a D:\opencv\install\include:


En Windows 10 no estoy segura si es necesario reiniciar el equipo, pero por precaución es mejor reiniciar.

Y ahora a configurar el proyecto de Visual Studio. El mejor tutorial que encontré es éste, pero no incluye las librerías opencv_contrib, pero los pasos son muy parecidos:

Ponemos el proyecto en Release y en la misma plataforma para la que fue compilada opencv (x64 en mi caso).

Configuramos lo siguiente en las propiedades del proyecto (en lugar de elegir Debug o Release, yo escogí Todas las Configuraciones):

General->Versión del SDK de Windows Última instalada (en mi caso, estoy en Windows 10 con Visual Studio 2019, mi versión es la 10.0)

En la opción Directorios de VC++ ponemos las siguientes rutas:

Directorios de archivos de inclusión: Donde se ubican los archivos de cabecera, es la misma ruta que la variable de entorno INCLUDE

Directorios de archivos de bibliotecas: Donde se ubican los archivos de librerías estáticas. Es la misma que la ingresada en la variable de entorno Path. 


Luego en la opción VC++ -> General ingresamos en Directorios de Inclusión Adicionales la ruta de la variable INCLUDE:


En Vinculador -> General ponemos en Directorios de Bibliotecas Adicionales la ruta de la variable Path donde están las librerías estáticas:

 

Y finalmente en la opción Vinculador -> Entrada, en donde dice Dependencias Adicionales, en lugar de la librería opencv_worldxxx.lib, se deben ingresar la lista de TODAS las librerías en la carpeta de la variable Path donde están las librerías estáticas:


Lo bueno, tengo este script, sólo hay que reemplazar la ruta de las librerías y escribirá en un archivo de texto la lista de todos los archivos que se hallan en esa carpeta:

@echo off
REM Next command creates a list of program files
dir "D:\opencv\install\x64\vc16\lib" > D:\list_of_libs.txt

Luego abrimos el archivo creado con notepad++ y con alt+selección se puede seleccionar sólo los nombres de los archivos para luego pegarlos:


Y eso es todo!

Y ahora la pregunta:

¿cómo distribuyo mi aplicación que usa opencv_contrib?

para distribuir esta... cosa se deben copiar los directorios apuntados por las variables INCLUDE y Path, y pegarse en rutas específicas de la pc destino. En esta pc se deben configurar las respectivas variables INCLUDE y Path para que apunten a las respectivas rutas de los archivos de cabecera h, hpp y los archivos de librerías lib y dll. A pesar de que en Visual Studio sólo se referencian los archivos lib también son necesarias las dlls.

Es probable que también sea necesario instalar los redistributables de Visual Studio 2019 para C++, se pueden descargar de aquí de forma directa. Descripción y descarga aquí.