Onvif 라이브러리 크로스 컴파일 방법 정리

2022. 1. 5. 14:28Programming/C, C++

반응형

Onvif 라이브러리 크로스 컴파일 방법 정리

이번에는 libOnvif.so 라이브러리 파일을 생성하는 방법에 대해 정리해보고자 합니다. 현대사회를 살아가는 여러분들에게 이미 WSDL이나 SOAP은 거의 잊혀진 기술이다보니, 마치 로스트 테크놀로지같은 느낌일텐데요. 그나마 다행인 점은 검색하면 위키피디아 등에 어떤 기술인지 잘 설명이 되어있다는 점입니다. 물론 크게 도움은 안된다는게 문제지만 말이죠.

이 글에서는 SOAP 프로토콜을 통해 제공되는 Onvif WSDL을 기반으로 C/C++ 코드를 생성해내는 방법과, 오류 대처 요령에 대해 살펴봅니다. 만약 여러분이 Onvif 라이브러리를 빌드해야 할 일이 생겨서 컨플루언스 등에 남겨진 고문서를 찾아본다면 생각보다 쉽게 해결될수도 있지만, 의외로 난항에 부딪힐수도 있을겁니다. WSDL로 제공되는 문서들을 파싱해서 C/C++를 생성하다보니, gSOAP의 버전이나 제공되는 typemap.dat의 내용에 따라서 생성 결과가 조금씩 달라지기 때문이죠. 일단 본문을 시작하기 전에, WSDL과 SOAP에 대해 살짝 알아봅시다.

WSDL

WSDL는 Web Services Description Language의 약자로, 웹 서비스가 어떤 서비스를 제공하는지 설명하는 문서를 말합니다. 일반적으로는 XML으로 작성되어있죠. Onvif 역시 스펙을 WSDL로 공개하고 있어서, Onvif 라이브러리를 빌드하다가 문제가 생겼다면 WSDL이 뭔지 정도는 알고 있는 편이 좋답니다. 자세한 내용은 위키피디아같은거 찾아보세요. 이 글도 위키피디아 보고 적은 내용이니까요.

SOAP

SOAP은 Simple Object Access Protocol의 약자로써, 일반적으로 잘 알려진 HTTP, HTTPS, SMTP 등을 통해 XML 기반의 메시지를 네트워크 상에서 교환하는 프로토콜을 말합니다. 대충 네트워크를 통해서 메시지를 전송하기 위한 프로토콜이구나~ 하고 이해하면 된답니다. 웹 서비스(WS)에서 기본적인 메시지를 전달하는 기반이 되는 이 프로토콜은, 마찬가지로 WSDL을 전달할때도 사용되는데요. 이 SOAP을 구현한 라이브러리들로는 cSOAP, gSOAP, libsoup 등이 있답니다.

가장 많이 사용되는 gSOAP은 플로리다 주립대에서 만들었으며, 서드파티 라이브러리에 의존하지 않고 모든 기능을 가지고 있어 편리하다는 장점이 있죠. 그리고 이번 글에서는 웹 서비스(WS)에서 제공되는 WSDL을 이용해서, C/C++ 파일을 생성하는 역할을 담당하게 됩니다. 뭐 사실 파싱에 가깝지만 말이죠. 와아ㅡ 갱장해ㅡ! 역시 자세한 내용은 위키피디아같은거 찾아보십쇼. 저도 이 이상은 잘 모르고, 이 이상 알고싶지도 않은게 솔직한 심정입니다. 아무튼 뭐 현재 기술의 근간이 되기는 하지만, 로스트 테크놀로지마냥 알고 있어봤자 딱히 도움이 되지는 않을 것 같거든요.

아무튼 여기까지 WSDL과 SOAP에 대해 간단히 살펴봤으니, 이제 본격적으로 gSOAP과 onvif 라이브러리를 빌드하는 방법에 대해 살펴볼 차례입니다. 간략하게 순서를 정리하면 다음과 같으니 한번 훑어보십쇼.

빌드 순서

