Koromix / koffi

Fast and easy-to-use C FFI module for Node.js
https://koffi.dev/
MIT License
191 stars 3 forks source link

[koffi] Code execution stops when calling DLL methods using Koffi #7

Open IgorSamer opened 2 years ago

IgorSamer commented 2 years ago

First off, congratulations on your project! I was using node-ffi-napi, but when I found Koffi and saw the benchmarks I got really excited to migrate from node-ffi-napi to Koffi.

I'm able to load my DLL normally and declare some methods, however, when comparing a code I made for tests, I noticed that the results are different than expected.

The DLL is used to communicate with turnstiles and to do the migration I just made a small snippet of code to do the initial tests.

Following is my code using node-ffi-napi and working as expected:

const ffi = require('ffi-napi');

const turnstile = new ffi.Library('turnstile', {
    'CloseCommunicationPort': ['char', []],
    'SetConnectionType': ['char', ['int']],
    'OpenCommunicationPort': ['char', ['int']]
});

let status = 'close';

setInterval(() => {
    console.log(status);

    switch (status) {
        case 'close':
            if (turnstile.CloseCommunicationPort() == 0) status = 'set';
            break;

        case 'set':
            if (turnstile.SetConnectionType(2) == 0) status = 'open';
            break;

        case 'open':
            status = turnstile.OpenCommunicationPort(3570);
            break;
    }
}, 300);

And the result in console is:

PS C:\Users\Igor\Desktop\MyApp> node index close set open 8 8 8 ...

Just to clarify, when an action is successfully executed, the value 0 is returned by the DLL. Otherwise, another integer is returned with the error code. In this case the value of OpenCommunicationPort is 8 because the physical turnstile is not currently connected, so it is the expected result and the setInterval loop continues until I stop the code execution via CTRL + C.

And here's the code I'm trying to adapt to use in the same way with Koffi:

const koffi = require('koffi');

const turnstile = koffi.load('turnstile.dll');

const closeCommunicationPort = turnstile.stdcall('CloseCommunicationPort', 'char', []);
const setConnectionType = turnstile.stdcall('SetConnectionType', 'char', ['int']);
const openCommunicationPort = turnstile.stdcall('OpenCommunicationPort', 'char', ['int']);

let status = 'close';

setInterval(() => {
    console.log(status);

    switch (status) {
        case 'close':
            if (closeCommunicationPort() == 0) status = 'set';
            break;

        case 'set':
            if (setConnectionType(2) == 0) status = 'open';
            break;

        case 'open':
            status = openCommunicationPort(3570);
            break;
    }
}, 300);

And the result in console is:

PS C:\Users\Igor\Desktop\KoffiTest> node index close set PS C:\Users\Igor\Desktop\KoffiTest>

When running the above code, the loop breaks and execution ends immediately after I set status = 'set'. That is, it seems that the code is terminated as soon as it tries to execute the setConnectionType method. I also tried going from close to open status directly just to check the results, but the same behavior occurs and the openCommunicationPort method is not even called, thus terminating the execution.

I also tested it with the code outside the loop to check the results, as follows:

const koffi = require('koffi');

const turnstile = koffi.load('turnstile.dll');

const closeCommunicationPort = turnstile.stdcall('CloseCommunicationPort', 'char', []);
const setConnectionType = turnstile.stdcall('SetConnectionType', 'char', ['int']);
const openCommunicationPort = turnstile.stdcall('OpenCommunicationPort', 'char', ['int']);

if (closeCommunicationPort() == 0) {
    console.log('Closed!');

    if (setConnectionType(2) == 0) {
        console.log('Setted!');

        if (openCommunicationPort(3570) == 0) {
            console.log('Opened!');
        } else {
            console.log('Error on open!');
        }
    } else {
        console.log('Error on set!');
    }
} else {
    console.log('Error on close!');
}

And the result was:

PS C:\Users\Igor\Desktop\KoffiTest> node index Closed! PS C:\Users\Igor\Desktop\KoffiTest>

How can I handle this?

Koromix commented 2 years ago

Hi!

Looks like it crashes when calling SetConnectionType(). It might come from Koffi, or from a declaration mismatch. Hard to say without seeing the turnstile code.

Could you send me the turnstile code so I can test this? Send it here if you don't want to make it public: niels.martignene@protonmail.com

Regards,

Niels

EDIT : or, at least, the DLL itself :)

IgorSamer commented 2 years ago

Actually I confused the error code. Code 8 does not mean that the turnstile is not connected, but rather that:

"There was a GPF error inside the DLL. An exception occurred inside the DLL."

According to the turnstile integration documentation, this error can occur if the .NET Framework 3.5 is not installed/enabled (I formatted my computer recently and so I ended up forgetting some dependencies). After enabling it, the code now works normally as expected (just like in the node-ffi-napi example).

However, I believe that even if I had not yet discovered the cause of the problem and the error persisted, the correct thing would be for Koffi to return the value (as well as node-ffi-napi, which correctly returned the value 8) instead of terminating code execution, correct?

I don't know if the information above will be enough to help you deal with this type of situation in Koffi, but if you still need it I can send you DLL's and documentation in a way that makes it easier for you to test.

