在需要使用FIX protocol進行通訊的軟體開發時,除了自己由無到有地依FIX通訊格式開發自行的套件外,如要快速能有一個成品,多數人應該會選擇使用quickfix這個open source函式庫。
近期因為開始在Raspberry Pi上玩container,所以就想說用VSCode在本機透過ssh連上container的ubuntu來編譯quickfix看看。
由source code開始編譯quickfix
quickfix source code的來源可以到官網的GitHub中取得,並確認你的開發環境有gcc / openssl / cmake等工具。接著就可以開始用以下的指令來進行編譯。
cd <workspace>
git clone https://github.com/quickfix/quickfix.git
cd quickfix
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHAVE_SSL=ON -DCMAKE_INSTALL_PREFIX:PATH="/usr/local" ..
make -j 4 install
沒什麼問題下,基本上這樣就可以正常編譯quickfix套件,並安裝到你指定的目錄中。
在ARM64中出現不支援assembly code的問題
但是沒想到在ARM64的環境要編譯quickfix source code,竟然出現以下的錯誤:
/tmp/ccBckn7v.s: Assembler messages:
/tmp/ccBckn7v.s:10663: Error: unknown mnemonic `lock' -- `lock'
/tmp/ccBckn7v.s:10664: Error: unknown mnemonic `xadd' -- `xadd x1,[x2]'
/tmp/ccBckn7v.s:10945: Error: unknown mnemonic `lock' -- `lock'
/tmp/ccBckn7v.s:10946: Error: unknown mnemonic `xadd' -- `xadd x0,[x1]'
/tmp/ccBckn7v.s:16105: Error: unknown mnemonic `lock' -- `lock'
/tmp/ccBckn7v.s:16106: Error: unknown mnemonic `xadd' -- `xadd x1,[x2]'
/tmp/ccBckn7v.s:28896: Error: unknown mnemonic `lock' -- `lock'
/tmp/ccBckn7v.s:28897: Error: unknown mnemonic `xadd' -- `xadd x0,[x1]'
/tmp/ccBckn7v.s:29698: Error: unknown mnemonic `lock' -- `lock'
/tmp/ccBckn7v.s:29699: Error: unknown mnemonic `xadd' -- `xadd x0,[x1]'
/tmp/ccBckn7v.s:53176: Error: unknown mnemonic `lock' -- `lock'
/tmp/ccBckn7v.s:53177: Error: unknown mnemonic `xadd' -- `xadd x0,[x1]'
/tmp/ccBckn7v.s:53786: Error: unknown mnemonic `lock' -- `lock'
/tmp/ccBckn7v.s:53787: Error: unknown mnemonic `xadd' -- `xadd x0,[x1]'
/tmp/ccBckn7v.s:54887: Error: unknown mnemonic `lock' -- `lock'
/tmp/ccBckn7v.s:54888: Error: unknown mnemonic `xadd' -- `xadd x0,[x1]'
/tmp/ccBckn7v.s:72879: Error: unknown mnemonic `lock' -- `lock'
/tmp/ccBckn7v.s:72880: Error: unknown mnemonic `xadd' -- `xadd x0,[x1]'
/tmp/ccBckn7v.s:79932: Error: unknown mnemonic `lock' -- `lock'
/tmp/ccBckn7v.s:79933: Error: unknown mnemonic `xadd' -- `xadd x0,[x1]'
/tmp/ccBckn7v.s:80060: Error: unknown mnemonic `lock' -- `lock'
/tmp/ccBckn7v.s:80061: Error: unknown mnemonic `xadd' -- `xadd x0,[x1]'
/tmp/ccBckn7v.s:80558: Error: unknown mnemonic `lock' -- `lock'
/tmp/ccBckn7v.s:80559: Error: unknown mnemonic `xadd' -- `xadd x0,[x1]'
make[2]: *** [src/C++/CMakeFiles/quickfix.dir/build.make:76: src/C++/CMakeFiles/quickfix.dir/DataDictionary.cpp.o] Error 1
make[1]: *** [CMakeFiles/Makefile2:518: src/C++/CMakeFiles/quickfix.dir/all] Error 2
make: *** [Makefile:130: all] Error 2
初看之下只能知道是在編譯DataDictionary.cpp這個檔案時發生了錯誤。如果沒有其它資訊的話,一般人應該就是會去查看DataDictionary.cpp這個檔案中是否有lock / xadd這些關鍵字。這時你會發現在DataDictionary.cpp中並沒有這些錯誤的程式碼。
找不到錯誤來源下,只好請教google大神了。查了一下發現在quickfix的issue中有人也有提到相關的問題。參考之後才知道原來這些assembly code是在quickfix的AtomicCount.h這份檔案中。於是就轉為查看這個檔案。看了一下大致上理解相關的程式碼意圖是如果都沒有特別的定義,那quickfix會用自已實作的atomic_count,而它的atomic_count是參考自boost的函式庫的,在裏面測是用assembly code來實作。
至於為何用assembly code來實作這我就不確定,或許這樣的方式是最有效率的,但可移植性就不高了。因為在ARM64中就無法使用。
以前寫c++程式時知道在STL中也有實作atomic的類別可以來提供所需的功能,於就是想要將quickfix的程式碼改為如果有指定是ARM64的話,就使用STL的atomic實作。
修改AtomicCount.h
/* -*- C++ -*- */
/****************************************************************************
** Copyright (c) 2001-2014
**
** This file is part of the QuickFIX FIX Engine
**
** This file may be distributed under the terms of the quickfixengine.org
** license as defined by quickfixengine.org and appearing in the file
** LICENSE included in the packaging of this file.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** See http://www.quickfixengine.org/LICENSE for licensing information.
**
** Contact ask@quickfixengine.org if any conditions of this licensing are
** not clear to you.
**
****************************************************************************/
#ifndef ATOMIC_COUNT
#define ATOMIC_COUNT
#include "Utility.h"
#if defined(__SUNPRO_CC) || defined(__TOS_AIX__)
#include "Mutex.h"
#endif
#if defined(HAVE_ARM64OS)
#include <atomic>
#endif
namespace FIX
{
/// Atomic count class - consider using interlocked functions
#ifdef ENABLE_BOOST_ATOMIC_COUNT
#include <boost/smart_ptr/detail/atomic_count.hpp>
typedef boost::detail::atomic_count atomic_count;
#elif _MSC_VER
//atomic counter based on interlocked functions for Win32
class atomic_count
{
public:
explicit atomic_count( long v ): m_counter( v )
{
}
long operator++()
{
return ::InterlockedIncrement( &m_counter );
}
long operator--()
{
return ::InterlockedDecrement( &m_counter );
}
operator long() const
{
return ::InterlockedExchangeAdd(const_cast<long volatile *>( &m_counter ), 0 );
}
private:
atomic_count( atomic_count const & );
atomic_count & operator=( atomic_count const & );
long volatile m_counter;
};
#elif defined(__SUNPRO_CC) || defined(__TOS_AIX__)
// general purpose atomic counter using mutexes
class atomic_count
{
public:
explicit atomic_count( long v ): m_counter( v )
{
}
long operator++()
{
Locker _lock(m_mutex);
return ++m_counter;
}
long operator--()
{
Locker _lock(m_mutex);
return --m_counter;
}
operator long() const
{
return static_cast<long const volatile &>( m_counter );
}
private:
atomic_count( atomic_count const & );
atomic_count & operator=( atomic_count const & );
Mutex m_mutex;
long m_counter;
};
#elif defined(HAVE_ARM64OS)
typedef std::atomic<long> atomic_count;
#else
//
// boost/detail/atomic_count_gcc_x86.hpp
//
// atomic_count for g++ on 486+/AMD64
//
// Copyright 2007 Peter Dimov
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
class atomic_count
{
public:
explicit atomic_count( long v ) : value_(static_cast<int>(v)) {}
long operator++()
{
return atomic_exchange_and_add( &value_, 1 ) + 1;
}
long operator--()
{
return atomic_exchange_and_add( &value_, -1 ) - 1;
}
operator long() const
{
return atomic_exchange_and_add( &value_, 0 );
}
private:
atomic_count( atomic_count const & );
atomic_count & operator=( atomic_count const & );
mutable int value_;
private:
static int atomic_exchange_and_add(int * pw, int dv)
{
// int r = *pw;
// *pw += dv;
// return r;
int r;
__asm__ __volatile__
(
"lock\n\t"
"xadd %1, %0":
"+m"(*pw), "=r"(r) : // outputs (%0, %1)
"1"(dv) : // inputs (%2 == %1)
"memory", "cc" // clobbers
);
return r;
}
};
#endif
}
#endif
有修改的部份是31 ~ 33這幾行,以及113 ~ 116這幾行。用意是如有是AMR64的話,就引用STL的atomic,並定義quickfix內部的atomic_count類別為std::atomic<long>。
如何定義HAVE_ARM64
修改了AtomicCount.h後,接下來的問題就是要如何能加入HAVE_ARM64這個巨集了。感覺上這些定義要被所有的quickfix套件中的程式碼,以及未來要使用這個函式庫的其它程式都能知道才行,不然就算quickfix函式庫能正常編譯,但其它程式引用時就會編譯失敗。有了這樣的想法,看來比較可行的就是去調整quickfix的config.h了。
以下是在第一次使用CMake指令下產生的config.h:
#ifndef CONFIG_H_IN
#define CONFIG_H_IN
/* #undef HAVE_EMX */
#define HAVE_CXX17
#define HAVE_STD_SHARED_PTR
/* #undef HAVE_SHARED_PTR_IN_TR1_NAMESPACE */
/* #undef HAVE_STD_TR1_SHARED_PTR_FROM_TR1_MEMORY_HEADER */
#define HAVE_STD_UNIQUE_PTR
#endif
但如果是直接手動改這個檔案的話,下次再重新用CMake指令要重新建置時,會發現config.h又會被調整回原來的版本。於是就查看了一下CMakeLists.txt:
cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
option(HAVE_EMX "Build with EMX")
if(HAVE_EMX)
message("-- Building with EMX")
set (quickfix_PROJECT_NAME quickfix-emx)
else()
set (quickfix_PROJECT_NAME quickfix)
endif()
project(${quickfix_PROJECT_NAME} VERSION 0.1 LANGUAGES CXX C)
message("-- Project name ${CMAKE_PROJECT_NAME}")
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED OFF)
if( CMAKE_CXX_STANDARD EQUAL 17 )
option( HAVE_CXX17 "Using C++17" ON)
endif()
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/")
include(QuickfixPlatformSettings)
include(QuickfixPrebuildSetup)
# Call cmake with-DHAVE_SSL=ON to compile with SSL. Similarly for others.
option(HAVE_SSL "Build with SSL")
option(HAVE_MYSQL "Build with MySQL")
option(HAVE_POSTGRESQL "Build with PostgreSQL")
option(HAVE_PYTHON "Build with default Python version")
option(HAVE_PYTHON2 "Build with default Python2 version")
option(HAVE_PYTHON3 "Build with default Python3 version")
#Make sure that a previous config.h has not undefined HAVE_SSL
if(HAVE_SSL)
# Can set location explicitly, example, cmake -DOPENSSL_ROOT_DIR=/usr/local/ssl -DOPENSSL_LIBRARIES=/usr/local/ssl/lib
find_package(OpenSSL REQUIRED)
include_directories(${OPENSSL_INCLUDE_DIR})
message("-- OPENSSL_INCLUDE_DIR: ${OPENSSL_INCLUDE_DIR}")
message("-- OPENSSL_LIBRARIES: ${OPENSSL_LIBRARIES}")
message("-- OPENSSL_ROOT_DIR: ${OPENSSL_ROOT_DIR}")
message("-- Building with SSL")
endif()
if(HAVE_MYSQL)
find_package(MySQL REQUIRED)
include_directories(${MYSQL_INCLUDE_DIR})
message("-- Building with MySQL")
endif()
if(HAVE_POSTGRESQL)
find_package(PostgreSQL REQUIRED)
include_directories(${PostgreSQL_INCLUDE_DIRS})
message("-- Building with POSTGRESQL")
endif()
include(FindSharedPtr)
FIND_SHARED_PTR()
if (HAVE_SHARED_PTR_IN_STD_NAMESPACE)
message("-- set HAVE_STD_SHARED_PTR")
option( HAVE_STD_SHARED_PTR "HAVE_STD_SHARED_PTR" ON)
elseif(HAVE_SHARED_PTR_IN_TR1_NAMESPACE)
message("-- set HAVE_STD_TR1_SHARED_PTR")
option( HAVE_SHARED_PTR_IN_TR1_NAMESPACE "HAVE_SHARED_PTR_IN_TR1_NAMESPACE" ON)
elseif(HAVE_SHARED_PTR_IN_TR1_NAMESPACE_FROM_TR1_MEMORY_HEADER)
message("-- set HAVE_STD_TR1_SHARED_PTR_FROM_TR1_MEMORY_HEADER")
option( HAVE_STD_TR1_SHARED_PTR_FROM_TR1_MEMORY_HEADER "HAVE_STD_TR1_SHARED_PTR_FROM_TR1_MEMORY_HEADER" ON)
else()
message("-- shared_ptr not found.")
endif()
include(FindUniquePtr)
FIND_UNIQUE_PTR()
if (HAVE_UNIQUE_PTR_IN_STD_NAMESPACE)
message("-- set HAVE_STD_UNIQUE_PTR")
option( HAVE_STD_UNIQUE_PTR "HAVE_STD_UNIQUE_PTR" ON)
endif()
if (HAVE_PYTHON)
find_package(PythonLibs REQUIRED)
include_directories(${PYTHON_INCLUDE_DIRS})
elseif (HAVE_PYTHON2)
find_package(PythonLibs 2 REQUIRED)
include_directories(${PYTHON_INCLUDE_DIRS})
elseif (HAVE_PYTHON3)
find_package(PythonLibs 3 REQUIRED)
include_directories(${PYTHON_INCLUDE_DIRS})
endif ()
if( WIN32 OR ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" )
add_subdirectory(UnitTest++)
endif()
add_subdirectory(src)
add_subdirectory(examples)
if( WIN32)
add_subdirectory(test)
endif()
configure_file(cmake_config.h.in ${CMAKE_SOURCE_DIR}/src/C++/config.h @ONLY)
configure_file(cmake_config.h.in ${CMAKE_SOURCE_DIR}/include/quickfix/config.h @ONLY)
file(REMOVE config.h)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/spec/ DESTINATION share/quickfix
FILES_MATCHING PATTERN "FIX*.xml")
可以看到quickfix中的config.h主要是由106~107這兩行產生的。於是就再去了解一下CMake中的configure_file()這個語法的使用方式。才知道原來是利用cmake_config.h.in這個檔案來控制及產生的。於是就手動加了第11行:
#cmakedefine HAVE_ARM64OS
#ifndef CONFIG_H_IN
#define CONFIG_H_IN
#cmakedefine HAVE_EMX
#cmakedefine HAVE_CXX17
#cmakedefine HAVE_STD_SHARED_PTR
#cmakedefine HAVE_SHARED_PTR_IN_TR1_NAMESPACE
#cmakedefine HAVE_STD_TR1_SHARED_PTR_FROM_TR1_MEMORY_HEADER
#cmakedefine HAVE_STD_UNIQUE_PTR
#cmakedefine HAVE_ARM64OS
#endif
後續使用CMake的話,就是在產生編譯設定時在指令列中加入-DHAVE_ARM64OS=ON,這樣產生出來的config.h就會有含有:
#define HAVE_ARM64OS
結論
經過上述的調整,改了cmake_config.h.in及AtomicCount.h後,總算是可以順利地在Raspberry Pi的ARM64環境中正常編譯quickfix source code了。