分类目录归档:Linux/Unix

Linux/Unix 操作系统相关

解决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、拷贝修改记录、提交变更

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

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

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

zmq、czmq及其他相关动态库的编译和生成

zeromq是非常优秀的开源库,但是由于作者感觉visual studio的编译工具维护起来相当费事,当然也可能是作者更多的是在非windows下工作的原因吧,msvc build不再维护了,这导致了很多在windows下的用户编译zmq非常不方便。尝试了一下,libzmq\builds\deprecated-msvc\vs20xx的工具的确很多已经不工作了。
下面记录一下通过cmake编译windows下的工程的方法及步骤。
我在项目中用到了zmq、czmq、zyre,由于后两个项目都要依赖前一个项目,所以我们需要统一管理起来。
1、建立一个zeromq_v4.3.1目录(我编译的是4.3.1,所以最好带上版本号,方便后期维护);

2、克隆代码
cmd进入此目录,执行:

https://github.com/zeromq/libzmq.git

将libzmq克隆到zeromq_v4.3.1目录下;

执行:

https://github.com/zeromq/czmq.git

将czmq克隆到zeromq_v4.3.1目录下;

执行:

https://github.com/jedisct1/libsodium.git

将libsodium克隆到zeromq_v4.3.1目录下。
libsodium是一个加密库,czmq将要依赖它,所以这里预先准备好。
执行:

https://github.com/zeromq/zyre.git

将zyre克隆到zeromq_v4.3.1目录下。

3、cmake配置zmq库
cd进入libzmq目录,执行:

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

通常cmake都能成功,这就在build目录下生成了一个vs2015的编译工程,名称是ZeroMQ.sln

需要注意的是,ZeroMQ.sln生成的zmq文件名是带版本号的,类似libzmq-mt-gd-4_3_1.dll这样的格式。这会导致我们项目引用配置的频繁更改,也不利于通过dll替换的方式升级zmq,所以把zmq的CMakeLists.txt配置改一下,让他按libzmq.dll文件名的方式生成:

if(MSVC)
# Suppress linker warnings caused by #ifdef omission
# of file content.
set(CMAKE_STATIC_LINKER_FLAGS “${CMAKE_STATIC_LINKER_FLAGS} /ignore:4221”)
set(PDB_OUTPUT_DIRECTORY “${CMAKE_CURRENT_BINARY_DIR}/bin”)
set(PDB_NAME “libzmq${MSVC_TOOLSET}-mt-gd-${ZMQ_VERSION_MAJOR}_${ZMQ_VERSION_MINOR}_${ZMQ_VERSION_PATCH}”)
function(enable_vs_guideline_checker target)
set_target_properties(${target} PROPERTIES
VS_GLOBAL_EnableCppCoreCheck true
VS_GLOBAL_CodeAnalysisRuleSet CppCoreCheckRules.ruleset
VS_GLOBAL_RunCodeAnalysis true)
endfunction()
if(BUILD_SHARED)
add_library(libzmq SHARED ${sources} ${public_headers} ${html-docs} ${readme-docs} ${CMAKE_CURRENT_BINARY_DIR}/NSIS.template.in ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
if(ENABLE_ANALYSIS)
enable_vs_guideline_checker(libzmq)
endif()
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 “libzmq”)
endif()

if(BUILD_STATIC)
add_library(libzmq-static STATIC ${sources} ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
set_target_properties(libzmq-static PROPERTIES
PUBLIC_HEADER “${public_headers}”
#RELEASE_POSTFIX “${MSVC_TOOLSET}-mt-s-${ZMQ_VERSION_MAJOR}_${ZMQ_VERSION_MINOR}_${ZMQ_VERSION_PATCH}”
#RELWITHDEBINFO_POSTFIX “${MSVC_TOOLSET}-mt-s-${ZMQ_VERSION_MAJOR}_${ZMQ_VERSION_MINOR}_${ZMQ_VERSION_PATCH}”
#MINSIZEREL_POSTFIX “${MSVC_TOOLSET}-mt-s-${ZMQ_VERSION_MAJOR}_${ZMQ_VERSION_MINOR}_${ZMQ_VERSION_PATCH}”
#DEBUG_POSTFIX “${MSVC_TOOLSET}-mt-sgd-${ZMQ_VERSION_MAJOR}_${ZMQ_VERSION_MINOR}_${ZMQ_VERSION_PATCH}”
COMPILE_FLAGS “/DZMQ_STATIC”
OUTPUT_NAME “libzmq”)
endif()
else()

……

就是把一些’POSTFIX’注释掉了,让他不要自动在文件名后串接版本号和编译器等信息。

当然还有个方法就是手动修改项目属性,直接在visual studio中,在libzmq工程上mouse right button -> Properties,在弹出来的属性框中,手动把 Configuration Properties -> General -> Target Name 改成 “libzmq”,记得把需要的configuration都改一下(Debug、Release、RelWithDebInfo…)。另外还得把lib文件名也改了:
Configuration Properties -> Linker -> Advanced -> Import Library 改成 “xxxxxxx/libzmq.lib”
同样,如果需要pdb文件,把pdb文件名也改了:
Configuration Properties -> Linker -> Debugging -> Generate Program Database File 改成 “xxxxxxx/libzmq.pdb”。

4、生成zmq库
编译ZeroMQ.sln,一般都能正确编译的。

5、cmake配置czmq库
czmq也抛弃了windows,所以也得自己配置。
czmq配置相对麻烦一些,因为他要依赖zmq,所以我们需要改一下czmq目录下的Findlibzmq.cmake文件,让cmake能正确找到我们刚才编译的zmq的lib文件。

################################################################################
#  THIS FILE IS 100% GENERATED BY ZPROJECT; DO NOT EDIT EXCEPT EXPERIMENTALLY  #
#  Read the zproject/README.md for information about making permanent changes. #
################################################################################

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 ../libzmq/include)
	set(PC_LIBZMQ_LIBRARY_DIRS ../libzmq/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}")

find_path (
    LIBZMQ_INCLUDE_DIRS
    NAMES zmq.h
    HINTS ${PC_LIBZMQ_INCLUDE_HINTS}
)

find_library (
    LIBZMQ_LIBRARIES
    NAMES libzmq
    HINTS ${PC_LIBZMQ_LIBRARY_HINTS}
)

include(FindPackageHandleStandardArgs)

find_package_handle_standard_args(
    LIBZMQ
    REQUIRED_VARS LIBZMQ_LIBRARIES LIBZMQ_INCLUDE_DIRS
)
mark_as_advanced(
    LIBZMQ_FOUND
    LIBZMQ_LIBRARIES LIBZMQ_INCLUDE_DIRS
)

################################################################################
#  THIS FILE IS 100% GENERATED BY ZPROJECT; DO NOT EDIT EXCEPT EXPERIMENTALLY  #
#  Read the zproject/README.md for information about making permanent changes. #
################################################################################

czmq依赖的另一个库是libsodium,还好,libsodium的vs编译还在,打开libsodium\builds\msvc\vs2015目录下的libsodium.sln文件,可直接生成对应的libsodium.dll和libsodium.lib
然后在czmq目录下找到Findlibsodium.cmake文件,修改如下:

################################################################################
#  THIS FILE IS 100% GENERATED BY ZPROJECT; DO NOT EDIT EXCEPT EXPERIMENTALLY  #
#  Please refer to the README for information about making permanent changes.  #
################################################################################

