分类目录归档:其他

其他

zmq-4.3.4、czmq-4.2.1、zyre-2.0.1及libsodium-1.0.18的编译

之前写过一次zmq的编译问题的文章,当时编译的是zmq-4.3.2,但是由于这次要将zmq移植到Android一体机,发生了一些奇怪的问题:一体机客户端zmq程序成功的send了数据,但是运行在云端的server毫无反应,一体机中用到的zmq是最新的4.3.4,所以决定将PC版的也升级看看问题是否还存在(4.3.2->4.3.4),本来没报太大希望的,结果将云端server的4.3.2升级到4.3.4后,Android的zmq客户端居然成功连上了,奇葩。

以下是最新4.3.4的编译过程:

1、编译libsodium-1.0.18

msvc已经配置好,直接打开解决方案:

libsodium-1.0.18\builds\msvc\vs2019\libsodium.sln

编译结果在bin目录下:

libsodium-1.0.18\bin

2、编译zeromq-4.3.4

没有现有msvc解决方案,得自己通过cmake指令生成。

  • 为避免生成带版本号的库文件,需要在CMakeLists.txt的1315行将POSTFIX的代码全部注释掉:
    set_target_properties(
      libzmq
      PROPERTIES PUBLIC_HEADER "${public_headers}"
                 #RELEASE_POSTFIX "${MSVC_TOOLSET}-mt-${ZMQ_VERSION_MAJOR}_${ZMQ_VERSION_MINOR}_${ZMQ_VERSION_PATCH}"
                 #RELWITHDEBINFO_POSTFIX "${MSVC_TOOLSET}-mt-${ZMQ_VERSION_MAJOR}_${ZMQ_VERSION_MINOR}_${ZMQ_VERSION_PATCH}"
                 #MINSIZEREL_POSTFIX "${MSVC_TOOLSET}-mt-${ZMQ_VERSION_MAJOR}_${ZMQ_VERSION_MINOR}_${ZMQ_VERSION_PATCH}"
                 #DEBUG_POSTFIX "${MSVC_TOOLSET}-mt-gd-${ZMQ_VERSION_MAJOR}_${ZMQ_VERSION_MINOR}_${ZMQ_VERSION_PATCH}"
                 RUNTIME_OUTPUT_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}"
                 COMPILE_DEFINITIONS "DLL_EXPORT"
                 OUTPUT_NAME "lib${ZMQ_OUTPUT_BASENAME}")
  • 这个版本编译静态库会有一堆错误,我们没用静态库,所以没详细研究为什么,直接把静态库编译选项关了:
option(BUILD_STATIC "Whether or not to build the static archive" OFF)
  • 由于后面用到了zyre,而zyre默认为zmq使用了sodium加密,会直接去链接zmq的相关函数,如果zmq库不使用sodium,zyre会编译出错,所以需要将sodium激活,即,制作一个FindSodium.cmake文件,内容为:
########################################################################
# CMake module for finding SODIUM
#
# The following variables will be defined:
#
#  SODIUM_FOUND
#  SODIUM_INCLUDE_DIRS
#  SODIUM_LIBRARIES
#

find_path(SODIUM_INCLUDE_DIRS
  NAMES sodium.h
  PATHS ${PROJECT_SOURCE_DIR}/../libsodium-1.0.18/src/libsodium/include
  NO_DEFAULT_PATH)
find_library(SODIUM_LIBRARIES
  NAMES libsodium
  PATHS ${PROJECT_SOURCE_DIR}/../libsodium-1.0.18/bin/x64/Release/v142/dynamic
  NO_DEFAULT_PATH)

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(SODIUM DEFAULT_MSG SODIUM_LIBRARIES SODIUM_INCLUDE_DIRS)

一旦找到sodium,zmq会自动激活CURVE模式,将自动使用sodium加密算法。

  • 在zeromq-4.3.4目录中打开cmd,执行如下指令:
cmake -H. -Bbuild -G"Visual Studio 16 2019" -Ax64
cmake --build build --config release
cmake --build build --config debug

经过以上步骤,libzmq会顺利生成到lib及bin目录下,不会有任何编译错误。

3、编译czmq-4.2.1

  • czmq依赖libzmq,所以需要修改Findlibzmq.cmake文件,让其找到上面编译出来的libzmq:在if (NOT MSVC)加一段else就行了:
