当前位置: 首页 > news >正文

网站开发者工具的网络选项wordpress 缩略图类型

网站开发者工具的网络选项,wordpress 缩略图类型,南宁网站建设制作,个人网站程序下载前言 Node.js Addon 是 Node.js 中为 JavaScript 环境提供 C/C 交互能力的机制。其形态十分类似 Java 的 JNI#xff0c;都是通过提供一套 C/C SDK#xff0c;用于在 C/C 中创建函数方法、进行数据转换#xff0c;以便 JavaScript / Java 等语言进行调用。这样编写的代码通常… 前言 Node.js Addon 是 Node.js 中为 JavaScript 环境提供 C/C 交互能力的机制。其形态十分类似 Java 的 JNI都是通过提供一套 C/C SDK用于在 C/C 中创建函数方法、进行数据转换以便 JavaScript / Java 等语言进行调用。这样编写的代码通常叫做 Bindings。 此外还有基于 C ABI Calling Convention (例如 stdcall / System-V 等标准) 直接进行跨语言调用的方案例如 Rust FFI、Python 的 ctypes、Node.js 的 ffi 包等。这两者的差别在于 Rust 等原生语言是直接针对平台来将函数调用编译为机器码而 ctypes 和 ffi 包则是基于 libffi 动态生成机器码来完成函数调用的。和 Node.js Addon 的差别则在于调用和类型转换的开销上。 本文将围绕 Node.js Addon 进行介绍即创建一个 Bindings 来增强 Node.js 或 Electron 应用的原生能力使其可以和系统进行交互或者使用一些基于 C/C 编写的第三方库。 Node.js 和 Electron 的关系 Electron 在主进程和渲染进程中都包含了完整的 Node.js 环境因此本文既适用于 Node.js 程序也适用于 Electron 程序。 Node.js Addon 的类型 在 Node.js 的 Addon有三种类型 本文主要介绍 Node-API 的原理以及以 node-addon-api 作为例子。 Node-API 基本原理 Node.js 本质上是一个动态链接库即 Windows 下的 .dll 文件、MacOS 下的 .dylib 文件、Linux 下的 .so 文件只不过在分发时会将文件的扩展名改为 .node 加载 Node.js Addon 通常通过 CommonJS 的 require 函数进行导入和初始化。require 在被 .node 扩展名路径作为参数进行调用的情况下最终会利用 dlopenWindows 下是 LoadLibrary方法来动态加载这个以 .node 扩展名的动态链接库 初始化 以 https://github.com/nodejs/node-addon-examples/blob/main/1_hello_world/napi/hello.c 作为参考 static napi_value Init(napi_env env, napi_value exports) {napi_status status;napi_property_descriptor desc  DECLARE_NAPI_METHOD(hello, Method);status  napi_define_properties(env, exports, 1, desc);assert(status  napi_ok);return exports; }NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) NAPI_MODULE 宏用来绑定一个 C 函数作为初始化函数。这个函数中可以用来给模块的 exports 对象添加所需要的功能。 例如上述的代码中给 exports 添加了一个叫做 hello 的函数。这样一来我们在 Node.js 中 require 这个模块之后就能获得到一个包含 hello 函数的 exports 对象 调用 以 https://github.com/nodejs/node-addon-examples/blob/main/1_hello_world/napi/hello.c 作为参考 static napi_value Method(napi_env env, napi_callback_info info) {napi_status status;napi_value world;status  napi_create_string_utf8(env, world, 5, world);assert(status  napi_ok);return world; } Method 本身是一个 C 函数接受 napi_env 作为 JavaScript 的上下文信息。napi_callback_info 作为当前函数调用的信息例如函数参数等。返回一个 napi_value 作为函数的返回结果。 从这个函数的例子中可以看到在 C 中是可以获取到函数的调用参数并且产生一个值作为函数的返回结果。稍后我们会以 node-addon-api 作为例子来具体介绍其编写方式。 模块编写指南 本节介绍使用 C 配合 node-addon-api 开发模块时常见的一些模式和样板代码仅供参考。 更多用法详见官方文档https://github.com/nodejs/node-addon-api/blob/main/doc/hierarchy.md 模块初始化 使用 NODE_API_MODULE 宏绑定一个 C 函数进行模块初始化 Napi::Object Init(Napi::Env env, Napi::Object exports) {exports.Set(Napi::String::New(env, hello),Napi::Function::New(env, Method));return exports; }NODE_API_MODULE(hello, Init) 其中 Napi::Env 是对 napi_env 的封装代表一个 JavaScript 上下文大部分和 JavaScript 交互的场景都需要这个上下文可以保存起来以供下次使用但是不要跨线程使用。Napi::Object exports 则是这个模块的 exports 对象可以把想要给 JavaScript 暴露的值和函数都设置到这个上面。 创建 JavaScript 函数 首先需要创建一个如下函数签名的 C 函数 Napi::Value Add(const Napi::CallbackInfo info) {Napi::Env env  info.Env();double arg0  info[0].AsNapi::Number().DoubleValue();double arg1  info[1].AsNapi::Number().DoubleValue();Napi::Number num  Napi::Number::New(env, arg0  arg1);return num; } 其中函数的返回值可以是任何派生自 Napi::Value 的类型也可以是 Napi::Value 本身。 获取函数参数 通过 Napi::CallbackInfo 来获取函数参数例如 info[0] 代表第一个参数。 info[n] 会获取一个 Napi::Value 值我们需要调用它的 AsT 方法来转换为具体的值我们才能将它继续转换为 C/C 可用的数据类型。例如我们希望将函数的第一个参数转换为字符串我们需要经过两个步骤 将 Napi::Value 转换为 Napi::String Napi::String js_str  info[0].AsNapi::String(); 将 Napi::String 转换为 std::string std::string cpp_str  js_str.Utf8Value(); 其他数据类型例如 Napi::Number、Napi::BufferT 均有类似的方法。 返回函数结果 我们可以直接创建一个 JavaScript 值并在 C 函数中返回。具体创建值的方法详见下一小节。 创建 JavaScript 值 我们可以利用各种实例化方法来从 C/C 的数据类型中创建 JavaScript 的值下面举几个常见的例子。 创建字符串 Napi::String::New(env, 字符串内容) 创建数字 Napi::Number::New(env, 123) 创建 Buffer 创建 Buffer 是一个有风险的操作。Node-API 提供了两种创建方式 提供一个指针和数据长度创建一个数据的拷贝 ✅ 安全首选这种方法✅ v8 会负责这个 Buffer 的垃圾回收 Napi::Buffer::Copy(napi_env env, const T* data, size_t length) 直接基于指针和数据长度创建一个 External Buffer ⚠️ 同一个指针相同的内存地址只能创建一个 Buffer重复创建会引起错误⚠️ v8 / Node.js 不负责这个 Buffer 的内存管理 Napi::Buffer::New(napi_env env, const T* data, size_t length) 异步代码 异步函数 异步函数通常用于实现一些异步 IO 任务、事件例如实现一个异步网络请求库的绑定。 异步函数通常有两种实现方式回调 和 Promise。 同线程回调 同线程回调的使用场景比较少 使用了 libuv 来运行了一些异步任务并且这个异步任务会在 libuv 主线程唤醒事件循环来返回结果这时候可以比较安全地直接进行同线程回调。但是要求事先把 Napi::Env 保存在一个地方。实现一个函数的时候在实现中直接同步调用一个 Napi::Function。 获取函数 通常我们会从函数调用的参数中获取到 Napi::Function一般来说我们需要在当次调用就把这个函数给使用掉避免后续被 v8 GC 回收。 持久化函数 如果我们确实需要在之后的其他时机去使用函数我们需要将它通过 Napi::Persistent 持久化 Napi::FunctionReference func_persist  Napi::Persistent(func); 使用时可以作为一个正常的函数去使用。 调用函数 无论是 Napi::Function 还是 Napi::FunctionReference我们都可以通过 Call 方法来调用 Napi::Value ret_val  func_persist.Call({Napi::String::New(env, Arg0) }); 跨线程回调 跨线程回调是比较常见使用场景因为我们通常会想在另外一个线程调用 JavaScript 函数。 使用线程安全函数 (ThreadSafeFunction) 为了在其他线程中调用 JavaScript 函数我们需要基于 Napi::Function 去创建一个 Napi::ThreadSafeFunction。 Napi::ThreadSafeFunction tsfn  Napi::ThreadSafeFunction::New(env,                     // Napi::Envinfo[0].AsFunction(),  // JavaScript 函数handler,               // 异步函数的名称用于调试的识别0,                       // 队列最大大小通常指定为 0 代表没有限制。如果队列已满则可能会导致调用时阻塞。1                        // 初始线程数量通常指定为 1。实际上是作为内存管理使用。可参考这篇文档。 ); 接着就可以把 tsfn 保存在任何位置并且并不需要同时保存一份 Napi::Env。 调用线程安全函数 调用线程函数有两种形式一种是同步调用另一种是异步调用。 同步调用 同步调用指的是如果我们限制了 ThreadSafeFunction 的队列大小并对其进行了多次调用从而创建了许多调用任务则会导致队列已满调用就会被阻塞直到成功插入队列后返回结果。 这是进行一次同步调用的例子 const char* value  hello world; napi_status status  tsfn.BlockingCall(value, [](Napi::Env env, Napi::Function callback, const char* value) {Napi::String arg0  Napi::String::New(env, value);callback.Call({ arg0 }); }); 这样一来就能顺利地在任意线程去调用 JavaScript 函数。 但是我们发现实际上我们并不能同步地获取函数调用的返回结果。并且 Node-API 或者 node-addon-api 都没有提供这么一种机制。但是我们可以借助 libuv 的信号量来达到这个目的。 uv_sem_t sem; uv_sem_init(sem, 0); const char* value  hello world; Napi::Value ret_val; napi_status status  tsfn.BlockingCall(value, [ret_val](Napi::Env env, Napi::Function callback, const char* value) {Napi::String arg0  Napi::String::New(env, value);*ret_val  callback.Call({ arg0 });uv_sem_post(sem); }); uv_sem_wait(sem);// 直至 JavaScript 运行结束并返回结果才会走到这里 // 这里就可以直接使用 ret_val 了 异步调用 异步调用则会在队列已满时直接返回错误状态而不进行函数调用。除此之外的使用方法同 “同步调用” 完全一致 const char* value  hello world; napi_status status  tsfn.NonBlockingCall(value, [](Napi::Env env, Napi::Function callback, const char* value) {Napi::String arg0  Napi::String::New(env, value);callback.Call({ arg0 }); }); Promise C 中创建 Promise 给 JavaScript 使用 我们通常会需要在 C 中实现异步函数。除了直接用上面已经介绍的基于回调的方法之外我们还可以直接在 C 中创建一个 Promise。 Promise 只支持同 线程 调用 由于 Promise 并未提供跨线程 Resolve 的方式因此如果希望在其他线程对 Promise 进行 Resolve 操作则需要结合 libuv 来实现。此方法比较繁琐建议转而使用跨线程回调函数。如果读者感兴趣后续本文可以补充相关内容。 我们可以直接创建一个 Promise并在函数中返回 Napi::Value YourFunction(const Napi::CallbackInfo info) {Napi::Promise::Deferred deferred  Napi::Promise::Deferred::New(info.Env());// 我们可以把 env 和 Napi::Promise::Deferred 保存在任何地方。// deferred_ 会在 Resolve 或者 Reject 之后释放。env_  info.Env();deferred_  deferred;return deferred.Promise(); } 接着我们可以在其他地方调用 Napi::Promise::Deferred 来完成 Promise。注意这里一定需要在主线程中调用 // 返回成功结果 deferred_.Resolve(Napi::String::New(info.Env(), OK)); // 返回错误 deferred_.Reject(Napi::String::New(info.Env(), Error)); C 中使用来自 JavaScript 的 Promise 由于 Node-API 或者 node-addon-api 均没有提供使用 Promise 的封装因此我们需要像在 JavaScript 中通过 .then 手动使用 Promise 的方式在 C 中使用 Promise。 // 首先需要定义两个函数用来接受 Promise 成功和失败 Napi::Value ThenCallback(const Napi::CallbackInfo info) { Napi::Value result  info[0];// result 是 Promise 的返回结果return info.Env().Undefined(); } Napi::Value CatchCallback(const Napi::CallbackInfo info) { Napi::Value error  info[0];// error 是 Promise 的错误信息return info.Env().Undefined(); }Napi::Promise promise  async_function.Call({}).AsNapi::Promise() Napi::Value then_func  promise.Get(then).AsNapi::Function(); then_func.Call(promise, { Napi::Function::New(env, ThenCallback, then_callback) }); Napi::Value catch_func  promise.Get(catch).AsNapi::Function(); catch_func.Call(promise, { Napi::Function::New(env, CatchCallback, catch_callback) }); 显然这种使用方式是比较繁琐的我们也可以通过一些办法使其可以将 C Lambda 作为回调函数来使用但是本文暂时不涉及这部分内容。 异步任务 异步任务通常是利用 libuv 提供的线程池来运行一些 CPU 密集型的工作。而对于一些跨线程异步回调的 Bindings 实现则直接使用 ThreadSafeFunction 即可。 具体使用可以参考https://github.com/nodejs/node-addon-api/blob/main/doc/async_worker.md Node-API 的构建 基本构建配置 Node.js Addon 通常使用 node-gyp 构建这是一个基于 Google 的 gyp 构建系统实现的构建工具。至于为何是 gyp因为 Node.js 是基于 gyp 构建的。 我们来看一个 node-addon-api 项目的构建配置以 bindings.gyp 命名 {targets: [{target_name: hello,cflags!: [ -fno-exceptions ],cflags_cc!: [ -fno-exceptions ],sources: [ hello.cc ],include_dirs: [!(node -p require(node-addon-api).include)],defines: [ NAPI_DISABLE_CPP_EXCEPTIONS ],}] } 具体配置可以参考官方使用文档https://gyp.gsrc.io/docs/UserDocumentation.md 一些常识 sources 中需要包含所有 C/C 代码文件不需要包含头文件!(node -p require(node-addon-api).include) 在使用 Node-API 还是 node-addon-api 的情况下是不同的。target_name 通常需要修改为你希望使用的扩展名称它会影响编译产物的名称。 常用构建命令 node-gyp rebuild 重新构建会清理掉已有的构建缓存推荐每次都使用这个命令来构建产物避免出现奇怪的问题 可以添加 --arch ARCH 参数来指定构建的目标架构例如希望构建一个 32 位的产物则可以使用 --arch ia32 来构建。node-gyp clean 清理构建缓存。如果希望使用 node-gyp build 来进行构建的话需要善用 clean 功能。 实用构建配置 添加头文件目录 include_dirs: [win32/x64/include ] 在 Windows 下进行动态链接 / 静态链接 libraries: [some_library.lib ] 对于动态链接需要指定 .dll 对应的 .lib 文件并在分发的时候将 .dll 放在 .node 相同的目录下。对于静态链接则直接指定 .lib 文件即可。但是在 Node.js Addon 中进行静态链接是一个比较费劲的事情因为通常涉及到对其他静态依赖的管理需要谨慎选择此方案。 在 Windows 下设置 C 版本 msvs_settings: {VCCLCompilerTool: {AdditionalOptions: [/std:c20]} } 在 Windows MSVC 下构建支持代码文件中的 UTF-8 字符中文注释等 本质上是给 MSVC 的编译器添加一个 /utf-8 参数 msvs_settings: {VCCLCompilerTool: {AdditionalOptions: [/utf-8]} } 在 MacOS 下进行动态链接 / 静态链接 link_settings: {libraries: [-L动态库或静态库所在的文件夹,-l动态库名称] } 在 MacOS 下引入系统 Framework 依赖 libraries: [-framework MediaPlayer,-framework Cocoa, ] 在 MacOS 下设置 C 版本 cflags_cc: [-stdc20 ] 在 MacOS Xcode 的 Release 构建下生成 .dSYM 调试文件 xcode_settings: {DEBUG_INFORMATION_FORMAT: dwarf-with-dsym } 使 MacOS 下的 addon 能够使用同目录下的动态库 / Framework link_settings: {libraries: [-Wl,-rpath,loader_path,## 此外还可以设置到任何相对于 .node 文件的其他目录下-Wl,-rpath,loader_path/../../darwin/arm64,] }, 但是这也要求 .dylib 文件支持该功能可以通过 otool -D 你的动态链接库位置.dylib 的返回结果来检查 你的动态链接库.dylib: rpath/链接库名称.dylib 如果文件名往前的开头是 rpath则意味着支持该功能。如果不是则可以使用 install_name_tool 来修改动态链接库使其支持 install_name_tool -id rpath/链接库名称.dylib 你的动态链接库位置.dylib 在 MacOS 下支持 Objective-C 和 C 混编 xcode_settings: {OTHER_CFLAGS: [-ObjC] } 开发分发使用 项目文件组织 通常来说我们可以用下面的文件夹结构来扁平地组织我们的 addon 文件 . ├── node_modules                   ## npm 依赖 ├── build                          ## 构建路径 │   ├── Release                   ## Release 产物路径 │       ├── myaddon.node          ## addon 产物 │       ├── myaddon.node.dSYM     ## addon 的符号文件 ├── binding.gyp                    ## 构建配置 ├── addon.cc                       ## Addon 的 C 源码 ├── index.js                       ## Addon 的 JavaScript 源码 ├── index.d.ts                     ## Addon 的 TypeScript 类型下方会介绍 └── package.json                   ## Addon 的 package.json 文件 当然我们也可以把 JavaScript 源码和 C 源码分别放入不同的文件夹只需要修改对应的构建配置和 package.json 即可。 编写 index.js - 使用 bindings 包 一般来说我们会直接在 C 中实现大部分逻辑JavaScript 文件只用来引入 .node 文件。由于 Node.js Addon 存在各种不同的方案、构建配置因此 .node 文件产物的位置可能也会因此不同所以我们需要借助一个第三方 npm 包来自动为我们寻找 .node 文件的位置 https://github.com/TooTallNate/node-bindings 通过 bindings我们的 index.js 仅需一行代码就能自动获取并导出 .node 模块 module.exports  require(bindings)(binding.node) 同时保证 package.json 的 main 配置为我们的 index.js {// ...main: index.js// ... } 为 Addon 添加 TypeScript 类型 添加 TypeScript 类型最简单的方式只需要创建一个 index.d.ts 文件并在其中声明在 C 代码中创建的函数们即可 export interface FooOptions {bar: string }export function foo(options: FooOptions) 并在 package.json 添加一行参数用于指向类型文件 {// ...types: index.d.ts// ... } 大部分情况下这个方法就可以给你的 Node.js Addon 声明类型。 分发形式 安装时编译 一种方式是在使用者进行 npm install 时使用用户设备进行 Addon 的编译。这时候我们可以使用 install 钩子来实现我们仅需在 package.json 文件中添加如下内容 {// ...scripts: {// ...install: prebuild-install || node-gyp rebuild --release// ...}// ... } 保险起见确保 node-gyp 在你的 devDependencies 之中这样就能在用户通过 npm 安装你的 Addon 时自动编译当前系统架构所对应的产物。 预编译 如果希望更近一步节约用户安装 Addon 的时间或者是为了让用户无需具备编译环境即可安装 Addon可以使用预编译方案。即在集成环境中提前编译常见的操作系统、架构对应的 .node 文件并随着 npm 包进行分发再通过 bindings 或者其他一些库来自动匹配寻找系统所需要的对应 .node 文件。 由于预编译方案涉及到更多的细节本文不再做介绍大家可以参考该项目 https://github.com/mapbox/node-pre-gyp
http://www.zqtcl.cn/news/57520/