빌드 순서는 대략 아래와 같습니다.

  • HOST용 gSOAP 빌드
    • WSDL 파싱을 위해 HOST 환경에서 사용합니다.
  • ARM용 gSOAP 빌드
    • SOAP 프로토콜 통신을 위해 사용합니다.
  • libonvif.so 빌드
    • Onvif 스펙에 명시되어있는 기능을 위해 사용합니다. 아래와 같은 세부 절차를 따라 빌드됩니다.
      1. gSOAP 결과물에서 제공되는 C, H파일 복사
      2. gSOAP 결과물에서 제공되는 typemap.dat 파일 복사
      3. typemap.dat 파일 수정
      4. gSOAP 결과물 중 하나인 wsdl2h를 사용해서, WSDL을 파싱하여 헤더 파일(H) 생성
      5. gSOAP 결과물 중 하나인 soapcpp2를 사용해서, 헤더 파일로부터 C/C++ 파일 생성
      6. 빌드 시도
      7. 에러 메시지를 보고 C, H에 명시된 enum값을 적절히 수정하거나, 3~6의 과정을 반복
      8. 빌드 결과물 설치

libonvif.so 빌드 과정은 복잡한 만큼 사람을 환장하게 만드는데, 이유는 gSOAP의 버전, typemap.dat의 내용, WSDL의 내용에 따라 생성된 C/C++ 파일이 조금씩 달라지기 때문입니다. 무엇보다 Onvif에서 제공하는 WSDL도 조금씩 달라지고, gSOAP 버전도 조금씩 올라가는데 무려 구버전 소스코드는 공개되지도 않으며, OpenSSL처럼 의존성 있는 라이브러리에서 Deprecated로 선언하는 경우도 가끔씩 있기 때문이죠. 이런 내용을 모른 상태에서 아 이러이러하게 빌드하면 결과물이 뚝딱하고 나오더라라고 기록해놓으면, 일정 시간 이후에는 전혀 도움이 안되는 문서가 될 수 있습니다. Onvif 스펙이 변경되는 일도 많지 않고, 크로스 컴파일 할 일도 많지는 않기 때문이죠.

아무튼 시간이 꽤 흘러도 도움이 될 수 있게끔, 오류 대처 요령을 자세히 기술해놓을 생각이니 마음의 부담을 조금 덜어놓으시길 바랍니다. 그렇다고 다 내려놓진 마세요. 생각만큼 잘 해결되진 않을거거든요. 아무튼 gSOAP부터 시작하도록 하겠습니다.

1. gSOAP (HOST) 빌드

자 우선 호스트 환경에서 WSDL로 제공되는 Onvif 스펙을 헤더 파일로 파싱하고, C/C++ 파일을 생성하기 위해서 gSOAP을 설치할 필요가 있습니다. gSOAP은 GPLv2 라이센스를 가진 오픈소스 툴킷으로, 상용 버전은 GENIVIA에서 배포되고 있는데요. 어차피 ARM용은 제공되지 않기 때문에 소스 코드를 받아서 크로스 컴파일해야되며, 호스트 환경에 설치하는 것은 어렵지 않기 때문에 Sourceforge에서 소스 파일을 받도록 합니다.

Sourceforge에 공개된 소스 파일은 구 버전은 삭제됩니다. 이 글에서 사용한 버전은 2.8.118버전이지만, 시간이 오래 흘렀다면 해당 버전의 소스코드는 구하기 어려울 수도 있고, 설령 구한다고 하더라도 OpenSSL 버전 처리 등의 문제가 있을 수 있으므로 그냥 최신 버전을 받도록 합니다.

어차피 빌드 과정에서 문제가 발생한다 치더라도, GPLv2 라이센스기 때문에 소스를 뜯어고치지도 못하는데다가, 소스를 뜯어고쳐봤자 사이드 이펙트가 발생할 확률만 높아지므로 얌전히 오류 대응 요령을 보고 대처하도록 하시는 것이 멘탈 관리에 큰 도움이 됩니다. 어떻게 아냐구요? 저도 알고싶지는 않았습니다.

호스트 환경에서 돌아가는 gSOAP 바이너리를 생성하는 건 어렵지 않습니다. 다음과 같이 configure를 실행해줍니다.

configure --preifx=/home/$USER/Library \
  --exec-prefix=/home/$USER/Library \
  CXXFLAGS=-I/home/$USER/library/openssl/include \
  CFLAGS=-I/home/$USER/library/openssl/include \
  LDFLAGS=-L/home/$USER/library/openssl/lib

prefixexec-prefix는 각각 설치될 경로와 라이브러리가 실행될 경로를 의미하는데, 루트 경로에 설치할 권한도 없거니와 권한을 받기도 귀찮고, 서버 관리자도 귀찮을게 뻔하기때문에 home 디렉토리에 적당한 경로를 지정해서 설치해줬습니다.

