Open4

em2native: Node.js ビルドの復活

okuokuokuoku

いろいろ考えたけど、ちゃんとしたnpmモジュールにするのは時期尚早ということで、CMakeでビルド時にモジュールを生成してしまう方向にした。

Visual Studioデバッガ用のjsonとかも生成にしたいがちょっと良いアイデアがない(ソースコードと同じ場所に置く必要がある)。

okuokuokuoku

標準libcがリンクされない問題

Visual StudioではSDLが /NODEFAULTLIB しているので、それにリンクすると一緒に指定されてしまう問題。

C:\cygwin64\home\oku\repos\em2c\nodejs\out\build\x64-Debug\msvcrtd.lib(utility.obj) : error LNK2019: 未解決の外部シンボル __vcrt_initialize が関数 __scrt_initialize_crt で参照されました
C:\cygwin64\home\oku\repos\em2c\nodejs\out\build\x64-Debug\msvcrtd.lib(utility.obj) : error LNK2019: 未解決の外部シンボル __vcrt_uninitialize が関数 __scrt_initialize_crt で参照されました
C:\cygwin64\home\oku\repos\em2c\nodejs\out\build\x64-Debug\msvcrtd.lib(utility.obj) : error LNK2019: 未解決の外部シンボル __vcrt_uninitialize_critical が関数 __scrt_dllmain_uninitialize_critical で参照されました
C:\cygwin64\home\oku\repos\em2c\nodejs\out\build\x64-Debug\msvcrtd.lib(utility.obj) : error LNK2019: 未解決の外部シンボル __vcrt_thread_attach が関数 __scrt_dllmain_crt_thread_attach で参照されました
C:\cygwin64\home\oku\repos\em2c\nodejs\out\build\x64-Debug\msvcrtd.lib(utility.obj) : error LNK2019: 未解決の外部シンボル __vcrt_thread_detach が関数 __scrt_dllmain_crt_thread_attach で参照されました
C:\cygwin64\home\oku\repos\em2c\nodejs\out\build\x64-Debug\msvcrtd.lib(utility.obj) : error LNK2019: 未解決の外部シンボル _is_c_termination_complete が関数 __scrt_dllmain_uninitialize_c で参照されました
C:\cygwin64\home\oku\repos\em2c\nodejs\out\build\x64-Debug\msvcrtd.lib(utility.obj) : error LNK2019: 未解決の外部シンボル __acrt_initialize が関数 __scrt_initialize_crt で参照されました
C:\cygwin64\home\oku\repos\em2c\nodejs\out\build\x64-Debug\msvcrtd.lib(utility.obj) : error LNK2019: 未解決の外部シンボル __acrt_uninitialize が関数 __scrt_uninitialize_crt で参照されました
C:\cygwin64\home\oku\repos\em2c\nodejs\out\build\x64-Debug\msvcrtd.lib(utility.obj) : error LNK2019: 未解決の外部シンボル __acrt_uninitialize_critical が関数 __scrt_dllmain_uninitialize_critical で参照されました
C:\cygwin64\home\oku\repos\em2c\nodejs\out\build\x64-Debug\msvcrtd.lib(utility.obj) : error LNK2019: 未解決の外部シンボル __acrt_thread_attach が関数 __scrt_dllmain_crt_thread_attach で参照されました
C:\cygwin64\home\oku\repos\em2c\nodejs\out\build\x64-Debug\msvcrtd.lib(utility.obj) : error LNK2019: 未解決の外部シンボル __acrt_thread_detach が関数 __scrt_dllmain_crt_thread_detach で参照されました
C:\cygwin64\home\oku\repos\em2c\nodejs\out\build\x64-Debug\yfrm\yfrm.dll : fatal error LNK1120: 11 件の未解決の外部参照

_is_c_termination_complete など見たことないシンボルのエラーが出る。

https://github.com/okuoku/yfrmbase/commit/1f47a16792239961badb9e604dc0391938ebbd1d