if (NOT MSVC)
    include(FindPkgConfig)
    pkg_check_modules(PC_LIBZMQ "libzmq")
    if (PC_LIBZMQ_FOUND)
        # add CFLAGS from pkg-config file, e.g. draft api.
        add_definitions(${PC_LIBZMQ_CFLAGS} ${PC_LIBZMQ_CFLAGS_OTHER})
        # some libraries install the headers is a subdirectory of the include dir
        # returned by pkg-config, so use a wildcard match to improve chances of finding
        # headers and SOs.
        set(PC_LIBZMQ_INCLUDE_HINTS ${PC_LIBZMQ_INCLUDE_DIRS} ${PC_LIBZMQ_INCLUDE_DIRS}/*)
        set(PC_LIBZMQ_LIBRARY_HINTS ${PC_LIBZMQ_LIBRARY_DIRS} ${PC_LIBZMQ_LIBRARY_DIRS}/*)
    endif(PC_LIBZMQ_FOUND)
else()
    set(PC_LIBZMQ_INCLUDE_DIRS ../zeromq-4.3.4/include)
    set(PC_LIBZMQ_LIBRARY_DIRS ../zeromq-4.3.4/build/lib/Release)
    set(PC_LIBZMQ_INCLUDE_HINTS ${PC_LIBZMQ_INCLUDE_DIRS} ${PC_LIBZMQ_INCLUDE_DIRS}/*)
    set(PC_LIBZMQ_LIBRARY_HINTS ${PC_LIBZMQ_LIBRARY_DIRS} ${PC_LIBZMQ_LIBRARY_DIRS}/*)
endif (NOT MSVC)

message("######## ${PC_LIBZMQ_INCLUDE_HINTS}")
message("######## ${PC_LIBZMQ_LIBRARY_HINTS}")
  • 由于静态库编译会有一堆错误,没有详细研究过,也不需要用静态库,所以把他关了:
option(CZMQ_BUILD_STATIC "Whether or not to build the static archive" OFF)
  • 执行指令生成配置、生成库文件:
cmake -H. -Bbuild -G"Visual Studio 16 2019" -Ax64
cmake --build build --config release
cmake --build build --config debug

4、编译zyre-2.0.1

由于zyre同时依赖zmq、czmq,所以:

  • 将czmq中的Findlibzmq.cmake拷贝直接覆盖zyre下的Findlibzmq.cmake文件;
  • 修改Findczmq.cmake,方式与修改Findlibzmq.cmake一样
    然后执行指令生成配置、生成库文件:
cmake -H. -Bbuild -G"Visual Studio 16 2019" -Ax64
cmake --build build --config release
cmake --build build --config debug

5、czmq版本号问题

在这个版本中,czmq彻底删除了msvc的配置,所以没有version.rc或resource.rc文件,我从上一个版本找到了用过的resource.rc和resource.h文件,拷贝到src目录,改一下版本号,在CMakeList.txt中加进去就可以了:

resource.h:

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by resource.rc

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        101
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1001
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

resource.rc:

// Microsoft Visual C++ generated resource script.
//
#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"

/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

/////////////////////////////////////////////////////////////////////////////
// English (United States) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US

#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE
BEGIN
    "resource.h\0"
END

2 TEXTINCLUDE
BEGIN
    "#include ""winres.h""\r\n"
    "\0"
END

3 TEXTINCLUDE
BEGIN
    "\r\n"
    "\0"
END

#endif    // APSTUDIO_INVOKED

/////////////////////////////////////////////////////////////////////////////
//
// Version
//

VS_VERSION_INFO VERSIONINFO
 FILEVERSION 4,2,1,0
 PRODUCTVERSION 4,2,1,0
 FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
 FILEFLAGS 0x1L
#else
 FILEFLAGS 0x0L
#endif
 FILEOS 0x40004L
 FILETYPE 0x7L
 FILESUBTYPE 0x0L
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "040904b0"
        BEGIN
            VALUE "CompanyName", "The AUTHORS"
            VALUE "FileDescription", "The high-level C binding for 0MQ"
            VALUE "FileVersion", "4.2.1.0"
            VALUE "InternalName", "CZMQ"
            VALUE "LegalCopyright", "Copyright (c) the Authors"
            VALUE "OriginalFilename", "libczmq.dll"
            VALUE "ProductName", "CZMQ"
            VALUE "ProductVersion", "4.2.1.0"
        END
    END
    BLOCK "VarFileInfo"
    BEGIN
        VALUE "Translation", 0x409, 1200
    END
END

#endif    // English (United States) resources
/////////////////////////////////////////////////////////////////////////////

#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//

/////////////////////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED

CMakeList.txt第415行,添加对rc文件的引用

      add_library(czmq SHARED ${czmq_sources} ${CMAKE_CURRENT_SOURCE_DIR}/src/resource.rc)

opencv world版编译说明

1、命令模式生成工程

先用命令模式生成工程:

cmake . -Bbuild -G"Visual Studio 16 2019" -Ax64

指令要求生成VS2019的msvc16版本工程。
基本毫无悬念,都能正确生成(最多会有几个需要从网站上现下的库下载失败,不需要,无所谓)
工程文件生成到了build目录下,名为OpenCV.sln。

  • 关于CMake动态下载失败问题

在执行cmake生成指令时,CMake会根据配置从网站下载一些第三方库,opencv用到的几个为IPPICV、FFMPEG等,但均提示下载失败,原因是”无法解析主机名”,这里用的是主机’raw.githubusercontent.com’。CMake要求用IP,所以失败,这也许是CMake的bug。
解决办法:在hosts文件中增加github的IP映射:

  • hosts文件: C:\Windows\System32\drivers\etc\hosts (记得要用管理员打开,否则无法保存)
  • 映射到IP: 199.232.68.133 raw.githubusercontent.com
    内容大概是如下样子:
# Copyright (c) 1993-2009 Microsoft Corp.
#
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
#
# This file contains the mappings of IP addresses to host names. Each
# entry should be kept on an individual line. The IP address should
# be placed in the first column followed by the corresponding host name.
# The IP address and the host name should be separated by at least one
# space.
#
# Additionally, comments (such as these) may be inserted on individual
# lines or following the machine name denoted by a '#' symbol.
#
# For example:
#
#      102.54.94.97     rhino.acme.com          # source server
#       38.25.63.10     x.acme.com              # x client host

# localhost name resolution is handled within DNS itself.
#    127.0.0.1       localhost
#    ::1             localhost
199.232.68.133      raw.githubusercontent.com

因为其他第三方库都是这个IP,所以增加此行后,其他几个库也能正常下载。

2、编译opencv_world版本

opencv被分成了大量的动态库,使用起来非常不方便,所以有人用CMake配置了一个opencv_world版,干净利落,所以更倾向于使用world版,但不知道怎么在命令模式增加此选项,只能打开cmake GUI程序,在里面找到BUILD_opencv_world选项,勾上,然后点configure按钮,然后点Generate按钮,这时候项目只会生成opencv_ts和opencv_world两个模块。

3、编译、安装

理想情况下opencv的sdk就是一个包含了头文件、lib文件dll文件、相关文档等的目录,但是它的include目录并没有整理得很好。所幸它的install做的极其完美(这也是他没有按照预想的方式整理include、lib、dll目录的原因吧),利用它的install就能把编译完的include目录、lib文件等完美放到build目录下,方法:
到build目录双击OpenCV.sln,在vs2019中打开工程。在解决方案中,找到CMakeTarges,展开后,右键编译INSTALL。它会编译各个模块并在build目录下生成一个install目录,然后把所有所需文件“安装”到这个目录,成为一个完整的sdk包,直接拿去用。
记得debug和release两个版本都install build一下,因为如果用的是静态库,debug和release模式得用相应版本。

4、关于opencv contrib库

很多东西从3.0就被拆分到contrib中了,而且单独维护,就是说,默认下载的很多东西没了,如果要用contrib里的东西,比如tracking等,你需要下载contrib库:

https://github.com/opencv/opencv_contrib

下完后解压,放到某目录,在cmake-gui中搜索extra,搜到后指定你的contrib中的modules目录,再次配置、生成,就有了tracking等功能;

  • 组件下载失败导致contrib编译不通过

contrib中有一个xfeatures2d库,这个库用到的一些文件需要动态从github下载,而如果下载配置有问题,大概率就会编译失败:

C1083    Cannot open include file: 'vgg_generated_120.i': No such file or directory
C1083    Cannot open include file: 'boostdesc_bgm.i': No such file or directory

配置成功后,执行cmake生成工程时会看到如下信息:

-- xfeatures2d/boostdesc: Download: boostdesc_bgm.i
-- xfeatures2d/boostdesc: Download: boostdesc_bgm_bi.i
-- xfeatures2d/boostdesc: Download: boostdesc_bgm_hd.i
-- xfeatures2d/boostdesc: Download: boostdesc_binboost_064.i
-- xfeatures2d/boostdesc: Download: boostdesc_binboost_128.i
-- xfeatures2d/boostdesc: Download: boostdesc_binboost_256.i
-- xfeatures2d/boostdesc: Download: boostdesc_lbgm.i
-- xfeatures2d/vgg: Download: vgg_generated_48.i
-- xfeatures2d/vgg: Download: vgg_generated_64.i
-- xfeatures2d/vgg: Download: vgg_generated_80.i
-- xfeatures2d/vgg: Download: vgg_generated_120.i
  • opencv_contrib_4.5无法编译的问题

上面提到的contrib库,我是从contrib的master上下载的,与4.5配合发布的contrib 4.5无法编译,master里已经解决了这个问题。

5、第三方引用的cmake find文件

假设名为FindOpencvWorld.cmake,其内容为:

########################################################################
# CMake module for finding OpencvWorld
#
# The following variables will be defined:
#
#  OPENCVWORLD_FOUND
#  OPENCVWORLD_INCLUDE_DIR
#  OPENCVWORLD_LIBRARY
#

unset(OpenCV_INCLUDE_DIR CACHE)
unset(OpenCV_FOUND CACHE)
unset(OpenCV_LIBRARY CACHE)

if (${arch} STREQUAL "i386")
    set(build_arch "x86")
endif (${arch} STREQUAL "i386")
if (${arch} STREQUAL "x86_64")
    set(build_arch "x64")
endif (${arch} STREQUAL "x86_64")

find_path(OPENCVWORLD_INCLUDE_DIR
  NAMES opencv2/opencv.hpp
  PATHS ${PROJECT_SOURCE_DIR}/thirdparty/opencv-4.5.0/include
  NO_DEFAULT_PATH)
find_library(OPENCVWORLD_LIBRARY_debug
  NAMES opencv_world450d
  PATHS ${PROJECT_SOURCE_DIR}/thirdparty/opencv-4.5.0/${build_arch}/vc16/lib/
  NO_DEFAULT_PATH)

find_library(OPENCVWORLD_LIBRARY_release
   NAMES opencv_world450
   PATHS ${PROJECT_SOURCE_DIR}/thirdparty/opencv-4.5.0/${build_arch}/vc16/lib/
   NO_DEFAULT_PATH)

set(OPENCVWORLD_LIBRARY optimized ${OPENCVWORLD_LIBRARY_release} debug ${OPENCVWORLD_LIBRARY_debug})
#set(OPENCVWORLD_LIBRARY ${OPENCVWORLD_LIBRARY_release})

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(OpencvWorld DEFAULT_MSG OPENCVWORLD_LIBRARY OPENCVWORLD_INCLUDE_DIR)

Windows下提高sleep精度且不占CPU的两种方式

Windows下sleep精度不够,所以在做高精度即时、媒体播放等对时间间隔有苛刻要求的情况下,sleep完全不满足需求。

C++14中实现了一种‘sleep’的方式,sleep_until()函数,时间精度非常高:

// only some test code
std::chrono::steady_clock::time_point tp = std::chrono::steady_clock::now();

while (true)
{
    // do something...
    on_vitrualRec();

    long interv = long(1.0 / 90 * 1e6);
    tp += std::chrono::microseconds(interv);
    std::this_thread::sleep_until(tp);
}

这种方式甚至处理了do something的耗时,强烈推荐这种方式。

在未发现sleep_until神器之前,都用select函数来达到高精度sleep:

    inline void sleep_with_high_precision(long microseconds)
    {
        timeval timeout = { 0, microseconds };

        // init env
        static bool isInit = false;
        if (!isInit)
        {
            isInit = true;
            WSADATA data;
            WORD w = MAKEWORD(2, 0);
            ::WSAStartup(w, &data);
        }

        // Initial a socket
        static fd_set rfds;
        static SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);

        // set a socket to watch
        FD_ZERO(&rfds);
        FD_SET(sock, &rfds);

        // Watch a never used socket
        ::select(1, &rfds, NULL, NULL, &timeout);
    }

解决MacBookPro安装Win10没声音的问题

MBP下安装的Windows没有声音已经是老问题了,每次装完都得面对这个问题,而每次都得上网搜罗一番,运气好则找到合适的解决方法,运气不好折腾几天也不一定能解决问题。这里记录下我的解决方案,是我自己试过可用的方法,所以如果有新的发现,也会在这里补充。

我的系统是MacBook Pro 2018 earl的本子,带touchbar。

安装方式没有太大区别,都是Mac中用BootCamp下载Windows驱动,然后安装Windows,装好后安装Bootcamp下载下来的驱动,安装就算完成了。但是通常情况下Windows都是没声音的,在驱动管理器中可以看到如下信息:

声卡驱动正确安装

可以看到, 声卡驱动已经正确安装 ,就是没有声音。试了更新驱动、在下载的目录中用声卡驱动安装文件重新安装等等,都没用。最后一个操作解决了问题:

在Cirrus Logic CS8409(AB 54)声音设备上右键

在右键菜单中选择卸载设备

然后点击刷新发现新硬件,自动安装,

可以看到安装后还是这个名称,但是有声音了

以前没有用这个方法是因为感觉卸载设备太危险,都不敢尝试,所以不是万不得已不会做这种危险动作,结果今天实在受不了,试一下,居然可以了!

解决SVN 从branch 合并到trunk时出现“Reintegrate can only be used“ 的问题

通常情况下我们会在working copy下通过右键菜单选择Merge,然后选择Merge from连接,将某个branch合并到当前工作目录,然后提交。

但是今天却出现了如下错误:

Reintegrate can only be used if revisions 5265 through 5689 were previously
 merged from
 https://192.168.XXX.XXX:XXX/svn/ProjectAliceVR/AliceOperationAgent/trunk to the
 reintegrate source, but this is not the case:
  AliceOperationAgent/branches/AliceAgent3.1b5264
    Missing ranges:
 /AliceOperationAgent/trunk:5283,5297,5325,5391,5489,5496,5514,5554,5591

原因可能是trunk目录下有几个目录是跟另一个项目共用的,是通过在属性中增加external链接导入到这个项目里的,而那个项目之前已经从分支合并到了trunk,导致这边的这个项目认为这几个目录已经合并过了,所以出现了如上面的错误,当然这也只是猜测。

网上也有一些解决办法,主要参考这个链接:

https://blog.csdn.net/qian_348840260/article/details/61923627?utm_source=blogkpcl8

和这个链接:

https://stackoverflow.com/questions/4737605/reintegrate-can-only-be-used-if-revisions-x-through-y-were-previously-merged-fro

我们的根本目的无非就是从brunch合并brunch中的修改到trunk,所以我想到的解决办法也许更直接一些:

1、通过svn show log,浏览brunch中的修改记录

2、按住Shift或Ctrl,结合鼠标选择要合并到trunk的修改记录

3、在选择的修改上右键单击鼠标,选择“Merge revisions to…”

4、弹出目录对话框“Select merge target”对话框,选择要将变更合并到哪个工作目录

5、合并就开始了

6、合并过程中会出现一些冲突而使合并中断,没关系,点击OK退出合并对话框

7、解决这些冲突,然后继续走上面的1~7步,直到完全合并为止

8、拷贝修改记录、提交变更

合并完成后就可以提交了,但是提交记录最好保留之前在分支中的提交记录。在选择要合并的变更时,对话框中其实已经提供了修订记录,可以直接拷贝过来,这个真的非常重要:

全选后粘贴到提交对话框即可。

总结:指定要合并的某一条或某几条变更,然后合并,这招既直接又可靠,值得推荐!

简单快捷的代码量统计工具

方法一:在VS中直接用正则表达式实现
对于Visual Studio 2012/2013/2015等,仅仅Ctrl+F只会出现简单框,需要Ctrl+Shift+F,才会出现Find对话窗,选择整个解决方案,记得勾选正则表达式,填入如下正则表达式:

^(?!(\s*\*))(?!(\s*\-\-\>))(?!(\s*\<\!\-\-))(?!(\s*\n))(?!(\s*\*\/))(?!(\s*\/\*))(?!(\s*\/\/\/))(?!(\s*\/\/))(?!(\s*\}))(?!(\s*\{))(?!(\s(using))).*$

这会过滤”{“、”}”、”using”等行,如果不过滤这些,则输入如下正则表达式:

^(?!(\s*\*))(?!(\s*\-\-\>))(?!(\s*\<\!\-\-))(?!(\s*\n))(?!(\s*\*\/))(?!(\s*\/\*))(?!(\s*\/\/\/))(?!(\s*\/\/)).*$

方法二:使用Powershell

PS C:\Users\NOITOM> E:
PS E:\> cd E:\ProjectAlice
PS E:\ProjectAlice> (gci -include *.cs,*.xaml -recurse | select-string .).Count
76
PS E:\ProjectAlice> (gci -include *.c,*.cpp,*.h,*.hpp -recurse | select-string .).Count
803225
PS E:\ProjectAlice>

The end.

在Windows 10中编译libzip

slam dso依赖libzip,libzip又依赖zlib。zlib本身很好编译通过,但是libzip在通过cmake查找zlib时,规则、文件夹结构很奇怪,费了好大劲才配置好,将libzip编译过去,这里记录一下注意事项。

cmake中内嵌了一些常用模块的查找脚本,zlib就是一个。从官网上下载zlib源码,也是通过cmake指令:cmake -H. -Bbuild -G”Visual Studio 14 2015 Win64″ 生成vs2015的x64解决方案,很容易就编译除了x64的debug和release版本,但是cmake的内嵌缺省FindZlib.cmake查找zlib对目录结构有一定要求。

1、首先,将下载到的源代码放入如下目录

C:/Program Files/zlib-1.2.11

2、然后在libzip的CMakeLists.txt文件中加入:

set(ZLIB_ROOT "C:/Program Files/zlib-1.2.11")

这是设置了cmake的缺省FindZlib.cmake中的ZLIB_ROOT变量,以便libzip在查找依赖项时能用正确的目录去查

3、在C:/Program Files/zlib-1.2.11下新建一个文件夹,lib,然后将刚才编译好的zlib.lib、zilb.dll文件放在lib目录下

因为FindZlib.cmake在ZLIB_ROOT目录下查找到zlib.h文件并获取到版本号后,接着在ZLIB_ROOT/lib目录下查找.lib文件。

这是缺省FindZlib.cmake对zlib目录结构的要求。

我只编译了x64的版本,x86的需要用cmake指令另外生成解决方案:

cmake -H. -Bbuild-win32 -G"Visual Studio 14 2015"

 

Protobuf 语法指南

======注:本文只是转载,作为个人备查,原文请访问如下地址:=======

http://colobu.com/2015/01/07/Protobuf-language-guide/

 

目录 [−]

  1. 定义一个消息类型
    1. 指定字段类型
    2. 分配标识号
    3. 指定字段规则
    4. 添加更多消息类型
    5. 添加注释
    6. 从.proto文件生成了什么?
    7. 标量数值类型
    8. Optional的字段和默认值
    9. 枚举
  2. 使用其他消息类型
    1. 导入定义
    2. 嵌套类型
    3. 更新一个消息类型
    4. 扩展
    5. 嵌套的扩展
    6. 选择可扩展的标量符号
  3. Oneof
    1. 使用Oneof
    2. 向后兼容性问题
  4. 包(Package)
    1. 包及名称的解析
  5. 定义服务(Service)
  6. 选项(Options)
    1. 自定义选项
  7. 生成访问类

本指南描述了怎样使用protocol buffer 语法来构造你的protocol buffer数据,包括.proto文件语法以及怎样生成.proto文件的数据访问类。

本文是一个参考指南——如果要查看如何使用本文中描述的多个特性的循序渐进的例子,请在http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/tutorials.html中查找需要的语言的教程。

 

定义一个消息类型

先来看一个非常简单的例子。假设你想定义一个“搜索请求”的消息格式,每一个请求含有一个查询字符串、你感兴趣的查询结果所在的页数,以及每一页多少条查询结果。可以采用如下的方式来定义消息类型的.proto文件了:

1
2
3
4
5
message SearchRequest {
required string query = 1;
optional int32 page_number = 2;
optional int32 result_per_page = 3;
}

SearchRequest消息格式有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]的标识号, Protobuf协议实现中对这些进行了预留。如果非要在.proto文件中使用这些预留标识号,编译时就会报警。

指定字段规则

所指定的消息字段修饰符必须是如下之一:

  • required:一个格式良好的消息一定要含有1个这种字段。表示该值是必须要设置的;
  • optional:消息格式中该字段可以有0个或1个值(不超过1个)。
  • repeated:在一个格式良好的消息中,这种字段可以重复任意多次(包括0次)。重复的值的顺序会被保留。表示该值可以重复,相当于java中的List。

由于一些历史原因,基本数值类型的repeated的字段并没有被尽可能地高效编码。在新的代码中,用户应该使用特殊选项[packed=true]来保证更高效的编码。如:

1
repeated int32 samples = 4 [packed=true];

required是永久性的:在将一个字段标识为required的时候,应该特别小心。如果在某些情况下不想写入或者发送一个required的字段,将原始该字段修饰符更改为optional可能会遇到问题——旧版本的使用者会认为不含该字段的消息是不完整的,从而可能会无目的的拒绝解析。在这种情况下,你应该考虑编写特别针对于应用程序的、自定义的消息校验函数。Google的一些工程师得出了一个结论:使用required弊多于利;他们更 愿意使用optional和repeated而不是required。当然,这个观点并不具有普遍性。

添加更多消息类型

在一个.proto文件中可以定义多个消息类型。在定义多个相关的消息的时候,这一点特别有用——例如,如果想定义与SearchResponse消息类型对应的回复消息格式的话,你可以将它添加到相同的.proto文件中,如:

1
2
3
4
5
6
7
8
9
message SearchRequest {
required string query = 1;
optional int32 page_number = 2;
optional int32 result_per_page = 3;
}
message SearchResponse {
}

添加注释

向.proto文件添加注释,可以使用C/C++/java风格的双斜杠(//) 语法格式,如:

1
2
3
4
5
message SearchRequest {
required string query = 1;
optional int32 page_number = 2;// Which page number do we want?
optional int32 result_per_page = 3;// Number of results to return per page.
}

从.proto文件生成了什么?

当用protocolbuffer编译器来运行.proto文件时,编译器将生成所选择语言的代码,这些代码可以操作在.proto文件中定义的消息类型,包括获取、设置字段值,将消息序列化到一个输出流中,以及从一个输入流中解析消息。

  • 对C++来说,编译器会为每个.proto文件生成一个.h文件和一个.cc文件,.proto文件中的每一个消息有一个对应的类。
  • 对Java来说,编译器为每一个消息类型生成了一个.java文件,以及一个特殊的Builder类(该类是用来创建消息类接口的)。
  • 对Python来说,有点不太一样——Python编译器为.proto文件中的每个消息类型生成一个含有静态描述符的模块,,该模块与一个元类(metaclass)在运行时(runtime)被用来创建所需的Python数据访问类。

你可以从如下的文档链接中获取每种语言更多API。http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference/overview.html

标量数值类型

一个标量消息字段可以含有一个如下的类型——该表格展示了定义于.proto文件中的类型,以及与之对应的、在自动生成的访问类中定义的类型:

.proto类型 Java 类型 C++类型 备注
double double double
float float float
int32 int int32 使用可变长编码方式。编码负数时不够高效——如果你的字段可能含有负数,那么请使用sint32。
int64 long int64 使用可变长编码方式。编码负数时不够高效——如果你的字段可能含有负数,那么请使用sint64。
uint32 int[1] uint32 Uses variable-length encoding.
uint64 long[1] uint64 Uses variable-length encoding.
sint32 int int32 使用可变长编码方式。有符号的整型值。编码时比通常的int32高效。
sint64 long int64 使用可变长编码方式。有符号的整型值。编码时比通常的int64高效。
fixed32 int[1] uint32 总是4个字节。如果数值总是比总是比228大的话,这个类型会比uint32高效。
fixed64 long[1] uint64 总是8个字节。如果数值总是比总是比256大的话,这个类型会比uint64高效。
sfixed32 int int32 总是4个字节。
sfixed64 long int64 总是8个字节。
bool boolean bool
string String string 一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。
bytes ByteString string 可能包含任意顺序的字节数据。

你可以在文章http://code.google.com/apis/protocolbuffers/docs/encoding.html 中,找到更多“序列化消息时各种类型如何编码”的信息。

Optional的字段和默认值

如上所述,消息描述中的一个元素可以被标记为“可选的”(optional)。一个格式良好的消息可以包含0个或一个optional的元素。当解 析消息时,如果它不包含optional的元素值,那么解析出来的对象中的对应字段就被置为默认值。默认值可以在消息描述文件中指定。例如,要为 SearchRequest消息的result_per_page字段指定默认值10,在定义消息格式时如下所示:

1
optional int32 result_per_page = 3 [default = 10];

如果没有为optional的元素指定默认值,就会使用与特定类型相关的默认值:对string来说,默认值是空字符串。对bool来说,默认值是false。对数值类型来说,默认值是0。对枚举来说,默认值是枚举类型定义中的第一个值。

枚举

当需要定义一个消息类型的时候,可能想为一个字段指定某“预定义值序列”中的一个值。例如,假设要为每一个SearchRequest消息添加一个 corpus字段,而corpus的值可能是UNIVERSAL,WEB,IMAGES,LOCAL,NEWS,PRODUCTS或VIDEO中的一个。 其实可以很容易地实现这一点:通过向消息定义中添加一个枚举(enum)就可以了。一个enum类型的字段只能用指定的常量集中的一个值作为其值(如果尝 试指定不同的值,解析器就会把它当作一个未知的字段来对待)。在下面的例子中,在消息格式中添加了一个叫做Corpus的枚举类型——它含有所有可能的值 ——以及一个类型为Corpus的字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
message SearchRequest {
required string query = 1;
optional int32 page_number = 2;
optional int32 result_per_page = 3 [default = 10];
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
optional Corpus corpus = 4 [default = UNIVERSAL];
}

你可以为枚举常量定义别名。 需要设置allow_alias option 为 true, 否则 protocol编译器会产生错误信息。

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)。

关于如何在你的应用程序的消息中使用枚举的更多信息,请查看所选择的语言http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference/overview.html。

使用其他消息类型

你可以将其他消息类型用作字段类型。例如,假设在每一个SearchResponse消息中包含Result消息,此时可以在相同的.proto文件中定义一个Result消息类型,然后在SearchResponse消息中指定一个Result类型的字段,如:

1
2
3
4
5
6
7
8
9
message SearchResponse {
repeated Result result = 1;
}
message Result {
required string url = 1;
optional string title = 2;
repeated string snippets = 3;
}

导入定义

在上面的例子中,Result消息类型与SearchResponse是定义在同一文件中的。如果想要使用的消息类型已经在其他.proto文件中已经定义过了呢?
你可以通过导入(importing)其他.proto文件中的定义来使用它们。要导入其他.proto文件的定义,你需要在你的文件中添加一个导入声明,如:

1
import “myproject/other_protos.proto”;

默认情况下你只能使用直接导入的.proto文件中的定义. 然而, 有时候你需要移动一个.proto文件到一个新的位置, 可以不直接移动.proto文件, 只需放入一个dummy .proto 文件在老的位置, 然后使用import转向新的位置:

1
2
// new.proto
// All definitions are moved here
1
2
3
4
// old.proto
// This is the proto that all clients are importing.
import publicnew.proto”;
import “other.proto”;

// client.proto

1
2
import “old.proto”;
// You use definitions from old.proto and new.proto, but not other.proto

protocol编译器就会在一系列目录中查找需要被导入的文件,这些目录通过protocol编译器的命令行参数-I/–import_path指定。如果不提供参数,编译器就在其调用目录下查找。

嵌套类型

你可以在其他消息类型中定义、使用消息类型,在下面的例子中,Result消息就定义在SearchResponse消息内,如:

1
2
3
4
5
6
7
8
message SearchResponse {
message Result {
required string url = 1;
optional string title = 2;
repeated string snippets = 3;
}
repeated Result result = 1;
}

如果你想在它的父消息类型的外部重用这个消息类型,你需要以Parent.Type的形式使用它,如:

1
2
3
message SomeOtherMessage {
optional SearchResponse.Result result = 1;
}

当然,你也可以将消息嵌套任意多层,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
message Outer { // Level 0
message MiddleAA { // Level 1
message Inner { // Level 2
required int64 ival = 1;
optional bool booly = 2;
}
}
message MiddleBB { // Level 1
message Inner { // Level 2
required int32 ival = 1;
optional bool booly = 2;
}
}
}

注:该特性已被弃用,在创建新的消息类型的时候,不应该再使用它——可以使用嵌套消息类型来代替它。

“组”是指在消息定义中嵌套信息的另一种方法。比如,在SearchResponse中包含若干Result的另一种方法是 :

1
2
3
4
5
6
7
message SearchResponse {
repeated group Result = 1 {
required string url = 2;
optional string title = 3;
repeated string snippets = 4;
}
}

一个“组”只是简单地将一个嵌套消息类型和一个字段捆绑到一个单独的声明中。在代码中,可以把它看成是含有一个Result类型、名叫result的字段的消息(后面的名字被转换成了小写,所以它不会与前面的冲突)。

因此,除了数据传输格式不同之外,这个例子与上面的SearchResponse例子是完全等价的。

更新一个消息类型

如果一个已有的消息格式已无法满足新的需求——如,要在消息中添加一个额外的字段——但是同时旧版本写的代码仍然可用。不用担心!更新消息而不破坏已有代码是非常简单的。在更新时只要记住以下的规则即可。

  • 不要更改任何已有的字段的数值标识。
    *所添加的任何字段都必须是optional或repeated的。这就意味着任何使用“旧”的消息格式的代码序列化的消息可以被新的代码所解析,因为它们 不会丢掉任何required的元素。应该为这些元素设置合理的默认值,这样新的代码就能够正确地与老代码生成的消息交互了。类似地,新的代码创建的消息 也能被老的代码解析:老的二进制程序在解析的时候只是简单地将新字段忽略。然而,未知的字段是没有被抛弃的。此后,如果消息被序列化,未知的字段会随之一 起被序列化——所以,如果消息传到了新代码那里,则新的字段仍然可用。注意:对Python来说,对未知字段的保留策略是无效的。
  • 非required的字段可以移除——只要它们的标识号在新的消息类型中不再使用(更好的做法可能是重命名那个字段,例如在字段前添加“OBSOLETE_”前缀,那样的话,使用的.proto文件的用户将来就不会无意中重新使用了那些不该使用的标识号)。
  • 一个非required的字段可以转换为一个扩展,反之亦然——只要它的类型和标识号保持不变。
  • int32, uint32, int64, uint64,和bool是全部兼容的,这意味着可以将这些类型中的一个转换为另外一个,而不会破坏向前、 向后的兼容性。如果解析出来的数字与对应的类型不相符,那么结果就像在C++中对它进行了强制类型转换一样(例如,如果把一个64位数字当作int32来 读取,那么它就会被截断为32位的数字)。
  • sint32和sint64是互相兼容的,但是它们与其他整数类型不兼容。
  • string和bytes是兼容的——只要bytes是有效的UTF-8编码。
  • 嵌套消息与bytes是兼容的——只要bytes包含该消息的一个编码过的版本。
  • fixed32与sfixed32是兼容的,fixed64与sfixed64是兼容的。

扩展

通过扩展,可以将一个范围内的字段标识号声明为可被第三方扩展所用。然后,其他人就可以在他们自己的.proto文件中为该消息类型声明新的字段,而不必去编辑原始文件了。看个具体例子:

1
2
3
4
message Foo {
//
extensions 100 to 199;
}

这个例子表明:在消息Foo中,范围[100,199]之内的字段标识号被保留为扩展用。现在,其他人就可以在他们自己的.proto文件中添加新字段到Foo里了,但是添加的字段标识号要在指定的范围内——例如:

1
2
3
extend Foo {
optional int32 bar = 126;
}

这个例子表明:消息Foo现在有一个名为bar的optional int32字段。

当用户的Foo消息被编码的时候,数据的传输格式与用户在Foo里定义新字段的效果是完全一样的。
然而,要在程序代码中访问扩展字段的方法与访问普通的字段稍有不同——生成的数据访问代码为扩展准备了特殊的访问函数来访问它。例如,下面是如何在C++中设置bar的值:

1
2
Foo foo;
foo.SetExtension(bar, 15);

类似地,Foo类也定义了模板函数 HasExtension(),ClearExtension(),GetExtension(),MutableExtension(),以及 AddExtension()。这些函数的语义都与对应的普通字段的访问函数相符。要查看更多使用扩展的信息,请参考相应语言的代码生成指南。注:扩展可 以是任何字段类型,包括消息类型。

嵌套的扩展

可以在另一个类型的范围内声明扩展,如:

1
2
3
4
5
6
message Baz {
extend Foo {
optional int32 bar = 126;
}
}

在此例中,访问此扩展的C++代码如下:

1
2
Foo foo;
foo.SetExtension(Baz::bar, 15);

In other words, the only effect is that bar is defined within the scope of Baz.

This is a common source of confusion: Declaring an extend block nested inside a message type does not imply any relationship between the outer type and the extended type. In particular, the above example does not mean that Baz is any sort of subclass of Foo. All it means is that the symbol bar is declared inside the scope of Baz; it’s simply a static member.

一个通常的设计模式就是:在扩展的字段类型的范围内定义该扩展——例如,下面是一个Foo的扩展(该扩展是Baz类型的),其中,扩展被定义为了Baz的一部分:

1
2
3
4
5
6
message Baz {
extend Foo {
optional Baz foo_ext = 127;
}
}

然而,并没有强制要求一个消息类型的扩展一定要定义在那个消息中。也可以这样做:

1
2
3
4
5
6
7
8
message Baz {
}
// This can even be in a different file.
extend Foo {
optional Baz foo_baz_ext = 127;
}

事实上,这种语法格式更能防止引起混淆。正如上面所提到的,嵌套的语法通常被错误地认为有子类化的关系——尤其是对那些还不熟悉扩展的用户来说。

选择可扩展的标量符号

在同一个消息类型中一定要确保两个用户不会扩展新增相同的标识号,否则可能会导致数据的不一致。可以通过为新项目定义一个可扩展标识号规则来防止该情况的发生。

如果标识号需要很大的数量时,可以将该可扩展标符号的范围扩大至max,其中max是229 – 1, 或536,870,911。如下所示:

1
2
3
4
5
message Foo {
extensions 1000 to max;
}

max 是 2^29 – 1, 或者 536,870,911.

通常情况下在选择标符号时,标识号产生的规则中应该避开[19000-19999]之间的数字,因为这些已经被Protocol Buffers实现中预留了。

Oneof

如果你的消息中有很多可选字段, 并且同时至多一个字段会被设置, 你可以加强这个行为,使用oneof特性节省内存.

Oneof字段就像可选字段, 除了它们会共享内存, 至多一个字段会被设置。 设置其中一个字段会清除其它oneof字段。 你可以使用case()或者WhichOneof() 方法检查哪个oneof字段被设置, 看你使用什么语言了.

使用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 定义中. 你可以增加任意类型的字段, 但是不能使用 required, optional, repeated 关键字.

在产生的代码中, oneof字段拥有同样的 getters 和setters, 就像正常的可选字段一样. 也有一个特殊的方法来检查到底那个字段被设置. 你可以在相应的语言API中找到oneof API介绍.

Oneof 特性:

  • 设置oneof会自动清楚其它oneof字段的值. 所以设置多次后,只有最后一次设置的字段有值.
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());
  • If the parser encounters multiple members of the same oneof on the wire, only the last member seen is used in the parsed message.
  • oneof不支持扩展.
  • oneof不能 repeated.
  • 反射API对oneof 字段有效.
  • 如果使用C++,需确保代码不会导致内存泄漏. 下面的代码会崩溃, 因为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
  • Again in C++, if you Swap() two messages with oneofs, each message will end up with the other’s oneof case: in the example below, msg1 will have a sub_message and msg2 will have a 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字段没有被赋值或者在一个不同的版本中赋值了。 你不会知道是哪种情况。

Tag 重用问题

  • Move optional fields into or out of a oneof: You may lose some of your information (some fields will be cleared) after the message is serialized and parsed.
  • Delete a oneof field and add it back: This may clear your currently set oneof field after the message is serialized and parsed.
  • Split or merge oneof: This has similar issues to moving regular optional fields.

包(Package)

当然可以为.proto文件新增一个可选的package声明符,用来防止不同的消息类型有命名冲突。如:

1
2
package foo.bar;
message Open { }

在其他的消息格式定义中可以使用包名+消息名的方式来定义域的类型,如:

1
2
3
4
5
message Foo {
required foo.bar.Open open = 1;
}

包的声明符会根据使用语言的不同影响生成的代码。

  • 对于C++,产生的类会被包装在C++的命名空间中,如上例中的Open会被封装在 foo::bar空间中;
  • 对于Java,包声明符会变为java的一个包,除非在.proto文件中提供了一个明确有java_package;
  • 对于 Python,这个包声明符是被忽略的,因为Python模块是按照其在文件系统中的位置进行组织的。

包及名称的解析

Protocol buffer语言中类型名称的解析与C++是一致的:首先从最内部开始查找,依次向外进行,每个包会被看作是其父类包的内部类。当然对于 (foo.bar.Baz)这样以“.”分隔的意味着是从最外围开始的。ProtocolBuffer编译器会解析.proto文件中定义的所有类型名。 对于不同语言的代码生成器会知道如何来指向每个具体的类型,即使它们使用了不同的规则。

定义服务(Service)

如果想要将消息类型用在RPC(远程方法调用)系统中,可以在.proto文件中定义一个RPC服务接口,protocol buffer编译器将会根据所选择的不同语言生成服务接口代码及存根。如,想要定义一个RPC服务并具有一个方法,该方法能够接收 SearchRequest并返回一个SearchResponse,此时可以在.proto文件中进行如下定义:

1
2
3
service SearchService {
rpc Search (SearchRequest) returns (SearchResponse);
}

protocol编译器将产生一个抽象接口SearchService以及一个相应的存根实现。存根将所有的调用指向RpcChannel,它是一 个抽象接口,必须在RPC系统中对该接口进行实现。如,可以实现RpcChannel以完成序列化消息并通过HTTP方式来发送到一个服务器。换句话说, 产生的存根提供了一个类型安全的接口用来完成基于protocolbuffer的RPC调用,而不是将你限定在一个特定的RPC的实现中。C++中的代码 如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using google::protobuf;
protobuf::RpcChannel* channel;
protobuf::RpcController* controller;
SearchService* service;
SearchRequest request;
SearchResponse response;
void DoSearch() {
// You provide classes MyRpcChannel and MyRpcController, which implement
// the abstract interfaces protobuf::RpcChannel and protobuf::RpcController.
channel = new MyRpcChannel(“somehost.example.com:1234”);
controller = new MyRpcController;
// The protocol compiler generates the SearchService class based on the
// definition given above.
service = new SearchService::Stub(channel);
// Set up the request.
request.set_query(“protocol buffers”);
// Execute the RPC.
service->Search(controller, request, response, protobuf::NewCallback(&Done));
}
void Done() {
delete service;
delete channel;
delete controller;
}

所有service类都必须实现Service接口,它提供了一种用来调用具体方法的方式,即在编译期不需要知道方法名及它的输入、输出类型。在服务器端,通过服务注册它可以被用来实现一个RPC Server。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using google::protobuf;
class ExampleSearchService : public SearchService {
public:
void Search(protobuf::RpcController* controller,
const SearchRequest* request,
SearchResponse* response,
protobuf::Closure* done) {
if (request->query() == “google”) {
response->add_result()->set_url(“http://www.google.com”);
} else if (request->query() == “protocol buffers”) {
response->add_result()->set_url(“http://protobuf.googlecode.com”);
}
done->Run();
}
};
int main() {
// You provide class MyRpcServer. It does not have to implement any
// particular interface; this is just an example.
MyRpcServer server;
protobuf::Service* service = new ExampleSearchService;
server.ExportOnPort(1234, service);
server.Run();
delete service;
return 0;
}

There are a number of ongoing third-party projects to develop RPC implementations for Protocol Buffers. For a list of links to projects we know about, see the third-party add-ons wiki page.

选项(Options)

在定义.proto文件时能够标注一系列的options。Options并不改变整个文件声明的含义,但却能够影响特定环境下处理方式。完整的可用选项可以在google/protobuf/descriptor.proto找到。

一些选项是文件级别的,意味着它可以作用于最外范围,不包含在任何消息内部、enum或服务定义中。一些选项是消息级别的,意味着它可以用在消息定 义的内部。当然有些选项可以作用在域、enum类型、enum值、服务类型及服务方法中。到目前为止,并没有一种有效的选项能作用于所有的类型。

如下就是一些常用的选择:

  • java_package (file option): 这个选项表明生成java类所在的包。如果在.proto文件中没有明确的声明java_package,就采用默认的包名。当然了,默认方式产生的 java包名并不是最好的方式,按照应用名称倒序方式进行排序的。如果不需要产生java代码,则该选项将不起任何作用。如:
1
option java_package = “com.example.foo”;
  • java_outer_classname (file option): 该选项表明想要生成Java类的名称。如果在.proto文件中没有明确的java_outer_classname定义,生成的class名称将会根据.proto文件的名称采用驼峰式的命名方式进行生成。如(foo_bar.proto生成的java类名为FooBar.java),如果不生成java代码,则该选项不起任何作用。如:
1
option java_outer_classname = “Ponycopter”;
  • optimize_for (fileoption): 可以被设置为 SPEED, CODE_SIZE,or 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_generic_services, java_generic_services, py_generic_services (file options): 在C++、java、python中protocol buffer编译器是否应该基于服务定义产生抽象服务代码。由于历史遗留问题,该值默认是true。但是自2.3.0版本以来,它被认为通过提供代码生成 器插件来对RPC实现更可取,而不是依赖于“抽象”服务。
1
2
3
4
5
6
7
// This file relies on plugins to generate service code.
option cc_generic_services = false;
option java_generic_services = false;
option py_generic_services = false;
  • message_set_wire_format (message option): 如果该值被设置为true,该消息将使用一种不同的二进制格式来与Google内部的MessageSet的老格式相兼容。对于Google外部的用户来说,该选项将不会被用到。如下所示:
1
2
3
4
5
6
7
message Foo {
option message_set_wire_format = true;
extensions 4 to max;
}
  • packed (field option): 如果该选项在一个整型基本类型上被设置为真,则采用更紧凑的编码方式。当然使用该值并不会对数值造成任何损失。在2.3.0版本之前,解析器将会忽略那些 非期望的包装值。因此,它不可能在不破坏现有框架的兼容性上而改变压缩格式。在2.3.0之后,这种改变将是安全的,解析器能够接受上述两种格式,但是在 处理protobuf老版本程序时,还是要多留意一下。
1
repeated int32 samples = 4 [packed=true];
  • deprecated (field option): 如果该选项被设置为true,表明该字段已经被弃用了,在新代码中不建议使用。在多数语言中,这并没有实际的含义。在java中,它将会变成一个 @Deprecated注释。也许在将来,其它基于语言声明的代码在生成时也会如此使用,当使用该字段时,编译器将自动报警。如:
1
optional int32 old_field = 6 [deprecated=true];

自定义选项

ProtocolBuffers允许自定义并使用选项。该功能应该属于一个高级特性,对于大部分人是用不到的。由于options是定在 google/protobuf/descriptor.proto中的,因此你可以在该文件中进行扩展,定义自己的选项。如:

1
2
3
4
5
6
7
8
9
import “google/protobuf/descriptor.proto”;
extend google.protobuf.MessageOptions {
optional string my_option = 51234;
}
message MyMessage {
option (my_option) = “Hello world!”;
}

在上述代码中,通过对MessageOptions进行扩展定义了一个新的消息级别的选项。当使用该选项时,选项的名称需要使用()包裹起来,以表明它是一个扩展。在C++代码中可以看出my_option是以如下方式被读取的。

1
string value = MyMessage::descriptor()->options().GetExtension(my_option);

在Java代码中的读取方式如下:

1
2
String value = MyProtoFile.MyMessage.getDescriptor().getOptions()
.getExtension(MyProtoFile.myOption);

在Python中:

1
2
value = my_proto_file_pb2.MyMessage.DESCRIPTOR.GetOptions()
.Extensions[my_proto_file_pb2.my_option]

正如上面的读取方式,定制选项对于Python并不支持。定制选项在protocol buffer语言中可用于任何结构。下面就是一些具体的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import “google/protobuf/descriptor.proto”;
extend google.protobuf.FileOptions {
optional string my_file_option = 50000;
}
extend google.protobuf.MessageOptions {
optional int32 my_message_option = 50001;
}
extend google.protobuf.FieldOptions {
optional float my_field_option = 50002;
}
extend google.protobuf.EnumOptions {
optional bool my_enum_option = 50003;
}
extend google.protobuf.EnumValueOptions {
optional uint32 my_enum_value_option = 50004;
}
extend google.protobuf.ServiceOptions {
optional MyEnum my_service_option = 50005;
}
extend google.protobuf.MethodOptions {
optional MyMessage my_method_option = 50006;
}
option (my_file_option) = “Hello world!”;
message MyMessage {
option (my_message_option) = 1234;
optional int32 foo = 1 [(my_field_option) = 4.5];
optional string bar = 2;
}
enum MyEnum {
option (my_enum_option) = true;
FOO = 1 [(my_enum_value_option) = 321];
BAR = 2;
}
message RequestType {}
message ResponseType {}
service MyService {
option (my_service_option) = FOO;
rpc MyMethod(RequestType) returns(ResponseType) {
// Note: my_method_option has type MyMessage. We can set each field
// within it using a separate “option” line.
option (my_method_option).foo = 567;
option (my_method_option).bar = “Some string”;
}
}

注:如果要在该选项定义之外使用一个自定义的选项,必须要由包名 + 选项名来定义该选项。如:

1
2
3
4
5
6
// foo.proto
import “google/protobuf/descriptor.proto”;
package foo;
extend google.protobuf.MessageOptions {
optional string my_option = 51234;
}
1
2
3
4
5
6
// bar.proto
import “foo.proto”;
package bar;
message MyMessage {
option (foo.my_option) = “Hello world!”;
}

最后一件事情需要注意:因为自定义选项是可扩展的,它必须象其它的域或扩展一样来定义标识号。正如上述示例,[50000-99999]已经被占 用,该范围内的值已经被内部所使用,当然了你可以在内部应用中随意使用。如果你想在一些公共应用中进行自定义选项,你必须确保它是全局唯一的。可以通过protobuf-global-extension-registry@google.com来获取全局唯一标识号。 只需提供你的项目名和项目网站. 通常你只需要一个扩展号。 你可以使用一个扩展号声明多个选项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
message FooOptions {
optional int32 opt1 = 1;
optional string opt2 = 2;
}
extend google.protobuf.FieldOptions {
optional FooOptions foo_options = 1234;
}
// usage:
message Bar {
optional int32 a = 1 [(foo_options).opt1 = 123, (foo_options).opt2 = “baz”];
// alternative aggregate syntax (uses TextFormat):
optional int32 b = 2 [(foo_options) = { opt1: 123 opt2: “baz” }];
}

生成访问类

可以通过定义好的.proto文件来生成Java、Python、C++代码,需要基于.proto文件运行protocol buffer编译器protoc。运行的命令如下所示:

1
protoc –proto_path=IMPORT_PATH –cpp_out=DST_DIR –java_out=DST_DIR –python_out=DST_DIR path/to/file.proto
  • IMPORT_PATH声明了一个.proto文件所在的具体目录。如果忽略该值,则使用当前目录。如果有多个目录则可以 对–proto_path 写多次,它们将会顺序的被访问并执行导入。-I=IMPORT_PATH是它的简化形式。
  • 当然也可以提供一个或多个输出路径:

你必须提供一个或多个.proto文件作为输入。多个.proto文件能够一次全部声明。虽然这些文件是相对于当前目录来命名的,每个文件必须在一个IMPORT_PATH中,只有如此编译器才可以决定它的标准名称。


中文翻译出处: http://www.open-open.com/home/space.php?uid=37924&do=blog&id=5873
原文: https://developers.google.com/protocol-buffers/docs/proto#generating

转载时加入了新增加的内容

交换机数据包转发方法

2011-06-13 10:48:27

 在过去,交换机使用下面的两种转发方法之一来进行网络端口间的数据交换:存储转发交换或直通交换。按下“交换机转发方法”按钮可显示这两种方法。不过,存储转发是当前型号的 Cisco Catalyst 交换机中唯一使用的转发方法。

存储转发交换
在存储转发交换中,当交换机收到帧时,它将数据存储在缓冲区中,直到收下完整的帧。存储过程期间,交换机分析帧以获得有关其目的地的信息。在此过程中,交换机还将使用以太网帧的循环冗余校验 (CRC) 帧尾部分来执行错误检查。
CRC 根据帧中的位数(即 1 位的数量),使用数学公式来确定收到的帧是否有错。在确认帧的完整性之后,帧将从对应的端口转发出去,并发往其目的地。当在帧中检测到错误时,交换机放弃该帧。放弃有错的帧可减少损坏的数据所耗用的带宽量。存储转发交换对于融合网络中的服务质量 (QoS) 分析是必需的,在融合网络中,必须对帧进行分类以划分流量优先级。例如,IP 语音数据流的优先级需要高于 Web 浏览流量。
直通交换
在直通交换中,交换机在收到数据时立即处理数据,即使传输尚未完成。交换机只缓冲帧的一部分,缓冲的量仅足以读取目的 MAC 地址,以便确定转发数据时应使用的端口。目的 MAC 地址位于帧中前导码后面的前 6 个字节。交换机在其交换表中查找目的 MAC 地址,确定外发接口端口,然后通过指定的交换机端口将帧转发到其目的地。交换机对该帧不执行任何错误检查。由于交换机不必等待完全缓冲整个帧,且不执行任何错误检查,因此直通交换比存储转发交换更快。但是,因为交换机不执行任何错误检查,因此它会在网络中转发损坏的帧。转发损坏的帧时,这些帧会耗用带宽。目的网卡最终将放弃损坏的帧。
直通交换有两种变体:
快速转发交换:快速转发交换提供最低程度的延时。快速转发交换在读取目的地址之后立即转发数据包。由于快速转发交换在收到整个数据包之前就开始转发,因此有时候中继数据包时会出错。这种情况并不经常发生,而且目的网络适配器在收到含错数据包时会将其丢弃。在快速转发模式下,延时是指从收到第一个位到传出第一个位之间的时间差。快速转发交换是典型的直通交换方法。
免分片 (fragment) 交换:在免分片交换中,交换机在转发之前存储帧的前 64 个字节。可以将免分片交换视为存储转发交换和直通交换之间的折衷。免分片交换只存储帧的前 64 个字节的原因是,大部分网络错误和冲突都发生在前 64 个字节。免分片交换在转发帧之前对帧的前 64 个字节执行小错误检查以确保没有发生过冲突,并且尝试通过这种方法来增强直通交换功能。免分片交换是存储转发交换的高延时和高完整性与直通交换的低延时和弱完整性之间的折衷。
某些交换机可配置为按端口执行直通交换,当达到用户定义的错误阈值时,这些端口自动切换为存储转发。当错误率低于该阈值时,端口自动恢复到直通切换。

本文转自 “07net01” 博客,请务必保留此出处http://07net01.blog.51cto.com/1192774/586950

在命令模式下编译Android NDK 的 *.so 库

为Android编译c++写的库文件(*.so文件)有很多种方式,实际上就是有很多种不同的工具可以选择:Eclipse+ADT、Android Studio,本质上还是调用android-ndk下的build-ndk(.bat)指令来编译arm版本的、针对Android操作系统的so文件。

我们在用Eclipse编译so文件时基本上就是建一个Android工程,为其定义一个编译配置(配置好的Android ndk路径、src路径、workspace路径等等),然后为其增加Application.mk和Android.mk两个文件,Eclipse就会自动调用Application.mk和Android.mk make文件去编译、生成so文件了。

我一直都在用Eclipse配置、编译so库,但说实话,Eclipse不但配置繁琐、容易出错,Eclipse对工程的管理也是非常不灵活的:我要是有多个不相关的so库需要编译,每次打开时都会全部加载(也许是我用的不好吧),编译时又得指定要编译的库单独编译;如果某个库的路径变了,Eclipse得重新配置,否则一大堆错误,真正要编译的项目却淹没其中。

既然Eclipse也不过是调用了Android-ndk的指令去结合Application.mk和Android.mk文件实现编译,为什么不能从命令模式直接调用android-ndk命令结合Application.mk和Android.mk文件来编译呢?这样每个项目各自不会纠缠在一起,干净利落,岂不美哉?

下面拿一个项目做个测试。

step 1: 建立一个目录,名称为:PerceptionNeuronPrj

step 2: 将项目的源码拷贝进去

这里是PerceptionNeuronSDK目录,可以看到此SDK的所有源代码都放在了PerceptionNeuronSDK目录下的src下(文件太多,打印此目录树时暂时移走了),对外的头文件直接放在PerceptionNeuronSDK目录下;

step 3: 加入依赖的第三方库或源码

与PerceptionNeuronSDK目录同级的是Eigen-3.2.2和InhouseLibs,即PerceptionNeuronSDK依赖的第三方库或源代码;

step 4: 创建用于编译PerceptionNeuronSDK的NDK配置文件

在PerceptionNeuronSDK目录下新建一个目录,这里命名为build_Android,同时在build_Android下新建jni目录、libs目录;

step 5: 为PerceptionNeuronSDK增加Application.mk及Android.mk配置文件

这里一定要小心,不能再Android.mk里通过脚本加载第三方的源码进去,而应该在PerceptionNeuronSDK的src目录中,在使用到诸如Eigen的地方通过#include引用!

附1:Application.mk文件内容

APP_ABI           := all
#APP_ABI          := armeabi armeabi-v7a x86

APP_OPTIM         := release
APP_PLATFORM      := android-8
#APP_BUILD_SCRIPT := Android.mk

# GNU STL implements most C++11 features. Use either gnustl_static or gnustl_shared
# Without this your C++ code will not be able to access headers like <thread>, <mutex>
#APP_STL      := stlport_static
#APP_CPPFLAGS := -std=c++11 -frtti -fexceptions 
APP_STL       := gnustl_static
APP_CPPFLAGS  := -std=gnu++11 -pthread -frtti -fexceptions -DNDEBUG #-NDEBUG -mfpu=neon -fomit-frame-pointer -DULM_BLOCKED -msse3 -mfpmath=sse

附2: Android.mk文件内容

# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE   := PNLib

# 锟斤拷锟斤拷锟皆硷拷锟斤拷源锟侥硷拷目录锟斤拷源锟侥硷拷锟斤拷缀锟斤拷
MY_FILES_PATH  :=  $(LOCAL_PATH)/../../src

#$(warning $(MY_FILES_PATH))

MY_FILES_SUFFIX := %.cpp %.c

# 递归遍历目录下的所有的文件
rwildcard=$(wildcard $1$2) $(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2))

# 获取相应的源文件
MY_ALL_FILES := $(foreach src_path,$(MY_FILES_PATH), $(call rwildcard,$(src_path),*.*) ) 
MY_ALL_FILES := $(MY_ALL_FILES:$(MY_CPP_PATH)/./%=$(MY_CPP_PATH)%)
MY_SRC_LIST  := $(filter $(MY_FILES_SUFFIX),$(MY_ALL_FILES)) 
MY_SRC_LIST  := $(MY_SRC_LIST:$(LOCAL_PATH)/%=%)

# 去除字串的重复单词
define uniq =
  $(eval seen :=)
  $(foreach _,$1,$(if $(filter $_,${seen}),,$(eval seen += $_)))
  ${seen}
endef

# 递归遍历获取所有目录
MY_ALL_DIRS := $(dir $(foreach src_path,$(MY_FILES_PATH), $(call rwildcard,$(src_path),*/) ) )
MY_ALL_DIRS := $(call uniq,$(MY_ALL_DIRS))