相关文章:

  • 网站制作容易吗学做网站如何创业
  • 手机网站开发公司南充网站建设服务商
  • 合肥市建设局网站注册深圳公司需要多少钱
  • 推文最好的网站是哪个世界500强企业招聘网站
  • 济阳建设局网站怎样看一个网站的浏览量
  • 网站建设市场价三个字最吉利最旺财的公司名
  • 免费建站优化8个实用的wordpress数据库技巧
  • 域名及网站建设实训企业php网站建设
  • 免费永久个人网站可以自己做论坛网站吗
  • 购买 做网站 客户昆明做网站的公司哪家好
  • 石景山网站建设的大公司大连2021建设网
  • wordpress 下拉式菜单一个网站多个域名的seo优化
  • 南京制作网站公司网站安康公司网站制作
  • 湖南营销型网站建设 搜搜磐石网络wordpress 外贸站
  • 网站建设如何吸引投资网络营销系统
  • 合肥网站建设团队免费搭建网站模板
  • 建设银行网上银行网站网线制作实验总结
  • 搬家网站怎么做wordpress 500 php版本
  • 可以做ppt的网站有哪些方面南宁小程序开发网站建设公司
  • 网站建设主要营销内客微网站和app的区别
  • 网站做法软件外包价格一般多少
  • 美容产品网站建设多少钱怎样做信息收费网站
  • 做网站哪里便宜厦门自己建网站
  • 餐饮网站建设优化建站宁波建设监理管理协会网站
  • 网站制作最域名过期了被别人拿去做违法
  • 网站开发和软件开发含义做赚钱问卷调查的网站
  • 大连哪家做网站比较好公总号开发就是网站开发吗
  • 雅客网站建设管理系统英文
  • 玩网页游戏的网站网站数据采集怎么做
  • 东莞网站建设dgjwz网站所有权