I say this because all the example code, methods and documentation provided by the turnstile manufacturer are in portuguese (I'm from Brazil), and in this case I would translate and format the documentation only with the necessary information for you to be able to test this same code snippet that I did.

IgorSamer commented 2 years ago

By the way, I would like to take the opportunity to ask a question regarding pointers. If it is not appropriate to continue here, please let me know so I can delete this comment and create another issue.

To be honest, I understand practically nothing about DLL's, let alone programming in C. The first and only time I needed to interact with a DLL was actually through Node (since my desktop app is made in Electron) in order to communicate with some models of turnstiles, as in this case.

I understand that your documentation is very well written and with many examples, but even so, I'm having trouble understanding the correct way to interact with pointers.

I have the following code to get the turnstile date and time using node-ffi-napi:

const ffi = require('ffi-napi');
const ref = require('ref-napi');

const refInt = ref.refType('int');

const turnstile = new ffi.Library('turnstile', {
    // Method to get the current datetime on the tunstile (params: turnstileId, day, month, year, hour, minute, second)
    'GetDateTime': ['char', ['int', refInt, refInt, refInt, refInt, refInt, refInt]]
});

let day = ref.alloc('int');
let month = ref.alloc('int');
let year = ref.alloc('int');
let hour = ref.alloc('int');
let minute = ref.alloc('int');
let second = ref.alloc('int');

if (turnstile.GetDateTime(1, day, month, year, hour, minute, second) == 0) {
    console.log(day);
    console.log(month);
    console.log(year);
    console.log(hour);
    console.log(minute);
    console.log(second);
}

And to better understand how Koffi works, I wrote the following code based on the documentation:

const koffi = require('koffi');
const lib = koffi.load('user32.dll');

const POINT = koffi.struct({
    x: 'int',
    y: 'int'
});

const GetCursorPos = lib.stdcall('GetCursorPos', 'bool', [koffi.out(koffi.pointer(POINT))]);

let pos = {};

if (!GetCursorPos(pos)) throw new Error('Failed to get cursor position');

console.log(pos);

It works correctly but in this example we use the struct to return an object. However, I only need to return parameters of type int. In that case, should I also use struct or is there another way?

I thought of something like:

const GetDateTime = turnstile.stdcall('GetDateTime', 'char', ['int', koffi.out(koffi.pointer('int')), koffi.out(koffi.pointer('int')), koffi.out(koffi.pointer('int')), koffi.out(koffi.pointer('int')), koffi.out(koffi.pointer('int')), koffi.out(koffi.pointer('int'))]);

let day = [null];
let month = [null];
let year = [null];
let hour = [null];
let minute = [null];
let second = [null];

if (GetDateTime(1, day, month, year, hour, minute, second) != 0) throw new Error('Failed to get datetime');

console.log(day);
console.log(month);
console.log(year);
console.log(hour);
console.log(minute);
console.log(second);

Is the code correct? This method can only be called with the turnstile already connected and I will only be able to test it on the physical turnstile from tomorrow, but I would like to clear this doubt.

And in that case should I use koffi.dispose method to avoid some overhead?

Thank you very much in advance!

Koromix commented 2 years ago

For the first question/bug, it would be easier if you send me the code/DLL/manual (everything you can) privately. I'll try to manage with Google Translate ;)

niels.martignene@protonmail.com

Regarding your second question, yes this is how you would do it, if the function is indeed declared as:

char __stdcall GetDateTime(int, int *day, int *month, int *year, int *hour, int *min, int *sec);

Please note that you can also use this syntax with Koffi:

// Same thing, use the syntax you prefer basically :)
const GetDateTime = turnstile.func('char __stdcall GetDateTime(int, _Out_ int *day, _Out_ int *month, _Out_ int *year, _Out_ int *hour, _Out_ int *min, _Out_ int *sec)');

You don't need to use koffi.dispose in this case, which is only needed for long-lived callbacks where you pass a JS function to a C function expecting a callback.

IgorSamer commented 2 years ago

Great! Thank you for the clarifications!

Actually, for this specific turnstile model the manufacturer makes the SDK publicly available, so just go to https://suporte.topdata.com.br/suporte/sdk-easyinner/ and then click on the download link. An installer will be downloaded and through it you will get all DLL files, manuals and examples in Visual Basic, Delphi, C# and Java.

If you have any questions or need me to do tests on the physical turnstile, just let me know! Thank you so much again and congratulations for the great work!

Koromix commented 2 years ago

Okay, I've played with the DLL a bit.

First, I've changed your declarations. Per the documentation, CloseCommunicationPort does not return a value (so there is nothing to check). And I've adjusted the parameter types.

const koffi = require('koffi');

let turnstile = koffi.load('EasyInner.dll');

const openCommunicationPort = turnstile.func('uint8_t __stdcall AbrirPortaComunicacao(int port)');
const closeCommunicationPort = turnstile.func('void __stdcall FecharPortaComunicacao()');
const setConnectionType = turnstile.func('uint8_t __stdcall DefinirTipoConexao(uint8_t type)');
const getDateTime = turnstile.func(`char __stdcall ReceberRelogio(int inner, _Out_ uint8_t *day, _Out_ uint8_t *month, _Out_ uint8_t *year,
                                                                             _Out_ uint8_t *hour, _Out_ uint8_t *min, _Out_ uint8_t *sec)`);

// This function returns void (no return value)
closeCommunicationPort();

if (setConnectionType(2) == 0) {
    console.log('Setted!');

    if (openCommunicationPort(3570) == 0) {
        console.log('Opened!');
    } else {
        console.log('Error on open!');
    }
} else {
    console.log('Error on set!');
}

I haven't yet tried to reproduce the crash you mentioned when the .NET Framework 3.5 is not installed.

When I tried to declare GetDateTime(), I hit a limitation in Koffi: only four output parameters were supported. I've raised the limit to 8 and released Koffi 2.1.2.

IgorSamer commented 2 years ago

Thanks for the example! Is there any advantage (either in performance or any other aspect) to use func() as in your examples or use stdcall() as in mine?

And since you mentioned the output parameter limitation (which I didn't know yet), could you increase it from 8 to 9? One of the methods I use is ReceberDadosOnLine() and it has 9 output parameters:

ReceberDadosOnLine(int Inner, ref byte Origem, ref byte Complemento, StringBuilder Cartao, ref byte Dia, ref byte Mes, ref byte Ano, ref byte Hora, ref byte Minuto, ref byte Segundo) - all parameters after int Inner are data I must receive.

Koromix commented 2 years ago

No advantage to either, I prefer the latter but you're free to prefer the former :)

I've raised the limitation to 16, in Koffi 2.1.3!

IgorSamer commented 2 years ago

Hi Niels!

I've been researching the possibilities in order to better understand the reason for the termination of the code execution and after investing a long time in this I ended up finding the node-segfault-handler library. So now my test code looks like this:

const SegfaultHandler = require('segfault-handler');

SegfaultHandler.registerHandler('crash.log');

const koffi = require('koffi');

const EasyInner = koffi.load('EasyInner.dll');

const closeCommunicationPort = EasyInner.func('void __stdcall FecharPortaComunicacao()');
const defineConnectionType = EasyInner.func('uint8_t __stdcall DefinirTipoConexao(uint8_t Tipo)');
const openCommunicationPort = EasyInner.func('uint8_t __stdcall AbrirPortaComunicacao(int Porta)');

let status = 'close';

setInterval(() => {
    console.log(status);

    switch (status) {
        case 'close':
            closeCommunicationPort();

            status = 'define';
            break;

        case 'define':
            if (defineConnectionType(2) == 0) status = 'open';
            break;

        case 'open':
            status = openCommunicationPort(3570);
            break;
    }
}, 300);

And now I have the following crash log after Node dies:

PID 8492 received SIGSEGV for address: 0x7614df72 SymInit: Symbol-SearchPath: '.;C:\Users\igore\OneDrive\Área de Trabalho\Desenvolvimento\C-FFI;C:\Program Files\nodejs;C:\WINDOWS;C:\WINDOWS\system32;SRVC:\websymbolshttp://msdl.microsoft.com/download/symbols;', symOptions: 530, UserName: 'igore' OS-Version: 10.0.19045 () 0x300-0x1 C:\Users\igore\OneDrive\Área de Trabalho\Desenvolvimento\C-FFI\node_modules\segfault-handler\src\StackWalker.cpp (922): StackWalker::ShowCallstack C:\Users\igore\OneDrive\Área de Trabalho\Desenvolvimento\C-FFI\node_modules\segfault-handler\src\segfault-handler.cpp (242): segfault_handler 778ACEE8 (ntdll): (filename not available): LdrSetDllManifestProber 778A916B (ntdll): (filename not available): RtlUnwind 778B5006 (ntdll): (filename not available): KiUserExceptionDispatcher 1005CA99 (EasyInner): (filename not available): RespostaListarUsuariosBio 10040206 (EasyInner): (filename not available): RespostaListarUsuariosBio 1003FB71 (EasyInner): (filename not available): RespostaListarUsuariosBio 10024F45 (EasyInner): (filename not available): RespostaListarUsuariosBio 10005CE5 (EasyInner): (filename not available): RespostaListarUsuariosBio 10005AB8 (EasyInner): (filename not available): RespostaListarUsuariosBio 10004B1B (EasyInner): (filename not available): RespostaListarUsuariosBio 10006260 (EasyInner): (filename not available): RespostaListarUsuariosBio 100268E7 (EasyInner): (filename not available): RespostaListarUsuariosBio 74FD6457 (koffi): (filename not available): (function-name not available)

I found it strange that the RespostaListarUsuariosBio method from DLL is mentioned, but I don't call it from anywhere. Anyway, as I said before, when testing the exact same code structure, but using node-ffi-napi, the code works correctly (without terminating execution) and returns the error value normally via openCommunicationPort method until I exit myself execution via CTRL + C.

I really hope that this information can help you find the error in Koffi, as I really liked its structure and documentation and of course all your support. I don't want to have to continue with node-ffi-napi, so if you need any other data, just give me the instructions and I'll do my best to help.

Regards!

EDIT: I'm using Node.js 18.12.0 (32-bit)

Koromix commented 2 years ago

Thanks for the information, I'll look into this this week-end ++ ;) Is this on Windows 11, 10 or 7 (or even older)?

IgorSamer commented 2 years ago

Windows 10 Home (64-bit). I'm using Node 32-bit because otherwise I get the error: Failed to load shared library: %1 is not a valid Win32 application.

Koromix commented 2 years ago

Thanks! Looking into it during the week-end :)

I'm merging my two monorepos into one, that's why the issue has been transferred ;)

