0%

写在前面

有段时间没有更新博客了,一方面因为工作繁忙,另一方面则是我最近一直在坚持设计和完善基于DDD的应用系统开发框架Apworks。读过我《领域驱动设计案例:Tiny Library》这一系列文章的朋友一定听说过Apworks框架,虽然这个框架至今仍未成熟到能够用在真正的系统开发上,但它已经实现了CQRS体系结构模式,已经可以用于软件架构设计的演示上了。从这一讲开始,我将逐步介绍如何采用CQRS架构实现Tiny Library的业务。你可能会听得烦了:又是Tiny Library,能不能换点别的?呵呵,我开始时想做一个简单的论坛出来,不过为了能让读者朋友在经典DDD实践和CQRS实践上做个对比,我决定继续使用Tiny Library的业务。

扩展阅读

在阅读本系列文章之前,如果您对领域驱动设计(DDD)、命令与查询职责分离(CQRS)、事件溯源(Event Sourcing)、事件存储(Event Store)、WCF、ASP.NET MVC等概念和技术不了解的话,请自己先对这些内容做个了解。这里我给出一些链接,希望能对您有所帮助。

下载案例

请读者朋友到http://tlibcqrs.codeplex.com站点下载源代码。单击进入站点首页,然后在Source Code选项卡下,选择最新的Change Set,在打开的Change Set页面中,单击Download按钮下载源代码包。

