javacjna

How to make a proper description of Java JNA objects


Please help me create the correct JNA objects for the C code. I have a dynamic library n2q_lib.dll . This library used its own configuration file and possibly other libraries that are located in the same directory (secrets of an external developer). The library is compiled only with ASCII support (it does not understand UTF strings) and for x86 architecture (I use Java x86).

// C code example
// n2q_lib.h
#ifndef _N2Q_LIB_H_
#define _N2Q_LIB_H_
#include "n2q_msg.h"

extern "C" HN2QCONN __stdcall N2QLibConnCreate(LPCTSTR lpszIniFile, LPCTSTR lpszSection, N2Q_CALLBACK_PROC Callback);
extern "C" BOOL __stdcall N2QLibConnectQuik(HN2QCONN hConn);
extern "C" BOOL __stdcall N2QLibIsConnected(HN2QCONN hConn);

typedef HN2QCONN (_stdcall *N2QLIB_CONN_CREATE_DLL_FUNC)(LPCTSTR, LPCTSTR, N2Q_CALLBACK_PROC, int, int);
typedef BOOL (_stdcall *N2QLIB_CONNECT_QUIK_DLL_FUNC)(HN2QCONN);
typedef BOOL (_stdcall *N2QLIB_IS_CONNECTED_DLL_FUNC)(HN2QCONN);
#endif

// n2q_msg.h
#ifndef _N2Q_MSG_H_
#define _N2Q_MSG_H_

struct N2Q_MESSAGE
{
    int     nSeverity;
    DWORD   dwCode;
    char    szMsg[512];
    N2Q_MESSAGE() {dwCode=0; nSeverity=0; memset(szMsg, 0, sizeof(szMsg)); };
    N2Q_MESSAGE& operator=(N2Q_MESSAGE& r) {
        if( this == &r ) return *this; 
        nSeverity=r.nSeverity;
        dwCode=r.dwCode;
        strcpy(szMsg, r.szMsg);
        return *this;
    }; 
    N2Q_MESSAGE(N2Q_MESSAGE& r) {*this=r;};
};

typedef int (WINAPI *N2Q_CALLBACK_PROC)(N2Q_MESSAGE*);
typedef int (WINAPI *N2Q_SEND_MESSAGE_PROC)(MSG_TITLE*, MSG_BODY*);

DECLARE_HANDLE (HN2QCONN);

typedef N2Q_CALLBACK_PROC (WINAPI *SET_CALLBACK_DLL_FUNC)(N2Q_CALLBACK_PROC);
typedef N2Q_SEND_MESSAGE_PROC (FAR *SET_SEND_MESSAGE_PROC_DLL_FUNC)(N2Q_SEND_MESSAGE_PROC);

// if I understand correctly, then this is not used
typedef BOOL (WINAPI *START_SEANCE_DLL_FUNC)();
typedef BOOL (WINAPI *STOP_SEANCE_DLL_FUNC)();
typedef BOOL (WINAPI *IS_SEANCE_STARTED_DLL_FUNC)();
typedef BOOL (WINAPI *IS_SEANCE_STOPPED_DLL_FUNC)();
typedef BOOL (WINAPI *GET_SETTINGS_DLL_FUNC)();

//__declspec(dllimport) N2Q_CALLBACK_PROC SetCallback(N2Q_CALLBACK_PROC Callback);
extern "C" N2Q_CALLBACK_PROC WINAPI SetCallback(N2Q_CALLBACK_PROC Callback);

// n2q_test.cpp
#include "stdafx.h"
#include "windows.h"
#include "n2q_lib.h"

#pragma comment (lib, "n2q_lib.lib")

int WINAPI CallbackProc(N2Q_MESSAGE* msg)
{
    printf("Sev=%d; Code=%d; Msg=%s\n", msg->nSeverity, msg->dwCode, msg->szMsg);
    return 1;
}