CXXFLAGS, CFLAGS, DLFALGS에 OpenSSL이 설치된 경로를 지정해준 이유는, 서버에 설치된 OpenSSL 버전과 필요한 OpenSSL 버전이 다르기 때문입니다. (정확히는 서버에는 1.0.2 버전이 설치되어있지만, 크로스 컴파일 된 결과물은 1.1.1 버전을 참조해야했기 때문) 당연히 OpenSSL이 해당 경로에 설치되어있어야한데, OpenSSL을 호스트 환경에 설치하는 방법같은 걸 다루자면 분량이 안드로메다행 편도 티켓을 끊고 달려갈테니 다른 글에서 다루도록 하겠습니다. 어차피 호스트 환경에 설치하는거니까, 딱히 필요하지는 않겠지만 말이죠.

이후 makemake install을 실행하면 —prefix로 지정한 경로에 gSOAP 바이너리가 설치됩니다. 다음과 같이 .bashrc를 실행해서 설치된 바이너리의 경로를 환경변수에 등록해줍니다.

export PATH=$PATH:'/home/zerodice0/library/bin'

이제 soapcpp2wsdl2h를 입력해서 정상적으로 실행되는지 살펴봅니다. 정상적으로 실행된다면 준비는 OK. 여기까지는 해피ㅡ하게 성공했을겁니다. 이제 언해피ㅡ해지기 시작할테니까, 담배 피시는 분들은 담배도 좀 피시고, 안피시는 분들은 바람도 쐐고 한숨도 좀 미리 푹푹 쉬어두고 돌아오십시오.

2. gSOAP(ARM) 크로스 컴파일

이제 크로스 컴파일을 할 차례입니다. 사실 gSOAP 크로스 컴파일은 Onvif 라이브러리를 빌드하는 것에 비하면 쉬운 편이긴 합니다. 언제나처럼 configure ➡ Make ➡ Make install의 순서대로 진행됩니다. 우선 아래 configure를 위해 생성해놓은 쉘 스크립트를 참고해주세요.

./configure --host=$CROSS \
  --enable-ipv6 \
  CXXFLAGS="-I$FILESYS/usr/include" \
  CFLAGS="-I$FILESYS/usr/include" \
  CROSS_COMPILER="$CROSS" \
  LDFLAGS="-L$FILESYS/usr/lib"

echo -e "\e[31m <<<< Configuration END\e[0m";

우선 —enable-ipv6옵션은 SOAP 프로토콜을 사용해서 통신을 할 때, IPv6를 지원할지 여부를 의미합니다. 어떤 옵션들이 있는지는 configure파일을 참조해주시구요. 그 외에 참조하는 인자들은... 딱히 별거 없죠? 타겟 시스템에 설치된 라이브러리들을 참조하기 위해, usr/includeusr/libCXXFLAGSCFLAGS로 지정해줍니다.

이제 설정이 끝났다면 make를 실행합니다. 저는 OpenSSL 버전이 서버와 달라서 별도로 버전을 지정해줬다고 했죠? 그래서 make를 위한 쉘 스크립트도 아래와 같이 만들어줬답니다. 참고만 하십쇼, 참고만.

make -j 4 CROSS_COMPILER="arm-ca53-linux-gnueabihf" \
  CXXFLAGS="-I$FILESYS/usr/include -I/home/$USER/Library/openssl/include -fPIC" \
  CFLAGS="-I$FILESYS/usr/include -I/home/$USER/Library/openssl/include -fPIC" \
  LDFLAGS="-L$FILESYS/usr/lib";
echo -e "\e[31m <<<< Make END\e[0m";

위와 같이 실행하면 빌드가 진행되는 도중 다음과 같은 에러가 발생합니다.

Making all in wsdl
make[4]: Entering directory '/home/zerodice0/gsoap-2.8/gsoap/wsdl'
../../gsoap/src/soapcpp2 -SC -pwsdl -I../../gsoap/wsdl -I../../gsoap/import ../../gsoap/wsdl/wsdl.h
../../gsoap/src/soapcpp2: ../../gsoap/src/soapcpp2: cannot execute binary file
Makefile:914: recipe for target 'wsdlC.cpp' failed
make[4]: *** [wsdlC.cpp] Error 126

