仓颉语言的编译和构建
一、cjc 使用
cjc是仓颉编程语言的编译命令,其提供了丰富的功能及对应的编译选项,本章将对基本使用方法进行介绍。
cjc-frontend (仓颉前端编译器)会随 cjc 一起通过 Cangjie SDK 提供,cjc-frontend 能够将仓颉源码编译至仓颉的中间表示 (LLVM IR)。 cjc-frontend 仅进行仓颉代码的前端编译,虽然 cjc-frontend 和 cjc 共享部分编译选项,但编译流程会在前端编译结束时中止。使用 cjc 时仓颉编译器会自动进行前端、后端的编译以及链接工作。cjc-frontend 仅作为前端编译器的实体体现提供,除编译器开发者外,仓颉代码的编译应优先使用 cjc 。
1.1 cjc 基本使用方法
想必你已经在学习仓颉的过程中尝试着使用 cjc 了,我们先来看一下 cjc 的基本使用方法,如果你想了解详细的编译选项内容,请自行查阅“cjc 编译选项”章节。
cjc 的使用方式如下:
cjc [option] file...
假如我们有一个名为 hello.cj 的仓颉文件:
main() {println("Hello, World!")
}
我们可以使用以下命令来编译此文件:
$ cjc hello.cj
此时工作目录下会新增可执行文件 main ,cjc 默认会将给定源代码文件编译成可执行文件,并将可执行文件命名为 main。
以上为不给任何编译选项时 cjc 的默认行为,我们可以通过使用编译选项来控制 cjc 的行为,例如让 cjc 进行整包编译,又或者是指定输出文件的名字。
二、cjpm 介绍
CJPM(Cangjie Package Manager) 是仓颉语言的官方包管理工具,用来管理、维护仓颉项目的模块系统,并且提供更简易统一的编译入口,支持自定义编译命令。通过包管理器自动依赖管理实现对引入的多版本三方依赖软件进行分析合并,无需开发者担心多版本依赖冲突问题,大大减轻开发者负担;同时提供基于仓颉语言原生的自定义构建机制,允许开发者在构建的不同阶段增加预处理和后处理流程,实现构建流程可灵活定制,能够满足开发者不同业务场景下的编译构建诉求。
2.1 cjpm 基本使用方法
通过 cjpm -h 即可查看主界面,由几个板块组成,从上到下分别是: 当前命令说明、使用示例(Usage)、支持的可用命令(Available subcommands)、支持的配置项(Available options)、更多提示内容。
Cangjie Package ManagerUsage:cjpm [subcommand] [option]Available subcommands:init Init a new cangjie modulecheck Check the dependenciesupdate Update cjpm.locktree Display the package dependencies in the source codebuild Compile the current modulerun Compile and run an executable producttest Unittest a local package or moduleclean Clean up the target directorypublish Push a module to the repositoryload Load a module from the repositorylist Get module list from the repositoryinstall Install a cangjie binaryuninstall Uninstall a cangjie binaryAvailable options:-h, --help help for cjpm-v, --version version for cjpmUse "cjpm [subcommand] --help" for more information about a command.
cjpm init 用来初始化一个新的仓颉模块或者工作空间。初始化模块时会默认在当前文件夹创建 cjpm.toml 文件,并且新建 src 源码文件夹,在 src 下生成默认的 main.cj 文件。自定义参数初始化功能支持可以通过cjpm init -h 查看。
例如:
输入: cjpm init
输出: cjpm init success
cjpm build 用来构建当前仓颉项目,执行该命令前会先检查依赖项,检查通过后调用 cjc 进行构建。支持全量编译、增量编译、交叉编译、并行编译等,更多编译功能支持可以通过cjpm build -h 查看。通过cjpm build -V 命令可以打印所有的编译过程命令。
例如:
输入: cjpm build -V
输出:
compile package module1.package1: cjc --import-path target -p "src/package1" --output-type=staticlib -o target/release/module1/libmodule1.package1.a
compile package module1: cjc --import-path target -L target/release/module1 -lmodule1.package1 -p "src" --output-type=exe --output-dir target/release/bin -o maincjpm build success
2.2 cjpm.toml 配置文件说明
配置文件 cjpm.toml 用来配置一些基础信息、依赖项、编译选项等内容,cjpm 主要通过这个文件进行解析执行。
配置文件代码如下所示:
[package]cjc-version = "0.49.1" # 所需 `cjc` 的最低版本要求,必须name = "demo" # 模块名及模块 root 包名,必须description = "nothing here" # 描述信息,非必须version = "1.0.0" # 模块版本信息,必须compile-option = "" # 额外编译命令选项,非必须link-option = "" # 链接器透传选项,可透传安全编译命令,非必须output-type = "executable" # 编译输出产物类型,必须src-dir = "" # 指定源码存放路径,非必须target-dir = "" # 指定产物存放路径,非必须package-configuration = {} # 单包配置选项,非必须[workspace] # 工作空间管理字段,与 package 字段不能同时存在members = []build-members = []test-members = []compile-option = ""link-option = ""target-dir = ""[dependencies] # 源码依赖配置项aoo = { version = "1.0.0" } # 导入中心仓依赖boo = "1.1.0" # 导入中心仓依赖coo = { git = "xxx",branch = "dev" , version = "1.0.0"} # 导入 `git` 依赖,`version`字段可缺省doo = { path = "./pro1" ,version = "1.0.0"} # 导入源码依赖,`version`字段可缺省[test-dependencies] # 测试阶段的依赖配置项[ffi.c] # 导入 `c` 库依赖clib1.path = "xxx"[profile] # 命令剖面配置项build = {}test = {}customized-option = {}[target.x86_64-unknown-linux-gnu] # 后端和平台隔离配置项compile-option = "value1" # 额外编译命令选项,适用于特定 target 的编译流程和指定该 target 作为交叉编译目标平台的编译流程,非必须link-option = "value2" # 链接器透传选项,适用于特定 target 的编译流程和指定该 target 作为交叉编译目标平台的编译流程,非必须[target.x86_64-w64-mingw32.dependencies] # 适用于对应 target 的源码依赖配置项,非必须[target.x86_64-w64-mingw32.test-dependencies] # 适用于对应 target 的测试阶段依赖配置项,非必须[target.cjvm.bin-dependencies] # 仓颉二进制库依赖,适用于特定 target 的编译流程和指定该 target 作为交叉编译目标平台的编译流程,非必须path-option = ["./test/pro0", "./test/pro1"]
[target.cjvm.bin-dependencies.package-option]"pro0.xoo" = "./test/pro0/pro0.xoo.cjo""pro0.yoo" = "./test/pro0/pro0.yoo.cjo""pro1.zoo" = "./test/pro1/pro1.zoo.cjo"
三、条件编译
开发者可以通过预定义或自定义的条件完成条件编译;仓颉目前支持导入和声明的条件编译。
3.1 导入和声明的条件编译
仓颉支持使用内置编译标记 @When 来完成条件编译,编译条件使用 [] 括起来,[] 内支持输入一组或多组编译条件。@When 可以作用于导入节点和除 package 外的声明节点。
使用方法
以内置 os 编译条件为例,其使用方法如下:
@When[os == "Linux"]
class mc{}main(): Int64 {var a = mc()return 0
}
在上面代码中,开发者在 Linux 系统中可以正确编译执行;在 非 Linux 系统中,则会遇到找不到 mc 类定义的编译错误。
值得注意的是:
- 仓颉不支持编译条件嵌套,以下写法均不允许:
@When[os == "Windows"]
@When[os == "Linux"] // Error, illegal nested when conditional compilation
import std.ast.*
@When[os == "Windows"]
@When[os == "Linux"] // Error, illegal nested when conditional compilation
func A(){}
- @When[…] 作为内置编译标记,在导入前处理,由宏展开生成的代码中含有 @When[…] 会编译报错,如:
@M0 // macro which returns the input
@When[os == "Linux"] // Error, unexpected when conditional compilation directive
func A(){}
3.2 内置编译条件变量
仓颉提供了五个内置条件变量: os、 backend、 cjc_version、 debug 和 test。
3.2.1 os
os 表示目标平台的操作系统。os 支持 == 和 != 两种操作符。支持的操作系统有:Windows、Linux、macOS、HarmonyOS。
使用方式如下:
@When[os == "Linux"]
func foo() {print("Linux, ")
}
@When[os == "Windows"]
func foo() {print("Windows, ")
}
@When[os != "Windows"]
func fee() {println("NOT Windows")
}
@When[os != "Linux"]
func fee() {println("NOT Linux")
}
main() {foo()fee()
}
如果在 Windows 环境下编译执行,会得到 Windows, NOT Linux 的信息;如果是在 Linux 环境下,则会得到 Linux, NOT Windows 的信息。
3.2 .2 backend
backend 是仓颉内置的条件。仓颉是多后端语言,支持多种后端条件编译。backend 条件支持 == 和 != 两种操作符。
支持的后端有:cjnative、cjnative-x86、cjnative-x86_64、cjnative-arm、cjnative-aarch64、cjvm、cjvm-x86、cjvm-x86_64、cjvm-arm、cjvm-aarch64。
当用户使用的条件为 cjnative/cjvm 时,arch 信息将会按编译器执行时环境信息自动补全。
使用方式如下:
@When[backend == "cjnative"]
func foo() {print("cjnative backend, ")
}
@When[backend == "cjvm"]
func foo() {print("cjvm backend, ")
}
@When[backend != "cjnative"]
func fee() {println("NOT cjnative backend")
}
@When[backend != "cjvm"]
func fee() {println("NOT cjvm backend")
}
main() {foo()fee()
}
用 cjnative 后端的发布包编译执行,会得到 cjnative backend, NOT cjvm backend 的信息;用 cjvm 后端的发布包编译执行,则会得到 cjvm backend, NOT cjnative backend 的信息。
3.2.3 cjc_version
cjc_version 是仓颉内置的条件,开发者可以根据当前仓颉编译器的版本选择要编译的代码。cjc_version 条件支持 ==、!=、>、<、>=、<= 六种操作符,格式为 xx.xx.xx 支持每个 xx 支持 1-2 位数字,计算规则为补位 (补齐 2 位) 比较,例如:0.18.8 < 0.18.11, 0.18.8 == 0.18.08。
使用方式如下:
@When[cjc_version == "0.18.6"]
func foo() {println("cjc_version equals 0.18.6")
}
@When[cjc_version != "0.18.6"]
func foo() {println("cjc_version is NOT equal to 0.18.6")
}
@When[cjc_version > "0.18.6"]
func fnn() {println("cjc_version is greater than 0.18.6")
}
@When[cjc_version <= "0.18.6"]
func fnn() {println("cjc_version is less than or equal to 0.18.6")
}
@When[cjc_version < "0.18.6"]
func fee() {println("cjc_version is less than 0.18.6")
}
@When[cjc_version >= "0.18.6"]
func fee() {println("cjc_version is greater than or equal to 0.18.6")
}
main() {foo()fnn()fee()
}
根据 cjc 的版本,上面代码的执行输出结果会有不同。
3.2.4 debug
debug 表示当前是否启用了调试模式即开启 -g 编译选项, 可以用于在编译代码时进行调试和发布版本之间的切换。debug 条件仅支持逻辑非运算符(!)。
使用方式如下:
@When[debug]
func foo() {println("debug")
}
@When[!debug]
func foo() {println("NOT debug")
}
main() {foo()
}
启用 -g 编译执行会得到 cjc debug 的信息,如果没有启用 -g 编译执行会得到 NOT debug 的信息。
3.2.5 test
test 表示当前是否启用了单元测试选项 --test。test 条件仅支持逻辑非运算符(!)。可以用于区分测试代码与普通代码。
使用方式如下:
@When[test]
@Test
class Tests {@TestCasepublic func case1(): Unit {@Expect("run", foo())}
}func foo() {"run"
}@When[!test]
main () {println(foo())
}
使用 --test 编译执行得到的测试结果,不使用 --test 也可正常完成编译运行得到 run 的信息。
3.3 自定义编译条件变量
仓颉允许开发者自定义编译条件变量和取值,自定义的条件变量必须是一个合法的标识符且不允许和内置条件变量同名,其值是一个字符串字面量。自定义条件支持 == 和 != 两种运算符。和内置条件变量不同点在于自定义的条件需要开发者在编译时通过 --cfg 编译选项或者在配置文件 cfg.toml 中定义。
3.3.1 配置自定义条件变量
配置自定义条件变量的方式有两种:在编译选项中直接配置键值对或在配置文件配置键值对。
用户可以使用 --cfg 以键值对的形式向编译器传递自定义编译条件变量或者指定配置文件 cfg.toml 的搜索路径。
-
选项值需要使用双引号括起来
-
若选项值中包含 = 则会按照键值对的形式直接进行配置(若路径中包含 = 则需要通过 \ 转义),多个键值对可以使用逗号 , 分隔。如:
$ cjc --cfg "feature = lion, platform = dsp" source.cj
- 允许多次使用 --cfg 编译选项配置进行配置, 如:
$ cjc --cfg "feature = lion" --cfg "platform = dsp" source.cj
- 不允许多次定义同一个条件变量, 如:
$ cjc --cfg "feature = lion" --cfg "feature = meta" source.cj
$ cjc --cfg "feature = lion, feature = meta" source.cj
上述两条编译指令都会报错。
- 若选项值中不包含 = 或 存在通过 \ 转义的 = 则将选项值作为配置文件 cfg.toml 的搜索路径传递给编译器,如:
$ cjc --cfg "./cfg" source.cj
若 ./cfg 目录下存在 cfg.toml 则在编译时,编译器会将 ./cfg/cfg.toml 中配置的自定义编译条件传递给编译器。cfg.toml 文件中应采用键值对的方式配置自定义条件变量,每个键值对独占一行, 健名是一个合法的标识符, 键值是一个双引号括起来的字符串。如:
feature = "lion"
platform = "dsp"
-
多次使用 --cfg 配置 cfg.toml 文件的搜索路径时,按照传入的顺序依次搜索cfg.toml 文件,若在所有传入的搜索路径下都没有找到 cfg.toml 文件,则在默认路径下搜索配置文件 cfg.toml。
-
多次使用 --cfg 编译选项进行配置时,若某次以键值对的形式直接进行配置,则会忽略配置文件 cfg.toml 中的配置。
-
若没有使用 --cfg 编译选项,编译器会在默认路径(通过–package 或 -p 指定的 package 目录或 cjc 执行目录)下搜索配置文件 cfg.toml。
3.4 多条件编译
仓颉条件编译允许开发者自由组合多个条件编译选项。支持逻辑运算符组合多个条件,支持括号运算符明确优先级。
使用方式如下:
//source.cj
@When[(test || feature == "lion") && !debug]
func fee() {println("feature lion")
}
main() {fee()
}
使用如下编译命令编译运行上段代码,
$ cjc --cfg="feature=lion" source.cj -o runner.out
会得到输出结果如下:
platform lion
相关文章:
仓颉语言的编译和构建
一、cjc 使用 cjc是仓颉编程语言的编译命令,其提供了丰富的功能及对应的编译选项,本章将对基本使用方法进行介绍。 cjc-frontend (仓颉前端编译器)会随 cjc 一起通过 Cangjie SDK 提供,cjc-frontend 能够将仓颉源码编…...