結局、SDLでもlibcを使うようにして回避してしまった。

diff --git a/CMakeLists.txt b/CMakeLists.txt
index f8721ed..7b16ac7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -57,6 +57,7 @@ endif()
 # Configure SDL
 set(SDL_SHARED OFF CACHE BOOL "" FORCE)
 set(SDL_STATIC ON CACHE BOOL "" FORCE)
+set(LIBC ON CACHE BOOL "" FORCE)
 add_subdirectory(ext/platform/SDL sdl)

今やWindowsもUCRT(Universal C Runtime)が標準添付になったので、C標準ライブラリが付属してこないOSは無いといっても良いのではないだろうか。。

https://github.com/libsdl-org/SDL/issues/4258#issuecomment-817095137

SDLは依存DLLを減らすためにlibcの一部を自前で実装しているが↑のようにSDLを静的リンクしたい場合に問題になることがある。これはちょっと機序が複雑で、

  1. Visual Studioのコンパイラは最適化で memset 等の呼出しを生成してしまうことがある
  2. LIBC なしでビルドされたSDLには memset が無いのでリンクエラーになる → memsetを自前で実装する
  3. SDLを静的リンクした場合、 memset は標準libcにも存在するので、それと衝突してしまう

GCCでも似たような問題がある:

http://blog.kmckk.com/archives/5710914.html

okuokuokuoku

デバッグ用にDLLをコピーする

https://github.com/okuoku/nccc/commit/92e03c4dc8a840c9f2ce2710ccc7efb4c6efce39

diff --git a/javascript/node-nccc/CMakeLists.txt b/javascript/node-nccc/CMakeLists.txt
index 9ed4f34..8ef3c20 100644
--- a/javascript/node-nccc/CMakeLists.txt
+++ b/javascript/node-nccc/CMakeLists.txt
@@ -32,3 +32,10 @@ set_target_properties(node-nccc
     PREFIX ""
     SUFFIX ".node")
 
+if(NODENCCC_DEBUG_COPY)
+    add_custom_command(TARGET node-nccc POST_BUILD
+        COMMAND ${CMAKE_COMMAND} -E copy
+        "$<TARGET_FILE:node-nccc>"
+        "${NODENCCC_DEBUG_COPY}")
+endif()

個人的には POST_BUILD でコピーするのをよく使ってるけど、そもそもコピーとか考えるのが面倒なので本当にマジで必要でない限りは共有ライブラリにしないようにしている。。

okuokuokuoku

復活した

https://github.com/okuoku/em2native-proto/commit/79a8456e215f8a70977ee240824835d085e6f8f7

一発完動でよかった。

DLLのパス指定方法でちょっと悩んだが、とりあえずデバッグ用と割り切って、ネイティブコードをビルドしたディレクトリで、JavaScript側のランチャーを相対パスで呼ぶということにした。

oku@stripe ~/repos/em2c/nodejs/out/build/x64-Debug
$ node ../../../../runtime/run_nodejs.js

現状のNode.jsはESMに対応しているので、JavaScript側はコミットされているものそのままで動作する。

ESMでは __dirname が使えない対策

ESMなNode.jsには __dirname が無いため、 import.meta.url でURL 文字列 を取得し、適当に加工してURLオブジェクトに変換して使っている。

import launch from "./launcher.js";
const path = require("path");

const TESTAPPROOT = path.dirname(import.meta.url) + "/../app/testapp";

const BOOTPROTOCOL = "plain";
const BOOTSTRAP = new URL(TESTAPPROOT + "/app1/example_emscripten_opengl3.js");
const BOOTWASM = new URL(TESTAPPROOT + "/app1/example_emscripten_opengl3.wasm");

Node.jsの fs APIにはURLオブジェクトが渡せるが、 file:// で始まる文字列ではダメなのでどうしても変換は必要になってしまう。