如何用C++扩展NodeJS的能力?

news2024/11/15 18:15:07

文章目录

    • 前言
    • C++结合NodeJS的魅力
    • C++和NodeJS怎么结合
    • 通过Addon增强NodeJS
    • 环境的准备
      • 1. node-gyp
      • 2. nan (Native abstraction for NodeJS)
    • 编写Addon的C++代码
      • JS方法的C++表示
      • JS方法的传入参数 v8::Argument
    • 进阶
      • 进阶1: 输出一个JS包装类型
      • 进阶2: 使用多线程异步计算
    • 最后

前言

Javascript 是一门强大的语言,看似简单,其实包罗万象。NodeJS 是一个非常有活力的平台,JS社区是GitHub上最有创造力的社区。而C++是一门古老而强大的语言,是最有历史积淀的语言之一。那么两者结合,各自发挥自己的优势,就成了一种顺理成章的选择。

C++结合NodeJS的魅力

NodeJS 在网络,文件,数据库等方面有丰富的支持,可以几分钟就构建起一个小型网站,而C++在性能和内存占用方面有压倒性的优势。因此,在一些网络,数据库等I/O为主的场景时,使用Javascript而在一些局部计算密集任务时使用C++,这样可以同时享受JS带来的开发效率和稳定的好处,也可以享受C++带来的性能提升。

关于C++和JS的性能对比,我写了一个KMP算法的例子,完全一样的代码,C++性能大约是JS的7~8倍
chart

详细的测试代码:C++ ,JS ,计时代码
特定的场景下,由于C++可以做一些更精细的优化,实际可以做到更大的提升。

另外从我曾经做的一些项目的改造来看,内存使用上,使用JS对象存储和使用C++的struct存储同样数据,C++可以省下90%以上的内存。

C++和NodeJS怎么结合

Google的V8引擎是用C++开发的,NodeJS本身大部分基础Addon也是C++开发的,因此C++和NodeJS的结合比想象中容易得很多:

node和V8都提供了完善的C++ API来管理JS原生的类型,对象和模块, 所有的JS对象,函数,都能在V8的API中找到相对应的类型,如果同时了解C++和JS的语法,可以极快上手。
无需定义专门的语言绑定层,例如Java的JNI。这是由于JS的所有函数在V8引擎中,实际上都是同样形式(同样的返回值和参数列表),因此无须JNI那种复杂的方法签名机制, 于是Node就帮我们包办了标准C的输出接口,开发者只要将精力更集中在实际的C++逻辑上就好了。
最重要的,Node改造了Google的GYP(Generate Your Project)作为NodeJS addon的夸平台编译工具node-gyp。GYP本身就是特点就是简单易用,而它基于JSON格式的配置文件,更是对Javascript开发者极度友好。

说了半天,可能有人要骂 No BB, show me the code,程序员不来点实际的怎么行。接下来,介绍一下NodeJS的C++需要准备哪些东西,如何使用node-gyp,以及,如何将一个C++的class做成一个JS的class,变得和js对象的实例那样变得有生命周期,自己的方法等。

通过Addon增强NodeJS

对计算密集任务可以用C++写NodeJS的Addon来提高性能的优势。然而究竟应该怎么做呢?先给出一个addon文件的例子:

-rwxrwxr-x 1 melode 88920 Jun 17 22:32 meshtool.node

可以看到NodeJS的addon就是一个后缀为node的文件,这货实际上应该就是一个动态链接库,通过JS代码

var tool = require('./build/Release/meshtool');

即可加载。然而怎么得到这个东东?待我细细道来。

环境的准备

1. node-gyp

这个是C++ addon的跨平台构建工具,有了它我们才能在各个平台编译出.node文件。可以通过一个简单的配置文件binding.gyp描述编译的内容,如源代码文件,依赖的头文件目录,静态库目录,编译器参数等。然后在主流平台上,它都可以将你的配置转化为一套构建脚本(Makefile或VS工程).

获取它很简单,可以通过npm安装:

npm install -g node-gyp

如果没有安装tnpm,用npm安装也可以,注意,在内网指定阿里内部的目录镜像可以爽到飞起:

npm install -g node-gyp --registry=http://registry.npm.aliyun.com

装完以后就可以使用 node-gyp这个命令了:)
第一次用以前,建议调用如下命令先:

node-gyp install --ensure --disturl=http://npm.taobao.org/dist

上面那个命令是安装node-gyp编译所需的node和v8等依赖库文件。若不运行也会在第一次编译时自动运行。但事先通过指定disturl安装的话,节约了等待时间。
你可以通过运行

