C++/Python 환경에서 Coveralls + Travis-CI로 코드커버리지 테스트

코드 커버리지에 대한 설명들. https://blog.outsider.ne.kr/954

코드 커버리지를 테스트하기 위한 방법들을 정리해본다.


C++에서 코드커버리지 테스트

C++이나 gcc를 사용하고 있다면 g++ 컴파일 옵션에 --coverage 를 넣는다. 만약, cmake를 사용한다면, 다음과 같은 코드로 코드커버리지 테스트와 coveralls까지 연동 가능하다

https://github.com/dongbum/DServer/blob/master/.travis.yml

language: cpp

os:
- linux

compiler:
- gcc

dist: bionic

before_install:
- pip install --user cpp-coveralls
- sudo add-apt-repository ppa:mhier/libboost-latest -y
- sudo apt update
- sudo apt-get install libjemalloc-dev libjemalloc1 libjemalloc-dev libjsoncpp-dev libjsoncpp1 libmysqlcppconn-dev libmysql++-dev libboost1.70-dev

env:
  global:
  - MAKEFLAGS=-j 2

script:
- cmake CMakeLists.txt
- make
- ./GameServer

after_success:
- coveralls --exclude external_library --exclude make_gcc --exclude vs_solution --exclude CMakeFiles --gcov-options '\-lp'
© 2019 GitHub, Inc.

cmake로는 따로 옵션이 필요 없었다.


Python에서 코드커버리지 테스트

https://docs.coveralls.io/python 와 coveralls-python 두가지를 추천하고 있다. python-coveralls로 설정하던 중 다음과 같은 에러를 만났다.

다음과 같이 간단한 Traivs-CI 스크립트로 커버리지 테스트를 성공했다.

language: python
python:
  - 3.6
install:
  - pip install -r requirements.txt
  - pip install coverage
  - pip install coveralls
script:
  - python3 update.py
  - coverage run update.py
after_success:
  - coveralls

다만 coverage 명령은 run을 같이 해야되기 때문에 프로그램 실행되어야만 한다. 그래서 이 방법은 바로 실행되는 파이썬 프로그램에 한해서만 가능하다.


Python Flask에서 코드커버리지 테스트

Flask 프레임워크를 사용하면 웹서버를 바로 실행시키는 형태로 프로그램이 작동하는지라 위의 coverage run 명령어로 테스트를 할 수가 없다. 이 명령으로 테스트를 하면 웹서버가 실행되고 프로세스가 종료되지 않기 때문에 travis의 빌드도 영원히 계속된다. 물론 이 상태로 10분 정도가 지나면 travis가 빌드실패로 처리해버린다. 따라서 Flask를 쓸때는 travis-ci에서 실제 프로그램을 실행시키는 형태가 아니라 코드커버리지 테스트만 수행해야한다. 이렇게 작동하기 위해서는 nose라는 또다른 코드커버리지 툴이 필요했다. travis-ci 스크립트는 다음처럼 같이 작성했다.

language: python
services:
  - mysql
python:
  - 3.6
before_install:
  - mysql -e 'CREATE DATABASE IF NOT EXISTS test;'
install:
  - pip install -r requirements.txt
  - pip install coverage
  - pip install coveralls
  - pip install nose
  - pip install nose-exclude
script:
  - nosetests --with-coverage --cover-erase --cover-package=board
after_success:
  - coveralls

nosetests는 프로그램을 실제로 수행하지 않고도 코드커버리지만 테스트할 수 있기에 Flask로 작성된 프로그램들은 nose를 이용해야할 것 같다. 번외로, Flask를 사용하면서도 위에 있는 coverage run 명령을 이용할 수도 있는데, 웹서버를 실행시킨 상태로 특정 경로(예를 들면, /exit 같은...)을 호출하면 웹서버가 스스로 종료되도록 하는 방법이다. 하지만 이 방법은 그야말로 테스트를 위해 경로를 만들고 테스트만을 위한 종료코드까지 만들어야하는 관계로 nose를 쓰는 방법이 더 나은 것 같다.


참고자료

UnityBuild와 pragma once

회사에 들어와서 쓰던 것 중, 성준씨의 도움으로 유니티빌드라는 컴파일 기법을 알게 되었는데...

