0
votes

I am trying to do a simple proof of concept and call a very basic c++ dll from node by using ffi-napi.

It works fine when I am using methods that take/return int or char, but my attempts to return a string have failed.

c++ code, main.h:

#include <string>
#include <stdlib.h>
using namespace std;

#define EXPORT __declspec(dllexport) 

extern "C" {
    EXPORT string hello(string x);
    EXPORT int count(int x);
}

c++ code, main.cpp:

#include "pch.h"
#include <string>
#include <stdlib.h>
#include "Main.h"
using namespace std;


string hello(string name)
{
    return "Hello " + name + "! ";
}

int count(int x)
{
    return x+1;
}

When I am running node code below,

import { Console } from "node:console";

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

export class DllTester {

  LibName: string = 'D:\\temp\\DemoDll\\x64\\Debug\\DemoDll.dll';
  Lib: any = null;

  private setupOcrLib()
  {
    this.Lib = ffi.Library(this.LibName, 
      {'hello': [ 'string', ['string']],
      'count': [ ref.types.int, [ref.types.int]] 
      }
    );
  }

  count(x: number): number {
    this.setupOcrLib();
    console.log("Calling count");
    return this.Lib.count(x);
  }

  hello(name: string): string {
    this.setupOcrLib();
    console.log("Calling hello: " + name);
    return this.Lib.hello(name);
  }

}

const tester = new DllTester();
console.log("Count: " + tester.count(3));
console.log("Hello: " + tester.hello("Markus"));
console.log("Count: " + tester.count(10));

I get the following console output:

Calling count
Count: 4
Calling hello: Markus

So the first call (sending int, receiving int) works fine, but the second call (sending string, receiving string) silently fails and since the second call to count is not logged, I believe my unsuccessfull call disturbs the flow.

Have tried the following:

  • using char* instead of string in the dll. I then receive "something" but not readable text
  • using ref-napi to set types instead of strings but demos suggest that this is not required: https://github.com/node-ffi-napi/node-ffi-napi

My understanding of c++ is limited and might have missed some part about types and pointers but this looks so simple that nothing should be able to go wrong...

Platform: windows 10, 64 bit, node v14.12.0, VS 2019 Community

Versions: "ffi-napi": "^4.0.3", "ref-napi": "^3.0.2"

Thanks for your input :-)

1
Have you tried using std::string? On the JS side, there is nothing that operates similar to using namespace std; on the C++ side. BTW: Doing so in C++ is frowned upon, because it pollutes the namespace, but that's a different issue. - Ulrich Eckhardt
The traditional way that strings are marshalled between DLL's and even different languages is for the client to create a buffer, and the client sends that buffer to the DLL to be filled in with the character data. A std::string is not a universal, built-in type, like int. A std::string internals differ between C++ compiler, build settings, etc. If you want a real-world example, look at the Windows API and how it handles string data. The user creates the buffer, sends it to the API function, and the function fills the buffer. - PaulMcKenzie
@UlrichEckhardt Tried that on "both" sides. Using it only on the c++ side gives the same result as before, and if I try it in typescript I get an error: could not determine a proper "type" from: 'std::string' Also tried using CString on the c++ side, did not work either. Thanks for your input! - Markus
@PaulMcKenzie Ok, so I shouldn't expect a string to be returned so easily? So then I should send a pointer to my method instead of using a return value, I guess. Have you done this before using ffi? Would love to see a code example how to handle a string. Thanks! - Markus

1 Answers

0
votes

Got it to work after looking into @PaulMcKenzies answer: instead of simply using strings, I create a buffer and use a pointer instead.

C++ code, main.h:

#include <string>
#include <stdlib.h>
using namespace std;

#define EXPORT __declspec(dllexport) 

extern "C" {
    EXPORT void helloPtr(char* x);
    EXPORT int count(int x);
}

c++ code, main.cpp:

#include "pch.h"
#include <string>
#include <stdlib.h>
#include "Main.h"
using namespace std;

void helloPtr(char* name)
{
    std::string names(name);
    std::string results = "Hello " + names + "!";
    strcpy_s(name, 200, results.c_str());
}

int count(int x)
{
    return x+1;
}

Node:

import { Console } from "node:console";

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

export class DllTester {

  LibName: string = 'D:\\temp\\DemoDll\\x64\\Debug\\DemoDll.dll';
  Lib: any = null;

  constructor() {
    this.setupLib();
  }

  private setupLib()
  {
    this.Lib = ffi.Library(this.LibName, 
      {'helloPtr': [ ref.types.void, ['char *']],
      'count': [ ref.types.int, [ref.types.int]] 
      }
    );
  }

  count(x: number): number {
    return this.Lib.count(x);
  }


  helloPtr(name: string): string {

    //setup buffer
    var maxLength = 200;
    var nameBuffer = Buffer.alloc(maxLength);
    nameBuffer.fill(0); 
    nameBuffer.write(name, 0, "utf-8"); 

    this.Lib.helloPtr(nameBuffer);

    //get result from buffer
    var helloResult = nameBuffer.toString('utf-8');
    var terminatingNullPos = helloResult.indexOf('\u0000');
    if (terminatingNullPos >= 0) {
      helloResult = helloResult.substr(0, terminatingNullPos);
    }

    return helloResult;
  }

}

const tester = new DllTester();
console.log(tester.helloPtr("Markus"));

This prints the desired message into the console: Hello Markus!

If anyone knows of a simpler way or doing this (sending strings instead?), that would be great.