# 赋值给NDK编译系统
LOCAL_SRC_FILES  := $(MY_SRC_LIST)
LOCAL_C_INCLUDES := $(MY_ALL_DIRS)

# Add additional include directories
LOCAL_C_INCLUDES += $(LOCAL_PATH)/../../
#LOCAL_C_INCLUDES += $(LOCAL_PATH)/../../../Eigen-3.2.2   
#必须从Android.mk配置文件中拿掉对Eigen的直接包含,放到程序代码中用相对路径包含:
# #include "../../Eigen-3.2.2/Eigen"
# using namespace Eigen;

#$(warning $(LOCAL_SRC_FILES))
#$(warning $(LOCAL_C_INCLUDES))

# use log system in NDK
LOCAL_LDLIBS += -llog

include $(BUILD_SHARED_LIBRARY)

step 6: 为了方便使用,增加一个build.bat文件

在里面加入如下脚本:

ndk-build NDK_APPLICATION_MK=./Application.mk

pause

至此,所需文件及配置均已建立完毕,在windows中通过 ‘tree /f’ 指令可以看到其中的目录结构如下所示:

 

 

1

双击build.bat或通过命令行加载build.bat文件,即可编译出结果:

D:\PerceptionNeuronPrj\PerceptionNeuronSDK\build_Android\jni>build.bat

