electron: [Bug]: Link error for native c++ modules

Preflight Checklist

Electron Version

13.1.4

What operating system are you using?

Windows

Operating System Version

Windows 10 Home (19042)

What arch are you using?

x64

Last Known Working Electron version

No response

Expected Behavior

The native module in the testcase gist should be able to link. The problematic part is the function v8::ArrayBuffer::GetBackingStore()

Actual Behavior

Linking fails with the error error LNK2019: unresolved external symbol "__declspec(dllimport) public: class std::shared_ptr<class v8::BackingStore> __cd ecl v8::ArrayBuffer::GetBackingStore(void)" (__imp_?GetBackingStore@ArrayBuffer@v8@@QEAA?AV?$shared_ptr@VBackingStore@v8@@@std@@XZ) re ferenced in function "void __cdecl foobar(class v8::FunctionCallbackInfo<class v8::Value> const &)" (?foobar@@YAXAEBV?$FunctionCallbac kInfo@VValue@v8@@@v8@@@Z) [c:\Work\repro_bug_arraybuffer\build\foobar.vcxproj]

Testcase Gist URL

https://gist.github.com/TanninOne/d321ab1676798647cf672f08e78b88d8

Additional Information

Building the same module for node 14.17.1 works fine.

The error message, although cryptic, is correct: the header and link library shipped do not seem to match. the header declares GetBackingStore like this: std::shared_ptr<BackingStore> GetBackingStore(); but the library exports this: ?GetBackingStore@ArrayBuffer@v8@@QEAA?AV?$shared_ptr@VBackingStore@v8@@@__1@std@@XZ which demangles to public: class std::__1::shared_ptr<class v8::BackingStore> __cdecl v8::ArrayBuffer::GetBackingStore(void) __ptr64

so as far as I understand it the namespace is different and thus the linker doesn’t find the function. node.exe version 14.17.1 otoh exports ?GetBackingStore@ArrayBuffer@v8@@QEAA?AV?$shared_ptr@VBackingStore@v8@@@std@@XZ aka public: class std::shared_ptr<class v8::BackingStore> __cdecl v8::ArrayBuffer::GetBackingStore(void) __ptr64

which matches the declaration in v8.h exactly.

With prior versions of electron (up to 12 I think) we could use the deprecated function GetContent to get at the content of an ArrayBuffer but that function has been removed so until this is fixed, if I’m not completely mistaken, any native module that requires access to the content of an ArrayBuffer, including helper libraries like nan or nbind and all modules that depend on those will be broken.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 3
  • Comments: 21 (10 by maintainers)

Most upvoted comments

@VerteDinde, I find the decision to build Electron on Windows using a C++ runtime that it just out of beta instead of the MS C++ runtime highly unusual - but there is probably a good reason for it. But in this case, I suggest that you also include this runtime along the node.lib DLL import library so that native modules can link with it. Currently, if I want to port my native Node.js addon to Electron I have only two choices and both are horrible:

  • Reimplement the APIs that use std::shared_ptr as some people in this thread did
  • Recompile the clang libc++ for Windows

All of this renders the port of a native Node.js addon to Electron a huge undertaking and completely destroys the dream of simply launching electron-rebuild and going for a cup of coffee.

I ended up adding the following to my node-gyp config:

{
        'variables': { 'runtime%': 'node' },
        'conditions': [
            ['runtime=="electron"', { 'defines': ['NODE_RUNTIME_ELECTRON=1'] }],
        ]
}

And using the follow #if

  #if _MSC_VER && NODE_RUNTIME_ELECTRON && NODE_MODULE_VERSION >= 89
    v8::Local<v8::Object> local;
    node::Buffer::New(isolate, buffer, 0, buffer->ByteLength()).ToLocal(&local);
    return Neon_Buffer_Data(isolate, base_out, local);
  #else
    #if (V8_MAJOR_VERSION >= 8)
        auto contents = buffer->GetBackingStore();
        *base_out = contents->Data();
        return contents->ByteLength();
    #else
        v8::ArrayBuffer::Contents contents = buffer->GetContents();
        *base_out = contents.Data();
        return contents.ByteLength();
    #endif
  #endif

Not the most elegant or low cost solution, but hopefully that works as a stop gap while my users migrate to Node-API.

Is there a #def that can be used to tell if the module is being built for electron? My solution was a bit convoluted.

Just a note: I’d recommend that all native modules use N-API and avoid nan, for this kind of reason. C++ ABIs are very difficult to stabilize. N-API is explicitly designed as a stable ABI target. https://nodejs.org/api/n-api.html

@somanuell That’s a sample of the neon project source. Neon_Buffer_Data is a very small helper that gets the contents of the buffer:

extern "C" size_t Neon_Buffer_Data(v8::Isolate *isolate, void **base_out, v8::Local<v8::Object> obj) {
  *base_out = node::Buffer::Data(obj);

  return node::Buffer::Length(obj);
}

The result of node::Buffer::New goes into local and then node::Buffer::Data can be used to get the buffer data out.

Is the solution here to switch to clang?

Yep, using the same clang + std library that Electron uses to build is the safest way to build native modules that interact with the std library.