https://openshortcuts.com/archives/922

이 개념은 헤더파일을 다시 컴파일하는 것을 막아보자가 중심이다. 이 내용을 읽어보며 그럼 #pragma once#ifndef 가드가 있는데 왜 이걸로 컴파일 시간이 줄어든다는 것인가...? 에 대해 생각해보다가 다음과 같은 글을 보게 되었다. https://oojjrs.tistory.com/32 이 글이 내가 궁금했던 것을 정확히 설명해두었다.

한 번만 include 하는 것은 모두 오브젝트 단위에서의 이야기다. 오브젝트가 다르면 제 아무리 이전에 8만줄짜리 파일을 포함하여 컴파일을 이미 진행했다 하더라도 컴파일러는 다시 컴파일을 진행해버린다는 이야기다.

이것이 바로 유니티빌드를 사용할 때 컴파일 속도가 올라가는 이유였던 것.

Visual Studio 에서 cURL 설치

윈도우에서 웹페이지와 통신할 때 WinHTTP 모듈을 쓰고 있었는데 이 모듈은 윈도우 전용이므로 리눅스에서는 사용이 불가능하다.

cURL 모듈을 사용할 수 있는지 테스트.

vcpkg로 cURL을 간단히 설치할 수 있었다.

그러나....

도저히 해결이 불가능했다. 구글과 스택오버플로우를 열심히 뒤졌으나 빌드플랫폼이 맞지 않아서 그럴거라는 의견뿐. x86 라이브러리와 x64 라이브러리를 모두 받아서 테스트 해봤으나 실패했다.

vcpkg에 대한 환상이 좀 깨졌다.

 

직접 받아서 빌드해보기로 했다.

cURL은 git 에서 소스 관리를 하고 있다.

cURL을 클론한다.

git clone https://github.com/curl/curl.git curl.git

다운 받은 폴더의 projects/Windows 폴더에 가면 Visual Studio 버전별로 쭉 있다. 난 2015이므로 VC12 선택.

curl-all.sln 솔루션 파일을 더블클릭하여 VS2015로 열어본다.

왜인지 모르겠는데 프로젝트 로드가 안된다. 프로젝트 파일이 없나 해당 위치에 가서 살펴봤는데.... 진짜로 프로젝트 파일이 없다!

 

어떻게 해야되나 고민하던 중에 project 폴더를 보니 여러개의 배치파일이 보였다. 일단 checksrc.bat 파일을 실행했다. 한참 아무메시지가 없더니만 프로그램이 종료되었다. 별 문제가 없으면 별 메시지를 출력하지 않나보다. (bat 파일 내용을 읽어보진 않았다.)

이제 왠지 프로젝트를 생성해줄 것 같은 generate.bat 파일을 실행했다. 예상대로...

D:\Library\curl.git\projects>checksrc.bat

D:\Library\curl.git\projects>generate.bat

Generating prerequisite files
* D:\Library\curl.git\Makefile
* D:\Library\curl.git\src\tool_hugehelp.c

Generating VC6 project files
* D:\Library\curl.git\projects\Windows\VC6\src\curl.dsp
* D:\Library\curl.git\projects\Windows\VC6\lib\libcurl.dsp

(...중략...)

Generating VC12 project files
* D:\Library\curl.git\projects\Windows\VC12\src\curl.vcxproj
* D:\Library\curl.git\projects\Windows\VC12\lib\libcurl.vcxproj

(...중략...)

D:\Library\curl.git\projects>

예상대로 프로젝트 파일을 생성해주었다.

이제 다시 아까 VC12 폴더로 가서 솔루션 파일을 여니까 프로젝트가 보인다! 솔루션 빌드를 눌렀더니....

 

...안된다...

 

1>d:\library\curl.git\lib\ssh.h(28): fatal error C1083: 포함 파일을 열 수 없습니다. 'libssh2.h': No such file or directory
1>..\..\..\..\lib\md5.c(88): fatal error C1083: 포함 파일을 열 수 없습니다. 'openssl/md5.h': No such file or directory
1>..\..\..\..\lib\md4.c(31): fatal error C1083: 포함 파일을 열 수 없습니다. 'openssl/opensslconf.h': No such file or directory