D:\PerceptionNeuronPrj\PerceptionNeuronSDK\build_Android\jni>ndk-build NDK_APPLI
CATION_MK=./Application.mk
[arm64-v8a] Compile++      : PNLib <= AntiJointCompensation.cpp
[arm64-v8a] Compile++      : PNLib <= BVHPlayerWrapper.cpp
[arm64-v8a] Compile++      : PNLib <= BoneMapping.cpp
[arm64-v8a] Compile++      : PNLib <= BoneMass.cpp
[arm64-v8a] Compile++      : PNLib <= BoneTable.cpp
[arm64-v8a] Compile++      : PNLib <= BvhBinaryOutputPacker.cpp
[arm64-v8a] Compile++      : PNLib <= BvhDataConvert.cpp
In file included from D:/PerceptionNeuronPrj/PerceptionNeuronSDK/build_Android/j
ni/../../src/./AvatarManagement/../PluginMngr/Interface/IPluginActionRecog.h:3:0
,
                 from D:/PerceptionNeuronPrj/PerceptionNeuronSDK/build_Android/j
ni/../../src/./AvatarManagement/Avatar.h:13,
                 from D:/PerceptionNeuronPrj/PerceptionNeuronSDK/build_Android/j
ni/../../src/BvhDataConvert.cpp:9:
D:/PerceptionNeuronPrj/PerceptionNeuronSDK/build_Android/jni/../../src/./AvatarM
anagement/../PluginMngr/Interface/IPluginObject.h:55:69: warning: 'visibility' a
ttribute ignored [-Wattributes]
         PNLIB_PLUGIN_EXPORT typedef IPluginObject* (*GetPluginFunc)();