gsoap/wsdl 경로로 이동한 뒤 에러가 발생한 ../../gsoap/src/soapcpp2 -SC -pwsdl -I../../gsoap/wsdl -I../../gsoap/import ../../gsoap/wsdl/wsdl.h 명령을 실행해보면 동일한 에러가 출력되는 것을 확인할 수 있습니다. 빌드 결과물인 /gsoap/src/soapcpp2은 크로스 컴파일 된 결과물로써, 타겟 프로세서에 맞게끔 생성된 바이너리기 때문에 실행할 수 없는 것이죠. 어차피 호스트 환경에도 gSOAP을 설치했기 때문에 soapcpp2 -SC -pwsdl -I../../gsoap/wsdl -I../../gsoap/import ../../gsoap/wsdl/wsdl.h를 입력해주면 결과물이 생성되고, 다시 한 번 make 과정을 진행하면 soapcpp2를 테스트하는 과정은 생략되므로 에러가 사라지는 것을 확인할 수 있습니다.

이제 다음 구문을 실행하면 libgsoapssl.a 파일을 libgsoapssl.so 파일로 변환할 수 있습니다. 이 파일을 타겟 시스템에 복사하면 gSOAP 크로스 컴파일 과정이 완료됩니다. 그리고 make install DESTDIR=dist를 실행해서, 빌드 결과물을 생성해놓도록 합시다. 이 빌드 결과물 gsoap/dist/usr/local에 생성되며, Onvif 라이브러리를 빌드할 때 사용됩니다.

호스트 환경에서 사용하기 위해 설치한 gSOAP 툴킷의 경로에도 동일한 C, CPP, H파일이 존재하기 때문에 이 파일들을 사용해도 큰 문제는 발생하지 않을겁니다. 여기서는 크로스 컴파일 과정에서 혹시나 호스트 환경과 내용이 다른 C, CPP, H파일이 존재하는 것을 우려하여 make install까지 진행했으니 참고해주세요.

$CC -shared -o libgsoapssl.so.2.8.118 -Wl,-whole-archive ./gsoap/libgsoapssl.a -Wl,-no-whole-archive

3. Onvif 라이브러리 빌드

3-1. gSOAP에서 제공되는 파일 복사

이제 밑준비는 끝났고 남은 것은 Onvif 라이브러리 빌드 뿐입니다. 역시 몹시 스트레스받는 과정이므로, 미리 바람도 좀 쐐고 오시고 가족분들께 안부인사도 하고 오십쇼. 준비가 됐다면 gSOAP 결과물로 제공되는 C/H 파일과 typemap.dat 파일을 빈 폴더에 복사하는 것부터 시작합니다.

makdir ~/onvif_build
cp -r ~/gsoap-2.8/gsoap/dist/usr/local gsoap
ln -s gsoap/share/gsoap/custom/duration.c
ln -s gsoap/share/gsoap/custom/duration.h
ln -s gsoap/share/gsoap/plugin/httpda.c
ln -s gsoap/share/gsoap/plugin/httpda.h
ln -s gsoap/share/gsoap/plugin/md5evp.c
ln -s gsoap/share/gsoap/plugin/md5evp.h
ln -s gsoap/share/gsoap/plugin/smdevp.c
ln -s gsoap/share/gsoap/plugin/smdevp.h
ln -s gsoap/include/stdsoap2.h
ln -s gsoap/share/gsoap/plugin/threads.c
ln -s gsoap/share/gsoap/plugin/threads.h
ln -s gsoap/share/gsoap/WS/typemap.dat
ln -s gsoap/share/gsoap/plugin/wsaapi.c
ln -s gsoap/share/gsoap/plugin/wsaapi.h
ln -s gsoap/share/gsoap/plugin/wsseapi.c
ln -s gsoap/share/gsoap/plugin/wsseapi.h

여기서는 복사해온 디렉터리의 share/gsoap에 위치한 파일들을 심볼릭 링크로 걸어주고 있습니다. 어차피 심볼릭 링크로 걸어줄거면 왜 복사를 해오는지 의아하실수도 있는데, 몇몇 파일들은 빌드 전에 수정이 필요하기 때문입니다. 만약 빌드가 완료된 Onvif 라이브러리에서 특정 함수를 참조하지 못하는 경우, gsoap/share 내에서 해당 함수가 정의되어있는 C파일을 찾아서 링크를 걸고, 빌드 과정에 해당 파일을 포함하도록 수정해줍시다.