网络基础-协议
一、ARP 通过IP得到Mac 首先会查看缓存的arp表中是否有相应的IP和Mac对应关系,如果有直接进行包封装。如果没有则进行广播当对应的地址就收到广播包后会根据arp中的源地址进行单播返回相应的IP和Mac对应关系。 arp -a 查看现有的arp缓存 二、RARP反向地址解析 通过…...

电子设备抗震等级与电子设备震动实验
若该文为原创文章,转载请注明原文出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/139923445 红胖子(红模仿)的博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软…...

你还在手动操作仓库?这款 CLI 工具让你效率飙升300%!
前言 作为一名开发者,我经常会在 GitHub 和 Gitee 上 fork 各种项目。时间一长,这些仓库就会堆积如山,变成了“垃圾仓库”。每次打开代码托管平台,看到那些不再需要的仓库,我的强迫症就会发作。手动一个一个删除这些仓…...
未来已来!GPT-5震撼登场,工作与生活面临新变革!
随着科技界领袖对AI系统发展之快的惊叹,新一代大语言模型GPT-5即将登场,引发了我们对工作和日常生活的新一轮思考。微软CTO Kevin Scott和阿里巴巴董事长蔡崇信等人的言论为我们描绘了一幅生动的未来图景,即AI将在我们的生活中扮演越来越重要…...