메시지가 쭉 박혀있었다.

다시 project 폴더에 가서 왠지 ssl 문제를 해결해줄 것 같은 build-openssl.bat 파일을 실행시키니 다음과 같은 안내가 나왔다.

D:\Library\curl.git\projects>build-openssl.bat

Usage: build-openssl <compiler> [platform] [configuration] [directory] [-VSpath] ["VSpath"] [-perlpath] ["perlpath"]

Compiler:

vc6       - Use Visual Studio 6
vc7       - Use Visual Studio .NET
vc7.1     - Use Visual Studio .NET 2003
vc8       - Use Visual Studio 2005
vc9       - Use Visual Studio 2008
vc10      - Use Visual Studio 2010
vc11      - Use Visual Studio 2012
vc12      - Use Visual Studio 2013
vc14      - Use Visual Studio 2015
vc14.1    - Use Visual Studio 2017

Platform:

x86       - Perform a 32-bit build
x64       - Perform a 64-bit build

Configuration:

debug     - Perform a debug build
release   - Perform a release build

Other:

directory - Specifies the OpenSSL source directory

-VSpath - Specify the custom VS path if Visual Studio is installed at other location
          then "C:/<ProgramFiles>/Microsoft Visual Studio[version]
          For e.g. -VSpath "C:\apps\MVS14"

-perlpath - Specify the custom perl root path if perl is not located at "C:\Perl" and it is a
            portable copy of perl and not installed on the win system
            For e.g. -perlpath "D:\strawberry-perl-5.24.3.1-64bit-portable"

D:\Library\curl.git\projects>

어렵구나...

OpenSSL을 같이 연동해줘야하는것 같은데 정녕 이 방법 밖에 없는 것인지 다시 생각해봤다.

 

다시 vcpkg를 쓰는 방법을 찾아본다.

 

많은 글들을 참고로 했다. ( http://bugsfixed.blogspot.com/2017/05/vcpkg.html )

이것저것 해보던 중. 갑자기 프로그램이 정상작동 되었다. -_-;;

설정은 프로젝트 설정에서 추가 포함 디렉토리로 include와 추가 라이브러리 디렉토리로 lib 디렉토리만 설정했고 다른 설정은 모두 삭제했다.

프로그램을 빌드해봤더니 바이너리 파일과 함께 libcurl.dll, zlib1.dll 파일이 자동으로 생성되었다. 이제 cURL을 테스트 해볼 차례.

 

<참고자료>

  • cURL 사용하여 웹페이지 긁어오기 : http://sobusted.cf/221485638633
    이 예제 중 fstream 관련하여 약간 문제가 있어 수정해서 사용했다.
  • libcurl 메뉴얼 : https://curl.haxx.se/libcurl/c/
  • 테스트 해봤던 프로젝트 : https://github.com/dongbum/CURLTest

boost에서 OpenSSL 사용하기 (2)

빌드된 OpenSSL을 프로젝트 설정에 넣고 다시 빌드해본다.

include를 해야한다.

#include <boost/asio/ssl.hpp>

를 하면 ssl 코드들을 가져온다.

라이브러리 설정에는 빌드된 OpenSSL 폴더의 lib 폴더 경로를 포함시켜줘야한다.

 

이렇게하고 빌드를 해보니...

빌드에 오류가 생겼다.

에러 메시지를 구글에 다시 검색해보니,

https://stackoverflow.com/questions/37522654/linking-with-openssl-lib-statically

링크에서 답을 찾을 수 있었다. http://wiki.openssl.org/index.php/Compilation_and_Installation 에 가서 enable-capieng 이라는 옵션에 대해 찾아보라는 얘기였다.

해당 문서에서 enable-capieng 으로 검색해보니

Enables the Microsoft CAPI engine on Windows platforms. Used to access the Windows Certificate Store. Also see Using Windows certificate store through OpenSSL on the OpenSSL developer list.

라는 내용이 있었다.

빌드 옵션에 추가한다.

perl Configure VC-WIN64A --D:\ --openssldir=D:\Library\OpenSSL enable-capieng no-shared no-asm threads

이렇게 하고 다시 openssl을 빌드해본다.

 

이 옵션으로 해결되지 않아서 -d 옵션 추가. -d 옵션을 추가했더니 nmake 과정에서 알 수 없는 옵션이라는 에러메시지가 나오는 것을 확인했다.

 

빌드옵션을 변경했다.

perl Configure VC-WIN64A --prefix=D:\Library\OpenSSL --openssldir=D:\Library\OpenSSL --debug no-shared no-idea no-mdc2 no-rc5 no-asm no-capieng threads enable-ssl-trace enable-static-engine

 

이렇게 빌드했더니 오류가 절반 정도 사라졌다. 나머지 링킹 오류는 라이브러리 파일을 인식하는 위치에서 추가적인 라이브러리 파일을 불러들여야 했다.

.lib 파일을 불러들이는 곳에서 다음을 추가한다. (혹은 프로젝트 설정에서 해도 된다.)

#pragma comment(lib, "advapi32.lib")
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "libcrypto.lib")
#pragma comment(lib, "libssl.lib")