누락된 C파일에 대한 에러 대응 방법

최종적으로는 gSOAP을 사용해서 만들어진 파일들과, gSOAP에서 제공되는 C, H파일을 사용해서 빌드한 오브젝트 파일(.o)을 합쳐서 Shared library를 만들게 됩니다. 이 때 gSOAP에서 제공된 헤더를 참조하도록 빌드 스크립트를 작성할 예정인데, C파일이 포함되지 않으면 함수 구현부를 찾을 수 없기 때문에 에러가 발생할 수 있습니다. 빌드 에러를 기준으로 적당한 C파일을 참조할 수 있도록 링크를 걸어주세요. 적당한 C파일은 어떻게 찾냐구요? 이렇게 막막한 상황에 직면한 여러분에게는, Linux가 실낱같은 희망을 부여하기 위해 grep이라는 녀석을 제공해준답니다...

3-2. typemap.dat 파일 수정

이제 Onvif에서 제공하는 스펙 페이지를 참조해서 typemap.dat 파일을 수정해야합니다. 대충 어떤 것을 수정하는지는 아래 예시를 보고 참고해주세요. typemap.dat은 wsdl2h를 통해 생성되는 헤더 파일에 영향을 미치므로, soapcpp2 혹은 빌드 시 에러가 발생한다면 typemap.dat부터 확인해봐야 합니다.

#       The following mapping is internally applied by wsdl2h for C (not C++):
#       (assuming option -e is used to remove the xsd__boolean__ name prefix)
xsd__boolean  = enum xsd__boolean { false_, true_ }; | enum xsd__boolean
#       Uncomment the line below to use LONG64 int for xsd:duration instead of
#       mapping xsd:duration to string (in milliseconds precision).
#       Then rerun wsdl2h and also compile and link custom/duration.c.
#
xsd__duration = #import "custom/duration.h" | xsd__duration

위와 같이 typemap.dat에 주석 처리된 구문을 잘 읽어보고 주석 구문을 해제합니다. 위의 두 구문을 해제하면 xsd__boolean에 대한 enum값이 false_, true_로 변경되며, xsd:duration으로 기술된 값을 파싱할 때 string으로 파싱하지 않고 LONG64로 파싱하게 됩니다.

이 외에도 Onvif Specification에서 제공되는 WSDL 주소를 확인한 뒤, 아래와 같이 Prefix를 지정할 수 있습니다.

schema        = "http://www.onvif.org/ver10/schema"
device        = "http://www.onvif.org/ver10/device/wsdl"
media         = "http://www.onvif.org/ver10/media/wsdl"

이미 Onvif 라이브러리를 만들어서 소스 코드를 작성한 이력이 있고, 모종의 사유로 인해 Onvif 라이브러리를 다시 빌드해야하는 상황이라면 prefix도 무척 중요한 요소로 작용하게 됩니다. 만약 prefix가 달라진다면 함수/변수명이 달라지기 때문에, Onvif 라이브러리를 참조하는 코드에서 에러도 가득! 컴퓨터 앞의 당신의 입에서 비명도 가득! 한 상황이 펼쳐지기 때문이죠.

Prefix에 대한 에러 대응 방법

만약 prefix를 수정했지만 typemap.dat을 찾을 수 없다! 이런 불상사가 벌어진다면, Onvif 라이브러리를 참조하는 코드에서 어떤 함수를 찾을 수 없는지 에러가 주르륵 뜰 겁니다. 예를 들어서 과거에 http://www.onvif.org/ver10/device/wsdl에서 제공되는 wsdl의 prefix가 dd로 지정되어있었고, 이 prefix에 대한 초기값을 위에서 명시한 것처럼 device로 지정해서 라이브러리를 빌드했다고 가정합시다. http://www.onvif.org/ver10/device/wsdl에는 GetServices라는 구조체를 제공하는데요. 예전에 만들었던 Onvif 라이브러리에는 dd라는 prefix를 붙여서, _dd__GetServices라는 구조체로 명명되어 있을겁니다. 하지만 이번에는 prefix를 device로 변경했기 때문에, 구조체 명이 _device__GetServices로 변경이 되어버리죠. 그러면 이전에 Onvif 라이브러리를 참조하던 코드는 엥...? 나는 _dd__GetServices같은 구조체는 모르는디...?라는 에러가 뜨겠죠? 그럼 이제 prefix를 제외한 GetServices라는 구조체를 제공하는 WSDL 주소를 Onvif Specification에서 검색해본 뒤, 알맞은 prefix를 지정해주면 됩니다. 말은 간단하지만 실제로 하고있자면, 과거에 자신이 어떤 업보를 쌓았는지 생각하게되는 참회의 시간을 강제로 맞이하게 될 수도 있습니다. 만약 이런 작업을 해야한다면, 다시 한 번 밖에 나가서 상쾌한 공기도 좀 쐐고 비명도 좀 미리 질러두고 오십시오.

