gmock
gmock 用来模拟接口,使得测试脱离函数间的依赖
1 | //demo.h |
由于gmock是用来测试c++的,但是在实际测试中有对c函数测试的需求,我们使用一下库来对c函数打桩
https://github.com/apriorit/gmock-global
1 | //1.引入头文件 |
gmock 用来模拟接口,使得测试脱离函数间的依赖
1 | //demo.h |
https://github.com/apriorit/gmock-global
1 | //1.引入头文件 |
1 | MemPoolCollectionMemoryBlock m_introspectionMemPoolBlock; |
1 | 1 //client require |
client:发送客户端信息 //获取iox-roudi 共享内存信息
server:返回给客户端 关于共享内存的信息
// 1 个memory provide 分配内存过程
// 1 个memeory provider 消费内存过程
一个memroy manager 包含多个 memory provider;
一个memroy provider 包含多个 memory block;
实际分配内存的对象是 memory provider,然后分配segmentID,再然后拆分到 n个 memroy block中去;
客户端在使用内存的时候 就是按照segmentID来先获取 segment内存,然后,
ros2 pkg create
1 | //生成的pkg结构 |
$ros2 topic pub
<msg/msg_type> //msg 是需要yaml格式
egs. ros2 topic pub /chatter std_msgs/String “data: Hello!”
ros2 topic echo
egs. ros2 topic echo /chatter
1 | $ ros2 service list //查看服务 |
1 | $ ros2 param list //查看参数列表 |
1 | $ros2 action list //查看动作 |
1 | ros2 lifecycle nodes // 列出所有 LC 节点 |
$ ros2 pkg create test_msgs //包名一定是xxx_xxx格式,不要是XX2XX格式
创建目录
$ mkdir -p test_msgs/msg //创建msg目录
编写*.msg文件
$ vim msg/TestMsgs.msg //首字母大写 后边生成的hpp文件名根这个名字有关。 [TestMsgs <–> test_msgs.hpp] [Testmsgs <–> testmsgs.hpp
// TestMsgs.msg内容:
int32 Num
修改CMmakeList.txt 规则
1 | find_package(rosidl_default_generators REQUIRED) |
修改package.xml规则
1 | <build_depend>rosidl_default_generators</build_depend> |
构建
$ colcon build
设置环境变量
$ source ./install/setup.bash
// 之后就可以查看时候设置到ros2的环境中了
ros2 msg show [tab自动补全] //如果发现有test_msgs/TestMsgs 就证明成功,之后在其他节点引用将用到
测试自定义消息
1 | //消息使用格式: |
XXXmsgs.msg
1 | std_msgs/Header header |
XXXmsgsarray.msg
1 | std_msgs/Header header |
1 | //ros2 没有使用init初始化 |
1 |
|
-DXXX_AP_PATH=/share/XXX-CORE/core
构建
data_stream_framework 依赖ros2_XXX_msg/XXX_msgs, ros2_XXX_msg/XXX_msgs_array, XXX_api,
构建控制脚本为.cmake/ros2build.cmake。//构建过程中需要的依赖可以再该脚本中增加/修改。典型依赖是protobuf_util.so
再依赖工程下有build_alone.sh构建脚本,构建需要修改-DXXX_AP_PATH=/share/XXX-CORE/core变量
其中ros2_XXX_msg/XXX_msgs_array 依赖 ros2_XXX_msg/XXX_msgs工程,用户需依次构建[构建指令:colcon build],每个工程构建完并source/install/setup.bash将自定义消息加载到ros2系统,
$ros2 msg list //出现XXX_msg/xx/xx 和 XXX_msg_array/xx/xx 即表示成功
构建XXX_api
使用工程下build_alone.sh //注意修改-DXXX_AP_PATH=/share/XXX-CORE/core变量
构建data_stream_framework
使用工程下build_alone.sh //注意修改-DXXX_AP_PATH=/share/XXX-CORE/core变量
构建环境:
docker image:172.16.8.120/XXX/XXX0.8:v1.0.9
构建平台:
x64-ros2
测试
测试demo
demo目录下.cc文件
测试yaml
data_stream_framwork/Debug/conf
server使用test_copy_server client使用test_copy_client
pub/sub使用test
copy XXX_api/pacakge.xml YOUP_PORJECT_DIR
修改
copy XXX_api/build_alone.sh YOU_PROJECT_DIR
修改build_alone.sh中-DXXX_AP_PATH=/YOU_PROJECT_DIR/core
bash build_alone.sh //即可构建功能层软件,构建成功会在Debug下生成目标文件。
找不到protobuf_util头文件
在ros2build.cmake中有指定protobuf_util的头文件和lib,用户需要指定该路径,两种方法1. 你可以指定已经构建好的protobuf-util[x64]; 2. 你可以使用common/develop_jay直接在172.16.8.120/XXX/XXX0.8:v1.0.9下构建后指定该路径
找不到jsoncpp头文件
apt-get install libjsoncpp-dev
#protobuf_utils
list(APPEND protobuf_utils_INCLUDE_DIR
/share/XXX-COMMON/common/protobuf_utils/protobuf_utils_cpp/include
/share/XXX-COMMON/common/protobuf_utils/protobuf_utils_cpp/pb_src
)
list(APPEND protobuf_utils_LIBRARIES
/share/XXX-COMMON/common/protobuf_utils/protobuf_utils_cpp/Debug/lib/libprotobuf_utils.so
)
1 | //Logger |
// 将node添加到executor中(publisher subcriptiion service client)
executor.add_node(){
weak_nodes_; //添加node
guard_conditions_; //添加node 的 guard_conditions
然后在spin循环中先查找any_exec [subscription_handles_,service_handles_,client_handles_,timer_handles_,waitable_handles_],如果exec_存在,则执行对应的回调,如果不存在,collect_entities,设置wait_set_然后使用rcl_wait监控wait_set_, 如果有fd[rmw_subscriptions,rmw_guard_conditions,rmw_services,rmw_clients,rmw_events]被触发,然后根据wait_set_清理无关实体[比如说waitable_handles_中没有数据的(IPM)[ipm中is_ready(wait_set)中wait_set无用,直接检测buffer_->has_data()]],然后继续获取any_exec,获取到exec后执行exec对应的回调。
}
发布数据的时候,发布者会判断intra_process_is_enabled_, 如果使用ipm则继续判断是否多个订阅者订阅一个发布者,如果有多个订阅者,则将msg转为shared_ptr 然后发布
vscode编辑器是可高度自定义的,我们使用vscode插件几乎可以对vscode编辑器进行所有形式的自定义,只要你想做,基本上没有不能实现的。
vscode
插件开发的官方文档为:
https://code.visualstudio.com/api
中文文档:
https://liiked.github.io/VS-Code-Extension-Doc-ZH/#/
vscode插件可以实现
可以通过官方脚手架来生成vscode插件模板工程。
首先安装脚手架
npm install -g yo generator-code
然后进入工作目录,使用脚手架
yo code
通过上图可以看到,生成一个vscode插件工程时可以选择是创建一个已有的语言的插件还是一个全新的语言的插件,并且可以选择插件开发语言。
本文以创建一个新语言的插件为例。
vscode插件开发可以使用TypeScript开发,也可以使用JS,两种方式能实现的功能是一样的。
下面是自动生成的插件工程文件
其中,最核心的两个文件是package.json
和extension.js
,package.json
是整个插件工程的配置文件,extension.js
则是工程的入口文件。下面将对这两个文件进行详细的介绍。
注:<u>以上配置项在刚创建完的工程文件中不全存在,本文为了更全面介绍配置项,所以后面人为添加了一些配置项。</u>
activationEvents
activationEvents
配置项配置插件的激活数组,即在什么情况下插件会被激活,目前支持以下8种配置:
contributes
contributes
配置项是整个插件的贡献点,也就是说这个插件有哪些功能。contributes
字段可以设置的key
也基本显示了vscode
插件可以做什么。
vscode
的settings.json
中设置,然后在插件工程中可以读取用户设置的这个值,进行相应的逻辑。cmd
+shift
+p
进行输入来实现的。extension.js
是插件工程的入口文件,当插件被激活,即触发package.json
中的activationEvents
配置项时,extension.js
文件开始执行。
在extension.js
中对需要的功能进行注册,主要使用vscode.commands.register...
相关的api
,来为package.json
中的contributes
配置项中的事件绑定方法或者监听器。
vscode.commands.register...
相关的api
主要有:
vscode.languages.registerCompletionItemProvider()
vscode.commands.registerCommand()
vscode.languages.registerCodeActionsProvider()
vscode.languages.registerCodeLensProvider()
vscode.languages.registerHoverProvider()
下面我们运行一下这个插件工程,按F5
运行插件,这个时候会自动打开一个新的vscode界面,我们按cmd
+shift
+p
,在命令框输入plugin-demo.helloWorld
命令,既可以看到在vscode的界面的右下角弹出一个弹框,弹框显示Hello World from plugin-demo2
,这正是我们在extension.js
中为plugin-demo.helloWorld
中为plugin-demo.helloWorld
命令绑定的事件。
下面我们梳理一下这个弹框出现的整个流程:
activationEvents
:在package.json
的activationEvents
配置项中设置插件激活时机,这里设置的是onCommand:plugin-demo.helloWorld
,即输入命令onCommand:plugin-demo.helloWorld
时激活。contributes
:package.json
中的contributes
配置项表示这个插件增加了哪些功能,这里设置了commands
,增加的命令,在这一项中声明了一个命令plugin-demo.helloWorld
。Register
:在extension.js
文件中的activate(context)
方法中,使用vscode.commands.registerCommand()
这一API为命令plugin-demo.helloWorld
绑定事件,绑定的事件为vscode.window.showInformationMessage('Hello World from plugin-demo2!')
,即弹出弹框。plugin-demo.helloWorld
,此时插件被激活,进入extension.js
中执行activate()
方法,由于已经在contributes
配置项中声明了命令plugin-demo.helloWorld
,所以在activate()
方法中为该命令绑定一个事件,由于在命令框中输入了这个命令,所以命令绑定的事件立即被触发执行,所以在vscode的右下角弹出了弹出框。VSCode的插件都运行在一个独立的进程里, 被称为 Extension Host, 它加载并运行插件, 让插件感觉自己好像在主进程里一样, 同时又严格限制插件的响应时间, 避免插件影响主界面进程。
关于命令我们之前在分析插件的生命周期的时候就已经讲过,首先在package.json
的contributes
配置项中声明命令:
"commands": [
{
"command": "plugin-demo.helloWorld",
"title": "Hello World"
}
]
然后在extension.js
的activate()
中去注册该命令,绑定事件:
let disposable = vscode.commands.registerCommand('plugin-demo.helloWorld', function () {
vscode<span class="token punctuation">.</span>window<span class="token punctuation">.</span><span class="token function">showInformationMessage</span><span class="token punctuation">(</span><span class="token string">'Hello World from plugin-demo2!'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
所有注册类的API执行后都要将将结果放到context.subscriptions
中去:
context.subscriptions.push(disposable);
这样当插件被激活后,输入命令,命令绑定的事件就会被执行。
菜单也是通过和命令关联起来来实现其功能的
"menus": {
"editor/title": [{
"when": "editorFocus",
"command": "plugin-demo.helloWorld",
"alt": "",
"group": "navigation"
}]
}
以上是一个菜单项的完整配置.
editor/title:
定义这个菜单出现在哪里,这里是定义出现在编辑标题菜单栏。when:
菜单在什么时候出现,这里是有光标的时候出现command:
点击这个菜单要执行的命令alt:
按住alt
再选择菜单时应该执行的命令group:
定义菜单分组菜单项对应的命令为plugin-demo.helloWorld
,我们再在contributions
的commands
中找到这个命令:
"commands": [
{
"command": "plugin-demo.helloWorld",
"title": "菜单栏测试"
}
]
这里命令的title
将作为菜单项的名字显示,当然我们也可以设置菜单项的icon
。
我们之前已经在extension.js
中注册过这个命令了,因此不用再注册。
按F5
运行插件,保证插件被激活后,使光标出现,在编辑器的右上角我们可以看到出现一个新增的菜单:
当我们点击这个菜单时,其会执行关联的command
在extension.js
中绑定的事件。
快捷键的设置比较简单,其执行功能同样依赖于其关联的命令
command
。
"keybindings": [
{
"command": "plugin-demo.helloWorld",
"key": "ctrl+{",
"mac": "cmd+{",
"when": "editorTextFocus"
}
]
command:
快捷键关联的命令key:
Windows平台对应的快捷键mac:
mac平台对应的快捷键when:
什么时候快捷键有效当插件被激活后,并且满足快捷键有效的时间,按快捷键就可以找到extension.js
中与快捷键关联的command
所不绑定的事件并执行。
悬停提示的思路是在
extension.js
中注册一个悬停事件,然后根据提供的docuemnt
、position
已经文件名,文件路径等信息作出相应的逻辑。
主要API:
function registerHoverProvider(selector: DocumentSelector, provider: HoverProvider): Disposable;
这一API返回一个HoverProvider
对象,这一对象需要加入到context.subscription
中。
provideHover(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<Hover>;
这一API返回一个PrioviderResult
对象,当我们把光标放在某个位置时显示的内容,就是这个对象封装的。
下面我们写一个简单的demo,我们对package.json文件中的main
这个单词进行悬停提示:
function activate(context) {
<span class="token keyword">const</span> hover <span class="token operator">=</span> vscode<span class="token punctuation">.</span>languages<span class="token punctuation">.</span><span class="token function">registerHoverProvider</span><span class="token punctuation">(</span><span class="token string">'json'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
<span class="token function">provideHover</span><span class="token punctuation">(</span><span class="token parameter">document<span class="token punctuation">,</span> position<span class="token punctuation">,</span> token</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> fileName <span class="token operator">=</span> document<span class="token punctuation">.</span>fileName<span class="token punctuation">;</span>
<span class="token keyword">const</span> word <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getText</span><span class="token punctuation">(</span>document<span class="token punctuation">.</span><span class="token function">getWordRangeAtPosition</span><span class="token punctuation">(</span>position<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token regex">/\/package\.json$/</span><span class="token punctuation">.</span><span class="token function">test</span><span class="token punctuation">(</span>fileName<span class="token punctuation">)</span> <span class="token operator">&&</span> <span class="token regex">/\bmain\b/</span><span class="token punctuation">.</span><span class="token function">test</span><span class="token punctuation">(</span>word<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">vscode<span class="token punctuation">.</span>Hover</span><span class="token punctuation">(</span><span class="token string">"测试悬停提示"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> <span class="token keyword">undefined</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
context<span class="token punctuation">.</span>subscriptions<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>hover<span class="token punctuation">)</span><span class="token punctuation">;</span>
}
运行插件,保证插件被激活的状态下,将光标放在package.json
文件的main
单词上:
代码片段也叫snippets
,就是输入一个前缀,会得到一个或多个提示,然后回车带出很多代码。
想要在vscode插件中实现snippets
的功能,首先要在package.json
的contributes
配置项中配置代码提示文件的文件路径:
"snippets": [
{
"language": "lizard",
"path": "./snippets.json"
}
]
这里language
设置了snippets
作用于何种语言,path
设置了服务于snippets
的文件的路径。
再看一下在snippets.json
文件中:
{
"View组件": {
"prefix": "View",
"body": [
"<View>",
"${1}",
"</VIew>"
],
"description": "View组件"
}
}
"View组件":
snippet
的名称"prefix":
前缀,即输入什么可以出现snippets
的提示"body":
按回车后出现的一大段代码,是一个数组,数组里面是字符串,每个字符串代表一行代码,${1}
表示第一个光标的位置,同样,${2}
表示第二个光标的位置"description":
对于这个snippet
的描述,当我们选中这个snipets
提示时,描述会出现在后面。View
:当我们为一个已有的语言创建插件时,package.json
中默认不会有代码高亮相关的配置,当我们为一个新语言开发插件时,插件工程的package.json
文件中默认有语法高亮相关的配资。
这一配置仍然在contributes
中:
上图中grammars
的path
项设置了描述新语言的语法的文件路径。
然后我们看到这个语法文件lizard.tmLanguage.json
,vscode会根据这个语法文件自动实现语法高亮的功能。我们找到该文件中的一段:
先不管这里每一项表示什么含义,首先运行代码,输入for
、return
、if
、while
等关键字中的其中一个,会发现关键字出现了高亮,这便实现了简单的高亮功能。
上面的的代码中。
lizard.tmLanguage.json
中的语法是TextMate
语法,关于TextMate
的介绍:
https://macromates.com/manual/en/language_grammars
https://www.apeth.com/nonblog/stories/textmatebundle.html
上面的代码中:
match
是一个正则表达式,但是使用的是ruby regular expression
,进行匹配,name
是被匹配的表达式的scope selector
,关于scope selector
的介绍见链接:
https://macromates.com/manual/en/scope_selectors
vscode
根据这个scope selector
进行上色。
下面介绍一下本文为新语言写语法文件的案例:
为属性结构写语法,属性结构模板为:style = {width:8, height:9}
代码提示是我们使用vscode开发的时候不可获取的一个功能,即当我们输入代码的一部分的时候,这时候
vscode
显示一个提示列表,我们可以选择一个提示项,然后回车,这样代码的剩余部分就自动补全了。
代码提示相关的主要的API是:
registerCompletionItemProvider(selector: DocumentSelector, provider: CompletionItemProvider, ...triggerCharacters: string[]): Disposable;
CompletionItemProvider
类型的对象,在创建这个对象内部,我们需要根据document
、position
等信息进行逻辑处理,返回一个CompletionItem
的数组,每一个CompletionItem
就代表一个提示项。下面列出一些与代码提示相关的其他的一些API,这些API大多与文本、单词的处理相关,因为我们进行代码提示时需要知道当前光标所在单词的上下文,这样才能很好的给出智能提示,而要得到当前光标的上下文,就需要对光标附近乃至整个文件进行文本分析。
与TextDocument相关
TextDocument
的对象实际是当前文件对象,所以我们可以根据该对象得到当前文件与文本相关的所有信息。lineAt(line: number): TextLine;
根据行数返回一个行的对象lineAt(position: Position): TextLine;
根据一个位置返回这一行的行对象getText(range?: Range): string;
根据范围,返回这个范围的文本getWordRangeAtPosition(position: Position, regex?: RegExp): Range | undefined;
根据position
返回这个位置所在的单词。text.charAt()
返回字符串在某个位置的字符下面写一个代码提示的简单的demo:
function activate(context) {
<span class="token keyword">const</span> provider <span class="token operator">=</span> vscode<span class="token punctuation">.</span>languages<span class="token punctuation">.</span><span class="token function">registerCompletionItemProvider</span><span class="token punctuation">(</span><span class="token string">'plaintext'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
<span class="token function">provideCompletionItems</span><span class="token punctuation">(</span><span class="token parameter">document<span class="token punctuation">,</span> position</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> completionItem1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">vscode<span class="token punctuation">.</span>CompletionItem</span><span class="token punctuation">(</span><span class="token string">'Hello World!'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> completionItem2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">vscode<span class="token punctuation">.</span>CompletionItem</span><span class="token punctuation">(</span><span class="token string">'World Peace!'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token punctuation">[</span>completionItem1<span class="token punctuation">,</span> completionItem2<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
context<span class="token punctuation">.</span>subscriptions<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>provider<span class="token punctuation">)</span><span class="token punctuation">;</span>
}
我们在这里创建了两个CompletionItem
对象,这样,当我们输入Hello World!
或World Peace
的一部分时,插件会自动显示提示项,回车即可进行补全。
上面的demo中我们实现了一个简单的代码提示的demo,但是这种异常简单的代码提示机会是没有任何价值的,为一个语言实现代码提示必须要结合当前光标位置的上下文来实现,根据上下文来分析当前光标所在单词属于类名、变量名、函数名等等,再提供相对应的提示。
为一个语言实现代码提示的主要方式有两种,第一种是使用抽象语法树,分析语法节点,分析当前位置属于哪一节点,第二种方式是直接使用正则匹配等方式来粗略判断当前位置的上下文,目前成熟的开发语言的代码提示均使用第一种方式,但是第一种方法同时也要处理语法错误时的分析,因此对个人而言难度相对比较大,本文采用第二种方式对新语言提供代码提示。
下面是新语言的一个模板:
其中主要包括两种结构:组件
、全局变量
,这两种结构的形式都是非常固定的,组件
的一般结构如下:
<ComponentName propertyName = {key: value, key: value} propertyName2 = {}···/>
或者
<ComponentName propertyName = {key: value, key: value} propertyName2 = {}···></ComponentName>
而全局变量
的结构如果我们把globalVar =
这部分结构忽略,只看{}
里面的内容,很容易发现其结构与json
无异,这提醒我全局变量的结构可以把它当成一个json
对象进行解析,当然在此之前还需要做许多额外工作,保证解析的正确进行。
简而言之,对这个新语言的代码提示主要集中在5个部分:组件名称、组件的属性名称、组件的属性名称里的key、组件的属性名称里的value(有一些value是枚举值,因此需要进行提示)、全局变量的key。
我们前面一直提到实现代码提示要结合当前光标的上下文进行分析,其实质就是根据光标位置的上下文分析当前光标的位置属于哪一类,是属于组件名还是属性名等等。
因此问题就转化为如何根据当前光标的上下文得到光标处属于哪一类。本文处理次问题的逻辑如下:
<
,在此过程中如果遇见一个组件结尾标志的/>
或者</NAME>
结构,则停止寻找,说明当前光标不在组件里,可以判断当前光标是在全局变量处。<
位置之间是否有除空格外的其他字符,若没有,则当前位置是组件名,若有,则当前位置不是组件名,需要继续区分,通过是否在括号内判断当前位置是不是属性名。{}
内是key
和value
,使用:
来进行区分。这样就对五种情况进行了区分,然后可以针对每种情况给出有正对性的提示。
下面是最后的实现效果:
在
html
中当我们输入<label
再输入一个>
时,这时候vscode会自动帮我们添加上</label>
,不需要我们敲回车就能完成,这种自动补全的方式能提高开发效率,下面就谈一下其实现。
下面就以<label></label>
的实现为例:
当敲入>
时,首先要计算得到组件名componnetName
,然后:
实现效果:
有时候插件可能想要读取用户自己自定义的文件,来实现某个功能,这个时候就需要把用户的文件路径传递给插件。
解决这个问题的办法可以是给vscode
的settings
增加有一个设置项,用户填写对应的值,vscode
插件就可以读取这个值,进而读取相关的文件。
给settings
增加设置项可以通过package.json
文件的contributes
进行配置:
然后我们就可以在settings
中设置customPath
这个设置项的值:
最后在插件工程中读取customPath
的值:
const path = vscode.workspace.getConfiguration('lizard').get('customPath');
vsce(Visual Studio Code Extension)
npm install -g vsce
https://dev.azure.com/vscode
获取一个access token
,这个token用来创建一个publisher
publisher
vscr create-publisher (publisher name)
publisher
vscde login (publisher name)
vsce package
vsce publish
package.json
文件中修改插件的版本号。vsce publish
升级TextMate官方介绍
对理解TextMate极有帮助的文档
https://www.apeth.com/nonblog/stories/textmatebundle.html
知乎讲vscode原理的
https://zhuanlan.zhihu.com/p/99198980
vscode入门的博文教程
https://www.cnblogs.com/liuxianan/p/vscode-plugin-overview.html
typescript:
npm install –save-dev webpack webpack-dev-server typescript ts-loader
1 | // msg.proto |
在proto3中,repeated的标量域默认情况虾使用packed。
先来看一个非常简单的例子。假设你想定义一个“搜索请求”的消息格式,每一个请求含有一个查询字符串、你感兴趣的查询结果所在的页数,以及每一页多少条查询结果。可以采用如下的方式来定义消息类型的.proto文件了:
1 2 3 4 5 6 7 | syntax = "proto3" ; message SearchRequest { string query = 1 ; int32 page_number = 2 ; int32 result_per_page = 3 ; } |
在上面的例子中,所有字段都是标量类型:两个整型(page_number和result_per_page),一个string类型(query)。当然,你也可以为字段指定其他的合成类型,包括枚举(enumerations)或其他消息类型。
正如你所见,在消息定义中,每个字段都有唯一的一个数字标识符。这些标识符是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改变。注:[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。切记:要为将来有可能添加的、频繁出现的标识号预留一些标识号。
最小的标识号可以从1开始,最大到2^29 - 1, or 536,870,911。不可以使用其中的[19000-19999]( (从FieldDescriptor::kFirstReservedNumber 到 FieldDescriptor::kLastReservedNumber))的标识号, Protobuf协议实现中对这些进行了预留。如果非要在.proto文件中使用这些预留标识号,编译时就会报警。同样你也不能使用早期保留的标识号。
所指定的消息字段修饰符必须是如下之一:
repeated:在一个格式良好的消息中,这种字段可以重复任意多次(包括0次)。重复的值的顺序会被保留。
在proto3中,repeated的标量域默认情况虾使用packed。
你可以了解更多的pakced属性在Protocol Buffer 编码
在一个.proto文件中可以定义多个消息类型。在定义多个相关的消息的时候,这一点特别有用——例如,如果想定义与SearchResponse消息类型对应的回复消息格式的话,你可以将它添加到相同的.proto文件中,如:
1 2 3 4 5 6 7 8 9 | message SearchRequest { string query = 1 ; int32 page_number = 2 ; int32 result_per_page = 3 ; } message SearchResponse { ... } |
向.proto文件添加注释,可以使用C/C++/java风格的双斜杠(//) 语法格式,如:
1 2 3 4 5 | message SearchRequest { string query = 1 ; int32 page_number = 2 ; // Which page number do we want? int32 result_per_page = 3 ; // Number of results to return per page. } |
如果你通过删除或者注释所有域,以后的用户可以重用标识号当你重新更新类型的时候。如果你使用旧版本加载相同的.proto文件这会导致严重的问题,包括数据损坏、隐私错误等等。现在有一种确保不会发生这种情况的方法就是指定保留标识符(and/or names, which can also cause issues for JSON serialization不明白什么意思),protocol buffer的编译器会警告未来尝试使用这些域标识符的用户。
1 2 3 4 | message Foo { reserved 2 , 15 , 9 to 11 ; reserved "foo" , "bar" ; } |
注:不要在同一行reserved声明中同时声明域名字和标识号
当用protocol buffer编译器来运行.proto文件时,编译器将生成所选择语言的代码,这些代码可以操作在.proto文件中定义的消息类型,包括获取、设置字段值,将消息序列化到一个输出流中,以及从一个输入流中解析消息。
你可以从如下的文档链接中获取每种语言更多API(proto3版本的内容很快就公布)。API Reference
一个标量消息字段可以含有一个如下的类型——该表格展示了定义于.proto文件中的类型,以及与之对应的、在自动生成的访问类中定义的类型:
.proto Type | Notes | C++ Type | Java Type | Python Type[2] | Go Type | Ruby Type | C# Type | PHP Type |
---|---|---|---|---|---|---|---|---|
double | double | double | float | float64 | Float | double | float | |
float | float | float | float | float32 | Float | float | float | |
int32 | 使用变长编码,对于负值的效率很低,如果你的域有可能有负值,请使用sint64替代 | int32 | int | int | int32 | Fixnum 或者 Bignum(根据需要) | int | integer |
uint32 | 使用变长编码 | uint32 | int | int/long | uint32 | Fixnum 或者 Bignum(根据需要) | uint | integer |
uint64 | 使用变长编码 | uint64 | long | int/long | uint64 | Bignum | ulong | integer/string |
sint32 | 使用变长编码,这些编码在负值时比int32高效的多 | int32 | int | int | int32 | Fixnum 或者 Bignum(根据需要) | int | integer |
sint64 | 使用变长编码,有符号的整型值。编码时比通常的int64高效。 | int64 | long | int/long | int64 | Bignum | long | integer/string |
fixed32 | 总是4个字节,如果数值总是比总是比228大的话,这个类型会比uint32高效。 | uint32 | int | int | uint32 | Fixnum 或者 Bignum(根据需要) | uint | integer |
fixed64 | 总是8个字节,如果数值总是比总是比256大的话,这个类型会比uint64高效。 | uint64 | long | int/long | uint64 | Bignum | ulong | integer/string |
sfixed32 | 总是4个字节 | int32 | int | int | int32 | Fixnum 或者 Bignum(根据需要) | int | integer |
sfixed64 | 总是8个字节 | int64 | long | int/long | int64 | Bignum | long | integer/string |
bool | bool | boolean | bool | bool | TrueClass/FalseClass | bool | boolean | |
string | 一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。 | string | String | str/unicode | string | String (UTF-8) | string | string |
bytes | 可能包含任意顺序的字节数据。 | string | ByteString | str | []byte | String (ASCII-8BIT) | ByteString | string |
你可以在文章Protocol Buffer 编码中,找到更多“序列化消息时各种类型如何编码”的信息。
当一个消息被解析的时候,如果被编码的信息不包含一个特定的singular元素,被解析的对象锁对应的域被设置位一个默认值,对于不同类型指定如下:
对于消息类型(message),域没有被设置,确切的消息是根据语言确定的,详见generated code guide
对于可重复域的默认值是空(通常情况下是对应语言中空列表)。
注:对于标量消息域,一旦消息被解析,就无法判断域释放被设置为默认值(例如,例如boolean值是否被设置为false)还是根本没有被设置。你应该在定义你的消息类型时非常注意。例如,比如你不应该定义boolean的默认值false作为任何行为的触发方式。也应该注意如果一个标量消息域被设置为标志位,这个值不应该被序列化传输。
查看generated code guide选择你的语言的默认值的工作细节。
当需要定义一个消息类型的时候,可能想为一个字段指定某“预定义值序列”中的一个值。例如,假设要为每一个SearchRequest消息添加一个 corpus字段,而corpus的值可能是UNIVERSAL,WEB,IMAGES,LOCAL,NEWS,PRODUCTS或VIDEO中的一个。 其实可以很容易地实现这一点:通过向消息定义中添加一个枚举(enum)并且为每个可能的值定义一个常量就可以了。
在下面的例子中,在消息格式中添加了一个叫做Corpus的枚举类型——它含有所有可能的值 ——以及一个类型为Corpus的字段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | message SearchRequest { string query = 1 ; int32 page_number = 2 ; int32 result_per_page = 3 ; enum Corpus { UNIVERSAL = 0 ; WEB = 1 ; IMAGES = 2 ; LOCAL = 3 ; NEWS = 4 ; PRODUCTS = 5 ; VIDEO = 6 ; } Corpus corpus = 4 ; } |
如你所见,Corpus枚举的第一个常量映射为0:每个枚举类型必须将其第一个类型映射为0,这是因为:
这个零值必须为第一个元素,为了兼容proto2语义,枚举类的第一个值总是默认值。
你可以通过将不同的枚举常量指定位相同的值。如果这样做你需要将allow_alias设定位true,否则编译器会在别名的地方产生一个错误信息。
1 2 3 4 5 6 7 8 9 10 11 | enum EnumAllowingAlias { option allow_alias = true ; UNKNOWN = 0 ; STARTED = 1 ; RUNNING = 1 ; } enum EnumNotAllowingAlias { UNKNOWN = 0 ; STARTED = 1 ; // RUNNING = 1; // Uncommenting this line will cause a compile error inside Google and a warning message outside. } |
枚举常量必须在32位整型值的范围内。因为enum值是使用可变编码方式的,对负数不够高效,因此不推荐在enum中使用负数。如上例所示,可以在 一个消息定义的内部或外部定义枚举——这些枚举可以在.proto文件中的任何消息定义里重用。当然也可以在一个消息中声明一个枚举类型,而在另一个不同 的消息中使用它——采用MessageType.EnumType的语法格式。
当对一个使用了枚举的.proto文件运行protocol buffer编译器的时候,生成的代码中将有一个对应的enum(对Java或C++来说),或者一个特殊的EnumDescriptor类(对 Python来说),它被用来在运行时生成的类中创建一系列的整型值符号常量(symbolic constants)。
在反序列化的过程中,无法识别的枚举值会被保存在消息中,虽然这种表示方式需要依据所使用语言而定。在那些支持开放枚举类型超出指定范围之外的语言中(例如C++和Go),为识别的值会被表示成所支持的整型。在使用封闭枚举类型的语言中(Java),使用枚举中的一个类型来表示未识别的值,并且可以使用所支持整型来访问。在其他情况下,如果解析的消息被序列号,未识别的值将保持原样。
关于如何在你的应用程序的消息中使用枚举的更多信息,请查看所选择的语言generated code guide
你可以将其他消息类型用作字段类型。例如,假设在每一个SearchResponse消息中包含Result消息,此时可以在相同的.proto文件中定义一个Result消息类型,然后在SearchResponse消息中指定一个Result类型的字段,如:
1 2 3 4 5 6 7 8 9 | message SearchResponse { repeated Result results = 1 ; } message Result { string url = 1 ; string title = 2 ; repeated string snippets = 3 ; } |
你可以在其他消息类型中定义、使用消息类型,在下面的例子中,Result消息就定义在SearchResponse消息内,如:
1 2 3 4 5 6 7 8 | message SearchResponse { message Result { string url = 1 ; string title = 2 ; repeated string snippets = 3 ; } repeated Result results = 1 ; } |
如果你想在它的父消息类型的外部重用这个消息类型,你需要以Parent.Type的形式使用它,如:
1 2 3 | message SomeOtherMessage { SearchResponse.Result result = 1 ; } |
如果一个已有的消息格式已无法满足新的需求——如,要在消息中添加一个额外的字段——但是同时旧版本写的代码仍然可用。不用担心!更新消息而不破坏已有代码是非常简单的。在更新时只要记住以下的规则即可。
Any类型消息允许你在没有指定他们的.proto定义的情况下使用消息作为一个嵌套类型。一个Any类型包括一个可以被序列化bytes类型的任意消息,以及一个URL作为一个全局标识符和解析消息类型。为了使用Any类型,你需要导入import google/protobuf/any.proto
1 2 3 4 5 6 | import "google/protobuf/any.proto" ; message ErrorStatus { string message = 1 ; repeated google.protobuf.Any details = 2 ; } |
对于给定的消息类型的默认类型URL是type.googleapis.com/packagename.messagename
。
不同语言的实现会支持动态库以线程安全的方式去帮助封装或者解封装Any值。例如在java中,Any类型会有特殊的pack()
和unpack()
访问器,在C++中会有PackFrom()
和UnpackTo()
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // Storing an arbitrary message type in Any. NetworkErrorDetails details = ...; ErrorStatus status; status.add_details()->PackFrom(details); // Reading an arbitrary message from Any. ErrorStatus status = ...; for ( const Any& detail : status.details()) { if (detail.Is<NetworkErrorDetails>()) { NetworkErrorDetails network_error; detail.UnpackTo(&network_error); ... processing network_error ... } } |
目前,用于Any类型的动态库仍在开发之中
如果你已经很熟悉proto2语法,使用Any替换拓展
如果你的消息中有很多可选字段, 并且同时至多一个字段会被设置, 你可以加强这个行为,使用oneof特性节省内存.
Oneof字段就像可选字段, 除了它们会共享内存, 至多一个字段会被设置。 设置其中一个字段会清除其它字段。 你可以使用case()
或者WhichOneof()
方法检查哪个oneof字段被设置, 看你使用什么语言了.
为了在.proto定义Oneof字段, 你需要在名字前面加上oneof关键字, 比如下面例子的test_oneof:
1 2 3 4 5 6 | message SampleMessage { oneof test_oneof { string name = 4 ; SubMessage sub_message = 9 ; } } |
然后你可以增加oneof字段到 oneof 定义中. 你可以增加任意类型的字段, 但是不能使用repeated 关键字.
在产生的代码中, oneof字段拥有同样的 getters 和setters, 就像正常的可选字段一样. 也有一个特殊的方法来检查到底那个字段被设置. 你可以在相应的语言API指南中找到oneof API介绍.
1 2 3 4 5 | SampleMessage message; message.set_name( "name" ); CHECK(message.has_name()); message.mutable_sub_message(); // Will clear name field. CHECK(!message.has_name()); |
repeated
.sub_message
已经通过set_name()
删除了1 2 3 4 | SampleMessage message; SubMessage* sub_message = message.mutable_sub_message(); message.set_name( "name" ); // Will delete sub_message sub_message->set_... // Crashes here |
Swap()
两个oneof消息,每个消息,两个消息将拥有对方的值,例如在下面的例子中,msg1
会拥有sub_message
并且msg2
会有name
。1 2 3 4 5 6 7 | SampleMessage msg1; msg1.set_name( "name" ); SampleMessage msg2; msg2.mutable_sub_message(); msg1.swap(&msg2); CHECK(msg1.has_sub_message()); CHECK(msg2.has_name()); |
当增加或者删除oneof字段时一定要小心. 如果检查oneof的值返回None/NOT_SET
, 它意味着oneof字段没有被赋值或者在一个不同的版本中赋值了。 你不会知道是哪种情况,因为没有办法判断如果未识别的字段是一个oneof字段。
Tage 重用问题:
如果你希望创建一个关联映射,protocol buffer提供了一种快捷的语法:
map<key_type, value_type> map_field = N;
其中key_type
可以是任意Integer或者string类型(所以,除了floating和bytes的任意标量类型都是可以的)value_type
可以是任意类型。
例如,如果你希望创建一个project的映射,每个Projecct
使用一个string作为key,你可以像下面这样定义:
map<string, Project> projects = 3;
生成map的API现在对于所有proto3支持的语言都可用了,你可以从API指南找到更多信息。
map语法序列化后等同于如下内容,因此即使是不支持map语法的protocol buffer实现也是可以处理你的数据的:
message MapFieldEntry { key_type key = 1; value_type value = 2; }repeated MapFieldEntry map_field
= N;
当然可以为.proto文件新增一个可选的package声明符,用来防止不同的消息类型有命名冲突。如:
1 2 | package foo.bar; message Open { ... } |
在其他的消息格式定义中可以使用包名+消息名的方式来定义域的类型,如:
1 2 3 4 5 | message Foo { ... required foo.bar.Open open = 1 ; ... } |
包的声明符会根据使用语言的不同影响生成的代码。
Open
会被封装在 foo::bar
空间中; - 对于Java,包声明符会变为java的一个包,除非在.proto文件中提供了一个明确有java_package
;option go_package
在你的.proto文件中。Open
会在Foo::Bar
名称空间中。option java_package
。PascalCase
后作为名称空间,除非你在你的文件中显式的提供一个option csharp_namespace
,例如,Open
会在Foo.Bar
名称空间中Protocol buffer语言中类型名称的解析与C++是一致的:首先从最内部开始查找,依次向外进行,每个包会被看作是其父类包的内部类。当然对于 (foo.bar.Baz
)这样以“.”分隔的意味着是从最外围开始的。
ProtocolBuffer编译器会解析.proto文件中定义的所有类型名。 对于不同语言的代码生成器会知道如何来指向每个具体的类型,即使它们使用了不同的规则。
如果想要将消息类型用在RPC(远程方法调用)系统中,可以在.proto文件中定义一个RPC服务接口,protocol buffer编译器将会根据所选择的不同语言生成服务接口代码及存根。如,想要定义一个RPC服务并具有一个方法,该方法能够接收 SearchRequest并返回一个SearchResponse,此时可以在.proto文件中进行如下定义:
1 2 3 | service SearchService { rpc Search (SearchRequest) returns (SearchResponse); } |
最直观的使用protocol buffer的RPC系统是gRPC一个由谷歌开发的语言和平台中的开源的PRC系统,gRPC在使用protocl buffer时非常有效,如果使用特殊的protocol buffer插件可以直接为您从.proto文件中产生相关的RPC代码。
如果你不想使用gRPC,也可以使用protocol buffer用于自己的RPC实现,你可以从proto2语言指南中找到更多信息
还有一些第三方开发的PRC实现使用Protocol Buffer。参考第三方插件wiki查看这些实现的列表。
Proto3 支持JSON的编码规范,使他更容易在不同系统之间共享数据,在下表中逐个描述类型。
如果JSON编码的数据丢失或者其本身就是null
,这个数据会在解析成protocol buffer的时候被表示成默认值。如果一个字段在protocol buffer中表示为默认值,体会在转化成JSON的时候编码的时候忽略掉以节省空间。具体实现可以提供在JSON编码中可选的默认值。
proto3 | JSON | JSON示例 | 注意 |
---|---|---|---|
message | object | {“fBar”: v, “g”: null, …} | 产生JSON对象,消息字段名可以被映射成lowerCamelCase形式,并且成为JSON对象键,null被接受并成为对应字段的默认值 |
enum | string | “FOO_BAR” | 枚举值的名字在proto文件中被指定 |
map | object | {“k”: v, …} | 所有的键都被转换成string |
repeated V | array | [v, …] | null被视为空列表 |
bool | true, false | true, false | |
string | string | “Hello World!” | |
bytes | base64 string | “YWJjMTIzIT8kKiYoKSctPUB+” | |
int32, fixed32, uint32 | number | 1, -10, 0 | JSON值会是一个十进制数,数值型或者string类型都会接受 |
int64, fixed64, uint64 | string | “1”, “-10” | JSON值会是一个十进制数,数值型或者string类型都会接受 |
float, double | number | 1.1, -10.0, 0, “NaN”, “Infinity” | JSON值会是一个数字或者一个指定的字符串如”NaN”,”infinity”或者”-Infinity”,数值型或者字符串都是可接受的,指数符号也可以接受 |
Any | object | {“@type”: “url”, “f”: v, … } | 如果一个Any保留一个特上述的JSON映射,则它会转换成一个如下形式:{"@type": xxx, "value": yyy} 否则,该值会被转换成一个JSON对象,@type 字段会被插入所指定的确定的值 |
Timestamp | string | “1972-01-01T10:00:20.021Z” | 使用RFC 339,其中生成的输出将始终是Z-归一化啊的,并且使用0,3,6或者9位小数 |
Duration | string | “1.000340012s”, “1s” | 生成的输出总是0,3,6或者9位小数,具体依赖于所需要的精度,接受所有可以转换为纳秒级的精度 |
Struct | object | { … } | 任意的JSON对象,见struct.proto |
Wrapper types | various types | 2, “2”, “foo”, true, “true”, null, 0, … | 包装器在JSON中的表示方式类似于基本类型,但是允许nulll,并且在转换的过程中保留null |
FieldMask | string | “f.fooBar,h” | 见fieldmask.proto |
ListValue | array | [foo, bar, …] | |
Value | value | 任意JSON值 | |
NullValue | null | JSON null |
在定义.proto文件时能够标注一系列的options。Options并不改变整个文件声明的含义,但却能够影响特定环境下处理方式。完整的可用选项可以在google/protobuf/descriptor.proto找到。
一些选项是文件级别的,意味着它可以作用于最外范围,不包含在任何消息内部、enum或服务定义中。一些选项是消息级别的,意味着它可以用在消息定义的内部。当然有些选项可以作用在域、enum类型、enum值、服务类型及服务方法中。到目前为止,并没有一种有效的选项能作用于所有的类型。
如下就是一些常用的选择:
java_package
(文件选项) :这个选项表明生成java类所在的包。如果在.proto文件中没有明确的声明java_package,就采用默认的包名。当然了,默认方式产生的 java包名并不是最好的方式,按照应用名称倒序方式进行排序的。如果不需要产生java代码,则该选项将不起任何作用。如:option java_package = "com.example.foo";
java_outer_classname
(文件选项): 该选项表明想要生成Java类的名称。如果在.proto文件中没有明确的java_outer_classname定义,生成的class名称将会根据.proto文件的名称采用驼峰式的命名方式进行生成。如(foo_bar.proto生成的java类名为FooBar.java),如果不生成java代码,则该选项不起任何作用。如:option java_outer_classname = "Ponycopter";
optimize_for
(文件选项): 可以被设置为 SPEED, CODE_SIZE,或者LITE_RUNTIME。这些值将通过如下的方式影响C++及java代码的生成:
SPEED (default)
: protocol buffer编译器将通过在消息类型上执行序列化、语法分析及其他通用的操作。这种代码是最优的。CODE_SIZE
: protocol buffer编译器将会产生最少量的类,通过共享或基于反射的代码来实现序列化、语法分析及各种其它操作。采用该方式产生的代码将比SPEED要少得多, 但是操作要相对慢些。当然实现的类及其对外的API与SPEED模式都是一样的。这种方式经常用在一些包含大量的.proto文件而且并不盲目追求速度的 应用中。LITE_RUNTIME
: protocol buffer编译器依赖于运行时核心类库来生成代码(即采用libprotobuf-lite 替代libprotobuf)。这种核心类库由于忽略了一 些描述符及反射,要比全类库小得多。这种模式经常在移动手机平台应用多一些。编译器采用该模式产生的方法实现与SPEED模式不相上下,产生的类通过实现 MessageLite接口,但它仅仅是Messager接口的一个子集。1 | option optimize_for = CODE_SIZE; |
cc_enable_arenas
(文件选项):对于C++产生的代码启用arena allocationobjc_class_prefix
(文件选项):设置Objective-C类的前缀,添加到所有Objective-C从此.proto文件产生的类和枚举类型。没有默认值,所使用的前缀应该是苹果推荐的3-5个大写字符,注意2个字节的前缀是苹果所保留的。deprecated
(字段选项):如果设置为true
则表示该字段已经被废弃,并且不应该在新的代码中使用。在大多数语言中没有实际的意义。在java中,这回变成@Deprecated
注释,在未来,其他语言的代码生成器也许会在字标识符中产生废弃注释,废弃注释会在编译器尝试使用该字段时发出警告。如果字段没有被使用你也不希望有新用户使用它,尝试使用保留语句替换字段声明。int32 old_field = 6 [deprecated=true];
ProtocolBuffers允许自定义并使用选项。该功能应该属于一个高级特性,对于大部分人是用不到的。如果你的确希望创建自己的选项,请参看 Proto2 Language Guide。注意创建自定义选项使用了拓展,拓展只在proto3中可用。
aarch64
$./configure CC=aarch64-target-linux-gnu-gcc CXX=aarch64-target-linux-gnu-g++ –host=aarch64-target-linux –disable-protoc –prefix=/home/jay/Work/Tools/protobuf-c/protobuf-c-1.4.0/build-armx
x86_64
$./configure –prefix=/home/dev/Tools/protobuf-c-x64/protobuf-c-1.4.0/build-x64 protobuf_LIBS=/home/dev/Tools/protobuf-cpp-x64/protobuf-3.5.1/build-x64/lib/libprotobuf.so protobuf_CFLAGS=’-L/home/dev/Tools/protobuf-cpp-x64/protobuf-3.5.1/build-x64/lib/ -I/home/dev/Tools/protobuf-cpp-x64/protobuf-3.5.1/build-x64/include’
注 protobuf-c 编译依赖 protobuf-cpp 所以 请先编译protobuf-cpp。 protobuf-cpp中包含google/compiler/*
其他错误 protoc(protoc –version) 的版本应该跟protobuf-cpp的本版对应 如果不对应会提示
$ ./configure CC=aarch64-target-linux-gnu-gcc CXX=aarch64-target-linux-gnu-g++ –host=aarch64-target-linux –prefix=/home/dev/Tools/protobuf-cpp-aarch64/protobuf-3.5.1/build-aarch64 –with-sysroot=/home/dev/App/MDC_Cross_Compiler/sysroot/ –with-protoc=/home/dev/Tools/protobuf-cpp-x64/protobuf-3.5.1/build-x64/bin/protoc //protc最好先生成x64的备用,后边针对.proto文件会有一个版本的验证
如果出现as:xxxxxxx”64”:是因为使用了编译器的as,使用export=/usr/bin/as/“$PATH 更新成系统的as就可以了
$./configure –prefix=/home/dev/Tools/protobuf-cpp-x64/protobuf-3.5.1/build-x64
/etc/
其中[Unit][Service][Install]不可缺少
1 | [Unit] |
1 | Description:运行软件描述 |
qt 多媒体模块介绍
类名 英文描述 中文描述
|类名|英文描述|中文描述|
|:-|:-:|:-:|
|a|2|3|
|QAudioBuffer|Represents a collection of audio samples with a specific format and sample rate|表示具有特定格式和采样率的音频样本的集合|
QAudioBuffer::StereoFrame| Simple wrapper for a stereo audio frame |立体声音频框架的简单包装|
|QAudioDecoder |Allows decoding audio 允许解码音频|
|QAudioDeviceInfo |Interface to query audio devices and their functionality |用于查询音频设备及其功能的接口|
|QAudioFormat |Stores audio stream parameter information 存储音频流参数信息|
|QAudioInput |Interface for receiving audio data from an audio input device |用于从音频输入设备接收音频数据的接口|
|QAudioOutput|Interface for sending audio data to an audio output device |用于将音频数据发送到音频输出设备的接口|
|QAudioProbe|Allows you to monitor audio being played or recorded |允许您监视正在播放或录制的音频|
||QAbstractAudioDeviceInfo |Base class for audio backends |音频后端的基础类|
QAbstractAudioInput Access for |QAudioInput to access the audio device provided by the plugin |访问QAudioInput访问插件提供的音频设备|
|QAbstractAudioOutput |Base class for audio backends |音频后端的基础类|
||QAudioSystemPlugin |Abstract base for audio plugins |音频插件抽象基础|
|QSound |Method to play .wav sound files |播放.wav声音文件的方法|
|QSoundEffect |Way to play low latency sound effects |播放低延时音效的方式|
|QCamera::FrameRateRange |A FrameRateRange represents a range of frame rates as minimum and maximum rate |FrameRateRange表示帧速率的范围为最小和最大速率|
|QCamera |Interface for system camera devices |系统相机设备接口|
|QCameraExposure |Interface for exposure related camera settings |曝光相关摄像机设置界面|
|QCameraFocus |Interface for focus and zoom related camera settings |用于对焦和变焦相关设置的界面|
|QCameraFocusZone |Information on zones used for autofocusing a camera |关于用于自动对焦相机的区域的信息|
|QCameraImageProcessing |Interface for image processing related camera settings |图像处理相关摄像机设置界面|
|QCameraInfo |General information about camera devices |有关相机设备的一般信息|
|QCameraViewfinderSettings |Set of viewfinder settings |设置取景器设置|
|QAudioDecoderControl |Access to the audio decoding functionality of a QMediaService |访问QMediaService的音频解码功能|
|QAudioEncoderSettingsControl |Access to the settings of a media service that performs audio encoding |访问执行音频编码的媒体服务的设置|
|QAudioInputSelectorControl |Audio input selector media control |音频输入选择器媒体控制|
|QAudioOutputSelectorControl |Audio output selector media control |音频输出选择器媒体控制|
|QAudioRoleControl |Control over the audio role of a media object |控制媒体对象的音频角色|
|QCameraCaptureBufferFormatControl |Control for setting the capture buffer format |用于设置捕获缓冲区格式的控制|
|QCameraCaptureDestinationControl |Control for setting capture destination |控制设置捕获目的地|
|QCameraControl |Abstract base class for classes that control still cameras or video cameras |用于控制静态相机或摄像机的类的抽象基类|
|QCameraExposureControl |Allows controlling camera exposure parameters |允许控制相机曝光参数|
|QCameraFeedbackControl |Allows controlling feedback (sounds etc) during camera operation |允许在相机操作期间控制反馈(声音等)|
|QCameraFlashControl |Allows controlling a camera’s flash |允许控制相机的闪光灯|
|QCameraFocusControl |Supplies control for focusing related camera parameters |用于控制相关的相机参数|
|QCameraImageCaptureControl |Control interface for image capture services |图像捕获服务控制界面|
|QCameraImageProcessingControl |Abstract class for controlling image processing parameters, like white balance, contrast, saturation, sharpening and denoising |用于控制图像处理参数的抽象类,如白平衡,对比度,饱和度,锐化和去噪|
|QCameraInfoControl |Camera info media control |相机信息媒体控制|
|QCameraLocksControl |Abstract base class for classes that control still cameras or video cameras |用于控制静态相机或摄像机的类的抽象基类|
|QCameraViewfinderSettingsControl |Abstract class for controlling camera viewfinder parameters |用于控制摄像机取景器参数的抽象类|
|QCameraViewfinderSettingsControl2 |Access to the viewfinder settings of a camera media service |访问摄像机媒体服务的取景器设置|
|QCameraZoomControl |Supplies control for optical and digital camera zoom |用于光学和数码相机变焦的耗材控制|
|QImageEncoderControl |Access to the settings of a media service that performs image encoding |访问执行图像编码的媒体服务的设置|
|QMediaAudioProbeControl |Allows control over probing audio data in media objects |允许控制媒体对象中的探测音频数据|
|QMediaAvailabilityControl |Supplies a control for reporting availability of a service |提供报告服务可用性的控制|
|QMediaContainerControl |Access to the output container format of a QMediaService |访问QMediaService的输出容器格式|
|QMediaGaplessPlaybackControl |Access to the gapless playback related control of a QMediaService |访问无缝播放相关控件的QMediaService|
|QMediaNetworkAccessControl |Allows the setting of the Network Access Point for media related activities |允许为媒体相关活动设置网络接入点|
|QMediaPlayerControl |Access to the media playing functionality of a QMediaService |访问QMediaService的媒体播放功能|
|QMediaRecorderControl |Access to the recording functionality of a QMediaService |访问QMediaService的录制功能|
|QMediaStreamsControl |Media stream selection control |媒体流选择控制|
|QMediaVideoProbeControl |Allows control over probing video frames in media objects |允许控制媒体对象中的探测视频帧|
|QMetaDataReaderControl |Read access to the meta-data of a QMediaService’s media |读取对QMediaService媒体元数据的访问权限|
|QMetaDataWriterControl |Write access to the meta-data of a QMediaService’s media |写入对QMediaService媒体元数据的访问|
|QRadioDataControl |Access to the RDS functionality of the radio in the QMediaService |访问QMediaService中无线电的RDS功能|
|QRadioTunerControl |Access to the radio tuning functionality of a QMediaService |访问QMediaService的无线电调谐功能|
|QVideoDeviceSelectorControl |Video device selector media control |视频设备选择器媒体控制|
|QVideoEncoderSettingsControl |Access to the settings of a media service that performs video encoding |访问执行视频编码的媒体服务的设置|
|QVideoRendererControl |Media control for rendering video to a QAbstractVideoSurface |媒体控制,用于将视频渲染到QAbstractVideoSurface|
|QVideoWindowControl |Media control for rendering video to a window |用于将视频渲染到窗口的媒体控制|
|QMediaContent |Access to the resources relating to a media content |访问与媒体内容相关的资源|
|QMediaPlayer |Allows the playing of a media source |允许播放媒体源|
|QMediaPlaylist |List of media content to play |要播放的媒体内容列表|
|QMediaResource |Description of a media resource |媒体资源的描述|
|QMediaBindableInterface |The base class for objects extending media objects functionality |扩展媒体对象功能的对象的基类|
|QMediaControl |Base interface for media service controls |媒体服务控制的基本界面|
|QMediaObject |Common base for multimedia objects |多媒体对象的共同基础|
|QMediaService |Common base class for media service implementations |媒体服务实现的通用基类|
|QMediaServiceCameraInfoInterface |Interface provides camera-specific information about devices supported by a camera service plug-in |接口提供有关相机服务插件支持的设备的相机特定信息|
|QMediaServiceDefaultDeviceInterface |Interface identifies the default device used by a media service plug-in |接口标识介质服务插件使用的默认设备|
|QMediaServiceFeaturesInterface |Interface identifies features supported by a media service plug-in |接口识别媒体服务插件支持的功能|
|QMediaServiceProviderPlugin |Interface provides an interface for QMediaService plug-ins |接口为QMediaService插件提供了一个接口|
|QMediaServiceSupportedDevicesInterface Interface identifies the devices supported by a media service plug-in |接口标识媒体服务插件支持的设备|
|QMediaServiceSupportedFormatsInterface |Interface identifies if a media service plug-in supports a media format |接口识别媒体服务插件是否支持媒体格式|
|QMediaTimeInterval |Represents a time interval with integer precision |表示整数精度的时间间隔|
|QMediaTimeRange |Represents a set of zero or more disjoint time intervals |表示一组零个或多个不相交的时间间隔|
|QRadioData |Interfaces to the RDS functionality of the system radio |与系统无线电的RDS功能的接口|
|QRadioTuner |Interface to the systems analog radio device |与系统模拟无线电设备的接口|
|QAudioRecorder |Used for the recording of audio |用于录制音频|
|QAudioEncoderSettings |Set of audio encoder settings |音频编码器设置|
|QImageEncoderSettings |Set of image encoder settings |设置图像编码器设置|
|QVideoEncoderSettings |Set of video encoder settings |视频编码器设置集|
|QMediaRecorder |Used for the recording of media content |用于录制媒体内容|
|QAbstractPlanarVideoBuffer |Abstraction for planar video data |平面视频数据的抽象|
|QAbstractVideoBuffer |Abstraction for video data |视频数据抽象|
|QAbstractVideoFilter |Represents a filter that is applied to the video frames received by a VideoOutput type |表示应用于VideoOutput类型接收到的视频帧的过滤器|
|QVideoFilterRunnable |Represents the implementation of a filter that owns all graphics and computational resources, and performs the actual filtering or calculations |表示具有所有图形和计算资源的过滤器的实现,并执行实际的过滤或计算|
|QAbstractVideoSurface |Base class for video presentation surfaces |视频演示表面的基类|
|QVideoFrame |Represents a frame of video data |表示一帧视频数据|
|QVideoProbe |Allows you to monitor video frames being played or recorded |允许您监视正在播放或录制的视频帧|
|QVideoSurfaceFormat |Specifies the stream format of a video presentation surface |指定视频演示表面的流格式|
|
[TOC]
指定变量存储位置
C++ 类中 static 关键字修饰的成员变量
C++ 类中被 static 关键字修饰的成员函数
[^1]参考:https://www.jianshu.com/p/0b2d9679a9f2[^1]
[^2]参考:https://www.cnblogs.com/zeppelin5/p/10075569.html[^2]
[^3]参考:https://baijiahao.baidu.com/s?id=1643939248815582630&wfr=spider&for=pc[^3]
1 | #ifdef __cplusplus |
1 | #undef LIKELY |
1 | union u { |
1 | int main(void) { |
1 | union littleOrBig { |
1 | 同理,解决TCP黏包的核心在于每次发送数据时,需要把数据的长度,放在数据的前面一起发送出去。我们使用联合体,就可以很容的把数据长度转成byte。联合体如下: |
[^4]参考:https://www.jianshu.com/p/35b187613ec6[^4]
[^5]参考:https://blog.csdn.net/HFUTWXY/article/details/102574101[^5]
1 | 把 0x112233 这个数存放到存储器中,采用大小端方式存储就是如下方式: |
1 | auto_ptr< string> p1 (new string ("I reigned lonely as a cloud.")); |
1 | #include <iostream> |
1 | class B; |
fork:创建一个和当前进程映像一样的进程可以通过 fork() 系统调用
1 | #include <sys/types.h> |
成功调用 fork() 会创建一个新的进程,它几乎与调用 fork() 的进程一模一样,这两个进程都会继续运行。在子进程中,成功的 fork() 调用会返 回0。在父进程中 fork() 返回子进程的 pid。如果出现错误,fork() 返回一个负值。
最常见的 fork() 用法是创建一个新的进程,然后使用 exec() 载入二进制映像,替换当前进程的映像。这种情况下,派生(fork)了新的进程,而这个子进程会执行一个新的二进制可执行文件的映像。这种“派生加执行”的方式是很常见的。
在早期的Unix系统中,创建进程比较原始。当调用fork时,内核会把所有的内部数据结构复制一份,复制进程的页表项,然后把父进程的地址空间中的内容逐页的复制到子进程的地址空间中。但从内核角度来说,逐页的复制方式是十分耗时的。现代 的Unix系统采取了更多的优化,例如Linux,采用了写时复制的方法,而不是对父进程空间进程整体复制。
写入时复制是一种计算机程序设计领域的优化策略。其核心思想是,如果有多个调用者同时请求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这个过程对其他的调用者是透明的(transparently)。此作法的主要优点是如果调用者没有修改该资源,就不会有副本(private copy)被建立,因此多个调用者只是读取操作是可以共享同一份资源。
应用
1 | class Season{ |
1 | class Egle{ |
1 | class Goose{ |
1 | class Wing{ |
1 | class Goose : public Bird{ |
1 | class Animal{ |
SIGHUP
本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联。
SIGINT
程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl-C)时发出,用于通知前台进程组终止进程。
SIGQUIT
和SIGINT类似, 但由QUIT字符(通常是Ctrl-/)来控制. 进程在因收到SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信号。
SIGILL
执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号。
SIGTRAP
由断点指令或其它trap指令产生. 由debugger使用。
SIGABRT
调用abort函数生成的信号。
SIGBUS
非法地址, 包括内存地址对齐(alignment)出错。比如访问一个四个字长的整数, 但其地址不是4的倍数。它与SIGSEGV的区别在于后者是由于对合法存储地址的非法访问触发的(如访问不属于自己存储空间或只读存储空间)。
SIGFPE
在发生致命的算术运算错误时发出. 不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误
SIGKILL
用来立即结束程序的运行. 本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号。
SIGUSR1
留给用户使用
SIGSEGV
试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据.
SIGUSR2
留给用户使用
SIGPIPE
管道破裂。这个信号通常在进程间通信产生,比如采用FIFO(管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到SIGPIPE信号。此外用Socket通信的两个进程,写进程在写Socket的时候,读进程已经终止。
SIGALRM
时钟定时信号, 计算的是实际的时间或时钟时间. alarm函数使用该信号.
SIGTERM
程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出,shell命令kill缺省产生这个信号。如果进程终止不了,我们才会尝试SIGKILL。
SIGCHLD
子进程结束时, 父进程会收到这个信号。
如果父进程没有处理这个信号,也没有等待(wait)子进程,子进程虽然终止,但是还会在内核进程表中占有表项,这时的子进程称为僵尸进程。这种情况我们应该避免(父进程或者忽略SIGCHILD信号,或者捕捉它,或者wait它派生的子进程,或者父进程先终止,这时子进程的终止自动由init进程来接管)。
SIGCONT
让一个停止(stopped)的进程继续执行. 本信号不能被阻塞. 可以用一个handler来让程序在由stopped状态变为继续执行时完成特定的工作. 例如, 重新显示提示符
SIGSTOP
停止(stopped)进程的执行. 注意它和terminate以及interrupt的区别:该进程还未结束, 只是暂停执行. 本信号不能被阻塞, 处理或忽略.
SIGTSTP
停止进程的运行, 但该信号可以被处理和忽略. 用户键入SUSP字符时(通常是Ctrl-Z)发出这个信号
SIGTTIN
当后台作业要从用户终端读数据时, 该作业中的所有进程会收到SIGTTIN信号. 缺省时这些进程会停止执行.
SIGTTOU
类似于SIGTTIN, 但在写终端(或修改终端模式)时收到.
SIGURG
有”紧急”数据或out-of-band数据到达socket时产生.
SIGXCPU
超过CPU时间资源限制. 这个限制可以由getrlimit/setrlimit来读取/改变。
SIGXFSZ
当进程企图扩大文件以至于超过文件大小资源限制。
SIGVTALRM
虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间.
SIGPROF
类似于SIGALRM/SIGVTALRM, 但包括该进程用的CPU时间以及系统调用的时间.
SIGWINCH
窗口大小改变时发出.
SIGIO
文件描述符准备就绪, 可以开始进行输入/输出操作.
SIGPWR
Power failure
SIGSYS
非法的系统调用。
在以上列出的信号中,程序不可捕获、阻塞或忽略的信号有:
SIGKILL,SIGSTOP
不能恢复至默认动作的信号有:
SIGILL,SIGTRAP
默认会导致进程流产的信号有:
SIGABRT,SIGBUS,SIGFPE,SIGILL,SIGIOT,SIGQUIT,SIGSEGV,SIGTRAP,SIGXCPU,SIGXFSZ
默认会导致进程退出的信号有:
SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM
默认会导致进程停止的信号有:
SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU
默认进程忽略的信号有:
SIGCHLD,SIGPWR,SIGURG,SIGWINCH
此外,SIGIO在SVR4是退出,在4.3BSD中是忽略;SIGCONT在进程挂起时是继续,否则是忽略,不能被阻塞。
1 | template<typename T> |
1 | #define MIN(A,B) ((A) <= (B) ? (A) : (B)) |
1 | void LoopMove(char *str, int steps) { |
1 | class String |
1 | //普通构造函数 |
docker常用的命令
1 | docker images //1.查看本地docker images |
注:启动的容器不管是运行中的还是停止的只要是容器存在,那么操作过的容器内容就会一直存在这个容器中.直到将这个容器销毁数据将不存在.即:只要容器不删除 操作的数据就一直在
8.删除一个docker image
$docker rmi image_name
$docker rmi image_id
使用image 名字 或者 image id来指定要删除的镜像,当有相同的镜像的时候只能使用指定名字来删除
9.删除一个docker 容器
$docker rm container_name
$docker rm container_id
使用container 名字或这ID来删除一个容器
注意,容器在删除的时候必须已经停止
1 | #杀死所有正在运行的容器 |
docker run -v hostpath:/home/user/web xximg
1 | # 将本地镜像打tag 准备push到私有仓库 |
1 | #使用dockerfile创建镜像 |
ubuntu:docker-19.x:/etc/default下添加:
DOCKER_OPTS=–graph=”/vdb1/docker”
然后重启docker
1.docker在安装的时候会创建一个docker组,只需要将非root用户添加到docker组后就可以操作docker,具体操作如下:
linux将用户添加组别操作:
a1. usermod -a -G docker jay //将jay添加到docker组别
a2. gpassword -M jay docker //将jay添加到docker组别
b. nergrp docker //刷新组别
docker 映射主机端口到容器内端口