....................
.....
.                                                                 ^

 

编译速度非常慢,看项目大小和编译的架构多少,十几分钟几十分钟不等。

编译完成后,so文件自动放到libs下的相应架构目录下,目录结构如下:

QQ截图20160311143749

 

QQ截图20160311143406

step 7: 清除及重新编译

由于ndk-build.bat编译完项目后会缓存所有中间文件(*.obj及其他中间文件),再次运行ndk-build.bat只是简单的从缓冲拷贝so文件,所以需要增加一个build clean,以便清除缓冲:

附3:build_clean.bat

#自动到NDK_PROJECT_PATH目录下找jni目录,利用其中的Android.mk清除上次编译的结果
ndk-build NDK_PROJECT_PATH=../ clean

pause

 

从C++的dll中Callback到C#返回数组:只取到了第一个元素

假设C/C++的dll中定义回调函数:

// Received data callback
typedef void(__stdcall *ReceivedDataCallback)(SerialPortRef sp, unsigned char* data, int len);


/*********************************************************
* Library API definitions *
/*********************************************************/
#ifdef __cplusplus
extern "C" {
#endif

 // Register handl to receive data from a SerialPort.
 SERIALPORTRW_API void RegisterReceivedDataCallback(SerialPortRef sp, ReceivedDataCallback handle);

//......................

C#中包含dll,通过包装dll的函数和回调函数,以便dll中收到数据时回调到C#:

// Wrapper of library by C#
namespace PluginImport
{
	#region delegates
    public delegate void ReceivedDataCallback(IntPtr serialPortRef, [MarshalAs(UnmanagedType.LPArray)]byte[] data, int len);
	#endregion
		
	#region API Wraper
	public class SerialPortDevice
	{
		// Register handl to receive data from a SerialPort.
		[DllImport("SerialPortDevice", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
        public static extern void RegisterReceivedDataCallback(UIntPtr sp, ReceivedDataCallback handle);

使用此DLL包装的API:

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            ReceivedDataCallback = (IntPtr serialPortRef, byte[] data, int len) =>
            {
                byte[] localData = data;

                this.Dispatcher.Invoke(new Action(() => { tbLog.Text += ByteUtils.bytesToHexString(localData, 0, len); }));
            };

            SerialPortStatusChanged = (IntPtr serialPortRef, SerialPortStatus status, string msg) =>
            {
                this.Dispatcher.Invoke(new Action(() => { tbLog.Text += "\r\n" + msg; }));
            };


            spRef = SerialPortDevice.CreateSerialPort();
            if (spRef != UIntPtr.Zero)
            {
                SerialPortDevice.RegisterReceivedDataCallback(spRef, ReceivedDataCallback);
                SerialPortDevice.RegisterSerialPortStatusChangedCallback(spRef, SerialPortStatusChanged);
            }

发现上面红色部分的data的长度总是一个字节。

原因:

marshaller不知道data的长度到底是多少,只是简单的按照数据类型返回了数组中的第一个元素,根本没有办法marshal。

正确的做法是将data定义为指针,然后通过拷贝的方法将数据取到C#端:

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            ReceivedDataCallback = (IntPtr serialPortRef, IntPtr data, int len) =>
            {
                byte[] localData = new byte[len];
                Marshal.Copy(data, localData, 0, len);
                
                string strHex = ByteUtils.bytesToHexString(localData, 0, len);

                this.Dispatcher.Invoke(new Action(() => { tbLog.Text += "\r\n" + strHex; }));
            };

            SerialPortStatusChanged = (IntPtr serialPortRef, SerialPortStatus status, string msg) =>
            {
                this.Dispatcher.Invoke(new Action(() => { tbLog.Text += "\r\n" + msg; }));
            };


            spRef = SerialPortDevice.CreateSerialPort();
            if (spRef != UIntPtr.Zero)
            {
                SerialPortDevice.RegisterReceivedDataCallback(spRef, ReceivedDataCallback);
                SerialPortDevice.RegisterSerialPortStatusChangedCallback(spRef, SerialPortStatusChanged);
            }
        }

当然,在这之前要在C#端的回调函数定义中的数据也要改成指针:

public delegate void ReceivedDataCallback(IntPtr serialPortRef, IntPtr data, int len);

 

Apple ios8 beta download

WWDC2014于2014年6月2日(北京时间6月3日凌晨)发布了IOS8 bet版,以及其他一些重量级内容!
这里只提供IOS8 beta下载地址,方便各位发烧友尝鲜 :)

 

官 方 下 载 地 址


iPad  Air

iPad air(Model A1474)WIFI
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_air_model_a1474__12a4265u.zip

iPad Air(model A1475)CDMA
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_air_model_a1475__12a4265u.zip

iPad Air(model A1476)GSM
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_air_model_a1476__12a4265u.zip

iPad  mini  Retina

iPad mini Retina(model A1489)WIFI
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_mini_model_a1489__12a4265u.zip

iPad mini Retina(model A1490)CDMA
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_mini_model_a1490__12a4265u.zip

iPad mini Retina(model A1491)GSM
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_mini_model_a1491__12a4265u.zip

iPad 4

iPad 4(generation model A1458)WIFI
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_4th_generation_model_a1458__12a4265u.zip

iPad 4(generation model A1459)GSM
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_4th_generation_model_a1459__12a4265u.zip

iPad 4(generation model A1460)CDMA
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_4th_generation_model_a1460__12a4265u.zip

iPad mini

iPad mini(model A1432)WIFI
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_mini_model_a1432__12a4265u.zip

iPad mini(model A1454)GSM
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_mini_model_a1454__12a4265u.zip

iPad mini(model A1455)CDMA
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_mini_model_a1455__12a4265u.zip

iPad 3

iPad WiFi(3rd generation)WIFI
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_wifi_3rd_generation__12a4265u.zip

iPad  3 WiFi+cellular(model for ATT)GSM
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_wifi__cellular_model_for_att__12a4265u.zip

iPad 3 WiFi+cellular(model for Verizon)CDMA
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_wifi__cellular_model_for_verizon__12a4265u.zip

iPad 2

iPad 2 WiFi(Rev A)
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_2_wifi_rev_a__12a4265u.zip

iPad 2  WiFi
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_2_wifi__12a4265u.zip

iPad 2 WiFi + 3G(GSM)
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_2_wifi__3g_gsm__12a4265u.zip

iPad 2 WiFi + 3G(CDMA)
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_2_wifi__3g_cdma__12a4265u.zip

iPhone 5S

iPhone 5S(model A1453,A1533)CDMA
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__iphone_5s_model_a1453_a1533__12a4265u.zip

iPhone 5S(model A1457,A1518,A1526,A1529)GSM
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__iphone_5s_model_a1457_a1518_a1528_a1530__12a4265u.zip

iPhone 5C

iPhone 5C(model A1456,A1532)GSM
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__iphone_5c_model_a1456_a1532__12a4265u.zip

iPhone 5C(model A1507,A1516,A1526,A1529)CDMA
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__iphone_5c_model_a1507_a1516_a1526_a1529__12a4265u.zip

iPhone 5

iPhone 5(model A1428)GSM
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__iphone_5_model_a1428__12a4265u.zip

iPhone 5(model A1429)CDMA
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__iphone_5_model_a1429__12a4265u.zip

iPhone 4S
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__iphone_4s__12a4265u.zip

iPod touch 5
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipod_touch_5th_generation__12a4265u.zip

 

vs2010 error lnk2001无法解析的外部符号

一个项目需要引用另一个静态库,而这个静态库中有个全局变量,这样的全局变量只有静态连接性能,编译时不报错,链接时则会报LNK2001错误。

解决办法也很怪异,你需要再次在主程序需要用到的地方申明一个同名全局变量,这样则能消除链接错误!

Settings g_Settings;

 

============================================

这里有一篇文章详细讲解了LNK2001错误:

 

vs2010 error LNK2001: 无法解析的外部符号

        学习VC++时经常会遇到链接错误LNK2001,该错误非常讨厌,因为对于 编程者来说,最好改的错误莫过于编译错误,而一般说来发生连接错误时, 编译都已通过。产生连接错误的原因非常多,尤其LNK2001错误,常常使人不 明其所以然。如果不深入地学习和理解VC++,要想改正连接错误LNK2001非 常困难。
初学者在学习VC++的过程中,遇到的LNK2001错误的错误消息主要为: unresolved   external   symbol   “symbol”(不确定的外部“符号”)。 如果连接程序不能在所有的库和目标文件内找到所引用的函数、变量或 标签,将产生此错误消息。一般来说,发生错误的原因有两个:一是所引用 的函数、变量不存在、拼写不正确或者使用错误;其次可能使用了不同版本 的连接库。
以下是可能产生LNK2001错误的原因:
一.由于编码错误导致的LNK2001。
1.不相匹配的程序代码或模块定义(.DEF)文件能导致LNK2001。例如,   如果在C++   源文件内声明了一变量“var1”,却试图在另一文件内以变量 “VAR1”访问该变量,将发生该错误。
2.如果使用的内联函数 是在.CPP文件内定义的,而不是在头文件 内定义将导致LNK2001错误。
3.调用函数时如果所用的参数类型同函数声明时的类型不符将会产生 LNK2001。
4.试图从基类 的构造函数 或析构函数 中调用虚拟函数时将会导致LNK2001。
5.要注意函数和变量的可公用性,只有全局变量 、函数是可公用的。
静态函数 和静态变量 具有相同的使用范围限制。当试图从文件外部访问 任何没有在该文件内声明的静态变量时将导致编译错误或LNK2001。 函数内声明的变量(局部变量)   只能在该函数的范围内使用。
C++   的全局常量只有静态连接性能。这不同于C,如果试图在C++的 多个文件内使用全局变量也会产生LNK2001错误。一种解决的方法是需要时在 头文件中加入该常量的初始化代码,并在.CPP文件中包含该头文件;另一种 方法是使用时给该变量赋以常数 。
二.由于编译和链接的设置而造成的LNK2001
1.如果编译时使用的是/NOD(/NODEFAULTLIB)选项,程序所需要的运行 库和MFC库在连接时由编译器 写入目标文件模块,   但除非在文件中明确包含 这些库名,否则这些库不会被链接进工程文件。在这种情况下使用/NOD将导 致错误LNK2001。
2.如果没有为wWinMainCRTStartup设定程序入口,在使用Unicode和MFC 时将得到“unresolved   external   on   _WinMain@16”的LNK2001错误信息。
3.使用/MD选项编译时,既然所有的运行库都被保留在动态链接库之内, 源文件中对“func”的引用,在目标文件里即对“__imp__func”   的引用。 如果试图使用静态库LIBC.LIB或LIBCMT.LIB进行连接,将在__imp__func上发 生LNK2001;如果不使用/MD选项编译,在使用MSVCxx.LIB连接时也会发生LNK2001。
4.使用/ML选项编译时,如用LIBCMT.LIB链接会在_errno上发生LNK2001。
5.当编译调试版的应用程序时,如果采用发行版模态库进行连接也会产 生LNK2001;同样,使用调试版模态库连接发行版应用程序时也会产生相同的 问题。
6.不同版本的库和编译器的混合使用也能产生问题,因为新版的库里可 能包含早先的版本没有的符号和说明。
7.在不同的模块使用内联和非内联的编译选项 能够导致LNK2001。如果 创建C++库时打开了函数内联(/Ob1或/Ob2),但是在描述该函数的相应头 文件里却关闭了函数内联(没有inline关键字 ),这时将得到该错误信息。 为避免该问题的发生,应该在相应的头文件中用inline关键字标志内联函数。
8.不正确的/SUBSYSTEM或/ENTRY设置也能导致LNK2001。
其实,产生LNK2001的原因还有很多,以上的原因只是一部分而已,对初 学者来说这些就够理解一阵子了。但是,分析错误原因的目的是为了避免错 误的发生。LNK2001错误虽然比较困难,但是只要注意到了上述问题,还是能 够避免和予以解决的。

ios6上读取通讯录的方法

ios升级到6.0以后的变化之一就是增加了通讯录的隐私设置,防止程序未通过许可就访问用户的通讯录。因此在app开发中,读取通讯录的方法也有了一些变化。上代码:

-(BOOL)isABAddressBookCreateWithOptionsAvailable {
return &ABAddressBookCreateWithOptions != NULL;
}

-(void)loadContacts {
ABAddressBookRef addressBook;
if ([self isABAddressBookCreateWithOptionsAvailable]) {

// ios6以上读取方法
CFErrorRef error = nil;
addressBook = ABAddressBookCreateWithOptions(NULL,&error);
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
// callback can occur in background, address book must be accessed on thread it was created on
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {

} else if (!granted) {

} else {
// access granted
AddressBookUpdated(addressBook, nil, (__bridge void *)(self));
CFRelease(addressBook);
}
});
});
} else {
// iOS 4/5
addressBook = ABAddressBookCreate();
AddressBookUpdated(addressBook, NULL, (__bridge void *)(self));
CFRelease(addressBook);
}
}