IgorSamer commented 2 years ago

Thanks! Looking into it during the week-end :)

I'm merging my two monorepos into one, that's why the issue has been transferred ;)

Got it!

And just to let you know and clear all doubts, I tested the following example with node-ffi-napi and just added node-segfault-handler to see if anything happened. And the truth is that the same error occurs when using node-ffi-napi, but the execution is not terminated (if it is in a loop, for example) and Node returns the expected value.

const SegfaultHandler = require('segfault-handler');

SegfaultHandler.registerHandler('crash.log');

const ffi = require('ffi-napi');

const EasyInner = new ffi.Library('EasyInner', {
    'FecharPortaComunicacao': ['void', []],
    'DefinirTipoConexao': ['char', ['int']],
    'AbrirPortaComunicacao': ['char', ['int']]
});

EasyInner.FecharPortaComunicacao();

if (EasyInner.DefinirTipoConexao(2) == 0) {
    console.log('Setted!');

    const open = EasyInner.AbrirPortaComunicacao(3570);

    if (open == 0) {
        console.log('Opened!');
    } else {
        console.log(`Error on open! (${open})`);
    }
} else {
    console.log('Error on set!');
}

And the result is:

PS C:\Users\igore\OneDrive\Área de Trabalho\Desenvolvimento\NAPI> node index
ERROR 1 (below)
Setted!
ERROR 2 (below)
Error on open! (8)
ERROR 3 (below)
PS C:\Users\igore\OneDrive\Área de Trabalho\Desenvolvimento\NAPI>

ERROR 1:

PID 284 received SIGSEGV for address: 0x7614df72 SymInit: Symbol-SearchPath: '.;C:\Users\igore\OneDrive\�rea de Trabalho\Desenvolvimento\NAPI;C:\Program Files\nodejs;C:\WINDOWS;C:\WINDOWS\system32;SRVC:\websymbolshttp://msdl.microsoft.com/download/symbols;', symOptions: 530, UserName: 'igore' OS-Version: 10.0.19045 () 0x300-0x1 C:\Users\igore\OneDrive\�rea de Trabalho\Desenvolvimento\NAPI\node_modules\segfault-handler\src\StackWalker.cpp (922): StackWalker::ShowCallstack C:\Users\igore\OneDrive\�rea de Trabalho\Desenvolvimento\NAPI\node_modules\segfault-handler\src\segfault-handler.cpp (242): segfault_handler 778ACEE8 (ntdll): (filename not available): LdrSetDllManifestProber 778A916B (ntdll): (filename not available): RtlUnwind 778B5006 (ntdll): (filename not available): KiUserExceptionDispatcher 1005CA99 (EasyInner): (filename not available): RespostaListarUsuariosBio 10040206 (EasyInner): (filename not available): RespostaListarUsuariosBio 1003FB71 (EasyInner): (filename not available): RespostaListarUsuariosBio 10024F45 (EasyInner): (filename not available): RespostaListarUsuariosBio 10005CE5 (EasyInner): (filename not available): RespostaListarUsuariosBio 10005AB8 (EasyInner): (filename not available): RespostaListarUsuariosBio 10004B1B (EasyInner): (filename not available): RespostaListarUsuariosBio 10006260 (EasyInner): (filename not available): RespostaListarUsuariosBio 100268E7 (EasyInner): (filename not available): RespostaListarUsuariosBio 74EAAD68 (ffi_bindings): (filename not available): ffi_call_i386 C:\Users\igore\OneDrive\�rea de Trabalho\Desenvolvimento\NAPI\node_modules\ffi-napi\deps\libffi\src\x86\ffi.c (392): ffi_call_int C:\Users\igore\OneDrive\�rea de Trabalho\Desenvolvimento\NAPI\node_modules\ffi-napi\src\ffi.cc (255): FFI::FFI::FFICall C:\Users\igore\OneDrive\�rea de Trabalho\Desenvolvimento\NAPI\node_modules\node-addon-api\napi-inl.h (74): Napi::details::WrapCallback< > C:\Users\igore\OneDrive\�rea de Trabalho\Desenvolvimento\NAPI\node_modules\node-addon-api\napi-inl.h (125): Napi::details::CallbackData<void (__cdecl*)(Napi::CallbackInfo const &),void>::Wrapper 00F58F7B (node): (filename not available): cppgc::internal::LargePage::PayloadStart 017974EB (node): (filename not available): v8::internal::Builtins::code 017976DF (node): (filename not available): v8::internal::Builtins::code 017975A6 (node): (filename not available): v8::internal::Builtins::code 0185826B (node): (filename not available): v8::internal::SetupIsolateDelegate::SetupHeap

ERROR 2:

PID 284 received SIGSEGV for address: 0x7614df72 SymInit: Symbol-SearchPath: '.;C:\Users\igore\OneDrive\�rea de Trabalho\Desenvolvimento\NAPI;C:\Program Files\nodejs;C:\WINDOWS;C:\WINDOWS\system32;SRVC:\websymbolshttp://msdl.microsoft.com/download/symbols;', symOptions: 530, UserName: 'igore' OS-Version: 10.0.19045 () 0x300-0x1 C:\Users\igore\OneDrive\�rea de Trabalho\Desenvolvimento\NAPI\node_modules\segfault-handler\src\StackWalker.cpp (922): StackWalker::ShowCallstack C:\Users\igore\OneDrive\�rea de Trabalho\Desenvolvimento\NAPI\node_modules\segfault-handler\src\segfault-handler.cpp (242): segfault_handler 778ACEE8 (ntdll): (filename not available): LdrSetDllManifestProber 778A916B (ntdll): (filename not available): RtlUnwind 778B5006 (ntdll): (filename not available): KiUserExceptionDispatcher 1005CA99 (EasyInner): (filename not available): RespostaListarUsuariosBio 10040206 (EasyInner): (filename not available): RespostaListarUsuariosBio 1003FB71 (EasyInner): (filename not available): RespostaListarUsuariosBio 1002502C (EasyInner): (filename not available): RespostaListarUsuariosBio 10004FA6 (EasyInner): (filename not available): RespostaListarUsuariosBio 10006334 (EasyInner): (filename not available): RespostaListarUsuariosBio 10026937 (EasyInner): (filename not available): RespostaListarUsuariosBio 74EAAD68 (ffi_bindings): (filename not available): ffi_call_i386 C:\Users\igore\OneDrive\�rea de Trabalho\Desenvolvimento\NAPI\node_modules\ffi-napi\deps\libffi\src\x86\ffi.c (392): ffi_call_int C:\Users\igore\OneDrive\�rea de Trabalho\Desenvolvimento\NAPI\node_modules\ffi-napi\src\ffi.cc (255): FFI::FFI::FFICall C:\Users\igore\OneDrive\�rea de Trabalho\Desenvolvimento\NAPI\node_modules\node-addon-api\napi-inl.h (74): Napi::details::WrapCallback< > C:\Users\igore\OneDrive\�rea de Trabalho\Desenvolvimento\NAPI\node_modules\node-addon-api\napi-inl.h (125): Napi::details::CallbackData<void (__cdecl*)(Napi::CallbackInfo const &),void>::Wrapper 00F58F7B (node): (filename not available): cppgc::internal::LargePage::PayloadStart 017974EB (node): (filename not available): v8::internal::Builtins::code 017976DF (node): (filename not available): v8::internal::Builtins::code 017975A6 (node): (filename not available): v8::internal::Builtins::code 0185826B (node): (filename not available): v8::internal::SetupIsolateDelegate::SetupHeap

ERROR 3:

PID 284 received SIGSEGV for address: 0x7614df72 SymInit: Symbol-SearchPath: '.;C:\Users\igore\OneDrive\�rea de Trabalho\Desenvolvimento\NAPI;C:\Program Files\nodejs;C:\WINDOWS;C:\WINDOWS\system32;SRVC:\websymbolshttp://msdl.microsoft.com/download/symbols;', symOptions: 530, UserName: 'igore' OS-Version: 10.0.19045 () 0x300-0x1 C:\Users\igore\OneDrive\�rea de Trabalho\Desenvolvimento\NAPI\node_modules\segfault-handler\src\StackWalker.cpp (922): StackWalker::ShowCallstack C:\Users\igore\OneDrive\�rea de Trabalho\Desenvolvimento\NAPI\node_modules\segfault-handler\src\segfault-handler.cpp (242): segfault_handler 778ACEE8 (ntdll): (filename not available): LdrSetDllManifestProber 778A916B (ntdll): (filename not available): RtlUnwind 778B5006 (ntdll): (filename not available): KiUserExceptionDispatcher 1005CA99 (EasyInner): (filename not available): RespostaListarUsuariosBio 10040206 (EasyInner): (filename not available): RespostaListarUsuariosBio 1003FB71 (EasyInner): (filename not available): RespostaListarUsuariosBio 10024F45 (EasyInner): (filename not available): RespostaListarUsuariosBio 10005CE5 (EasyInner): (filename not available): RespostaListarUsuariosBio 10005AB8 (EasyInner): (filename not available): RespostaListarUsuariosBio 10004A75 (EasyInner): (filename not available): RespostaListarUsuariosBio 100061C5 (EasyInner): (filename not available): RespostaListarUsuariosBio 100060BF (EasyInner): (filename not available): RespostaListarUsuariosBio 1003F805 (EasyInner): (filename not available): RespostaListarUsuariosBio 1003F70A (EasyInner): (filename not available): RespostaListarUsuariosBio 1005EC10 (EasyInner): (filename not available): RespostaListarUsuariosBio 778B2AB6 (ntdll): (filename not available): RtlIpv6AddressToStringA 7788DE02 (ntdll): (filename not available): RtlActivateActivationContextUnsafeFast 7789D912 (ntdll): (filename not available): LdrShutdownProcess 7789D795 (ntdll): (filename not available): RtlExitUserProcess 772D4113 (KERNEL32): (filename not available): ExitProcess 01CD6116 (node): (filename not available): v8::internal::compiler::ToString 01CD60D4 (node): (filename not available): v8::internal::compiler::ToString 01CD61E4 (node): (filename not available): v8::internal::compiler::ToString 01CC760D (node): (filename not available): v8::internal::compiler::ToString 772CFA29 (KERNEL32): (filename not available): BaseThreadInitThunk 778A7BBE (ntdll): (filename not available): RtlGetAppContainerNamedObjectPath 778A7B8E (ntdll): (filename not available): RtlGetAppContainerNamedObjectPath

Knowing this, the only difference is that Koffi terminates execution when detecting these errors while node-ffi-napi does not. That way, when using Koffi in my project with Electron the program would close whenever this failure occurred.

Anyway, I'll be spending the entire weekend at one of my client's establishments just to do these tests directly at the physical turnstile. This way it will be easier to test the real behavior of the equipment and the code to be able to give you more assertive information :)

Koromix commented 2 years ago

I've made progress on this issue :)

So far, I think the problem comes from Structured Exception Handling (SEH) being broken inside the DLL because Koffi uses a separate stack (unlike libffi/node-ffi which reuse the caller stack). The Koffi approach has some advantages but it may cause the NT/SEH unwind code to abort because the stack is outside of the native stack.

This is my theory anyway, and I could be wrong (not an expert on SEH, and the guts of SEH are badly documented). The EasyInner DLL sems to use SEH to catch _com_error exception, which happens when .NET 3.5 is not installed, and then return 8. But because SEH may not work when Koffi is involved, the exception handlign seems to not happen, the code goes on through until another fatal error happens (segmentation fault) and everything crashes.

This means outside of the .NET error, I don't think anything inside EasyInner will fail with Koffi.

I will fix this issue but it is pretty involved, and I'd like to keep the separate stack because it brings us good things. There may be a way to get the both of both worlds, I'll make some tests next week and keep you informed ;)