이렇게하고 빌드하니 오류 없이 빌드가 되었다.

 

참고했던 자료들

  • https://github.com/openssl/openssl/blob/OpenSSL_1_1_0-stable/INSTALL
  • https://www.lesstif.com/pages/viewpage.action?pageId=6291508
  • http://www.programmersought.com/article/8711142745/
  • https://github.com/libevent/libevent/issues/59

boost에서 OpenSSL 사용하기 (1) : 윈도우에서 OpenSSL 빌드

boost에서 OpenSSL을 사용하기 위해서는 먼저 OpenSSL 설치가 필요하다. boost의 ssl 관련 파일들에서 openssl 관련 파일들을 인클루드해오기 때문이다.

검색엔진을 찾아보다가, https://jethro.tistory.com/entry/CC-Windows에서-OpenSSL-써먹기 를 참고했다.

 

Step 1. ActivePerl 설치

https://www.activestate.com/products/activeperl/ 로 이동해서 ActivePerl 을 설치한다.

 

Step 2. OpenSSL 소스 다운로드

https://www.openssl.org/source/ 에서 소스코드를 다운로드한다. 난 1.1.1j 버전을 다운로드했다.

 

Step 3. Configure 설정

perl Configure VC-WIN32 --openssldir=C:\OpenSSL-x86-debug no-shared no-asm threads

를 입력하라고 하는데 64비트 윈도우이니 VC-WIN64A 로 입력. (VC-WIN64I 도 있긴한데 아마 Itanium 아키텍쳐일 때 쓰라는 것일 것 같다.)

다음과 같은 오류가 발생.

dmake나 nmake가 필요한 것 같다.

안내 받은대로 ppm install dmake 를 입력하면, 이런저런 패키지들을 설치한다.

 

다시 configure 명령어 입력.

perl Configure VC-WIN64A --openssldir=D:\Library\OpenSSL no-shared no-asm threads

별 문제 없이 끝났다.

 

Step 4. make

그런데 참고하고 있는 블로그 글에는 ms\do_ms.bat 파일이 있다고 했는데 그 위치에 해당 파일이 없다.

구글에 검색해보니...

https://stackoverflow.com/questions/39076244/why-there-is-no-ms-do-ms-bat-after-perl-configure-vc-win64a

1.1.0 버전부터는 그 명령어가 없어졌다고 한다. 그냥 nmake 를 입력하라고 되어있다.

nmake를 해보니 그런 명령어가 없다고 한다. 일반 cmd가 아니라 VS2015 x64 네이티브 도구 명령 프롬프트를 다시 실행해야한다.

 

다시 nmake 를 입력하면 빌드가 시작된다.

다 완료된 후 nmake test 를 입력하여 테스트. 테스트도 잘 완료되었다.

nmake install 을 입력했더니...

*** Installing runtime librariesCannot create directory C:/Program Files/OpenSSL: No such file or directoryNMAKE : fatal error U1077: 'C:\Perl64\bin\perl.exe' : '0x2' 반환 코드입니다.Stop.

에러로 중지된다. 으어...

구글에 검색해보니 나만 겪는 문제는 아닌듯하다. github에 이슈로 올라와있다.

https://github.com/openssl/openssl/issues/2034

댓글을 보고 configure 명령어에 prefix를 추가한다.

