Subversion을 열심히 애용 중이던 나. KLDP의 “Perl도 git으로 전환…” 이란 글을 보고 Git란 것에 관심이 생기긴 시작했다. 리누즈가 만든 분산형 SCM 이란 이야기는 예전부터 들었으나 아직 사용해보지 않아 어떤지 잘 모르겠지만 Painless merging이 가능하다는 이야기에 매우 관심이 간다.
SVN으로 프로젝트 관리시에 branch한 트리에서의 Merge작업이나 한 트리의 작업내용을 다른 트리에 반영하기 꽤나 까다로웠던 것으로 생각되어 더 더욱 그렇다.
예전 과제시에 사용했던 Mercurial 역시 분산형 SCM으로 이 역시 많이 사용되는 것 같다. Subversion도 좋지만, 그 단점을 보완할 수 있는 툴이 있다면(TortoiseSVN과 같은 GUI Frontend가 존재한다는 것이 상당한 장점이지만) 역시 고려해보는 것이 좋겠다. Mercurial로 관리되던 소스 트리를 실제 과제시에는 익숙한 SVN을 사용했었기에 안타까웠던 마음도 이에 영향을 미치는 것 같다. ㅎㅎ
Mercurial에 관해서는 우리먈로 된 친절한 튜토리얼(Mercurial 사용 입문서)이 존재하고(과제시에 도움을 많이 받았다^^), Git에 관해서도 충분한 도움말이 있는 듯 보인다.
여러 SCM 들에 대한 간단한 설명과 사용법은 IBM DeveloperWorks의 리눅스 버전 컨트롤 (한글) 글이 도움이 될 것 같다. 말미에 Git 에 대한 사용법도 약간 나온다. 실제 사용을 위한 무언가를 준비해보아야 겠다. TortoiseSVN과 같은 GUI frontend가 있다면 더할 나위 없이 좋겠다. ^^
[카테고리:] Development
내가 Visual C++ 6를 아직도 쓰는 이유
“VC++ 6.0을 쓰지 말아야하는 이유(http://minjang.egloos.com/1783328)” 라는 글을 읽고나서도 나는 별로 VC++ 6를 버리고 싶은 생각이 들지 않는다.
난 VC++ 6가 더 손에 착착 붙으니까.
익숙하지 않은 툴을 쓸 때의 그 느낌은 몸에 맞지 않는 옷을 입은 것처럼 코딩 작업을 매우 불편하게 한다. 물론 그 이후 버전에 익숙하지 않은 것은 내 천성적인 게으름 탓일 수도 있을거다. 흐흐흐..
그러나 MFC Application을 만들 때 굳이 VC.NET 이후 버전을 써야 할 이유를 느끼지 못한 것도 한 이유다. 그렇다고 그 이후 버전을 써야만 만들 수 있는 프로그램을 만드는 것도 아니었기 때문에.. 굳이 새버전이라는 이유로 내 손에 익은 단축키와 툴들을 버리고 굳이 옮기고 싶은 마음은 들지 않는다.
게다가, “VC++ 6.0을 쓰지 말아야하는 이유” 에서 쓰고 있는 이유들, 대부분이 아직 잘 와닿지 않는다.
“1. 보다 안전한 프로그래밍”에서의 _s버전의 함수를 쓰라는 건 나보고 특정 플랫폼의 함수에 종속되란 말로 들리고, 특히나 strcpy 같은 함수를 잘 사용하지 않는 나로서는 특별히 신경쓰이지 않는다. strcpy의 버퍼오버 플로우 같은 문제는 해묵은 문제이고 이와 관련된 이야기는 많이 들은 문제 아닌가?;
“2. 유니코드 프로그래밍” 같은 문제는 사실 프로그래머의 문제지 툴의 문제가 아니라고 생각한다.
“3. 보다 뛰어난 인텔리센스”는 VC++ 6의 인텔리센스 기능에도 만족해 했던 나로서 별로..;; Visual Assist를 써도 좋고..
“4. STL의 (거의) 완벽한 지원” 의 이유는 좀 써볼만하다고 생각하긴 한다.
“5. 뛰어난 IDE 매크로 사용”은 별다른 매크로를 사용하지 않아서인지도 모르겠다. 글에 나왔던 .cpp와 .h를 연결해주는 매크로 같은건 VC++ 6에서도 만들어쓴다. 나 같은 경우는 매크로를 쓰다가 Wndtabs에 아예 내장기능으로 들어가 있는 걸 보고 그 기능을 사용하는 중이다.
“6. 더 훌륭해진 컴파일러”는 약간 수긍. 그러나 더 빠른 컴파일러가 필요할 정도로 크리티컬한 상황을 MFC App.를 짜면서는 별로 느껴보지 못했다. 빠른 컴파일러가 필요하다면 인텔 컴파일러가 최고 아닐까?
“7. 더 괜찮아진 IDE” 는 개인차일 수 있으나, 난 VC++ 6의 IDE가 더 맘에 든다. 예전, C++ Builder의 IDE가 나중에 VC.NET의 인터페이스와 비슷해진 걸 써보고, “으.. 난 전꺼가 더 좋은데…” 한탄했던 기억이.. 게다가 클래스 위저드의 기능을 사용할 수 있다고 하나, “클래스 위저드를 사용하는 것”과 “클래스 위저드의 기능이 있는 것” 과는 매우 다르다.
“8. 멀티 코어의 사용”은 내가 아직 대규모 프로젝트에 투입되지 않아봐서 그런걸까.. 하지만, 컴파일 시간이 그만큼 길다는 것은 그 프로젝트의 구성 자체가 문제일 가능성이 더 높다.
“9. MFC/ATL의 변화” 는 MFC의 클래스들을 MFC 이외에서 사용하고 싶은 욕심이 없어서일까..;;
써야 하는 이유의 대두분이 나한테는 아직 써야하는 이유로 다가오지 않으므로, 고로, 그래서, 나는 아직 VC++ 6를 쓸란다.
그리고, 또 다른 이유는 “내가 좀 많이 게으르다.” 라는 것이다. 흐흐….
..라고는 하지만 이렇게 결론내기 좀 부끄러워 몇마디 더 하자면, 툴은 익숙한 툴이 좋고, 상황에 맞는 툴을 골라서 사용할 줄 아는 것이 현명하다는 것이다. 그리고 현재 내 상황은 아직 VC++ 6이 가장 적합한 툴이다. 흐흐..
재밌는 블로그를 발견했다!~ art.oriented(http://minjang.egloos.com/)
우연히 멤버십 인트라넷을 보다가 다른 지역 게시판엔 뭐가 있을까 하고 보던 중에 대구 멤버십의 자유게시판에서 재밌는 글을 발견하고는 그 링크를 따라갔더니 재밌는 블로그가 나왔다. art.oriented(http://minjang.egloos.com/)
맨 처음 본 글은 “C로도 얼마든지 객체
지향이 가능합니다.“이다. 보던 중 정말 srand, rand가 전역변수를 쓰나? 나라면 static으로 scope를 줄텐데 하면서 glibc도 까보기도 하고, 글 내에 링크된 스레드 관련 부분에 관한 내용에 고개를 끄덕이기도 하고 하면서 재밌게 읽었다.
또, 컴퓨터 라는 분류에 재밌는 글들이 많은데 “gcc STL map iterator의 버그(?)” 라는 글에서는 왜 이 사람이 map을 iterator로 순회하면서 begin() 부터 –연산자를 써서 코딩하는 걸까 생각해보게 되었다. 저렇게 써도 되는건가? 그리고 저런 식의 코드를 만들어야 하는 상황이 생길까? 그런 상황은 무엇일까? 저렇게 쓰면 안되는거 아닌가? map을 쓰는 데 있어 저런 방식은 비정상적인거 같은데… map 자체가 저렇게 쓰라고 만든게 아닐텐데.. 등등..
다른 글들도 읽어보면서 얼마간은 좋은 생각거리들을 만들어 줄 수 있을 듯 하다.
Visual C++ 6 SP6에서의 명시적 형변환을 믿지마세요(?)
using namespace::std;
int main(void)
{
float f = 1234567890;
int i = f;
int i2 = float(1234567890);
cout << i << endl;
cout << i2 << endl;
return 0;
}
위 코드에서 i와 i2가 다를까요? 네. 다릅니다. Visual C++ 6를 쓰신다면요..;;
발견하게 된 계기는 다음과 같습니다.
“The C++ Programming Language” 의 C.6.2.6 부동소수점 실수-정수 변환 절에는 다음과 같은 절이 나옵니다.
반대로, 정수를 부동소수점 실수 타입으로 바꾸는 것은 하드웨어가 허용하는 한 수학적으로 정확히 보장된다. 단, 정밀도가 손실되는 경우는 주어진 정수를 해당 부동소수점 실수 타입으로 정확히 표현할 수 없을 때이다.
int i = float(1234567890);
int와 float를 32비트로 처리하는 컴퓨터에서는 i에 1234567936이 들어간다.
제 컴퓨터는 int와 float를 32비트로 처리합니다. 따라서 같은 결과를 예상하고 다음과 같은 코드를 작성합니다.
using namespace::std;
int main(void)
{
int i = float(1234567890);
cout << i << endl;
return 0;
}
결과는?
허걱! 뭐야..;; float 형을 32비트로 처리 안하나? sizeof 로 확인해보니 4 랍니다. 그럽 32비트 맞겠죠? float형 정밀도가 저 범위를 모두 처리할 수 있나? 1비트 사인비트에 8비트 지수, 23비트로 mantisa를 처리하니.. 안될텐데;; 1234567890 은 2진수로 “1001001100101100000001011010010” 니까 31비트인데..;; 뭐지..;; 책 내용은 맞는 것 같은데… 까지 생각하고 혹시 컴파일러 문제인가? 하고 2005를 띄웁니다.
같은 코드에 결과는 책과 같은 결과가 나옵니다.
으음.. 그럼 얘는 뭘까 형변환을 명시적으로 안하는 건가 라고 생각하고 그냥 float 형에 넣어봅니다.
using namespace::std;
int main(void)
{
float f = 1234567890;
int i = f;
cout << i << endl;
return 0;
}
이제서야 책과 같은 결과가 나옵니다. 헐……….. 뭘까요… 얘…..
STL 쓰니 편하긴 하다.. 그렇지만..
컴퓨터비전 숙제로 Component와 Boundary를 구하는 프로그램을 짰다.
Equivalent Table에서 동등한 값들 중 가장 작은 값을 찾을 때, set 써서 동등한 거 보이는 데로 insert해버리고..
set이 내부적으로 정렬해주니.. 그냥 맨 앞에 값 불러다 쓰면 되고.. 참 편하긴 하다..
그치만 set이 내부적으로 정렬해주는 것 때문에 추가된 것만 따로 검색하지 않고 다시 앞에 있는 것부터 테이블을 검색해서 다시 추가해주도록 했으니 여기서는 시간이 마이너스.
그리고 생각해보면 Equivalent table을 pair의 vector처럼 썼으니.. 결국 multimap쓰면 될 걸 링크드리스트에 직접 때려넣어서 순차검색하도록 구현했으니 여기는 구현면에서 마이너스.
게다가 multimap쓰면 table안의 label에 대해서 정렬되니까 안에 이미 있는지 찾는 것도 더 빠를텐데.. 그러므로 시간상으로도 마이너스.
음.. 그치만 오늘은 고치기 싫으므로 또 마이너스..-_-
<아래부터 추가>
multimap 쓰는 것으로 수정. 간단하다. multimap 안에 있는지 검색을 리스트를 순차검색하던 것에서 equal_range를 쓰므로 내부에서 더 빨리 찾을 것으로 생각한다.
근
데 과연 equivalent table 같은 걸 multimap 식으로 생각해도 될까. multimap 은 “키”에 연관하므로,
equilvalent 짝 중에서 한가지가 키가 됨을 강제한다. 둘은 동등하므로 어떤 것이 키가 될 지 사실 정할 수 없다. 난
짝 중에서 큰 값을 키로 잡았지만.. 작은 값을 키로 잡으면 작은 값이 반복되므로(connectedness를 찾는 과정에서
euilvalent table의 작은 label은 동등한 것들이 계속 나올테니까) 내부에서 트리를 이용한다면, 잘 분포되지 않을
거라는 가정에서.. 잘 분포된 트리가 검색시 더 빠를테니까..
트리로 구성되어 있을 거라는 가정은 Bjarne
Stroustrup의 “The C++ Programming Language(C++ 프로그래밍 언어)” 특별판의 17.4.1.1의
“대개 map은 형태는 조금씩 다를지언정 거의 다 트리로 구현되기 때문에, 반복자 역시 트리 횡단으로 원소 집합을 돌아다닌다.”
라는 구절(한글판 기준)에서 가정한다. 실제 확인은? ..글쎄;;
equivalent table에서 label을
찾을 때는 짝 중 아무값이나 맞으면 그 pair의 다른 짝을 equivalent set에 넣어주어야 한다. 양 쪽 둘다 중에
아무거나 맞는 것을 찾아야 하므로 결국 equivalent table을 모두 검색하도록 만들었는데, 이 걸 어떻게 더 효율적으로
할 수 있는 방법 없으려나.. 항상 모든 걸 다 찾아야 할까…
프로그래밍 심리학 – 2부 사회 활동으로 보는 프로그래밍
2부 사회 활동으로 보는 프로그래밍
프로그래머는 보통 고립된 상태에서 일하지 않는다. … 그러나 다른 프로그래머와 함께 일할 때에도 그들 간에는 다양한 관계가 존재할 수 있다. 그 관계를 연구하고자 우리는 프로그래머의 집단을 그룹과 팀, 프로젝트, 이렇게 세가지로 구별할 것이다. 간략하게 정의하면, 그룹은 같은 장소에서 일하는 프로그래머 집단이다. … 반면에 프로그래밍 팀은 한 프로그램을 개발하기 위해 함께 일하는 프로그래머 집단이다. … 프로젝트는 프로그래머 그룹에 하나로 통합된 시스템(또는 적어도 긴밀하게 짜인 프로그램들)을 만들어 내는 지원 활동을 더한 것이다. 프로젝트에는 보통 전용 기계가 있고, 더불어 시스템 작업, 표준화, 문서화 등의 부차 기능을 담당하는 특별 팀들이 포함된다. 그리고 보통 프로젝트 관리자와 관료주의적인 정규 조직도(흡사 군대 같은) 추가된다.
2부에 보태는 글:
이제는 프로그래밍 팀이란 더 나은 제품들을 만들기 위해 함께 일하는 프로그래머들의 모임이라 하겠다. 달리 말하자면, 팀은 제품을 만들기 위해 구성원들이 함께 일한다는 점에서 그룹과 차별된다(이때 제품이란 프로그래머가 각자 따로 일해서는 만들 수 없거나 또는 팀을 이룰 때만큼 효율적으로 만들 수 없는 수준의 것을 의미한다). 어떤 그룹이 제품 하나를 개발하는 데 참여한다고 해서 팀이라 볼 수 있는 것은 아니다. 또, 구성원 각자가 별도의 제품을 만든다 해도(흔한 경우는 아니지만) 팀은 팀인 반면에, 그룹은 같은 장소에서 일하며 같은 관리자 밑에 있다는 점만 제외하고는 구성원들이 서로 공유하는 바가 없다.
그러나 결국 팀과 그룹의 차별성은 구성원들의 배우는 방식에 있다. … 팀에게는 항상 공통의 목표 즉, 구성원이 서로 가르치고 배워 각자 더 나은 능력을 가질 수 있도록 한다는 목표가 있다. 제품과는 상관없이 말이다.
“그렇다면 기업에서 제품 개발을 위해 그룹보다는 팀을 만들도록 돕는 데 이 기준이 쓰일 수 있을까요? 그리고 무엇을 배우게 될까요? 더 나은 프로그램을 만드는 방법? 더 잘 듣는 방법? 의사소통을 더 잘하는 방법? 자신에 대해 더 좋은 감정을 느끼는 방법? 저는 이 질문들에 관한 답이 모두 ‘네’라고 생각합니다.
제가 고객들에게 팀의 일원으로 가장 좋았던 경험을 물을 때 가장 자주 듣는 대답은 가족 같았다는 말입니다. 추수감사절 즈음의 가족 말이죠. 모두 뭔가를 만들어 식탁에 내어 놓습니다. 그리고 서로 나누며 축하하죠. 저는 건전한 팀에게 있는 또 하나의 특징은 스스로 영속시키는 데 있다고 봅니다. 그런 팀의 구성원 중 하나가 또 다른 팀을 만들어 원래 팀에서 배웠던 가치와 관습을 이어가는 것이죠.”
-> 가족같은 팀. 스스로 영속되고 싶어하는 팀. 내가 만들 팀!
Crosstool을 이용한 Softfloat EABI 툴체인 빌드 방법
안드로이드에서 구동될 커널이나 C/C++ 애플리케이션, 라이브러리 등은 ARM용 EABI와 soft-float을 지원하는 툴체인으로 빌드되어야 합니다. 현재 많이 쓰이는 안드로이드 포팅이나 라이브러리 빌드를 위해 사용하는 툴체인을 실제 구글링해보시면 대부분 Codesourcery 사의 툴체인을 사용 중인 것 같은데요. 저 역시, 처음에는 Codesourcery 사의 2008q3 버전의 툴체인(arm-none-linux-gnueabi-*)을 사용했었습니다. 그런데, 그 툴체인으로 커널을 빌드하니까, 왠 일인지 이미지만 3GB인지, 300MB인지가 나와서 실제 부팅 중 Uncompressing Linux…………….. 메시지에서 재부팅되거나 멈춰버리는 것이었습니다.
그래서, 자체적으로 툴체인 빌드를 하기 위한 방법을 찾아보았습니다. KLDP 위키의 Android Porting On Real Target 에서 역시 툴체인 빌드를 해서 썼다고 되어 있기 때문이기도 하구요. 일단은 KLDP 위키의 방법과 같은 방법을 사용하기로 하고(저 역시, “To make life easier,”) 같은 방법인 crosstool과 EABI patch를 이용하기로 했습니다. 다음은 그 방법을 설명합니다.
일단, http://www.kegel.com/crosstool/ 에서 crosstool 0.43을 다운로드 받고 압축을 해제합니다.
crosstool의 사용법은 http://www.kegel.com/crosstool/current/doc/crosstool-howto.html 에서 설명하고 있습니다. 약간 설명을 드리자면, 빌드하고자 하는 툴체인의 .sh 파일 안의 TARBALLS_DIR, RESULT_TOP, GCC_LANGUAGES 를 수정해야 하는데, TARBALLS_DIR은 crosstool이 받아올 Source tarball을 저장할 디렉토리이고, RESULT_TOP은 빌드된 최종 툴체인이 저장될 디렉토리의 탑 디렉토리(이 디렉토리는 만들어져 있어야 하고, 빌드하는 유저에게 쓰기 권한이 있어야 합니다.), GCC_LANGUAGES 는 빌드할 툴체인에서 지원할 언어에 대한 것입니다. 파일을 열어보시면 금방 아실 수 있을 겁니다.
자, 이제 soft-float eabi 패치를 구하여야 합니다. KLDP 위키 글을 따라가보면, Khem Raj의 a glibc 2.5+ nptl build for arm softfloat eabi patch를 적용했다고 나와 있습니다. 링크된 글의 본문에 나와있는 패치를 파일로 저장한 후 아까 받았던 crosstool에 적용시킵니다. 패치를 적용한 후 빌드를 수행하면, 잘 되지 않습니다. follow된 글을 따라가보면 스크립트 안의 "-nounpack" 옵션을 제거하고 다시 해보란 코멘트를 찾아볼 수 있습니다.
그럼 -nounpack을 제거하고 다시 시도해 봅시다. 그래도 잘 안될텐데, 기억에는 아마도 arm-softfloat.dat 파일 안의 “KERNELCONFIG=`pwd`/arm.config” 내용을 그대로 복사해서 arm-softfloat-eabi.dat 파일 안에 넣어주었던 것 같습니다. 그리고 나서도 저는 안되었는데, 그 이유는 스크립트 내부에서 사용되는 awk를 gawk가 아닌 mawk를 사용해서 발생한 문제였습니다. 우분투 8.04에서 기본으로 깔린 awk가 mawk더군요(제가 진행한 작업은 모두 우분투 8.04 하에서 진행되었습니다.). mawk를 사용하고 싶다면, http://sourceware.org/ml/crossgcc/2007-07/msg00029.html 을 참조하여 패치한 후 진행합니다. 그냥 귀찮다면, 패치 제작자가 권장하는 gawk를 사용하시구요. 우분투에서 기본 awk를 gawk로 바꾸는 방법은 gawk를 내려받아 설치한 후(sudo apt-get install gawk), 기본 awk를 gawk로 설정하시면(sudo update-alternative –config awk:확실하지 않습니다.;;) 될 겁니다.
아무튼, 이제 빌드하면(당연히 빌드 이전에 demo-arm-softfloat-eabi.sh 파일 안의 세 변수는 적절히 설정되어 있어야 합니다.) 설정되어 있던 디렉토리에 툴체인이 빌드되어 있는 것을 보실 수 있습니다.
KLDP의 원문을 곱씹으며 읽어볼겸, 영어공부도 할겸, 영어에 익숙하지 않은 개발자를 위해서겸, 겸사겸사, 번역해 보았습니다. 허접한 번역이지만 보실 분은 http://wiki.dasomoli.org/wiki.php/AndroidPortingOnRealTarget/ko 를 보시면 됩니다(우리나라 사람이 쓴 원문을 번역한다는 것이 참 아이러니합니다만..).
안드로이드 올렸습니다. 흐흐..
자세한 건 나중에 정리해서 올리겠습니다. 흐흐…
일단 되는 것부터 보시죠. ㅋㅋ
C++ library를 안드로이드에서 돌리기 위한 삽질
요즘 C/C++ 로 된 OpenCV 라이브러리를 안드로이드 플랫폼 안에 포팅해서 자바 라이브러리로 만드는 과정 중인데.. 순수 C App는 안드로이드 내부의 라이브러리로 빌드/실행이 가능한데 C++ 쪽은 문제를 겪고 있다.
내가 생각한 해결 방법은 그냥 GNU C++ library를 통째로 안드로이드 안으로 이식하려고 생각한 것이었다.
그러나 나같은 삽질을 한 사람이 또 있겠지 싶은 마음에..
여기저기 뒤져보던 중 구글의 Andorid Interanls 그룹에서 여러가지 포스팅 들을 찾을 수 있었는데,
글 들 중 가장 최신의 글은 삽질하지 말고 기다려라. 라는 것이다-_-
좌절이다.. OTL…
그러나 그 이전인 2월의 글 들 중 C++ Application 을 포팅하는데 성공했다는 사람(Susmith M R)이 있다.
이 포스팅(Dynamic Shared Library in Android Another Approach – Don’t Use android Linker)에서 찾을 수 있는데, 안드로이드 내부의 링커(/system/lib/linker)를 사용하지 말라는 이야기이다.
gcc의 옵션으로 -dynamic-linker=/system/lib/linker 대신,
링커 파일(ld-linux-so.6)을 넣고 –dynamic-linker=/system/lib/ld-linux-so.6 옵션으로 컴파일 한다는 건데.
링커 자체가 바뀐다면 추측상으론 DATA 섹션에서 RO 영역과 RW 영역 구분을 없앴던 부분도 수행하지 않고(바꿔 말하면 armelf_linux_eabi.xsc 를 사용하지 않고) 사용할 수 있을 듯도 하다. 음.. 잉? 아닌가? 음.. 생각해봐야겠다..
한번 해봐야겠다.
참고 :
Problem loading C++ library in android
Need little help for JNI
Dynamic Shared Library in Android Another Approach – Don’t Use android Linker
OpenCV의 안드로이드 포팅을 위한 첫단계!
OpenCV의 리눅스 소스를 안드로이드에 포팅하기 위해서 cvCreateImage와 cvReleaseImage 함수가 들어있는 cxcore 라이브러리의 빌드에 들어갔다.
소스의 루트디렉토리에서 ./configure 의 옵션을 조정하여 빌드하는 방법에 실패하고,
직접 cxcore/src 디렉토리 안에서 makefile을 작성하여 빌드하였다.
다음은 빌드할 때 사용한 makefile이다.
CC = arm-none-linux-gnueabi-gcc
LD = arm-none-linux-gnueabi-ld
INC =
LIBS =
CFLAGS = -I/usr/lib/jvm/java-6-sun/include -I/usr/lib/jvm/java-6-sun/include/linux -I../include -fpic -c
#LDFLAGS = -shared -T armelf_linux_eabi.xsc –dynamic-linker /system/bin/linker -nostdlib -rpath /system/lib -L/home/dasomoli/lib/arm-none-linux-eabi/ -lcv -lcxcore -lml
LDFLAGS = -shared -nostdlib -T /home/dasomoli/src/opencv_bak/opencv-1.0.0/armelf_linux_eabi.xsc -rpath /system/lib -rpath . -L . -L/home/dasomoli/androidinternal/system/lib -lc -lm -lstdc++
OBJS = cxalloc.o cxjacobieigens.o cxpersistence.o cxarithm.o cxlogic.o cxprecomp.o cxarray.o cxlut.o cxrand.o cxcmp.o cxmathfuncs.o cxsumpixels.o cxconvert.o cxmatmul.o cxsvd.o cxcopy.o cxmatrix.o cxswitcher.o cxdatastructs.o cxmean.o cxtables.o cxdrawing.o cxmeansdv.o cxutils.o cxdxt.o cxminmaxloc.o dummy.o cxerror.o cxnorm.o cximage.o cxouttext.o
SRCS = cxalloc.cpp cxjacobieigens.cpp cxpersistence.cpp cxarithm.cpp cxlogic.cpp cxprecomp.cpp cxarray.cpp cxlut.cpp cxrand.cpp cxcmp.cpp cxmathfuncs.cpp cxsumpixels.cpp cxconvert.cpp cxmatmul.cpp cxsvd.cpp cxcopy.cpp cxmatrix.cpp cxswitcher.cpp cxdatastructs.cpp cxmean.cpp cxtables.cpp cxdrawing.cpp cxmeansdv.cpp cxutils.cpp cxdxt.cpp cxminmaxloc.cpp dummy.cpp cxerror.cpp cxnorm.cpp cximage.cpp cxouttext.cpp
TARGET = libcxcore.so
all : $(TARGET)
$(TARGET) : $(OBJS)
$(LD) $(LDFLAGS) -o $@ $(OBJS) $(LIBS)
$(OBJS) : $(SRCS)
$(CC) $(CFLAGS) $(SRCS)
dep :
gccmakedep $(INC) $(SRCS)
clean :
rm -rf $(OBJS) $(TARGET) core
new :
$(MAKE) clean
$(MAKE)
CFLAGS의 옵션 중 -I/usr/lib/jvm/java-6-sun/include와 -I/usr/lib/jvm/java-6-sun/include/linux 옵션은 구글 안드로이드 Application 개발시 사용한 java 6 라이브러리의 include 경로이고, ../include 는 실제 $OPENCV_HOME/cxcore/include 이다. -c 옵션으로 *.o 파일을 생성한다.
물론, 이전에 javah로 구글 안드로이드 Application의 JNI를 사용하는 클래스의 C/C++용 헤더 파일(org_swssm_NativeOpenCV.h)이 생성되어져 있었고, 헤더 파일 안의 함수는 cxarray.cpp 함수 안에 구현되어져 있었다. 당연히 cxarray.cpp 에는 #include “org_swssm_NativeOpenCV.h” 가 들어가 있다. cpp 안의 함수 안에서 클래스를 env->FindClass 로 찾을 때 org.swssm.andCvSize 와 같이 클래스의 전체 경로를 적어주어야 한다는 점에 주의하자.
LFLAGS의 옵션 중 -T /home/dasomoli/src/opencv_bak/opencv-1.0.0/armelf_linux_eabi.xsc는 이전에 언급했던 툴체인 내의 ldscript의 수정본이다. 또한, -nostdlib 는 툴체인의 기본 라이브러리를 사용하지 않겠다는 옵션으로 보인다. 또, -rpath /system/lib -rpath . 는 라이브러리 실행 시 참조할 라이브러리 경로로써 안드로이드의 라이브러리 폴더인 /system/lib와 라이브러리가 들어가 있는 곳의 경로를 참조하도록 한다. -L . -L/home/dasomoli/androidinternal/system/lib 는 링킹 타임에 참조할 라이브러리가 들어가 있는 경로로 /home/androidinternal/ 아래에 Benno의 System image 가 압축 해제되어 있다. 이는 안드로이드에서 실제 사용되는 library를 링킹 시 참조하여 링킹하도록 한다. -lc -lm -lstdc++ 는 각각 안드로이드 /system/lib 의 libc.so, libm.so, libstdc++.so 를 참조하도록 한다.
make가 성공적으로 이루어지기 위해서는 OpenCV의 cxcore 라이브러리 내의 소스들에 약간의 수정이 필요한데, 이는 안드로이드 내부에서 사용되는 라이브러리들에서 지원되지 않는 함수(혹은, 내가 제대로 링크시키지 못했거나..;;)들을 제거해야 하기 때문이다. 대표적인 함수들로 assert 함수가 있으므로 include안의 #include <assert.h>를 주석처리 하고, cxtypes.h 내의 assert 문들이 사용된 부분을 삭제하도록 한다. 추가적으로 #define assert(x) (x) 하여 bool 처리만 하도록 처리하기도 하였다.
make clean; make를 마치면 libcxcore.so 파일이 생성되는데 이를 안드로이드 에뮬레이터의 /system/lib 안에 넣어주도록 한다.(adb push libcxcore.so /system/lib/)
안드로이드 플랫폼 내부에서의 libcxcore.so 파일의 동작여부를 확인하기 위해서 별도의 간단한 application을 build하여 테스트하도록 한다. 먼저 main.c 를 작성한다.
#include “cxcore.h”
int main(void)
{
IplImage *pImage;
CvSize size;
size.width = 100;
size.height = 100;
pImage = cvCreateImage(size, 8, 3);
cvReleaseImage(&pImage);
return 0;
}
cvCreateImage와 cvReleaseImage 함수만을 사용하여 동작하는 지를 살펴본다.
main.c의 구동을 위해서 함수의 entry point가 되는 _start 함수를 구현한다. 이는 http://honeypod.blogspot.com/2007/12/dynamically-linked-hello-world-for.html 에서 사용한 start.c 파일을 그대로 사용하였다.(http://honeypod.blogspot.com/2007/12/initialize-libc-for-android.html의 _start 함수를 어셈블러로 구현하여 사용할 수도 있을 것 같다.)
extern int main(int argc, char **argv);
void _start(int argc, char **argv)
{
exit (main (argc, argv));
}
기억상으로는 start.o 는 arm-none-linux-gnueabi-gcc -c start.c 하여 그대로 사용했던 듯 하다.
main.o 는 arm-none-linux-gnueabi-gcc -c -I../include main.c 와 같이 하여 cxcore의 include 경로를 추가한 후 컴파일 하여 생성한다.
중요한 cxcoretest라는 간단한 Executable 생성은 다음의 옵션으로 한다.
shared library 생성시와는 다르게 armelf_linux_eabi.xsc 를 사용하지 않음에 주의한다. 이를 안드로이드 에뮬레이터에 넣고(adb push cxcoretest /data/) 실행해본다.
WARNING: `libcxcore.so` is not a prelinked library
#
실행은 잘 되는 것을 볼 수 있다. 근데, 제대로 호출된 것 맞는건가? 확인해보자. Benno의 strace를 받아 안드로이드에 넣고(adb push strace /data/), strace로 실행(./strace ./cxcoretest)해보자!
getpid() = 1662
syscall_983045(0xb0015cb0, 0xb00128d8, 0x3d4, 0, 0xbeddddc8, 0x1, 0, 0xf0005, 0xb00128d8, 0, 0, 0xbeddddc4, 0, 0xbedddd78, 0xb0000dd9, 0xb00016fc, 0x10, 0xb0015cb0, 0, 0, 0xc764, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) = 0
gettid() = 1662
sigaction(SIGILL, {0xb0001471, [], SA_RESTART}, {SIG_DFL}, 0) = 0
sigaction(SIGABRT, {0xb0001471, [], SA_RESTART}, {SIG_DFL}, 0) = 0
sigaction(SIGBUS, {0xb0001471, [], SA_RESTART}, {SIG_DFL}, 0) = 0
sigaction(SIGFPE, {0xb0001471, [], SA_RESTART}, {SIG_DFL}, 0) = 0
sigaction(SIGSEGV, {0xb0001471, [], SA_RESTART}, {SIG_DFL}, 0) = 0
sigaction(SIGSTKFLT, {0xb0001471, [], SA_RESTART}, {SIG_DFL}, 0) = 0
open(“libc.so”, O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open(“/system/lib/libc.so”, O_RDONLY|O_LARGEFILE) = 3
lseek(3, -8, SEEK_END) = 241860
read(3, “\0\0\340\257PRE “, 8) = 8
mmap2(0xafe00000, 245760, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE, 3, 0) = 0xafe00000
close(3) = 0
mmap2(0xafe3c000, 45056, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0) = 0xafe3c000
mprotect(0xafe00000, 233472, PROT_READ|PROT_EXEC) = 0
open(“libcxcore.so”, O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open(“/system/lib/libcxcore.so”, O_RDONLY|O_LARGEFILE) = 3
lseek(3, -8, SEEK_END) = 2199920
read(3, “veSlice\0”, 8) = 8
fstat64(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 0), …}) = 0
brk(0) = 0x13000
brk(0x13000) = 0x13000
brk(0x14000) = 0x14000
ioctl(1, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo …}) = 0
write(1, “WARNING: `libcxcore.so` is not a”…, 51WARNING: `libcxcore.so` is not a prelinked library
) = 51
lseek(3, 0, SEEK_END) = 2199928
mmap2(0x80100000, 2203648, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE, 3, 0) = 0x80100000
close(3) = 0
open(“libm.so”, O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open(“/system/lib/libm.so”, O_RDONLY|O_LARGEFILE) = 3
lseek(3, -8, SEEK_END) = 133184
read(3, “\0\0\300\257PRE “, 8) = 8
mmap2(0xafc00000, 135168, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE, 3, 0) = 0xafc00000
close(3) = 0
mprotect(0xafc00000, 131072, PROT_READ|PROT_EXEC) = 0
open(“libstdc++.so”, O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open(“/system/lib/libstdc++.so”, O_RDONLY|O_LARGEFILE) = 3
lseek(3, -8, SEEK_END) = 4144
read(3, “\0\0\320\257PRE “, 8) = 8
mmap2(0xafd00000, 8192, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE, 3, 0) = 0xafd00000
close(3) = 0
mprotect(0x80100000, 1986560, PROT_READ|PROT_EXEC) = 0
brk(0) = 0x14000
brk(0x14000) = 0x14000
brk(0x15000) = 0x15000
brk(0x16000) = 0x16000
mprotect(0, 0, PROT_READ|PROT_EXEC) = 0
brk(0x1d000) = 0x1d000
exit_group(0) = ?
Process 1662 detached
음… 봐도 잘 모르겠다.. 잘 안되는 것 같기도 하고… -_-;;;
흥미로운 점은, 빨간 색으로 표시한 부분을 보면 알겠지만, 사용된 안드로이드에서 제공하는 모든 library의 마지막 4 바이트에는 “PRE “가 들어가있다.
일단 JNI를 사용하는 Application을 사용해서 죽는지부터 보자-_-;;;
Call 버튼을 눌러보자!
오! 안죽는다! 안죽는게 뭐가 대단하냐고? ..당신은 삽질을 덜 한 것이다-_-+
그럼 cvCreateImage가 리턴하는 포인터의 값을 int로 찍어보자. 일단 값을 1234로 넣어두고 리턴하는 값을 받은 후에 찍어보자. 이왕 하는 김에 Object로 리턴하는 andCvSize 클래스의 값도 찍어보자!
Log.i(LOG_TAG, “pImage = ” + pImage);
pImage = NativeOpenCV.andcvCreateImage(cvSize, 8, 3);
Log.i(LOG_TAG, “after call, pImage = ” + pImage);
andCvSize cvSize2 = NativeOpenCV.andcvGetSize(pImage);
Log.i(LOG_TAG, “cvSize2.width : ” + cvSize2.width + “, cvSize2.height : ” + cvSize2.height);
NativeOpenCV.andcvReleaseImage(pImage);
adb logcat
D/dalvikvm( 1830): LOADING path /system/lib/libcxcore.so 0x4006ddb0
I/dalvikvm( 1830): Added shared lib /system/lib/libcxcore.so 0x4006ddb0
I/dalvikvm( 1830): No JNI_OnLoad found in /system/lib/libcxcore.so 0x4006ddb0
D/dalvikvm( 1830): +++ not scanning ‘/system/lib/libwebcore.so’ for ‘andcvCreateImage’ (wrong CL)
D/dalvikvm( 1830): +++ not scanning ‘/system/lib/libmedia_jni.so’ for ‘andcvCreateImage’ (wrong CL)
I/CallOpenCV( 1830): after call, pImage = 1459360
D/dalvikvm( 1830): +++ not scanning ‘/system/lib/libwebcore.so’ for ‘andcvGetSize’ (wrong CL)
D/dalvikvm( 1830): +++ not scanning ‘/system/lib/libmedia_jni.so’ for ‘andcvGetSize’ (wrong CL)
I/CallOpenCV( 1830): cvSize2.width : 100, cvSize2.height : 100
D/dalvikvm( 1830): +++ not scanning ‘/system/lib/libwebcore.so’ for ‘andcvReleaseImage’ (wrong CL)
D/dalvikvm( 1830): +++ not scanning ‘/system/lib/libmedia_jni.so’ for ‘andcvReleaseImage’ (wrong CL)
오오! 잘된다! 한번 더!
I/CallOpenCV( 1830): calling native method
I/CallOpenCV( 1830): pImage = 1234
I/CallOpenCV( 1830): after call, pImage = 1459360
I/CallOpenCV( 1830): cvSize2.width : 100, cvSize2.height : 100
잉? 포인터 값이 같네? 제대로 된 거 맞나? Release했던 부분을 놔뒀다가 다시 사용하나? cvReleaseImage를 주석처리 하고 계속 할당하는지 보자!
D/dalvikvm( 1889): LOADING path /system/lib/libcxcore.so 0x40060948
I/dalvikvm( 1889): Added shared lib /system/lib/libcxcore.so 0x40060948
I/dalvikvm( 1889): No JNI_OnLoad found in /system/lib/libcxcore.so 0x40060948
D/dalvikvm( 1889): +++ not scanning ‘/system/lib/libwebcore.so’ for ‘andcvCreateImage’ (wrong CL)
D/dalvikvm( 1889): +++ not scanning ‘/system/lib/libmedia_jni.so’ for ‘andcvCreateImage’ (wrong CL)
I/CallOpenCV( 1889): after call, pImage = 1460544
D/dalvikvm( 1889): +++ not scanning ‘/system/lib/libwebcore.so’ for ‘andcvGetSize’ (wrong CL)
D/dalvikvm( 1889): +++ not scanning ‘/system/lib/libmedia_jni.so’ for ‘andcvGetSize’ (wrong CL)
I/CallOpenCV( 1889): cvSize2.width : 100, cvSize2.height : 100
I/CallOpenCV( 1889): onClick
I/CallOpenCV( 1889): calling native method
I/CallOpenCV( 1889): pImage = 1234
I/CallOpenCV( 1889): after call, pImage = 1490752
I/CallOpenCV( 1889): cvSize2.width : 100, cvSize2.height : 100
후후후.. 포인터 값이 계속 바뀌는군.. 그럼 계속 할당되는 게 맞겠지 ㅋㅋㅋ
그럼 성공!!! 으하하하하하하!
참고 :
http://groups.google.com/group/android-developers/browse_thread/thread/b3376922f5b7a93a
http://groups.google.co.jp/group/android-developers/browse_thread/thread/ed437a61e678aab8
http://benno.id.au/blog/2007/11/13/android-native-apps
http://honeypod.blogspot.com/2007/12/shared-library-hello-world-for-android.html
http://honeypod.blogspot.com/2007/12/dynamically-linked-hello-world-for.html
http://benno.id.au/blog/
http://honeypod.blogspot.com/2007/12/initialize-libc-for-android.html
http://davanum.wordpress.com/2007/12/09/android-invoke-jni-based-methods-bridging-cc-and-java/
http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/jniTOC.html