洗地机选购指南,什么品牌最值得购买?2024四大口碑品牌推荐
随着炎炎夏日的到来,家里的地板清洁会成为人们“沉重”的负担,而拥有一台能够高效又轻松完成地板深度清洁的洗地机是一件非常幸福的事儿。但是,面对市场上琳琅满目的洗地机品牌和型号,如何找到一款综合性能都不错的洗地机成为了许…...
住宅IP与普通IP的区别
在互联网连接中,IP地址是识别每个网络节点的关键。在众多类型的IP地址中,住宅IP和普通IP是两种常见的分类。本文将深入探讨住宅IP与普通IP之间的主要区别。 一、定义与来源 住宅IP指的是由互联网服务提供商(ISP)直接分配给家庭或…...

【Java】线程池技术(三)ThreadPoolExecutor 状态与运行源码解析
ThreadPoolExecutor 状态 ThreadPoolExecutor 继承了 AbstractExecutorService,并实现了 ExecutorService 接口,用于管理线程。内部使用了原子整型 AtomicInteger ctl 来表示线程池状态和 Worker 数量。前 3 位表示线程池状态,后 29 位表示 …...

vscode使用内置插件断点调试vue2项目
1、首先项目中要开启source-map 在vue.config.js 文件中 module.exports {configureWebpack: {devtool: process.env.NODE_ENV ! "production" ? "source-map" : ,} }2、项目根目录新建.vscode/launch.js文件 {"configurations": [{"ty…...
centos7 低版本docker 升级为高版本
删除 docker yum -y remove docker*安装 yum 管理工具 yum install -y yum-utils添加国内镜像 manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo查看可用软件版本yum list docker-ce --showduplicates|sort -r安装 yum -y install docke…...

了解SD-WAN与传统WAN的区别
近年来,许多企业选择了SD-WAN作为他们的网络解决方案。云基础架构的SD-WAN不仅具备成本效益,而且提供更安全、更可靠的WAN连接,有助于实现持续盈利。客户能够更好地控制他们的网络,个性化定制且无需额外成本。 那么,为…...

技术干货 | AI驱动工程仿真和设计创新
在当今快速发展的技术领域,人工智能(AI)、机器学习和深度学习等技术已经成为推动工程仿真和设计创新的关键力量。Altair技术经理张晨在Altair “AI FOR ENGINEERS”线下研讨会上发表了相关精彩演讲,本文摘自演讲内容,与…...
深度分析SQL与NoSQL数据库:优缺点、使用场景及选型指南
在大数据和云计算时代,数据库技术的发展日新月异。SQL(关系型数据库)和NoSQL(非关系型数据库)作为两大主流数据库技术,各有其独特的优势和使用场景。本文将深入分析SQL和NoSQL的定义、优缺点、使用场景&…...

Linux基础 - shell基础
目录 零. 简介 一、常见的 Shell 类型 二、Shell 命令格式 三、基本命令 四、通配符 五、重定向 六、管道 七、变量 八、条件判断和流程控制 零. 简介 Shell 是一种命令解释器,在 Ubuntu 系统中,它负责接收用户在命令行中输入的命令,…...

一文搞懂Linux命令行下载OneDrive分享文件
一文搞懂Linux命令行下载OneDrive分享文件 什么问题? 因为OneDrive有些坑,无法从分享界面获取真实下载链接,比如下面这个链接: https://connecthkuhk-my.sharepoint.com/:f:/g/personal/jhyang13_connect_hku_hk/EsEgHtGOWbJIm…...

SpringBoot 实现RequestBodyAdvice封装统一接受类功能
一、相关往期文章 SpringBootVue实现AOP系统日志功能_aop的vue完整项目 Spring AOP (面向切面编程)原理与代理模式—实例演示_面向切面aop原理详解 二、需求分析 按照一般情况,统一接受类可以像以下的方式进行处理: 如果不想使用 Request…...

贪吃蛇——c语言版
文章目录 演示效果实现的基本功能技术要点源代码实现功能GameStart打印欢迎界面和功能介绍绘制地图创建蛇创建食物 GameRun打印提示信息蛇每走一步 GameEnd蛇死亡后继续游戏 演示效果 贪吃蛇1.0演示视频 将终端应用程序改为控制台主机 实现的基本功能 贪吃蛇地图绘制蛇吃食物的…...

ctr/cvr预估之WideDeep模型
ctr/cvr预估之Wide&Deep模型 在探索点击率(CTR)和转化率(CVR)预估的领域中,我们始终追求的是一种既能捕获数据中的线性关系,又能发现复杂模式的模型。因子分解机(Factorization Machines, …...

快速生成基于vue-element的后台管理框架,实现短时间二次开发
你是否遇到过当你想要独立开发一个项目时对反复造轮子的烦扰? 这种流水线的操作实在让人受不了 而vue-element-template很好的帮你解决了这个烦恼 只需克隆下来,改改图标,模块名,甚至样式,就会变成一个全新的自己的项目…...

PCIe 7.0 要来了,一文看懂PCIe发展和技术
PCIe(Peripheral Component Interconnect Express),即外围组件高速串行扩展总线标准,自其诞生以来,已成为计算机硬件中不可或缺的一部分。它以其高速串行通信能力和不断演进的技术规范,满足了日益增长的数据…...

网络六边形受到攻击
大家读完觉得有帮助记得关注和点赞!!! 抽象 现代智能交通系统 (ITS) 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 (…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...

idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...

Spark 之 入门讲解详细版(1)
1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algorithms, Machines, and People Lab)开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目,8个月后成为Apache顶级项目,速度之快足见过人之处&…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...

CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...
Swagger和OpenApi的前世今生
Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...

AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...
IP如何挑?2025年海外专线IP如何购买?
你花了时间和预算买了IP,结果IP质量不佳,项目效率低下不说,还可能带来莫名的网络问题,是不是太闹心了?尤其是在面对海外专线IP时,到底怎么才能买到适合自己的呢?所以,挑IP绝对是个技…...

Kafka入门-生产者
生产者 生产者发送流程: 延迟时间为0ms时,也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于:异步发送不需要等待结果,同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...