perl Configure VC-WIN64A --prefix=D:\Library\OpenSSL --openssldir=D:\Library\OpenSSL no-shared no-asm threads

다시 nmake clean, nmake, nmake test 실행.

다시 make install 을 실행하니 제대로 진행되었다.

 

빌드가 성공되면 prefix로 지정된 위치에 파일들이 생성된다.

OpenSSL 빌드는 끝.

boost 1.70.0 버전 업그레이드시 참고사항

boost를 1.60.0 버전을 계속 사용하다가 1.70.0으로 업그레이드하니 변경된 점이 많았다.

https://zepeh.tistory.com/498 를 참고한다.

ASIO에서 io_service가 io_context 로 변경되었다. 하지만 1.70.0 버전을 적용하면 io_service도 그대로 사용할 수 있긴 하다. cpp 파일과 h 파일도 그대로 들어있다. 비주얼스튜디오 인텔리센스에서도 io_context 가 바로 인식되지는 않는데, 이런 경우 전처리기 설정에서 BOOST_ASIO_NO_DEPRECATED 를 추가해주면 io_service 가 비활성화되고 io_context만 사용할 수 있게 된다.

io_context 로 코드를 바꾸고 나면 충돌사항들이 나오게 된다.

 

from_string 변경

from_string() 함수가 삭제되었다.

아래 코드를

boost::asio::ip::address_v4 address = boost::asio::ip::address_v4::from_string(server_ip, error_code);

아래처럼 변경한다.

boost::asio::ip::address_v4 address = boost::asio::ip::make_address_v4(server_ip, error_code);

이 문제도 해결.

 

strand.wrap() 변경

strand의 wrap() 함수도 변경되었다.

다음의 코드를

boost::asio::async_write(socket_,
            boost::asio::buffer(send_queue_.front().first, send_queue_.front().second),
            strand_.wrap(
                boost::bind(
                    &BasicSocket::OnSendHandler,
                    shared_from_this(),
                    boost::asio::placeholders::error,
                    boost::asio::placeholders::bytes_transferred
                )
            )
        );

아래처럼 변경한다.

boost::asio::async_write(socket_,
            boost::asio::buffer(send_queue_.front().first, send_queue_.front().second),
            boost::asio::bind_executor(strand_, boost::bind(
                &BasicSocket::OnSendHandler,
                shared_from_this(),
                boost::asio::placeholders::error,
                boost::asio::placeholders::bytes_transferred
            )
            )
        );

 

이 문제도 해결.

Google Breakpad 설치 (2)

gyp 파일을 다 실행하고 나면 솔루션 파일과 프로젝트 파일 등이 생겨난다. 비주얼스튜디오로 솔루션 파일을 열어보면 다음과 같이 여러개의 프로젝트가 솔루션 안에 들어있다.

K-007

맨 밑에 build_all 프로젝트를 빌드해보면...

84개의 오류와 28개의 경고로 빌드가 안된다. 뭐 어차피 기대도 안했다.

에러메시지를 보면

#include "testing/gtest/include/gtest/gtest.h"
#include "testing/include/gmock/gmock.h"

이 부분에서부터 에러가 시작된다. gtest와 gmock이 필요하다고 한다. 이건 아마 외부라이브러리인 모양.

이 패키지들은 https://github.com/google/googletest 에서 받을 수 있다. 이 레포지토리를 git으로 클론한다. 다운로드 받으면 googlemock과 googletest다 들어있으며, 각 디렉토리에 msvc 라는 디렉토리가 있고 여기에 비주얼스튜디오로 열 수 있는 솔루션 파일과 프로젝트 파일들이 들어있다.

솔루션 파일들을 열어서 실행해보면 .lib 파일들이 생성된다. 이것들은 지금 당장은 필요 없다.

 

이제 이 파일들을 구조에 맞게 넣어줘야한다. 이걸 어떻게 해야하는지 몰라서 한참 찾았다.

https://github.com/Mendeley/breakpad

이 사람이 디렉토리 구조를 이미 만들어서 넣어놨길래 그대로 참고했다. (아마 이 사람은 브레이크패드에 맞도록 모든 라이브러리 파일을 다 넣어서 올려놓은듯하다. 이 소스를 받아다가 사용하면 잘될꺼 같기도하다.)

