一步一步在 Windows 10 用 visual studio 2019 编译 zmqpp 4.2.0 版

伸手党可以到 zmqpp库windows编译结果-C++文档类资源-优快云下载 去下载

zmqpp 是 libzmq 的高级 C++ 封装,不但提供了针对 libzmq C 接口的 C++ 封装,还提供了一些附加功能(Reactor模式, Actor模式 和 ZAP支持),详细见 zmqpp 文档 zmqpp: Main Page。在 ubuntu linux发行版上,可以直接用 apt-get 来安装使用,非常方便,但是在 windows 上如何编译使用却资料甚少。因为没有找到 zmqpp 的 windows 预编译发行版,所以只能自己用源码编译。这篇文章将一步一步实现在 windows 10 操作系统下用 visual studio 2019 来编译 zmqpp。

1. 下载 cmake windows 版

我们需要用到 cmake 来进行编译,Download | CMake 处可以找到最新的发行版(Latest Release),目前的最新发行版是 3.23.2,下载 Windows x64 Installer 文件 cmake-3.23.2-windows-x86_64.msi 并且执行安装。

2. 下载 libzmq 的 windows 预编译版

到地址 Releases · zeromq/libzmq · GitHub 下载 libzmq-v142-x64-4_3_4.zip 文件并且解压缩到自己喜欢的地方。

还有为其它版本 visual studio 准备的预编译版,命名中 libzmq-vXXX-x64-4_3_4.zip 中分别如下对应:

visual studio 2019 对应 v142

visual studio 2017 对应 v141

visual studio 2015 对应 v140

3. 下载 zmqpp 源码

Releases · zeromq/zmqpp · GitHub 链接内下载最新的发布版本,目前是 4.2.0 版,Source code (zip),下载后解压缩到自己喜欢的地方,我放在 D:\zmqpp-4.2.0 。

4. 用 cmake 生成 visual studio 解决方案

以 zmqpp 源码位置在 D:\zmqpp-4.2.0 为例,创建目录 D:\zmqpp-4.2.0\bin\v142\x64 作为生成目标路径。

开始菜单中去启动 CMake(cmake-gui),如图填写源码路径和生成目标路径,点击 configure

在下图弹出的窗口中指定生成器版本为 Visual Studio 16 2019,生成平台为 x64,点击 Finish。

这时,可以查看到 cmake 的配置结果,黄色框中显示 NOTFOUND 的 3 个东西需要手工指定到之前第 2 步下载的 libzmq windows 预编译版的位置:

手工指定 libzmq windows 预编译版的位置之后的截图,注意黄框内容:

我想将 zmqpp 的例子和测试都编译出来,所以将 ZMQPP_BUILD_CLIENT, ZMQPP_BUILD_EXAMPLES, ZMQPP_BUILD_TESTS 也都打上了勾,如果不打这些勾,等解决方案生成之后,在 visual studio 中是看不到 client、 examples、 tests 这 3 个项目的

手工指定之后需要再次点击 Configure 按钮来更新配置,更新配置之后出现报错,如图:

 报错原因是因为我勾选了编译样例,而编译样例需要 Boost 库,需要下载安装 Boost 库。

5. 下载安装 Boost 库

下载地址 Boost C++ Libraries - Browse /boost-binaries at SourceForge.net ,当前最新版是 1.79.0,进到 1.79.0 目录,同样也有针对不同 visual studio 版本的对应版本,我们需要的是 msvc-14.2 版。

visual studio 2019 对应 msvc-14.2

visual studio 2017 对应 msvc-14.1

visual studio 2015 对应 msvc-14.0

下载 boost_1_79_0-msvc-14.2-64.exe 并且运行安装到自己指定的目录中去,我是安装在 D:\local\boost_1_79_0 目录。

6. 继续 cmake 项目配置,指定 boost 目录

指定 Boost_INCLUDE_DIR 路径到 D:\local\boost_1_79_0 再点击 Configure, 如图:

最终的输出结果如下图,红字部分的告警可以忽略,点击 Generate 以生成 visual studio 2019 的解决方案 sln 文件

点击 Generate 之后,在我们指定的生成目标路径 D:\zmqpp-4.2.0\bin\v142\x64 中,会生成一个 visual studio 的解决方案文件 Project.sln

7. 打开解决方案,生成 zmqpp 项目

直接在生成目标路径 D:\zmqpp-4.2.0\bin\v142\x64 中打开 Project.sln,或者在 CMake 窗口中点击 Open Project 按钮,都可以打开 visual studio 2019。

 我们先试一下生成 zmqpp 库,右击 zmqpp 选取生成选项。