IgorSamer commented 2 years ago

Allright! Good to hear that! :)

I've made some tests along this morning with my client's turnstile and they were promising. To be honest, I had some crashes again and right now I don't know if the problem involves EasyInner or if it is related to Electron (or both), since after connecting I need to keep a loop to interact with the turnstile through a state machine (as in SDK EasyInner/Manuais/Manual de Desenvolvimento - EasyInner.pdf - page 9) and for that reason I don't know if there may be a memory leak - I will do new tests again first directly with Node and later using Electron.

Anyway, tomorrow I will contact the manufacturer again to rent a physical equipment so that I can test more freely on my main computer for development (with the .NET issue already resolved) and thus have more concrete data to be able to tell you about the behavior of libraries.

And since I talked about memory leak, I accept opinions about good practices regarding state machine loops and DLL calls (if I keep the setInterval or if I should use another approach, for example) 🤓

Thanks again for all the effort and attention and as soon as I have the physical turnstile already configured on my computer I will do new tests and let you know!

IgorSamer commented 2 years ago

After solving the .NET issue and several tests it seems to be a problem/conflict when using Koffi with Electron.

I made two identical projects using Koffi, one to run directly with Node and the other to run with Electron. With Node, everything works as expected but with Electron the first problem appears when I try to call the FecharPortaComunicacao, where it simply terminates the execution (regardless of where I call the method).

When I remove the FecharPortaComunicacao method the code continues its execution but the same behavior occurs in a completely random way, where sometimes the execution is terminated immediately, other times it occurs only after a few seconds and in some (rare) cases the problem does not seem to happen - in fact I think it's just a matter of time.

I ran these tests without having the physical turnstile connected, so the expected result in the loop is always 1 (when an error occurs when receiving data from the turnstile because it is disconnected), this way, you can also reproduce the same behavior in your tests.

I recorded a short video showing exactly how it happens:

knCQDhtj69

Both examples are with "koffi": "^2.1.3" and in the case of Electron it is with "^21.2.2" in package.json.

Node.js index.js file:

const koffi = require('koffi');

let EasyInner = null;

const initEasyInner = () => {
    try {
        const lib = koffi.load('EasyInner.dll');

        EasyInner = {
            FecharPortaComunicacao: lib.func('void __stdcall FecharPortaComunicacao()'),
            DefinirTipoConexao: lib.func('uint8_t __stdcall DefinirTipoConexao(uint8_t Tipo)'),
            AbrirPortaComunicacao: lib.func('uint8_t __stdcall AbrirPortaComunicacao(int Porta)'),
            ReceberRelogio: lib.func('uint8_t __stdcall ReceberRelogio(int Inner, _Out_ uint8_t *Dia, _Out_ uint8_t *Mes, _Out_ uint8_t *Ano, _Out_ uint8_t *Hora, _Out_ uint8_t *Minuto, _Out_ uint8_t *Segundo)')
        };

        toggleTurnstileConnection();
    } catch (error) {
        console.log(error);
    }
};

const testTurnstileConnection = () => {
    let dia = [null];
    let mes = [null];
    let ano = [null];
    let hora = [null];
    let minuto = [null];
    let segundo = [null];

    return EasyInner.ReceberRelogio(1, dia, mes, ano, hora, minuto, segundo);
};

const toggleTurnstileConnection = () => {
    if (!EasyInner) {
        initEasyInner();

        return;
    }

    EasyInner.FecharPortaComunicacao();

    if (EasyInner.DefinirTipoConexao(2) == 0) {
        const AbrirPortaComunicacao = EasyInner.AbrirPortaComunicacao(3570);

        if (AbrirPortaComunicacao == 0) {
            setInterval(() => console.log(testTurnstileConnection()), 300);

            return;
        }

        console.log('Error on open!');

        return;
    }

    console.log('Error on set!');
};

toggleTurnstileConnection();

Electron main.js file:

const { app, BrowserWindow, Menu } = require('electron');
const koffi = require('koffi');

let EasyInner = null;

const createMenu = async (mainWindow) => {
    const webContents = mainWindow.webContents;

    const menu = Menu.buildFromTemplate([
        {
            label: 'File',
            submenu: [
                {
                    label: 'Test',
                    click: () => console.log('Test')
                },
                { type: 'separator' },
                {
                    label: 'Clear cache',
                    click: () => webContents.session.clearCache()
                },
                {
                    label: 'Clear storage data',
                    click: () => webContents.session.clearStorageData()
                },
                { type: 'separator' },
                { role: 'quit' }
            ]
        },
        {
            label: 'Turnstile',
            submenu: [
                {
                    label: 'Toggle connection',
                    click: () => toggleTurnstileConnection()
                }
            ]
        }
    ]);

    Menu.setApplicationMenu(menu);
};

const initEasyInner = () => {
    try {
        const lib = koffi.load('EasyInner.dll');

        EasyInner = {
            FecharPortaComunicacao: lib.func('void __stdcall FecharPortaComunicacao()'),
            DefinirTipoConexao: lib.func('uint8_t __stdcall DefinirTipoConexao(uint8_t Tipo)'),
            AbrirPortaComunicacao: lib.func('uint8_t __stdcall AbrirPortaComunicacao(int Porta)'),
            ReceberRelogio: lib.func('uint8_t __stdcall ReceberRelogio(int Inner, _Out_ uint8_t *Dia, _Out_ uint8_t *Mes, _Out_ uint8_t *Ano, _Out_ uint8_t *Hora, _Out_ uint8_t *Minuto, _Out_ uint8_t *Segundo)')
        };

        toggleTurnstileConnection();
    } catch (error) {
        console.log(error);
    }
};

const testTurnstileConnection = () => {
    let dia = [null];
    let mes = [null];
    let ano = [null];
    let hora = [null];
    let minuto = [null];
    let segundo = [null];

    return EasyInner.ReceberRelogio(1, dia, mes, ano, hora, minuto, segundo);
};

const toggleTurnstileConnection = () => {
    if (!EasyInner) {
        initEasyInner();

        return;
    }

    EasyInner.FecharPortaComunicacao();

    if (EasyInner.DefinirTipoConexao(2) == 0) {
        const AbrirPortaComunicacao = EasyInner.AbrirPortaComunicacao(3570);

        if (AbrirPortaComunicacao == 0) {
            setInterval(() => console.log(testTurnstileConnection()), 300);

            return;
        }

        console.log('Error on open!');

        return;
    }

    console.log('Error on set!');
};

const createWindow = () => {
    const mainWindow = new BrowserWindow({ backgroundColor: '#009688' });

    createMenu(mainWindow);

    mainWindow.loadURL('https://koffi.dev/');
};

app.whenReady().then(() => {
    createWindow();

    app.on('activate', () => {
        if (BrowserWindow.getAllWindows().length === 0) createWindow();
    });
});