系统需求

  • Microsoft .NET Framework 3.5 SP1
  • Microsoft Patterns&Practices Enterprise Library 5.0 (April 2010)
  • Microsoft SQL Express 2008
  • Microsoft Visual Studio 2010 (打开解决方案时需要VS2010)
  • Apworks DDD framework (http://apworks.codeplex.com)。 Apworks的程序集已经被包含在Tiny Library CQRS的源代码包中,您无需单独下载Apworks

注意:目前Apworks框架只能用于演示,仍在开发中,请不要用于实际项目!

安装部署

请按下列步骤安装部署Tiny Library CQRS:

  • 打开Microsoft Visual Studio 2010
  • 打开Server Explorer
  • 邮件单击Data Connections, 选择Create New SQL Server Database option
  • 在Create New SQL Server Database对话框中,在Server name中输入.\SQLEXPRESS,然后在 New database name 中输入 TinyLibraryEventDB
  • 使用上面同样的方法创建另一个数据库,取名为TinyLibraryQueryDB
  • 在Microsoft Visual Studio 2010中,打开 TinyLibraryCQRS 解决方案
  • 在Additions 目录下,执行 TinyLibraryEventDB.sql 和 TinyLibraryQueryDB.sql 脚本
  • 编译整个解决方案 
  • 在Solution Explorer上, 邮件单击 TinyLibrary.Services.CommandService.svc 文件, 选择 View in Browser, 这将启动 ASP.NET Development Server,端口号是1421
  • 将TinyLibrary.WebApp项目设置成启动项目,然后按下CTRL+F5以启动应用程序

运行案例应用程序

启动应用程序后,将出现如下界面:

image

请使用默认的账户登录系统:用户名:daxnet,密码:123456

从下一讲开始,我将详细介绍Tiny Library CQRS的体系结构和设计思路。敬请期待!

CQRS框架:AxonFramework 之 Hello World - 菩提树下的杨过 - 博客园

Excerpt

Command Query Responsibility Segregation,CQRS 这个架构好象最近博客园里讨论得比较多,有几篇园友的文章很有深度,推荐阅读: CQRS架构简介 浅谈命令查询职责分离(CQRS)模式 DDD CQRS架构和传统架构的优缺点比较 比较有趣的是,以往一断谈及架构思


Command Query Responsibility Segregation,CQRS 这个架构好象最近博客园里讨论得比较多,有几篇园友的文章很有深度,推荐阅读:

CQRS架构简介 

浅谈命令查询职责分离(CQRS)模式

DDD CQRS架构和传统架构的优缺点比较

比较有趣的是,以往一断谈及架构思路、OO这些,往往都是java大佬们的专长,而CQRS这个话题,好象.NET占了上风。园友汤雪华ENODE开源大作,在github上人气也很旺。

于是,我逆向思路搜索了下java的类似项目,果然有一个AxonFramework,甚至还有一个专门的网站。按文档上的介绍,弄了一个hello world,记录一下:

CRQS是基于事件驱动的,其主要架构并不复杂,见下图:

点击看大图

简单来讲,对数据库的修改操作,UI层只管发送各种命令(Command),触发事件(Event),然后由EventHandler去异步处理,最终写入master DB,对于数据库的查询,则查询slave DB(注:这里的master db, slave db只是一个逻辑上的区分,可以是真正的主-从库,也可以都是一个库)。 这样的架构,很容易实现读写分离,也易于大型项目的扩展。

项目结构:

点击看大图

package的名称上大概就能看出用途:

command包定义各种命令,

event包定义各种事件,

handler包定义事件处理逻辑,

model包相当于领域模型

最外层的ToDOItemRunner相当于应用程序入口。

gradle依赖项:

command命令:

这里我们假设了二个命令:创建命令、完成命令

CreateToDoItemCommand

MarkCompletedCommand

Event事件:

ToDoItemCreatedEvent

1
ToDoItemCompletedEvent

EventHandler事件处理

上面的代码只是演示,将事件信息输出而已,真实应用中,这里可以完成对db的更新操作。 

领域模型model

然后让Spring将这些东西串在一起,配置文件如下:

View Code

最后,提供一个舞台,让整个应用run起来:

输出结果:

axon框架测试也很容易:

given/when/expectEvents的意思是,给(given)一个事件,然后当(when)某个命令被调用时,期待(expectEvents)某个事件被触发。

最后 github上还有一个比较复杂的示例项目:https://github.com/AxonFramework/Axon-trader,想深入了解的可以研究下

C++ 资源大全

关于 C++ 框架、库和资源的一些汇总列表,内容包括:标准库、Web应用框架、人工智能、数据库、图片处理、机器学习、日志、代码分析等。

标准库

C++标准库,包括了STL容器,算法和函数等。

框架

C++通用框架和库

  • Apache C++ Standard Library:是一系列算法,容器,迭代器和其他基本组件的集合
  • ASL :Adobe源代码库提供了同行的评审和可移植的C++源代码库。
  • Boost :大量通用C++库的集合。
  • BDE :来自于彭博资讯实验室的开发环境。
  • Cinder:提供专业品质创造性编码的开源开发社区。
  • Cxxomfort:轻量级的,只包含头文件的库,将C++ 11的一些新特性移植到C++03中。
  • Dlib:使用契约式编程和现代C++科技设计的通用的跨平台的C++库。
  • EASTL :EA-STL公共部分
  • ffead-cpp :企业应用程序开发框架
  • Folly:由Facebook开发和使用的开源C++库
  • JUCE :包罗万象的C++类库,用于开发跨平台软件
  • libPhenom:用于构建高性能和高度可扩展性系统的事件框架。
  • LibSourcey :用于实时的视频流和高性能网络应用程序的C++11 evented IO
  • LibU : C语言写的多平台工具库
  • Loki :C++库的设计,包括常见的设计模式和习语的实现。
  • MiLi :只含头文件的小型C++库
  • openFrameworks :开发C++工具包,用于创意性编码。
  • Qt :跨平台的应用程序和用户界面框架
  • Reason :跨平台的框架,使开发者能够更容易地使用Java,.Net和Python,同时也满足了他们对C++性能和优势的需求。
  • ROOT :具备所有功能的一系列面向对象的框架,能够非常高效地处理和分析大量的数据,为欧洲原子能研究机构所用。
  • STLport:是STL具有代表性的版本
  • STXXL:用于额外的大型数据集的标准模板库。
  • Ultimate++ :C++跨平台快速应用程序开发框架
  • Windows Template Library:用于开发Windows应用程序和UI组件的C++库
  • Yomm11 :C++11的开放multi-methods.

人工智能

  • btsk :游戏行为树启动器工具
  • Evolving Objects:基于模板的,ANSI C++演化计算库,能够帮助你非常快速地编写出自己的随机优化算法。
  • Neu:C++11框架,编程语言集,用于创建人工智能应用程序的多用途软件系统。

异步事件循环

  • Boost.Asio:用于网络和底层I/O编程的跨平台的C++库。
  • libev :功能齐全,高性能的时间循环,轻微地仿效libevent,但是不再像libevent一样有局限性,也修复了它的一些bug。
  • libevent :事件通知库
  • libuv :跨平台异步I/O。

音频

音频,声音,音乐,数字化音乐库

  • FMOD :易于使用的跨平台的音频引擎和音频内容的游戏创作工具。
  • Maximilian :C++音频和音乐数字信号处理库
  • OpenAL :开源音频库—跨平台的音频API
  • Opus:一个完全开放的,免版税的,高度通用的音频编解码器
  • Speex:免费编解码器,为Opus所废弃
  • Tonic: C++易用和高效的音频合成
  • Vorbis: Ogg Vorbis是一种完全开放的,非专有的,免版税的通用压缩音频格式。

生态学

生物信息,基因组学和生物技术

  • libsequence:用于表示和分析群体遗传学数据的C++库。
  • SeqAn:专注于生物数据序列分析的算法和数据结构。
  • Vcflib :用于解析和处理VCF文件的C++库
  • Wham:直接把联想测试应用到BAM文件的基因结构变异。

压缩

压缩和归档库

  • bzip2:一个完全免费,免费专利和高质量的数据压缩
  • doboz:能够快速解压缩的压缩库
  • PhysicsFS:对各种归档提供抽象访问的库,主要用于视频游戏,设计灵感部分来自于Quake3的文件子系统。
  • KArchive:用于创建,读写和操作文件档案(例如zip和 tar)的库,它通过QIODevice的一系列子类,使用gzip格式,提供了透明的压缩和解压缩的数据。
  • LZ4 :非常快速的压缩算法
  • LZHAM :无损压缩数据库,压缩比率跟LZMA接近,但是解压缩速度却要快得多。
  • LZMA :7z格式默认和通用的压缩方法。
  • LZMAT :及其快速的实时无损数据压缩库
  • miniz:单一的C源文件,紧缩/膨胀压缩库,使用zlib兼容API,ZIP归档读写,PNG写方式。
  • Minizip:Zlib最新bug修复,支持PKWARE磁盘跨越,AES加密和IO缓冲。
  • Snappy :快速压缩和解压缩
  • ZLib :非常紧凑的数据流压缩库
  • ZZIPlib:提供ZIP归档的读权限。

并发性

并发执行和多线程

  • Boost.Compute :用于OpenCL的C++GPU计算库
  • Bolt :针对GPU进行优化的C++模板库
  • C++React :用于C++11的反应性编程库
  • Intel TBB :Intel线程构件块
  • Libclsph:基于OpenCL的GPU加速SPH流体仿真库
  • OpenCL :并行编程的异构系统的开放标准
  • OpenMP:OpenMP API
  • Thrust :类似于C++标准模板库的并行算法库
  • HPX :用于任何规模的并行和分布式应用程序的通用C++运行时系统
  • VexCL :用于OpenCL/CUDA 的C++向量表达式模板库。

容器

  • C++ B-tree :基于B树数据结构,实现命令内存容器的模板库
  • Hashmaps: C++中开放寻址哈希表算法的实现

密码学

  • Bcrypt :一个跨平台的文件加密工具,加密文件可以移植到所有可支持的操作系统和处理器中。
  • BeeCrypt
  • Botan: C++加密库
  • Crypto++:一个有关加密方案的免费的C++库
  • GnuPG: OpenPGP标准的完整实现
  • GnuTLS :实现了SSL,TLS和DTLS协议的安全通信库
  • Libgcrypt
  • libmcrypt
  • LibreSSL:免费的SSL/TLS协议,属于2014 OpenSSL的一个分支
  • LibTomCrypt:一个非常全面的,模块化的,可移植的加密工具
  • libsodium:基于NaCI的加密库,固执己见,容易使用
  • Nettle 底层的加密库
  • OpenSSL : 一个强大的,商用的,功能齐全的,开放源代码的加密库。
  • Tiny AES128 in C :用C实现的一个小巧,可移植的实现了AES128ESB的加密算法

数据库

数据库,SQL服务器,ODBC驱动程序和工具

  • hiberlite :用于Sqlite3的C++对象关系映射
  • Hiredis: 用于Redis数据库的很简单的C客户端库
  • LevelDB: 快速键值存储库
  • LMDB:符合数据库四大基本元素的嵌入键值存储
  • MySQL++:封装了MySql的C API的C++ 包装器
  • RocksDB:来自Facebook的嵌入键值的快速存储
  • SQLite:一个完全嵌入式的,功能齐全的关系数据库,只有几百KB,可以正确包含到你的项目中。

调试

调试库, 内存和资源泄露检测,单元测试

  • Boost.Test:Boost测试库
  • Catch:一个很时尚的,C++原生的框架,只包含头文件,用于单元测试,测试驱动开发和行为驱动开发。
  • CppUnit:由JUnit移植过来的C++测试框架
  • CTest:CMake测试驱动程序
  • googletest:谷歌C++测试框架
  • ig-debugheap:用于跟踪内存错误的多平台调试堆
  • libtap:用C语言编写测试
  • MemTrack —用于C++跟踪内存分配
  • microprofile- 跨平台的网络试图分析器
  • minUnit :使用C写的迷你单元测试框架,只使用了两个宏
  • Remotery:用于web视图的单一C文件分析器
  • UnitTest++:轻量级的C++单元测试框架

游戏引擎

  • Cocos2d-x :一个跨平台框架,用于构建2D游戏,互动图书,演示和其他图形应用程序。
  • Grit :社区项目,用于构建一个免费的游戏引擎,实现开放的世界3D游戏。
  • Irrlicht :C++语言编写的开源高性能的实时#D引擎
  • Polycode:C++实现的用于创建游戏的开源框架(与Lua绑定)。

图形用户界面

  • CEGUI : 很灵活的跨平台GUI库
  • FLTK :快速,轻量级的跨平台的C++GUI工具包。
  • GTK+: 用于创建图形用户界面的跨平台工具包
  • gtkmm :用于受欢迎的GUI库GTK+的官方C++接口。
  • imgui:拥有最小依赖关系的立即模式图形用户界面
  • libRocket :libRocket 是一个C++ HTML/CSS 游戏接口中间件
  • MyGUI :快速,灵活,简单的GUI
  • Ncurses:终端用户界面
  • QCustomPlot :没有更多依赖关系的Qt绘图控件
  • Qwt :用户与技术应用的Qt 控件
  • QwtPlot3D :功能丰富的基于Qt/OpenGL的C++编程库,本质上提供了一群3D控件
  • OtterUI :OtterUI 是用于嵌入式系统和互动娱乐软件的用户界面开发解决方案
  • PDCurses 包含源代码和预编译库的公共图形函数库
  • wxWidgets C++库,允许开发人员使用一个代码库可以为widows, Mac OS X,Linux和其他平台创建应用程序

图形

  • bgfx:跨平台的渲染库
  • Cairo:支持多种输出设备的2D图形库
  • Horde3D 一个小型的3D渲染和动画引擎
  • magnum C++11和OpenGL 2D/3D 图形引擎
  • Ogre 3D 用C++编写的一个面向场景,实时,灵活的3D渲染引擎(并非游戏引擎)
  • OpenSceneGraph 具有高性能的开源3D图形工具包
  • Panda3D 用于3D渲染和游戏开发的框架,用Python和C++编写。
  • Skia 用于绘制文字,图形和图像的完整的2D图形库
  • urho3d 跨平台的渲染和游戏引擎。

图像处理

  • Boost.GIL:通用图像库
  • CImg :用于图像处理的小型开源C++工具包
  • CxImage :用于加载,保存,显示和转换的图像处理和转换库,可以处理的图片格式包括 BMP, JPEG, GIF, PNG, TIFF, MNG, ICO, PCX, TGA, WMF, WBMP, JBG, J2K。
  • FreeImage :开源库,支持现在多媒体应用所需的通用图片格式和其他格式。
  • GDCM:Grassroots DICOM 库
  • ITK:跨平台的开源图像分析系统
  • Magick++:ImageMagick程序的C++接口
  • MagickWnd:ImageMagick程序的C++接口
  • OpenCV : 开源计算机视觉类库
  • tesseract-ocr:OCR引擎
  • VIGRA :用于图像分析通用C++计算机视觉库
  • VTK :用于3D计算机图形学,图像处理和可视化的开源免费软件系统。

国际化

  • gettext :GNU `gettext’
  • IBM ICU:提供Unicode 和全球化支持的C、C++ 和Java库
  • libiconv :用于不同字符编码之间的编码转换库

Jason

  • frozen : C/C++的Jason解析生成器
  • Jansson :进行编解码和处理Jason数据的C语言库
  • jbson :C++14中构建和迭代BSON data,和Json 文档的库
  • JeayeSON:非常健全的C++ JSON库,只包含头文件
  • JSON++ : C++ JSON 解析器
  • json-parser:用可移植的ANSI C编写的JSON解析器,占用内存非常少
  • json11 :一个迷你的C++11 JSON库
  • jute :非常简单的C++ JSON解析器
  • ibjson:C语言中的JSON解析和打印库,很容易和任何模型集成。
  • libjson:轻量级的JSON库
  • PicoJSON:C++中JSON解析序列化,只包含头文件
  • qt-json :用于JSON数据和 QVariant层次间的相互解析的简单类
  • QJson:将JSON数据映射到QVariant对象的基于Qt的库
  • RapidJSON: 用于C++的快速JSON 解析生成器,包含SAX和DOM两种风格的API
  • YAJL :C语言中快速流JSON解析库

日志

  • Boost.Log :设计非常模块化,并且具有扩展性
  • easyloggingpp:C++日志库,只包含单一的头文件。
  • Log4cpp :一系列C++类库,灵活添加日志到文件,系统日志,IDSA和其他地方。
  • templog:轻量级C++库,可以添加日志到你的C++应用程序中

机器学习

  • Caffe :快速的神经网络框架
  • CCV :以C语言为核心的现代计算机视觉库
  • mlpack :可扩展的C++机器学习库
  • OpenCV:开源计算机视觉库
  • Recommender:使用协同过滤进行产品推荐/建议的C语言库。
  • SHOGUN:Shogun 机器学习工具
  • sofia-ml :用于机器学习的快速增量算法套件

数学

  • Armadillo :高质量的C++线性代数库,速度和易用性做到了很好的平衡。语法和MatlAB很相似
  • blaze:高性能的C++数学库,用于密集和稀疏算法。
  • ceres-solver :来自谷歌的C++库,用于建模和解决大型复杂非线性最小平方问题。
  • CGal: 高效,可靠的集合算法集合
  • cml :用于游戏和图形的免费C++数学库
  • Eigen :高级C++模板头文件库,包括线性代数,矩阵,向量操作,数值解决和其他相关的算法。
  • GMTL:数学图形模板库是一组广泛实现基本图形的工具。
  • GMP:用于个高精度计算的C/C++库,处理有符号整数,有理数和浮点数。

多媒体

  • GStreamer :构建媒体处理组件图形的库
  • LIVE555 Streaming Media :使用开放标准协议(RTP/RTCP, RTSP, SIP) 的多媒体流库
  • libVLC :libVLC (VLC SDK)媒体框架
  • QtAv:基于Qt和FFmpeg的多媒体播放框架,能够帮助你轻而易举地编写出一个播放器
  • SDL :简单直控媒体层
  • SFML :快速,简单的多媒体库

网络

  • ACE:C++面向对象网络变成工具包
  • Boost.Asio:用于网络和底层I/O编程的跨平台的C++库
  • Casablanca:C++ REST SDK
  • cpp-netlib:高级网络编程的开源库集合
  • Dyad.c:C语言的异步网络
  • libcurl :多协议文件传输库
  • Mongoose:非常轻量级的网络服务器
  • Muduo :用于Linux多线程服务器的C++非阻塞网络库
  • net_skeleton :C/C++的TCP 客户端/服务器库
  • nope.c :基于C语言的超轻型软件平台,用于可扩展的服务器端和网络应用。 对于C编程人员,可以考虑node.js
  • Onion :C语言HTTP服务器库,其设计为轻量级,易使用。
  • POCO:用于构建网络和基于互联网应用程序的C++类库,可以运行在桌面,服务器,移动和嵌入式系统。
  • RakNet:为游戏开发人员提供的跨平台的开源C++网络引擎。
  • Tuf o :用于Qt之上的C++构建的异步Web框架。
  • WebSocket++ :基于C++/Boost Aiso的websocket 客户端/服务器库
  • ZeroMQ :高速,模块化的异步通信库

物理学

动力学仿真引擎

  • Box2D:2D的游戏物理引擎。
  • Bullet :3D的游戏物理引擎。
  • Chipmunk :快速,轻量级的2D游戏物理库
  • LiquidFun:2D的游戏物理引擎
  • ODE :开放动力学引擎-开源,高性能库,模拟刚体动力学。
  • ofxBox2d:Box2D开源框架包装器。
  • Simbody :高性能C++多体动力学/物理库,模拟关节生物力学和机械系统,像车辆,机器人和人体骨骼。

机器人学

  • MOOS-IvP :一组开源C++模块,提供机器人平台的自主权,尤其是自主的海洋车辆。
  • MRPT:移动机器人编程工具包
  • PCL :点云库是一个独立的,大规模的开放项目,用于2D/3D图像和点云处理。
  • Robotics Library (RL): 一个独立的C++库,包括机器人动力学,运动规划和控制。
  • RobWork:一组C++库的集合,用于机器人系统的仿真和控制。
  • ROS :机器人操作系统,提供了一些库和工具帮助软件开发人员创建机器人应用程序。

科学计算

  • FFTW :用一维或者多维计算DFT的C语言库。
  • GSL:GNU科学库。

脚本

  • ChaiScript :用于C++的易于使用的嵌入式脚本语言。
  • Lua :用于配置文件和基本应用程序脚本的小型快速脚本引擎。
  • luacxx:用于创建Lua绑定的C++ 11 API
  • SWIG :一个可以让你的C++代码链接到JavaScript,Perl,PHP,Python,Tcl和Ruby的包装器/接口生成器
  • V7:嵌入式的JavaScript 引擎。
  • V8 :谷歌的快速JavaScript引擎,可以被嵌入到任何C++应用程序中。

序列化

  • Cap’n Proto :快速数据交换格式和RPC系统。
  • cereal :C++11 序列化库
  • FlatBuffers :内存高效的序列化库
  • MessagePack :C/C++的高效二进制序列化库,例如 JSON
  • protobuf :协议缓冲,谷歌的数据交换格式。
  • protobuf-c :C语言的协议缓冲实现
  • SimpleBinaryEncoding:用于低延迟应用程序的对二进制格式的应用程序信息的编码和解码。
  • Thrift :高效的跨语言IPC/RPC,用于C++,Java,Python,PHP,C#和其它多种语言中,最初由Twitter开发。

视频

  • libvpx :VP8/VP9编码解码SDK
  • FFmpeg :一个完整的,跨平台的解决方案,用于记录,转换视频和音频流。
  • libde265 :开放的h.265视频编解码器的实现。
  • OpenH264:开源H.364 编解码器。
  • Theora :免费开源的视频压缩格式。

虚拟机

  • CarpVM:C中有趣的VM,让我们一起来看看这个。
  • MicroPython :旨在实现单片机上Python3.x的实现
  • TinyVM:用纯粹的ANSI C编写的小型,快速,轻量级的虚拟机。

Web应用框架

  • Civetweb :提供易于使用,强大的,C/C++嵌入式Web服务器,带有可选的CGI,SSL和Lua支持。
  • CppCMS :免费高性能的Web开发框架(不是 CMS).
  • Crow :一个C++微型web框架(灵感来自于Python Flask)
  • Kore :使用C语言开发的用于web应用程序的超快速和灵活的web服务器/框架。
  • libOnion:轻量级的库,帮助你使用C编程语言创建web服务器。
  • QDjango:使用C++编写的,基于Qt库的web框架,试图效仿Django API,因此得此名。
  • Wt :开发Web应用的C++库。

XML

XML就是个垃圾,xml的解析很烦人,对于计算机它也是个灾难。这种糟糕的东西完全没有存在的理由了。-Linus Torvalds

  • Expat :用C语言编写的xml解析库
  • Libxml2 :Gnome的xml C解析器和工具包
  • libxml++ :C++的xml解析器
  • PugiXML :用于C++的,支持XPath的轻量级,简单快速的XML解析器。
  • RapidXml :试图创建最快速的XML解析器,同时保持易用性,可移植性和合理的W3C兼容性。
  • TinyXML :简单小型的C++XML解析器,可以很容易地集成到其它项目中。
  • TinyXML2:简单快速的C++CML解析器,可以很容易集成到其它项目中。
  • TinyXML++:TinyXML的一个全新的接口,使用了C++的许多许多优势,模板,异常和更好的异常处理。
  • Xerces-C++ :用可移植的C++的子集编写的XML验证解析器。

多项混杂

一些有用的库或者工具,但是不适合上面的分类,或者还没有分类。

  • C++ Format :C++的小型,安全和快速格式化库
  • casacore :从aips++ 派生的一系列C++核心库
  • cxx-prettyprint:用于C++容器的打印库
  • DynaPDF :易于使用的PDF生成库
  • gcc-poison :帮助开发人员禁止应用程序中的不安全的C/C++函数的简单的头文件。
  • googlemock:编写和使用C++模拟类的库
  • HTTP Parser :C的http请求/响应解析器
  • libcpuid :用于x86 CPU检测盒特征提取的小型C库
  • libevil :许可证管理器
  • libusb:允许移动访问USB设备的通用USB库
  • PCRE:正则表达式C库,灵感来自于Perl中正则表达式的功能。
  • Remote Call Framework :C++的进程间通信框架。
  • Scintilla :开源的代码编辑控件
  • Serial Communication Library :C++语言编写的跨平台,串口库。
  • SDS:C的简单动态字符串库
  • SLDR :超轻的DNS解析器
  • SLRE: 超轻的正则表达式库
  • Stage :移动机器人模拟器
  • VarTypes:C++/Qt4功能丰富,面向对象的管理变量的框架。
  • ZBar:‘条形码扫描器’库,可以扫描照片,图片和视频流中的条形码,并返回结果。
  • CppVerbalExpressions :易于使用的C++正则表达式
  • QtVerbalExpressions:基于C++ VerbalExpressions 库的Qt库
  • PHP-CPP:使用C++来构建PHP扩展的库
  • Better String :C的另一个字符串库,功能更丰富,但是没有缓冲溢出问题,还包含了一个C++包装器。

软件

用于创建开发环境的软件

编译器

C/C++编译器列表

  • Clang :由苹果公司开发的
  • GCC:GNU编译器集合
  • Intel C++ Compiler :由英特尔公司开发
  • LLVM :模块化和可重用编译器和工具链技术的集合
  • Microsoft Visual C++ :MSVC,由微软公司开发
  • Open WatCom :Watcom,C,C++和Fortran交叉编译器和工具
  • TCC :轻量级的C语言编译器

在线编译器

在线C/C++编译器列表

  • codepad :在线编译器/解释器,一个简单的协作工具
  • CodeTwist:一个简单的在线编译器/解释器,你可以粘贴的C,C++或者Java代码,在线执行并查看结果
  • coliru :在线编译器/shell, 支持各种C++编译器
  • Compiler Explorer:交互式编译器,可以进行汇编输出
  • CompileOnline:Linux上在线编译和执行C++程序
  • Ideone :一个在线编译器和调试工具,允许你在线编译源代码并执行,支持60多种编程语言。

调试器

C/C++调试器列表

集成开发环境(IDE)

C/C++集成开发环境列表

  • AppCode :构建与JetBrains’ IntelliJ IDEA 平台上的用于Objective-C,C,C++,Java和Java开发的集成开发环境
  • CLion:来自JetBrains的跨平台的C/C++的集成开发环境
  • Code::Blocks :免费C,C++和Fortran的集成开发环境
  • CodeLite :另一个跨平台的免费的C/C++集成开发环境
  • Dev-C++:可移植的C/C++/C++11集成开发环境
  • Eclipse CDT:基于Eclipse平台的功能齐全的C和C++集成开发环境
  • Geany :轻量级的快速,跨平台的集成开发环境。
  • IBM VisualAge :来自IBM的家庭计算机集成开发环境。
  • Irony-mode:由libclang驱动的用于Emacs的C/C++微模式
  • KDevelop:免费开源集成开发环境
  • Microsoft Visual Studio :来自微软的集成开发环境
  • NetBeans :主要用于Java开发的的集成开发环境,也支持其他语言,尤其是PHP,C/C++和HTML5。
  • Qt Creator:跨平台的C++,Javascript和QML集成开发环境,也是Qt SDK的一部分。
  • rtags:C/C++的客户端服务器索引,用于 跟基于clang的emacs的集成
  • Xcode :由苹果公司开发
  • YouCompleteMe:一个用于Vim的根据你敲的代码快速模糊搜索并进行代码补全的引擎。

构建系统

  • Bear :用于为clang工具生成编译数据库的工具
  • Biicode:基于文件的简单依赖管理器。
  • CMake :跨平台的免费开源软件用于管理软件使用独立编译的方法进行构建的过程。
  • CPM:基于CMake和Git的C++包管理器
  • FASTBuild:高性能,开源的构建系统,支持高度可扩展性的编译,缓冲和网络分布。
  • Ninja :专注于速度的小型构建系统
  • Scons :使用Python scipt 配置的软件构建工具
  • tundra :高性能的代码构建系统,甚至对于非常大型的软件项目,也能提供最好的增量构建次数。
  • tup:基于文件的构建系统,用于后台监控变化的文件。

静态代码分析

提高质量,减少瑕疵的代码分析工具列表

 说明:本文相当于官方文档的个人重新实现,官方文档链接:https://developer.android.com/studio/projects/add-native-code

向项目添加C/C++代码分为两种情况,一种是创建支持C/C++代码的新项目,一种是向原先不支持C/C++的已有项目添加C/C++代码。这两种情况分别对应本教程的第一大点和第二大点。

一、创建支持C/C++原生代码的新项目教程

1.1、下载NDK和构建工具

要为应用编译和调试原生代码,需要安装以下组件:

Android原生开发工具包 (NDK)—-这套工具集允许我们为Android使用C和C++代码,且其提供众多平台库让我们可以管理原生Activity和访问物理设备组件,例如传感器和触摸输入。

CMake—-一款外部构建工具,可与Gradle搭配使用来构建原生库。如果只计划使用ndk-build,则不需要此组件。

LLDB—-一种调试程序,Android Studio使用它来调试原生代码。

安装步骤如下:

菜单栏-—Tools—-SDK Manager

SDK Tools—-钩选CMake/LLDB/NDK三项-—点击确定

文件比较大,大概需要一二十分钟(受网速影响),完成后点击“Finish”即可

1.2、创建支持C/C++的新项目

菜单栏--File—new—New Project

其他信息按自己的需要填,主要是钩选“Include C++ support”

接下来的三步和正常的项目创建没什么区,按自己的需要钩选或填写即可。我这里演示使用,全直接使用默认配置。

在向导最后“Customize C++ Support”会有以下几项内容:

C++ Standard—-使用下拉列表选择您希望使用哪种 C++ 标准。选择 Toolchain Default 会使用默认的 CMake 设置。

Exceptions Support—-如果您希望启用对 C++ 异常处理的支持,请选中此复选框。如果启用此复选框,Android Studio 会将 -fexceptions 标志添加到模块级 build.gradle 文件的 cppFlags 中,Gradle 会将其传递到 CMake。

Runtime Type Information Support—-如果您希望支持 RTTI,请选中此复选框。如果启用此复选框,Android Studio 会将 -frtti 标志添加到模块级 build.gradle 文件的 cppFlags 中,Gradle 会将其传递到 CMake。

我这里使用默认配置,直接点击“Finish”

1.3、确认程序可以成功调用****C++函数

将项目切换到“Android”视图观察整个项目,可发现较没有“Include C++ support”的项目,多了cpp和External Build Files两个组

在设计中cpp组用于存放项目的所有原生源文件、标头和预构建库。对于当前项目,Android Studio只创建了一个名为native-lib.cpp的C++源文件(位于src/main/cpp/目录)其中只有一个简单的C++函数stringFromJNI(),该函数返回字符串“Hello from C++”。

External Build Files组用于存放CMake或ndk-build的构建脚本。与Gradle需要build.gradle文件来指示如何构建应用一样,CMake和ndk-build依照一个构建脚本来构建原生库。对于当前项目,Android Studio创建了一个CMake构建脚本CMakeLists.txt(位于模块的根目录),用于指示编译构建native-lib.cpp。

点击查看“native-lib.cpp”内容如下,只有一个返回“Hello from C++”的函数

查看生成的activity_main.xml,如容如下,和Android Studio正常默认生成的项目一样,只有一个显示“Hello World!”的文本框

查看MainActivity.java内容如下,首选使用了System.loadLibrary()加载了本地库,然后在onCreate()中将activity_main.xml中的文框的内容修改为原生函数stringFromJNI()返回的字符串(Hello from C++)

我们直接在模拟器上运行app,如果界面文本框显示的不是“Hello World!”而是“Hello from C++”那说明程序成功调用原生函数。

菜单栏-—Run—-Run ‘app’选择虚拟机运行

图中的”Nexus 5X API 28”是我之前创建的虚拟机,没有虚拟机点击左下方的“Create New Virtual Device”创建即可。

可以看到界面如下,确实显示的是“Hello from C++”,也就是说经过如此配置之后程序确实可以成功调用C++函数

1.4 创建新的原生源文件【可选】

如果是简单使用,那直接在上边的native-lib.cpp后边追加函数即可(不过要注意前边的extern “C” JNIEXPORT jstring JNICALL相当于jstring,用于指明函数的返回值类型,自己在添加新函数时不要忘了需要指明返回值类型这回事,以致函数名位置一直报“Cannot resolve type”错误),在实际使用中经常需要创建多个文件。下边介绍如何创建新的原生源文件。

1.4.1 创建新的原生源文件

切换到“Project”视图,定位到app–src-main–cpp,在其上右键New–C/C++ Source File

这里我创建一个native-test.cpp

然后将native-lib.cpp中的函数复制过来,修改一下函数名和返回的字符串

1.4.2 修改CMake构建脚本

构建脚本只能有一个,而且名字必须为CMakeLists.txt,我们在Project视图app根目录下找到CMakeLists.txt。

模仿书写add_library()指出要生成的lib名及对应的源文件

接下来修改MainActivity.java将默认生成的调用stringFromJNI()改为调用我们修改过的stringFromJNITest()

程序成功安装运行,显示字符串也确实为”native-test:Hello from C++”,证明成功调用

二、向已有项目添加添加C/C++代码

首先要明确,“为已有项目添加添加C/C++代码”其实质就要将“创建支持C/C++原生代码的新项目”中IDE自动为做好的步骤手动去实现。

C/C++原生代码支持在Android Studio中就是以下四步:第一步,安装CMake/LLDB/NDK。第二步,在项目中创建原生源文件。第三步,创建和编写CMake构建脚本CMakeLists.txt。第四步,向Gradle注册构建请求。

手动实现时照葫芦画标即可。

2.1 安装CMake/LLDB/NDK

与前边1.1节一样,不再赘述。

2.2 在项目中创建原生源文件

写入一个测试函数(这个函数就是“创建支持C/C++原生代码的新项目”时默认生成的函数,借过来用即可。不过千万要注意函数名前的包名要改成自己当前的包名,不然在java中调用就报找不到函数了)

复制代码

#include <jni.h> #include <string>

extern “C” JNIEXPORT jstring

JNICALL
Java_com_example_ls_test1_Main1Activity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = “Hello from C++”; return env->NewStringUTF(hello.c_str());
}

复制代码

2.3 创建和编写CMake构建脚本CMakeLists.txt

写入以下内容(这里“创建支持C/C++原生代码的新项目”时自动生成的内容,find_library和target_link_libraries还不很清楚什么用但全复制进去准没错)

复制代码

cmake_minimum_required(VERSION 3.4.1)

add_library( # Sets the name of the library.
native-lib

         # Sets the library as a shared library.
         SHARED

         # Provides a relative path to your source file(s).
         src/main/cpp/native-lib.cpp )

find_library( # Sets the name of the path variable.
log-lib

          # Specifies the name of the NDK library that
          # you want CMake to locate.
          log )

target_link_libraries( # Specifies the target library.
native-lib

                   # Links the target library to the log library
                   # included in the NDK.
                   ${log\-lib} )

复制代码

2.4 向Gradle注册构建请求

打开build.gradle向android/defaultConfig节区追加以下内容:

externalNativeBuild {
cmake {
cppFlags “” }
}

向android节区追加以下内容:

externalNativeBuild {
cmake {
path “CMakeLists.txt” }
}

2.5 确认程序可以成功调用****C++函数

这个和1.3一样然证即可,我自己测试结果和1.3一样是可以成功调用的。

  本文通过docker-compose方式安装运行drone,先将drone的server和agent镜像拉取到本地,这样docker-compose脚本执行速度会快一点。当然,不是必须先拉取drone镜像,完全可以直接用docker-compose执行编写好的脚本。

  拉取drone镜像

sudo docker pull drone/drone:1.0.0-rc.5 sudo docker pull drone/agent:1.0.0-rc.5

  安装docker-compose,已安装的话可跳过。

sudo curl -L “https://github.com/docker/compose/releases/download/1.24.1/docker-compose-$(uname -s)-$(uname -m)” -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

  需要创建一个目录,在此目录下编写docker-compose.yml文件,然后运行docker-compose命令

mkdir /etc/drone
vi /etc/drone/docker-compose.yml

   docker-compose.yml内容如下:

复制代码

version: ‘3’

services:
drone-server:
image: drone/drone:1.0.0-rc.5 ports: - 10081:80 volumes: - /var/lib/drone:/var/lib/drone
      - /var/run/docker.sock:/var/run/docker.sock
restart: always
env_file: - /etc/drone/server.env

drone-agent:
image: drone/agent:1.0.0-rc.5 command: agent
depends_on: - drone-server
volumes: - /var/run/docker.sock:/var/run/docker.sock
restart: always
env_file: - /etc/drone/agent.env

复制代码

  生成drone和agent之间通信需要的密钥

LC_ALL=C </dev/urandom tr -dc A-Za-z0-9 | head -c 65 && echo

  此次生成的密钥内容为:MWckgvhjqg4E3eQ0psgZX4iNCxoQiyU4LLvO4eXFFuHtrTkIy8vwcAc3erB5f9reM

  在/etc/drone/目录创建文件server.env,保存如下内容:

复制代码

DRONE_RPC_SECRET=MWckgvhjqg4E3eQ0psgZX4iNCxoQiyU4LLvO4eXFFuHtrTkIy8vwcAc3erB5f9reM
DRONE_SERVER_HOST=192.168.1.137:10081 DRONE_SERVER_PROTO=http
DRONE_OPEN=true DRONE_GOGS=true DRONE_GOGS_SERVER=http://192.168.1.137:10080 DRONE_PROVIDER=gogs
DRONE_LOGS_TRACE=true
DRONE_LOGS_DEBUG=true

复制代码

  agent.env内容如下:

DRONE_RPC_SECRET=MWckgvhjqg4E3eQ0psgZX4iNCxoQiyU4LLvO4eXFFuHtrTkIy8vwcAc3erB5f9reM
DRONE_RPC_SERVER=http://192.168.1.137:10081
DRONE_LOGS_TRACE=true
DRONE_LOGS_DEBUG=true

  通过命令启动drone

  本地访问drone服务器地址http://192.168.1.137:10081,用gogs账号登录drone

   登录失败,gogs服务器connection refused,应该是端口问题,那就开放gogs端口。请确保gogs已经启动。

sudo firewall-cmd –zone=public –add-port=10080/tcp –permanent
sudo firewall-cmd –reload

  登录之后在drone面板先同步gogs代码仓库,选择一个仓库,在设置选项填写之前生成的密钥,保存设置之后,gogs仓库会生成一个web hook

  由于之前已经配置好了gogs,进入项目仓库,更新web钩子,将推送地址加上drone服务器端口号

  更新web hook之后,点击下面的测试推送,如果失败提示connection refused的话,就开放drone的10081端口。

  为这个空仓库新增.drone.yml文件

复制代码

kind: pipeline
name: greeting

steps: - name: en
image: alpine
commands: - echo hello - echo world - name: es
image: alpine
commands: - echo hola - echo mundo - name: fr
image: alpine
commands: - echo bonjour - echo monde - name: zh
image: alpine
commands: - echo 你好 - echo 世界

复制代码

  提交到gogs仓库后,自动触发web hook事件,drone会执行yml文件中的脚本

  然鹅构建却一直pending…

  这个drone.yml脚本比较简单,只是简单的打印消息,然而不解的是不知道为什么没有输出。不得其解就继续翻官方文档和各种issue以及网络教程,没什么头绪就把配置项尽可能写全一点,发现原来是数据库配置项的原因,drone默认使用sqlite做数据持久化,但是文档也写明说不需要额外的配置,但是按照目前的配置,虽然web hook可以触发,但是pipeline任务一直处于pending状态,加上数据库配置项,终于有了动静。

  虽然官方强烈建议使用postgres而不是mysql,但是既然drone支持使用mysql,而且我安装的就是mysql,那当然是使用mysql配置项了。drone还强烈建议使用gitea而不是gogs呢。在server.env中加上下面这两行配置,mysql账号密码和ip改成自己的。

DRONE_DATABASE_DRIVER=mysql
DRONE_DATABASE_DATASOURCE=root:password@tcp(1.2.3.4:3306)/drone?parseTime=true

  也可以使用下面的配置,会在配置路径下产生sqlite脚本,drone产生的数据会保存在这个脚本中。

- DRONE_DATABASE_DATASOURCE=/var/lib/drone/drone.sqlite - DRONE_DATABASE_DRIVER=sqlite3

  重新执行一下pipeline脚本,终于成功。

  以上的docker-compose.yml也可以整合为一个文件,贴一下内容,可供参考

复制代码

version: ‘3’
services:
drone-server:
image: drone/drone:1.0.0-rc.5 ports: - 10081:80 volumes: - ./drone:/var/lib/drone/
- /var/run/docker.sock:/var/run/docker.sock
restart: always
environment: - DRONE_OPEN=true
- DRONE_SERVER_HOST=192.168.1.137:10081
- DRONE_SERVER_PROTO=http - DRONE_LOGS_TRACE=true
- DRONE_LOGS_DEBUG=true
- DRONE_GOGS=true
- DRONE_GOGS_SERVER=http://192.168.1.137:10080
- DRONE_PROVIDER=gogs - DRONE_DATABASE_DATASOURCE=root:your_password@tcp(192.168.1.137:3306)/drone?parseTime=true
- DRONE_DATABASE_DRIVER=mysql - DRONE_RPC_SECRET=MWckgvhjqg4E3eQ0psgZX4iNCxoQiyU4LLvO4eXFFuHtrTkIy8vwcAc3erB5f9reM
drone-agent:
image: drone/agent:1.0.0-rc.5 depends_on: - drone-server
volumes: - /var/run/docker.sock:/var/run/docker.sock
restart: always
environment: - DRONE_RPC_SERVER=http://192.168.1.137:10081
- DRONE_RPC_SECRET=MWckgvhjqg4E3eQ0psgZX4iNCxoQiyU4LLvO4eXFFuHtrTkIy8vwcAc3erB5f9reM - DRONE_LOGS_TRACE=true
- DRONE_LOGS_DEBUG=true

复制代码

  记录一下可能会遇到的问题:

  1、web hook可以正常推送消息,而pipeline脚本一直pending,检查drone-server的数据库配置项;

  2、以非root用户运行,可能会遇到docker权限问题,需要将当前用户加入到docker用户组,避免重新登录的话可以su root,然后再su当前用户;

  3、pipeline脚本执行的时候抛出docker权限异常,需要将drone-agent挂载docker执行目录。

  通过此番操作,已经实现了持续集成,要实现持续交付,关键在于.drone.yml的编写,Deployment部分待下回推送代码之后再做分享。

  顺便说一下drone的文档,1.0之前的文档提供的安装方式是docker compose,有中文文档;1.0之后的文档提供的安装方式的使用docker,只有英文版,不过好像断层了,没有提供1.0版本之前的文档链接。

CICD-Drone整合sonar扫描bug并推送钉钉| 8月更文挑战Drone-Sonar扫描bug推送钉钉,以及Dr - 掘金

Excerpt

Drone-Sonar扫描bug推送钉钉,以及DroneSecret使用 紧接着上一篇文章 DroneCi安装和Nexus3私有仓库搭建整合 1. 安装Sonar 我这里快速安装,就不挂载配置和安装数


CICD-Drone整合sonar扫描bug并推送钉钉| 8月更文挑战

Drone-Sonar扫描bug推送钉钉,以及DroneSecret使用

紧接着上一篇文章 DroneCi安装和Nexus3私有仓库搭建整合

本章内容介绍安装sonar和drone整合以及推送钉钉

1. 安装Sonar

我这里快速安装,就不挂载配置和安装数据库了

1
docker run -d --name sonar -p 9000:9000 -p 9092:9092 sonarqub

2. 生成令牌

访问ip:9000登录sonar,点击头像MyAccout->Security Generate Tokens,生成sonar token

1
- name: 质量扫描 image: aosapps/drone-sonar-plugin settings: sonar_host: {sonar ip:port} sonar_token: {sonar token}

3.使用DroneSecret

进入Drone页面,点击对应项目的settings->secrets,添加密文 在这里插入图片描述 此时可以替换为以下写法,并且在step日志中会以****的形式出现(env不能这样使用不会生效)

1
- name: 质量扫描 #如果是java必须要编译后再进行扫描 image: aosapps/drone-sonar-plugin #官方插件库中的镜像 settings: sonar_host: from_secret: sonar_host sonar_token: from_secret: sonar_token

3.配置sonar-project.properties

在项目根目录添加一个配置文件

1
sonar.projectKey={在sonar创建项目时填的} sonar.projectName={在sonar创建项目时填的} sonar.projectVersion=1 sonar.sources=/drone/src sonar.java.binaries=/drone/src/target/classes #这个要指定编译后文件的目录

4.没有找到合适的插件,自己写了个推送报告到钉钉的插件

1
- name: 推送报告到钉钉 #该步骤建议放在最后面或运行前,因为执行速度快可能获取的数据还没更新到sonar image: yujian1996/sonar-ding:1 environment: accessKey: {钉钉的accesskey} projectKeys: {sonar项目的key} sonarUrl: {sonar的ip:port}

源码: github.com/NoBugBoy/So…

5.运行一个容器

1
- name: start image: plugins/docker commands: - docker run -d --name test 192.168.31.79:5000/mytest/test volumes: - name: docker path: /var/run/docker.sock when: branch: master event: [ push ]

在这里插入图片描述 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210520183921683.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0RheV9EYXlfTm9fQnVn,size_16,color_FFFFFF,t_70

6.覆盖率问题

如果执行发现覆盖率一直是0则需要生成覆盖报告

1
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.7.8</version> <executions> <execution> <goals> <goal>prepare-agent</goal> <goal>prepare-agent-integration</goal> <goal>report</goal> <goal>report-integration</goal> </goals> </execution> </executions> </plugin> </plugins> </build>

打包&单元测试流程可改为如下操作

1
- name: 打包&单元测试 image: maven:3.6.2-jdk-8 commands: - mvn clean - mvn org.jacoco:jacoco-maven-plugin:prepare-agent install -Dmaven.test.failure.ignore=true - mvn package volumes: - name: cache path: /root/.m2 when: branch: master event: [ push ]

本文收录于以下专栏

cover

下一篇

Drone统一管理.drone.yaml文件

http://www.blogcn.com/user8/flier_lu/index.html?id=1670460&run=.0FB98AB

CLR中通过预定义属性(Attribute)为值类型结构的定义提供了很大的灵活性,基本上可以很灵活地处理绝大部分原有Win32 API和COM接口的定义。

          对没有显式指定的类和结构,C#及其它编译器有权利任意更改字段定义顺序,以优化占用空间和访问时间。例如一个结构

以下为引用:

 struct A
 {
   short a;
   int b;
   byte c;
   byte d;
 }  

         在缺省情况下使用的是LayoutKind.Auto模式,编译器可以任意重排字段顺序,如

以下为引用:

 struct A
 {
   int b;
   short a;
   byte c;
   byte d;
 }  

      以保障字节对齐,在不增加填补(padding)空间的同时提高访问效率。
     但这样一来就无法与C++/COM等定义的结构兼容,因此需要显式指定排列方式为LayoutKind.Sequential或LayoutKind.Explicit。前者要求编译器保障内存中字段顺序与定义的相同,按照StructLayoutAttribute.Pack进行对齐;后者则通过FieldOffsetAttribute预定义属性显式定义每个字段的位置,可以用于模拟Union语义。
     例如前面例子可以强制要求结构保持字段顺序同时按1字节对齐

以下为引用:

 [StructLayout(LayoutKind.Sequential, Pack=1)]
 struct A
 {
   short a;
   int b;
   byte c;
   byte d;
 }  

        而通过 LayoutKind.Explicit 模式则可以模拟 Union 语义

以下为引用:

 union MyUnion
 {
     int number;
     double d;
 }  

以下为引用:

 [ StructLayout( LayoutKind.Explicit )]
 public struct MyUnion 
 {
    [ FieldOffset( 0 )]
    public int i;
    [ FieldOffset( 0 )]
    public double d;
 }  

         但这里的 Union 语义受到一定的限制:在被 Union 语义使用的字段或结构中,必须很小心地使用引用类型或 Marshal 形式的字段,因为 CLR 不允许值类型和引用类型在内存布局上重叠。准确的说是 LayoutKind.Explicit 这种内存布局中不能有引用类型字段与值类型字段重叠,而引用类型字段与引用类型字段,或者值类型字段与值类型字段完全重叠都是允许的。

          实际上 C# 中提供了很多 syntax sweet 辅助用户处理常见数据结构,例如对数组和字符串的处理就提供了很全面的支持,如

以下为引用:

 typedef struct _MYSTRSTRUCT
 {
    wchar_t* buffer;
    UINT size; 
 } MYSTRSTRUCT;

 struct UnmanagedInformation {
   int num;
   char* string;
   int array[32];
 };  

         可以通过 PInvoke 内建支持很容易转换为 Managed 结构定义

以下为引用:

 [ StructLayout( LayoutKind.Sequential, CharSet=CharSet.Unicode )]
 public struct MyStrStruct 
 {  
    public String buffer;
    public int size;
 }

 [StructLayout( LayoutKind.Sequential, CharSet=CharSet.Ansi )]
 struct ManagedInformation {
   public int num;
   public string str;
   [MarshalAs (UnmanagedType.ByValArray, SizeConst=32)]
   public int[] array[];
 }  

         这种特殊的语法现象,实际上是使用引用类型的语法来操作值类型的语义。例如上面通过 [MarshalAs (UnmanagedType.ByValArray, SizeConst=32)] 定义的数组,在 CLR 中语法上是作为一个引用类型存在的。

以下为引用:

 .field public  marshal( fixed array [32]) int32[] ‘array’  

         使用的时候同样需要先 info.array = new int[32]; 然后才能使用;但同时它在语义上定义了 ManagedInformation 结构中的一个连续内存区域。

以下为引用:

 void test2()
 {
   ManagedInformation info = new ManagedInformation();

   info.num = 3;
   info.str = “Test”;
   info.array = new int[32];
   for(int i=0; i<info.array.Length; i++)
     info.array[i] = i+1;

   IntPtr pInfo = Marshal.AllocHGlobal(Marshal.SizeOf(info));
   Marshal.StructureToPtr(info, pInfo, false);
   Marshal.FreeHGlobal(pInfo);
 }  

         从上面的例子可以看到 info.str 和 info.array 的使用完全和引用类型一致,但在执行完 Marshal.StructureToPtr 后,在调试器的数据窗口直接查看 pInfo 指向的内存区域,就会发现 info.str 保存了一个指向 “Test” 字符串的 Unmanaged 指针,而 info.array 是一个固定长度的连续地址数组。

          在平时这种 syntax sweet 可以大大简化用户的操作,但是在使用 Union 语义的时候就不成了,因为这种用引用类型对值类型的包装,是无法与其他值类型字段在内存上重叠的。做这样限定的原因,可能是因为对 GC 来说需要确定地知道某个字段是否为引用类型,CLR不能给 GC 一个具有二义性的语义。而 Marshal 的字段实际上也是一个引用字段,故而受到同样的限制。因为当一个引用类型字段与值类型字段重叠的时候就会出现二义性的问题;而值类型字段互相重叠(GC看来此内存还是保存一个值类型),或者引用类型字段互相重叠不存在二义性的问题。

          也就是说类似 DEBUG_EVENT 的结构,是无法在C#中直接通过正常语法定义的。

以下为引用:

 typedef struct _EXCEPTION_RECORD {  
   DWORD ExceptionCode;  
   DWORD ExceptionFlags;  
   struct _EXCEPTION_RECORD* ExceptionRecord;  
   PVOID ExceptionAddress;  
   DWORD NumberParameters;  
   ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
 } EXCEPTION_RECORD, *PEXCEPTION_RECORD;

 typedef struct _EXCEPTION_DEBUG_INFO {  
   EXCEPTION_RECORD ExceptionRecord;  
   DWORD dwFirstChance;
 } EXCEPTION_DEBUG_INFO, *LPEXCEPTION_DEBUG_INFO;

 typedef struct _DEBUG_EVENT {  
   DWORD dwDebugEventCode;  
   DWORD dwProcessId;  
   DWORD dwThreadId;  
   union {    
     EXCEPTION_DEBUG_INFO Exception;    
     CREATE_THREAD_DEBUG_INFO CreateThread;    
     CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;    
     EXIT_THREAD_DEBUG_INFO ExitThread;    
     EXIT_PROCESS_DEBUG_INFO ExitProcess;    
     LOAD_DLL_DEBUG_INFO LoadDll;    
     UNLOAD_DLL_DEBUG_INFO UnloadDll;    
     OUTPUT_DEBUG_STRING_INFO DebugString;    
     RIP_INFO RipInfo;  
   } u;
 } DEBUG_EVENT, *LPDEBUG_EVENT;  

         如果定义成类似上面 Union 的形式,如

以下为引用:

 [StructLayout(LayoutKind.Sequential)]
   public struct EXCEPTION_RECORD 
 {
   public const int EXCEPTION_MAXIMUM_PARAMETERS = 15;

   public int ExceptionCode;
   public uint ExceptionFlags;
   public IntPtr ExceptionRecord;
   public IntPtr ExceptionAddress;
   public uint NumberParameters;

   [MarshalAs(UnmanagedType.ByValArray, SizeConst=EXCEPTION_MAXIMUM_PARAMETERS)]
   public uint[] ExceptionInformation;
 }

 [StructLayout(LayoutKind.Sequential)]
   public struct EXCEPTION_DEBUG_INFO 
 {
   public EXCEPTION_RECORD ExceptionRecord;
   public uint dwFirstChance;
 } 

 [StructLayout(LayoutKind.Explicit)]
 public struct DEBUG_EVENT
 {
   [FieldOffset(0)] public uint dwDebugEventCode;
   [FieldOffset(4)] public uint dwProcessId;
   [FieldOffset(8)] public uint dwThreadId;

      [FieldOffset(12)] public EXCEPTION_DEBUG_INFO Exception;      
   [FieldOffset(12)] public CREATE_THREAD_DEBUG_INFO CreateThread;
   [FieldOffset(12)] public CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;
   [FieldOffset(12)] public EXIT_THREAD_DEBUG_INFO ExitThread;
   [FieldOffset(12)] public EXIT_PROCESS_DEBUG_INFO ExitProcess;
   [FieldOffset(12)] public LOAD_DLL_DEBUG_INFO LoadDll;
   [FieldOffset(12)] public UNLOAD_DLL_DEBUG_INFO UnloadDll;
   [FieldOffset(12)] public OUTPUT_DEBUG_STRING_INFO DebugString;
   [FieldOffset(12)] public RIP_INFO RipInfo;
 }

         则在动态构造 DEBUG_EVENT 时会引发异常,在静态构造时程序进入函数前就异常。异常信息如下:

以下为引用:

 An unhandled exception of type ‘System.TypeLoadException’ occurred in Debugger.exe

 Additional information: 未能从程序集 Debugger, Version=1.0.1572.568, Culture=neutral, PublicKeyToken=null 中加载类型 DEBUG_EVENT,因为它在 16 偏移位置处包含一个对象字段,该字段已由一个非对象字段不正确地对齐或重叠。  

         最可恨的是如果静态构造时,程序根本就在构造函数堆栈时歇菜了,不进入函数体也不抛出异常。害得我晚上调试时以为是自己程序的问题,把代码改的乱七八糟,最后只能靠屏蔽代码发现这个问题 :( 
     例如下面的简单代码会导致函数无法进入但也没有异常抛出,就看到 CPU 到了 100%,几秒钟后就一切正常,但函数并没有被调用。

以下为引用:

 class Test
 {
   void DoSomething()
   {
     Win32.DEBUG_EVENT evtHeader;
   }
 }  

         直接查看 IL 代码也没有什么异常用法,问题可能出在 JIT 载入类型的时候 :( 不知道这算不算 .NET Framework 1.1 的一个 bug。IL 代码如下,很简单,问题应该出在载入类型构造堆栈的时候

以下为引用:

 .method public hidebysig newslot virtual final 
         instance void  DebugEvent(unsigned int32 pDebugEvent,
                                   int32 fOutOfBand) cil managed
 {
   // Code size       2 (0x2)
   .maxstack  0
   .locals init ([0] valuetype Debugger.Utils.Win32/DEBUG_EVENT evtHeader)
   IL_0000:  nop
   IL_0001:  ret
 } // end of method UnmanagedEventListener::DebugEvent  

         MSDN上举例时使用的方式是为 Union 语义的每种情况定义一个单独的结构,但这样代码太过繁琐。我现在采用的是通过设计的方式回避这个问题,根据DEBUG_EVENT.dwDebugEventCode动态判断其后字段的内容,再手工转换。

          我首先定义一个DEBUG_EVENT_HEADER类型,然后在代码中先转换这一部分内容

以下为引用:

 public class Win32 
 {
   [StructLayout(LayoutKind.Sequential)]
     public struct DEBUG_EVENT_HEADER
   {
     public uint dwDebugEventCode;
     public uint dwProcessId;
     public uint dwThreadId;
   }
 }

 // …

      public class UnmanagedEventListener
 {
   public void DebugEvent(uint pDebugEvent, int fOutOfBand)
   {
     IntPtr pEvent = new IntPtr(pDebugEvent);

          Win32.DEBUG_EVENT_HEADER evtHeader = (Win32.DEBUG_EVENT_HEADER)Marshal.PtrToStructure(
       new IntPtr(pDebugEvent), typeof(Win32.DEBUG_EVENT_HEADER));

          // …
   }
 }

 // …  

     这里的 pDebugEvent 参数是一个指针,我使用 new IntPtr(pDebugEvent) 将之转换为 Managed 指针,传递给 Marshal.PtrToStructure 函数,从一个 Unmanaged 内存指针读入一个结构到 Managed 值类型对象。然后就可以根据事件类型进行处理,如

以下为引用:

 public class UnmanagedEventListener
 {
   public void DebugEvent(uint pDebugEvent, int fOutOfBand)
   {
     // …

          IntPtr pEventData = new IntPtr(pDebugEvent + Marshal.SizeOf(evtHeader));

          switch(evtHeader.dwDebugEventCode)
     {
       case Win32.CREATE_PROCESS_DEBUG_EVENT:
       {
         ProcessEvent(ctxt, (Win32.CREATE_PROCESS_DEBUG_INFO)
           Marshal.PtrToStructure(pEventData, typeof(Win32.CREATE_PROCESS_DEBUG_INFO)));          
         break;
       }
       case Win32.EXIT_PROCESS_DEBUG_EVENT:
       {
         ProcessEvent(ctxt, (Win32.EXIT_PROCESS_DEBUG_INFO)
         Marshal.PtrToStructure(pEventData, typeof(Win32.EXIT_PROCESS_DEBUG_INFO)));
         break;
       }     
       // … 
     } 
     // …
   } 
   // …
 }

         这样一来就可以通过指针操作模拟 Union 语义,但远不如直接用属性定义看起来舒服,呵呵

          一个稍微好一点的解决方法是自定义一些属性,标记 DEBUG_EVENT 结构里那些 Union 中的结构,然后通过 Reflection 完成读取和转换工作,代码比较繁琐,这里就不一一列举了。

          但是实际上在传统C++语言中这种用法是大量存在,例如在处理复杂网络协议和文件结构的程序里面,多层 Union 加上数组的结构是无法避免的。而 CLR 在处理结构的时候,实际上可以通过进一步将之细分为运行时内存布局和存储时内存布局来解决这个问题,以免用户通过较为 dirty 的方式模拟这个语义。但可惜到最新的 .NET Framework 2.0 都还是存在这个限制。

               关于结构类型定义的方法下面这篇文章里面有非常详细的讲解

 btw: 感谢MSN上的朋友 笨笨 (-_-b)和水木网友 Nineteen 提示我找出真正问题所在 :P

作者:林冠宏 / 指尖下的幽灵

掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8

博客:http://www.cnblogs.com/linguanh/

GitHub : https://github.com/af913337456/

大部分人学习或者使用某样东西,喜欢在直观上看到动手后的结果,才会有继续下去的兴趣。

前言:

Golang 调用 C/C++ 的教程网上很多,就我目前所看到的,个人见解就是比较乱,坑也很多。希望本文能在一定程度上,做到更通俗明了。

下面 golang 简称 go , 一如既往,少说废话,我们现在开始。


go 调用 c/c++ 函数的实现方式有:

  • 直接嵌套在go文件中使用,最简单直观的
  • 导入动态库 .so 或 dll 的形式,最安全但是很不爽也比较慢的
  • 直接引用 c/c++ 文件的形式,层次分明,容易随时修改看结果的

第三个直接引用 c/c++ 文件的形式 是我要介绍的重点。

需要的环境支持

  • Linux 具备 gcc 与 g++ 即可
  • Windows 需要安装 mingw,否则编译时会有这类错:cannot find -lmingwex
  • Mac 参考 Linux

1,直接嵌套在go文件

1
2
3
4
5
6
7
8
9
package main

import "C"

import "fmt"

func main() {
fmt.Println(C.add(2, 1))
}

上面的代码,直接拷贝运行就能输出结果:3

结论:

  • 但凡要引用与 c/c++ 相关的内容,写到 go 文件的头部注释里面
  • 嵌套的 c/c++ 代码必须符合其语法,不与 go 一样
  • import "C" 这句话要紧随,注释后,不要换行,否则报错
  • go 代码中调用 c/c++ 的格式是: C.xxx(),例如 C.add(2, 1)

2,导入动态库 .so 或 .dll 的形式

假设项目目录如下

1
2
3
4
5
6
7
8
|-project
| |-lib
| | |-libvideo.dll
| | |-libvideo.so
| |-include
| | |-video.h
| |-src
| | |-main.go

头文件 .h 如下面这样

1
2
3
4
5

#ifndef VIDEO_H
#define VIDEO_H
void exeFFmpegCmd(char* cmd);
#endif

源文件 .c 如下面这样

1
2
3
4
5
6
7
#include <stdio.h>
#include "video.h"

void exeFFmpegCmd(char* cmd){

printf("finish");
}

使用 gcc 或 g++ 生成 .so库,或 win 下生成 dll

例如: gcc video.c -fPIC -shared -o libvideo.so

最后 main.go

把动态库放到一个你喜欢的目录,也可以放到当前项目里面,像上面列出的例子一样。再引用

1
2
3
4
5
6
7
8
9
10
11
12
13

package main


import "C"

import "fmt"

func main() {
cmd := C.CString("ffmpeg -i ./xxx/*.png ./xxx/yyy.mp4")
C.exeFFmpegCmd(&cmd)
}

先回答为什么说这种是最安全的和最不爽的?原因如下:

  • 动态库破解十分困难,如果你的 go 代码泄露,核心动态库没那么容易被攻破
  • 动态库会在被使用的时候被加载,影响速度
  • 操作难度比方式一麻烦不少

结论

  • CFLAGS: -I路径 这句话指明头文件所在路径,-Iinclude 指明 当前项目根目录的 include 文件夹
  • LDFLAGS: -L路径 -l名字 指明动态库的所在路径,-Llib -llibvideo,指明在 lib 下面以及它的名字 video
  • 如果动态库不存在,将会爆找不到定义之类的错误信息

3,直接引用 c/c++ 文件的形式 (重点)

假设项目目录如下

1
2
3
4
|-util
| |-util.h
| |-util.c
| |-util.go

util.h

1
int sum(int a,int b);

util.c

1
2
3
4
#include "util.h"
int sum(int a,int b){
return (a+b);
}

util.go

1
2
3
4
5
6
7
8
9
10
11
package util


import "C"

import "fmt"

func GoSum(a,b int) int {
s := C.sum(C.int(a),C.int(b))
fmt.Println(s)
}

这样调用 main.go

1
2
3
4
5
package main

func main(){
util.GoSum(4,5)
}

第三种方式便是如此简洁明了

最后,补充一下,一般需要 go 调用 c/c++ 的,主要是使用一些著名的开源库,例如 ffmpegopencv,等这些源码是基于 c/c++ 语言的,除此之外还有一个很重要的点,便是运行速度!

利用ArcGIS EngineVS .NETWindows控件开发GIS应用

Dixon整理

此过程说明适合那些使用.NET建立和部署应用的开发者,它描述了使用ArcGIS控件建立和部署应用的方法和步骤。

你可以在下面的目录下找到相应的样例程序:

<安装目录>/DeveloperKit/Samples/Developer_Guide_Scenarios/ ArcGIS_Engine/Building_an_ArcGIS_Control_Application/Map_Viewer

注:ArcGIS__样例程序不包含在__ArcGIS Engine__开发工具包“典型”安装方式中。如果你没有安装它们,则可以重新运行开发工具包安装向导,选择“定制”或“修改”方式,并选择软件开发包下的样例项进行安装。

一、****项目描述

利用视窗控件建立应用程序的目标是演示并使你熟悉在微软Visual Studio .NET API中使用标准ArcGIS控件开发和部署GIS应用所需的步聚。本节中使用了Visual Studio .NET开发环境中的MapControl、 PageLayoutControl、TOCControl和ToolbarControl等视窗控件。COM、Java和C++程序员应该参考如下章节:_利用ActiveX建立应用程序利用可视化JavaBeans建立应用程序建立命令行方式的Java应用建立命令行方式的C++应用_。

本节演示了创建查看ArcMap和ArcGIS桌面应用图形文档的GIS应用程序的步骤。此节包含了以下技术:

l 在微软Visual Studio .NET中加载和嵌入ArcGIS控件。

l 向PageLayoutControl和MapControl中加载图形文档。

l 设置ToolbarControl和TOCControl的绑定控件。

l 处理窗口缩放。

l 向ToolbarControl添加ArcGIS Engine命令和工具。

l 创建弹出式菜单

l 在TOCControl中管理标签编辑

l 在MapControl中绘制图形。

l 为MapControl、PageLayoutControl和ToolbarControl创建定制工具。

l 用户化ToolbarControl。

l 在Windows操作系统中部署应用。

二、****概述

本方案使用微软Visual Studio .NET开发环境加以实现,并使用了ESRI interop程序集(Interop Assemblies),它服务于被放置在.NET窗体上的、位于.NET 窗体控件(.NET Windows Controls)中的ArcGIS控件,这些程序集在托管的.NET代码和非托管的COM代码之间起了桥梁作用。对COM ArcGIS控件(COM ArcGIS Controls)成员的引用都要经过Interop程序集,然后到达实际的COM对象。同样,也从COM对象经过Interop程序集到达.NET应用程序。每个ArcGIS Engine控件具有方法、属性与事件,它们能够被控件嵌入的容器(如,.NET窗体)访问。每个控件对象及其功能可以与其他ESRI ArcObjects和自定义控件组合使用,创建用户化的客户应用程序。

此方案是使用了C#和Visual Basic .NET两种语言创建,但以下技术实现集中倾向于C#方案。许多开发者可能会感觉用Visual Basic .NET更舒服,那是因为他们已经比较熟悉Visual Basic 6.0代码,然而,对于Java和C++程序员来说,他们将会觉得对C#程序语言的语法更熟悉。无论你使用哪种开发环境,对于使用ArcGIS控件的好坏既依赖于你的编程环境技术,也依赖于你所掌握的ArcObjects技术。

在本方案中,使用ToolbarControl、TOCControl、PageLayoutControl和MapControl来为应用程序提供用户界面。这些ArcGIS控件与其他ArcObjects和ArcGIS Engine命令被开发者一起使用,用来创建一个GIS视窗应用。

三、****设计

此方案在设计时,首先强调了ArcGIS 控件如何互相之间进行交互,其次,向开发者解释说明了ArcGIS 控件对象模型的一部分。

每个.NET ArcGIS Engine控件包含有一套能够被嵌入其内的窗口即时访问的属性页。这些属性些为控件属性和方法的选择提供了捷径,并且允许开发者不写任何代码即可创建一个应用程序。本方案并没有使用属性页,而是采用写代码的方式建立应用程序。关于属性页的更进一步的信息,请参考_ArcGIS__开发帮助__(ArcGIS Developer Help)_。

四、****条件需求

要顺利地完成以下方案,你需要以下条件(对于部署的需求将在后续的部署章节涉及到):

l 安装具有授权文件的ArcGIS Engine开发工具包(Developer Kit),使之能够用于开发。

l 安装有微软Visual Studio .NET 2003开发环境和微软.NET Framework 1.1及其相应协议。

l 熟悉微软Windows操作系统和Microsoft Visual Studio .NET的工作知识,会用C#或Visual Basic .NET编程语言。当然,此方案中提供了一些如何在Microsoft Visual Studio .NET中使用ArcGIS控件的信息,但它不能替代对开发环境的培训。

l 不需要对ESRI其它软件有足够的经验,但如果以前对ArcObjects有所接触并对ArcGIS应用(如,ArcCatalog,ArcMap)有一个基本了解,则对于开发更有利。

l 访问来自本方案的样例数据和代码,它位于:

<安装目录>/DeveloperKit/Samples/Developer_Guide_Scenarios/ ArcGIS_Engine/Building_an_ArcGIS_Control_Application/Map_Viewer

本方案中使用到的控件和库如下:

l AxMapControl

l AxTOCControl

l AxPageLayoutControl

l AxToolbarControl

l ESRI.ArcGIS.Carto

l ESRI.ArcGIS.System

l ESRI.ArcGIS.Display

l ESRI.ArcGIS.SystemUI

l ESRI.ArcGIS.Geometry

l ESRI.ArcGIS.Utility

l esriMapControl

l esriTOCControl

l esriPageLayoutControl

l esriToolbarControl

五、实现

下面的实现过程中提供了你成功完成方案所需所有代码。假设你对于开发环境已经有了一定的知识,所以下面没有逐步地详细介绍如何用Microsoft Visual Studio .NET开发应用。

(一) 加载ArcGIS控件

在你为应用程序编写代码之前,应该先将应用程序将用到的ArcGIS控件和其他ArcGIS Engine库引用装载到开发环境之中。

  1. 启动Visual Studio .NET,并从新建项目对话框中创建一个新的Visual C# “Windows应用程序”项目。

  2. 将项目命名为“Controls”,并选择位置存取该项目。

  3. 在“工具箱”的“Windows窗体”标签栏中单击右键,然后从上下文菜单中选择“添加/移除项(I)…”。

  4. 在“自定义工具箱”中选择“.NET Framework组件”,并复选“AxMapControl”,“AxPageLayoutControl”,“AxTOCControl”和“AxToolbarControl”,单击确定按钮。这样所选择的控件将显示在工具箱Windows****窗体标签栏中。

  5. 单击项目菜单,并选择“添加引用(R)…”。

  6. 添加引用对话框中,双击“ESRI.ArcGIS.Carto”,“ESRI.ArcGIS.Display”,“ESRI.ArcGIS.Geometry”,“ESRI.ArcGIS.System”,“ESRI.ArcGIS.SystemUI”,“ESRI.ArcGIS.Utility”。单击确定

_注:对于ESRI .NET__程序集,将通过具体实例来说明,并使用.NET__框架提供的COM__传送服务从你的C#_项目中调用ESRI__对象库中的实体对象。

(二) 在容器中嵌入ArcGIS控件

在你能够访问每个控件的事件、属性和方法之前,需要将控件嵌入到.NET容器中。一旦将控件嵌入窗体内,它们将图形化应用程序的用户界面。

  1. 在设计模式下打开.NET窗体。

  2. 双击工具箱Windows标签栏中的AxMapControl控件,将MapControl加入到窗体上。

  3. 再将AxPageLayoutControl、AxTOCControl和AxToolbarControl如上添加到窗体中。

  4. 重新调整窗体上各个控件的大小和位置,调整结果如下所示。

  5. 在窗体上双击显示窗体代码窗口,在代码窗口的顶部增加“using”命令:

using System;

using System.Windows.Forms;

// ArcGIS Engine引用

using ESRI.ArcGIS.SystemUI;

using ESRI.ArcGIS.Carto;

using ESRI.ArcGIS.Display;

using ESRI.ArcGIS.Geometry;

using ESRI.ArcGIS.esriSystem;

using ESRI.ArcGIS.ToolbarControl;

using ESRI.ArcGIS.TOCControl;

_注:需注意__C#__是区分大小写的。当你键入“__ESRI._”时,智能敏感的自动完成功能将允许你通过按__Tab__键完成下一节。

(三) 加载Map文档到MapControl与PageLayoutControl

单独的数据层或者使用ArcMap、ArcGIS桌面应用程序产生的图形文档,能够被加载到MapControl和PageLayoutControl中。你可以加载样例图形文档,或者加载你自己的图形文档。后面你将增加一个浏览图形文档的对话框。

  1. 选择Form_Load事件,并输入下列代码(如果你使用你自己的图形文档,要替换为正确的文件名):

     // 使用相对路径向PageLayoutControl加载一个图形文档

     string filename = @”../../../../../../../../Data//ArcGIS_Engine_Developer_Guide//gulf of st. lawrence.mxd”;

     if ( axPageLayoutControl1.CheckMxFile(filename) )

     {

         axPageLayoutControl1.LoadMxFile(filename, “”);

     }

  1. 在设计模式显示窗体并从属性窗选择axPageLayoutControl1控件,显示axPageLayoutControl事件。在OnPageLayoutReplaced事件上双击向代码窗口添加该事件的处理函数。

  2. 在axPageLayoutControl1_OnPageLayoutReplaced事件中键入以下向MapControl加载样例图形文档的代码。当文档被装载入PageLayoutControl时OnPageLayoutReplaced事件将会被触发。

     private void axPageLayoutControl1_OnPageLayoutReplaced(object sender, ESRI.ArcGIS.PageLayoutControl.IPageLayoutControlEvents_OnPageLayoutReplacedEvent e)

     {

         // 加载同样的文档到MapControl

         axMapControl1.LoadMxFile(axPageLayoutControl1.DocumentFilename, null, null);

         // 设置MapControl显示范围至数据的全局范围

         axMapControl1.Extent = axMapControl1.FullExtent;

}

(四) 设置ToolbarControl与TOCControl控件的绑定控件

对于此应用程序,TOCControl和ToolbarControl控件将与PageLayoutControl相互协作,而不是MapControl。为此PageLayoutControl必须设置为绑定控件。TOCControl使用绑定的ActiveView显示图形、图层和符号。而位于ToolbarControl上的任何命令、工具或菜单项会受绑定控件的显示影响。

  1. 在Form_Load事件中的加载文档代码的后面键入以下红色部分内容:

     private void Form1_Load(object sender, System.EventArgs e)

     {

         // 使用相对路径向PageLayoutControl加载一个图形文档

         string filename = @”../../../../../../../../Data//ArcGIS_Engine_Developer_Guide//gulf of st. lawrence.mxd”;

         if ( axPageLayoutControl1.CheckMxFile(filename) )

         {

             axPageLayoutControl1.LoadMxFile(filename, “”);

         }

         // 设置绑定控件

         axTOCControl1.SetBuddyControl(axPageLayoutControl1);

         axToolbarControl1.SetBuddyControl(axPageLayoutControl1);

}

  1. 生成并运行应用程序。图形文档被加载到PageLayoutControl,并且TOCControl列出了图形文档中的数据图层。使用TOCControl通过复选和取消复选框控制图层的可见性。默认地,图形文档的焦点图(focus map)被装入MapControl控件。在这种当前情况下,ToolbarControl控件显示是空的,因为没有为它添加任何命令。试着缩放窗体,你会注意到控件不会改变尺寸。

(五) 处理窗口缩放

当窗口在运行时进行缩放时,PageLayoutControl和MapControl不会自动改变自身的尺寸。要改变控件的尺寸以便它们总是与匹配窗口的范围,你必须将控件锚定在窗口上。如果PageLayoutControl或MapControl包含大量的数据,在窗口缩放期间重绘这些数据显得相当重要。为了提高执行效率,你可以禁止数据重绘直到缩放操作完成后再重绘之。在缩放时,可以用一个可伸缩的位图来替代重绘数据。

  1. 在设计模式显示窗体并从属性窗口中选择axPageLayoutControl1。单击Anchor属性,将axPageLayoutControl1锚定在窗体的顶、左、底和右部。

  2. 锚定axMapControl控件到窗体的顶、左和底部。

  3. 在Form_Load事件的开头增加以下代码:

// 当缩放时禁止重绘

this.SetStyle(ControlStyles.EnableNotifyMessage, true);

  1. 向类增加以下常量:

        public class Form1 : System.Windows.Forms.Form

     {

         // ……

         private const int WM_ENTERSIZEMOVE = 0x231;

private const int WM_EXITSIZEMOVE = 0x232;

// ……

}

  1. 向重载的OnNotifyMessage方法中增加下列代码:

     protected override void OnNotifyMessage(Message m)

     {

         base.OnNotifyMessage (m);

         // 以下为手工添加的代码

         if ( m.Msg == WM_ENTERSIZEMOVE)

         {

             axMapControl1.SuppressResizeDrawing(true, 0);

             axPageLayoutControl1.SuppressResizeDrawing(true, 0);

         }

         else if ( m.Msg == WM_EXITSIZEMOVE)

         {

             axMapControl1.SuppressResizeDrawing(false, 0);

             axPageLayoutControl1.SuppressResizeDrawing(false, 0);

         }

}

  1. 生成并运行应用程序,试着缩放窗口。

注:禁止缩放时重画方法是通过检查发送到窗体的Windows消息工作的。当窗口开发缩放时,Windows发送WM_ENTERSIZEMOVE窗口消息。此时,我们禁止在MapControl和PageLayoutControl上绘制图形,而是使用“stretchy bitmap”绘制。当Windows发送WM_EXITSIZEMOVE消息时,窗体结束缩放,这时我们全部重绘新的范围。

(六) 向ToolbarControl增加命令

ArcGIS Engine提供了120多个命令和工具,它们与MapControl、PageLayoutControl和ToolbarControl直接相互协作。这些命令和工具为你提供了大量的经常使用的地图导航、图形管理、地物选择等方面的GIS功能。现在将在你的应用程序中增加这些命令和工具的一部分。

  1. 在Form_Load事件中的加载文档代码之前添加如下代码。

         // 增加打开档命令

         string progID;

         progID = “esriControlToolsGeneric.ControlsOpenDocCommand”;

         axToolbarControl1.AddItem(progID, -1, -1, false, 0,

             esriCommandStyles.esriCommandStyleIconOnly);

         // 增加PageLayout导航命令

         progID = “esriControlToolsPageLayout.ControlsPageZoomInTool”;

         axToolbarControl1.AddItem(progID, -1, -1, true, 0,

             esriCommandStyles.esriCommandStyleIconOnly);

         progID = “esriControlToolsPageLayout.ControlsPageZoomOutTool”;

         axToolbarControl1.AddItem(progID, -1, -1, true, 0,

             esriCommandStyles.esriCommandStyleIconOnly);

         progID = “esriControlToolsPageLayout.ControlsPagePanTool”;

         axToolbarControl1.AddItem(progID, -1, -1, true, 0,

             esriCommandStyles.esriCommandStyleIconOnly);

         progID = “esriControlToolsPageLayout.ControlsPageZoomWholePageCommand”;

         axToolbarControl1.AddItem(progID, -1, -1, true, 0,

             esriCommandStyles.esriCommandStyleIconOnly);

         progID = “esriControlToolsPageLayout.ControlsPageZoomPageToLastExtentBackCommand”;

         axToolbarControl1.AddItem(progID, -1, -1, true, 0,

             esriCommandStyles.esriCommandStyleIconOnly);

         progID = “esriControlToolsPageLayout.ControlsPageZoomPageToLastExtentForwardCommand”;

         axToolbarControl1.AddItem(progID, -1, -1, true, 0,

             esriCommandStyles.esriCommandStyleIconOnly);

         // 增加地图导航命令

         progID = “esriControlToolsMapNavigation.ControlsMapZoomInTool”;

         axToolbarControl1.AddItem(progID, -1, -1, true, 0,

             esriCommandStyles.esriCommandStyleIconOnly);

         progID = “esriControlToolsMapNavigation.ControlsMapZoomOutTool”;

         axToolbarControl1.AddItem(progID, -1, -1, true, 0,

             esriCommandStyles.esriCommandStyleIconOnly);

         progID = “esriControlToolsMapNavigation.ControlsMapPanTool”;

         axToolbarControl1.AddItem(progID, -1, -1, true, 0,

             esriCommandStyles.esriCommandStyleIconOnly);

         progID = “esriControlToolsMapNavigation.ControlsMapFullExtentCommand”;

         axToolbarControl1.AddItem(progID, -1, -1, true, 0,

             esriCommandStyles.esriCommandStyleIconOnly);

// 使用相对路径向PageLayoutControl加载一个图形文档

// ……

  1. 生成并运行应用程序。现在ToolbarControl包含了ArcGIS Engine命令和工具,你可以使用它们导航加载到PageLayoutControl中的图形文档。使用页面布局命令对当前的页面布局进行导航控制,要对存在于数据框架中的数据进行导航则使用地图命令。利用找开文档命令可以浏览并加载其他的图形文档。

(七) 给PageLayoutControl添加弹出式菜单

与给跟绑定控件协作的ToolbarControl增加ArcGIS Engine命令一样,按照前面的步骤,你也可以从ArcGIS Engine命令创建弹出式菜单。下面将向你的应用程序中增加与PageLayoutControl协作的弹出式菜单。当在PageLayoutControl可视区域点击鼠标右键的时候,弹出式菜单将显示。

  1. 向类中添加如下的成员变量(红色部分):

  public class Form1 : System.Windows.Forms.Form

  {

      private ESRI.ArcGIS.MapControl.AxMapControl axMapControl1;

      private ESRI.ArcGIS.PageLayoutControl.AxPageLayoutControl axPageLayoutControl1;

      private ESRI.ArcGIS.TOCControl.AxTOCControl axTOCControl1;

      private ESRI.ArcGIS.ToolbarControl.AxToolbarControl axToolbarControl1;

   private IToolbarMenu m_ToolbarMenu = new ToolbarMenuClass(); // 弹出式菜单

// ……

  1. 在Form_Load事件中向ToolbarControl增加命令代码的后面加载文档代码的前面增加如下代码。

     private void Form1_Load(object sender, System.EventArgs e)

     {

         // 前面是增加地图导航的代码……

// 共享ToolbarControl的命令池

         m_ToolbarMenu.CommandPool = axToolbarControl1.CommandPool;

         // 向ToolbarMenu增加命令

         progID = “esriControlToolsPageLayout.ControlsPageZoomInFixedCommand”;

         m_ToolbarMenu.AddItem(progID, -1, -1, false,

             esriCommandStyles.esriCommandStyleIconAndText);

         progID = “esriControlToolsPageLayout.ControlsPageZoomOutFixedCommand”;

         m_ToolbarMenu.AddItem(progID, -1, -1, false,

             esriCommandStyles.esriCommandStyleIconAndText);

         progID = “esriControlToolsPageLayout.ControlsPageZoomWholePageCommand”;

         m_ToolbarMenu.AddItem(progID, -1, -1, false,

             esriCommandStyles.esriCommandStyleIconAndText);

         progID = “esriControlToolsPageLayout.ControlsPageZoomPageToLastExtentBackCommand”;

         m_ToolbarMenu.AddItem(progID, -1, -1, true,

             esriCommandStyles.esriCommandStyleIconAndText);

         progID = “esriControlToolsPageLayout.ControlsPageZoomPageToLastExtentForwardCommand”;

         m_ToolbarMenu.AddItem(progID, -1, -1, false,

             esriCommandStyles.esriCommandStyleIconAndText);

         // 设置与PageLayoutControl挂接

       m_ToolbarMenu.SetHook(axPageLayoutControl1);

// 后面是加载图形文档的代码……

// ……

  1. 在设计模式显示窗体并从属性窗口中选择axPageLayoutControl1,显示axPageLayoutControl事件。双击OnMouseDown事件,向代码窗口中增加事件处理代码。

  2. 在axPageLayoutControl1_OnMouseDown事件中增加如下代码:

     private void axPageLayoutControl1_OnMouseDown(object sender, ESRI.ArcGIS.PageLayoutControl.IPageLayoutControlEvents_OnMouseDownEvent e)

     {

         // 弹出ToolbarMenu

         if ( e.button == 2)

         {

             m_ToolbarMenu.PopupMenu(e.x, e.y, axPageLayoutControl1.hWnd);

         }

}

  1. 生成并运行应用程序。在PageLayoutControl的显示区域单击右键以显示弹出菜单,并为页面布局导航。

(八) 在TOCControl中控制标签编辑

TOCControl默认允许用户自动地切换图层的可见性并改变显示在目录表中的名称。你可以增加代码防止用户在编辑名称时输入空的字符串。

  1. 在Form_Load事件的开始增加下列代码。

     private void Form1_Load(object sender, System.EventArgs e)

     {

         // 当缩放时禁止重绘

         this.SetStyle(ControlStyles.EnableNotifyMessage, true);

         // 设置标签编辑为手动方式

         axTOCControl1.LabelEdit = esriTOCControlEdit.esriTOCControlManual;

// 后面是加载文档代码

// ……

  1. 在设计模式显示窗体并从属性窗口选择AxTOCControl1控件,显示AxTOCControl事件。双击OnEndLabelEdit向代码窗口添加事件处理函数。

  2. 在axTOCControl1_OnEndLabelEdit事件中添加以下代码:

     private void axTOCControl1_OnEndLabelEdit(object sender, ESRI.ArcGIS.TOCControl.ITOCControlEvents_OnEndLabelEditEvent e)

     {

         // 禁止在编辑标签时键入空字串

         string newLabel = e.newLabel;

         if ( newLabel.Trim() == “” )

         {

             e.canEdit = false;

         }

     }

  1. 生成并生成应用程序。编辑TOCControl控件的地图、图层、标题或图例类的标签,在其上点击一次,然后再点一次调用标签编辑。试着用空字串替代标签。在编辑期间,你可以随时使用键盘上的ESC键取消编辑。

(九) 在MapControl上绘制图形

你可以将MapControl作为缩略图窗体使用,并在其上绘制显示PageLayoutControl内的焦点地图的当前范围。当你浏览PageLayoutControl数据框架内的数据时,你将看到缩略图窗口也进行了更新。

注:使用地图导航工具导航焦点图(活动图)将改变__PageLayoutControl__中焦点地图的范围并引起__MapControl__更新。使用页面布局工具导航页面布局将改变页面布局的范围(不是__PageLayoutControl__中的焦点图的范围),而__MapControl__将不更新。

  1. 向类中增加下列成员变量:

public class Form1 : System.Windows.Forms.Form

{

     private ESRI.ArcGIS.MapControl.AxMapControl axMapControl1;

     private ESRI.ArcGIS.PageLayoutControl.AxPageLayoutControl axPageLayoutControl1;

     private ESRI.ArcGIS.TOCControl.AxTOCControl axTOCControl1;

     private ESRI.ArcGIS.ToolbarControl.AxToolbarControl axToolbarControl1;

     private IToolbarMenu m_ToolbarMenu = new ToolbarMenuClass(); // 弹出式菜单

     private IEnvelope m_Envelope;   // MapControl绘制的范围

     private Object m_FillSymbol;    // 在MapControl上绘制范围使用的符号

     private ITransformEvents_VisibleBoundsUpdatedEventHandler

          visBoundsUpdatedE;          // PageLayoutControl的焦点图事件

注:声明的变量visBoundsUpdatedE__是一个托管。托管是一个类,它能够拥有对指定方法的引用,并使它链接到一个特定的事件。在事件和方法之间的链接过程有时在.NET__中被称作wiring__。

  1. 创建一个叫CreateOverviewSymbol的新函数。这个函数是创建你将在MapControl中使用的符号的地方,此符号是用来描述PageLayoutControl焦点地图数据范围的。函数中增加的代码如下:

private void CreateOverviewSymbol()

     {

         // 获取IRGBColor接口

         IRgbColor color = new RgbColor();

         // 设置颜色属性

         color.RGB = 255;

         // 获取ILine符号接口

         ILineSymbol outline = new SimpleLineSymbol();

         // 设置线符号属性

         outline.Width = 1.5;

         outline.Color = color;

         // 获取IFillSymbol接口

         ISimpleFillSymbol simpleFillSymbol = new SimpleFillSymbolClass();

         // 设置填充符号属性

         simpleFillSymbol.Outline = outline;

         simpleFillSymbol.Style = esriSimpleFillStyle.esriSFSHollow;

         m_FillSymbol = simpleFillSymbol;           

}

  1. 从Form_Load事件在TOCControl标签编辑代码之前调用CreateOverviewSymbol函数。

     private void Form1_Load(object sender, System.EventArgs e)

     {

         // 当缩放时禁止重绘

         this.SetStyle(ControlStyles.EnableNotifyMessage, true);

         // 创建MapControl使用的符号

CreateOverviewSymbol();

// 下面是标签编辑处理代码

// ……

}

  1. 增加下列OnVisibleBoundsUpdated函数。此函数将与地图范围改变时触发的事件相连接,并用来设置新的地图可见边界范围框。通过刷新MapControl,你强制它重绘其上显示的图形。

     private void OnVisibleBoundsUpdated(IDisplayTransformation sender, bool sizeChanged)

     {

         // 设置新的可见范围

         m_Envelope = sender.VisibleBounds;

         // 改变MapControl的前景状态

axMapControl1.ActiveView.PartialRefresh(

esriViewDrawPhase,esriViewForeground, null, null);      

}

5.       PageLayoutControl默认的事件接口是IPageLayoutControlEvents。这些事件不告诉我们数据边框内的地图范围。为此你需要使用PageLayoutControl的焦点地图的ItransformEvents接口。在PageLayoutControl_OnPageLayoutReplaced事件处理中的加载文档代码前面增加以下代码。

private void axPageLayoutControl1_OnPageLayoutReplaced(object sender, ESRI.ArcGIS.PageLayoutControl.IPageLayoutControlEvents_OnPageLayoutReplacedEvent e)

     {

         // 获取PageLayoutControl中焦点地图的IActiveView对象

         IActiveView activeView = (IActiveView)

             axPageLayoutControl1.ActiveView.FocusMap;

         // 捕捉PageLayoutControl的焦点图的ITransformEvents事件

         visBoundsUpdatedE = new         ITransformEvents_VisibleBoundsUpdatedEventHandler(OnVisibleBoundsUpdated);

         ((ITransformEvents_Event)activeView.ScreenDisplay

             .DisplayTransformation).VisibleBoundsUpdated += visBoundsUpdatedE;

         // 获取焦点图的范围

m_Envelope = activeView.Extent;

// 后面是加载地图文档的代码

// ……

  1. 在设计模式下显示窗体并从属性窗中选择axMapControl1,显示axMapControl事件。双击OnAfterDraw向代码窗口中增加事件处理。

  2. 向axMapControl1_OnAfterDraw事件处理中增加以下代码,使用前面创建的符号绘制MapControl显示边框。

private void axMapControl1_OnAfterDraw(object sender, ESRI.ArcGIS.MapControl.IMapControlEvents2_OnAfterDrawEvent e)

     {

         if ( m_Envelope == null)

         {

             return;

         }

         // 如果前景状态被重绘

         esriViewDrawPhase viewDrawPhase = (esriViewDrawPhase)e.viewDrawPhase;

         if ( viewDrawPhase == esriViewDrawPhase.esriViewForeground )

         {

             IGeometry geometry = m_Envelope;

             axMapControl1.DrawShape(geometry, ref m_FillSymbol);

         }

}

生成并运行应用程序。使用你先前已经加好的地图导航工具改变PageLayoutControl中焦点地图的范围。新的范围被绘制在MapControl上。

(十) 创建自定义工具

创建协同MapControl和PageLayoutControl一起运作的自定义命令和工具,与你或许已经做过的创建ESRI ArcMap应用程序命令非常相似。你将创建一个在PageLayoutControl鼠标点击位置添加包含当天日期的文本元素的自定义工具。不管用何种方法,创建同MapControl和ToolbarControl协作的命令与同PageLayoutControl一样。

这个自定义工具的相关代码与其他本方案源代码一样很有用。如果你想要直接使用自定义命令,而不自己创建它,请直接到第24步。

  1. 新建项目对话框创建一个新的Visual C# “类库”项目。

  2. 将项目命名为“Commands”,并选择保存位置存贮之。

  3. 单击项目菜单并选择“添加引用(R)…”。

  4. 在添加引用对话框中,复选“ESRI.ArcGIS.Carto”,“ESRI.ArcGIS.Display”,“ESRI.ArcGIS.Geometry”,“ESRI.ArcGIS.System”,“ESRI.ArcGIS.SystemUI”,“ESRI.ArcGIS..Utility”和“ESRI.ArcGIS.ControlCommands”。

  5. 在项目中增加一个类,名字叫AddDateTool。

  6. 点击项目菜单并选择添加现有项,浏览样例源码目录并找到date.bmp文件将其加入到你的项目。

  7. 解决方案资源管理器中点击date.bmp在属性窗口显示其属性。改变生成操作属性为嵌入的资源。这张位图将被用来作为命令按钮的外观。

  8. 改变AddDateTool的命名空间的名称为CSharpDotNETCommands。

namespace CSharpDotNETCommands

{

……

注:要在Visual Basi .NET中改变命名空间的名称,则在解决方案资源管理器的项目上点击右键并选择属性,在项目属性页中选择常规并改变根命名空间后,按确定

  1. 在AddDateTool类代码窗口的顶部增加以下引用。

using System;

using ESRI.ArcGIS.Carto;

using ESRI.ArcGIS.Display;

using ESRI.ArcGIS.Geometry;

using ESRI.ArcGIS.SystemUI;

using ESRI.ArcGIS.esriSystem;

using ESRI.ArcGIS.ControlCommands;

using ESRI.ArcGIS.Utility.BaseClasses;

using System.Runtime.InteropServices;

  1. 指定AddDateTool类继承自ESRI BaseTool抽象类,并增加密封(sealed)类修饰。

public sealed class AddDateTool : BaseTool

{

……

注:抽象类是不能被实例化的类,通常仅包含部分实现代码,或者不包含任何实现代码。它们与接口密切相关;但与接口有明显的区别,也就是说,一个类可能实现任意数量的接口,但它仅能够从一个抽象类中继承。继承了ESRI BaseTool__抽象类,你便可以比直接实现esriSystemUI ICommand__和ITool__接口更快速、简便地创建命令和工具。

密封类修饰说明一个类不能被继承。此类的设计是为了限制其他类从该类继承。

  1. 向AddDateTool类的构造函数中增加下列代码:

    public sealed class AddDateTool : BaseTool

    {

        public AddDateTool()

        {

            // 获取程序集中的资源数组

            string[] res = GetType().Assembly.GetManifestResourceNames();

            // 设置工具属性

            base.m_bitmap = new System.Drawing.Bitmap(

                GetType().Assembly.GetManifestResourceStream(res[0]));

            base.m_caption = “添加日期”;

            base.m_category = “CustomCommands”;

            base.m_message = “在页面布局中增加一个日期元素”;

            base.m_name = “CustomCommands_Add Date”;

            base.m_toolTip = “添加日期”;

        }

    }

注:类构造函数是一个当类创建时被调用的方法。它可以用来初始化类成员变量。构造函数名与类名相同;__与其他方法不同的是它没有返回类型__。

程序中只个别地替换实现了位图、标题、目录、名称、消息和提示方法,你可以设置从这此方法返回的值,且依赖于BaseTool类为这此方法提供的实现。其它的成员保留BaseTool类返回的默认值。

  1. 向AddDateTool类增加下列成员变量。

public sealed class AddDateTool : BaseTool

{

    // HookHelper对象处理通过OnCreate事件的回调

    private IHookHelper m_HookHelper = new HookHelperClass();

……

  1. 类视图窗口中,定位到BaseCommand类的OnCreate方法,右键点击之显示上下文菜单。选择增加,然后重载并增加该方法至代码窗口。

  2. 在重载的OnCreate方法中增加以下代码。

     public override void OnCreate(object hook)

     {

         m_HookHelper.Hook = hook;

     }

注:要在Visual Basic .NET中重载属性和方法,从代码窗口顶部的“Class Name”组合框中选择“Overrides”,从“Method Name”组合框中选择属性或方法。

  1. 在类视图中定位到BaseCommand类的Enabled属性并在其上点击右键显示上下文菜单。选择添加,然后点重写增加该属性至代码窗口。

  2. 增加以下代码,重写BaseTool类的默认Enabled值。

     public override bool Enabled

     {

         get

         {

             // 设置使能属性

             if ( m_HookHelper.ActiveView != null )

             {

                 return true;

             }

             else

             {

                 return false;

             }

         }

     }

注:ICommand_OnCreate事件向命令工作的应用程序传送一个句柄或回调。在这种情况下,它可以是MapControl,PageLayoutControl或ToolbarControl。除向OnCreate事件增加代码外,你可以使用HookHelper判断传向命令的回调类型。命令或工具需要知道如何处理传送的回调,所以必须对ArcGIS Control传送的类型作检查。HookHelper用来控件回调并返回ActiveView忽略的回调类型(MapControl、PageLayoutControl和ToolbarControl都是这样)。

  1. 在类视图中定位到BaseTool基类的OnMouseDown方法,并在其上点击右键显示上下文菜单。选择添加,然后重载并增加该属性至代码窗口。

  2. 增加下列代码,重载BaseTool类实现的默认OnMouseDown函数。

     public override void OnMouseDown(int Button, int Shift, int X, int Y)

     {

         // TODO:  添加 AddDateTool.OnMouseDown 实现

         base.OnMouseDown (Button, Shift, X, Y);

         // 获取活动视图

         IActiveView activeView = m_HookHelper.ActiveView;

         // 创建新的文本元素

         ITextElement textElement = new TextElementClass();

         // 创建文本符号

         ITextSymbol textSymbol = new TextSymbolClass();

         textSymbol.Size = 25;

         // 设置文本元素属性

         textElement.Symbol = textSymbol;

         textElement.Text = DateTime.Now.ToShortDateString();

         // 对IElementQI

         IElement element = (IElement) textElement;

         // 创建页点

         IPoint point = new PointClass();

         point = activeView.ScreenDisplay.DisplayTransformation.ToMapPoint(X, Y);

         // 设置元素图形

         element.Geometry = point;

         // 增加元素到图形绘制容器

         activeView.GraphicsContainer.AddElement(element, 0);

         // 刷新图形

         activeView.PartialRefresh(esriViewDrawPhase.esriViewGraphics,

             null, null);

     }

19.     ArcGIS Engine期望自定义命令是一个COM类;因此,你必须指定你所创建的.NET类也成为一个COM类,它是通过创建一个COM可调用包装(callable wrapper)实现的。在解决方案资源管理器窗口中,在Commands项目上右击鼠标键并从上下文菜单中选择属性

  1. 在项目属性页对话框中选择配置属性;并点击生成。在右面的面板中,改变为“为Com Interop注册”为True,点确定

注:设置“为Com Interop__注册”属性为True__会调用程序集注册工具(Regasm.exe__)。这将增加客户端期望找到的类信息。

_如果“为Com Interop__注册”属性设为False__,则使项目不要是一个C#_类库类型。

  1. 在AddDateTool类的代码编写窗口的AddDateTool类声明的开始位置增加下列代码,指定COM需要的属性。

    [ClassInterface(ClassInterfaceType.None)]

    [Guid(“D880184E-AC81-47E5-B363 -781F4DC4528F”)]

    注:新的__GUID__可能通过__Visual Studio .NET__中的__GuidGen.exe__实用工具生成,或者从工具菜单中选择创建GUID。__GUID__应该像上面的格式并不包含大括号_(curly braces)_

  1. 向AddDateTool类成员变量的后面增加下列代码。此代码定义了一些函数,这些函数使用目录实用工具向ESRI控件命令(ESRI Control Commands)组件目录注册和取消注册AddDateTool类。

     // 在“ESRI Controls Commands”组件目录注册

     #region Component Category Registration

     [ComRegisterFunction()]

     [ComVisible(false)]

     static void RegisterFunction(String sKey)

     {

         string fullKey = sKey.Remove(0, 18) + @”/nImplemented Categories”;

         Microsoft.Win32.RegistryKey regKey =

             Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(fullKey, true);

         if (regKey != null)

         {

             regKey.CreateSubKey(“{B284D891-22EE-4F12-A0A9-B1DDED9197F4}”);

         }

     }

     [ComUnregisterFunction()]

     [ComVisible(false)]

     static void UnregisterFunction(String sKey)

     {

         string fullKey = sKey.Remove(0, 18) + @”/Implemented Categories”;

         Microsoft.Win32.RegistryKey regKey =

             Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(fullKey, true);

         if (regKey != null)

         {

             regKey.DeleteSubKey(“{B284D891-22EE-4F12-A0A9-B1DDED9197F4}”);

         }

     }

     #endregion

  1. 生成工程。

  2. 在方案开始创建的Visual Studio .NET “Windows应用程序”项目中,增加地图导航命令代码的后面增加以下代码。

     private void Form1_Load(object sender, System.EventArgs e)

     {

         // 前面是命令导航代码……          

         // 添加自定义日期工具

         progID = “CSharpDotNETCommands.AddDateTool”;

         axToolbarControl1.AddItem(progID, -1, -1, true, 0,

             esriCommandStyles.esriCommandStyleIconAndText);

// 后面是ToolbarMenu相关代码……

}

  1. 生成并运行应用程序,使用添加日期工具向PageLayoutControl上增加一个包含当天日期的文本元素。

(十一) 自定义ToolbarControl

同在Form_Load事件中向ToolbarControl控件增加ArcGIS Engine命令和工具一样,你也可以使用定制对话框和自定义ToolbarControl的方式添加命令和工具。要实现它,就要将ToolbarControl置为定制模式并显示定制对话框。

  1. 向类中增加下列成员变量:

……

     private ITransformEvents_VisibleBoundsUpdatedEventHandler

         visBoundsUpdatedE;          // PageLayoutControl的焦点图事件

     private ICustomizeDialog m_CustomizeDialog = new

         CustomizeDialogClass(); // CurtomizeDialog被ToolbarControl使用

     private ICustomizeDialogEvents_OnStartDialogEventHandler

         startDialogE; // CustomizeDialog启动事件

     private ICustomizeDialogEvents_OnCloseDialogEventHandler

         closeDialogE; // CustomizeDialog关闭事件

……

注:Visual Studio .NET__提供了当程序集对COM interop__开放时执行的函数在系统中被注册和取消注册的功能。这就允许你在定制对话框可能找到的组件目录中注册你自己的类。

  1. 创建一个叫CreateCustomizeDialog的新函数,这个函数是你通过增加如下代码创建自定义对话框的地方。

     private void CreateCustomizeDialog()

     {

         // 设置自定义对话框事件

         startDialogE = new

             ICustomizeDialogEvents_OnStartDialogEventHandler(OnStartDialog);

         ((ICustomizeDialogEvents_Event)m_CustomizeDialog).OnStartDialog +=

             startDialogE;

         closeDialogE = new

             ICustomizeDialogEvents_OnCloseDialogEventHandler(OnCloseDialog);

         ((ICustomizeDialogEvents_Event)m_CustomizeDialog).OnCloseDialog +=

             closeDialogE;

         // 设置标题

         m_CustomizeDialog.DialogTitle = “自定义ToolbarControl项目”;

         // 显示“从文件添加”按钮

         m_CustomizeDialog.ShowAddFromFile = true;

         // 设置将增加新项目的ToolbarControl

         m_CustomizeDialog.SetDoubleClickDestination(axToolbarControl1);

}

注:设置ComVisible属性为false确保此方法不能被COM客户端直接调用。当程序集通过COM注册时,它不影响被调用的方法。

  1. 在Form_Load事件中调用CreateOverviewSymbol子过程以前调用CreateCustomizeDialog函数。

     private void Form1_Load(object sender, System.EventArgs e)

     {

         // 当缩放时禁止重绘

         this.SetStyle(ControlStyles.EnableNotifyMessage, true);

         // 为ToolbarControl创建自定义对话框

CreateCustomizeDialog();

……

}

  1. 在窗体上增加一个名叫“chkCustomize”的复选框,并将标题命名为“定制”。

  2. 在设计模式显示窗体并从属性窗口选择chkCustomize控件,显示chkCustomize事件。在CheckedChanged事件上双击向代码窗口增加相应的事件处理。

  3. 向chkCustomize_CheckedChanged事件中增加下列代码。

     private void chkCustomize_CheckedChanged(object sender, System.EventArgs e)

     {

         // 显示或隐藏自定义对话框

         if (chkCustomize.Checked == false )

         {

             m_CustomizeDialog.CloseDialog();

             axToolbarControl1.Customize = false;

         }

         else

         {

             m_CustomizeDialog.StartDialog(axToolbarControl1.hWnd);

             axToolbarControl1.Customize = true;

         }

}

  1. 增加下以下OnStartDialog和OnCloseDialog事件处理函数。这些函数将与自定义对话框打开或关闭时触发的事件紧密连接。

     private void OnStartDialog()

     {

            axToolbarControl1.Customize = true;     

     }

     private void OnCloseDialog()

     {

         axToolbarControl1.Customize = false;

         chkCustomize.Checked = false;

}

  1. 生成并运行应用程序,选择定制复选框使ToolbarControl进入自定义模式,并打开自定义对话框。

  2. 在自定义ToolbarControl项目对话框中的左边目录(Categories)列表中选择“Graphic Element”项,然后在右边的命令(Commands)列表中“Select Elements”项上双击将其加入到ToolbarControl工具栏中。右键点击ToolbarControl上的任何一个项目,你可以调整它的显示样式和组合特性。

  3. 结束定制应用。使用选择工具移动包含日期的文本元素。

六、部署

要将应用程序成功地部署到另一台机器上,必须为应用程序配置协议。首先,它必须检查产品协议是否有效,其次,它必须初始化协议。如果协议配置不正确有,应用程序将不能运行。

注:当采用ESRI ArcObjects__开发独立运行的程序时,应用程序负责检查并配置协议选项。它通过实现CoClass AoInitialize__和IAoInitialize__接口来支持协议配置。应用程序运行时,在任何ESRI ArcObject__功能被访问之前协议初始化必须先被执行。如果初始化失败将导致应用程序错误。

  1. 向类中增加下列成员变量。

public class Form1 : System.Windows.Forms.Form

    {

        private ESRI.ArcGIS.MapControl.AxMapControl axMapControl1;

        private ESRI.ArcGIS.PageLayoutControl.AxPageLayoutControl axPageLayoutControl1;

        private ESRI.ArcGIS.TOCControl.AxTOCControl axTOCControl1;

        private ESRI.ArcGIS.ToolbarControl.AxToolbarControl axToolbarControl1;

        // 应用初始化对象

        private IAoInitialize m_AoInitialize = new AoInitializeClass();

// 后面是弹出菜单变量声明代码

……

  1. 在Form_Load事件的最开始位置增加下列代码。

     private void Form1_Load(object sender, System.EventArgs e)

     {

         // 创建新的AoInitialize对象

         if ( m_AoInitialize == null)

         {

             System.Windows.Forms.MessageBox.Show(

                 “初始化失败,程序不能运行!”);

             this.Close();

         }

         // 判断产品是否有效

         esriLicenseStatus licenseStatus = (esriLicenseStatus)

             m_AoInitialize.IsProductCodeAvailable(

             esriLicenseProductCode.esriLicenseProductCodeEngine);

         if (licenseStatus == esriLicenseStatus.esriLicenseAvailable )

         {

             licenseStatus = (esriLicenseStatus)

                 m_AoInitialize.Initialize(esriLicenseProductCode.esriLicenseProductCodeEngine);

             if (licenseStatus != esriLicenseStatus.esriLicenseCheckedOut )

             {

                 System.Windows.Forms.MessageBox.Show(

                     “初始化失败,应用程序不能运行!”);

                 this.Close();

             }

         }

         else

         {

             System.Windows.Forms.MessageBox.Show(

                 “ArcGIS Engine产品无效,此程序不能运行!”);

             this.Close();

         }

         // 当缩放时禁止重绘

this.SetStyle(ControlStyles.EnableNotifyMessage, true);

// 后面是创建自定义对话框的代码……

……

}

  1. 在设计模式显示窗体并在属性窗口选择Form1,显示窗体事件。在Closing事件上双击向代码窗口增加事件处理代码。

  2. 在Form_Closing事件中增加以下代码:

     private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e)

     {

         // 释放COM对象并关闭AoInitialize对象

         ESRI.ArcGIS.Utility.COMSupport.AOUninitialize.Shutdown();

         m_AoInitialize.Shutdown();

}

  1. 在Release模式下生成项目和解决方案。

要将应用程序成功地部署到用户机器上:

l 要将应用程序的可执行文件和包含自定义命令的动态链接库DLL发布到用户机器上。程序集注册工具(RegAsm.exe)必须被用来向注册表增加关于自定义类的信息。

l 用户机器上需要安装有ArcGIS Engine运行时库和标准ArcGIS Engine协议。

l 客户机上需要安装Microsoft .NET Framework 1.1。

七、附加资源

下列资源可以帮助你理解和应用在本方案中在在的概念和技术。

l 在ArcGIS Engine开发工具包中包含了其他可用的文档:ArcGIS开发帮助,组件帮助,对象模型图表和适合于初学者的样例程序。

l ArcGIS开发在线——一个Web站点,提供了最新的ArcGIS开发信息,包括程序样例和技术文档。请访问http://arcgisdeveloperonline.esri.com/

l ESRI在线讨论组——Web站点,从其他ArcGIS开发者提供无偿援助。请访问http://support.esri.com/并点击用户论坛页签。

l 微软Visual Studio .NET开发环境中的文档。

暗诡刺 于 2013-09-22 15:07:07 发布 13170 收藏 6

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

定义一个类

1
class Class1    {        public const int port = 11000;        public void StarListener()        {            UdpClient udpclient = new UdpClient(port);            IPEndPoint ipendpoint = new IPEndPoint(IPAddress.Any, port);            try            {                while (true)                {                    byte[] bytes = udpclient.Receive(ref ipendpoint);                    string strIP = "信息来自"+ ipendpoint.Address.ToString();                    string strInfo = Encoding.GetEncoding("gb2312").GetString(bytes, 0, bytes.Length);                    MessageBox.Show(strInfo, strIP);                }            }            catch (Exception e)            {                MessageBox.Show(e.ToString());            }            finally            {                socket.Close();            }        }        public string Send(string strServer, string strContent)        {            Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);            IPAddress ipaddress = IPAddress.Parse(strServer);            byte[] btContent = Encoding.GetEncoding("gb2312").GetBytes(strContent);            IPEndPoint ipendpoint = new IPEndPoint(ipaddress, port);            socket.SendTo(btContent, ipendpoint);            socket.Close();            return "发送成功!";        }

服务端监听

1
Class1 class1 = new Class1();            class1.StarListener();

客户端发送

1
//textBox1是IP地址栏,textBox2是要发送的信息,button1发送按钮        Class1 class1 = new Class1();        System.Diagnostics.Process myProcess;        private void Form2_Load(object sender, EventArgs e)        {            myProcess = System.Diagnostics.Process.Start("Server.exe");//开启服务            richTextBox1.Text = string.Empty;            richTextBox1.Focus();        }        private void button1_Click(object sender, EventArgs e)//发送按钮        {            MessageBox.Show(class1.Send(textBox1.Text, richTextBox1.Text));        }        private void textBox1_KeyPress(object sender, KeyPressEventArgs e)        {            if (e.KeyChar == 13)//按下Enter的时候                richTextBox1.Focus();        }        private void richTextBox1_KeyPress(object sender, KeyPressEventArgs e)        {            if (e.KeyChar == 13)//按下Enter的时候                button1.Focus();        }        private void Form2_FormClosing(object sender, FormClosingEventArgs e)        {            myProcess.Kill();//关闭服务        }

这样就可以了,先启用服务端代码,然后在客户端发送,在服务端就可以接收到发送的信息。