if (NOT MSVC)
    include(FindPkgConfig)
    pkg_check_modules(PC_LIBSODIUM "libsodium")
    if (NOT PC_LIBSODIUM_FOUND)
        pkg_check_modules(PC_LIBSODIUM "libsodium")
    endif (NOT PC_LIBSODIUM_FOUND)
    if (PC_LIBSODIUM_FOUND)
        # 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_LIBSODIUM_INCLUDE_HINTS ${PC_LIBSODIUM_INCLUDE_DIRS} ${PC_LIBSODIUM_INCLUDE_DIRS}/*)
        set(PC_LIBSODIUM_LIBRARY_HINTS ${PC_LIBSODIUM_LIBRARY_DIRS} ${PC_LIBSODIUM_LIBRARY_DIRS}/*)
    endif(PC_LIBSODIUM_FOUND)
else()
	set(PC_LIBSODIUM_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/../libsodium/src/libsodium/include)
	set(PC_LIBSODIUM_LIBRARY_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/../libsodium/bin/x64/Release/v140/dynamic)	
	set(PC_LIBSODIUM_INCLUDE_HINTS ${PC_LIBSODIUM_INCLUDE_DIRS} ${PC_LIBSODIUM_INCLUDE_DIRS}/*)
	set(PC_LIBSODIUM_LIBRARY_HINTS ${PC_LIBSODIUM_LIBRARY_DIRS} ${PC_LIBSODIUM_LIBRARY_DIRS}/*)
endif (NOT MSVC)

find_path (
    LIBSODIUM_INCLUDE_DIRS
    NAMES sodium.h
    HINTS ${PC_LIBSODIUM_INCLUDE_HINTS}
)

find_library (
    LIBSODIUM_LIBRARIES
    NAMES libsodium
    HINTS ${PC_LIBSODIUM_LIBRARY_HINTS}
)

include(FindPackageHandleStandardArgs)

find_package_handle_standard_args(
    LIBSODIUM
    REQUIRED_VARS LIBSODIUM_LIBRARIES LIBSODIUM_INCLUDE_DIRS
)
mark_as_advanced(
    LIBSODIUM_FOUND
    LIBSODIUM_LIBRARIES LIBSODIUM_INCLUDE_DIRS
)

################################################################################
#  THIS FILE IS 100% GENERATED BY ZPROJECT; DO NOT EDIT EXCEPT EXPERIMENTALLY  #
#  Please refer to the README for information about making permanent changes.  #
################################################################################

6、生成czmq库
这个就简单了,打开编译就行啦

7、zyre依赖zmq和czmq,参考czmq,把对应的依赖项Findlibzmq.cmake、Findczmq.cmake、Findlibsodium.cmake改为对应的目录和文件就行了。

8、czmq的版本号问题
czmq通过cmake编译完的windows dll是不带版本号的,也就是从dll文件的属性里看不到版本号信息,这让我们在使用时很难确定其版本号,还好builds/msvc目录下有个resource.rc文件,只要在如下地方把这个rc文件加进去就可以了:

# shared
if (CZMQ_BUILD_SHARED)
  IF (MSVC)
    add_library(czmq SHARED ${czmq_sources} ${CMAKE_CURRENT_SOURCE_DIR}/builds/msvc/resource.rc)
  ELSE (MSVC)
    add_library(czmq SHARED
 
lt;TARGET_OBJECTS:czmq_objects>) ENDIF (MSVC) set_target_properties (czmq PROPERTIES PUBLIC_HEADER "${public_headers}" DEFINE_SYMBOL "CZMQ_EXPORTS" SOVERSION "4" VERSION "${CZMQ_VERSION}" COMPILE_DEFINITIONS "DLL_EXPORT" OUTPUT_NAME "czmq" PREFIX "lib" ) target_link_libraries(czmq PUBLIC ${MORE_LIBRARIES} ) install(TARGETS czmq EXPORT czmq-targets LIBRARY DESTINATION "lib${LIB_SUFFIX}" # .so file ARCHIVE DESTINATION "lib${LIB_SUFFIX}" # .lib file RUNTIME DESTINATION bin # .dll file ) target_include_directories(czmq PUBLIC
 
lt;BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
 
lt;INSTALL_INTERFACE:include> ) endif() # static if (CZMQ_BUILD_STATIC) IF (MSVC) add_library(czmq-static STATIC ${czmq_sources} ${CMAKE_CURRENT_SOURCE_DIR}/builds/msvc/resource.rc) ELSE (MSVC) add_library(czmq-static STATIC
 
lt;TARGET_OBJECTS:czmq_objects>) ENDIF (MSVC) set_target_properties(czmq-static PROPERTIES PUBLIC_HEADER "${public_headers}" COMPILE_DEFINITIONS "CZMQ_STATIC" OUTPUT_NAME "czmq" PREFIX "lib" ) target_link_libraries(czmq-static PUBLIC ${MORE_LIBRARIES} ) install(TARGETS czmq-static EXPORT czmq-targets LIBRARY DESTINATION "lib${LIB_SUFFIX}" # .so file ARCHIVE DESTINATION "lib${LIB_SUFFIX}" # .lib file RUNTIME DESTINATION bin # .dll file ) target_include_directories(czmq-static PUBLIC
 
lt;BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
 
lt;INSTALL_INTERFACE:include> ) target_compile_definitions(czmq-static PUBLIC CZMQ_STATIC ) endif()

解决mingw中gcc生成静态库,另一个动态库或exe使用此静态库的问题

注意:以下都是基于CMake来配置项目

现在有三个项目:libA,一个代码库项目;libB,使用libA的另一个动态库;exeC,一个使用libA的可执行程序。

按照正常设置,libA生成动态库,libB和exeC使用libA,都没问题,完美编译通过并可正常执行。但由于发布问题,libA不想发布到用户手中,所以想把libA做成静态库,libB封装libA,将libA静态链接到libB中,同样exeC静态链接libA到exeC中。

于是如下设置libA,在libA的add_library()中增加STATIC标志:

  # Build library
add_library(${target} STATIC
    ${sources}
    ${headers}
    #${rc_file} 
)

 

未做任何变化,libA能生成静态库,但是libB和exeC都报错:

exeC报的错:

[100%] Linking CXX executable ..\..\..\test_apid.exe
CMakeFiles\test_api.dir/objects.a(main.cpp.obj): In function `main':
E:/SerialPortDevice/source/demo/test_api/main.cpp:42: undefined reference to `__imp_CreateSerialPort'
E:/SerialPortDevice/source/demo/test_api/main.cpp:45: undefined reference to `__imp_RegisterReceivedDataCallback'
E:/SerialPortDevice/source/demo/test_api/main.cpp:48: undefined reference to `__imp_RegisterSerialPortStatusChangedCallback'
E:/SerialPortDevice/source/demo/test_api/main.cpp:52: undefined reference to `__imp_OpenSerialPort'
E:/SerialPortDevice/source/demo/test_api/main.cpp:71: undefined reference to `__imp_DestroySerialPort'
E:/SerialPortDevice/source/demo/test_api/main.cpp:78: undefined reference to `__imp_GetSerialPortStatus'

 

libB的就不列出来了,这个好理解(没弄懂前还真不好理解 :-) ),就是还是在包含头文件时,头文件中的函数修饰符为import了:

// dynamic library
#ifdef SERIALPORTRW_EXPORTS
#define SERIALPORTRW_API __declspec(dllexport)
#else
#define SERIALPORTRW_API __declspec(dllimport)
#endif

 

 

知道了原因,解决办法就有了:把函数修饰符置为空,因为静态函数导出时是不需要 _deckspec(dllexport)的,在头文件中增加一段定义,同时在CMake配置中声明SERIALPORTRW_STATIC变量:

导出的头文件中:

// dynamic library
#ifdef SERIALPORTRW_EXPORTS
#define SERIALPORTRW_API __declspec(dllexport)
#else
#define SERIALPORTRW_API __declspec(dllimport)
#endif

// static library
#ifdef SERIALPORTRW_STATIC
#undef SERIALPORTRW_API
#define SERIALPORTRW_API // empty
#endif

 

CMake中:

# 
# Compile definitions
# 

target_compile_definitions(${target}
    PRIVATE
        #-DSERIALPORTRW_EXPORTS
        -DSERIALPORTRW_STATIC
    PUBLIC
        $<$<NOT:$<BOOL:${BUILD_SHARED_LIBS}>>:${target_upper}_STATIC_DEFINE>
        ${DEFAULT_COMPILE_DEFINITIONS}

    INTERFACE
)

 

 

 

同样,在使用libA的项目中同样要定义SERIALPORTRW_STATIC变量。

如此设置后,完美解决问题!

 

怪异的 Linker Error LNK2019

今天碰到一个很奇怪的链接错误:LNK2019.

通常碰到这个问题,都是因为只有申明没有实现导致的。要么是lib库没链接进来,要么是工程中只有头文件,没有添加其对应的cpp文件,导致只有申明没有定义。

而今天碰到的奇葩问题是,同一个工程里,h文件及其对应的cpp文件都在工程中,而且通过h文件中的函数申明按F12键转到定义也没问题,即,申明和定义都没问题。

最后猛然发现, 头文件中对一个类型的预定义出问题了!本来此类型是个struct,结果预定义成了class!

这就很好理解了:由于一个类在头文件中可能要用到另一个类型,又不想在头文件中包含另一个类型的头文件,或者因为循环引用的问题,没法在这里包含另一个类的头文件,则我们在这里先预定义这个类的申明,这样,就可以在头文件中用这个类来申明变量(只能申明指针变量,而不能使用实体,因为实体会真正导致使用另一个类的类定义),然后在cpp文件中才真正包含另一个类的头文件,并为这个预先定义的指针变量new一个实体对象。

那么问题来了,如果在对另一个类做预定义时本来是class,却定义成了struct,或者本来是struct,却错误的定义成了class,那么对于头文件,编译的时候使用的是预定义符号,而cpp文件中由于包含了另一个类的头文件,使用的是真正的申明和定义。

这种错误目前几乎所有编译器都会放过去的,但是链接的时候,目前看gcc编译器是放过去了,但微软的vc链接器包KNK2019链接错误!

这个问题,实在是因为黔驴技穷,耗费了好几个小时后没辙了,所以才打开错误警告才发现的。

 

Android内核编译过程全解

之前编译过锤子的坚果手机内核,摸索了很长时间,遗憾的是没有把一些填坑的细节记录下来,免不了有些细节还得摸索一遍。这次要编译三星的Galaxy Note 5内核,加上已经有了一次成功经历,所以想把它系统化的记录下来,供自己或其他需要的人参考。

我这里主要讲内核的编译,至于关联到的一些其他工具的安装或配置,这里就不展开了,碰到这样的问题请移步问问Google。

1.  获取CPU信息

要为某手机编译内核,首先要了解手机所用的CPU,不同厂商生产的CPU,对应的linux内核是不一样的。

查看CPU信息的一种方法是利用adb,“adb shell cat /roc/cpuinfo”可以得到cpu架构和生产厂商。另外,利用“adb shell cat /proc/version”还可以得到手机中正在使用的内核信息。以下是我的Galaxy note 5的信息:将手机与PC通过usb线相连,首先查看adb是否已经可以访问、然后获取cpu信息、最后获取linux内核信息:

C:\Users\yuanhui>adb devices
List of devices attached
0715e7e408981f38 device

C:\Users\yuanhui>adb shell cat /proc/cpuinfo
Processor : AArch64 Processor rev 2 (aarch64)
processor : 0
processor : 1
processor : 2
processor : 3
processor : 4
processor : 5
processor : 6
processor : 7
Features : fp asimd aes pmull sha1 sha2 crc32
CPU implementer : 0x41
CPU architecture: AArch64
CPU variant : 0x0
CPU part : 0xd03
CPU revision : 2

Hardware : SAMSUNG Exynos7420

C:\Users\yuanhui>adb shell cat /proc/version
Linux version 3.10.61-6137732 (dpi@SWDC3312) (gcc version 4.9 20140514 (prerelea
se) (GCC) ) #1 SMP PREEMPT Fri Feb 5 13:33:23 KST 2016
另外,通过官网也可以获得比较详细的硬件信息及参数:

QQ截图20160401115714

2. 下载源代码

得到CPU信息后,就可以到Android网站去下载linux内核源码了,下载地址说明:

http://source.android.com/source/building-kernels.html#figuring-out-which-kernel-to-build

这里需要特别注意,三星的源码树有两个,一个是kernel/exynos,一个是kernel/samsung,Galaxy note 5 用的是exynos芯片,所以一定要下载exynos的源码。

QQ截图20160401115941

所以,git命令为:

$ git clone https://android.googlesource.com/kernel/exynos

这里稍微注意一下,由于国内googlesource.com被墙了,只能通过VPN才能下载。

我为了方便,所有下载及编译都是在Ubuntu里完成的:

hyh@ubuntu:~$ git clone https://android.googlesource.com/kernel/exynos
Cloning into ‘exynos’…
remote: Sending approximately 733.06 MiB …
remote: Counting objects: 9, done
remote: Finding sources: 100% (9/9)
Receiving objects: 100% (3159494/3159494), 733.07 MiB | 631.00 KiB/s, done.
remote: Total 3159494 (delta 2631328), reused 3159494 (delta 2631328)
Resolving deltas: 100% (2631328/2631328), done.
Checking connectivity… done.
hyh@ubuntu:~$

这样,在我的home下就生成了一个exynos目录,源码就在这个文件夹里。

为了后面使用方便,把目录改成了linux-kernel-exynos。

到里面看看都有些什么分支:

hyh@ubuntu:~$ cd linux-kernel-exynos/
hyh@ubuntu:~/linux-kernel-exynos$ git branch -r
origin/HEAD -> origin/master
origin/android-exynos-3.4
origin/android-exynos-koi-3.10-marshmallow-mr1-wear-release
origin/android-exynos-manta-3.4-adf
origin/android-exynos-manta-3.4-jb-mr1
origin/android-exynos-manta-3.4-jb-mr1-fr
origin/android-exynos-manta-3.4-jb-mr1.1
origin/android-exynos-manta-3.4-jb-mr2
origin/android-exynos-manta-3.4-kitkat-mr0
origin/android-exynos-manta-3.4-kitkat-mr1
origin/android-exynos-manta-3.4-kitkat-mr2
origin/android-exynos-manta-3.4-lollipop-mr1
origin/android-exynos-manta-3.4-lollipop-release
origin/master
hyh@ubuntu:~/linux-kernel-exynos$

之前看到手机用的是3.10的内核,那就把3.10的分支checkout出来:

hyh@ubuntu:~/linux-kernel-exynos$ git checkout origin/android-exynos-koi-3.10-marshmallow-mr1-wear-release
Checking out files: 100% (45351/45351), done.
Note: checking out ‘origin/android-exynos-koi-3.10-marshmallow-mr1-wear-release’.

You are in ‘detached HEAD’ state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

git checkout -b <new-branch-name>

HEAD is now at 0f9bded… Merge “Fix that wrong eint information is displayed” into android-exynos-koi-3.10
hyh@ubuntu:~/linux-kernel-exynos$

这时候才能看到目录下有了源代码(之前目录里面实际有个800多兆的.git文件夹,隐藏了,看不到而已)。

3. 配置/定制内核

通常情况下,各个厂商都会针对自己的手机做大量定制,如果编译内核时选择生成相应的配置文件,则我们可以从手机中直接得到。能拿到这个内核配置文件编译内核就会顺利很多。

C:\Users\yuanhui>adb pull /proc/config.gz

大部分情况都能拿到这个config文件,然后解压出其中的.config文件放到linux-kernel-exynos目录下,直接调用make ARCH=arm menuconfig即可定制内核。

但是很不幸,三星的这款手机没有此文件:

C:\Users\yuanhui>adb pull /proc/config.gz
remote object ‘/proc/config.gz’ does not exist

好吧,没有也没关系,直接make ARCH=arm menuconfig,通常情况也是能顺利编译的……

我这里为了把CP210x的驱动编译进去,从Silicon官网下载了Android内核编译CP210x驱动的文档,照着把CP210x驱动编译选项勾选好,保存退出,在linux-kernel-exynos目录下生成了一个.config的文件:

hyh@ubuntu:~/linux-kernel-exynos$ make ARCH=arm menuconfig
HOSTCC scripts/basic/fixdep
HOSTCC scripts/kconfig/conf.o
HOSTCC scripts/kconfig/lxdialog/checklist.o
HOSTCC scripts/kconfig/lxdialog/inputbox.o
HOSTCC scripts/kconfig/lxdialog/menubox.o
HOSTCC scripts/kconfig/lxdialog/textbox.o
HOSTCC scripts/kconfig/lxdialog/util.o
HOSTCC scripts/kconfig/lxdialog/yesno.o
HOSTCC scripts/kconfig/mconf.o
SHIPPED scripts/kconfig/zconf.tab.c
SHIPPED scripts/kconfig/zconf.lex.c
SHIPPED scripts/kconfig/zconf.hash.c
HOSTCC scripts/kconfig/zconf.tab.o
In file included from scripts/kconfig/zconf.tab.c:2503:0:
scripts/kconfig/menu.c: In function ‘get_symbol_str’:
scripts/kconfig/menu.c:567:18: warning: ‘jump’ may be used uninitialized in this function [-Wmaybe-uninitialized]
jump->offset = r->len – 1;
^
scripts/kconfig/menu.c:528:19: note: ‘jump’ was declared here
struct jump_key *jump;
^
HOSTLD scripts/kconfig/mconf
scripts/kconfig/mconf Kconfig
#
# using defaults found in /boot/config-4.2.0-23-generic
#
/boot/config-4.2.0-23-generic:928:warning: symbol value ‘m’ invalid for BRIDGE_NETFILTER
/boot/config-4.2.0-23-generic:2668:warning: symbol value ‘m’ invalid for STMMAC_PLATFORM
/boot/config-4.2.0-23-generic:3834:warning: symbol value ‘m’ invalid for GPIO_UCB1400
/boot/config-4.2.0-23-generic:4336:warning: symbol value ‘m’ invalid for MFD_WM8994
/boot/config-4.2.0-23-generic:4343:warning: symbol value ‘m’ invalid for REGULATOR_88PM8607
/boot/config-4.2.0-23-generic:4365:warning: symbol value ‘m’ invalid for REGULATOR_LP872X
/boot/config-4.2.0-23-generic:4367:warning: symbol value ‘m’ invalid for REGULATOR_LP8788
/boot/config-4.2.0-23-generic:4410:warning: symbol value ‘m’ invalid for REGULATOR_TWL4030
/boot/config-4.2.0-23-generic:5453:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_REALTEK
/boot/config-4.2.0-23-generic:5454:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_ANALOG
/boot/config-4.2.0-23-generic:5455:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_SIGMATEL
/boot/config-4.2.0-23-generic:5456:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_VIA
/boot/config-4.2.0-23-generic:5457:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_HDMI
/boot/config-4.2.0-23-generic:5458:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_CIRRUS
/boot/config-4.2.0-23-generic:5459:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_CONEXANT
/boot/config-4.2.0-23-generic:5460:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_CA0110
/boot/config-4.2.0-23-generic:5461:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_CA0132
/boot/config-4.2.0-23-generic:5463:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_CMEDIA
/boot/config-4.2.0-23-generic:5464:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_SI3054
/boot/config-4.2.0-23-generic:5465:warning: symbol value ‘m’ invalid for SND_HDA_GENERIC
/boot/config-4.2.0-23-generic:6539:warning: symbol value ‘m’ invalid for COMEDI_PCI_DRIVERS
/boot/config-4.2.0-23-generic:6594:warning: symbol value ‘m’ invalid for COMEDI_PCMCIA_DRIVERS
/boot/config-4.2.0-23-generic:6602:warning: symbol value ‘m’ invalid for COMEDI_USB_DRIVERS
/boot/config-4.2.0-23-generic:7036:warning: symbol value ‘m’ invalid for LP8788_ADC
/boot/config-4.2.0-23-generic:8176:warning: symbol value ‘m’ invalid for KVM
configuration written to .config

*** End of the configuration.
*** Execute ‘make’ to start the build or try ‘make help’.

hyh@ubuntu:~/linux-kernel-exynos$

 

4. 编译内核

 

不同的CPU架构,就得选择不同架构的工具链。我们在刚开始的时候通过adb shell cat /proc/cpuinfo已经得到了CPU的家规信息,为aarch64,所以我们要选择的aarch64工具链来编译,Google Source 网站上同时提供了各个版本的编译工具:

https://android.googlesource.com/platform/prebuilts/

我们选择aarch64 gcc 4.9的版本来编译:

hyh@ubuntu:~$ git clone https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9
Cloning into ‘aarch64-linux-android-4.9’…
remote: Sending approximately 180.15 MiB …
remote: Counting objects: 108, done
remote: Finding sources: 100% (108/108)
remote: Total 1641 (delta 990), reused 1641 (delta 990)
Receiving objects: 100% (1641/1641), 180.18 MiB | 620.00 KiB/s, done.
Resolving deltas: 100% (990/990), done.
Checking connectivity… done.
hyh@ubuntu:~$

把这个路径加入到$PATH中,以便编译时省去冗长的路径:

hyh@ubuntu:~/linux-kernel-exynos$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
hyh@ubuntu:~/linux-kernel-exynos$ export PATH=$PATH:~/aarch64-linux-android-4.9/bin
hyh@ubuntu:~/linux-kernel-exynos$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/home/hyh/aarch64-linux-android-4.9/bin

可以看到,用于编译内核的编译器路径已经添加到了$PATH中。

下面开始编译:
hyh@ubuntu:~/linux-kernel-exynos$ make ARCH=arm CROSS_COMPILE=arm-linux-androideabi- zImage
CHK include/generated/uapi/linux/version.h
CHK include/generated/utsrelease.h
make[1]: ‘include/generated/mach-types.h’ is up to date.
CC kernel/bounds.s
GEN include/generated/bounds.h
CC arch/arm/kernel/asm-offsets.s
In file included from include/linux/scatterlist.h:10:0,
from include/linux/dma-mapping.h:9,
from arch/arm/kernel/asm-offsets.c:15:
/home/hyh/linux-kernel-exynos/arch/arm/include/asm/io.h:30:28: fatal error: mach/exynos-ss.h: No such file or directory
#include <mach/exynos-ss.h>
^
compilation terminated.
/home/hyh/linux-kernel-exynos/./Kbuild:81: recipe for target ‘arch/arm/kernel/asm-offsets.s’ failed
make[1]: *** [arch/arm/kernel/asm-offsets.s] Error 1
Makefile:836: recipe for target ‘prepare0’ failed
make: *** [prepare0] Error 2
hyh@ubuntu:~/linux-kernel-exynos$

出错了,改个编译工具,网上下载了一个arm-eabi-4.8,放到hyh目录下,在$PATH中加入目录/home/hyh/arm-eabi-4.8/bin,编译。这次好点,编译了大部分代码,但是在编译驱动时出错了:

hyh@ubuntu:~/linux-kernel-exynos$ make ARCH=arm CROSS_COMPILE=arm-eabi- zImage
CHK include/linux/version.h
CHK include/generated/utsrelease.h
make[1]: ‘include/generated/mach-types.h’ is up to date.
CALL scripts/checksyscalls.sh
CHK include/generated/compile.h
CC drivers/mfd/ezx-pcap.o
drivers/mfd/ezx-pcap.c: In function ‘pcap_isr_work’:
drivers/mfd/ezx-pcap.c:205:2: error: implicit declaration of function ‘irq_to_gpio’ [-Werror=implicit-function-declaration]
} while (gpio_get_value(irq_to_gpio(pcap->spi->irq)));
^
cc1: some warnings being treated as errors
scripts/Makefile.build:305: recipe for target ‘drivers/mfd/ezx-pcap.o’ failed
make[2]: *** [drivers/mfd/ezx-pcap.o] Error 1
scripts/Makefile.build:441: recipe for target ‘drivers/mfd’ failed
make[1]: *** [drivers/mfd] Error 2
Makefile:945: recipe for target ‘drivers’ failed
make: *** [drivers] Error 2
hyh@ubuntu:~/linux-kernel-samsung$

发现ezx-pcap.c文件中有一段如下代码:

static void pcap_isr_work(struct work_struct *work)
{
struct pcap_chip *pcap = container_of(work, struct pcap_chip, isr_work);
struct pcap_platform_data *pdata = pcap->spi->dev.platform_data;
u32 msr, isr, int_sel, service;
int irq;

do {
ezx_pcap_read(pcap, PCAP_REG_MSR, &msr);
ezx_pcap_read(pcap, PCAP_REG_ISR, &isr);

/* We can’t service/ack irqs that are assigned to port 2 */
if (!(pdata->config & PCAP_SECOND_PORT)) {
ezx_pcap_read(pcap, PCAP_REG_INT_SEL, &int_sel);
isr &= ~int_sel;
}

ezx_pcap_write(pcap, PCAP_REG_MSR, isr | msr);
ezx_pcap_write(pcap, PCAP_REG_ISR, isr);

local_irq_disable();
service = isr & ~msr;
for (irq = pcap->irq_base; service; service >>= 1, irq++) {
if (service & 1)
generic_handle_irq(irq);
}
local_irq_enable();
ezx_pcap_write(pcap, PCAP_REG_MSR, pcap->msr);
} while (gpio_get_value(irq_to_gpio(pcap->spi->irq)));
}