app.on('window-all-closed', () => {
    if (process.platform !== 'darwin') app.quit();
});

In this example, I only used these methods to make it simpler to reproduce without having the turnstile, but even with it connected the app in Electron is always closed when calling the methods in the sequence of the state machine (again, randomly and exactly like shown in the recording), unlike the example in Node that works normally also with the turnstile connected.

Any ideias? Hope this information can help! :)

Koromix commented 1 year ago

Thanks for the repro, I will try this weekend :)

IgorSamer commented 1 year ago

Any updates mate? 🙏🏻

Koromix commented 1 year ago

Sorry, just a lot to do at work >< I really want to work on this next week-end ++

Koromix commented 1 year ago

Good news :)

In the fiber git branch I've tried to use Win32 fibers to make sure the thread stack limits are set right when Koffi executes native code.

It seems to work, because with this change I get the "Error on open!" when .NET 3.5 is not installed (instead of a crash with latest Koffi release).

This branch has other problems right now (callbacks fail) but my hypothesis was correct and looks like I'm on the right track :)

Koromix commented 1 year ago

On the other hand I can confirm that this change is quite impactful for performance (33% slower at least).

So I think it will only get used when an explicit "__seh" flag is set in the function definition, or something like that.

Koromix commented 1 year ago

Okay, I've found a much better and easier way than using fibers... directly changing the TIB stack limits. Previous testing had left me thinking it was not possible/protected, but I guess I had messed up. This time it worked, which means no slowdown and no need for a special flag!

I've pushed Koffi 2.2.3-beta.1 to NPM, install with npm install koffi@beta.

Hopefully this solves your problems :) Fingers crossed!

Koromix commented 1 year ago

Pushed Koffi 2.2.3-beta.2 that should work better ++ ;)

IgorSamer commented 1 year ago

Good news!

The tests this time went as expected on my computer and the test program made in Electron works normally without crashing at any time!

I'm excited to be able to test this weekend with the physical turnstile connected and I'll let you know the results here as soon as I can :)

Koromix commented 1 year ago

Thanks! Let's hope for the best :) If the week-end goes well I'll release 2.2.3 ;)

Koromix commented 1 year ago

I've released Koffi 2.2.3 anyway!

Even if in the end it does not solve your issue completely, it's still a good fix :) We can always make a new release if something else happens.

Koromix commented 1 year ago

Any news? :)

IgorSamer commented 1 year ago

Sorry, I couldn't test it over the weekend because I had some unforeseen issues with my project and I had to fix them during those days. I had to reschedule the tests with the physical turnstile for this Thursday 🙌

Koromix commented 1 year ago

No problem, I'll wait!

I'll probably release 2.2.6 with new features for Koromix/koffi#45, just wanted to see if there's anything remaining to do for this issue too ;)

IgorSamer commented 1 year ago

Bringing you some news!

I finally managed to do the tests on the physical turnstile and had very promising results!

I was able to generate my app installer normally using electron-builder and after installing it on a Windows 11 computer everything worked great and this time with no random crashes!

Communication flows normally through all methods and a single detail is that when I call the FecharPortaComunicacao method to end communication with the turnstile the app stops working and then it ends up crashing. When calling this method before the first connection it works normally. The problem is after the connection is established, which makes me imagine that it has something to do with the fact that setInterval has accumulated some method calls that could not be executed before the FecharPortaComunicacao call.

I used the segfault-handler to generate the error log when calling the method and here it is:

PID 8780 received SIGSEGV for address: 0x76d80a82
SymInit: Symbol-SearchPath: '.;C:\Users\Igor\OneDrive\Área de Trabalho\KoffiTest;C:\Users\Igor\OneDrive\Área de Trabalho\KoffiTest\node_modules\electron\dist;C:\WINDOWS;C:\WINDOWS\system32;SRV*C:\websymbols*http://msdl.microsoft.com/download/symbols;', symOptions: 530, UserName: 'Igor'
OS-Version: 10.0.22000 () 0x300-0x1
PID 8780 received SIGSEGV for address: 0x76d80a82
SymInit: Symbol-SearchPath: '.;C:\Users\Igor\OneDrive\Área de Trabalho\KoffiTest;C:\Users\Igor\OneDrive\Área de Trabalho\KoffiTest\node_modules\electron\dist;C:\WINDOWS;C:\WINDOWS\system32;SRV*C:\websymbols*http://msdl.microsoft.com/download/symbols;', symOptions: 530, UserName: 'Igor'
OS-Version: 10.0.22000 () 0x300-0x1
C:\Users\Igor\OneDrive\Área de Trabalho\KoffiTest\node_modules\segfault-handler\src\StackWalker.cpp (922): StackWalker::ShowCallstack
C:\Users\Igor\OneDrive\Área de Trabalho\KoffiTest\node_modules\segfault-handler\src\StackWalker.cpp (922): StackWalker::ShowCallstack
C:\Users\Igor\OneDrive\Área de Trabalho\KoffiTest\node_modules\segfault-handler\src\segfault-handler.cpp (242): segfault_handler
7711E6F2 (ntdll): (filename not available): RtlIpv4AddressToStringA
771193B1 (ntdll): (filename not available): RtlUnwind
77127176 (ntdll): (filename not available): KiUserExceptionDispatcher
1709E4EC (mscorwks): (filename not available): GetPrivateContextsPerfCounters
170FCD70 (mscorwks): (filename not available): CorExeMain
5DECFB4D (mscorlib.ni): (filename not available): (function-name not available)
19654F81 ((module-name not available)): (filename not available): (function-name not available)
19654D18 ((module-name not available)): (filename not available): (function-name not available)
19650D3A ((module-name not available)): (filename not available): (function-name not available)
19650976 ((module-name not available)): (filename not available): (function-name not available)
19650917 ((module-name not available)): (filename not available): (function-name not available)
C:\Users\Igor\OneDrive\Área de Trabalho\KoffiTest\node_modules\segfault-handler\src\segfault-handler.cpp (242): segfault_handler
170A7539 (mscorwks): (filename not available): GetPrivateContextsPerfCounters
7711E6F2 (ntdll): (filename not available): RtlIpv4AddressToStringA
771193B1 (ntdll): (filename not available): RtlUnwind
170A7643 (mscorwks): (filename not available): GetPrivateContextsPerfCounters
77127176 (ntdll): (filename not available): KiUserExceptionDispatcher
170A77AC (mscorwks): (filename not available): GetPrivateContextsPerfCounters
1709E4EC (mscorwks): (filename not available): GetPrivateContextsPerfCounters
170FCD70 (mscorwks): (filename not available): CorExeMain
15C2A75D ((module-name not available)): (filename not available): (function-name not available)
5DECFB4D (mscorlib.ni): (filename not available): (function-name not available)
19654F81 ((module-name not available)): (filename not available): (function-name not available)
15F15E6F (EasyInner): (filename not available): RespostaListarUsuariosBio
19654D18 ((module-name not available)): (filename not available): (function-name not available)
19654DBB ((module-name not available)): (filename not available): (function-name not available)
15F1578F (EasyInner): (filename not available): RespostaListarUsuariosBio
1965263A ((module-name not available)): (filename not available): (function-name not available)
19DF4869 (System.ni): (filename not available): (function-name not available)
15EF56D9 (EasyInner): (filename not available): RespostaListarUsuariosBio
19DF793A (System.ni): (filename not available): (function-name not available)
15EF63D7 (EasyInner): (filename not available): RespostaListarUsuariosBio
16FA1B8C (mscorwks): (filename not available): (function-name not available)
15F16983 (EasyInner): (filename not available): RespostaListarUsuariosBio
6C14FD17 (koffi): (filename not available): (function-name not available)
16FB88E1 (mscorwks): (filename not available): (function-name not available)