生成的结果是 成功 1 个,失败 1 个。上图红框内为失败原因,双击红框中两行的任何一行,打开源码 compatibility.hpp,其中第 132 行有错,原因是 SOCKET 没有定义,需要包含 winsock2.h 头文件。

 在这个文件的第 37 行插入一行 #include <winsock2.h> 保存后再次生成

 再次生成 zmqpp 项目仍旧有错,这次是下图这里的错误

 双击这 3 行中的任一行,打开源码 socket.cpp 中 344 行,显示 std::min 有问题。查资料得,因为 windows.h 包含的 windef.h 中有宏定义 min 和 max,导致此处 min 被展开宏。

 查到一个简单的方法,使用项目范围的 /D "NOMINMAX" 选项就可以避免这个干扰。

右击打开 zmqpp 项目属性,在配置属性>C/C++>命令行页面中的其它选项内,加上  /D "NOMINMAX", 如下图:

再次生成 zmqpp 项目,还是出现了 1 个失败,这次的失败是因为缺少了一个头文件。

 提示缺少了 zmq_utils.h 头文件,这个头文件应该是 libzmq 库的,我们下载的预编译版中没有把这个文件放进去。去下面这个地址下载到 zmq_utils.h 文件,并且放到之前的 libzmq 目录中。https://github.com/zeromq/libzmq/blob/v4.3.4/include/zmq_utils.hhttps://github.com/zeromq/libzmq/blob/v4.3.4/include/zmq_utils.h

 再次生成 zmqpp 项目,编译成功,已经生成 zmqpp 库的 dll 文件和 lib 文件。如下图所示:

8. 生成 zmqpp-client

这是基于 libzmqpp 编写的命令行客户端程序,可以用来测试或者桥接 zmq 连接。具体用法请见 https://github.com/zeromq/zmqpp/blob/develop/README.md#usage

右击 zmqpp-client 选取生成菜单。此项目顺利生成。

 打开 cmd 窗口运行 zmqpp-client 报错找不到 libzmq-v142-mt-4_3_4.dll 文件

到第 2 步下载的 libzmq 的 windows 预编译版目录中将 libzmq-v142-mt-4_3_4.dll 文件复制到 zmqpp-client.exe 相同目录内,再次运行 zmqpp-client,又报错找不到 libsodium.dll 文件

仍旧是到第 2 步下载的 libzmq 的 windows 预编译版目录中将 libsodium.dll 文件复制到 zmqpp-client.exe 相同目录内

 再次运行 zmqpp-client,又报错,这次是 “zmqpp-client.exe” 已停止工作 错误。再查资料!stack over flow on windows x64 · Issue #2876 · zeromq/libzmq · GitHub 这里有个人报告了类似问题,虽然不是 zmqpp-client 的问题,而是 libzmq 中 inproc_thr.exe 的问题,我感觉应该是相同的问题,按照下面这个答案所说,在 libzmq/INSTALL 说明文件中已经提醒,要将栈的大小从默认的 1MB 增加到 2MB。

我们干脆增加到 40MB,右击 zmqpp-client 项目,找到 链接器 > 系统 > 堆栈保留大小,设置这个项为 41943040,再重新生成 zmqpp-client 项目

 再次运行 zmqpp-client ,发现它终于能够正常运行了!

9. 编译 example

一共有 7 个例子,我们逐个来编译

9.1 编译 zmqpp-example-grasslands

编译这个例子没有遇到问题,直接成功了,真是难得!

 运行也是正常。

9.2 编译 zmqpp-example-ironhouse

这个例子编译失败了,报了一堆 LNK2019 错误:

应该是 zmqpp 库的代码中有关 auth 部分的符号都没有导出,需要修改源代码。因为这个例子是涉及消息加密的,可能用的人比较少,开源社区里没有人提这个问题,也没有人修复。

下载依赖分析工具 Dependency Walker (depends.exe) Home Page 对我们在第 7 步生成 zmqpp 时产生的 zmqpp.dll 文件进行分析,如下图:

并没有找到 7 个 LNK2019 错误所指的符号,zmqpp::auth::auth、zmqpp::auth::~auth、zmqpp::auth::allow、zmqpp::auth::configure_domain、zmqpp::auth::configure_curve、zmqpp::auth::set_verbose、zmqpp::curve::generate_keypair 等,这个证实了之前猜想,这些符号并没有导出到 zmqpp.dll。所以问题出在 zmqpp 项目中,需要找个办法使 zmqpp 库编译时将 auth 类的相关函数导出到 zmqpp.dll 中。

打开 zmqpp 项目的 auth.hpp 文件,如下图在类定义中添加 ZMQPP_EXPORT 宏