void AddressBookUpdated(ABAddressBookRef addressBook, CFDictionaryRef info, void *context) {
ABAddressBookRevert(addressBook);
CFArrayRef people = ABAddressBookCopyArrayOfAllPeople(addressBook);
};

 

本文转自:http://bluevt.org/?p=41

用xcode 5 开发访问IOS 7上面的通讯录问题

NSMutableArray *addressBookTemp = [NSMutableArray array];ABAddressBookRef addressBooks = ABAddressBookCreate();
CFArrayRef allPeople = ABAddressBookCopyArrayOfAllPeople(addressBooks);

CFIndex nPeople = ABAddressBookGetPersonCount(addressBooks);

 

 

这段代码在IOS6是可以读取通讯录的,但在IOS7下面就不行,程序没报错,调试时候发现返回的都是nil,有木有朋友遇到这个问题?

找到原因了,原来IOS7需要隐私验证,调用通讯录里面API需要以下代码作为权限认证:

 __block BOOL accessGranted = NO;
 if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined)
{
  ABAddressBookRequestAccessWithCompletion(addressBooks, ^(bool granted, CFErrorRef error)
 {
   // First time access has been granted, add the contact 
   accessGranted = granted; });
 }
 else if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized)
 {
  // The user has previously given access, add the contact 
  accessGranted = YES;
 }
 else
 {
 // The user has previously denied access
 // Send an alert telling user to change privacy setting in settings app
 }