Another strange behavior I noticed is that methods that return strings through parameters end up crashing the program, as is the case with:

lib.func('uint8_t __stdcall ColetarBilhete(int Inner, _Out_ uint8_t *Tipo, _Out_ uint8_t *Dia, _Out_ uint8_t *Mes, _Out_ uint8_t *Ano, _Out_ uint8_t *Hora, _Out_ uint8_t *Minuto, _Out_ string *Cartao)')

If I change from _Out_ string *Cartao to _Out_ uint8_t *Cartao the code works normally but the value returned in Cartao is wrong. The method declaration in C# is:

private static extern byte ColetarBilhete(int Inner, ref byte Tipo, ref byte Dia, ref byte Mes, ref byte Ano, ref byte Hora, ref byte Minuto, StringBuilder Cartao);

Which data type should I use for this case?

On another computer (the gym's main one) with Windows 7, the app is also installed normally, but it never even manages to get past the initial connection attempt phase and since at that moment I was already short on time I couldn't run the debug properly to get more clues of what was happening, but I'll be able to do new tests to get this information during the weekend.

Anyway, I'm already pretty excited about the initial results! Thanks for all the support so far!

nwrkbiz commented 1 year ago

@IgorSamer Strings as output parameter work quite well like so:

https://github.com/Koromix/koffi/issues/51

You need to know the length through.

IgorSamer commented 1 year ago

@nwrkbiz thanks for the tip! I didn't know that for strings I should use different data types like char or char16.

Now that I've found https://koffi.dev/types#fixed-size-string-buffers it's clearer what I should do.

Thank you and when testing I'll come back with the results!

IgorSamer commented 1 year ago

@nwrkbiz I tried several ways but I still couldn't return the expected string. I tried:

_Out_ char *Cartao / _Out_ char16 *Cartao / _Out_ char16_t *Cartao (in these ways the app doesn't crash like with string data type)

So I did the following:

let tipo = [null]
let dia = [null]
let mes = [null]
let ano = [null]
let hora = [null]
let minuto = [null]
let cartao = ['']

EasyInner.ColetarBilhete(1, tipo, dia, mes, ano, hora, minuto, cartao)

But when checking cartao the value is: [ 'ÒÇ░' ]

So I tried using TextDecoder just like you did here but it always converts the value to an empty string. What I tried was:

console.log(new TextDecoder('UTF-8').decode(new Int8Array(cartao[0]))) // empty string
console.log(new TextDecoder('UTF-8').decode(new Int16Array(cartao[0]))) // empty string
console.log(new TextDecoder('UTF-8').decode(new Int32Array(cartao[0]))) // empty string

Did I miss something?

IgorSamer commented 1 year ago

Hi @Koromix!

First of all, I would like to apologize for getting out of focus on the issue in the previous comments, I just took the opportunity to try to clear up some doubts while doing all the tests possible regarding the main issue in the meantime.

Anyway, I did countless tests on 5 different computers (3 physical and 2 virtual machines) and with the same program made in Electron, where the code implementation is the same and the only difference was the package used to communicate with the DLL. In one program I used node-ffi-napi (generating the .exe through electron-packager, since with electron-builder compilation errors occur) and in another program I used Koffi (generating the installer through electron-builder).

As I mentioned earlier the results have been very satisfactory since your last update where you fixed the crashes that occurred in random ways. However, there are still two problems that I was able to detect when using Koffi for my case:

  1. Windows 10 or higher: everything works correctly, however, when calling the FecharPortaComunicacao method (to close the communication with the turnstile) after having the connection established with the turnstile, the program crashes. When using the version with node-ffi-napi everything works normally;
  2. Windows 7 Professional: the program cannot start its connection with the turnstile and does not show any errors. It just keeps trying to connect as if the turnstile was configured with an IP other than that of the computer in question, which was not the case. When using the version with node-ffi-napi everything works normally.

Just like the other times I used the segfault-handler package to detect and generate error logs, and in cases where errors did happen I made sure to generate two logs for the same situation, as I figured that would make your investigations easier.

Here are the results of all the tests done:

OS Turnstile Connected Package Result Notes
Windows 11 Home - 64 bits NO NAPI OK
Windows 11 Home - 64 bits NO Koffi OK
Windows 11 Home - 64 bits YES NAPI OK
Windows 11 Home - 64 bits YES Koffi Crash on call FecharPortaComunicacao() after the connection is established crash1.log - crash2.log
Windows 10 Home - 64 bits NO NAPI OK
Windows 10 Home - 64 bits NO Koffi OK
Windows 10 Home - 64 bits YES NAPI OK
Windows 10 Home - 64 bits YES Koffi Crash on call FecharPortaComunicacao() after the connection is established crash1.log - crash2.log
Windows 10 Home (VM) - 64 bits NO NAPI OK
Windows 10 Home (VM) - 64 bits NO Koffi OK
Windows 10 Home (VM) - 64 bits YES NAPI OK
Windows 10 Home (VM) - 64 bits YES Koffi Crash on call FecharPortaComunicacao() after the connection is established crash1.log - crash2.log
Windows 7 Ultimate SP1 (VM) - 64 bits NO NAPI OK
Windows 7 Ultimate SP1 (VM) - 64 bits NO Koffi OK
Windows 7 Ultimate SP1 (VM) - 64 bits YES NAPI OK
Windows 7 Ultimate SP1 (VM) - 64 bits YES Koffi OK (the connection is closed normally but an error log is generated) error1.log - error2.log
Windows 7 Professional SP1 - 64 bits NO NAPI OK
Windows 7 Professional SP1 - 64 bits NO Koffi OK
Windows 7 Professional SP1 - 64 bits YES NAPI OK
Windows 7 Professional SP1 - 64 bits YES Koffi Never even manages to get past the initial connection attempt phase

I'm open for tips on how I can debug more effectively to help you with investigations, especially in the case of Windows 7 where no error is shown but the connection is not established.

If you want to do remote access via AnyDesk or if you need any additional information, let me know!

Koromix commented 1 year ago

Thanks for the detailed info :) Sorry, busy week, I'll get back to you next week ;)

IgorSamer commented 1 year ago

Hi there!

Do you have any news? I imagine you're quite busy out there, but here in Brazil the next few days will be a holiday and all establishments will be closed so it would be perfect for me to do the tests with my clients' turnstiles :)

