• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2022-2025 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 #include <cstdint>
16 #include <cstring>
17 #include <vector>
18 #include <string>
19 
20 #include "interop-logging.h"
21 
22 #ifdef KOALA_INTEROP_MODULE
23 #undef KOALA_INTEROP_MODULE
24 #endif
25 #define KOALA_INTEROP_MODULE InteropNativeModule
26 #include "convertors-napi.h"
27 
28 
29 // Adapter for NAPI_MODULE
30 #define NODE_API_MODULE_ADAPTER(modname, regfunc)                                      \
31   static napi_value __napi_##regfunc(napi_env env, napi_value exports) {       \
32     return Napi::RegisterModule(env, exports, regfunc);                        \
33   }                                                                            \
34   NAPI_MODULE(modname, __napi_##regfunc)
35 
getValueTypeChecked(napi_env env,napi_value value)36 napi_valuetype getValueTypeChecked(napi_env env, napi_value value) {
37     napi_valuetype type;
38     napi_status status = napi_typeof(env, value, &type);
39     KOALA_NAPI_THROW_IF_FAILED(env, status, napi_undefined);
40     return type;
41 }
42 
isTypedArray(napi_env env,napi_value value)43 bool isTypedArray(napi_env env, napi_value value) {
44     bool result = false;
45     napi_status status = napi_is_typedarray(env, value, &result);
46     KOALA_NAPI_THROW_IF_FAILED(env, status, false);
47     return result;
48 }
49 
getBoolean(napi_env env,napi_value value)50 KBoolean getBoolean(napi_env env, napi_value value) {
51     if (getValueTypeChecked(env, value) == napi_valuetype::napi_boolean) {
52         bool result = false;
53         napi_get_value_bool(env, value, &result);
54         return static_cast<KBoolean>(result);
55     }
56     return static_cast<KBoolean>(getInt32(env, value) != 0);
57 }
58 
getInt32(napi_env env,napi_value value)59 KInt getInt32(napi_env env, napi_value value) {
60     if (getValueTypeChecked(env, value) != napi_valuetype::napi_number) {
61         napi_throw_error(env, nullptr, "Expected Number");
62         return 0;
63     }
64     int32_t result = false;
65     napi_get_value_int32(env, value, &result);
66     return static_cast<KInt>(result);
67 }
68 
getUInt32(napi_env env,napi_value value)69 KUInt getUInt32(napi_env env, napi_value value) {
70     if (getValueTypeChecked(env, value) != napi_valuetype::napi_number) {
71         napi_throw_error(env, nullptr, "Expected Number");
72         return 0;
73     }
74     uint32_t result = false;
75     napi_get_value_uint32(env, value, &result);
76     return static_cast<KUInt>(result);
77 }
78 
getFloat32(napi_env env,napi_value value)79 KFloat getFloat32(napi_env env, napi_value value) {
80     if (getValueTypeChecked(env, value) != napi_valuetype::napi_number) {
81         napi_throw_error(env, nullptr, "Expected Number");
82         return 0.0f;
83     }
84     double result = false;
85     napi_get_value_double(env, value, &result);
86     return static_cast<KFloat>(static_cast<float>(result));
87 }
88 
getFloat64(napi_env env,napi_value value)89 KDouble getFloat64(napi_env env, napi_value value) {
90     if (getValueTypeChecked(env, value) != napi_valuetype::napi_number) {
91         napi_throw_error(env, nullptr, "Expected Number");
92         return 0.0;
93     }
94     double result = false;
95     napi_get_value_double(env, value, &result);
96     return static_cast<KDouble>(result);
97 }
98 
getString(napi_env env,napi_value value)99 KStringPtr getString(napi_env env, napi_value value) {
100   KStringPtr result {};
101   napi_valuetype valueType = getValueTypeChecked(env, value);
102   if (valueType == napi_valuetype::napi_null || valueType == napi_valuetype::napi_undefined) {
103     return result;
104   }
105 
106   if (valueType != napi_valuetype::napi_string) {
107     napi_throw_error(env, nullptr, "Expected String");
108     return result;
109   }
110 
111   size_t length = 0;
112   napi_status status = napi_get_value_string_utf8(env, value, nullptr, 0, &length);
113   if (status != 0) return result;
114   result.resize(length);
115   status = napi_get_value_string_utf8(env, value, result.data(), length + 1, nullptr);
116   return result;
117 }
118 
getPointer(napi_env env,napi_value value)119 KNativePointer getPointer(napi_env env, napi_value value) {
120     napi_valuetype valueType = getValueTypeChecked(env, value);
121     if (valueType == napi_valuetype::napi_external) {
122         KNativePointer result = nullptr;
123         napi_status status = napi_get_value_external(env, value, &result);
124         KOALA_NAPI_THROW_IF_FAILED(env, status, nullptr);
125         return result;
126     }
127 
128     if (valueType != napi_valuetype::napi_bigint) {
129         napi_throw_error(env, nullptr, "cannot be coerced to pointer");
130         return nullptr;
131     }
132 
133     bool isWithinRange = true;
134     uint64_t ptrU64 = 0;
135     napi_status status = napi_get_value_bigint_uint64(env, value, &ptrU64, &isWithinRange);
136     KOALA_NAPI_THROW_IF_FAILED(env, status, nullptr);
137     if (!isWithinRange) {
138         napi_throw_error(env, nullptr, "cannot be coerced to uint64, value is too large");
139         return nullptr;
140     }
141     return reinterpret_cast<KNativePointer>(ptrU64);
142 }
143 
getInt64(napi_env env,napi_value value)144 KLong getInt64(napi_env env, napi_value value) {
145     if (getValueTypeChecked(env, value) != napi_valuetype::napi_bigint) {
146         napi_throw_error(env, nullptr, "cannot be coerced to int64");
147         return -1;
148     }
149 
150     bool isWithinRange = true;
151     int64_t ptr64 = 0;
152     napi_get_value_bigint_int64(env, value, &ptr64, &isWithinRange);
153     if (!isWithinRange) {
154         napi_throw_error(env, nullptr, "cannot be coerced to int64, value is too large");
155         return -1;
156     }
157     return static_cast<KLong>(ptr64);
158 }
159 
getUInt64(napi_env env,napi_value value)160 KULong getUInt64(napi_env env, napi_value value) {
161     if (getValueTypeChecked(env, value) != napi_valuetype::napi_bigint) {
162         napi_throw_error(env, nullptr, "cannot be coerced to uint64");
163         return -1;
164     }
165 
166     bool isWithinRange = true;
167     uint64_t ptr64 = 0;
168     napi_get_value_bigint_uint64(env, value, &ptr64, &isWithinRange);
169     if (!isWithinRange) {
170         napi_throw_error(env, nullptr, "cannot be coerced to uint64, value is too large");
171         return -1;
172     }
173     return static_cast<KLong>(ptr64);
174 }
175 
makeString(napi_env env,const KStringPtr & value)176 napi_value makeString(napi_env env, const KStringPtr& value) {
177     napi_value result;
178     napi_status status;
179     status = napi_create_string_utf8(env, value.isNull() ? "" : value.data(), value.length(), &result);
180     KOALA_NAPI_THROW_IF_FAILED(env, status, result);
181     return result;
182 }
183 
makeString(napi_env env,const std::string & value)184 napi_value makeString(napi_env env, const std::string& value) {
185     napi_value result;
186     napi_status status;
187     status = napi_create_string_utf8(env, value.c_str(), value.length(), &result);
188     KOALA_NAPI_THROW_IF_FAILED(env, status, result);
189     return result;
190 }
191 
makeBoolean(napi_env env,int8_t value)192 napi_value makeBoolean(napi_env env, int8_t value) {
193     napi_value result;
194     napi_status status;
195     status = napi_get_boolean(env,  value != 0, &result);
196     KOALA_NAPI_THROW_IF_FAILED(env, status, result);
197     return result;
198 }
199 
makeInt32(napi_env env,int32_t value)200 napi_value makeInt32(napi_env env, int32_t value) {
201     napi_value result;
202     napi_status status;
203     status = napi_create_int32(env,  value, &result);
204     KOALA_NAPI_THROW_IF_FAILED(env, status, result);
205     return result;
206 }
207 
makeUInt32(napi_env env,uint32_t value)208 napi_value makeUInt32(napi_env env, uint32_t value) {
209     napi_value result;
210     napi_status status;
211     status = napi_create_uint32(env,  value, &result);
212     KOALA_NAPI_THROW_IF_FAILED(env, status, result);
213     return result;
214 }
215 
makeInt64(napi_env env,int64_t value)216 napi_value makeInt64(napi_env env, int64_t value) {
217     napi_value result;
218     napi_status status;
219     status = napi_create_bigint_int64(env, value, &result);
220     KOALA_NAPI_THROW_IF_FAILED(env, status, result);
221     return result;
222 }
223 
makeUInt64(napi_env env,uint64_t value)224 napi_value makeUInt64(napi_env env, uint64_t value) {
225     napi_value result;
226     napi_status status;
227     status = napi_create_bigint_uint64(env, value, &result);
228     KOALA_NAPI_THROW_IF_FAILED(env, status, result);
229     return result;
230 }
231 
makeFloat32(napi_env env,float value)232 napi_value makeFloat32(napi_env env, float value) {
233     napi_value result;
234     napi_status status;
235     status = napi_create_double(env,  value, &result);
236     KOALA_NAPI_THROW_IF_FAILED(env, status, result);
237     return result;
238 }
239 
makePointer(napi_env env,void * value)240 napi_value makePointer(napi_env env, void* value) {
241     napi_value result;
242     napi_status status;
243     status = napi_create_bigint_uint64(env, static_cast<uint64_t>(reinterpret_cast<uintptr_t>(value)), &result);
244     KOALA_NAPI_THROW_IF_FAILED(env, status, result);
245     return result;
246 }
247 
makeVoid(napi_env env)248 napi_value makeVoid(napi_env env) {
249     napi_value result;
250     napi_status status;
251     status = napi_get_undefined(env, &result);
252     KOALA_NAPI_THROW_IF_FAILED(env, status, result);
253     return result;
254 }
255 
makeObject(napi_env env,napi_value object)256 napi_value makeObject(napi_env env, napi_value object) {
257     napi_value result;
258     napi_status status;
259     status = napi_create_object(env, &result);
260     KOALA_NAPI_THROW_IF_FAILED(env, status, result);
261     return result;
262 }
263 
264 #if _MSC_VER >= 1932 // Visual Studio 2022 version 17.2+
265 #    pragma comment(linker, "/alternatename:__imp___std_init_once_complete=__imp_InitOnceComplete")
266 #    pragma comment(linker, "/alternatename:__imp___std_init_once_begin_initialize=__imp_InitOnceBeginInitialize")
267 #endif
268 
getInstance()269 Exports* Exports::getInstance() {
270     static Exports *instance = nullptr;
271     if (instance == nullptr) {
272         instance = new Exports();
273     }
274     return instance;
275 }
276 
getModules()277 std::vector<std::string> Exports::getModules() {
278     std::vector<std::string> result;
279     for (auto it = implementations.begin(); it != implementations.end(); ++it) {
280         result.push_back(it->first);
281     }
282     return result;
283 }
284 
addMethod(const char * module,const char * name,napi_type_t impl)285 void Exports::addMethod(const char* module, const char* name, napi_type_t impl) {
286     auto it = implementations.find(module);
287     if (it == implementations.end()) {
288         it = implementations.insert(std::make_pair(module, std::vector<std::pair<std::string, napi_type_t>>())).first;
289     }
290     it->second.push_back(std::make_pair(name, impl));
291 
292 }
293 
getMethods(const std::string & module)294 const std::vector<std::pair<std::string, napi_type_t>>& Exports::getMethods(const std::string& module) {
295     auto it = implementations.find(module);
296     if (it == implementations.end()) {
297         LOGE("Module %s is not registered", module.c_str());
298         INTEROP_FATAL("Fatal error");
299     }
300     return it->second;
301 }
302 
303 //
304 // Callback dispatcher
305 //
306 // TODO Should we get rid of explicit Node_* declrations and hide the naming convention behind the macro definitions?
307 
308 static napi_ref g_koalaNapiCallbackDispatcher = nullptr;
309 
310 // TODO: shall we pass name in globalThis instead of object reference?
Node_SetCallbackDispatcher(napi_env env,napi_callback_info cbinfo)311 napi_value Node_SetCallbackDispatcher(napi_env env, napi_callback_info cbinfo) {
312     fprintf(stderr, "Node_SetCallbackDispatcher!\n");
313 
314     CallbackInfo info(env, cbinfo);
315     napi_value dispatcher = info[0];
316     napi_value result = makeVoid(env);
317     napi_status status = napi_create_reference(env, dispatcher, 1, &g_koalaNapiCallbackDispatcher);
318     KOALA_NAPI_THROW_IF_FAILED(env, status, result);
319 
320     return result;
321 }
MAKE_NODE_EXPORT(KOALA_INTEROP_MODULE,SetCallbackDispatcher)322 MAKE_NODE_EXPORT(KOALA_INTEROP_MODULE, SetCallbackDispatcher)
323 
324 napi_value Node_CleanCallbackDispatcher(napi_env env, napi_callback_info cbinfo) {
325     napi_value result = makeVoid(env);
326     if (g_koalaNapiCallbackDispatcher) {
327         napi_status status = napi_delete_reference(env, g_koalaNapiCallbackDispatcher);
328         g_koalaNapiCallbackDispatcher = nullptr;
329         KOALA_NAPI_THROW_IF_FAILED(env, status, result);
330     }
331     return result;
332 }
MAKE_NODE_EXPORT(KOALA_INTEROP_MODULE,CleanCallbackDispatcher)333 MAKE_NODE_EXPORT(KOALA_INTEROP_MODULE, CleanCallbackDispatcher)
334 
335 napi_value getKoalaNapiCallbackDispatcher(napi_env env) {
336     if (!g_koalaNapiCallbackDispatcher) {
337         abort();
338     }
339     napi_value value;
340     napi_status status = napi_get_reference_value(env, g_koalaNapiCallbackDispatcher, &value);
341     KOALA_NAPI_THROW_IF_FAILED(env, status, makeVoid(env));
342     return value;
343 }
344 
345 //
346 // Module initialization
347 //
348 
349 using ModuleRegisterCallback = napi_value (*)(napi_env env, napi_value exports);
350 
351 /**
352  * Sets a new callback and returns its previous value.
353  */
ProvideModuleRegisterCallback(ModuleRegisterCallback value=nullptr)354 ModuleRegisterCallback ProvideModuleRegisterCallback(ModuleRegisterCallback value = nullptr) {
355     static const ModuleRegisterCallback DEFAULT_CB = [](napi_env env, napi_value exports) { return exports; };
356     static ModuleRegisterCallback curCallback = DEFAULT_CB;
357 
358     ModuleRegisterCallback prevCallback = curCallback;
359     curCallback = value ? value : DEFAULT_CB;
360     return prevCallback;
361 }
362 
363 static constexpr bool splitModules = true;
364 
InitModule(napi_env env,napi_value exports)365 static napi_value InitModule(napi_env env, napi_value exports) {
366     LOG("InitModule: " QUOTE(INTEROP_LIBRARY_NAME) "\n");
367     Exports* inst = Exports::getInstance();
368     napi_status status;
369     napi_value target = exports;
370     for (const auto &module : inst->getModules()) {
371         if (splitModules) {
372             status = napi_create_object(env, &target);
373             KOALA_NAPI_THROW_IF_FAILED(env, status, exports);
374             status = napi_set_named_property(env, exports, module.c_str(), target);
375             KOALA_NAPI_THROW_IF_FAILED(env, status, exports);
376         }
377 
378         for (const auto &impl : inst->getMethods(module)) {
379             napi_value implFunc;
380             status = napi_create_function(env, impl.first.c_str(), NAPI_AUTO_LENGTH, impl.second, nullptr, &implFunc);
381             KOALA_NAPI_THROW_IF_FAILED(env, status, exports);
382             status = napi_set_named_property(env, target, impl.first.c_str(), implFunc);
383             KOALA_NAPI_THROW_IF_FAILED(env, status, exports);
384         }
385     }
386     return ProvideModuleRegisterCallback()(env, exports);
387 }
388 
389 NAPI_MODULE(INTEROP_LIBRARY_NAME, InitModule)
390