3-3. wsdl2h를 사용해서 wsdl을 h파일로 파싱하기

wsdl2h를 사용하면 인자로 전달된 WSDL 파일에서 헤더 파일을 추출할 수 있습니다. wsdl2h를 사용할 때 중요한 옵션은 -c-n이 있는데요. -c옵션을 지정하면 C언어를 위한 헤더 파일을 생성하며, -n옵션을 지정하면 namespace 영역을 구분지어주게 됩니다. namespace를 지정하지 않고 wsdl2h를 사용하게 되면, 매번 메소드에 다른 prefix가 지정되므로 문제가 발생할 수 있으니 주의해주세요. -t 옵션은 참조할 typemap.dat 파일을 지정합니다.

wsdl2h -c -d -nonvif -t ./typemap.dat -o onvif.h \
[https://www.onvif.org/ver10/device/wsdl/devicemgmt.wsdl](https://www.onvif.org/ver10/device/wsdl/devicemgmt.wsdl) \
[https://www.onvif.org/ver10/media/wsdl/media.wsdl](https://www.onvif.org/ver10/media/wsdl/media.wsdl) \
[https://www.onvif.org/ver20/media/wsdl/media.wsdl](https://www.onvif.org/ver20/media/wsdl/media.wsdl) \
[https://www.onvif.org/ver20/imaging/wsdl/imaging.wsdl](https://www.onvif.org/ver20/imaging/wsdl/imaging.wsdl) \
[https://www.onvif.org/ver20/ptz/wsdl/ptz.wsdl](https://www.onvif.org/ver20/ptz/wsdl/ptz.wsdl) \
[https://www.onvif.org/ver20/analytics/wsdl/analytics.wsdl\](https://www.onvif.org/ver20/analytics/wsdl/analytics.wsdl%5C%5C)
[https://www.onvif.org/ver10/deviceio.wsdl](https://www.onvif.org/ver10/deviceio.wsdl)

이렇게 하면 제공된 WSDL을 파싱하여 하나의 헤더 파일로 묶어내게 됩니다. 만약 기능별로 다른 H파일을 생성해서 분리하려면, -n옵션을 통해 namespace를 구분해주면 되니 참고해주세요.

3-4. soapcpp2를 사용해서 헤더 파일로부터 C파일 추출하기

이제 wsdl2h를 통해 생성한 헤더파일을 가지고, soapcpp2를 사용해서 C파일을 만들어줄 수 있게 됐다. 아래의 명령어를 살펴봅시다.

soapcpp2 -L -c -C -n -x -ponvif -I$GSOAP:$GSOAP/import:$GSOAP/plugin:$GSOAP/custom onvif.h

정확한 옵션은 soapcpp2 -h 내지는 Linux man page를 참조해주세요. 이제 위의 예시대로 명령어를 입력하면 C/H 코드가 생성되지만... 여기서 잠시. share/gsoap/plugin/wsseapi.h 파일을 열어보면 다음과 같은 구문을 확인할 수 있습니다.

/* When using soapcpp2 option -q<name> or -p<name>, you need to change "soapH.h" below */

/* include soapH.h generated by soapcpp2 from .h file containing #import "wsse.h" */
#ifdef SOAP_H_FILE      /* if set, use the soapcpp2-generated fileH.h file as specified with: cc ... -DSOAP_H_FILE=fileH.h */
# include "stdsoap2.h"
# include SOAP_XSTRINGIFY(SOAP_H_FILE)
#else
# include "onvifH.h"    /* or manually replace with soapcpp2-generated *H.h file */
#endif

즉, soapcpp2를 실행하기에 앞서, wsdl2h에서 생성된 onvif.h파일을 열어, #import “wsse.h”를 추가해줘야 한다는 얘기입니다. 또, -p옵션을 사용해서 prefix를 onvif로 바꾸었기 때문에, 사용할 C파일에서 #include “soapH.h”로 선언된 구문이 있다면 모두 onvifH.h를 include하도록 변경해줘야 빌드시 에러가 발생하지 않습니다. 이제 soapcpp2를 실행해서 C, H파일이 생성됐다면, 남은 과정은 빌드 뿐입니다.

3-5. Onvif 라이브러리 빌드

드디어 마지막 과정인 Onvif 라이브러리 빌드입니다. 역시 괴로운 과정이 남아있기 때문에, 마음의 준비를 어느정도 갖추도록 합시다. 여기서 크로스컴파일을 해줘야하기 때문에, 다음과 같이 필요한 C파일을 컴파일러로 컴파일하여 오브젝트 파일을 생성해줍니다.

$CC -march=armv8-a -mfpu=neon -DWITH_OPENSSL -fPIC -c onvifC.c $LDFLAGS $CFLAGS;

이제 운이 정말 좋다면 에러 없이 빌드가 되겠지만, 그만큼 운이 좋다면 빌드를 하기 전에 로또부터 사는게 좋을지도 모릅니다. 여기서 자주 빌드 에러가 발생하는 유형과 대응 요령을 살펴봅시다. 아래는 제가 onvifC.c를 빌드하는 과정에서 만난 에러입니다.

In file included from onvifH.h:16:0,
                 from onvifClient.c:18:
onvifStub.h:611:2: error: ‘open’ redeclared as different kind of symbol
  open = 1
  ^~~~
In file included from stdsoap2.h:883:0,
                 from onvifStub.h:45,
                 from onvifH.h:16,
                 from onvifClient.c:18:
/sysroot/usr/include/fcntl.h:180:12: note: previous declaration of ‘open’ was here
 extern int open (const char *__file, int __oflag, ...) __nonnull ((1));
            ^~~~
/* onvif.h:1025 */
#ifndef SOAP_TYPE_device__RelayIdleState
#define SOAP_TYPE_device__RelayIdleState (394)
/* tt:RelayIdleState */
enum device__RelayIdleState {
  closed = 0,
    open = 1
};
#endif
/* onvif.h:1025 */
#ifndef SOAP_TYPE_device__RelayIdleState
#define SOAP_TYPE_device__RelayIdleState (394)
/* tt:RelayIdleState */
enum device__RelayIdleState {
    device__RelayIdleState__closed = 0,
    device__RelayIdleState__open = 1
};
#endif
static const struct soap_code_map soap_codes_tt__RelayIdleState[] =
{    { (LONG64)closed, "closed" },
    { (LONG64)open, "open" },
    { 0, NULL }
};
static const struct soap_code_map soap_codes_tt__RelayIdleState[] =
{    { (LONG64)tt__RelayIdleState__closed, "closed" },
    { (LONG64)tt__RelayIdleState__open, "open" },
    { 0, NULL }
};

이런 식으로 필요한 기능에 해당하는 C 파일을 빌드해줍니다. 이 과정에서 diff를 통해 patch파일을 만들어놓으면 나중에 도움이 될 수도 있지만, gSOAP 버전 및 typemap.dat, 의존 관계에 있는 라이브러리, WSDL 파일의 수에 따라서 wsdl2h 실행 결과물이 달라지므로, gSOAP의 버전이 올라가거나 기능이 추가될 때는 의미가 없어질 가능성이 높습니다.

위의 과정을 반복해서 빌드가 완료되면 아래와 같이 오브젝트 파일을 묶어서 Shared library를 생성해줍니다.

$CC -march=armv8-a -mfpu=neon -shared -o libonvif.so -Wl,-whole-archive onvifC.o onvifClient.o duration.o mecevp.o smdevp.o wsaapi.o wsseapi.o md5evp.o struct_timeval.o -Wl,-no-whole-archive;

문제없이 Shared library가 만들어졌다면, 타겟 파일 시스템에 헤더 파일과 함께 복사해주면 마무리됩니다. 만약 prefix가 틀리거나 혹은 typemap.dat을 수정해야된다면, wsdl2h을 통한 헤더 파싱, soapcpp2를 사용한 stub file 추출, enum값 수정 후 빌드 과정을 다시 거쳐서 라이브러리를 재생성해야합니다.

이 과정이 끝나면 Onvif 라이브러리를 사용할 수 있게됩니다. 와아!

반응형