Search code examples
c++node.jsdllelectronffi

Handling Memory Allocation and Pointers in Electron between Node.js and C++ DLL


I'm developing an Electron application that requires interfacing with a C++ DLL to manage USB devices. Recently, based on recommendations and issues encountered with node-ffi-napi (Issue #238), I transitioned to using koffi, a different FFI library designed to facilitate calling C functions from Node.js environments, including Electron.

The core functionality I'm trying to implement involves calling a C++ DLL function that allocates memory for an array of device structures using malloc and then returns a pointer to this array. Here's the C function prototype I'm dealing with:

/**
 * \brief This routine allows to get ST-LINK connected probe(s).
 * \param stLinkList  : Filled with the connected ST-LINK list and its default configurations.
 * \param shared      : Enable shared mode allowing connection of two or more instances to the same ST-LINK probe.
 * \return Number of the ST-LINK probes already exists.
 */
int getStLinkList(debugConnectParameters** stLinkList, int shared);

Attempting to interact with this function from my Electron app has presented some challenges, particularly around correctly allocating and passing a pointer to a pointer (debugConnectParameters**) that the DLL can use to allocate memory and return data.

Here's an approximation of my current approach in Node.js using koffi:

// Assuming 'lib' is the loaded DLL
const getStLinkList = lib.func('int', ['pointer', 'int']);

// Attempt to call getStLinkList
let stLinkListPtr = null; // Placeholder for a pointer to pointers
let shared = 0; // Example value for demonstration
let numberOfDevices = getStLinkList(stLinkListPtr, shared);

My questions are as follows:

How can I properly allocate a pointer to a pointer in an Electron app using koffi, allowing the DLL to allocate memory and return device data? Once the DLL allocates memory and populates it with device structures, how do I access and iterate through this data in Node.js within an Electron context? What are the best practices for managing memory allocated by a DLL in an Electron app, particularly to prevent memory leaks or access violations? Any advice, code snippets, or resources on managing FFI interactions and memory allocation between Node.js (within Electron) and C++ DLLs would be incredibly helpful. I'm particularly interested in solutions or examples that address similar scenarios, taking into account the transition from node-ffi-napi to koffi.

I've been trying to interface an Electron app with a C++ DLL to manage USB devices. Specifically, I need to call a DLL function that allocates an array of structures for device data using malloc and returns a pointer to this array. I expected to properly allocate and pass a pointer to a pointer (debugConnectParameters**) to this function and then access the device data in Node.js.

What I Tried:

Switched from node-ffi-napi to koffi: Based on recommendations and an issue discussion, I moved to koffi for better Electron compatibility. Pointer Allocation: Attempted to allocate a pointer to a pointer for the DLL function to allocate memory and fill with device data but struggled with correct implementation. Function Call: Called the DLL function, expecting it to allocate memory for the device data array and to be able to iterate over it in Node.js. Issues Encountered:

Unclear on how to correctly allocate and initialize a pointer to a pointer for the DLL to use. Struggled with accessing and managing the memory allocated by the DLL post-function call. Concerned about properly freeing the allocated memory to prevent memory leaks. I'm seeking advice on handling pointer allocation and memory management for DLL interactions in an Electron context.

Update

  • I wrote a DLL that wraps the original DLL. The new interface does not involve to pass a pointer to memory to the nodeJs side (which I thought it was the cause of the problem). This approach does not solve the problem.
  • if I use the original DLL or this new wrapper-DLL from a C or CPP program, I get the correct behaviour from getStLinkList(); if I call either DLL via koffi, it returns 0 devices (instead of 1).

So for some reason, the function behaves incorrectly when called from koffi, either directly or via encapsulated wrapper.

I suspect that the problem is on koffi's side, but the fact that the function silently fails does not help (it does not give any error code).


Solution

  • Solution Update & Explanation

    After extensive troubleshooting and experimenting with various approaches to manage FFI interactions between my Electron app and a C++ DLL, I've identified and addressed the core issue I was facing. Here's a breakdown of the problem and the solution that worked for me:

    The Problem:

    • The main challenge was interfacing with a C++ DLL function getStLinkList(debugConnectParameters** stLinkList, int shared) from my Electron application using koffi. This function allocates memory for an array of device structures and returns a pointer to this array.
    • The function was expected to allocate memory and populate it with device data, which I planned to access in Node.js within an Electron context. However, when called from the Electron app using koffi, the function returned 0, indicating no devices were found, which was inconsistent with the expected behavior.
    • Initially, I suspected the issue might be related to how memory was allocated and passed between Node.js and the DLL. To address this, I even wrote a wrapper DLL to simplify the interface, but the problem persisted.

    Discovery:

    • Through further investigation, I realized that the issue was not directly related to memory allocation or pointer management as initially thought. Instead, the function's behavior was influenced by its execution context.
    • Calling the function from a C or C++ standalone application worked as expected, correctly identifying connected devices. However, calling the same function via koffi in the Electron app resulted in the function failing to identify any devices, always returning 0.

    Solution:

    • The breakthrough came when I focused on the preconditions and environment setup required by the DLL for getStLinkList to execute successfully. It turned out that specific initialization routines needed to be performed before calling getStLinkList, which were not initially apparent.
      • Key to the solution was ensuring the proper setup of the loaderPath and the displayCallback functions before making the call to getStLinkList. See here and here. These configurations were critical for the function to execute correctly in the context of an Electron app using koffi.
      • Implementing stub functions for logging and progress display (as required by the DLL) and correctly setting the loaderPath were the missing pieces. Once these were correctly configured in the Electron app environment, getStLinkList started to behave as expected, returning the correct number of connected devices.

    Conclusion:

    • This experience underscored the importance of thoroughly understanding the external libraries' requirements and execution context when integrating them into applications, especially in non-standard environments like Electron apps interfacing with C++ DLLs via FFI.
    • Moreover, it highlighted the challenges associated with silent failures and the need for clear documentation and error reporting from external libraries to aid in debugging and integration efforts.

    I have also submitted a request to ST, suggesting that they provide more detailed information instead of failing silently when their functions encounter errors.

    I hope this update provides clarity on the issue and might help others facing similar challenges with FFI interactions in Electron or similar frameworks.