node-gyp -v

来检查命令是否成功安装。

到此node-gyp已经安装完毕,可以用来编译项目的addon了。

首先得先准备好binding.gyp, 可以放在你项目的任何目录下,但要注意

  1. 要和你准备运行node-gyp命令的那个目录一致
  2. 配置中的涉及到的文件或目录的相对路径要从该目录开始。
    我们可以将它放在项目根目录。

配置文件的内容是一个大JSON,可以非常简单:

{
  "targets": [
    {
      "target_name": "meshtool",
      "sources": [./meshfilereader.cpp”,"./index.cpp"]
     }]
}

以上配置指定了一个叫meshtool的编译目标, 该目标需要编译的cpp文件为 index.cpp和meshfilereader.cpp.

接着在项目放binding.gyp的目录运行 node-gyp configure, 该目录下会生成一个build的目录,内含构建所需的规则和makefile.
于是我们再运行node-gyp build, 在运行目录/build/Release下我们便可以找到所需的.node文件了。

2. nan (Native abstraction for NodeJS)

当你开始写Addon不久以后,你便会发现一个令人抓狂的现实:NodeJS 的 0.12.x,0.11.x和0.10.x因为使用的V8版本不同,存在严重的API不兼容的情况。不管你愿不愿意,你发现只能做到令其中一个版本通过编译。你开始苦恼C++不能像JS代码那样,不用改一行代码就同时满足所有NodeJS版本, 直到你发现了nan.

nan 是老外的一个项目(不是"Not A Number"),解决了不同NodeJS版本间API的不兼容问题,同样可以用npm安装:

npm install nan
//or
npm install -g nan

也可以直接配置到package.json中。

安装完后要在binding.gyp中配置好nan的头文件依赖,修改如下:

{
  "targets": [
    {
      "target_name": "meshtool",
      "sources": [./meshfilereader.cpp”,"./index.cpp"],
      "include_dirs" : [
           "<!(node -e \"require('nan')\")"
     }]
}

之所以include_dirs要这么写是因为这样可以自适应nan模块安装在全局和安装在本地的情况。如果确定安装方式的话,也可以直接写路径。

至此万事俱备,只欠代码。

编写Addon的C++代码

写代码前的预备知识
写Addon之前,建议先要了解一下Google V8的API,至少要了解以下的一些概念:

JS 基本类型 对应的V8原生C++ 原生类型 :

Javascript	V8
Number	v8::Number, v8::Integer
String	v8::String
Array	v8::Array
Object	v8::Object
Function	v8::Function

JS基本类型和V8的原生类型之间实际是等价的,也就是C++层从JS层获取到的JS基本对象和返回JS层的结果,都是以v8的上述的原生类型形式。

JS句柄 v8::Handle 它相当于一个智能指针,所有上面的C++的原生类型都是由Handle来引用的,相当于JS中的那个var 变量,因此不管是从JS层获取到的原生类型对象还是在C++内部构造出的原生类型对象,都是以 v8::Handle 形式给出来的。
v8::Handle分为两种,v8::Local和v8::Persistant, 前者只在当前Scope中有效,后者是代表全局变量。
v8::Local 的Scope由 HandleScope管理,由离最近的HandleScope分配,并随HandleScope生命周期结束而结束。而v8::Persistant的生命周期由自己的New和Dispose方法管理。
生命周期结束的Handle,其指向的对象会随时被垃圾收集回收。

JS方法的C++表示

必须为为全局函数或静态方法,根据V8版本不同固定为如下的形式:

V8 3.11

v8::Handle<v8::Value>  AnyMethodName(v8::Argument args)
V8 3.28

void AnyMethodName(v8::Argument args)

JS方法的传入参数 v8::Argument

不管什么样的JS函数,其C++方法的传入参数都是一个v8::Argument对象,这是因为v8::Arugment本身就是一个list, 内含可变数量的实际参数,如果想取第i个传入参数,只需要使用args[i] 即可。另外还可以通过args.This()获取this对象。

了解完概念,我们试着写一个输出一个方法的Addon
和写普通的JS模块一样,Addon的代码需要确定模块的输出,这里就是借助Nan写输出一个叫parseMesh的JS方法的Addon:

NAN_METHOD(ParseMesh)
{
    NanScope();
    if(args.Length() < 2 || !args[0]->IsString() || !args[1]->IsFunction())
    {
        return NanThrowError("Bad Arguments");
    }
    Handle<String> filename = args[0].As<String>();
    Handle<Function> callback = args[1].As<Function>();
    ...
    NanReturnUndefined();
}
 
void init(Handle<Object> exports)
{
    NODE_SET_METHOD(exports,"parseMesh",ParseMesh);
}
 
NODE_MODULE(meshtool, init);

以上代码最后一行定义模块名称meshtool,和加载它的时候调用的初始化方法init.
而初始化方法中则设置了输出的函数名parseMesh, 而实际接受parseMesh调用的C++方法即ParseMesh。

再来看这个ParseMesh方法,由于前面所说,因为v8::Argument的存在,所有JS的函数在C++层的方法参数和返回值都是一致的,所以它可以被一个NAN_METHOD的宏来处理,该宏根据Node版本将方法展开成对应的形式。保证ParseMesh方法可以在初始化中注册为任意版本JS函数parseMesh的的实现。

最后的NanReturnUndefined()表示该方法返回undefined (没有返回值即返回undefined). Nan还有很多其他的Return形式可以使用。

到此我们已经可以用C++ Addon来输出简单的JS函数了,这对于大多数情况已经够用。然而NodeJS还提供了一些更高大上的东西,比如输出一个自定义的JS的类型,或者在C++中使用多线程,并异步执行回调等。

进阶

进阶1: 输出一个JS包装类型

前一篇只提到了如何输出一个JS方法,但有的时候如果我们想输出的是一个C++的对象呢,这种情况在想要包装一个现有的C++库到JS的时候出现的尤其频繁。
如果我们仅有输出C++方法成为JS函数一条路,那也有笨办法,用C++代码表示:

//C++
class Body
{
    Body();
    void Move();
};
 
NAN_METHOD(CreateBody)
{
    NanScope();
    Body* handle = new Body();
    NanReturnValue(NanNew<Integer>(reinterpret_cast<int>(handle)));
}
 
NAN_METHOD(BodyMove)
{
    //check arguments
      Body* handle = reinterpret_cast<Body*>(args[0].As<Integer>()->intValue());
      handle->Move();
}
 
NAN_METHOD(DestroyBody)
{
    //check arguments
     Body* handle = reinterpret_cast<Body*>(args[0].As<Integer>()->intValue());
     delete handle;
}
void init(Handle<Object> exports) {
     NODE_SET_METHOD(exports,"createBody",CreateBody);
     NODE_SET_METHOD(exports,"bodyMove",BodyMove);
     NODE_SET_METHOD(exports,"destroyBody", DestroyBody);
}
NODE_MODULE(native_body, init)

相应的使用native addon的JS代码:

var native=require("native_body");
var handle = native.createBody();
native.bodyMove(handle);
native.bodyDestroy(handle);

其实就是将一个Body对象的指针作为JS的一个int变量让JS层持有,每当要操作该对象时,重新将该指针传回。
但是这样的实现有很多缺点:

  1. 所有的JS方法需要传入一个额外的由CreateBody得到的handle。
  2. 用完必须显式调用BodyDestroy, 否则会内存泄露。
  3. 不安全,如果黑客通过外部传入特定地址的handle, 内部也会将它当做Body指针而执行对应方法,轻则程序崩溃,重则程序行为被控制。(不过这个问题可以通过向外部提供‘间接’地址解决,不展开了)

NodeJS对这种需求提供了比较完美的解决方案- ObjectWrap , 通过自定义C++ class继承ObjectWrap,NodeJS可以输出和自定义JS类型等价的对象。上面的代码可以改成这样:

class BodyWrap : public ObjectWrap
{
    Body* internalBody_;
 
public:
    BodyWrap():
    internalBody_(new Body())
    {
    }
 
    ~BodyWrap()
    {
        delete internalBody_;
    }
 
    static NAN_METHOD(New){
        NanScope();
        // arg check is omitted for brevity
        BodyWrap *jsBody = new BodyWrap();
        jsBody->Wrap(args.This());
        NanReturnValue(args.This());
    }
 
    static NAN_METHOD(Move)
    {
        //check arguments
        BodyWrap* thisObj = ObjectWrap::Unwrap<BodyWrap>(args.This());
        thisObj->internalBody_->Move();
    }
};
 
void init(Handle<Object> exports) {
    NanScope();
    Local<FunctionTemplate> t = NanNew<FunctionTemplate>(BodyWrap::New);
    t->InstanceTemplate()->SetInternalFieldCount(1);
    t->SetClassName(NanNew<String>("Body"));
    NODE_SET_PROTOTYPE_METHOD(t, "move", BodyWrap::Move);
    exports->Set(NanNew<String>("Body"), t->GetFunction());
}
 
NODE_MODULE(native_body, init)

相应的JS代码:

var Body = require("native_body").Body;
var b = new Body();
b.move();

从JS代码可以看到已经不存在什么handle了,需要Body实例的时候可以直接new 出来,在该实例上调用方法,对应的C++ Body类型的方法就会执行,这和普通的JS自定义class完全没什么区别。
另外还可以注意到一点,JS代码中没有执行任何类似于DestroyBody的方法。那C++的Body实例何时释放呢?-- 在上面这个代码中,当new出来的JS实例 b被垃圾回收时,C++ Body实例会被自然的析构。

进阶2: 使用多线程异步计算

通常使用C++ Addon的场景,都是计算密集的任务,另外从前面的一些实例代码可以看出,C++到JS之间的数据传递中,是有很多装箱/拆箱的消耗的(如从v8::Number 到double),因此我们为了避免这种损耗,通常希望在C++中做尽量多的事情,而不希望将任务过度切分,因此如果全部在主线程执行,无可避免的会对主线程造成阻塞。解决方案则是将主要的计算任务,放在另一个线程中执行,再将结果数据在主线程中通过回调交回给JS。
假设前面的Body对象多了一个计算量很高的 checkCollision方法检查是否与其他物体碰撞,并返回布尔值。如何将checkCollision放到其他线程,再将结果返回主线程呢?我们需要用到另一个NodeJS的基础库:libuv.
下面这个示例演示了如何创建一个线程来运行checkCollision,并在线程中使用uv_aync_send方法将结果带回到主线程回调给JS层。

class Body
{
    ...
    bool checkCollision();
};
 
struct BodyContext
{
    Body* body;
    uv_async_t async;
    bool result;
    NanCallback *callback;
    uv_thread_t tid;
};
 
void AfterCheckCollision(uv_async_t *async)
{
    BodyContext* ctx = async->data;
    Handle<Value> argv[] = {NanNew<Boolean>(ctx->result)};
    ctx->callback->Call(1,argv);
    uv_thread_t tid = ctx->tid;
    uv_thread_join(&tid);
    delete ctx->callback;
    delete ctx;
}
 
void RunCheckCollision(BodyContext* ctx)
{
    ctx->result = ctx->body->CheckCollision();
    uv_async_init(uv_default_loop(), &ctx->async,AfterCheckCollision);
    ctx->async.data = ctx;
    uv_async_send(&ctx->async);
}
 
class BodyWrap: public ObjectWrap
{
...
static NAN_METHOD(CheckCollision)
{
    //这里假设外部的this--也就是JS body对象是一直有引用持有的。否则要使用v8:Persistant进行保持不在异步执行过程中被GC.
    BodyWrap* thisObj = ObjectWrap::Unwrap<BodyWrap>(args.This());
    Handle<Function> cb = args[0].As<Function>();
    NanCallback *callback = new NanCallback(cb);
    BodyContext *ctx = new BodyContext();
    ctx->body = thisObj->internalBody_;
    ctx->callback = callback;
    uv_thread_t tid;
    uv_thread_create(&tid,RunCheckCollision,ctx);
    ctx->tid = tid;
}
...
}

相比之前的示例,这个示例稍显啰嗦,但实际上可以简化为简单的几步来讲:

  1. JS层调用C++层的计算接口 - NAN_METHOD(CheckCollision),创建线程,预先分配异步执行需要的上下文(BodyContext) , 然后使用启动线程的API, (这里用uv_thread,实际上线程API没有特定限制) 。
  2. 异步线程中执行的方法体 - RunCheckCollision , 实际执行计算,并保存计算结果,然后使用 uv_async_init和uv_async_send将结束回调发送到主线程。
  3. 主线程中AfterCheckCollision 执行,通知JS层结果,并释放#1中预先分配的上下文。

而示例中的BodyContext贯穿于整个3步中,成为两个线程间数据交流的载体.在多个线程中共享的对象必须是在堆上分配的,因此这里的BodyContext指针以及BodyContext中含有的指针必须指向堆上分配的对象。

更进一步的,这里的线程不是必须使用libuv的接口,你可以使用任何实现,比如std::thread, 或者是真正具有工程意义的各种线程池库, 任何能让你的 RunCheckCollision 运行在非主线程的办法都可以替换示例中的uv_thread , 不过要记得最好使用跨平台库哦,否则nodejs跨平台的特性可就丢了。更多的libuv 的示例可以参考libuv-examples。

最后

文章到这里就结束了,里面介绍的方法,已经足够满足用C++给NodeJS写任何Addon的需求,如果需要在具体细节上增加了解,建议大家还是多实践,多查NODE和V8文档。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/171082.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Qt使用第三方库QXlsx将数据库的数据导出为Excel表格

一、参考和下载第三方库QXlsx 参考1 这篇博客对第三方库QXlsx介绍的比较详细。 1、概述 QXlsx是一个可以读写Excel文件的库。不依赖office以及wps组件&#xff0c;可以在Qt5支持的任何平台上使用。 2、使用方式 (1) QXlsx可以编译为静态库库使用&#xff08;可以提升项目编…

第03讲:使用kubeadm搭建k8s单master集群方案

一、安装前的准备工作 本实验使用1个master节点和2个node节点。 硬件配置&#xff08;必要&#xff09;&#xff1a;2GB 或更多 RAM&#xff0c;2 个 CPU 或更多 CPU&#xff0c;硬盘 30GB 或更多 开始本实验之前请先按照 使用kubeadm搭建k8s集群的准备工作 进行实验前的准备工…

从零开始带你实现一套自己的CI/CD(五)Jenkins+K8s

目录一、简介二、Jenkins K8s2.1 Jenkins配置k8s-master服务器信息2.2 配置镜像仓库信息2.3 编写k8s yaml文件2.4 将yaml文件推送到k8s2.5 配置免密钥登录2.6 k8s部署yaml资源文件2.7 重新部署yaml资源文件2.8 构建注意事项2.9 完整Jenkinsfile2.10 构建成功三、Webhook源码一…

合宙ESP32S3 CameraWebServe 测试demo

合宙ESP32S3 CameraWebServe 合宙ESP32S3 CameraWebServe测试&#xff0c;我们需要一个OV2640的摄像头模组用来采集图像传输给ESP32的&#xff0c;这里使用的OV2640是之前安信可十周年的白嫖的。现在直接插到合宙ESP32S3开发板&#xff0c;简直完美。还是白嫖好&#xff01;&a…

评估-----评估算法的指标

评估算法的优劣一般会用到以下参数&#xff1a; TN&#xff1a; 真反例 FN: 假反例 TP&#xff1a; 真正例 FP: 假正例 正样本负样本预测正样本TPFP预测负样本FNTN**精确率/查准率&#xff08;precision&#xff09;&#xff1a;**预测正确的正样本个数与预测为正样本的个数的…

【NI Multisim 14.0虚拟仪器设计——放置虚拟仪器仪表(函数发生器)】

目录 序言 &#x1f34d;放置虚拟仪器仪表 &#x1f349;函数发生器 序言 NI Multisim最突出的特点之一就是用户界面友好。它可以使电路设计者方便、快捷地使用虚拟元器件和仪器、仪表进行电路设计和仿真。 首先启动NI Multisim 14.0&#xff0c;打开如图所示的启动界面&am…

3-Spring创建

目录 1.创建一个普通的Maven项目 2.添加Spring框架支持(spring-context&#xff0c;spring-beans) 3.添加启动类 1.创建一个普通的Maven项目 不选择任何模板&#xff0c;直接点Next。 Name&#xff1a;项目名称&#xff1b; Location&#xff1a;项目保存路径&#xff1b; …

Lesson 3. 线性回归的手动实现(3.1 变量相关性基础理论 3.2 数据生成器与 Python 模块编写)

文章目录一、变量相关性基础理论二、数据生成器与 Python 模块编写1. 自定义数据生成器1.1 手动生成数据1.2 创建生成回归类数据的函数2. Python 模块的编写与调用在此前的内容当中&#xff0c;我们已经学习了关于线性回归模型的基本概念&#xff0c;并且介绍了一个多元线性回归…

看了以后大呼过瘾的程序员必备网站,速速收藏!

程序员必备的网站&#xff0c;网络上一搜一大把&#xff0c;动辄几十个甚至一百个&#xff0c;虽说大多数网站也都是实用的&#xff0c;但数量庞杂未免让人眼花缭乱。 这里我就只挑选精华&#xff0c;只挑选出程序员必备的8个网站&#xff0c;服务于程序员的工作&#xff06;生…

Python和MySQL对比(5):用Pandas实现MySQL窗口函数的效果

文章目录一、前言二、语法对比数据表row_number()lead()/lag()rank()/dense_rank()first_value()count()/sum()三、小结一、前言 环境&#xff1a; windows11 64位 Python3.9 MySQL8 pandas1.4.2 本文主要介绍 MySQL 中的窗口函数row_number()、lead()/lag()、rank()/dense_ran…

工业互联网蓬勃发展,出奇才能制胜

近年来&#xff0c;随着我国工业数字化转型的快速推进&#xff0c;我国工业发展进入产业新阶段&#xff0c;工业互联网迎来更加强劲的发展动能和更加广阔的发展空间&#xff0c;我国希望把握住新一轮的科技革命和产业革命&#xff0c;推进工业领域实体经济数字化、网络化、智能…

Cassandra入门教程

文章目录一、数据存储方式和NoSQL1.1 数据存储方式1.2 NoSQL概述1.3 NoSQL的分类二、Cassandra的介绍2.1、Cassandra概述2.1.1 来自百科的介绍2.1.2 Cassandra的Logo2.2、Cassandra特点2.3、Cassandra使用场景2.3.1 特征2.3.2 场景举例三、Cassandra下载、安装、访问3.1 Cassan…

Datawhale 吃瓜教程组队学习 task01

Datawhale 吃瓜教程组队学习task01 还没写完&#xff0c;会持续更新~~ 上个月看了周志华老师的机器学习视频课的前三章&#xff0c;但是后面中断了没看…(主要是懒&#x1f910;) 于是打算这个月继续来学习西瓜书和南瓜书&#x1f92f; Task01&#xff1a;概览西瓜书南瓜书第1、…

【Kubernetes 企业项目实战】04、基于 K8s 构建 EFK+logstash+kafka 日志平台(上)

目录 一、日志对我们来说到底重不重要&#xff1f; 日志打印的常见级别 二、常见的日志收集方案 2.1 EFK 2.2 ELK Stack 2.3 ELKfilebeat 2.4 其他方案 三、EFK 组件详细介绍 3.1 Elasticsearch 组件介绍 3.2 Filebeat 组件介绍 1&#xff09;Flebeat 和 Beat 关系…

贪心策略(五)主持人调度(一、二)

主持人调度&#xff08;一&#xff09;_牛客题霸_牛客网 有 n 个活动即将举办&#xff0c;每个活动都有开始时间与活动的结束时间&#xff0c;第 i 个活动的开始时间是 starti ,第 i 个活动的结束时间是 endi ,举办某个活动就需要为该活动准备一个活动主持人。 一位活动主持人在…

InnoDB与MyISAM引擎的区别

1. InnoDB与MyISAM引擎的区别 常用引擎&#xff1a; – InnoDB&#xff1a;支持事务&#xff0c;行级锁&#xff0c;外键&#xff0c;崩溃修复&#xff0c;多版本并发控制&#xff1b;读写效率相对较差&#xff0c;内存使用相对较高&#xff0c;占用数据空间相对较大。 – MyI…

学习IBDP中文A课程需要提前准备吗?

俗话说“宜未雨而绸缪&#xff0c;毋临渴而掘井”&#xff0c;也就说凡事都应该要预先做好充分的准备&#xff0c;防患于未然。而学习DP的中文课程也是如此。那么我们一起来看看&#xff0c;在正式进入中文A课程的学习之前&#xff0c;我们可以做哪些准备&#xff0c;令我们的学…

Qml开发之环境搭建

进入官网下载相应版本的qtcreator &#xff1a;https://download.qt.io/archive/qt/5.12/5.12.6/ 1.1 安装的时候注意如下对话框&#xff0c;需要选择下图所示的必须选项&#xff0c;因为我是mac 所以选择的macOS下载完之后进行点击安装&#xff0c;安装后运行软件图片如下&…

C#使用Spire.OCR框架识别图片中的字母,数字,文字等

OCR OCR&#xff08;optical character recognition&#xff09;&#xff0c;光学字符识别。 OCR文字识别是指电子设备&#xff08;例如扫描仪或数码相机&#xff09;检查纸上打印的字符&#xff0c;然后用字符识别方法将形状翻译成计算机文字的过程&#xff1b;即&#xff0c…

AWS实战:S3 Cloud Watch Event 触发Lambda

架构 既然是S3 Cloud Watch Event 触发Lambda&#xff0c;首先就需要三个AWS的service: S3Event BridgeLambda S3有event产生时向Event Bridge发送event&#xff0c;Event Bridge通过event rule的配置过滤event&#xff0c;将符合规则的event发送给lambda进行处理。 S3如何向…