定位到申明的位置:<linux/gpio.h>,在gpio.h文件中有这样一段申明:

static inline int irq_to_gpio(unsigned irq)
{
/* irq can never have been returned from gpio_to_irq() */
WARN_ON(1);
return -EINVAL;
}

可见被加了static,外部无法访问。而且,看到注释,以及返回值,可见这个函数是无用的。分析在ezx-pcap.c文件中的这段代码,do while会执行一次这段代码,但irq_to_gpio()永远只会返回失败,所以一定只会执行一次,所以果断把 “do{” 和 “}while”  这两行代码屏蔽:

static void pcap_isr_work(struct work_struct *work)
{
struct pcap_chip *pcap = container_of(work, struct pcap_chip, isr_work);
struct pcap_platform_data *pdata = pcap->spi->dev.platform_data;
u32 msr, isr, int_sel, service;
int irq;

//do {
ezx_pcap_read(pcap, PCAP_REG_MSR, &msr);
ezx_pcap_read(pcap, PCAP_REG_ISR, &isr);

/* We can’t service/ack irqs that are assigned to port 2 */
if (!(pdata->config & PCAP_SECOND_PORT)) {
ezx_pcap_read(pcap, PCAP_REG_INT_SEL, &int_sel);
isr &= ~int_sel;
}

ezx_pcap_write(pcap, PCAP_REG_MSR, isr | msr);
ezx_pcap_write(pcap, PCAP_REG_ISR, isr);

local_irq_disable();
service = isr & ~msr;
for (irq = pcap->irq_base; service; service >>= 1, irq++) {
if (service & 1)
generic_handle_irq(irq);
}
local_irq_enable();
ezx_pcap_write(pcap, PCAP_REG_MSR, pcap->msr);
//} while (gpio_get_value(irq_to_gpio(pcap->spi->irq)));
}

再次编译,过了:

……

LD vmlinux.o
MODPOST vmlinux.o
GEN .version
CHK include/generated/compile.h
UPD include/generated/compile.h
CC init/version.o
LD init/built-in.o
LD .tmp_vmlinux1
KSYM .tmp_kallsyms1.S
AS .tmp_kallsyms1.o
LD .tmp_vmlinux2
KSYM .tmp_kallsyms2.S
AS .tmp_kallsyms2.o
LD vmlinux
SYSMAP System.map
SYSMAP .tmp_System.map
OBJCOPY arch/arm/boot/Image
Kernel: arch/arm/boot/Image is ready
GZIP arch/arm/boot/compressed/piggy.gzip
AS arch/arm/boot/compressed/piggy.gzip.o
SHIPPED arch/arm/boot/compressed/lib1funcs.S
AS arch/arm/boot/compressed/lib1funcs.o
LD arch/arm/boot/compressed/vmlinux
OBJCOPY arch/arm/boot/zImage
Kernel: arch/arm/boot/zImage is ready
hyh@ubuntu:~/linux-kernel-exynos$

5. 后记

最后一步的编译经常会出一些具体的问题,比如某个包含文件找不到、某段代码编译出错,这种错误需要仔细分析错误提示,往往能定位到问题。源码过于庞大,而实际的应用千差万别,导致自定义后的某些模块之间出现问题。所以这样的问题只能是具体问题具体分析,没用的代码甚至可以屏蔽。这恰恰也是导致部分自定义代码无法编译通过的原因。

 

Android SDK DDMS通过Wifi接收Android日志

将Android手机与PC通过USB连接,我们就可以在Android Device Monitor中看到Android手机的实时日志,这个对程序的调试非常有帮助。但是一般来讲手机只有一个usb口,如果usb被别的设备占用,我们就没法获取日志了。

实际上,Android Device Monitor是通过手机端的ADB服务与PC通讯来显示日志的。adb非常流弊,指令丰富,功能齐全,是黑进手机的必备工具。这里就用到了adb的网络功能。要想拿到日志,必须在手机端启动adb的tcp服务,然后在PC端通过adb与手机的adb服务连接,这样我们就可以通过网络获取日志了,而不是usb有线方式获取日志。

具体步骤如下:

step 1. 将手机通过usb连接到PC机

step 2. 通过adb的tcpip命令启动一个tcp监听服务

step 3. 通过adb的connect指令从PC端连接Android手机

step 4. 启动Dalvik Debug Monitor,就会看到日志从网络上发动到了本机

 

具体操作如下:

D:\Android>adb tcpip 8630
* daemon not running. starting it now on port 5037 *
* daemon started successfully *
restarting in TCP mode port: 8630

D:\Android>adb connect 192.168.3.42:8630
unable to connect to 192.168.3.42:8630

 

 

D:\Android>adb devices
List of devices attached
90a16a93 device
D:\Android>adb connect 192.168.3.42:8630
unable to connect to 192.168.3.42:8630

D:\Android>adb tcpip 9999
restarting in TCP mode port: 9999

D:\Android>adb connect 192.168.3.42:9999
connected to 192.168.3.42:9999

 

参考:http://stackoverflow.com/questions/2604727/how-can-i-connect-to-android-with-adb-over-tcp

 

在命令模式下编译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

 

在Unity Android 程序中使用动态库及注意事项

很多C/C++代码以动态库的方式供第三方调用,在Unity中,这类文件(dll for windows, *.so file for Android/Linux, *.dylib for MAC OSX)叫插件。

在Unity开发的Android程序中使用动态库插件是非常方便的,曾经因为被误导而放弃使用unity,转而研究Android通过原生Java的JNI方式包装*.so文件,虽然还行,但是JNI晦涩丑陋的API实在看着不舒服。相对而言,C#也是可以直接包装*.so文件并在Android系统中直接调用的,而且Unity制作Android app跟Unity制作其他平台的app是无缝的,只是在发布时选择要发布的平台就行了,真正做到了平台无关,极大的方便了开发。

我还没仔细研究如何在Android Studio中开发NDK,这里只介绍如何用Eclipse开发NDK。

在Eclipse中开发NDK的几个必须的安装项:NDK编译环境、Android SDK直接从官网单独下载单独安装;CDT、ADT插件可以直接从Eclipse的help的插件管理中安装。

然后就可以创建项目了:

1、打开Eclipse,通过File->New Project,弹出对话框,填入项目名称:

01

2、点Next,出现Config Project窗口:

02

由于只用于编译NDK,所以把前两项的钩钩去掉,第三个勾上,标记为so项目;目录也自定义一下,因为通常情况下我只会为编译so文件配置项目,真正的代码会单独放在更顶层的目录,方便跨平台的其他编译项目使用。结果如下:

03

4、按Finish结束创建过程,目录结构如下:

04

5、添加Native代码支持。也就是JNI相关的东西:在左侧项目根目录上右键->Android Tools->Add Native Support…

05

在随后弹出的对话框中输入要生成的so文件的名字:

06

这时候会发现多了一个目录:

07

6、编译配置

在jni目录中加入Application.mk文件和Android.mk文件

Application.mk:

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

#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

Android.mk:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := PNLib
LOCAL_SRC_FILES := PNLib.cpp

include $(BUILD_SHARED_LIBRARY)

本来Android.mk文件很简单,把所有头文件、cpp文件加入,直接编译就行。但是对于我的项目这远远不够,因为我的代码要跨平台,有各个平台的编译项目单独出去,同时使用一份src文件,所以代码被放到了顶层目录中的src目录下,我需要遍历这个目录并把它加入NDK编译系统中来,所以下面从网上找了一段遍历头文件和cpp文件的脚本来用了:

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

7、最后一步是为此项目配置一个NDK编译工具:

A、项目->右键->Properties,弹出项目属性对话框:

08

B、选中Builders,点击New按钮,弹出框中选中“Program”,点击OK:

09

C、出现新Builder配置对话框,随便起个名字“New_Builder_for_NDK”;

在Main标签页中,“Location”项,通过“Browse File System…“找到NDK的build文件,windows系统为ndk-build.cmd,Mac或其他类Linux系统为ndk-build;

工作目录”Working Directory“指定当前项目下的jni目录就行了。

10

D、切到Refresh页,勾选”Refresh resources upon completion“

11

E、切到”Build Options“页,勾选”Specify working set of relevant resources“,点击后面按钮”Specify Resources…“,指本项目下的jni目录。

12

最后的编译条如下:

13

点击左边锤子图标即可对项目进行编译。剩下的工作就是一步步修正跨平台代码,最后生成PNLib.so文件。

 

以上步骤有几个地方需要特别注意:

1、使用gun++11

NKD支持c++11,GCC已经支持大部分c++11特性,所以可以直接使用gun++11。实际上,如果用c++11,Android版程序可能报错:DLLNotFundException,编译出来的动态库在Android系统中无法加载,所以Application.mk文件中必须如下配置:

APP_STL       := gnustl_static
APP_CPPFLAGS  := -std=gnu++11 -pthread -frtti -fexceptions -DNDEBUG

2、x64版ndk目前还有bug

你的系统即便是x64的,也别下载x64版本的ndk,否则会报找不到make.exe的问题(make.exe不是内部命令XXXXXXXXX)

 

3、不识别vector、list等模板类的问题

Add

APP_STL := stlport_static

to their Application.mk file.

其实Android插件应该用APP_STL := gnustl_static

 

4、使用第三方库Eigen导致array等冲突的问题

原因是在NDK的Android.mk文件中把Eigen的路径也加入到里面了。

正确的做法是,不在Android.mk文件加入Eigen,而是在程序代码中直接包含Eigen头文件。

 

Eclipse 创建、生成NDK步骤总结

本说明基于Eclipse+ADT+CDT开发,Eclipse版本号:Mars.1 Release 4.5.1、ADT版本:23.0.7.2120684、CDT版本:8.8.0.201509131935。

注:部分内容来自网络,这里只是做个资料收集整理,为自己和其他读者提供便利。

1、搭建NDK开发环境

Eclipse和Android SDK的安装就不说了,注意顺序,尤其是CDT和ADT的安装顺序

(1)下载NDK包,并解压http://developer.android.com/tools/sdk/ndk/index.html

(2)下载CDT(C/C++开发环境插件),在Eclipse中安装此插件。

(3)下载ADT,在Ecplise中安装此插件,一定要选中NDK Plugin

(4)在Ecplise中配置Android SDK和NDK SDK的路径,在Eclipse的Window->Preferences->Android中设置,我本机的设置为Android SDK Location为“C:\Android\android-sdk”,NDK Location为“C:\Android\android-ndk-r8e”。

2、创建NDK项目

(1)首先在Eclipse中创建一个Android工程

(2)在Package Explorer中选中刚才创建的Android工程,右键选择Android Tools -> Add Native Support..,填写需要生成的动态库的名称,比如“HelloWorld”,此时工程中多了一个叫做“jni”的目录,并自动生成了一个C++文件和一个Androd.MK文件,所有的C++源代码就放在这个目录中。这样,就跟Eclipse工程添加了C/C++属性,就能够用CDT插件在Eclipse中进行C/C++开发了。