여기서부터 문제가 있는데, googlemock과 googletest 디렉토리를 구글브레이크패드 폴더에 복사해넣어줘야 한다. 그런데 내 경우에는, 회사컴퓨터에서 할 때와, 내 컴퓨터에서 했을 때 경로가 달랐다.

회사컴퓨터에서는... 구글 브레이크패드 디렉토리 중 src 디렉토리 밑에 testing 디렉토리를 만들고 거기에 googlemock 디렉토리 내용 전체를 복사해서 넣는다. 그리고 src/testing 디렉토리 밑에 gtest 디렉토리를 만들고 거기에는 googletest 디렉토리 내용 전체를 복사해서 넣는다.

집 컴퓨터에서는... 구글 브레이크 패드 디렉토리 중 src 디렉토리 밑에 testing 디렉토리를 만들고 거기에 googlemock과 googletest 디렉토리 두개를 그대로 복사해넣었다.

 

이제 구글 브레이크패드 솔루션 파일을 열고 build_all 프로젝트를 빌드해보면...

안된다.

 

오류가 난다. 오류내용은

C4091 'typedef ': 변수를 선언하지 않으면 '' 왼쪽은 무시됩니다.
C2220 경고가 오류로 처리되어 생성된 'object' 파일이 없습니다.

해당 코드는 다음과 같이 되어있다.

typedef enum {
    hdBase = 0, // root directory for dbghelp
    hdSym,      // where symbols are stored
    hdSrc,      // where source is stored
    hdMax       // end marker
};

enum인데 이름이 정의되지 않은 상태로 typedef가 되었기 때문에 나는 에러이다.

이걸 검색해보니 다음과 같은 내용을 찾았다.

https://stackoverflow.com/questions/913344/how-can-i-remove-the-vs-warning-c4091-typedef-ignored-on-left-of-spreadsh

typedef를 그냥 삭제해버리랜다. 삭제하려고 봤더니...

이 오류가 나는건, C:\Program Files (x86)\Windows Kits\8.1\Include\um\DbgHelp.h 파일이었다. C++ 라이브러리를 수정할 수는 없는 노릇... '프로젝트 속성'의 '구성 속성' -> 'C/C++' -> '고급'을 찾아가 '특정 경고 사용 안 함' 에 4091을 추가했다. 이제 빌드가 된다.

같은 에러가 나는 프로젝트를 찾아서 전부 위처럼 4091 에러에 대한 경고무시를 설정해줘야한다.

이렇게 또 build_all 을 해봤더니 또 오류가 난다.

프로젝트 중 unittests 에 있는 processor_bits 프로젝트가 계속 에러가 나는 것이었다. 경고를 전부 오류 처리해버리기에 빌드가 되지 않았다. 이것도 4091 에러처럼 4267, 4366 을 경고무시에 추가한다.

이제 다시 build_all 프로젝트를 빌드해본다.

또 오류가 난다.

unittest 에 있는 client_tests 프로젝트에서 에러가 났다. 마찬가지로 경고를 오류로 처리해서 나는 에러. 4389, 4312, 4267 을 경고무시로 넣어준다.

다시 build_all 프로젝트를 빌드해본다.

드디어 아무 에러 없이 성공.

이제 빌드된 구글브레이크패드를 이용해서 실제 사용하는 방법을 찾아봐야겠다.

C++에서 엑셀파일 처리하기

C++에서 엑셀파일을 다루는 법을 찾아보았다.

2012년도에 엑셀 파일을 처리할 방법을 찾다가 https://blog.dongbumkim.com/archives/754 이런 글을 썼었는데 이제는 그보다 더 나은 방법을 찾을 수 있었다.

예전에 작업하던 소스에서는 ODBC를 이용하여 연결했었는데 왠일인지 이 방법으로는 제대로 연결되지 않았다. 당시에는 잘 작동하던 소스였는데...

찾아보니 윈도우 버전이 올라가며 제대로 작동하지 않는듯하다.(확실히 끝까지 찾아보지는 못했다.) ADO 등의 다른 방법으로 연결하는 것을 안내하고 있었다.