不过IOS6好像没有,希望对其他朋友有帮助 – sfdux 2013-09-24 11:12 回复

iBeacon技术,苹果迟迟不肯支持NFC原来是为了另立山头?

 121911tybphw6o0m0wzkib
虎嗅注:苹果发布会真正的闪光点常常被“杂音”淹没——9月10日的发布会上,iPhone 5C的五种颜色和高昂定价让很多人忽略A7处理器这款第一次被运用在智能手机上的64位ARM架构处理器,领先于高通、Nvidia和三星;而在今年6月的WWDC上,iOS 7的“扁平化”风格也使人们忽略了其中的一些重要的新特性,比如iBeacon技术——到现在为止还有很多人不知道这是什么。
最近,国外科技媒体Gigaom上有一篇文章,其中详细介绍了苹果用以对抗NFC(近场通讯)的iBeacon技术,并认为该技术将是苹果未来重点发展对象之一。本文由腾讯数码编译,大意如下:
在今年六月举行的WWDC上,作为iOS 7中最重要的新特性之一,苹果正式对外发布了iBeacon。同时,一家名为Estimote的公司日前也宣布将推出支持iBeacon技术的基站。
Estimote为什么要支持iBeacon技术?因为iBeacon技术不仅为诸如室内地图等新兴应用提供了发展平台,能够让互联网更容易地融入我们的日常生活,甚至还有可能击败目前最有希望成为无线支付方式的NFC技术。
iBeacon技术和低耗蓝牙技术
通过使用低功耗蓝牙技术(Bluetooth Low Energy,也就是通常所说的Bluetooth 4.0或者Bluetooth Smart),iBeacon基站可以创建一个信号区域,当设备进入该区域时,相应的应用程序便会提示用户是否需要接入这个信号网络。通过能够放置在任何物体中的小型无线传感器和低功耗蓝牙技术,用户便能使用iPhone来传输数据。
举个例子,假如你带着一部iPhone 5s(运行iOS 7并支持iBeacon)走入一家大型商场的店铺,同时这也意味着你已经进入了这家店铺的iBeacon信号区域。然后iBeacon基站便可以向你的iPhone传输各种信息,比如优惠券或者是店内导航信息,甚至当你走到某些柜台前面时,iBeacon还会提供个性化的商品推荐信息。也就是说在iBeacon基站的信息区域内,用户通过手中的智能手机便能够获取个性化的微型位置信息以及通知。
iBeacon不仅能够为用户提供他们所需要的信息,甚至和NFC技术一样,用户也能通过iBeacon来完成支付。除此之外,每个iBeacon基站内置有加速度计、闪存、ARM架构处理器以及蓝牙模块,而一小块纽扣电池便能为一个iBeacon基站提供长达两年的续航时间。
低功耗蓝牙技术的最大特点便在于低功耗,从而能使设备拥有更长的续航时间。不过低功耗蓝牙技术仅支持较低的文件传输速率,因此可以用于可穿戴式智能设备之间的信息传送,但却不能完成像传输音频这样的任务。从目前的状况来看,只有Android 4.3才支持低功耗蓝牙技术,因此这就是为什么老款Android机型不支持某些导航应用的原因。
传输范围广,成本低廉的iBeacon技术
从技术角度讲,更广的信息传输范围是iBeacon相比于NFC最大的优势。对于如今的NFC技术,虽然NFC标签的价格要比NFC芯片便宜得多,但是NFC标签的理论有效距离只有20cm,而最理想的使用距离只有4cm,范围可谓非常之小。
同时,手机等移动设备必须搭载NFC芯片才能支持NFC通讯;而iBeacon基站虽然要比NFC芯片的价格稍微昂贵一些,但是iBeacon的信息传输距离可达50m左右,而且如今几乎每部手机都支持蓝牙技术,但却不一定配备有NFC芯片。
让我们回到前面的那个例子中去,假设一家店铺的面积为16000平方米左右,而如果每个iBeacon基站的最远传输距离为50m的话,那么可以覆盖的面积大约在2500平方米左右,因此这家店铺只需要购买7个iBeacon基站便能够满足要求。
而从Estimote公司给出的价格来看,3个iBeacon基站的预购价格为99美元(约合人民币610元)。需要注意的是,虽然Estimote公司推出的iBeacon基站的最远传输距离为50m,但他们推荐在10m范围内的使用效果最好。如果按照Estimote公司给出的建议的话,每个iBeacon基站的覆盖范围是100平方米左右,那么总共需花费约5000美元左右。
如果使用NFC标签的话,按每个标签10美分(约合人民币0.61元)来计算的话,10万件商品就需要花费10000美元,是使用iBeacon基站的两倍。
或许将诞生一系列新应用
从iBeacon的特点来说,该技术很可能会促进一系列室内地图应用的诞生。由于在室内时,有各种障碍物的阻挡,因此GPS信号非常微弱,因此无法实现导航。这也是为什么虽然谷歌现在已经建立了部分地区的室内地图,但是还无法实现室内导航的原因。而这也正是iBeacon技术的优势所在。
借助智能手机,用户可以连接到最近的iBeacon基站,从而获得该基站的GPS位置信息,从而知道目前所处的地点。当用户进入或离开某个iBeacon基站的通信范围时都会收到相应的通知信息,从而实现导航的目的。
对抗NFC,低功耗蓝牙技术才是将网络带进现实的最好方法?
每次新款iPhone发布之前,外界都有人预测新款iPhone将会搭载NFC技术,不过事实证明这都只是外界一厢情愿的想法罢了。在iOS 7中,苹果增加了AirDrop无线分享功能,而苹果公司软件工程高级副总裁Craig Federighi认为“没有必要为了分享文件而走到某个人的面前,然后仅仅是为了触碰一下手机”,而这也是苹果对于NFC技术的看法。
为了让网络与现实世界结合到一起,各种传感器的起到了非常重要的作用。对于一个传感器来说,尺寸、价格、网络连接性都是很关键的指标。如果能够远程遥控各种传感器的话,那么传感器的用途可以说是无穷无尽的。比如在回家的路上开启家中的电源,根据天气控制冰箱的温度,通过手机控制房间的灯光等等。Estimote表示他们正在努力改进iBeacon基站以进一步缩减价格和体积,从而使iBeacon更加实用。
既然苹果能够通过使用低功耗蓝牙技术的iBeacon来解决数据的短距离传输问题,那么又有什么理由让人们互相触碰手机呢?而且相比于NFC技术来说,蓝牙能够提供NFC无法实现的功能。

四元数与欧拉角之间的转换

在3D图形学中,最常用的旋转表示方法便是四元数和欧拉角,比起矩阵来具有节省存储空间和方便插值的优点。本文主要归纳了两种表达方式的转换,计算公式采用3D笛卡尔坐标系:

121309_1044_1

图1 3D Cartesian coordinate System (from wikipedia)

定义121309_1044_2分别为绕Z轴、Y轴、X轴的旋转角度,如果用Tait-Bryan angle表示,分别为Yaw、Pitch、Roll。

121309_1044_3

图2 Tait-Bryan angles (from wikipedia)

一、四元数的定义

121309_1044_4

通过旋转轴和绕该轴旋转的角度可以构造一个四元数:

121309_1044_5

其中121309_1044_6是绕旋转轴旋转的角度,121309_1044_7为旋转轴在x,y,z方向的分量(由此确定了旋转轴)。

二、欧拉角到四元数的转换

121309_1044_8

三、四元数到欧拉角的转换

121309_1044_9

       arctanarcsin的结果是121309_1044_10,这并不能覆盖所有朝向(对于121309_1044_11121309_1044_12的取值范围已经满足),因此需要用atan2来代替arctan

121309_1044_13

四、在其他坐标系下使用

在其他坐标系下,需根据坐标轴的定义,调整一下以上公式。如在Direct3D中,笛卡尔坐标系的X轴变为Z轴,Y轴变为X轴,Z轴变为Y轴(无需考虑方向)。

121309_1044_14

五、示例代码

http://www.cppblog.com/Files/heath/Euler2Quaternion.rar
Demo渲染两个模型,左边使用欧拉角,右边使用四元数,方向键Up、Left、Right旋转模型。

参考文献:
[1] http://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles
[2] Ken Shoemake, Animating Rotation with Quaternion Curves, 1985

 

本文转自:http://www.cppblog.com/heath/archive/2009/12/13/103127.html

 

四元数及其平滑处理

最近做camera 的 AI,需要对四元数,欧拉角等要有一定的了解,把前面学习的整理了一下:

1。四元数的优势

 三维空间的旋转完全可以由4元数来胜任。传统意义上需要3×3矩阵来进行向量的旋转(4×4矩阵的第四列表示平移)。所以四元数更节省空间,运算速度更快。既然四元数能方便的表示3D旋转,那么对他们进行插值就能产生平滑的旋转效果。

劣势可能是比较抽象,不大好理解。而且据说顶点变换还是矩阵效率更高(涉及到平移)。

2。四元数的物理意义

Q( x, y, z, w)来表示向量 绕轴 A(ax, ay,az) 旋转alpha

则: x = sin(alpha/2)*ax;

y =   sin(alpha/2)*ay;

z =  sin(alpha/2)*az;

w = cos(alpha/2);

 

3。四元素的数学意义

我们知道 复数的表示为 a+bi; 其中i*i = -1;

四元数 q = w + xi + yi +zi;