打开 curve.hpp, 在 keypair generate_keypair() 前加上 ZMQPP_EXPORT 宏

再打开 curve.cpp (注意这次是cpp文件不是hpp)文件,在 keypair 前添加 ZMQPP_EXPORT 宏

重新编译 zmqpp 项目,然后再重新编译 zmqpp-example-ironhouse 项目,这次编译成功了。

 运行 zmqpp-example-ironhouse 发现退出程序时报错 Assertion failed: Successful WSASTARTUP not yet performed [10093] (C:\projects\libzmq\src\signaler.cpp:190) 如下:

这个是 windows 操作系统底层的问题,只影响程序退出部分的功能,先不管它。

9.3 编译 zmqpp-example-ironhouse2

经过 9.2 节的修改之后,此例子程序可以顺利编译成功。也可以运行,只是退出时和 9.2 一样,也会有报错 Assertion failed: Successful WSASTARTUP not yet performed [10093] (C:\projects\libzmq\src\signaler.cpp:190)  

9.4 生成 zmqpp-example-simple_client 和 zmqpp-example-simple_server

这两个例子都可以顺利生成。这两个程序是一对,client 发消息,server 收消息的。运行结果如下:

 和通常理解的客户服务器程序不同,zmq的客户端和服务器程序可以按任意顺序运行,而不是非得要先启动服务器再启动客户端的。

9.5 生成 zmqpp-example-strawhouse 和 zmqpp-example-woodhouse

情况与 9.3 和 9.2 相同,正常生成,可以运行,但程序退出时有报错。

9.6 试图解决问题:Assertion failed: Successful WSASTARTUP not yet performed [10093]

根据资料 c++ - ZeroMQ context singleton, provided in a DLL, crashes when program exits (VS2010 win7 x64 zmq 4.0x) - Stack Overflow

所说,这是个 windows 特定问题,解决办法就是一定要在程序开始时调用一次 WSAStartup(),结束前调用一次 WSAShutdown()。

没有找到特定于 zmqpp 库的资料,实际上这个问题源头是 libzmq,根据 [4.1.3] Assertion failed: Successful WSASTARTUP not yet performed (..\..\..\..\src\signaler.cpp:181) · Issue #1708 · zeromq/libzmq · GitHub 的说法,如果是在 DLL 内创建 context(现在遇到的情况恰好就是 zmqpp 在 zmqpp.dll 中调用 zmq_ctx_new() 创建了 zmq::ctx_t),libzmq 程序在 windows 上退出时就会报这个错误,而在 main 中创建再传递给程序其它部分就不会报这个错误,Assertion failed: Successful WSASTARTUP not yet performed (..\..\..\..\src\signaler.cpp:192) · Issue #1788 · zeromq/czmq · GitHub 内 czmq 用户解释的原因是

It's a Windows problem in CZMQ, and it's because Windows lacks a usable way to do the equivalent of atexit() when built as DLL.

You need to manually call zsys_shutdown() before your Windows program exits.

但我们用的不是 czmq,没有 zsys_shutdown() 可以用。

9.6.1 shared_ptr 和 全局变量(不行)

因为这个问题是销毁变量和对象的问题,我们的程序加载了zmqpp.dll,它又加载了 libzmq 的 dll,libzmq 的 dll 进而加载了 winsock.dll。当程序退出时,winsock 先销毁了,而 libzmq 的析构函数中还调用了 winsock 中的变量和函数,这样就出错了。

试图通过使用 shared_ptr,然后在退出之前对这个 shared_ptr 进行 reset() 来显式的销毁对象。经过测试,在本例中这些方法无效。

试图使用全局变量 zmqpp::context* context 并且用 new 的方式创建 context,而且在程序结束时不去 delete,或者在程序退出前进行 delete,都不能消除错误。如果提早 delete,程序会阻塞住失去响应而不能退出。

这个方法行不通。

9.6.2 添加 zmqpp 的 context 构造函数(不行)

想办法不在 zmqpp.dll 中创建 libzmq 的 context,而是直接在程序中用 zmq_ctx_new() 创建。

去掉 zmqpp 中 context 构造函数的 zmq_ctx_new(),而是直接用参数将在外部创建好的 context 传递进来。

context.hpp 中添加一个构造函数为:

context(void* ctx) : _context(ctx){
	if (nullptr == _context)
	{
	    throw zmq_internal_exception();
	}
}

 ironhouse.cpp 中的 main() 函数修改为:

int main(int argc, char* argv[]) {
	// 在外部初始化的 libzmq 的 context
	void* ctx;
#if (ZMQ_VERSION_MAJOR < 3) || ((ZMQ_VERSION_MAJOR == 3) && (ZMQ_VERSION_MINOR < 2))
	ctx = zmq_init(threads);
#else
	ctx = zmq_ctx_new();
#endif

	// initialize the 0MQ context
	zmqpp::context context(ctx);

	// Start an authentication engine for this context. This engine
	// allows or denies incoming connections (talking to the libzmq
	// core over a protocol called ZAP).
	zmqpp::auth authenticator(context);
.
.
.

现象依旧,没有解决问题。 

 9.6.3 思路的转变

因为 zmqpp-example-grasslands 也是直接用 zmqpp::context context 创建的 context,但是它并不在退出时出现 Assertion failed 报错,所以前面的尝试应该是走错方向了。

在 zmqpp 的 context.hpp 中第 100 行的 ~context() 函数中的 terminate() 打上断点,启动调试。发现这个断点进入了 2 次!第 1 次是正常完成执行的,第 2 次才在 context.cpp 的第 30 行 zmq_ctx_destroy(_context) 出现了未经处理的异常。就是这里,我一直以为是 ironhouse.cpp 等程序的 zmqpp::context context; 这个变量析构时出现的异常,其实,第 1 次正常执行的应该是这个对象的析构,没有问题。那么第 2 次析构的 context 对象是哪里创建的呢?这需要从能正常退出的 zmqpp-example-grasslands 和那些不能正常退出的样例程序的对比中去找答案。

相比正常的 zmqpp-example-grasslands,那些不能正常退出的样例都用到了 zmqpp::auth 对象。通过查看源码发现,zmqpp::auth 对象构建时会创建 zmqpp::actor 对象,而 zmqpp::actor 对象会以全局变量的形式创建一个 zmqpp::context 对象。我推测,应该是这个全局变量形式的 context 对象在程序退出的时候晚于 winsock.dll 卸载再析构才造成异常的。

auth.cpp 第 71 行创建了一个 zmqpp::actor 对象:

authenticator = std::make_shared<actor>(
    std::bind(
        zap_auth_server, std::placeholders::_1, std::ref(ctx)
    )
);

在 actor.hpp 第 156 行,定义了一个静态的 zmqpp::context 对象:

    /**
     * This static, per process zmqpp::context, is used to connect PAIR socket
     * between Actor and their parent thread.
     */
    static context actor_pipe_ctx_;

在 actor.cpp 第 27 行:

zmqpp::context zmqpp::actor::actor_pipe_ctx_;

现在推测第 2 次的断点析构的就是这个 actor_pipe_ctx_。

尝试将这个全局变量形式的 actor_pipe_ctx_ 改成 shared_ptr,这样就有可能在所有引用它的对象析构之后,它会早于 winsock.dll 卸载析构。

修改 actor.hpp 第 156 行,使它变为:

std::shared_ptr<context> actor_pipe_ctx_;

去除 actor.cpp 第 27 行的声明,在 actor 构造函数中新增一行来创建 zmqpp::context 对象,并且在创建 socket 对象时传入 *actor_pipe_ctx_,actor.cpp 前几行如下:

/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file is part of zmqpp.
 * Copyright (c) 2011-2015 Contributors as noted in the AUTHORS file.
 */

/*
 * File:   actor.cpp
 * Author: xaqq
 *
 * Created on May 20, 2014, 10:51 PM
 */

#include <cassert>
#include <cstdlib>
#include <iostream>

#include "actor.hpp"
#include "socket.hpp"
#include "message.hpp"
#include "exception.hpp"
#include "context.hpp"

// zmqpp::context zmqpp::actor::actor_pipe_ctx_;

namespace zmqpp
{

  actor::actor(ActorStartRoutine routine)
      : parent_pipe_(nullptr)
      , child_pipe_(nullptr)
      , stopped_(false)
  {
    std::string pipe_endpoint;

    actor_pipe_ctx_ = std::make_shared<context>();

    parent_pipe_  = new socket(*actor_pipe_ctx_, socket_type::pair);
    pipe_endpoint = bind_parent();

    child_pipe_ = new socket(*actor_pipe_ctx_, socket_type::pair);
    child_pipe_->connect(pipe_endpoint);
.
.
.

 重新编译 zmqpp 项目和 zmqpp-example-ironhouse,这次问题解决了!!!

 

10 结果

至此,已经得到 zmqpp.dll 和一些例子的编译结果,生成目录内容如下:

所有结果文件,包括 Release 版的,都已经打包在 zmqpp库windows编译结果-C++文档类资源-优快云下载

其中的 zmqpp-static 库是没有修改过的,应该是不包含 auth 那几个符号的。

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值