웹서핑을 하며 엑셀 파일을 다룰 수 있는 라이브러리를 찾아보다가 스택오버플로우에서 xlnt 라는 라이브러리를 추천해줘서 한번 테스트해보았다.

https://github.com/tfussell/xlnt

무려 크로스플랫폼의 C++14에 맞춘 xlsx 파일 처리 가능한 라이브러리라고 설명이 되어있다.

처음에는 잘 작동하는듯했으나... 오 잘되네 하고 본격적인 테스트코드를 만들다보니 버그가 있었다. 시트를 하나 더 추가해서 2개의 시트를 만들고 컬럼을 추가하면 에러가 떴다. 엑셀 파일을 아무리 봐도 문제가 없어 검색해봤더니 이미 버그리포팅이 되어있던 이슈. 나도 가서 한마디 거들었다.

https://github.com/tfussell/xlnt/issues/330#issuecomment-440589168

xlnt로 하루 넘는 시간을 날리고 다시 검색해보니 ExcelFormat 라이브러리를 추천했다. 이것으로 작업을 성공적으로 했다는 댓글도 있어서 이걸로 결정.

https://www.codeproject.com/Articles/42504/ExcelFormat-Library

사용해보니 다른 외부 컴포넌트 없이 바로 사용 가능했다.

단점은,

  • xlsx는 지원하지 않고 xls 파일만 지원한다.
  • 데이터를 가져올 각 셀마다 데이터타입을 정확하게 지정해줘야한다.
  • 그런데 숫자만 들어있는 셀은 전부다 double 형으로 데이터를 가져온다. int로 캐스팅해야함.
    for (int k = 0; k < sheet->GetTotalRows(); ++k)
    {
        for (int j = 0; j < sheet->GetTotalCols(); ++j)
        {
            ExcelFormat::BasicExcelCell* cell = sheet->Cell(k, j);
            switch (cell->Type())
            {
            case ExcelFormat::BasicExcelCell::INT:
                std::cout << "INT:" << cell->GetInteger() << std::endl;
                break;
            case ExcelFormat::BasicExcelCell::DOUBLE:
                std::cout << "DOUBLE:" << static_cast<int>(cell->GetDouble()) << std::endl;
                break;
            case ExcelFormat::BasicExcelCell::STRING:
                std::cout << "STRING:" << cell->GetString() << std::endl;
                break;
            case ExcelFormat::BasicExcelCell::WSTRING:
                std::cout << "WSTRING:" << cell->GetWString() << std::endl;
                break;
            case ExcelFormat::BasicExcelCell::UNDEFINED:
            case ExcelFormat::BasicExcelCell::FORMULA:
            default:
                break;
            }
        }
    }
  • 암호 걸린 엑셀파일은 처리 불가능하다.

테스트 코드를 열심히 작성해봤더니 시트가 여러개일때도, 데이터가 꽤 많을 때에도 데이터 처리가 무난했다.

사용했던 테스트 코드의 일부.

class DataTableMgr
{
private:
	ExcelFormat::BasicExcel xls;
};

bool DataTableMgr::LoadWeaponBombTable(void)
{
    ExcelFormat::BasicExcelWorksheet* sheet = nullptr;
    
    for (int i = 0; i < xls.GetTotalWorkSheets(); ++i)
    {
        sheet = xls.GetWorksheet(i);
        if (nullptr == sheet)
            continue;

        if (!strcmp(WEAPONBOM_SHEETNAME, sheet->GetAnsiSheetName()))
        {
            sheet = xls.GetWorksheet(i);
            break;
        }
    }

    if (nullptr == sheet)
        return false;

    // 첫행은 컬럼 인덱스이므로 항상 패쓰
    for (int i = 1; i < sheet->GetTotalRows(); ++i)
    {
        DATATABLE_WEAPONBOMB weapon_bomb;
        weapon_bomb.index = static_cast<int>(sheet->Cell(i, 0)->GetDouble());
        weapon_bomb.id = static_cast<int>(sheet->Cell(i, 1)->GetDouble());
        weapon_bomb.id_5pack = static_cast<int>(sheet->Cell(i, 2)->GetDouble());
        weapon_bomb.id_11pack = static_cast<int>(sheet->Cell(i, 3)->GetDouble());
        weapon_bomb.itemid = static_cast<int>(sheet->Cell(i, 4)->GetDouble());

        weapon_bomb_map.insert(DATATABLE_WEAPONBOMB_MAP::value_type(weapon_bomb.index, weapon_bomb));
    }

    std::cout << "WeaponBomb count : " << weapon_bomb_map.size() << std::endl;

    return true;
}