复数的运算法则为四元数的数学运算提供了规则基础。比如说乘法,加法等。

4。四元数和矩阵间的转换

QX =0; QY=1;QZ=2;QW=3;

void quat_ConvertFromMatrix(float *pQuat, const float mat[4][4])
{
 float diag, s;
 int i, j, k;

 diag = mat[0][0] + mat[1][1] + mat[2][2];

 if(diag < -0.999f )
 {
  i = QX;
  if( mat[QY][QY] > mat[QX][QX] )
   i = QY;
  if( mat[QZ][QZ] > mat[i][i] )
   i = QZ;

  j = g_QNext[i];
  k = g_QNext[j];

  s = ltsqrtf( mat[i][i] - ( mat[j][j] + mat[k][k] ) + /*mat[3][3]*/ 1.0f );

  pQuat[i] = s * 0.5f;
  s = 0.5f / s;
  pQuat[QW] = ( mat[k][j] - mat[j][k] ) * s;
  pQuat[j] = ( mat[j][i] + mat[i][j] ) * s;
  pQuat[k] = ( mat[k][i] + mat[i][k] ) * s;
  return;
 }

 s = ltsqrtf( diag + /*mat[3][3]*/ 1.0f );

 pQuat[3] = s * 0.5f;
 s = 0.5f / s;

 pQuat[0] = (mat[2][1] - mat[1][2]) * s;
 pQuat[1] = (mat[0][2] - mat[2][0]) * s;
 pQuat[2] = (mat[1][0] - mat[0][1]) * s;
}

void quat_ConvertToMatrix(const float *pQuat, float mat[4][4])
{
 float s, xs, ys, zs, wx, wy, wz, xx, xy, xz, yy, yz, zz;

/*!
  get the values for matrix calcuation.
*/
 s = 2.0f / ((pQuat[0] * pQuat[0]) + (pQuat[1] * pQuat[1]) + 
  (pQuat[2] * pQuat[2]) + (pQuat[3] * pQuat[3]));

 xs = pQuat[0] * s;
 ys = pQuat[1] * s;
 zs = pQuat[2] * s;

 wx = pQuat[3] * xs;
 wy = pQuat[3] * ys;
 wz = pQuat[3] * zs;

 xx = pQuat[0] * xs;
 xy = pQuat[0] * ys;
 xz = pQuat[0] * zs;

 yy = pQuat[1] * ys;
 yz = pQuat[1] * zs;

 zz = pQuat[2] * zs;

/*!
  Fill in matrix

*/
 mat[0][0] = 1.0f - (yy + zz);
 mat[0][1] = xy - wz;
 mat[0][2] = xz + wy;

 mat[1][0] = xy + wz;
 mat[1][1] = 1.0f - (xx + zz);
 mat[1][2] = yz - wx;

 mat[2][0] = xz - wy;
 mat[2][1] = yz + wx;
 mat[2][2] = 1.0f - (xx + yy);

 mat[0][3] = mat[1][3] = mat[2][3] = mat[3][0] = mat[3][1] = mat[3][2] = 0.0f;
 mat[3][3] = 1.0f;
}

 

具体推倒过程可以参考相关文献。

5。四元数插值

有很多插值方法,比如线性,球形,样条插值等;

lerp (t;,q0,q1) = (1-t)q0 + tq1 ;//快速,但动画不平滑,需要归一化 / ||(1-t)q0 + tq1||;

slerp( t;, q0,q1) = [q0 *sin(thata(1-thata)) + q1sin(thata*t)] / sin(thata); //平滑,归一化;

thata 为q0 q1夹角。q0 dot q1 = cos(thata);

相关代码:

void quat_Slerp(float *pDest, const float *pQ1, const float *pQ2, float t)
{
 float rot1q[4];
 float omega, cosom, oosinom;
 float scalerot0, scalerot1;

/*!
  Calculate the cosine

*/
 cosom = pQ1[0]*pQ2[0] + pQ1[1]*pQ2[1] + pQ1[2]*pQ2[2] + pQ1[3]*pQ2[3];

/*!
  adjust signs if necessary

*/
 if(cosom < 0.0f)
 {
  cosom = -cosom;
  rot1q[0] = -pQ2[0];
  rot1q[1] = -pQ2[1];
  rot1q[2] = -pQ2[2];
  rot1q[3] = -pQ2[3];
 }
 else  
 {
  rot1q[0] = pQ2[0];
  rot1q[1] = pQ2[1];
  rot1q[2] = pQ2[2];
  rot1q[3] = pQ2[3];
 }

/*!
  calculate interpolating coeffs

*/
 if ( (1.0f - cosom) > 0.0001f ) 
 { 
/*!
   standard case

*/
  omega   = ltacosf(cosom);
  oosinom = 1.0f / ltsinf(omega);
  scalerot0 = ltsinf((1.f - t) * omega) * oosinom;
  scalerot1 = ltsinf(t * omega) * oosinom;
 }
 else
 { 
/*!
   rot0 and rot1 very close - just do linear interp.

*/
  scalerot0 = 1.0f - t;
  scalerot1 = t;
 }

 //! build the new quarternion
 pDest[0] = (scalerot0 * pQ1[0] + scalerot1 * rot1q[0]);
 pDest[1] = (scalerot0 * pQ1[1] + scalerot1 * rot1q[1]);
 pDest[2] = (scalerot0 * pQ1[2] + scalerot1 * rot1q[2]);
 pDest[3] = (scalerot0 * pQ1[3] + scalerot1 * rot1q[3]);
}

 

参考文献:

游戏编程精粹

 

本文转自:http://blog.csdn.net/hziee_/article/details/1630116

 

 

 

通过CreateFile操作串口,调用写入(WriteFile)时被挂起(hanges)

Windows中C++写的串口操作类,用CreateFile打开串口:

	HANDLE hSerialPort = ::CreateFile	(	szPortName, 
											GENERIC_READ| GENERIC_WRITE, 
											0, 
											NULL, 
											OPEN_EXISTING, 
											FILE_ATTRIBUTE_NORMAL,
											NULL);

 

设置参数:

		DCB dcb={0};
		if(::GetCommState(hSerialPort, &dcb))
		{
			dcb.BaudRate	= wBaundRate;
 			dcb.ByteSize	= 8;
 			dcb.Parity		= NOPARITY;
 			dcb.StopBits	= ONESTOPBIT;
			bRet = ::SetCommState(hSerialPort, &dcb);
		}

设置超时:

 

		if(::SetupComm(hSerialPort, CT_READ_CACHE, CT_WRITE_CACHE))
		{
			//
			COMMTIMEOUTS cto={0};
			cto.ReadIntervalTimeout			= 0;
			cto.ReadTotalTimeoutConstant	= 0;
			cto.ReadTotalTimeoutMultiplier	= 3000;
			cto.WriteTotalTimeoutConstant	= 0;
			cto.WriteTotalTimeoutMultiplier	= 0;
			bRet =  ::SetCommTimeouts(hSerialPort, &cto);
		}

清空串口并开启读取线程:

	if(bRet&&::PurgeComm(hSerialPort,PURGE_TXCLEAR|PURGE_RXCLEAR))
	{
		if(NULL == m_pbBuffer)
		{
			m_cbBuffer	= CT_READ_CACHE;
			bRet = (NULL!=(m_pbBuffer=(LPBYTE)::malloc(m_cbBuffer*sizeof(BYTE))));
		}
		if(bRet)
		{
			m_bStarted = TRUE;
			bRet = (INVALID_HANDLE_VALUE!=(m_hReadThread = ::CreateThread(NULL, 0, LPTHREAD_START_ROUTINE (_SerialPortProc), this, 0, NULL)));
		}
	}

读取线程:

DWORD SerialPortHelper::SerialPortProc	(	void	)
{
	DWORD dwError = 0;
	COMSTAT cs={0};

	SetCommMask (m_hSerialPort, EV_RXCHAR | EV_CTS | EV_DSR | EV_RLSD | EV_RING);
	DWORD dwCommStatus = 0;
	while (m_bStarted)
	{
		WaitCommEvent(m_hSerialPort, &dwCommStatus, 0);

		SetCommMask (m_hSerialPort, EV_RXCHAR);

		if(dwCommStatus & EV_RXCHAR)
		{
			if(::ClearCommError(m_hSerialPort, &dwError, &cs))
			{
				if(cs.cbInQue>0)
				{
					ReceiveData(cs.cbInQue);
				}
			}
		}
	}
	return 0;
}

这种情况下,如果调用WriteFile就永远无法返回,WriteFile被挂起了。

实际上原因在于读取线程中,如果串口没有任何数据到达,则串口被WaitCommEvent()挂起,所以无法写入数据,导致WriteFile()函数被挂起。

 

Macbook Pro换硬盘后的数据迁移

刚刚买了一块1TB 7200RPS的硬盘和一条8G内存条,打算把我的Macbook Pro升级一下。

原装的是一块320G HTC的硬盘,因为要装Windows、Linux等多种虚拟机,硬盘空间马上就告急了;又因为要开虚拟机,如果内存太小,多个系统一起跑显得很局促,于是搞了个1T的硬盘和一条8G 金士顿内存条。

买一条8G内存条是因为,现有的是两条2G内存条,如果买两条4G的差不多380rmb,把两条2G的全替了,可是一条8G的也就不到400rmb,跟两条4G价格差别不大,既然如此,何不买一条8G的,还可以保留一条2G,一共10G,而且万一以后觉得内存还是不够用,再搞一条8G的来组成16G,岂不是爽呆了?至于有人说即便都是DDR3 1333的内存条,容量不一样组不成双通道,首先比较质疑这种说法,即便如此,考虑到总的容量比较大,向后的兼容性也更好,认了!

至于拆机换硬盘和内存条的文章多如牛毛,就不再累赘了,看到一条保留原系统的迁移方法值得保留,便于稍后查阅:

过年了,拿奖金到村里买了块160G日立SATA盘准备替换我的mbp600原配的80G硬盘,另外再买了个希捷贴牌的远古代工SATA硬盘盒(USB接口),这个盒子跟远古的品质一样的,不过价格便宜30块钱,还是非常值的,看图片。
(比较遗憾,原帖图片看不到鸟...)
关于硬盘的拆机更换,可以参考坛子里南方石城同志的帖子。但关于两块硬盘的系统和数据迁移问题,我没找到合适的。因为我原硬盘用bootcamp装了windows,实际上有2个分区,mac和win,这两个分区上都有非常重要的工作文件,而且两个分区的系统都运行得非常稳定,因此我不想在新硬盘上完全重装MAC和WIN的操作系统,再迁移数据。
昨天晚上折腾了好久,发现其实用系统的磁盘工具的恢复功能,能非常简单地解决问题。实际上这个功能是将分区做了完整镜像,并且镜像后的目标盘是可以启动的,而且可以从USB启动MAC和WIN系统!
过程是这样的:
首先,当然把新买的160G硬盘装进外置硬盘盒,使用USB连接MBP,系统识别后,启动磁盘工具,进行分区:
(比较遗憾,原帖图片看不到鸟...)   
如图,分出mac os扩展日志式分区(后续恢复mac系统,我的是10.4.11),和一个windows分区(FAT32,msdos),大小自己定。注意,对新硬盘,要使用GUID分区表格式!!
图中,原“Macintosh HD”安装的是tiger系统,WINDOWS_HD是BootCamp的winXP。
分区完成后,使用磁盘工具的恢复功能,将原盘和目标盘分别拖拽到空白框位置:
(比较遗憾,原帖图片看不到鸟...)
点击恢复按钮,开始恢复过程(我的体验速度是,原mac系统分区46.68GB,恢复用了两个小时的时间)。完成mac区的恢复后,再恢复windows区。
恢复完成后,磁盘大小情况:
(比较遗憾,原帖图片看不到鸟...)
这时,你的数据恢复工作已经做完了。有什么效果?我们打开系统的启动磁盘功能看一下:
 (比较遗憾,原帖图片看不到鸟...)
可以看到,现在内置的两个磁盘分区(图中后两个)和外置USB连接的SATA硬盘的两个分区都是可以启动的!!
我测试了一下所有四个分区的启动和程序调用,文档查看,没有任何问题,而且,外置硬盘上的windows是可以完美启动的!(注意,如果你启动机器时按住Option来启动,是看不到外置硬盘上的win分区启动选项的,需要用mac下的启动磁盘来选择启动)。
那么,下一步你的工作就是拆机,换硬盘吧。
希望这个帖子能消除一些同志对换硬盘的数据恢复问题的疑虑。从这个过程我又体会到,Mac系统是多么人性化和简单!!虽然我也找了一些第三方工具,如discstudio,CCC等,实际都用不上。