(3)在Java代码中声明本地方法并调用
    网上资料很多
(4)在C/C++代码中实现本地方法
    网上资料很多

Unity 5.0 中使用C/C++开发的dll插件使用中的问题 DllNotFoundException 问题集

Unity 中会经常用到C/C++开发的动态库(dll),Unity称为第三方插件,或plugin,这是非常厉害的扩展方法,但是Unity使用dll也非常痛苦,错误百出。

最常见的问题是 DllNotFoundException,就是找不到动态库。导致DllNotFoundException的原因很多,现把一些可能的情况,以及其解决方案一一列举:

1、DllNotFoundException: SerialPortDevice.dll
demo.Start () (at Assets/demo.cs:39)

Unity基于Mono实现的跨平台特性,但是Mono的 .net framework只充分实现了.net 2.0 subet,至于.net framework 4.5,照目前的速度,估计要等到下辈子了。

操作串口有很多问题,于是我自己用C++分装了一个dll,用于管理串口。从dll中导出的C#代码如下:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

// Wrapper of library by C#
namespace PluginImport
{
	#region delegates
	public delegate void ReceivedDataCallback(IntPtr serialPortRef, ref byte[] data, int len);
	//
	public delegate void SerialPortStatusChanged(IntPtr serialPortRef, SerialPortStatus status, string msg);
	#endregion
	
	#region Data types	
	// Serial Port Status
	public enum SerialPortStatus
	{
		PortClosed,              // 端口已关闭
		PortOpened,              // 端口已打开
		PortError,               // 端口错误
		PortInvalid,             // 端口不可用
	};
	
	// SerialPort Identity	
	public struct SerialPortRef
	{
		public IntPtr Identity;
	};
	
	#endregion
	