Koromix commented 1 year ago

Hi! I'll post some news about this friday!

IgorSamer commented 1 year ago

Any news mate? 🤞

Koromix commented 1 year ago

I've pushed some changes to the repository to try to fix this, but it's untested. I have a Windows 7 64-bit machine at home, however I'm not there right now. I will test this on it tomorrow night, and get back to you!

Koromix commented 1 year ago

I haven't tested the changes but they are in Koffi 2.3.8 which in now on npm :) I'll make tests tomorrow but in the meantime maybe it already works.

IgorSamer commented 1 year ago

Great! I'll be able to test it again with Windows 7 and the turnstile between tomorrow and Monday and I'll let you know about the result :)

IgorSamer commented 1 year ago

Unfortunately the same behavior continues to happen.

In tests with Windows 10, the program keeps crashing after calling the disconnect method (FecharPortaComunicacao) after a connection has already been established (crash1.log, crash2.log).

And in Windows 7 I still can't even make the initial connection with the turnstile.

Just to explain better:

After calling the methods FecharPortaComunicacao, DefinirTipoConexao and AbrirPortaComunicacao (for the initial settings) the app enters a state loop (using a setInterval) where to check if the connection has been established it checks the value of the ReceberRelogio method. If the value is 0, the connection was established and then I can move on to the next states, otherwise the value 1 is returned.

In all cases the ReceberRelogio returns 1 on the first few calls and then about 2~5 seconds later it returns 0 and the connection is made. However, in this case on Windows 7 the value returned is always 1 regardless of how long I keep the seInterval loop running.

Remembering that the same code using Koffi works normally with other Windows (with the exception of the crash when disconnecting) and using the same code but with node-ffi-napi everything happens the same way, with the difference that it manages to connect normally also on that same computer with Windows 7.

Is there any way to debug what happens at connection time on Windows 7 when using node-ffi-napi and also when using Koffi so we can check the differences and gather more information for you to be able to fix this? If yes, how? 🙏🏻

Thanks!

IgorSamer commented 1 year ago

Just bringing some good news :)

I finally learned to work with strings as output parameter thanks to your help @nwrkbiz 🎉

@IgorSamer Strings as output parameter work quite well like so:

#8 (comment)

You need to know the length through.

At the first time I tried I was totally confused even looking into your code, but today I took a look again and did several tests until I understood what really needed to be done. Just for anyone who might have the same question, what I did to get it working was:

In the method definition:

_Out_ uint8_t *Cartao // used uint8_t instead of char or string

Then:

let Cartao = new Uint8Array(14); // 14 is the length of Cartao = 00000000000000

EasyInner.ColetarBilhete(1, Tipo, Dia, Mes, Ano, Hora, Minuto, Cartao);

Cartao = new TextDecoder('UTF-8').decode(Cartao);

Now the only problem that remains is the connection never established on Windows 7 and the crashes when I call the disconnection method on Windows 10 and above :/

IgorSamer commented 1 year ago

I have awesome news!

Since when I did the tests on countless computers with different versions of Windows I found it strange that the connection was not established only in that specific Windows 7, while with others it worked normally.

Today during my tests I was able to generate an installer for the version of the app that uses node-ffi-napi through electron-builder. When installing the program on the Windows 7 computer I noticed that the same behavior that happened using Koffi (not establishing the connection) this time was also happening with node-ffi-napi.

It was then that I began to notice what I had done differently from other times and then I noticed that it could be something related to the place where the program had been installed. I remembered that whenever I used electron-packager I simply moved the generated build to the Desktop and then decided to reinstall the program on the Desktop instead of C:\Program Files (x86).

Result: the connection started to be established normally!

I did the same with the version that uses Koffi: I installed it on the Desktop and for the first time it connected to the turnstile from that computer! 🚀

I apologize for all this confusion but at no time did it cross my mind that it could be something related to where the program was installed, since the installer always requires administrator permissions and everything has always worked fine in other versions of Windows and even in the same version of Windows 7 on another computer.

So far I just don't know what's different about the structure of this specific computer that doesn't allow the connection not to be established if the program is installed in the Program Files folder, but luckily we now know that it's not something with the structure of Koffi itself :)

IgorSamer commented 1 year ago

I also found out how to generate logs to detect possible failures in the DLL and then found out which error is always being triggered when calling the FecharPortaComunicacao method after having the connection established:

Log 18/03/2023 13:32:18 - Error: The given key was not present in the dictionary.
Log 18/03/2023 13:32:18 - Error path:

Most of the time the error path is empty, but one time it logged the following:

Log 19/03/2023 14:28:19 - Error: The given key was not present in the dictionary.
Log 19/03/2023 14:28:19 - Error path: at System.ThrowHelper.ThrowKeyNotFoundException()
   at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
   at Topdata.Inner.Interface.InnerInterface.RemoverConexoes(Int32 numeroInner)
   at Topdata.Inner.Interface.InnerInterface.RaiseDisconnected(Int32 numerInner)

I noticed that this log is always generated also when I use node-ffi-napi with the difference that it manages to handle it and everything continues to work normally, while with Koffi the program stops working and crashes.

I hope this information can help you :))

IgorSamer commented 1 year ago

Hi!

Any chance of solving this crash with the information I passed?

I understand that it is the company that supplies the DLL that should fix this and I even contacted them reporting the failure that does not crash the program but is also generated when using the example executables provided in their SDK. However, unfortunately the developer support is horrible and I haven't heard back from them about this issue for weeks.

I myself do not recommend this company to any of my new clients and even if they come to correct this behavior (which I find difficult) it would be interesting if Koffi could handle this type of failure as well.

I'm sorry again for the previous confusions and hope this improvement can be made 🤞

longhun12346 commented 6 months ago

@IgorSamer Hi, I notice you faced the same crash problem on win32 the same as me. I already fixed it last week. I'm not sure it can fix your problem but you can give it a try. you can use koffi 2.8.8 version to test. if it's fixed your problem please let me know.Thanks.

Hi!

Any chance of solving this crash with the information I passed?

I understand that it is the company that supplies the DLL that should fix this and I even contacted them reporting the failure that does not crash the program but is also generated when using the example executables provided in their SDK. However, unfortunately the developer support is horrible and I haven't heard back from them about this issue for weeks.

I myself do not recommend this company to any of my new clients and even if they come to correct this behavior (which I find difficult) it would be interesting if Koffi could handle this type of failure as well.

I'm sorry again for the previous confusions and hope this improvement can be made 🤞

IgorSamer commented 6 months ago

Hi @longhun12346! Great to hear the issue may have been resolved! As soon as possible I will update to 2.8.8 and come back here to tell you the results. Anyway, thanks for the effort!