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

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