int main(int argc, char* argv[])
{
    HN2QCONN hConn = N2QLibConnCreate("C:\\Temp\\n2q.ini", "local", CallbackProc);
    
    if (!N2QLibConnectQuik(hConn))                                  
        printf("error connect to server.");

    if(!N2QLibIsConnected(hConn))                                   
        printf("error connect to server.");
}

After compiling the sample C++ code, the connection is successful!

I want to make JNA code for calls to this native library.

// Java
import com.sun.jna.*;

public class JavaCallback implements Callback {
    public int callback (N2q_message msg) {
        System.out.println(msg.szMsg);
        return 1;
    }
}

public class N2q_message extends Structure {
    int nSeverity;
    int dwCode;
    String szMsg = new String();
    public N2q_message() {
        dwCode=0; 
        nSeverity=0; 
        szMsg="";
    };
    public N2q_message(N2q_message r) {
        dwCode=r.dwCode; 
        nSeverity=r.nSeverity; 
        szMsg=r.szMsg;
    };
}

public interface N2q_lib extends StdCallLibrary  {
    N2q_lib INSTANCE = (N2q_lib) Native.load(
            "n2q_lib.dll"
            , N2q_lib.class
            , Collections.singletonMap(Library.OPTION_STRING_ENCODING, "Cp1251")
    );
    Pointer N2QLibConnCreate(String lpszIniFile, String lpszSection, JavaCallback Callback);
    boolean N2QLibConnectQuik(Pointer hConn);
    boolean N2QLibIsConnected(Pointer hConn);
}

public class App {
  public static void main(String[] args) throws InterruptedException {
    System.setProperty("jna.library.path", System.getProperty("user.dir")+"\\dll");
    System.setProperty("jna.encoding", "Cp1251");
      
    Pointer hConn = N2q_lib.INSTANCE.N2QLibConnCreate(
            System.getProperty("jna.library.path")+"\\n2q.ini"
            , "local"
            , new JavaCallback());
    System.out.println(System.getProperty("jna.library.path")+"\\n2q.ini");
    System.out.println(hConn);
    Thread.sleep(1000);
    if (N2q_lib.INSTANCE.N2QLibConnectQuik(hConn) != true)
        System.out.println("error connect to server.");
    if (N2q_lib.INSTANCE.N2QLibIsConnected(hConn) != true)
        System.out.println("error connect to server.");
  }
}

When I start, I get connection errors:

C:\Users\123\myproject\dll\n2q.ini
native@0x5540598
error connect to server.
error connect to server.

Why can't I connect? Did I describe the objects for JNA incorrectly?

EDITED1: I missed that there were additional instructions in the n2q_msg.h file, which are probably important:

typedef int (WINAPI *N2Q_CALLBACK_PROC)(N2Q_MESSAGE*);
typedef int (WINAPI *N2Q_SEND_MESSAGE_PROC)(MSG_TITLE*, MSG_BODY*);

DECLARE_HANDLE (HN2QCONN);

typedef N2Q_CALLBACK_PROC (WINAPI *SET_CALLBACK_DLL_FUNC)(N2Q_CALLBACK_PROC);
typedef N2Q_SEND_MESSAGE_PROC (FAR *SET_SEND_MESSAGE_PROC_DLL_FUNC)(N2Q_SEND_MESSAGE_PROC);

// if I understand correctly, then this is not used
typedef BOOL (WINAPI *START_SEANCE_DLL_FUNC)();
typedef BOOL (WINAPI *STOP_SEANCE_DLL_FUNC)();
typedef BOOL (WINAPI *IS_SEANCE_STARTED_DLL_FUNC)();
typedef BOOL (WINAPI *IS_SEANCE_STOPPED_DLL_FUNC)();
typedef BOOL (WINAPI *GET_SETTINGS_DLL_FUNC)();

//__declspec(dllimport) N2Q_CALLBACK_PROC SetCallback(N2Q_CALLBACK_PROC Callback);
extern "C" N2Q_CALLBACK_PROC WINAPI SetCallback(N2Q_CALLBACK_PROC Callback);

But I don't understand how to read even the first line correctly :(