코드 자체는 잘 작동했지만, static 데이터를 엑셀 파일로 관리하는 것에 대해 위험함이 제기 되어 이 프로젝트는 무산되었다. ㅎㅎㅎ 나중에 언젠가는 쓸 일이 있겠지.

Visual Studio에서 jemalloc 설치하고 사용법 (1)

이 글은 jemalloc 을 설치하다가 애먹은 경험으로 쓰는 것. 혹시 나처럼 Visual Studio에서 jemalloc을 쓰려고 고생하는 사람들에게 도움이 되길 바란다. 그리고 퍼갈 때에는 출처도 밝혀주시기를...

tcmalloc 이 VIsual Studio 2015 에서 작동이 제대로 안된다는 것을 알고 난 이후 다른 대안을 찾아보았다. 비슷한 기능을 하는 것으로 jemalloc과 nedmalloc가 있었는데 nedmalloc은 github에서 보니 개발이 멈춘지가 한참전이었다. 몇년전부터 개발이 중지된 것을 쓰기는 싫기에 jemalloc을 다운받았다.

jemalloc 에 대한 성능이나 기술적인 이야기는 마지막 남은 공짜 점심. Facebook의 메모리 할당자 jemalloc라는 글에 잘 설명되어 있다. 한번 읽어보는 것도 괜찮겠다.

참고로 jemalloc 이 현재 쓰이는 곳은 github의 IntendedUse 항목에서 찾아볼 수 있다.

에서 쓰이고 있다고 한다. 개발자라면 대부분 알만한 유명한 프로젝트들.

Visual Studio에서 jemalloc 설치하고 사용법 (3)

jemalloc 라이브러리가 정상 작동하므로 솔루션에 포함되어 있는 test_threads 프로젝트를 빌드해본다.

별 설정 없이도 잘 빌드되고 실행된다.

....만, 이게 도대체 뭔 내용인지는 나도 잘 모르겠다. test_threads.cpp 파일에는 테스트용 코드가 잔뜩 있다. 다 읽어보기도 힘들어서 읽어보다가 관둿다. 대략 je_malloc, je_free 등의 함수를 이용하는 예제코드인 것 같다.

github에는 간단한 예제코드가 있다. ( https://github.com/jemalloc/jemalloc/wiki/Getting-Started )

....는 별로 도움이 되지 않았다.

그냥 쉽게 얘기하면 new/malloc 을 할 때 je_malloc 을 하면 되고 delete/free 를 할 때 je_free 를 하라면 되는 말.

그래서 new와 delete를 오버라이딩하는 클래스를 만든다.

new 를 사용하는 곳에서 이 오버라이딩 클래스를 상속 받게 해주면 끝.

그리고 헤더에서 #include <jemalloc/jemalloc.h> 를 넣어주고 추가 포함 디렉터리에는 jemalloc의 include 디렉토리를 지정해주고 빌드하면 된다.

이렇게하면 빌드가 성공하고 실행파일에 있는 위치에 jemalloc 빌드 후 생성된 .dll 파일을 같이 넣어주면 된다.

....인줄 알았는데 빌드가 안된다.

코드를 보니 jemalloc.h 파일에서

#include <string.h> 부분이 문제가 되었다. 이 문제에 대해 찾아보니...

https://stackoverflow.com/questions/15512790/error-about-finding-strings-h-in-htmlcxx

아마 유닉스쪽에서 쓰는 헤더파일일 거라고 한다.

해당 부분을

#ifdef _WIN32
#include <string.h> 
#else
#include <strings.h>
#endif

로 교체하면 잘 작동한다.

프로젝트의 구성속성을 보면 Debug, Debug-static 식으로 나뉘어 있는데, 전자는 .dll 파일을 쓰는 동적 링크, 후자는 .lib 파일을 쓰는 정적 링크이다. 각자 원하는 것으로 사용.