	#region API Wraper
	public class SerialPortDevice
	{
		// Create a serial port for lately operations. return Identity of new serial port.
		// Return NULL if create failed.
		[DllImport("SerialPortDevice.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
		[return: MarshalAs(UnmanagedType.Struct)]
		public static extern SerialPortRef CreateSerialPort();

		// Register handl to receive data from a SerialPort.
		[DllImport("SerialPortDevice.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
		public static extern void RegisterReceivedDataCallback(SerialPortRef sp, ReceivedDataCallback handle);
		
		// Register handl to receive serial port status changed event.
		[DllImport("SerialPortDevice.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
		public static extern void RegisterSerialPortStatusChangedCallback(SerialPortRef sp, SerialPortStatusChanged handle);
		
		// Open a serial port by name.
		// Return false if open failed.
		[DllImport("SerialPortDevice.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
		public static extern bool OpenSerialPort(SerialPortRef sp, string portName, int bauntRate);
		
		// Write and send data by serial port
		// Return the length sent by serial port
		[DllImport("SerialPortDevice.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
		public static extern int SerialPortWrite(SerialPortRef sp, byte[] data, int len);
		
		// Read data from serial port
		// Return the length readed from serial port
		[DllImport("SerialPortDevice.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
		public static extern int SerialPortRead(SerialPortRef sp, ref byte[] buffer, int bufferLen);
		
		// Check wether a serail port is openning.
		[DllImport("SerialPortDevice.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
		public static extern bool IsSerialPortOpenning(SerialPortRef sp);
		
		// Close a serial port.
		[DllImport("SerialPortDevice.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
		public static extern void CloseSerialPort(SerialPortRef sp);
		
		// Destroy a serial port.
		[DllImport("SerialPortDevice.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
		public static extern void DestroySerialPort(SerialPortRef sp);
	};
	#endregion
}

然后把dll拷贝到Unit Prj 的assert目录下,报错:

DllNotFoundException: SerialPortDevice.dll
demo.Start () (at Assets/demo.cs:39)

折腾了很久,发现在导入dll根本不需要后缀“.dll”,即写上dll文件名就OK了,不需要.dll后缀:

		[DllImport("SerialPortDevice", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
		[return: MarshalAs(UnmanagedType.Struct)]

但是这是VS的C#项目的默认做法。一个习惯性的动作导致了这个问题。于是把所有“.dll”删除,OK了。

 

2、Failed to load ‘Assets/PNLib.dll’ with error ‘The specified module could not be found.‘, GetDllDirectory returned ”. If GetDllDirectory returned non empty path, check that you’re using SetDirectoryDll correctly.

这种情况是,找到dll了,但是无法装载。看提示基本还是一头雾水: ‘The specified module could not be found.‘, GetDllDirectory returned ”。看似还是路径的问题。

这个问题测试了很多种情况,基本上是,只要是dll放到Assets目录下(或Assets下创建的Plugin目录下)就都会提示这个错误。而放到Assets外与Assets同目录就OK了,这难道不是Unity的某个bug吗?更奇怪的是之前在Assets下的Plugin里放的另一个dll(SerialPortDevice.dll)就不会有这样的问题。何解?

 

如果还有其他问题,后续继续补充。。。。。。

 

Image not found when using dylib in xcode

Some errors like this:

dyld: Library not loaded: /usr/local/lib/libXXX.dylib
Referenced from: /sers/david/Projekte/Test/build/Debug/Test.app/Contents/MacOS/Test
Reason: image not found

 

Answer:

Make sure to install the command line tools from developer.apple.com and then add the library in your project . Run the following script in your Xcode :Targets->Build Phases->Run Script and just execute :
install_name_tool -id @executable_path/../Frameworks/librayName.dylib “$SRCROOT/librayName.dylib”
——itechnician

 

Important information:

Before going for solution, you should know what is new with dependent library “dylib” in MAC as compare to dependent library “dll” in windows.

The major difference in dylib vs dll is “install name“. The install name is a path baked into the dynamic library that says where to find the library at runtime. It does not matter where you copy your dylibs, It will always point to old path(except without changing install name). You can know original search path(install name) by using command as below

otool -L a.dylib

(just drag dylib in place of a.dylib)

For more detail about install name, refer the link : http://www.chilkatforum.com/questions/4235/how-to-distribute-a-dylib-with-a-mac-os-x-application

Now, solution for changing the new location for dylib(“install name”) is just use install_name_tool as below

install_name_tool -change old path of dylb new path of dylib

you can get old path by otool -L command described above already.(old path i.e. original path)

“install_name_tool -change” can change search path of dylib and executable as well.

You can use this in Xcode by writing in “Run script” file in your project.

——NIlesh Srivastava

Address:

http://stackoverflow.com/questions/4876740/xcode-keeps-searching-dylib-at-wrong-path

 

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

In fact, there are 8 steps to deal with this issue:

1. Drag your custom dylib file to project

2. Click “+” at top of “Build Phases” page to add a “Copy Files” segment

3. Select “Framework” at destination line

4. Click “+” at the bottom of copy files segment to add your dylib

5. Click “+” at top of “Build Phases” page to add a “Run Script” segment

6. Past bellow line in shell box:

install_name_tool -id @executable_path/../Frameworks/libraryName.dylib “$SRCROOT/libraryName.dylib”

7. Change “libraryName.dylib” to your library name

8. Build.

屏幕快照 2014-12-25 下午2.12.25

Good luck! :)

 

Elapsed time in milliseconds in a C program

Use ‘gettimeofdata’ to do this job:

#include <sys/time.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>

/* Return 1 if the difference is negative, otherwise 0.  */
int timeval_subtract(struct timeval *result, struct timeval *t2, struct timeval *t1)
{
    long int diff = (t2->tv_usec + 1000000 * t2->tv_sec) - (t1->tv_usec + 1000000 * t1->tv_sec);
    result->tv_sec = diff / 1000000;
    result->tv_usec = diff % 1000000;

    return (diff<0);
}

void timeval_print(struct timeval *tv)
{
    char buffer[30];
    time_t curtime;

    printf("%ld.%06ld", tv->tv_sec, tv->tv_usec);
    curtime = tv->tv_sec;
    strftime(buffer, 30, "%m-%d-%Y  %T", localtime(&curtime));
    printf(" = %s.%06ld\n", buffer, tv->tv_usec);
}

int main()
{
    struct timeval tvBegin, tvEnd, tvDiff;

    // begin
    gettimeofday(&tvBegin, NULL);
    timeval_print(&tvBegin);

    // lengthy operation
    int i,j;
    for(i=0;i<999999L;++i) {
    	j=sqrt(i);
    }

    //end
    gettimeofday(&tvEnd, NULL);
    timeval_print(&tvEnd);

    // diff
    timeval_subtract(&tvDiff, &tvEnd, &tvBegin);
    printf("%ld.%06ld\n", tvDiff.tv_sec, tvDiff.tv_usec);

    return 0;
}

 

Check this thread to find more formation:

http://stackoverflow.com/questions/1468596/calculating-elapsed-time-in-a-c-program-in-milliseconds

 

Returning Strings from a C++ API to C#

从C++的dll中返回字符串是一种经常用到的C#调用C++函数的方式,但是带有返回值的调用一直是说不清道不明的问题,不细细研究,的确没法用起来。

这里直接给出一个正确的做法,后面会提供一份Google出来的资料,讲的非常好。原理知道了,对于返回结构体也应该是可以按同理搞定的。

 

Code in C++ dll:

// If any error, you can call 'BRGetLastErrorMessage' to get error information
BDR_API char* BRGetLastErrorMessage()
{
    return Error::Instance()->GetLastError();

    // follow code is not platform-cross, so throw away.

#ifdef __OS_XUN__
    return Error::Instance()->GetLastError();
#else
    char* msg = Error::Instance()->GetLastError();
    int len = strlen(msg);

    char* pszReturn = (char*)::CoTaskMemAlloc(len);
    // Copy the contents of szSampleString
    // to the memory pointed to by pszReturn.
    strcpy(pszReturn, msg);
    // Return pszReturn.
    return pszReturn;
#endif
}

 

C# side code:

        // If any error, you can call 'BRGetLastErrorMessage' to get error information
        [DllImport("BVHDataReader.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        //[return: MarshalAs(UnmanagedType.LPStr)]
        private static extern IntPtr BRGetLastErrorMessage();
        public static string strBRGetLastErrorMessage()
        {
            // Receive the pointer to Unicde character array
            // from API.
            IntPtr ptr = BRGetLastErrorMessage();
            // Construct a string from the pointer.
            string str = Marshal.PtrToStringAnsi(ptr);
            // Display the string.
            Console.WriteLine("Returned string : " + str);
            return str;
        }

 

 

下面是同名资料:

Returning Strings from a C++ API to C#

1. Introduction.

1.1 APIs that return strings are very common. However, the internal nature of such APIs, as well as the use of such APIs in managed code, require special attention. This blog will demonstrate both concerns.

1.2 I will present several techniques for returning an unmanaged string to managed code. But before that I shall first provide an in-depth explanation on the low-level activities that goes on behind the scenes. This will pave the way towards easier understanding of the codes presented later in this blog.

2. Behind the Scenes.

2.1 Let’s say we want to declare and use an API written in C++ with the following signature :

char* __stdcall StringReturnAPI01();

This API is to simply return a NULL-terminated character array (a C string).

2.2 To start with, note that a C string has no direct representation in managed code. Hence we simply cannot return a C string and expect the CLR to be able to transform it into a managed string.

2.3 The managed string is non-blittable. It can have several representations in unmanaged code : e.g. C-style strings (ANSI and Unicode-based) and BSTRs. Hence, it is important that you specify this information in the declaration of the unmanaged API, e.g. :

[DllImport("<path to DLL>", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.LPStr)]
public static extern string StringReturnAPI01();

In the above declaration, note that the following line :

[return: MarshalAs(UnmanagedType.LPStr)]

indicates that the return value from the API is to be treated as a NULL-terminated ANSI character array (i.e. a typical C-style string).

2.4 Now this unmanaged C-style string return value will then be used by the CLR to create a managed string object. This is likely achieved by using theMarshal.PtrToStringAnsi() method with the incoming string pointer treated as an IntPtr.

2.5 Now a very important concept which is part and parcel of the whole API calling operation is memory ownership. This is an important concept because it determines who is responsible for the deallocation of this memory. Now the StringReturnAPI01() API supposedly returns a string. The string should thus be considered equivalent to an “out” parameter, It is owned by the receiver of the string, i.e. the C# client code. More precisely, it is the CLR’s Interop Marshaler that is the actual receiver.

2.6 Now being the owner of the returned string, the Interop Marshaler is at liberty to free the memory associated with the string. This is precisely what will happen. When the Interop Marshaler has used the returned string to construct a managed string object, the NULL-terminated ANSI character array pointed to by the returned character pointer will be deallocated.

2.7 Hence it is very important to note the general protocol : the unmanaged code will allocate the memory for the string and the managed side will deallocate it. This is the same basic requirement of “out” parameters.

2.8 Towards this protocol, there are 2 basic ways that memory for an unmanaged string can be allocated (in unmanaged code) and then automatically deallocated by the CLR (more specifically, the interop marshaler) :

  • CoTaskMemAlloc()/Marshal.FreeCoTaskMem().
  • SysAllocString/Marshal.FreeBSTR().

Hence if the unmanaged side used CoTaskMemAlloc() to allocate the string memory, the CLR will use the Marshal.FreeCoTaskMem() method to free this memory.

The SysAllocString/Marshal.FreeBSTR() pair will only be used if the return type is specified as being a BSTR. This is not relevant to the example given in point 2.1 above. I will demonstrate a use of this pair in section 5 later.

2.9 N.B. : Note that the unmanaged side must not use the “new” keyword or the “malloc()” C function to allocate memory. The Interop Marshaler will not be able to free the memory in these situations. This is because the “new” keyword is compiler dependent and the “malloc” function is C-library dependent. CoTaskMemAlloc(), and SysAllocString() on the other hand, are Windows APIs which are standard.

Another important note is that although GlobalAlloc() is also a standard Windows API and it has a counterpart managed freeing method (i.e. Marshal.FreeHGlobal()), the Interop Marshaler will only use the Marshal.FreeCoTaskMem() method for automatic memory freeing of NULL-terminated strings allocated in unmanaged code. Hence do not use GlobalAlloc() unless you intend to free the allocated memory by hand using Marshal.FreeHGlobal() (an example of this is give in section 6 below).

3. Sample Code.

3.1 In this section, based on the principles presented in section 2, I shall present sample codes to demonstrate how to return a string from an unmanaged API and how to declare such an API in managed code.

3.2 The following is a listing of the C++ function which uses CoTaskMemAlloc() :

extern "C" __declspec(dllexport) char*  __stdcall StringReturnAPI01()
{
    char szSampleString[] = "Hello World";
    ULONG ulSize = strlen(szSampleString) + sizeof(char);
    char* pszReturn = NULL;

    pszReturn = (char*)::CoTaskMemAlloc(ulSize);
    // Copy the contents of szSampleString
    // to the memory pointed to by pszReturn.
    strcpy(pszReturn, szSampleString);
    // Return pszReturn.
    return pszReturn;
}

3.4 The C# declaration and sample call :

[DllImport("<path to DLL>", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.LPStr)]
public static extern string StringReturnAPI01();

static void CallUsingStringAsReturnValue()
{
  string strReturn01 = StringReturnAPI01();
  Console.WriteLine("Returned string : " + strReturn01);
}

3.5 Note the argument used for the MarshalAsAttribute : UnmanagedType.LPStr. This indicates to the Interop Marshaler that the return string from StringReturnAPI01() is a pointer to a NULL-terminated ANSI character array.

3.6 What happens under the covers is that the Interop Marshaler uses this pointer to construct a managed string. It likely uses the Marshal.PtrToStringAnsi() method to perform this. The Interop Marshaler will then use the Marshal.FreeCoTaskMem() method to free the character array.

4. Using a BSTR.

4.1 In this section, I shall demonstrate here how to allocate a BSTR in unmanaged code and return it in managed code together with memory deallocation.

4.2 Here is a sample C++ code listing :

extern "C" __declspec(dllexport) BSTR  __stdcall StringReturnAPI02()
{
  return ::SysAllocString((const OLECHAR*)L"Hello World");
}

4.3 And the C# declaration and usage :

[DllImport("<path to DLL>", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.BStr)]
public static extern string StringReturnAPI02();

static void CallUsingBSTRAsReturnValue()
{
  string strReturn = StringReturnAPI02();
  Console.WriteLine("Returned string : " + strReturn);
}

Note the argument used for the MarshalAsAttribute : UnmanagedType.BStr. This indicates to the Interop Marshaler that the return string from StringReturnAPI02() is a BSTR.

4.4 The Interop Marshaler then uses the returned BSTR to construct a managed string. It likely uses the Marshal.PtrToStringBSTR() method to perform this. The Interop Marshaler will then use the Marshal.FreeBSTR() method to free the BSTR.

5. Unicode Strings.

5.1 Unicode strings can be returned easily too as the following sample code will demonstrate.

5.2 Here is a sample C++ code listing :

extern "C" __declspec(dllexport) wchar_t*  __stdcall StringReturnAPI03()
{
  // Declare a sample wide character string.
  wchar_t  wszSampleString[] = L"Hello World";
  ULONG  ulSize = (wcslen(wszSampleString) * sizeof(wchar_t)) + sizeof(wchar_t);
  wchar_t* pwszReturn = NULL;

  pwszReturn = (wchar_t*)::CoTaskMemAlloc(ulSize);
  // Copy the contents of wszSampleString
  // to the memory pointed to by pwszReturn.
  wcscpy(pwszReturn, wszSampleString);
  // Return pwszReturn.
  return pwszReturn;
}

5.3 And the C# declaration and usage :

[DllImport("<path to DLL>", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.LPWStr)]
public static extern string StringReturnAPI03();

static void CallUsingWideStringAsReturnValue()
{
  string strReturn = StringReturnAPI03();
  Console.WriteLine("Returned string : " + strReturn);
}

The fact that a wide charactered string is now returned requires the use of the UnmanagedType.LPWStr argument for the MarshalAsAttribute.

5.4 The Interop Marshaler uses the returned wide-charactered string to construct a managed string. It likely uses the Marshal.PtrToStringUni() method to perform this. The Interop Marshaler will then use the Marshal.FreeCoTaskMem() method to free the wide-charactered string.

6. Low-Level Handling Sample 1.

6.1 In this section, I shall present some code that will hopefully cement the reader’s understanding of the low-level activities that had been explained in section 2 above.

6.2 Instead of using the Interop Marshaler to perform the marshaling and automatic memory deallocation, I shall demonstrate how this can be done by hand in managed code.

6.3 I shall use a new API which resembles the StringReturnAPI01() API which returns a NULL-terminated ANSI character array :

extern "C" __declspec(dllexport) char*  __stdcall PtrReturnAPI01()
{
  char   szSampleString[] = "Hello World";
  ULONG  ulSize = strlen(szSampleString) + sizeof(char);
  char*  pszReturn = NULL;

  pszReturn = (char*)::GlobalAlloc(GMEM_FIXED, ulSize);
  // Copy the contents of szSampleString
  // to the memory pointed to by pszReturn.
  strcpy(pszReturn, szSampleString);
  // Return pszReturn.
  return pszReturn;
}

6.4 And the C# declaration :

[DllImport("<path to DLL>", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr PtrReturnAPI01();

Note that this time, I have indicated that the return value is an IntPtr. There is no [return : …] declaration and so no unmarshaling will be performed by the Interop Marshaler.

6.5 And the C# low-level call :

static void CallUsingLowLevelStringManagement()
{
  // Receive the pointer to ANSI character array
  // from API.
  IntPtr pStr = PtrReturnAPI01();
  // Construct a string from the pointer.
  string str = Marshal.PtrToStringAnsi(pStr);
  // Free the memory pointed to by the pointer.
  Marshal.FreeHGlobal(pStr);
  pStr = IntPtr.Zero;
  // Display the string.
  Console.WriteLine("Returned string : " + str);
}

This code demonstrates an emulation of the Interop Marshaler in unmarshaling a NULL-terminated ANSI string. The returned pointer from PtrReturnAPI01() is used to construct a managed string. The pointer is then freed. The managed string remains intact with a copy of the returned string.

The only difference between this code and the actual one by the Interop Marshaler is that the GlobalAlloc()/Marshal.FreeHGlobal() pair is used. The Interop Marshaler always uses Marshal.FreeCoTaskMem() and expects the unmanaged code to use ::CoTaskMemAlloc().

7. Low-Level Handling Sample 2.

7.1 In this final section, I shall present one more low-level string handling technique similar to the one presented in section 6 above.

7.2 Again we do not use the Interop Marshaler to perform the marshaling and memory deallocation. Additionally, we will also not release the memory of the returned string.

7.3 I shall use a new API which simply returns a NULL-terminated Unicode character array which has been allocated in a global unmanaged memory :

wchar_t gwszSampleString[] = L"Global Hello World";

extern "C" __declspec(dllexport) wchar_t*  __stdcall PtrReturnAPI02()
{
  return gwszSampleString;
}

This API returns a pointer to the pre-allocated global Unicode string “gwszSampleString”. Because it is allocated in global memory and may be shared by various functions in the DLL, it is crucial that it is not deleted.

7.4 The C# declaration for PtrReturnAPI02() is listed below :

[DllImport("<path to DLL>", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr PtrReturnAPI02();

Again, there is no declaration for interop marshaling (no use of the [return : …] declaration). The returned IntPtr is returned as is.

7.5 And a sample C# code to manage the returned IntPtr :

static void CallUsingLowLevelStringManagement02()
{
  // Receive the pointer to Unicde character array
  // from API.
  IntPtr pStr = PtrReturnAPI02();
  // Construct a string from the pointer.
  string str = Marshal.PtrToStringUni(pStr);
  // Display the string.
  Console.WriteLine("Returned string : " + str);
}

Here, the returned IntPtr is used to construct a managed string from an unmanaged NULL-terminated Unicode string. The memory of the unmanaged Unicode string is then left alone and is not deleted.

Note that because a mere IntPtr is returned, there is no way to know whether the returned string is ANSI or Unicode. In fact, there is no way to know whether the IntPtr actually points to a NULL-terminated string at all. This knowledge has to be known in advance.

7.6 Furthermore, the returned IntPtr must not point to some temporary string location (e.g. one allocated on the stack). If this was so, the temporary string may be deleted once the API returns. The following is an example :

extern "C" __declspec(dllexport) char* __stdcall PtrReturnAPI03()
{
  char szSampleString[] = "Hello World";
  return szSampleString;
}

By the time this API returns, the string contained in “szSampleString” may be completely wiped out or be filled with random data. The random data may not contain any NULL character until many bytes later. A crash may ensue a C# call like the following :

IntPtr pStr = PtrReturnAPI03();
// Construct a string from the pointer.
string str = Marshal.PtrToStringAnsi(pStr);

Link:http://limbioliong.wordpress.com/2011/06/16/returning-strings-from-a-c-api/

 

Capturing the Desktop Screen with the Mouse Cursor Image

下面的文章介绍了两种截屏方式及如何“截取”光标的方法。

通常情况下是无法截取光标的,因为光标不属于窗口中的windows元素,而是独立与窗口系统的另一层东西,叫Hot Spot。

申明:本文以下内容归原作者所有!

By 27 Jan 2006

DesktopCaptureWithMouse

Introduction

This article shows how you can capture screen images including the mouse cursor.

Background

Screen capturing is a very useful way of resource sharing as used in applications like Remote Desktop, Virtual Network Computing (VNC), where a user can access, view, and interact with a remote desktop as his own desktop. Also, it is used in non ethical applications like hacking applications, where a hacker can hack a computer using some malicious server application, and the server then frequently takes screenshots of the prey machine and sends them to the clients. You will see a lot of source code resources over the internet discussing how to take screenshots of the desktop or an area of the desktop but none of them discuss how to capture the mouse cursor bitmap with the screenshot. Sometimes it becomes necessary to capture the mouse to see the whole activity of the hacked machine. First, we will discuss here what the actual problem is.

Problem

Most of us think that the mouse cursor image is a part of the desktop display but actually it works on an upper layer over the desktop. Windows always tracks mouse with a “Hot Spot”, the actual position seen by the Windows.

Currently, there are two common ways to capture and manipulate the desktop image:

  1. Copy the desktop bitmap data from Video Memory to the System Memory. Do processing and then again blit it back to the Video Memory. This can be easily done using the BitBlt() or the StretchBlt() APIs provided by Win32.
  2. Another way is to directly manipulate the desktop bitmap in the Video Memory if enough memory is available as provided by DirectDraw.

Both of these don’t provide us the facility to capture the mouse cursor image with the desktop image.

Solution

The solution to the problem of capturing the mouse cursor image with the desktop image is quite simple.

  1. First, get the bitmap of the screen using BitBlt(). I have provided a simple function namedCaptureDesktop() in the CaptureScreen.cs file that captures the screen bitmap as almost all the codes available over the internet do.
  2. Then capture the mouse cursor bitmap as:

    Get Cursor Icon:

    First, get the cursor information using the Win32 GetCursorInfo(). The function fills the CURSORINFOstructure provided as a parameter. Don’t forget to initialize the cbSize member of the structure before passing it as an argument. Then we check whether the cursor is visible or not, by checking for the equality of the flagsmember of the filled structure with the CURSOR_SHOWING constant. If they are equal then we get the handle to the cursor icon using the CopyIcon() function that takes the hCursor member of the above filled structure.

    Get Cursor Position:

    Now, we have to get the Icon information so that we can get the hotspot position. This information is easily retrieved using the GetIconInfo() function. Here is the C# implementation of the mouse capturing function:

    static Bitmap CaptureCursor(ref int x, ref int y)
    {
       Bitmap bmp;
       IntPtr hicon;
       Win32Stuff.CURSORINFO ci = new Win32Stuff.CURSORINFO(); 
       Win32Stuff.ICONINFO icInfo;
       ci.cbSize = Marshal.SizeOf(ci);
       if(Win32Stuff.GetCursorInfo(out ci))
       {
           if (ci.flags == Win32Stuff.CURSOR_SHOWING)
           { 
               hicon = Win32Stuff.CopyIcon(ci.hCursor);
               if(Win32Stuff.GetIconInfo(hicon, out icInfo))
               {
                   x = ci.ptScreenPos.x - ((int)icInfo.xHotspot);
                   y = ci.ptScreenPos.y - ((int)icInfo.yHotspot);
                   Icon ic = Icon.FromHandle(hicon);
                   bmp = ic.ToBitmap(); 
    
                   return bmp;
               }
           }
       }
       return null;
    }
  3. We now have both the bitmaps, i.e., the desktop bitmap and the mouse cursor bitmap with its position on the screen. Now, it’s time to place the mouse cursor bitmap on the desktop bitmap. I have provided the following function that places the mouse cursor image over the desktop bitmap at the proper position:
    public static Bitmap CaptureDesktopWithCursor()
    {     
       int cursorX = 0;
       int cursorY = 0;
       Bitmap desktopBMP;
       Bitmap cursorBMP;
       Bitmap finalBMP;
       Graphics g;
       Rectangle r;
       desktopBMP = CaptureDesktop();
       cursorBMP = CaptureCursor(ref cursorX, ref cursorY);
       if(desktopBMP != null)
       {
           if (cursorBMP != null)
           {
               r = new Rectangle(cursorX, cursorY, 
                       cursorBMP.Width, cursorBMP.Height);
               g = Graphics.FromImage(desktopBMP);
               g.DrawImage(cursorBMP, r);
               g.Flush();
               return desktopBMP;
           }
           else
               return desktopBMP;
       }
       return null;
    }
  4. The bitmap with the cursor is now ready to be rendered over a viewer surface (a PictureBox used here). Since the viewer’s visible area is smaller than the desktop bitmap area, scaling has been used in the function that displays the cooked desktop image.
    // ssWithMouseViewer is the PictureBox control
    private void Display(Bitmap desktop)
    {
        Graphics g;
        Rectangle r;
        if(desktop != null)
        {
            r = new Rectangle(0,0,ssWithMouseViewer.Width, 
                                ssWithMouseViewer.Height);
            g = ssWithMouseViewer.CreateGraphics();
            g.DrawImage(desktop,r);
            g.Flush();
        }
    }

Note

The binary image provided is compiled with Visual Studio .NET 2005 so you have to install .NET Framework 2.0.

Known problems

I have tested the application on my machine with no known problems, so I expect the same behavior on your machine.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

本文转自:http://www.codeproject.com/Articles/12850/Capturing-the-Desktop-Screen-with-the-Mouse-Cursor

十大滤波算法程序大全(Arduino精编无错版)

最近用Arduino做电子秤,为了解决数据的跳变研究了不少滤波算法。网上能找到大把的十大滤波算法帖子,每一篇都不太一样,都号称精编啊,除错啊什么的,可是放到板子里却没一个能正常跑起来的。于是决定自己整理一下这些程序,完美移植到Arduino中。

所以大家看到这个帖子的时候,不要怀疑我重复发帖。我的代码都是经过反复试验,复制到Arduino中就能开跑的成品代码,移植到自己的程序中非常方便。而且都仔细研究了各个算法,把错误都修正了的(别的程序连冒泡算法都是溢出的,不信自己找来细看看),所以也算个小原创吧,在别人基础上的原创。

转载请注明出处:极客工坊  http://www.geek-workshop.com/thread-7694-1-1.html

By shenhaiyu 2013-11-01

 

1、限幅滤波法(又称程序判断滤波法)
2、中位值滤波法
3、算术平均滤波法
4、递推平均滤波法(又称滑动平均滤波法)
5、中位值平均滤波法(又称防脉冲干扰平均滤波法)
6、限幅平均滤波法
7、一阶滞后滤波法
8、加权递推平均滤波法
9、消抖滤波法
10、限幅消抖滤波法
11、新增加 卡尔曼滤波(非扩展卡尔曼),感谢zhangzhe0617分享

程序默认对int类型数据进行滤波,如需要对其他类型进行滤波,只需要把程序中所有int替换成long、float或者double即可。

1、限幅滤波法(又称程序判断滤波法)

ARDUINO 代码
/*
A、名称:限幅滤波法(又称程序判断滤波法)
B、方法:
    根据经验判断,确定两次采样允许的最大偏差值(设为A),
    每次检测到新值时判断:
    如果本次值与上次值之差<=A,则本次值有效,
    如果本次值与上次值之差>A,则本次值无效,放弃本次值,用上次值代替本次值。
C、优点:
    能有效克服因偶然因素引起的脉冲干扰。
D、缺点:
    无法抑制那种周期性的干扰。
    平滑度差。
E、整理:shenhaiyu 2013-11-01
*/
 
int Filter_Value;
int Value;
 
void setup() {
  Serial.begin(9600);       // 初始化串口通信
  randomSeed(analogRead(0)); // 产生随机种子
  Value = 300;
}
 
void loop() {
  Filter_Value = Filter();       // 获得滤波器输出值
  Value = Filter_Value;          // 最近一次有效采样的值,该变量为全局变量
  Serial.println(Filter_Value); // 串口输出
  delay(50);
}
 
// 用于随机产生一个300左右的当前值
int Get_AD() {
  return random(295, 305);
}
 
// 限幅滤波法(又称程序判断滤波法)
#define FILTER_A 1
int Filter() {
  int NewValue;
  NewValue = Get_AD();
  if(((NewValue - Value) > FILTER_A) || ((Value - NewValue) > FILTER_A))
    return Value;
  else
    return NewValue;
}

2、中位值滤波法

ARDUINO 代码
/*
A、名称:中位值滤波法
B、方法:
    连续采样N次(N取奇数),把N次采样值按大小排列,
    取中间值为本次有效值。
C、优点:
    能有效克服因偶然因素引起的波动干扰;
    对温度、液位的变化缓慢的被测参数有良好的滤波效果。
D、缺点:
    对流量、速度等快速变化的参数不宜。
E、整理:shenhaiyu 2013-11-01
*/
 
int Filter_Value;
 
void setup() {
  Serial.begin(9600);       // 初始化串口通信
  randomSeed(analogRead(0)); // 产生随机种子
}
 
void loop() {
  Filter_Value = Filter();       // 获得滤波器输出值
  Serial.println(Filter_Value); // 串口输出
  delay(50);
}
 
// 用于随机产生一个300左右的当前值
int Get_AD() {
  return random(295, 305);
}
 
// 中位值滤波法
#define FILTER_N 101
int Filter() {
  int filter_buf[FILTER_N];
  int i, j;
  int filter_temp;
  for(i = 0; i < FILTER_N; i++) {
    filter_buf[i] = Get_AD();
    delay(1);
  }
  // 采样值从小到大排列(冒泡法)
  for(j = 0; j < FILTER_N - 1; j++) {
    for(i = 0; i < FILTER_N - 1 - j; i++) {
      if(filter_buf[i] > filter_buf[i + 1]) {
        filter_temp = filter_buf[i];
        filter_buf[i] = filter_buf[i + 1];
        filter_buf[i + 1] = filter_temp;
      }
    }
  }
  return filter_buf[(FILTER_N - 1) / 2];
}

3、算术平均滤波法

ARDUINO 代码
/*
A、名称:算术平均滤波法
B、方法:
    连续取N个采样值进行算术平均运算:
    N值较大时:信号平滑度较高,但灵敏度较低;
    N值较小时:信号平滑度较低,但灵敏度较高;
    N值的选取:一般流量,N=12;压力:N=4。
C、优点:
    适用于对一般具有随机干扰的信号进行滤波;
    这种信号的特点是有一个平均值,信号在某一数值范围附近上下波动。
D、缺点:
    对于测量速度较慢或要求数据计算速度较快的实时控制不适用;
    比较浪费RAM。
E、整理:shenhaiyu 2013-11-01
*/
 
int Filter_Value;
 
void setup() {
  Serial.begin(9600);       // 初始化串口通信
  randomSeed(analogRead(0)); // 产生随机种子
}
 
void loop() {
  Filter_Value = Filter();       // 获得滤波器输出值
  Serial.println(Filter_Value); // 串口输出
  delay(50);
}
 
// 用于随机产生一个300左右的当前值
int Get_AD() {
  return random(295, 305);
}
 
// 算术平均滤波法
#define FILTER_N 12
int Filter() {
  int i;
  int filter_sum = 0;
  for(i = 0; i < FILTER_N; i++) {
    filter_sum += Get_AD();
    delay(1);
  }
  return (int)(filter_sum / FILTER_N);
}

4、递推平均滤波法(又称滑动平均滤波法)

ARDUINO 代码
/*
A、名称:递推平均滤波法(又称滑动平均滤波法)
B、方法:
    把连续取得的N个采样值看成一个队列,队列的长度固定为N,
    每次采样到一个新数据放入队尾,并扔掉原来队首的一次数据(先进先出原则),
    把队列中的N个数据进行算术平均运算,获得新的滤波结果。
    N值的选取:流量,N=12;压力,N=4;液面,N=4-12;温度,N=1-4。
C、优点:
    对周期性干扰有良好的抑制作用,平滑度高;
    适用于高频振荡的系统。
D、缺点:
    灵敏度低,对偶然出现的脉冲性干扰的抑制作用较差;
    不易消除由于脉冲干扰所引起的采样值偏差;
    不适用于脉冲干扰比较严重的场合;
    比较浪费RAM。
E、整理:shenhaiyu 2013-11-01
*/
 
int Filter_Value;
 
void setup() {
  Serial.begin(9600);       // 初始化串口通信
  randomSeed(analogRead(0)); // 产生随机种子
}
 
void loop() {
  Filter_Value = Filter();       // 获得滤波器输出值
  Serial.println(Filter_Value); // 串口输出
  delay(50);
}
 
// 用于随机产生一个300左右的当前值
int Get_AD() {
  return random(295, 305);
}
 
// 递推平均滤波法(又称滑动平均滤波法)
#define FILTER_N 12
int filter_buf[FILTER_N + 1];
int Filter() {
  int i;
  int filter_sum = 0;
  filter_buf[FILTER_N] = Get_AD();
  for(i = 0; i < FILTER_N; i++) {
    filter_buf[i] = filter_buf[i + 1]; // 所有数据左移,低位仍掉
    filter_sum += filter_buf[i];
  }
  return (int)(filter_sum / FILTER_N);
}

5、中位值平均滤波法(又称防脉冲干扰平均滤波法)

ARDUINO 代码
/*
A、名称:中位值平均滤波法(又称防脉冲干扰平均滤波法)
B、方法:
    采一组队列去掉最大值和最小值后取平均值,
    相当于“中位值滤波法”+“算术平均滤波法”。
    连续采样N个数据,去掉一个最大值和一个最小值,
    然后计算N-2个数据的算术平均值。
    N值的选取:3-14。
C、优点:
    融合了“中位值滤波法”+“算术平均滤波法”两种滤波法的优点。
    对于偶然出现的脉冲性干扰,可消除由其所引起的采样值偏差。
    对周期干扰有良好的抑制作用。
    平滑度高,适于高频振荡的系统。
D、缺点:
    计算速度较慢,和算术平均滤波法一样。
    比较浪费RAM。
E、整理:shenhaiyu 2013-11-01
*/
 
int Filter_Value;
 
void setup() {
  Serial.begin(9600);       // 初始化串口通信
  randomSeed(analogRead(0)); // 产生随机种子
}
 
void loop() {
  Filter_Value = Filter();       // 获得滤波器输出值
  Serial.println(Filter_Value); // 串口输出
  delay(50);
}
 
// 用于随机产生一个300左右的当前值
int Get_AD() {
  return random(295, 305);
}
 
// 中位值平均滤波法(又称防脉冲干扰平均滤波法)(算法1)
#define FILTER_N 100
int Filter() {
  int i, j;
  int filter_temp, filter_sum = 0;
  int filter_buf[FILTER_N];
  for(i = 0; i < FILTER_N; i++) {
    filter_buf[i] = Get_AD();
    delay(1);
  }
  // 采样值从小到大排列(冒泡法)
  for(j = 0; j < FILTER_N - 1; j++) {
    for(i = 0; i < FILTER_N - 1 - j; i++) {
      if(filter_buf[i] > filter_buf[i + 1]) {
        filter_temp = filter_buf[i];
        filter_buf[i] = filter_buf[i + 1];
        filter_buf[i + 1] = filter_temp;
      }
    }
  }
  // 去除最大最小极值后求平均
  for(i = 1; i < FILTER_N - 1; i++) filter_sum += filter_buf[i];
  return filter_sum / (FILTER_N - 2);
}
 
 
//  中位值平均滤波法(又称防脉冲干扰平均滤波法)(算法2)
/*
#define FILTER_N 100
int Filter() {
  int i;
  int filter_sum = 0;
  int filter_max, filter_min;
  int filter_buf[FILTER_N];
  for(i = 0; i < FILTER_N; i++) {
    filter_buf[i] = Get_AD();
    delay(1);
  }
  filter_max = filter_buf[0];
  filter_min = filter_buf[0];
  filter_sum = filter_buf[0];
  for(i = FILTER_N - 1; i > 0; i--) {
    if(filter_buf[i] > filter_max)
      filter_max=filter_buf[i];
    else if(filter_buf[i] < filter_min)
      filter_min=filter_buf[i];
    filter_sum = filter_sum + filter_buf[i];
    filter_buf[i] = filter_buf[i - 1];
  }
  i = FILTER_N - 2;
  filter_sum = filter_sum - filter_max - filter_min + i / 2; // +i/2 的目的是为了四舍五入
  filter_sum = filter_sum / i;
  return filter_sum;
}*/

6、限幅平均滤波法

ARDUINO 代码
/*
A、名称:限幅平均滤波法
B、方法:
    相当于“限幅滤波法”+“递推平均滤波法”;
    每次采样到的新数据先进行限幅处理,
    再送入队列进行递推平均滤波处理。
C、优点:
    融合了两种滤波法的优点;
    对于偶然出现的脉冲性干扰,可消除由于脉冲干扰所引起的采样值偏差。
D、缺点:
    比较浪费RAM。
E、整理:shenhaiyu 2013-11-01
*/
 
#define FILTER_N 12
int Filter_Value;
int filter_buf[FILTER_N];
 
void setup() {
  Serial.begin(9600);       // 初始化串口通信
  randomSeed(analogRead(0)); // 产生随机种子
  filter_buf[FILTER_N - 2] = 300;
}
 
void loop() {
  Filter_Value = Filter();       // 获得滤波器输出值
  Serial.println(Filter_Value); // 串口输出
  delay(50);
}
 
// 用于随机产生一个300左右的当前值
int Get_AD() {
  return random(295, 305);
}
 
// 限幅平均滤波法
#define FILTER_A 1
int Filter() {
  int i;
  int filter_sum = 0;
  filter_buf[FILTER_N - 1] = Get_AD();
  if(((filter_buf[FILTER_N - 1] - filter_buf[FILTER_N - 2]) > FILTER_A) || ((filter_buf[FILTER_N - 2] - filter_buf[FILTER_N - 1]) > FILTER_A))
    filter_buf[FILTER_N - 1] = filter_buf[FILTER_N - 2];
  for(i = 0; i < FILTER_N - 1; i++) {
    filter_buf[i] = filter_buf[i + 1];
    filter_sum += filter_buf[i];
  }
  return (int)filter_sum / (FILTER_N - 1);
}

7、一阶滞后滤波法

ARDUINO 代码
/*
A、名称:一阶滞后滤波法
B、方法:
    取a=0-1,本次滤波结果=(1-a)*本次采样值+a*上次滤波结果。
C、优点:
    对周期性干扰具有良好的抑制作用;
    适用于波动频率较高的场合。
D、缺点:
    相位滞后,灵敏度低;
    滞后程度取决于a值大小;
    不能消除滤波频率高于采样频率1/2的干扰信号。
E、整理:shenhaiyu 2013-11-01
*/
 
int Filter_Value;
int Value;
 
void setup() {
  Serial.begin(9600);       // 初始化串口通信
  randomSeed(analogRead(0)); // 产生随机种子
  Value = 300;
}
 
void loop() {
  Filter_Value = Filter();       // 获得滤波器输出值
  Serial.println(Filter_Value); // 串口输出
  delay(50);
}
 
// 用于随机产生一个300左右的当前值
int Get_AD() {
  return random(295, 305);
}
 
// 一阶滞后滤波法
#define FILTER_A 0.01
int Filter() {
  int NewValue;
  NewValue = Get_AD();
  Value = (int)((float)NewValue * FILTER_A + (1.0 - FILTER_A) * (float)Value);
  return Value;
}

8、加权递推平均滤波法

ARDUINO 代码
/*
A、名称:加权递推平均滤波法
B、方法:
    是对递推平均滤波法的改进,即不同时刻的数据加以不同的权;
    通常是,越接近现时刻的数据,权取得越大。
    给予新采样值的权系数越大,则灵敏度越高,但信号平滑度越低。
C、优点:
    适用于有较大纯滞后时间常数的对象,和采样周期较短的系统。
D、缺点:
    对于纯滞后时间常数较小、采样周期较长、变化缓慢的信号;
    不能迅速反应系统当前所受干扰的严重程度,滤波效果差。
E、整理:shenhaiyu 2013-11-01
*/
 
int Filter_Value;
 
void setup() {
  Serial.begin(9600);       // 初始化串口通信
  randomSeed(analogRead(0)); // 产生随机种子
}
 
void loop() {
  Filter_Value = Filter();       // 获得滤波器输出值
  Serial.println(Filter_Value); // 串口输出
  delay(50);
}
 
// 用于随机产生一个300左右的当前值
int Get_AD() {
  return random(295, 305);
}
 
// 加权递推平均滤波法
#define FILTER_N 12
int coe[FILTER_N] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};    // 加权系数表
int sum_coe = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12; // 加权系数和
int filter_buf[FILTER_N + 1];
int Filter() {
  int i;
  int filter_sum = 0;
  filter_buf[FILTER_N] = Get_AD();
  for(i = 0; i < FILTER_N; i++) {
    filter_buf[i] = filter_buf[i + 1]; // 所有数据左移,低位仍掉
    filter_sum += filter_buf[i] * coe[i];
  }
  filter_sum /= sum_coe;
  return filter_sum;
}

9、消抖滤波法

ARDUINO 代码
/*
A、名称:消抖滤波法
B、方法:
    设置一个滤波计数器,将每次采样值与当前有效值比较:
    如果采样值=当前有效值,则计数器清零;
    如果采样值<>当前有效值,则计数器+1,并判断计数器是否>=上限N(溢出);
    如果计数器溢出,则将本次值替换当前有效值,并清计数器。
C、优点:
    对于变化缓慢的被测参数有较好的滤波效果;
    可避免在临界值附近控制器的反复开/关跳动或显示器上数值抖动。
D、缺点:
    对于快速变化的参数不宜;
    如果在计数器溢出的那一次采样到的值恰好是干扰值,则会将干扰值当作有效值导入系统。
E、整理:shenhaiyu 2013-11-01
*/
 
int Filter_Value;
int Value;
 
void setup() {
  Serial.begin(9600);       // 初始化串口通信
  randomSeed(analogRead(0)); // 产生随机种子
  Value = 300;
}
 
void loop() {
  Filter_Value = Filter();       // 获得滤波器输出值
  Serial.println(Filter_Value); // 串口输出
  delay(50);
}
 
// 用于随机产生一个300左右的当前值
int Get_AD() {
  return random(295, 305);
}
 
// 消抖滤波法
#define FILTER_N 12
int i = 0;
int Filter() {
  int new_value;
  new_value = Get_AD();
  if(Value != new_value) {
    i++;
    if(i > FILTER_N) {
      i = 0;
      Value = new_value;
    }
  }
  else
    i = 0;
  return Value;
}

10、限幅消抖滤波法

ARDUINO 代码
/*
A、名称:限幅消抖滤波法
B、方法:
    相当于“限幅滤波法”+“消抖滤波法”;
    先限幅,后消抖。
C、优点:
    继承了“限幅”和“消抖”的优点;
    改进了“消抖滤波法”中的某些缺陷,避免将干扰值导入系统。
D、缺点:
    对于快速变化的参数不宜。
E、整理:shenhaiyu 2013-11-01
*/
 
int Filter_Value;
int Value;
 
void setup() {
  Serial.begin(9600);       // 初始化串口通信
  randomSeed(analogRead(0)); // 产生随机种子
  Value = 300;
}
 
void loop() {
  Filter_Value = Filter();       // 获得滤波器输出值
  Serial.println(Filter_Value); // 串口输出
  delay(50);
}
 
// 用于随机产生一个300左右的当前值
int Get_AD() {
  return random(295, 305);
}
 
// 限幅消抖滤波法
#define FILTER_A 1
#define FILTER_N 5
int i = 0;
int Filter() {
  int NewValue;
  int new_value;
  NewValue = Get_AD();
  if(((NewValue - Value) > FILTER_A) || ((Value - NewValue) > FILTER_A))
    new_value = Value;
  else
    new_value = NewValue;
  if(Value != new_value) {
    i++;
    if(i > FILTER_N) {
      i = 0;
      Value = new_value;
    }
  }
  else
    i = 0;
  return Value;
}

 

11、卡尔曼滤波(非扩展卡尔曼)

ARDUINO 代码
#include <Wire.h> // I2C library, gyroscope

// Accelerometer ADXL345
#define ACC (0x53)    //ADXL345 ACC address
#define A_TO_READ (6)        //num of bytes we are going to read each time (two bytes for each axis)


// Gyroscope ITG3200 
#define GYRO 0x68 // gyro address, binary = 11101000 when AD0 is connected to Vcc (see schematics of your breakout board)
#define G_SMPLRT_DIV 0x15   
#define G_DLPF_FS 0x16   
#define G_INT_CFG 0x17
#define G_PWR_MGM 0x3E

#define G_TO_READ 8 // 2 bytes for each axis x, y, z


// offsets are chip specific. 
int a_offx = 0;
int a_offy = 0;
int a_offz = 0;

int g_offx = 0;
int g_offy = 0;
int g_offz = 0;
////////////////////////

////////////////////////
char str[512]; 

void initAcc() {
  //Turning on the ADXL345
  writeTo(ACC, 0x2D, 0);      
  writeTo(ACC, 0x2D, 16);
  writeTo(ACC, 0x2D, 8);
  //by default the device is in +-2g range reading
}

void getAccelerometerData(int* result) {
  int regAddress = 0x32;    //first axis-acceleration-data register on the ADXL345
  byte buff[A_TO_READ];
  
  readFrom(ACC, regAddress, A_TO_READ, buff); //read the acceleration data from the ADXL345
  
  //each axis reading comes in 10 bit resolution, ie 2 bytes.  Least Significat Byte first!!
  //thus we are converting both bytes in to one int
  result[0] = (((int)buff[1]) << 8) | buff[0] + a_offx;   
  result[1] = (((int)buff[3]) << 8) | buff[2] + a_offy;
  result[2] = (((int)buff[5]) << 8) | buff[4] + a_offz;
}

//initializes the gyroscope
void initGyro()
{
  /*****************************************
  * ITG 3200
  * power management set to:
  * clock select = internal oscillator
  *     no reset, no sleep mode
  *   no standby mode
  * sample rate to = 125Hz
  * parameter to +/- 2000 degrees/sec
  * low pass filter = 5Hz
  * no interrupt
  ******************************************/
  writeTo(GYRO, G_PWR_MGM, 0x00);
  writeTo(GYRO, G_SMPLRT_DIV, 0x07); // EB, 50, 80, 7F, DE, 23, 20, FF
  writeTo(GYRO, G_DLPF_FS, 0x1E); // +/- 2000 dgrs/sec, 1KHz, 1E, 19
  writeTo(GYRO, G_INT_CFG, 0x00);
}


void getGyroscopeData(int * result)
{
  /**************************************
  Gyro ITG-3200 I2C
  registers:
  temp MSB = 1B, temp LSB = 1C
  x axis MSB = 1D, x axis LSB = 1E
  y axis MSB = 1F, y axis LSB = 20
  z axis MSB = 21, z axis LSB = 22
  *************************************/

  int regAddress = 0x1B;
  int temp, x, y, z;
  byte buff[G_TO_READ];
  
  readFrom(GYRO, regAddress, G_TO_READ, buff); //read the gyro data from the ITG3200
  
  result[0] = ((buff[2] << 8) | buff[3]) + g_offx;
  result[1] = ((buff[4] << 8) | buff[5]) + g_offy;
  result[2] = ((buff[6] << 8) | buff[7]) + g_offz;
  result[3] = (buff[0] << 8) | buff[1]; // temperature
  
}


float xz=0,yx=0,yz=0;
float p_xz=1,p_yx=1,p_yz=1;
float q_xz=0.0025,q_yx=0.0025,q_yz=0.0025;
float k_xz=0,k_yx=0,k_yz=0;
float r_xz=0.25,r_yx=0.25,r_yz=0.25;
  //int acc_temp[3];
  //float acc[3];
  int acc[3];
  int gyro[4];
  float Axz;
  float Ayx;
  float Ayz;
  float t=0.025;
void setup()
{
  Serial.begin(9600);
  Wire.begin();
  initAcc();
  initGyro();
  
}

//unsigned long timer = 0;
//float o;
void loop()
{
  
  getAccelerometerData(acc);
  getGyroscopeData(gyro);
  //timer = millis();
  sprintf(str, "%d,%d,%d,%d,%d,%d", acc[0],acc[1],acc[2],gyro[0],gyro[1],gyro[2]);
  
  //acc[0]=acc[0];
  //acc[2]=acc[2];
  //acc[1]=acc[1];
  //r=sqrt(acc[0]*acc[0]+acc[1]*acc[1]+acc[2]*acc[2]);
  gyro[0]=gyro[0]/ 14.375;
  gyro[1]=gyro[1]/ (-14.375);
  gyro[2]=gyro[2]/ 14.375;
  
   
  Axz=(atan2(acc[0],acc[2]))*180/PI;
  Ayx=(atan2(acc[0],acc[1]))*180/PI;
  /*if((acc[0]!=0)&&(acc[1]!=0))
    {
      Ayx=(atan2(acc[0],acc[1]))*180/PI;
    }
    else
    {
      Ayx=t*gyro[2];
    }*/
  Ayz=(atan2(acc[1],acc[2]))*180/PI;
  
  
//kalman filter
  calculate_xz();
  calculate_yx();
  calculate_yz();
  
  //sprintf(str, "%d,%d,%d", xz_1, xy_1, x_1);
  //Serial.print(xz);Serial.print(",");
  //Serial.print(yx);Serial.print(",");
  //Serial.print(yz);Serial.print(",");
  //sprintf(str, "%d,%d,%d,%d,%d,%d", acc[0],acc[1],acc[2],gyro[0],gyro[1],gyro[2]);
  //sprintf(str, "%d,%d,%d",gyro[0],gyro[1],gyro[2]);
    Serial.print(Axz);Serial.print(",");
    //Serial.print(Ayx);Serial.print(",");
    //Serial.print(Ayz);Serial.print(",");
  //Serial.print(str);
  //o=gyro[2];//w=acc[2];
  //Serial.print(o);Serial.print(",");
  //Serial.print(w);Serial.print(",");
  Serial.print("\n");

  
  //delay(50);
}
void calculate_xz()
{

xz=xz+t*gyro[1];
p_xz=p_xz+q_xz;
k_xz=p_xz/(p_xz+r_xz);
xz=xz+k_xz*(Axz-xz);
p_xz=(1-k_xz)*p_xz;
}
void calculate_yx()
{
  
  yx=yx+t*gyro[2];
  p_yx=p_yx+q_yx;
  k_yx=p_yx/(p_yx+r_yx);
  yx=yx+k_yx*(Ayx-yx);
  p_yx=(1-k_yx)*p_yx;

}
void calculate_yz()
{
  yz=yz+t*gyro[0];
  p_yz=p_yz+q_yz;
  k_yz=p_yz/(p_yz+r_yz);
  yz=yz+k_yz*(Ayz-yz);
  p_yz=(1-k_yz)*p_yz;

}


//---------------- Functions
//Writes val to address register on ACC
void writeTo(int DEVICE, byte address, byte val) {
   Wire.beginTransmission(DEVICE); //start transmission to ACC 
   Wire.write(address);        // send register address
   Wire.write(val);        // send value to write
   Wire.endTransmission(); //end transmission
}


//reads num bytes starting from address register on ACC in to buff array
void readFrom(int DEVICE, byte address, int num, byte buff[]) {
  Wire.beginTransmission(DEVICE); //start transmission to ACC 
  Wire.write(address);        //sends address to read from
  Wire.endTransmission(); //end transmission
  
  Wire.beginTransmission(DEVICE); //start transmission to ACC
  Wire.requestFrom(DEVICE, num);    // request 6 bytes from ACC
  
  int i = 0;
  while(Wire.available())    //ACC may send less than requested (abnormal)
  { 
    buff[i] = Wire.read(); // receive a byte
    i++;
  }
  Wire.endTransmission(); //end transmission
}

 

本文转自:http://www.geek-workshop.com/thread-7694-1-1.html

 

三种截取屏幕的方式

Various methods for capturing the screen

By 19 Sep 2006

Contents

Introduction

Some times, we want to capture the contents of the entire screen programmatically. The following explains how it can be done. Typically, the immediate options we have, among others, are using GDI and/or DirectX. Another option that is worth considering is Windows Media API. Here, we would consider each of them and see how they can be used for our purpose. In each of these approaches, once we get the screenshot into our application defined memory or bitmap, we can use it in generating a movie. Refer to the article Create Movie From HBitmap for more details about creating movies from bitmap sequences programmatically.

Capture it the GDI way

When performance is not an issue and when all that we want is just a snapshot of the desktop, we can consider the GDI option. This mechanism is based on the simple principle that the desktop is also a window – that is it has a window Handle (HWND) and a device context (DC). If we can get the device context of the desktop to be captured, we can just blit those contents to our application defined device context in the normal way. And getting the device context of the desktop is pretty straightforward if we know its window handle – which can be achieved through the function GetDesktopWindow(). Thus, the steps involved are:

  1. Acquire the Desktop window handle using the function GetDesktopWindow();
  2. Get the DC of the desktop window using the function GetDC();
  3. Create a compatible DC for the Desktop DC and a compatible bitmap to select into that compatible DC. These can be done using CreateCompatibleDC() and CreateCompatibleBitmap(); selecting the bitmap into our DC can be done with SelectObject();
  4. Whenever you are ready to capture the screen, just blit the contents of the Desktop DC into the created compatible DC – that’s all – you are done. The compatible bitmap we created now contains the contents of the screen at the moment of the capture.
  5. Do not forget to release the objects when you are done. Memory is precious (for the other applications).

Example

Void CaptureScreen()
{
    int nScreenWidth = GetSystemMetrics(SM_CXSCREEN);
    int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);
    HWND hDesktopWnd = GetDesktopWindow();
    HDC hDesktopDC = GetDC(hDesktopWnd);
    HDC hCaptureDC = CreateCompatibleDC(hDesktopDC);
    HBITMAP hCaptureBitmap =CreateCompatibleBitmap(hDesktopDC, 
                            nScreenWidth, nScreenHeight);
    SelectObject(hCaptureDC,hCaptureBitmap); 
    BitBlt(hCaptureDC,0,0,nScreenWidth,nScreenHeight,
           hDesktopDC,0,0,SRCCOPY|CAPTUREBLT); 
    SaveCapturedBitmap(hCaptureBitmap); //Place holder - Put your code
                                //here to save the captured image to disk
    ReleaseDC(hDesktopWnd,hDesktopDC);
    DeleteDC(hCaptureDC);
    DeleteObject(hCaptureBitmap);
}

In the above code snippet, the function GetSystemMetrics() returns the screen width when used withSM_CXSCREEN, and returns the screen height when called with SM_CYSCREEN. Refer to the accompanying source code for details of how to save the captured bitmap to the disk and how to send it to the clipboard. Its pretty straightforward. The source code implements the above technique for capturing the screen contents at regular intervals, and creates a movie out of the captured image sequences.

And the DirectX way of doing it

Capturing the screenshot with DirectX is a pretty easy task. DirectX offers a neat way of doing this.

Every DirectX application contains what we call a buffer, or a surface to hold the contents of the video memory related to that application. This is called the back buffer of the application. Some applications might have more than one back buffer. And there is another buffer that every application can access by default – the front buffer. This one, the front buffer, holds the video memory related to the desktop contents, and so essentially is the screen image.

By accessing the front buffer from our DirectX application, we can capture the contents of the screen at that moment.

Accessing the front buffer from the DirectX application is pretty easy and straightforward. The interfaceIDirect3DDevice9 provides the GetFrontBufferData() method that takes a IDirect3DSurface9 object pointer and copies the contents of the front buffer onto that surface. The IDirect3DSurfce9 object can be generated by using the method IDirect3DDevice8::CreateOffscreenPlainSurface(). Once the screen is captured onto the surface, we can use the function D3DXSaveSurfaceToFile() to save the surface directly to the disk in bitmap format. Thus, the code to capture the screen looks as follows:

extern IDirect3DDevice9* g_pd3dDevice;
Void CaptureScreen()
{
    IDirect3DSurface9* pSurface;
    g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,
        D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &pSurface, NULL);
    g_pd3dDevice->GetFrontBufferData(0, pSurface);
    D3DXSaveSurfaceToFile("Desktop.bmp",D3DXIFF_BMP,pSurface,NULL,NULL);
    pSurface->Release(); 
}

In the above, g_pd3dDevice is an IDirect3DDevice9 object, and has been assumed to be properly initialized. This code snippet saves the captured image onto the disk directly. However, instead of saving to disk, if we just want to operate on the image bits directly – we can do so by using the method IDirect3DSurface9::LockRect(). This gives a pointer to the surface memory – which is essentially a pointer to the bits of the captured image. We can copy the bits to our application defined memory and can operate on them. The following code snippet presents how the surface contents can be copied into our application defined memory:

extern void* pBits;
extern IDirect3DDevice9* g_pd3dDevice;
IDirect3DSurface9* pSurface;
g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,
                                          D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, 
                                          &pSurface, NULL);
g_pd3dDevice->GetFrontBufferData(0, pSurface);
D3DLOCKED_RECT lockedRect;
pSurface->LockRect(&lockedRect,NULL,
                   D3DLOCK_NO_DIRTY_UPDATE|
                   D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY)));
for( int i=0 ; i < ScreenHeight ; i++)
{
    memcpy( (BYTE*) pBits + i * ScreenWidth * BITSPERPIXEL / 8 , 
        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch , 
        ScreenWidth * BITSPERPIXEL / 8);
}
g_pSurface->UnlockRect();
pSurface->Release();

In the above, pBits is a void*. Make sure that we have allocated enough memory before copying into pBits. A typical value for BITSPERPIXEL is 32 bits per pixel. However, it may vary depending on your current monitor settings. The important point to note here is that the width of the surface is not same as the captured screen image width. Because of the issues involved in the memory alignment (memory aligned to word boundaries are assumed to be accessed faster compared to non aligned memory), the surface might have added additional stuff at the end of each row to make them perfectly aligned to the word boundaries. The lockedRect.Pitch gives us the number of bytes between the starting points of two successive rows. That is, to advance to the correct point on the next row, we should advance by Pitch, not by Width. You can copy the surface bits in reverse, using the following:

for( int i=0 ; i < ScreenHeight ; i++)
{
    memcpy((BYTE*) pBits +( ScreenHeight - i - 1) * 
        ScreenWidth * BITSPERPIXEL/8 , 
        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch , 
        ScreenWidth* BITSPERPIXEL/8);
}

This may come handy when you are converting between top-down and bottom-up bitmaps.

While the above technique of LockRect() is one way of accessing the captured image content onIDirect3DSurface9, we have another more sophisticated method defined for IDirect3DSurface9, the GetDC()method. We can use the IDirect3DSurface9::GetDC() method to get a GDI compatible device context for the DirectX image surface, which makes it possible to directly blit the surface contents to our application defined DC. Interested readers can explore this alternative.

The sample source code provided with this article implements the technique of copying the contents of an off-screen plain surface onto a user created bitmap for capturing the screen contents at regular intervals, and creates a movie out of the captured image sequences.

However, a point worth noting when using this technique for screen capture is the caution mentioned in the documentation: The GetFrontBufferData() is a slow operation by design, and should not be considered for use in performance-critical applications. Thus, the GDI approach is preferable over the DirectX approach in such cases.

Windows Media API for capturing the screen

Windows Media 9.0 supports screen captures using the Windows Media Encoder 9 API. It includes a codec namedWindows Media Video 9 Screen codec that has been specially optimized to operate on the content produced through screen captures. The Windows Media Encoder API provides the interface IWMEncoder2 which can be used to capture the screen content efficiently.

Working with the Windows Media Encoder API for screen captures is pretty straightforward. First, we need to start with the creation of an IWMEncoder2 object by using the CoCreateInstance() function. This can be done as:

IWMEncoder2* g_pEncoder=NULL; 
CoCreateInstance(CLSID_WMEncoder,NULL,CLSCTX_INPROC_SERVER,
        IID_IWMEncoder2,(void**)&g_pEncoder);

The Encoder object thus created contains all the operations for working with the captured screen data. However, in order to perform its operations properly, the encoder object depends on the settings defined in what is called a profile. A profile is nothing but a file containing all the settings that control the encoding operations. We can also create custom profiles at runtime with various customized options, such as codec options etc., depending on the nature of the captured data. To use a profile with our screen capture application, we create a custom profile based on the Windows Media Video 9 Screen codec. Custom profile objects have been supported with the interfaceIWMEncProfile2. We can create a custom profile object by using the CoCreateInstance() function as:

IWMEncProfile2* g_pProfile=NULL;
CoCreateInstance(CLSID_WMEncProfile2,NULL,CLSCTX_INPROC_SERVER,
        IID_IWMEncProfile2,(void**)&g_pProfile);

We need to specify the target audience for the encoder in the profile. Each profile can hold multiple number of audience configurations, which are objects of the interface IWMEncAudienceObj. Here, we use one audience object for our profile. We create the audience object for our profile by using the methodIWMEncProfile::AddAudience(), which would return a pointer to IWMEncAudienceObj which can then be used for configurations such as video codec settings (IWMEncAudienceObj::put_VideoCodec()), video frame size settings (IWMEncAudienceObj::put_VideoHeight() and IWMEncAudienceObj::put_VideoWidth()) etc. For example, we set the video codec to be Windows Media Video 9 Screen codec as:

extern IWMEncAudienceObj* pAudience;
#define VIDEOCODEC MAKEFOURCC('M','S','S','2') 
    //MSS2 is the fourcc for the screen codec

long lCodecIndex=-1;
g_pProfile->GetCodecIndexFromFourCC(WMENC_VIDEO,VIDEOCODEC,
    &lCodecIndex); //Get the Index of the Codec
pAudience->put_VideoCodec(0,lCodecIndex);

The fourcc is a kind of unique identifier for each codec in the world. The fourcc for the Windows Media Video 9 Screen codec is MSS2. The IWMEncAudienceObj::put_VideoCodec() accepts the profile index as the input to recognize a particular profile – which can be obtained by using the method IWMEncProfile::GetCodecIndexFromFourCC().

Once we have completed configuring the profile object, we can choose that profile into our encoder by using the method IWMEncSourceGroup :: put_Profile() which is defined on the source group objects of the encoder. A source group is a collection of sources where each source might be a video stream or audio stream or HTML stream etc. Each encoder object can work with many source groups from which it get the input data. Since our screen capture application uses only a video stream, our encoder object need to have one source group with a single source, the video source, in it. This single video source needs to configured to use the Screen Device as the input source, which can be done by using the method IWMEncVideoSource2::SetInput(BSTR) as:

extern IWMEncVideoSource2* pSrcVid;
pSrcVid->SetInput(CComBSTR("ScreenCap://ScreenCapture1");

The destination output can be configured to save into a video file (wmv movie) by using the methodIWMEncFile::put_LocalFileName() which requires an IWMEncFile object. This IWMEncFile object can be obtained by using the method IWMEncoder::get_File() as:

IWMEncFile* pOutFile=NULL;
g_pEncoder->get_File(&pOutFile);
pOutFile->put_LocalFileName(CComBSTR(szOutputFileName);

Now, once all the necessary configurations have been done on the encoder object, we can use the methodIWMEncoder::Start() to start capturing the screen. The methods IWMEncoder::Stop() andIWMEncoder::Pause might be used for stopping and pausing the capture.

While this deals with full screen capture, we can alternately select the regions of capture by adjusting the properties of input video source stream. For this, we need to use the IPropertyBag interface of the IWmEnVideoSource2 object as:

#define WMSCRNCAP_WINDOWLEFT CComBSTR("Left")
#define WMSCRNCAP_WINDOWTOP CComBSTR("Top")
#define WMSCRNCAP_WINDOWRIGHT CComBSTR("Right")
#define WMSCRNCAP_WINDOWBOTTOM CComBSTR("Bottom")
#define WMSCRNCAP_FLASHRECT CComBSTR("FlashRect")
#define WMSCRNCAP_ENTIRESCREEN CComBSTR("Screen")
#define WMSCRNCAP_WINDOWTITLE CComBSTR("WindowTitle")
extern IWMEncVideoSource2* pSrcVid;
int nLeft, nRight, nTop, nBottom;
pSrcVid->QueryInterface(IID_IPropertyBag,(void**)&pPropertyBag);
CComVariant varValue = false;
pPropertyBag->Write(WMSCRNCAP_ENTIRESCREEN,&varValue);
varValue = nLeft;
pPropertyBag->Write( WMSCRNCAP_WINDOWLEFT, &varValue );
varValue = nRight;
pPropertyBag->Write( WMSCRNCAP_WINDOWRIGHT, &varValue );
varValue = nTop;
pPropertyBag->Write( WMSCRNCAP_WINDOWTOP, &varValue );
varValue = nBottom;
pPropertyBag->Write( WMSCRNCAP_WINDOWBOTTOM, &varValue );

The accompanied source code implements this technique for capturing the screen. One point that might be interesting, apart from the nice quality of the produced output movie, is that in this, the mouse cursor is also captured. (By default, GDI and DirectX are unlikely to capture the mouse cursor).

Note that your system needs to be installed with Windows Media 9.0 SDK components to create applications using the Window Media 9.0 API.

To run your applications, end users must install the Windows Media Encoder 9 Series. When you distribute applications based on the Windows Media Encoder SDK, you must also include the Windows Media Encoder software, either by redistributing Windows Media Encoder in your setup, or by requiring your users to install Windows Media Encoder themselves.

The Windows Media Encoder 9.0 can be downloaded from:

Conclusion

All the variety of techniques discussed above are aimed at a single goal – capturing the contents of the screen. However, as can be guessed easily, the results vary depending upon the particular technique that is being employed in the program. If all that we want is just a random snapshot occasionally, the GDI approach is a good choice, given its simplicity. However, using Windows Media would be a better option if we want more professional results. One point worth noting is, the quality of the content captured through these mechanisms might depend on the settings of the system. For example, disabling hardware acceleration (Desktop properties | Settings | Advanced | Troubleshoot) might drastically improve the overall quality and performance of the capture application.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)

How can I return a string from a C dll to C#?

// c code:
#include <ole2.h> /* needed for CoTaskMemAlloc */
EXPORT char* getUrlFromBaseURLInList(char *ListFilePath, char *SrchBaseURL, char* strBuffer)
{
    static const char[] sTest =  "Test My String";
    /* you must use CoTaskMemAlloc to allocate the memory, not malloc, new, or anything else */
    char* returnedString = CoTaskMemAlloc(sizeof(sTest));
    strcpy(returnedString, sTest);
    return returnedString;
}

// c# code:
[DllImport("SLSSeoUrlFunc.dll", CharSet=CharSet.Ansi, CallingConvention=CallingConvention.Cdecl)]
public static extern string getUrlFromBaseURLInList(byte[] ListFilePath, byte[] sSrchUrl);

string myTestString = SLSSeoUrlFuncDllWrap.getUrlFromBaseURLInList(null, null);
Console.WriteLine("Eureka - {0}", myTestString);

svn、bugzilla等穿过防火墙访问:如何在 Windows 防火墙中打开端口

如果 Windows 防火墙阻止某一程序,而您希望允许该程序通过防火墙进行通信,通常可以通过在 Windows 防火墙允许的程序列表(也称为“例外列表”)中选中该程序来实现。若要了解如何进行此操作,请参阅允许程序通过 Windows 防火墙进行通信

但是,如果没有列出该程序,则可能需要打开一个端口。例如,当您与朋友联机进行多人游戏时,可能需要为该游戏打开一个端口,这样防火墙才能允许游戏信息到达您的计算机。端口始终保持打开状态,因此请确保关闭不需要打开的端口。

  1. 通过单击「开始」按钮 「开始」按钮的图片,然后单击“控制面板”,打开“Windows 防火墙”。 在搜索框中,键入防火墙,然后单击“Windows 防火墙”

  2. 在左窗格中,单击“高级设置” 需要管理员权限 如果系统提示您输入管理员密码或进行确认,请键入该密码或提供确认。

  3. “高级安全 Windows 防火墙”对话框的左窗格中,单击“入站规则”,然后在右窗格中,单击“新建规则”

  4. 按照新建入站规则向导中的说明进行操作。

如果您无法通过 Windows 防火墙让其他计算机与您的计算机通信,则可以尝试使用“传入连接”疑难解答自动查找并修复一些常见问题。

通过单击“开始”按钮 「开始」按钮的图片,然后单击“控制面板”,打开“传入的连接”疑难解答。在搜索框中,键入疑难解答,然后单击“疑难解答”。单击“查看全部”,然后单击“传入的连接”

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

在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

 

在C#中调试C++的DLL

“我的小伙伴们都惊呆了!”

今天才发现,其实C#是可以跟进C++代码的!!!在VS2012里的做法是:

在C#项目上右键点击,在右键菜单上选择“属性” –> 调试–>启用调试器–>勾选“启用本机代码调试(T)”

这样之后,就可以直接在C++项目里的C++代码上直接打断点调试了!

如下图所示:

1、在项目上右键,然后在右键菜单中选择属性

2、勾选启用本机代码调试

能调试到C++代码,还有啥好担心的??? :)