EDITED2: When I compiled n2q_test in C++ via Visual Studio, I had the same problem with the "Error connecting to server" errors. In the project, the "Character Set" property is set to "Use Unicode Character Set". When I found out that the library was built without unicode support, I set the value to "Use Multi-Byte Character Set" and everything worked. Maybe it affects the work of JNA->JNI->Java? But I don't understand how to explicitly change this.


Solution

  • Many thanks to all the experts who spent their time on my problem! The main reason for the errors was that all dependent libraries must be located in the root directory! And the n2q.ini file has encoding to winows-1251. A working example:

    public class App {
        
        public interface N2QLib extends StdCallLibrary {
    
            N2QLib INSTANCE = (N2QLib) Native.load("n2q_lib", N2QLib.class, Collections.singletonMap(Library.OPTION_STRING_ENCODING, "Cp1251"));
                
            interface N2Q_CALLBACK_PROC extends StdCallCallback {
                int callback(N2Q_MESSAGE msg);
            }
    
            @FieldOrder ({ "nSeverity", "dwCode", "szMsg" })
            class N2Q_MESSAGE extends Structure {
                public int nSeverity;
                public int dwCode;
                public byte[] szMsg = new byte[512];
    
                public N2Q_MESSAGE() {
                }
    
                public N2Q_MESSAGE(N2Q_MESSAGE r) {
                    nSeverity=r.nSeverity; 
                    dwCode=r.dwCode; 
                    szMsg=r.szMsg;
                }
            }
    
            Pointer N2QLibConnCreate(String lpszIniFile, String lpszSection, N2Q_CALLBACK_PROC Callback);
    
            boolean N2QLibConnectQuik(Pointer hConn);
            boolean N2QLibIsConnected(Pointer hConn);
            boolean N2QLibDisconnectQuik(Pointer hConn);
            boolean N2QLibConnDestroy(Pointer hConn);
            boolean N2QLibReconnect(Pointer hConn);
        }
    
        public static void main(String[] args) throws InterruptedException, UnsupportedEncodingException {
            System.setProperty("jna.encoding", "Cp1251");
            System.setProperty("jna.library.path", System.getProperty("user.dir"));
            
            N2QLib lib = N2QLib.INSTANCE;
            final String conf = new String(System.getProperty("user.dir")+"\\n2q.ini");
            final String serv = new String("local");
            
            Pointer hConn = lib.N2QLibConnCreate(conf, serv, new N2QLib.N2Q_CALLBACK_PROC() {
                @Override
                public int callback(N2QLib.N2Q_MESSAGE msg) {
                    return 1;
                }
            });
            
            System.out.println(LocalDateTime.now());
            if (!lib.N2QLibConnectQuik(hConn))
            {
                System.out.println("Error - connecting to server.");
                System.out.println(lib.N2QLibIsConnected(hConn));
            } else {
                System.out.println("Successfully connected.");
                System.out.println(lib.N2QLibIsConnected(hConn));
            }
            if (!lib.N2QLibIsConnected(hConn)) {
                System.out.println("Error - was not connected to server.");
            } else {
                Thread.sleep(3000);
                if(lib.N2QLibDisconnectQuik(hConn))
                {
                    System.out.println("Successfully disconnected.");
                }
                if(lib.N2QLibConnDestroy(hConn))
                {
                    System.out.println("Successfully connect destroy.");
                }
                
                Native.unregister(lib.getClass());
                System.gc();
            }
        }
    }
    

    But I found another problem 😊

    If I use a native program to test the library n2q_lib.dll, then after starting, successfully connecting, disconnecting and exit, a TCP connection to port 16100 appears in the operating system on several seconds and closes. But if i use JNA to test the library, then after the program is successfully completed, socket 16100 remains in the operating system with the TIME_WAIT status for 2 minutes (but this is not an active session, the 16100 connection is closed on the server side). The TIME_WAIT status is the standard behavior according to the TCP documentation. But I don't understand why it doesn't close (as in the case of a native application)?