반응형


 

출처 : http://www.linuxlab.co.kr/docs/98-11-2.htm

 

공유 라이브러리 만들기

    번역 : 이성주/ 고려대학교 컴퓨터학과
    하이텔(linuxlee)

    이 글에 대한 원문은 다음의 웹 페이지에서 보실 수 있습니다.
    http://www.es.linuxfocus.org/English/November1997/article6.html

 

1. 소개 : 프로그램을 제작하는 과정

    근래의 프로그램 제작 과정은 오랜 시간동안 고통받으면서 쌓아올려진 프로그래머들과 디자이너들의 노력의 산물이다.

    이러한 프로그램 제작 과정은 다음과 같은 순서를 따른다.

    우선 텍스트 편집기로 고급 언어를 사용한 소스 코드를 만든다. 매우 큰 프로그램 같은 경우는 하나의 파일에 담기 매우 힘들기 때문에 소스 코드 자체가 기능적 모듈별로 그 기능을 수행하는데 적합한 프로그래밍 언어가 다를 수 있기 때문에 앞에서 나뉘어진 소스 코드들이 다 같은 프로그래밍 언어를 사용해야 하는 것은 아니다.

    소스 코드들을 생성한 후, 각각의 코드들은 기계(컴퓨터)에 의해 실행가능한 코드로 변환되어야 한다. 이렇게 기계에 의해 실행 가능한 코드를 목적 코드(object code)라고 한다. 이 코드는 기계에 의해 실행 가능하다는 것만 빼고는 앞에서의 소스코드와 똑같은 기능을 수행하게 된다. 소스코드를 목적 코드로 변환하는 과정을 일반적으로 컴파일(compile)이라고 부른다. 컴파일은 대개의 경우 앞에서 나눈 각각의 소스 파일들에 대해 따로따로 적용된다. 컴파일된 목적 코드들은 프로그램이나 서브 루틴, 변수 등 다음 단계에서 필요한 대부분의 요소들을 포함하고 있다.

    프로그램을 생성하는데 필요한 목적 코드들이 생성된 후, 링커(linker)라고 불리우는 프로그램에 의해 앞에서 생성된 목적 코드들을 한데 묶고 연결하는 과정이 진행된다. 이 과정에서는 각각의 모듈(목적 코드)들에 포함되어 있는 다른 모듈을 가리키는 메모리 주소들이 결정되게 된다(예를 들어 한 모듈에서 다른 모듈에 들어있는 변수를 가리키게 되는 경우 링크 과정에서 각 모듈들을 합한 후 최종적으로 결정되는 절대 주소를 변수 부분에 넣어 주게 된다.) 이렇게 해서 생성되는 제품이 일반적으로 메모리에 로딩되고 실행되는 실행 파일이다.

    프로그램 실행은 운영 체제에 포함된 특별한 소프트웨어에 의해 이루어지며 예를 들자면 리눅스의 시스템 콜exec()등이 있다. 이 시스템 콜은 실행 파일을 찾은 후, 프로세스에게 메모리를 할당하고, 파일의 특별한 부분(실행코드와 변수들)을 메모리에 적재하고 실행파일의 '텍스트'라는 부분에서 제어권을 CPU에게 넘긴다.

 

2. 프로그램 제작 과정의 간략한 역사

    프로그램 제작 과정은 좀더 나은 실행 성능과 시스템 리소스 사용을 위해 지속적인 개혁을 해왔다.

    초기에 개발자들은 직접적으로 기계어 코드를 이용하여 프로그램을 개발하였다. 이후에 프로그램을 고급 언어로 개발한 후 기계어로 번역하는 것이 자동화됨으로써 프로그램의 생산성은 비약적으로 증가하게 되었다.

    프로그램 컴파일이 가능해진 후, 프로그램 제작 과정은 소스 파일들을 생성하고, 컴파일하고, 최종적으로 실행 파일을 실행하는 일련의 과정을 포함하게 되었다.

    그러나 컴파일 과정에 CPU 시간을 포함한 상당히 많은 자원이 투자되고 컴파일을 통하여 생성된 많은 프로그램들이 계속 재 사용되는 부분을 포함하고 있다는 것을 차츰 깨닫게 되었다. 더욱이 누군가가 소스의 한 부분을 고치게 되면 그 소스 코드를 포함한 모든 소스코드를 다시 컴파일해야만 하는 불편함이 있다는 사실도 깨닫게 되었다.

    위와 같은 이유 때문에 컴파일을 모듈별로 따로 하는 방식이 나오게 되었다. 이 방식은 메인 부분과 자주 재 사용되는 함수 부분을 따로 분리하여 재 사용되는 함수 부분을 특별한 장소에 미리 컴파일하여 놓아 두는 것이다. (후에 이를 라이브러리라고 부르게 된다.)

    이로써 개발자는 재 사용되는 코드 부분을 다시 작성할 필요없이 위에 미리 컴파일된 목적 코드의 도움을 받으면서 프로그램을 제작할 수 있게 되었다. 그럼에도 불구하고, 이러한 과정 또한 프로그래머가 각각의 모듈들을 링크시킬 때 다 알고 있어야 한다는 점 때문에 복잡하기는 마찬가지였다.(또한 이러한 과정은 프로그래머가 알고 있는 함수만 쓸 수 있다는 제약점을 낳게 되었다.)

 

3. 라이브러리란 무엇인가?

    위에서 이야기한 여러 가지 불편함 때문에 사람들은 라이브러리를 만들게 되었다. 라이브러리란 링커가 이해할 수 있는 파일 포맷을 가지며 개발자가 라이브러리를 지정하면 링커가 알아서 프로그램에 필요한 모듈만 링크 시켜 주는 특수한 파일 형태일 뿐이다. 일 라이브러리를 통해 개발자는 거대한 라이브러리에 있는 함수들의 어떠한 종속성에도 신경쓰지 않고 라이브러리 내의 모든 함수를 마음껏 사용할 수 있게 되었다.

    우리가 지금까지 이야기한 라이브러리는 현재에도 별로 변화하지 않고 있다. 다만 라이브러리 파일의 포맷이 조금 바뀌었는데 새로 바뀐 포맷에서는 파일 맨 앞에 그 라이브러리에 대한 정보와 링커가 모든 라이브러리 파일을 풀지 않고도 라이브러리 내부의 함수들을 알 수 있게 해주는 식별자를 가지고 있다. 리눅스에서는 ranlib(1)이라는 명령어가 이러한 기능(라이브러리 파일에 심볼 테이블을 더하는 작업)을 수행해 준다. 위에서 설명한 라이브러리를 정적 라이브러리(Static Library)라고 한다.

    최초로 멀티 태스킹 시스템이 소개된 후 라이브러리에도 '코드의 공유'라는 변화가 몰려오게 된다. 만약 같은 시스템에서 같은 코드를 가진 두 개의 프로그램이 동시에 실행된다면, 두 프로그램의 코드는 공유될 수 있다.(프로그램실행 시, 프로그램이 코드를 전혀 바꾸지 않기 때문에 가능한 것이다.) 이러한 아이디어는 복수개의 중복된 코드를 메모리에 로드할 필요를 없애 주었고 거대한 다중 사용자 시스템에서 많은 양의 메모리를 절약할 수 있게 되었다.

    위의 개혁에서 한 발짝 앞으로 더 나아가, 많은 프로그램들이 같은 라이브러리를 사용한다는 사실을 생각해보자. 비록 각각의 프로그램들이 같은 라이브러리를 사용한다고 해도, 그들이 라이브러리에서 사용하는 코드의 양은 각기 다를 것이다. 더욱이 메인 코드는 서로 마다 다 다를 것이다. 더욱이 메인 코드는 서로 마다 다 다를 것이고 따라서 그들의 텍스트 또한 공유할 수 없을 것이다. 여기서 만약 하나의 라이브러리를 사용하는 각기 다른 프로그램들이 라이브러리 코드를 공유할 수 있다고 가정한다면 상당한 메모리를 절약할 수 있을 것이다. 자 그럼 서로 다른 프로그램 텍스트를 가지며 동일한 라이브러리를 공유하는 서로 다른 프로그램이 있다고 가정해 보자.

    그러나 프로세스의 입장에서 보면 일은 조금 복잡해 진다. 실행 가능한 프로그램은 완전히 링크된 것이 아니라, 라이브러리의 식별자들의 주소 값을 가지는데 그것도 프로그램의 프로세스로의 적재 때문에 지연된다. 링커는 자신이 공유 라이브러리를 취급하고 있다고 인지하고 프로그램에 공유된 코드를 포함시키지 않는다. 커널의 경우 exec() 함수를 통해 프로그램을 실행시킬 때 자신이 공유 라이브러리를 로딩하기 위한 특별한 프로그램을 실행시킨다.(이 프로그램은 그 자신의 텍스트에 대한 공유 메모리를 할당하고 라이브러리의 변수들을 위한 개인 메모리를 할당하는 등의 작업을 한다.) 실행 파일을 로딩할 때 이 프로세스는 실행되고 모든 프로시져는 더더욱 복잡하다.

    물론 링커가 일반적인 라이브러리를 만났을 때에는 원래대로 행동한다.

    공유 라이브러리는 목적 코드를 포함한 파일들의 집합이라기보다는 목적 코드를 포함한 파일 그 자체라고 볼 수 있다. 공유 라이브러리를 링킹하는 동안에 링커는 어떤 모듈이 프로그램에 첨가되고, 어떤 모듈은 빠져야 되는가에 대한 정보를 라이브러리로부터 얻어내지 않는다. 링커는 단지 할당되지 않은 주소 값이 제대로 할당되도록 보장만 하고 라이브러리를 포함함으로써 어떠한 것이 반드시 리스트에 추가되어야 하는가를 검사한다. 모든 공유 라이브러리들의 묶음을 만들 수도 있지만 대개의 경우 공유 라이브러리는 여러 다양한 모듈이 링크된 결과이고 따라서 라이브러리는 추후 실행시간에 필요하기 때문에 라이브러리들을 한 묶음으로 만드는 일은 드물다. 아마도 공유 라이브러리라는 말 보다도 공유 객체라는 말이 더 어울릴 것이다.(그럼에도 불구하고 공유 객체라는 용어를 사용하지 않는다.)

     

4. 라이브러리의 형태

    앞에서 미리 언급했지만, 리눅스에는 정적 라이브러리와 공유 라이브러리, 두  가지의 형태의 라이브러리가 존재한다. 정적 라이브러리들은 ar(1)이라는 유틸리티를 이용해 여러 모듈들을 하나의 파일에 모아놓고 ranlib(1) 이라는 유틸리티를 이용해 각 모듈들을 색인해 놓은 형태를 지니고 있다. 이러한 모듈들은 대개 파일의 이름이 .a로 끝나는 파일에 저장되어 있다. 링커는 파일의 이름이 .a로 끝나는 라이브러리를 만나게 되면 파일의 앞에 수록되어 있는 모듈들의 리스트를 뒤져서 라이브러리를 사용하는 프로그램 코드의 아직 정해지지 않은 주소 값들을 사용하는 모듈들의 주소 값으로 바꾸고 본 프로그램에 사용되는 모듈들을 더하게 된다.

    반대로, 공유 라이브러리의 경우는 하나의 파일이라기 보다는 특수한 코드로 표시된 재 할당가능한 객체라고 볼 수 있다. 앞에서도 언급했듯이 링커 ld(1)는 프로그램 코드에 직접 모듈을 더하지 않고 라이브러리에서 제공하는 식별자를 골라서 프로그램 코드에 삽입하게 된다. 이대 모듈의 코드는 본 프로그램에 삽입되지 않으며 마치 본 프로그램에 이미 삽입되어 있는 척 하게 된다. 링커 ld(1)는 파일의 끝이 .so로 끝나는 것을 보면 공유 라이브러리로 인식한다.

 

5. 리눅스에서의 링크 작업

     모든 프로그램은 실행시키기 위해 연결된 목적 모듈들로 이루어져 있다. 이렇게 모듈들을 연결하는 역할을 수행하는 프로그램을 리눅스 링커 ld(1) 이라고 한다.

    ld(1) 은 실행시 여러 가지 옵션을 제공하는데 본 기사에서는 편의를 위해 라이브러리 사용에 관한 옵션만을 다루도록 하겠다. ld(1) 은 사용자가 직접 실행시킬 수는 없으며 gcc(1) 와 같은 컴파일러가 컴파일의 마지막 단계에서 실행시키게 된다. ld(1)의 실행 방식을 이해함으로써 리눅스에서 라이브러리를 사용하는 방식을 좀 더 쉽게 이해할 수 있을 것이다.

    ld(1)은 그 자체의 적합한 수행 조건으로 프로그램에 링크될 오브젝트파일의 리스트를 필요로 한다. 우리가 앞에서 이야기한  조건(공유 라이브러리는 .so로 끝나고 정적 라이브러리는 .a로 끝난다.)을 따르기만 한다면 ld(1)은 어떠한 순서로라도 리스트에 있는 오브젝트파일들을 읽어들일 수 있다.

    그러나 실제로 '어떠한 순서로라도' 라는 용어가 꼭 맞지는 않는다. ld(1)은 라이브러리를 포함하는 순간에 주소 값을 결정하는데 필요한 모듈만을 포함하기 때문에 뒤에 포함되는 모듈에 의해 결정되어야 할 주소 값은 그대로 결정이 되지 않은 상태로 남게 된다. 따라서 실제로 어떠한 순서로라도 모듈을 읽어 들일 수 있는 것은 아니다.

    반면에 ld(1)은 옵션 -I 과 -L을 이용하여 표준 라이브러리를 포함할 수도 있다.

    그러나 표준 라이브러리라고 해서 다른 것은 별로 없다. 단지 표준 라이브러리의 경우는 미리 정해진 장소에서 모듈을 찾는 다는 것만이 다를 뿐이다.

    라이브러리는 기본적으로 /lib 이나 /usr/lib 디렉토리에 위치한다. -L 옵션은 사용자가 기본 디렉토리 이외에 다른 디렉토리를 기본 디렉토리(기본적으로 라이브러리를 찾는 디렉토리) 목록에 추가시켜 준다. 사용법은 다음과 같다.

    < -L 추가하고자 하는 디렉토리 이름 >

    표준 라이브러리는

    < -I 적재될 라이브러리의 이름 >

    과 같은 방식으로 결정된다. 그러면 ld(1)은 순서대로 해당되는 디렉토리에서 libName.so 파일을 찾는다.(적재될 라이브러리의 이름이 libName 이라고 가정) 만약 찾지 못한다면 그 다음으로 libName.a를 찾게 된다.

    만약 ld(1) 이 libName.so 파일을 찾게 되면, ld(1)은 지정된 라이브러리를 링크 시키게 된다. 그러나 libName.so는 찾지 못하고 libName.a를 찾았을 경우는 여기서 얻은 모듈로 주소 값을 결정하게 된다.

     

6. 공유 라이브러리의 동적 링킹과 로딩

    동적 링킹은 실행 파일을 메모리에 적재하는 순간에 /lib/ld-linux.so 라는 특수한 모듈에 의해 수행된다.(사실 이 자체도 공유 라이브러리이다.)

    실제로 동적 라이브러리와 링크하기 위해서 두 개의 모듈이 있다. /lib/ld.so(예전 a.out 형식의 실행포맷에서 사용한다.) 와 /lib/ld-linux.so(이것은 EFL 실행포맷에서 사용한다.)이다.

    이러한 모듈들은 프로그램이 동적으로 링크될 시기에는 반드시 로딩되어 있어야 한다. 이것들의 이름은 표준적인 것이다. (그래서 이것들을 /lib 디렉토리에서 옮기면 안되고 이것들의 이름도 변경되면 안된다.) 만약 /lib/ld-linux.so 라는 이름을 변경한다면 이 공유 라이브러리는 사용하는 어떠한 프로그램도 자동적으로 실행이 멈추게 된다. 왜냐하면 이 모듈은 실행시에 아직 해석되지 않은 모든 참조 위치를 해석하는 일을 맡고 있기 때문이다.

    /lib/ld.so 모듈은 /etc/ld.so.cache 파일을 사용한다. 이 파일은 해당 라이브러리를 포함하는 대부분의 실행파일을 가리키고 있다. 이것에 대해서는 나중에 다시 말할 것이다.

 

7. soname, 공유 라이브러리의 버전, 호환성

    이제부터 공유 라이브러리에 대한 이해하기 어려운 주제에 대해서 이야기하려고 한다. 버전이 바로 그것이다. 이 기사를 읽은 분들은 종종 'library libX11.so.3 not found' 라는 에러메세지를 발견하곤 했을 것이다. 그러나 현재 자신의 라이브러리 버전을 libX.so.6 인 것을 보고 매우 당혹했을 것이고 이러한 에러 메시지에 대해서 어떻게 처리해야 할지 난감해 하는 경우가 있을 것이다. 어째서 ld.so(8)이 libpepe.so.45.0.1과 libpepe.so.45.22.3 은 서로 교환할 수 있는 버전으로 이해하지만 libpepe.so.46.22.3 은 그렇지 못하다고 인식하는 것일까?

    리눅스에서(물론 다른 모든 ELF 형식을 가지는 운영체제에서도 마찬가지이지만) 라이브러리들은 이것들을 구별하는데 연속된 문자를 사용한다. 이것이 soname 이다.

    soname은 라이브러리 내부에 포함되어 있고 연속된 문자들은 라이브러리들을 구성하는 대상이 링크될 때 결정된다. 공유 라이브러리가 만들어질 때 우리는 ld(1) 에 -soname 옵션을 넘겨주어야 한다. 물론 -soname 다음에는 이 문자열을 값으로 주어야 한다.

    동적 로더는 어떠한 실행파일이 실행될 때 로드 되어지고 인식되어야 하는 공유 라이브러리를 인식하기 위해서 이 문자열을 사용한다. 이 과정은 다음과 같다. 먼저 ld-linux.so 는 프로그램이 어떠한 라이브러리가 필요하고 해당 soname 이 뭔지 결정한다. 그런 다음 해당 이름을 가지고 /etc/ld.so.cache에서 그 라이브러리를 포함하고 있는 파일의 이름을 얻어온다. 그리고 라이브러리 안에 존재하는 이름과 요청된 soname을 비교한다. 만약 서로 동일하다면 그것으로 끝이다. 그러나 만약 그렇지 않다면 발견될 때까지 검색을 하고 그래도 발견되지 않는다면 에러를 낸다.

    만약 라이브러리가 로드되기에 적절한 것이라면 soname을 발견할 수 있을 것이다. 왜냐하면 ld-linux.so는 요청되어진 soname이 요구되어질 파일과 일치한다고 확신하기 때문이다. 만약 일치하지 않는 경우라면 보통 눈에 익은 'library libXXX.so.Y not found'가 나타날 것이다. 찾아야 하는 것이 soname이고 해당 soname을 참조할 때 주어지는 에러메세지인 것이다.

    여기에서 우리는 라이브러리 이름이 변경되거나 지속성에 문제가 발생할 대에는 혼돈을 일으킬 소지를 가지고 있다. 그러나 Linux 사회에는 soname을 할당하는데 관례가 있기 때문에 soname을 접근하거나 soname을 변경하는 것을 좋은 생각이 아니다.

    관례상 라이브러리의 soname은 반드시 적절한 라이브러리와 동일해야 하고 그러한 라이브러리에 대한 인터페이스여야 한다. 만약 라이브러리가 변경되어진다면 그것은 단지 내부적으로 일어나는 변화에 그쳐야 하지 전체적인 인터페이스에 영향을 미쳐서는 안되는 것이다. 즉 그 라이브러리에 대한 함수나 변수 그리고 함수에 대한 인자의 수 등에 영향을 안된다는 것이다. 이럴 경우에 구버전과 신버전 사이에 상호교환적이 되어야 한다. 즉 전체적으로는 변함이 없고 변경된 것은 적은 부분에서 이루어짐으로써 서로간에 교환 즉 호환성이 보장된다는 것이다. 적은 부분의 변화로 minor 숫자의 변경이 이루어지는 경우가 많다. 즉 이것은 soname 자체적으로는 변화가 없다는 것이다. 그리고 해당 라이브러리는 어떠한 중대한 문제없이 교체할 수 있게 되는 것이다.

    그러나 함수를 더하거나 제거하거나 하는 경우 등과 같은 인터페이스가 변경되는 경우에는 상호 교환이 불가능하다는 것을 쉽게 짐작할 수 있을 것이다. 예를 들면 libX11.so.3 과 libX11.so.6 사이를 보면 알 수 있다. X11R5 에서 X11R6 로 업그레이드 되면서 새로운 많은 함수들이 정의되어졌고 인터페이스에도 변경이 가해졌는데 이것들 사이에 상호교환이라는 것은 큰 문제를 유발할 수 있게 될 것이다. X11R6-v3.1.2에서 X11R6-v3.1.3과 같은 변화는 인터페이스에는 변화가 없고 그 라이브러리도 같은 soname을 가지고 있다. 이 경우도 구 버전을 유지하기 위해서는 다른 이름을 주어야 하는 것은 당연하다.(이러한 이유로 라이브러리의 이름에 soname 에 보여지는 주어진 번호가 있고 그 외에 전체적인 버전 숫자가 나타나는 것이다.)

 

8. ldconfig(8)

    /etc/ld.so.cache 라는 파일을 언급한 것이 있는데 이것은 라이브러리가 어떠한 파일에 포함되어져 있는지에 대한 정보를 가지고 있는 것이다. 이것은 표율성을 위해서 바이너리 파일로 만들어지고 ldconfig(8) 유틸리티에 의해서 만들어진다. ldconfig(8)은 /etc/ld.so.conf에 지정된 디렉토리를 찾아다니면서 발견된 각각의 동적 라이브러리들에 대해서 그 라이브러리의 soname 에 의해서 불리어지는 심볼릭 링크들을 만들어낸다. ld.so가 파일의 이름을 얻으려고 할 때 이것은 soname을 발견한 파일의 디렉토리를 선택하는 그러한 일을 한다. 그리고 이렇게 함으로서 우리가 라이브러리를 추가할 때마다 ldconfig(8)를 수행할 필요는 없게 되는 것이다. ldconfig는 리스트에 새로운 디렉토리를 만들어 널때만 실행시키면 된다.

 

9. 동적 라이브러리 만들기

    동적 라이브러리를 만들기 전에 고려할 사항이 있다. 그것은 바로 이렇게 동적 라이브러리로 만들었을 때 유용한가 하는 것이다. 동적 라이브러리는 여러 가지 이유로 시스템에 부하를 주게 된다. 프로그램의 로딩중에는 여러 가지 단계가 있다. 첫째는 메인 프로그램을 로딩하는 것이고 그 외에 프로그램에서 사용하는 각 동적 라이브러리를 로딩한다.

    동적 라이브러리는 재할당이 가능한 코드로 만들어져야 한다. 이것은 당연한 것으로 프로세스를 위해서 가상 주소 공간 안에 할당된 주소가 해당 프로그램이 로드될 때까지 알 수 없기 때문이다. 컴파일러는 라이브러리의 로딩위치를 유지하기 위해서 레지스터를 예약해 놓아야 한다. 그리고 결과적으로 코드 최적화에서 이것 때문에 레지스터 하나를 사용할 수 없게 된다. 이러한 경우는 사소하다고 볼 수 있다. 왜냐하면 이러한 것 때문에 나타나는 시스템의 부하는 대부분의 경우 다른 부하들의 5%이하이기 때문이다.

    동적 라이브러리가 적절한 것이 되려면 다른 여러 프로그램에서 자주 사용되어야 한다. 즉 이렇게 하면 시작한 프로세스가 없어진 이후에도 그 라이브러리에 대한 TEXT가 다시 로딩되는 것을 방지할 수 있다. 다시 로딩이 안되는 이유는 해당 라이브러리는 다른 프로그램에서 사용하고 있기 때문에 메모리에서 없애 버리면 안되기 때문에 항상 메모리가 남아있어서 다시 불러들일 필요가 없어진다.

    동적 라이브러리의 좋은 예는 C 표준 라이브러리이다. (이 라이브러리들은 대부분의 C 프로그램을 작성하는데 사용 되어진다.)평균적으로 모든 함수들이 자주 사용되어진다.

    정적 라이브러리의 경우에 좀처럼 드물게 사용하는 함수를 포함할 때 유리하다. 즉 해당 함수는 이것을 포함하고 있는 모듈에 있기 때문에 내가 필요하지 않으면 링크를 시키지 않으면 된다.

 

    9.1  소스코드의 컴파일

    소스의 컴파일은 한 가지만 제외하고는 대부분의 소스를 컴파일하는 경우와 같은 형식으로 수행된다. 차이점이라면 프로세스의 가상 주소 공간 안의 다른 지역에 로드될 수 있도록 하는 코드를 만들기 위해서 '-f PIC' -PIC(Position Independent Code)- 라는 옵션을 사용해야 한다는 것이다.

    동적 라이브러리에서 이 단계는 반드시 필요한 것이다. 왜냐하면  정적으로 링크된 프로그램에서는 라이브러리 오브젝트의 위치가 링크시에 결정되고 그래서 고정된 시간이 되어 버리기 때문이다. 예전의 a.out 실행형식에서는 이러한 단계가 불가능했다. 왜냐하면 각 공유 라이브러리가 가상주소의 공간 안에 고정된 위치를 가지게 만들어졌기 때문이다. 결과적으로 가상메모리의 오버래핑된 지역에 로드된 두 라이브러리는 사용하길 원하는 프로그램에서는 어떠한 경우에는 충돌이 발생하게 되었다. 이것은 라이브러리들의 리스트를 유지해야만 하는 결과를 만들었다. 즉 동적 라이브러리로 만들기를 원한다면 해당 라이브러리가 어디에서 어디까지 사용한다는 것을 선언하는 부분이 반드시 필요하게 되어진 것이고 이 영역을 다른 사람들이 사용하면 안되게 된 것이다.

    그렇다면 우리가 이전에 언급한 것에 따르면 공식적인 리스트에 동적 라이브러리를 등록하는 것이 필요없게 된 것이다. 왜냐하면 라이브러리는 로딩이 되어질 때 로드될 위치를 결정하게 되기 때문이다. 이것은 코드를 위치조정이 가능하게 만들었기 때문에 가능한 일이다.

     

    9.2  라이브러리의 오브젝트를 링크하기

    모든 객체를 컴파일 한 후에 동적인 로딩이 가능한 오브젝트파일로 만들기 위한 특별한 옵션이 필요하다.

    gcc -shared -o libName.so.xxx.yyy.zzz -Wl, -soname, libName.so.xxx

    짐작할 수 있듯이 공유 라이브러리를 만들어내는 옵션이 보이는 것을 제외하고는 보통의 링크과정처럼 보인다.
    하나하나 확인해보자.

    -shared

    이것은 공유 라이브러리를 만들어라 하는 옵션이다. 그래서 라이브러리에 관련된 출력파일에 실행 가능한 형태가 되는 것이다.

    -o libName.so.xxx.yyy.zzz

    최종적인 출력파일이름이다. 이름을 짓는데는 보통의 관례를 따를 필요는 없지만 이것은 표준적으로 개발에 사용되기를 원한다면 관례를 따르는 것이 좋다.

    Wl, -soname, libName.so.xxx

    이 -W1옵션은 gcc(1)가 콤마로 부분된 다음 옵션들을 링커에게 알려주기 위한 것이다. 이것은 gcc(1)가 ld(1)에게 옵션을 넘겨주기 위한 방법이다. 이 예에서는 링커에게 다음과 같은 옵션을 넘겨준 것이다.

    -soname libName.so.xxx

    이 옵션은 라이브러리에 대한 soname을 고정시키는 것이다. 그리고 필요한 다른 프로그램에서는 이 soname을 사용해야만 한다.

     

    9.3 라이브러리를 설치하기

    이미 관련된 실행파일을 가지고 있게 되었다. 이제는 이것을 적절한 장소에 설치하고 사용하기만 하면 된다. 새로운 라이브러리를 원하는 프로그램을 컴파일하기 위해서 다음과 같이 하면 된다.

    gcc -o program libName.so.xxx.yyy.zzz

    또는 만약 라이브러리가 /usr/lib에 설치되어 있다면 단순히 다음과 같이 하는 것만으로 충분하다.

    gcc -o program -lName

    (만약 /usr/local/lib에 있다면 '-L/usr/local/lib'라는 것을 추가 시켜주면 된다.)

    라이브러리를 설치하기 위해서는 다음과 같이 한다.

    라이브러리를 /lib 난 또는 /usr/lib 에 복사한다. 만약 다른 장소에 복사하기로 했다면(예를 들면 /usr/local/lib) 링커인 ld(1)가 자동적으로 링크할 수는 없을 것이다. libName.so.xxx.yyy.zzz로부터 libName.so.xxx 라는 심볼릭링크를 만들기 위해서 ldconfig(1)을 수행한다. 이 단계에서 만약 이전단계가 확실히 수행되어졌다면 이 라이브러리는 동적 라이브러리로 인식이 될 것이다. 링크된 프로그램들은 이 단계에서는 영향을 받지 않는다. 이것은 실행시에 라이브러리가 로딩될 때 영향을 받는 것이다.

    libName.so.xxx.yyy.zzz(또는 soname 인 libName.so.xxx)로부터 libName.so 라는 심볼릭 링크를 만들어야 한다. 이렇게 해야만 하는 이유는 링커가 -1옵션으로 라이브러리를 발견할 수 있게 하기 위해서이다. libName.so 라는 형식에 맞추어진 라이브러리의 이름이 필요한 경우를 위해서 고안된 작동방법이다.

     

10. 정적 라이브러리 만들기

    만약에 위와는 반대로 정적 라이브러리를 만드려고 한다면(또는 정적으로 링크된 것과 동적으로 링크된 것 둘 다를 원한다면) 다음과 같은 과정을 따른다.
    주의 : 라이브러리를 발견하는데 있어서 링커는 libName.so 라는 파일을 먼저 찾고 그 다음에 libName.a를 찾는다. 만약 두 개의 라이브러리(정적인 버전과 동적인 버전)를 같은 이름으로 부른다면 일반적으로 각각의 경우를 링크(동적인 것이 항상 먼저 링크되기 때문에)하는 것을 결정하는 것은 불가능하게 된다.

    이러한 이유로 항상 만약에 두 개의 라이브러리가 필요한 버전이 있다면 정적인 것은 libName_s.a 라고 이름을 붙여주고 동적인 것은 그냥 libName.so 라고 이름 붙여줄 것을 권하고 싶다. 링크할 때에는 그래서 다음과 같이 하면 될 것이다.

    gcc -o program -lName_s

    이 경우는 정적인 경우이고 동적인 것을 만들 때면 다음과 같이 한다.

    gcc -0 program -lName

     

    10.1  소스를 컴파일하기

    소스를 컴파일하기 위해서는 특별한 방법은 없다. 위에서 말한 대로 링크단계에서 결정해야 한다. 필요하다면 -f PIC 옵션을 사용하는 것이다.

     

    10.2  라이브러리를 객체에 링크하기

    정적 라이브러리의 경우에는 링크 단계가 필요없다. 모든 객체는 ar(1) 명령어의 해서 라이브러리 파일들을 만들 수 있다. 그런 다음 심볼들을 빠르게 해석해내기 위해서 라이브러리에 대한 ranlib(1) 명령을 수행하는 것이 좋다. 비록 반드시 필요한 것은 아니지만 이 명령을 실행하지 않으면 실행파일에 대한 모듈에 대한 링크가 지워질지도 모른다.

     

    10.3  라이브러리의 설치

    정적 라이브러리는 만약 오로지 정적 라이브러리 형태로만 사용하려고 한다면 libName.a 형태로 되어있는 이름을 가지고 있을 것이다. 그러나 두 가지 형태의 라이브러리를 모두 가지고 있기를 바란다면 libName_s.a 라고 이름을 지어줄 것을 권하고 싶다. 이것은 정적이나 또는 동적으로 로딩하는 것을 쉽게 제어하기 위해서 이다.

    링크단계에서 -static 옵션을 줄 수 있다. 이 옵션은 /lib/ld-linux.so 모듈의 로딩을 제어하는 것이다. 그러나 이것은 라이브러리의 검색 순서에 영향을 미치지는 않는다. 그래서 만약에 -static 으로 지정하고 ld(1)이 동적 라이브러리를 발견한다면 ld(1)는 발견된 동적 라이브러리를 가지고 작업을 할 것이다. 즉 -static 이라고 지정했다고 해서 계속적으로 정적으로 되어 있는 것을 찾지는 않는다는 것이다. 이것은 실행시에 라이브러리 루틴을 호출할 때 에러를 발생시킬 수 있다. 왜냐하면 동적 라이브러리는 실제 자신의 프로그램에는 해당 라이브러리가 있지 않기 때문이다. -자동적으로 동적인 로딩을 위한 모듈이 링크되어지지 않았고 그래서 이 단계가 수행될 수 없으므로 에러를 내는 것이다. 그래서 사용할 때 -static이 주어지면 링커가 정확한 라이브러리를 찾도록 해주어야 한다.

     

11.정적 링크하기와 동적 링크하기

    잘 알려진 라이브러리를 사용한 프로그램을 배포하는 경우를 생각해보자.(아마 모티프라이브러리를 사용한 것이 적절한 예가 될 것이다.)

    이런 종류의 소프트웨어를 만들기 위해서는 세가지 선택 사항이 있다.
    첫 째는 실행파일을 정적으로 링크시켜 만드는 것이다.(오직 .a 라이브러리만을 사용한다.) 이러한 종류의 프로그램은 오직 한번만 로드되어지고 시스템 안에 있는 어떠한 라이브러리도 가지고 있을 필요가 없다.(심지어 /lib/ld-linux.so 까지도) 그러나 이것은 바이너리 파일 안에 해당 소프트웨어에 필요한 모든 것들을 가지고 다녀야 하는 단점이 있다. 그리고 대부분 프로그램의 크기가 매우 크다.
    두 번째는 동적으로 만드는 것이다. 이것은 즉 우리의 소프트웨어가 실행되기 위해서는 연관된 모든 동적 라이브러리들이 모두 제공되어져야 한다는 것을 의미한다. 실행파일은 매우 작지만 모든 라이브러리를 모두 가지고 다니는 것은 쉽지 않은 일일 것이다.(예를 들면 Motif를 가지고 있지 않은 사람도 있다.)
    세 번째 선택 사항이 있다. 이것은 두 가지 형태를 서로 합치는 것이다. 즉 어떠한 라이브러리는 동적으로 또 어떤 라이브러리는 정적으로 해서 두 개의 장점을 모두 취하는 것이다. 이러한 형태의 소프트웨어가 배포에는 가장 편한 방법이 된다.

    예를 들면, 세가지 다른 라이브러리 포함 형태는 다음과 같다.

    gcc -static -o program.static program.o -lm_s -lXm_s -lXt_s -lX11_s -lXmu_s -lXpm_s

    gcc -o program.dynamic program.o -lm -lXm -lXt -lX11 -lXmu -lXpm

    gcc -o program.mixed program.o -lm -lXm_s -lXt -lX11 -lXmu -lXpm

    세 번째 경우에 Motif 라이브러리인 Motif(-lXm_s)는 정적인 결과를 얻고 그리고 다른 것들은 동적인 결과를 얻게 된다. 프로그램이 실행되기 위해서는 적당한 버전을 위한 라이브러리들이 필요하게 된다. 여기에서는 libm.so.xx, libXt.so.xx, libX11.so.xx, libXmu.so.xx, libXpm.so.xx 등이 필요에 따라서 사용될 것이다.



 

컴파일 참고

 --------------------------------------------------------------------------------

gcc -fPIC -m64 -c 파일명.c

gcc -fPIC -m64 -I{INCLUDE_PATH} -c 파일명.c

ex) gcc -fPIC -m64 -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -c 파일명.c

 

gcc -shared -o 출력라이브러리명.so -WI,soname,라이브러리버전명.so.xx.xx.xx 목적파일1.o 목적파일2.o ....

 

Makefile 생성시

--------------------------------------------------------------------------------

INCLUDEPATH = -I$(JAVA_HOME)/include \

-I$(JAVA_HOME)/include/linux

 

BITS = -m64

CC = gcc

 

TARGET = 출력라이브러리명.so

OBJECTS = 목적파일1.o \

목적파일2.o ...

 

$(TARGET) : $(OBJECTS)

$(CC) $(BITS) -g -shared -o $@ -WI,soname,$@ @^

 

.c.o :

$(CC) $(BITS) -g -fPIC $(INCLUDEPATH) -c $<

 

clean :

rm *.so *.o

--------------------------------------------------------------------------------

 

 


반응형
반응형

 

 

Language C - Libxml

 

libxml2을 설치하면 libxml 폴더에 각종 헤더 파일이 들어있다.

현재 내가 사용하는 header 파일은 아래 두개인데..

물론 더 많이 사용하는 법이 있겠지만 기본적으로 사용하는 법을 알아보자..

 

libxml/xmlmemory.h

libxml/parser.h

 

노드 타입은 아래와 같다

참고 할 것.

XML_ELEMENT_NODE= 1,
XML_ATTRIBUTE_NODE= 2,
XML_TEXT_NODE= 3,
XML_CDATA_SECTION_NODE= 4,
XML_ENTITY_REF_NODE= 5,
XML_ENTITY_NODE= 6,
XML_PI_NODE= 7,
XML_COMMENT_NODE= 8,
XML_DOCUMENT_NODE= 9,
XML_DOCUMENT_TYPE_NODE= 10,
XML_DOCUMENT_FRAG_NODE= 11,
XML_NOTATION_NODE= 12,
XML_HTML_DOCUMENT_NODE= 13,
XML_DTD_NODE= 14,
XML_ELEMENT_DECL= 15,
XML_ATTRIBUTE_DECL= 16,
XML_ENTITY_DECL= 17,
XML_NAMESPACE_DECL= 18,
XML_XINCLUDE_START= 19,
XML_XINCLUDE_END= 20

 

참고 : http://blog.naver.com/kdepirate?Redirect=Log&logNo=50033294493

--------------------------------------------------------------------------------

[C] XML Parser (libxml)



간단한 사용법 및 자주 쓰이는 함수


○ 기본 데이타타입

ㅁxmlDoc, xmlDocPtr

ㅁxmlNode, xmlNodePtr

ㅁxmlAttr, xmlAttrPtr

ㅁxmlChar

 


○ XML문서 파싱할때

ㅁdoc = xmlParseDoc(string of docuement), xmlParseFile(filename) : XML문서 파싱

ㅁnode = xmlDocGetRootElement(doc) : 문서의 ROOT원소를 받음

ㅁnode = node->xmlChildrenNode : 그 밑의 노드로 감

ㅁnode->name : 노드의 이름

ㅁstr = xmlNodeGetContent(node) : 노드의 내용을 얻음

ㅁstr = xmlGetProp(node, property name) : 노드의 property내용을 얻어옴

-> xmlGetProp로 얻은 xmlChar * 내용은 xmlFree 또는 Free 해당 메모리영역을 해제 해줘야 한다.

    단 xmlFree의 경우 버그가 있다고 알려져 있는 듯한데 ... 그렇다고 free로 해제하면 SF가 날때가 있다...

    좀 더 여러가지로 사용해 봐야 할듯한데 우선 xmlFree로 하자.

ㅁnode = node->next : 다음 노드로 감

 

 

 

 

[출처] libxml을 사용하여 xml만들거 파싱하기 (Crazy For Computer) |작성자 coolpiece416

 

<함수>

xmlNewDoc //xml전체를 의미하는 document를 생성.

xmlNewNode //노드를 하나 추가하낟.

xmlDocSetRootElement //해당 노드를 root노드로 지정한다.

xmlNodeSetContent //생성된 노드에다가 내용을 넣는다.

xmlAddChild //한 노드를 다른 노드의 child로 단다.

xmlDocDumpMemory //생성된 xml전체를 가져온다. -> <root><aaa>qqqq</aaa><bbb>zzz</bbb></root> 이렇게 라인으로.

xmlParseDoc //xmlChar* 의 형태로 저장된 xml 데이터를 파싱되어 xmlNewDoc형태로 구성한다.

xmlDocGetRootElement //root 를 가져온다.

xmlNodeGetContent //해당 노드의 내용을 가져온다.

 

 

<xml 만들기>

xmlDocPtr doc;

xmlNodePtr rootNode;

xmlNodePtr subNode;

xmlChar* xmlData;

int size;

doc = xmlNewDoc((xmlChar*)"1.0"); //1.0 버전으로 생성

rootNode = xmlNewNode(NULL,(xmlChar*)"root"); //root라는 이름의 새 노드 생성

xmlDocSetRootElement(doc,rootNode); //rootNode를 root노드로 지정

subNode = xmlNewNode(NULL,(xmlChar*)"Company"); //Company라는 이름의 새 노드 생성

xmlNodeSetContent(subNode,(xmlChar*)"한국회사"); //Company노드의 내용 등록

xmlAddChild(rootNode,subNode); //rootNode에 subNode를 단다.

subNode = xmlNewNode(NULL,(xmlChar*)"location");

xmlNodeSetContent(subNode,(xmlChar*)"korea");

xmlAddChild(rootNode,subNode);

xmlDocDumpMemory(doc,&xmlData,&size); //xmlData에는 <Document version=1.0><root><Company>한국회사</Company><location>korea</location></root> 가 들어가 있다.

 

 

<xml 파싱>

xmlDocPtr doc;

xmlNodePtr node;

doc = xmlParseDoc((xmlChar*)xmlData); //xmlData = 위에서 xmlData안에 있는 내용의 형태를 가진 것

node = xmlDocGetRootElement(doc);

node = node->xmlChildrentNode;

xmlChar* data1 = xmlNodeGetContent(node);

xmlChar* data2 = xmlNodeGetContent(node);

xmlChar* data3 = xmlNodeGetContent(node);

...... 이런식으로 가져온다.

 

○ XML문서 생성할때 

ㅁdoc = xmlNewDoc("1.0") : 1.0버전의 xml문서객체 생성

rootnode = xmlNewNode(NULL, name of element) : 새로운 노드객체 생성

ㅁxmlDocSetRootElement(doc, rootnode) : 위에서 생성한 노드를 루트원소로 설정

ㅁnode = xmlNewNode(NULL, name of element) : 노드하나 더 생성

ㅁxmlNodeSetContent(node, 내용) : 노드에 내용 추가

xmlNewProp(node, name fo property, content of property) : 노드에 property추가

ㅁxmlAddChild(rootnode, node) : 루트노드 혹은 다른 노드의 하위노드로 추가

xmlDocPtr xmlParseDoc (const xmlChar * cur)

xmlNodePtr xmlDocGetRootElement (xmlDocPtr doc)

node = node->xmlChildrenNode : 그 밑의 노드로 감

node->name : 노드의 이름

str = xmlNodeGetContent(node) : 노드의 내용을 얻음

str = xmlGetProp(node, property name) : 노드의 property내용을 얻어옴

node = node->next : 다음 노드로 감

void xmlFreeDoc (xmlDocPtr cur)


Structure xmlNodestruct _xmlNode {
void * _private : application data
xmlElementType type : type number, must be second !
const xmlChar * name : the name of the node, or the entity
struct _xmlNode * children : parent->childs link
struct _xmlNode * last : last child link
struct _xmlNode * parent : child->parent link
struct _xmlNode * next : next sibling link
struct _xmlNode * prev : previous sibling link
struct _xmlDoc * doc : the containing document End of common p
xmlNs * ns : pointer to the associated namespace
xmlChar * content : the content
struct _xmlAttr * properties : properties list
xmlNs * nsDef : namespace definitions on this node
void * psvi : for type/PSVI informations
unsigned short line : line number
unsigned short extra : extra data for XPath/XSLT
}

 

 

 

makefile

구성 : xml_parser.c

xml_parser : xml_parser.o
gcc -o xml_parser xml_parser.o -L/usr/local/lib -lxml2

xml_parser.o : xml_parser.c
gcc -c xml_parser.c -I/usr/local/include/libxml2
clean :
rm -f ./xml_parser ./xml_parser.o


 

-c 옵션으로 .o 파일을 만들고

-o 옵션으로 바이너리 파일을 만든다.

 

따라서 include 하는 헤더파일들의 위치는 -c 옵션을 줄때 하고

라이브러리들을 사용할 때에는 -L 로 해당 라이브러리의 위치를 지정해주고 바로 뒤에 이에 해당하는 라이브러리를 입력한다.

이때 -lxml2의 실제 이름은 libxml2.a 이다. libxml2.a 에서 ib와 .a를 뺀것으로 lib중 l만, lib뒤에 붙는 이름만 쓰고 .a는 입력하지 않는다.

라이브러리가 여러 디렉토리에 있다면

 

-L라이브러리위치 -사용할 라이브러리 -L라이브러리위치 -사용할 라이브러리 ...

이렇게 해야 할 듯...

[출처] libxml 이것만 알면 될려나? 계속 업데이트하자...|작성자 kdepirate

 

 

 

----------------------------------------------------------------------------

libxml tutorial 한글번역

Libxml tutorial 원문 : http://xmlsoft.org/tutorial/index.html

출처 : http://kylesoft.blogspot.kr/2012/06/libxml-tutorial.html#!/2012/06/libxml-tutorial.html 


Abstract
Libxml은 자유롭게 사용할 수 있는 라이센스로 제공되고, 다양한 플랫폼에서 사용할 수 있는 XML을 다루기 위한 C언어 라이브러리이다. 이 문서에서는 기본적인 기능들의 예제를 제공한다.

Introduction
Libxml은 XML 데이터를 읽고, 쓰고, 다루기 위한 함수들을 구현한 C언어 라이브러리이다. 이 문서에서는 예제 코드와 기본적인 기능들에 대해서 설명한다. 완전한 문서와 더욱 자세한 사항에 대해서는 프로젝트 홈페이지에서 확인할 수 있다.
이 문서에서는 아래에 나열된 간단한 XML 프로그램들을 사용하여 설명한다.
  • XML 문서 해석
  • 특정 엘리먼트로부터 문자열 추출하기
  • 엘리먼트와 내용을 추가하기
  • 속성을 엘리먼트에 추가하기
  • 속성값 추출하기
예제 프로그램의 전체 소스코드는 Appendix에서 찾을 수 있다.

Data types
Libxml에서는 몇 가지 data type을 정의하고 있다.
  • xmlChar : UTF-8로 인코딩된 character type이다. UTF-8이 아닐 경우 반드시 UTF-8로 변환하여 사용해야한다.
  • xmlDoc : 해석된 XML 문서가 트리 구조로 저장될 수 있도록 마련된 구조체이다. xmlDocPtr은 xmlDoc 구조체의 포인터형이다.
  • xmlNode : 하나의 노드를 저장하기 위한 구조체. xmlNodePtr은 xmlNode 구조체의 포인터형이다. xmlDoc의 트리구조를 탐색하는데 사용된다.

Parsing the file
xml 파일을 불러와서 해석하고, 에러 검사를 하는데는 파일이름과 하나의 function만 있으면된다.
xmlDocPtr doc; // (1)
xmlNodePtr cur; // (2)
doc = xmlParseFile(docname); // (3)
if(doc == NULL) { // (4)
    fprintf(stderr, "Document not parsed successfully.\n");
    return;
}
cur = xmlDocGetRootElement(doc); // (5)
if(cur == NULL) { // (6)
    fprintf(stderr, "empty document\n");
    xmlFreeDoc(doc);
    return;
}
if(xmlStrcmp(cur->name, (const xmlChar *)"story")) { // (7)
    fprintf(stderr, "document of the wrong type, root node != story");
    xmlFreeDoc(doc);
    return;
}
(1) 해석된 문서를 가리킬 포인터 선언
(2) 노드를 가리키기 위한 포인터 선언
(3) docname의 문서를 불러와 해석한다.
(4) 문서가 정상적으로 로드/해석 되었는지 확인한다.
(5) root 엘리먼트를 찾는다.
(6) 문서에 내용이 있는지 확인한다.
(7) root 엘리먼트의 이름이 story인지 확인한다.
Note : 이 예제에서 에러가 발생할 수 있는 것은 적절하지 않은 인코딩이다. XML 표준에서는 문서가 UTF-8 또는 UTF-16이 아닌 다른 인코딩으로 저장되어 있을 경우 명시적으로 해당 인코딩 타입을 기술하도록 되어 있다. 문서에 인코딩 타입이 기술되어 있다면, libxml은 자동적으로 해당 인코딩 타입에서 UTF-8로 변환한다. 자세한 XML 인코딩 requirement에 대해서는 XML 표준을 참조하라.

Retrieving element content
엘리먼트의 내용을 추출하기 위해서는 문서 tree에서 해당 엘리먼트를 찾아야한다. 이 예제에서는 "story" 엘리먼트로부터 "keyword"라는 엘리먼트를 찾는다. 원하는 것을 찾기 위해서 tree를 하나하나 검색해야한다. doc(xmlDocPtr), cur(xmlNodePtr)은 이미 가지고 있다고 가정하고 설명한다.
cur = cur->xmlChildrenNode; // (1)
while(cur != NULL) { // (2)
    if((!xmlStrcmp(cur->name, (const xmlChar *)"storyinfo"))) {
        parseStory(doc, cur);
    }
    cur = cur->next;
}
(1) cur의 첫번째 자식 노드를 가져온다. 여기서 cur은 문서의 root 엘리먼트인 "story"이다. 즉, "story"의 첫번째 자식 엘리먼트를 가져오는 것이다.
(2) 이 loop에서는 "story" 엘리먼트의 자식들 중에서 "storyinfo"인 엘리먼트를 찾는다. "storyinfo"가 아니면 다음 자식 엘리먼트로 이동하고, 찾으면 parseStory()를 호출한다.

void parseStory(xmlDocPtr doc, xmlNodePtr cur) {
    xmlChar *key;
    cur = cur->xmlChildrenNode; // (1)
    while(cur != NULL) { // (2)
        if((!xmlStrcmp(cur->name, (const xmlChar *)"keyword"))) {
            key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); // (3)
            printf("keyword: %s\n", key);
            xmlFree(key);
        }
        cur = cur->next;
    }
    return;
}
(1) 첫 번째 자식 노드를 가지고온다.
(2) 이전 코드의 loop 처럼 loop를 사용하여 자식 노드들 중에서 "keyword"라는 이름을 가진 노드를 찾는다.
(3) "keyword" 노드를 찾으면 내용을 xmlNodeListGetString()을 사용해서 가져와 출력한다.
xmlNodeListGetString()을 호출할때 cur->xmlChildrenNode를 인자로 넘겨주는데, XML에서는 내용이 엘리먼트의 자식 노드로 표현이 되기 때문에 "keyword"의 자식노드들 중에서 문자열 데이터를 가져오기 위해서 자식 노드들 중에서 검색하는 것이다.
Note. xmlNodeListGetString()은 메모리에 공간을 잡아서(할당하고) 문자열을 넣어 return하기 때문에 사용한 이후에 반드시 메모리를 해제해야만 한다.(xmlFree() 사용)

Using xpath to retrieve element content
Libxml2에서는 문서 tree에서 엘리먼트를 탐색하기 위한 추가적인 방법인 XPath라는 것을 포함하고 있다. XPath는 문서에서 특정 노드를 찾기위한 표준적인 검색 방법을 제공한다.
Note. XPath에 대해서 자세히 알고 싶다면 XPath 문서를 참고하자.

XPath를 사용하기 위해서는 xmlXPathContext를 설정하고, xmlXPathEvalExpression() 함수를 호출한다. 이 함수는 xmlXPathObjectPtr를 반환한다.
xmlXPathObjectPtr getnodeset (xmlDocPtr doc, xmlChar *xpath) {
    xmlXPathContextPtr context; // (1)
    xmlXPathObjectPtr result;
    context = xmlXPathNewContext(doc); // (2)
    result = xmlXPathEvalExpression(xpath, context); // (3)
    if(xmlXPathNodeSetIsEmpty(result->nodesetval)) { // (4)
        printf("No result\n");
        return NULL;
    }
    xmlXPathFreeContext(context);
    return result;
}
(1) 변수 선언
(2) context 변수 초기화
(3) XPath 표현식 적용
(4) 결과 확인 & 메모리 해제
위 함수에서 반환되는 xmlXPathObjectPtr은 노드들과 반복적인 동작을 위한 정보들의 집합을 포함하고 있다. 노드 집합은 엘리먼트 개수(nodeNr)와 노드들의 배열(nodeTab)을 가지고 있다.
for(i = 0; i < nodeset->nodeNr; i++) { // (1)
    keyword = xmlNodeListGetString(doc, nodeset->nodeTab[i]->xmlChildrenNode, 1); // (2)
    printf("keyword: %s\n", keyword);
    xmlFree(keyword);
}
(1) nodeset->nodeNr은 노드 집합이 가지고 있는 엘리먼트 개수이다.
(2) 각 노드의 내용을 추출하여 출력한다.

Writing element content
엘리먼트 내용을 추가하는 것은 이전에 이미 했던 과정들(문서를 해석하고, 노드들을 탐색)과 많은 부분이 동일하다. 문서를 불러와 해석하고, 원하는 노드를 찾고, 내용을 추가하면 된다. 여기서는 "storyinfo" 엘리먼트를 찾아서 keyword를 추가하고 파일로 저장한다.
void parseStory(mxlDocPtr doc, xmlNodePtr cur, char *keyword) {
    xmlNewTextChild(cur, NULL, "keyword", keyword); // (1)
    return;
}
(1) xmlNewTextChild 함수는 현재 노드에 새로운 자식 노드를 추가한다.
노드를 추가한 후에는 파일로 저장하기를 원할 것이다. 네임스페이스가 포함되어 저장되기를 원한다면 여기서 추가할 수 있다. 아래 예제에서는 네임스페이스가 NULL인 경우이다.
xmlSaveFormatFile(docname, doc, 1);
첫번째 인자는 파일 이름이다. 읽어들인 파일명과 동일한 파일명을 입력하면 덮어쓰게 된다. 두번째 인자는 xmlDoc 구조체의 포인터이다. 세번째 인자를 1로 설정하면 indenting하여 저장한다.

Writing attribute
속성을 추가하는 것은 새 엘리먼트를 추가하는 것과 비슷하다. 이 예제에서는 libxml tutorial 문서의 URI를 추가한다.
추가할 위치는 story 엘리먼트의 child이다. 따라서 새로운 엘리먼트와 속성을 추가할 위치를 찾는 것을 간단하다.
먼저 변수를 선언한다.
xmlAttrPtr newattr;
xmlNodePtr도 추가적으로 필요하다.
xmlNodePtr newnode;
root 엘리먼트가 story이면, 다른 노드를 탐색하기 전까지는 cur이 root 엘리먼트를 가리키고 있다. 따라서 cur에 새 엘리먼트와 속성을 추가하면 된다.
newnode = xmlNewTextChild(cur, NULL, "reference", NULL); // (1)
newattr = xmlNewProp(newnode, "uri", uri); // (2)
(1) 먼저 새로운 노드를 현재 노드의 child로 xmlNewTextChild 함수를 사용해서 생성한다.
(2) 생성된 노드에 새로운 속성을 추가한다.
앞의 예제와 같이 노드가 추가되면 파일에 저장한다.

Retrieving attributes
속성값을 추출하는 것은 노드에서 내용을 추출하는 이전의 예제와 거의 동일하다. 이 예제에서는 앞의 예제에서 추가했던 URI 속성의 값을 추출할 것이다.
void getReference(xmlDocPtr doc, xmlNodePtr cur) {
    xmlChar *uri;
    cur = cur->xmlChildrenNode;
    while(cur != NULL) {
        if((!xmlStrcmp(cur->name, (const xmlChar *)"reference"))) {
            uri = xmlGetProp(cur, "uri"); // (1)
            printf("uri: %s\n", uri);
            xmlFree(uri);
        }
        cur = cur->next;
    }
    return;
}
(1) 핵심 함수는 xmlGetProp()이다. 이 함수는 속성의 값을 xmlChar 형식으로 반환한다.
Note. 만약 고정으로 선언된 DTD를 사용하거나 속성의 기본 값이 설정되어 있다면, 이 함수는 값을 추출할 것이다.

Encoding conversion
데이터 인코딩 호환성 문제는 XML을 다루는데 있어 가장 어려운 문제중 하나이다. 이 문제를 회피하고 싶다면 어플리케이션을 디자인할때 내부적으로, libxml로 저장/관리되는 데이터에 대해서 UTF-8 사용을 고려하라. 프로그램에서 사용되는 다른 포맷으로 된 데이터(ISO-88590-1과 같은 데이터)는 libxml 함수들로 전달되기 전에 반드시 UTF-8로 변환되어야한다. 출력 데이터가 UTF-8이 아닌 다른 포맷을 원할 경우 역시 반드시 변환을 거쳐야 한다.
Libxml은 데이터 변환이 가능한 경우 iconv를 사용한다. iconv가 없을 경우 UTF-8과 UTF-16, ISO-8859-1만 사용될 수 있다. iconv가 있을 경우 어떤 포맷이라도 변환을 거쳐 사용할 수 있다. 현재 iconv는 150여가지의 포맷을 지원한다.
Warning. 일반적으로 저지르기 쉬운 실수 중의 하나는, 하나의 코드 내, 다른 부분에서 사용되는 내부 데이터에 서로다른 포맷을 사용하는 것이다. 가장 흔한 경우가 libxml에서는 내부 데이터를 UTF-8로 가정하는데 libxml를 사용하는 어플리케이션에서는 내부 데이터를 ISO-8859-1로 가정하는 경우이다. 그 결과 어플리케이션 내부 데이터를 각 코드에서 다르게 실행하게 되므로 잘못 해석하는 경우가 발생할 수 있다.
이 예제는 간단한 문서를 구성하고, command line에서 입력되는 내용을 root 엘리먼트에 추가하고, 그 결과를 적절한 인코딩으로 stdout으로 내보낸다. 여기서는 ISO-8859-1 인코딩을 사용한다. command line에서 입력된 문자열은 ISO-8859-1에서 UTF-8로 변환된다. 예제에서 변환과 캡슐화를 위해 사용된 함수는 xmlFindCharEncodingHandler이다.
xmlCharEncodingHandlerPtr handler; // (1)
size = (int)strlen(in) + 1; // (2)
out_size = size * 2 - 1;
out = malloc((size_t)out_size);
...
handler = xmlFindCharEncodingHandler(encoding); // (3)
...
handler->input(out, &out_size, in, &temp); // (4)
...
xmlSaveFormatFileEnc("-", doc, encoding, 1); // (5)
(1) xmlCharEncodingHandler 함수를 위한 handler 포인터 선언
(2) xmlCharEncodingHandler 함수는 입력과 출력의 크기를 필요로한다. 여기서 그 크기를 계산한다.
(3) xmlFindCharEncodingHandler 함수는 인자로 초기 데이터 인코딩 타입을 받아 built-in 변환 핸들러를 검색하여 있으면 핸들러를 반환하고, 찾지 못한 경우 NULL을 반환한다.
(4) 변환 함수는 인자로 입력과 출력 문자열에 대한 포인터와 각각의 크기를 필요로 한다. 크기정보는 반드시 이전에 계산 되어 있어야 한다.
(5) 출력으로 UTF-8이 아닌 특정 인코딩 타입으로 원할 경우 xmlSaveFormatFileEnc 함수를 사용한다.

Appendix
a. Compilation
Libxml에는 xml2-config 스크립트가 포함되어 있으며, 이것은 컴파일에 필요한 flag들을 생성해준다. pre-processor 및 컴파일 flags를 생성하기 위해서는 xml2-config --cflags를 사용하고, linking 작업에는 xml2-config --libs를 사용하라. 다른 옵션을 확인하고 싶으면 xml2-config --help를 입력하면 된다.

b. Sample document
<?xml version="1.0"?>
<story>
    <storyinfo>
        <author>John Fleck</author>
        <datewritten>June 2, 2002</datewritten>
        <keyword>example keyword</keyword>
    </storyinfo>
    <body>
        <headline>This is the headline</headline>
        <para>This is the body text.</para>
    </body>
</story>

c. Code for keyword example
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>

void parseStory(xmlDocPtr doc, xmlNodePtr cur) {
    xmlChar *key;
    cur = cur->xmlChildrenNode;
    while(cur != NULL) {
        if((!xmlStrcmp(cur->name, (const xmlChar *)"keyword"))) {
            key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
            printf("keyword: %s\n", key);
            xmlFree(key);
        }
        cur = cur->next;
    }
    return;
}

static void parseDoc(char *docname) {
    xmlDocPtr doc;
    xmlNodePtr cur;
    doc = xmlParseFile(docname);
    if(doc == NULL) {
        fprintf(stderr, "Document not parsed successfully.\n");
        return;
    }
    cur = xmlDocGetRootElement(doc);
    if(cur == NULL) {
        fprintf(stderr, "empty document\n");
        xmlFreeDoc(doc);
        return;
    }
    if(xmlStrcmp(cur->name, (const xmlChar *)"story")) {
        fprintf(stderr, "document of the wrong type, root node != story\n");
        xmlFreeDoc(doc);
        return;
    }
    cur = cur->xmlChildrenNode;
    while(cur != NULL) {
        if((!xmlStrcmp(cur->name, (const xmlChar *)"storyinfo"))) {
            parseStory(doc, cur);
        }
        cur = cur->next;
    }
    xmlFreeDoc(doc);
    return;
}

int main(int argc, char **argv) {
    char *docname;
    if(argc <= 1) {
        printf("Usage: %s docname\n", argv[0]);
        return(0);
    }
    docname = argv[1];
    parseDoc(docname);
    return(1);
}

d. Code for xpath example
#include <libxml/parser.h>
#include <libxml/xpath.h>

xmlDocPtr getdoc (char *docname) {
    xmlDocPtr doc;
    doc = xmlParseFile(docname);
    if (doc == NULL ) {
         fprintf(stderr,"Document not parsed successfully. \n");
        return NULL;
    }
    return doc;
}

xmlXPathObjectPtr getnodeset (xmlDocPtr doc, xmlChar *xpath) {
    xmlXPathContextPtr context;
    xmlXPathObjectPtr result;
    context = xmlXPathNewContext(doc);
    result = xmlXPathEvalExpression(xpath, context);
    if(xmlXPathNodeSetIsEmpty(result->nodesetval)) {
        printf("No result\n");
        return NULL;
    }
    xmlXPathFreeContext(context);
    return result;
}

int main(int argc, char **argv) {
    char *docname;
    xmlDocPtr doc;
    xmlChar *xpath = (xmlChar *)"//keyword";
    xmlNodeSetPtr nodeset;
    xmlXPathObjectPtr result;
    int i;
    xmlChar *keyword;
    if (argc <= 1) {
        printf("Usage: %s docname\n", argv[0]);
        return(0);
    }
    docname = argv[1];
    doc = getdoc(docname);
    result = getnodeset (doc, xpath);
    if (result) {
        nodeset = result->nodesetval;
        for (i=0; i < nodeset->nodeNr; i++) {
            keyword = xmlNodeListGetString(doc, nodeset->nodeTab[i]->xmlChildrenNode, 1);
            printf("keyword: %s\n", keyword);
            xmlFree(keyword);
        }
        xmlXPathFreeObject (result);
    }
    xmlFreeDoc(doc);
    xmlCleanupParser();
    return (1);
}

e. Code for add keyword example
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>

void parseStory (xmlDocPtr doc, xmlNodePtr cur, char *keyword) {
    xmlNewTextChild (cur, NULL, "keyword", keyword);
    return;
}

xmlDocPtr parseDoc(char *docname, char *keyword) {
    xmlDocPtr doc;
    xmlNodePtr cur;
    doc = xmlParseFile(docname);
    if (doc == NULL ) {
        fprintf(stderr,"Document not parsed successfully. \n");
        return (NULL);
    }
    cur = xmlDocGetRootElement(doc);
    if (cur == NULL) {
        fprintf(stderr,"empty document\n");
        xmlFreeDoc(doc);
        return (NULL);
    }
    if (xmlStrcmp(cur->name, (const xmlChar *) "story")) {
        fprintf(stderr,"document of the wrong type, root node != story");
        xmlFreeDoc(doc);
        return (NULL);
    }
    cur = cur->xmlChildrenNode;
    while (cur != NULL) {
        if ((!xmlStrcmp(cur->name, (const xmlChar *)"storyinfo"))) {
            parseStory (doc, cur, keyword);
        }
        cur = cur->next;
    }
    return(doc);
}

int main(int argc, char **argv) {
    char *docname;
    char *keyword;
    xmlDocPtr doc;
    if (argc <= 2) {
        printf("Usage: %s docname, keyword\n", argv[0]);
        return(0);
    }
    docname = argv[1];
    keyword = argv[2];
    doc = parseDoc (docname, keyword);
    if (doc != NULL) {
        xmlSaveFormatFile (docname, doc, 0);
        xmlFreeDoc(doc);
    }
    return (1);
}

f. Code for add attribute example
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>

xmlDocPtr parseDoc(char *docname, char *uri) {
    xmlDocPtr doc;
    xmlNodePtr cur;
    xmlNodePtr newnode;
    xmlAttrPtr newattr;
    doc = xmlParseFile(docname);
    if (doc == NULL ) {
        fprintf(stderr,"Document not parsed successfully. \n");
        return (NULL);
    }
    cur = xmlDocGetRootElement(doc);
    if (cur == NULL) {
        fprintf(stderr,"empty document\n");
        xmlFreeDoc(doc);
        return (NULL);
    }
    if (xmlStrcmp(cur->name, (const xmlChar *) "story")) {
        fprintf(stderr,"document of the wrong type, root node != story");
        xmlFreeDoc(doc);
        return (NULL);
    }
    newnode = xmlNewTextChild (cur, NULL, "reference", NULL);
    newattr = xmlNewProp (newnode, "uri", uri);
    return(doc);
}

int main(int argc, char **argv) {
    char *docname;
    char *uri;
    xmlDocPtr doc;
    if (argc <= 2) {
        printf("Usage: %s docname, uri\n", argv[0]);
        return(0);
    }
    docname = argv[1];
    uri = argv[2];
    doc = parseDoc (docname, uri);
    if (doc != NULL) {
        xmlSaveFormatFile (docname, doc, 1);
        xmlFreeDoc(doc);
    }
    return (1);
}

g. Code for retrieving attribute value example
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>

xmlDocPtr parseDoc(char *docname, char *uri) {
    xmlDocPtr doc;
    xmlNodePtr cur;
    xmlNodePtr newnode;
    xmlAttrPtr newattr;
    doc = xmlParseFile(docname);
    if (doc == NULL ) {
        fprintf(stderr,"Document not parsed successfully. \n");
        return (NULL);
    }
    cur = xmlDocGetRootElement(doc);
    if (cur == NULL) {
        fprintf(stderr,"empty document\n");
        xmlFreeDoc(doc);
        return (NULL);
    }
    if (xmlStrcmp(cur->name, (const xmlChar *) "story")) {
        fprintf(stderr,"document of the wrong type, root node != story");
        xmlFreeDoc(doc);
        return (NULL);
    }
    newnode = xmlNewTextChild (cur, NULL, "reference", NULL);
    newattr = xmlNewProp (newnode, "uri", uri);
    return(doc);
}

int main(int argc, char **argv) {
    char *docname;
    char *uri;
    xmlDocPtr doc;
    if (argc <= 2) {
        printf("Usage: %s docname, uri\n", argv[0]);
        return(0);
    }
    docname = argv[1];
    uri = argv[2];
    doc = parseDoc (docname, uri);
    if (doc != NULL) {
        xmlSaveFormatFile (docname, doc, 1);
        xmlFreeDoc(doc);
    }
    return (1);
}

h. Code for encoding conversion example
#include <string.h>
#include <libxml/parser.h>

unsigned char* convert (unsigned char *in, char *encoding) {
    unsigned char *out;
    int ret,size,out_size,temp;
    xmlCharEncodingHandlerPtr handler;
    size = (int)strlen(in)+1;
    out_size = size*2-1;
    out = malloc((size_t)out_size);
    if (out) {
        handler = xmlFindCharEncodingHandler(encoding);
        if (!handler) {
            free(out);
            out = NULL;
        }
    }
    if (out){
        temp=size-1;
        ret = handler->input(out, &out_size, in, &temp);
        if (ret || temp-size+1) {
            if (ret) {
                printf("conversion wasn't successful.\n");
            }
            else {
                printf("conversion wasn't successful. converted\n");
            }
            free(out);
            out = NULL;
        }
        else {
            out = realloc(out,out_size+1);
            out[out_size]=0; /*null terminating out*/
        }
    }
    else {
        printf("no mem\n");
    }
    return (out);
}

int main(int argc, char **argv) {
    unsigned char *content, *out;
    xmlDocPtr doc;
    xmlNodePtr rootnode;
    char *encoding = "ISO-8859-1";
    if (argc <= 1) {
        printf("Usage: %s content\n", argv[0]);
        return(0);
    }
    content = argv[1];
    out = convert(content, encoding);
    doc = xmlNewDoc ("1.0");
    rootnode = xmlNewDocNode(doc, NULL, (const xmlChar*)"root", out);
    xmlDocSetRootElement(doc, rootnode);
    xmlSaveFormatFileEnc("-", doc, encoding, 1);
    return (1);
}
반응형
반응형


출처 : http://bestheroz.blog.me/109841952


시간과 날짜와 관련된 라이브러리 함수

구분

함수 원형과 인자

함수 설명

시간

계산

time_t time(time_t *timeptr);

1970 1 1일 자정부터 경과된 현재 시간을 초단위로 계산

시간을

문자열로

변환

char *asctime(strcut tm *time);

구조체 tm형식의 시간을 문자열로 변환

char *ctime(time_t *time);

함수 time()로부터 계산된 현재 시간을 문자열로 변환

시간을

구조체로

변환

struct tm *localtime(time_t *time);

지역 시간(local time)을 구조체 tm의 형식으로 가져오는 함수

struct tm *gmtime(time_t *time);

Greenwich Meam Time(GMT)을 구조체 tm 형식으로 가져옴

시간

차이

계산

clock_t clock(void);

clock tick으로 경과된 시간

double difftime(time_t time2, time_t time1);

두 시간의 차이를 초단위로 계산

시간

지연

void Sleep(unsigned millisecond);

인자가 지정하는 만큼의 1/1000초 단위의 시간을 지연

void delay(unsigned millisecond);

예제

#include<stdio.h>

#include<time.h>

int main(void)

{

time_t now;

time(&now);

printf("현재날짜와시간: %s", asctime(localtime(&now)));

printf("현재날짜와시간: %s", ctime(&now));

return 0;

}

strcut tm은 <time.h>에 다음과 같이 정의되어 있다.

struct tm {

int tm_sec; /* (seconds) - [0,59] */

int tm_min; /* (minutes) - [0,59] */

int tm_hour; /* 시간(hour) - [0,23] */

int tm_mday; /* 날짜(day) - [1,31] */

int tm_mon; /* (month) - [0,11] */

int tm_year; /* 1900년이후의연도수*/

int tm_wday; /* dydlf(second) - [0,6] */

int tm_yday; /* 연중날짜(day) - [0,365] */

int tm_isdst; /* 일광절약시간 - [0, 59] */

};

예제

#include<stdio.h>

#include<time.h>

int main(void)

{

time_t curr;

struct tm *d;

curr=time(NULL);

d=localtime(&curr);

printf("현재날짜\n");

printf("%d%d%d\n", d->tm_year+1900, d->tm_mon+1, d->tm_mday);

printf("현재시간\n");

printf("%d%d%d\n", d->tm_hour, d->tm_min, d->tm_sec);

return 0;

}

time과 clock의 차이

구분

함수 time

함수 clock

원형

time_t time(time_t *timer);

clock_t clock(void);

기능

절대시간 또는 달력시간(calendar time)을 계산

프로세서 시간을 계산

반환 값

1970 1 1일 자정 이후 현재까지 경과된 시간을 초(second)로 반환

프로세서 시간을 반환

예제 - 함수 time 사용

#include<stdio.h>

#include<time.h>

int main(void)

{

time_t start, end;

long i=0;

double pst;

start=time(NULL);

while(i<30000000)

{

i++;

}

end=time(NULL);

pst=difftime(end, start);

printf("time: %f\n", pst);

return 0;

}

/**초단위의시간을계산하는함수time()의결과는0초**/

예제 - 함수 clock 사용

#include<stdio.h>

#include<time.h>

int main(void)

{

time_t start, end;

long i=0;

double pst;

start=clock();

while(i<30000000)

{

i++;

}

end=clock();

pst=(double)(end-start)/CLK_TCK;

printf("time: %f\n", pst);

return 0;

}

시간을 지연시키는 함수 Sleep 예제

#include<stdio.h>

#include<time.h>

#include<windows.h> //Sleep()

int main(void)

{

clock_t start, end;

double pst;

start = clock();

printf("start!\n");

Sleep(3500);

end = clock();

printf("end\n");

pst = (double)(end-start)/CLK_TCK;

printf("경과시간: %f\n", pst);

return 0;

}

시간을 처리하는 함수와는 다른 개념의 함수이지만 유용하게 사용할 수 있는 함수로 kbhit가 있다. 이 함수의 원형은 다음과 같이 함수의 인자가 없으며, 키보드 상의 어떤 키를 누르면 0이 아닌 값을, 누르지 않은 상태라면 0값을 반환하는 함수다. 함수 kbhit는 <conio.h>를 필요로 한다.

kbhit

함수원형

int kbhit(void);

반환 값

입력된 키가 있으면 0이 아닌 값을, 입력된 키가 없으면 0을 반환

프로그램 실행중에 아무키나 누르기 전까지만 프로그램을 계속 반복시키고자 한다면 반복문 while과 함께 다음과 같이 사용할 수 있다.

while(!kbhit())

{

//반복할프로그램

}

예제 - 현재 시간을 연속적으로 출력(kbhit, localtime)

#include<stdio.h>

#include<time.h>

#include<stdlib.h>

#include<conio.h>

int main(void)

{

time_t now;

struct tm *d;

while(!kbhit())

{

system("cls");

now=time(NULL);

d=localtime(&now);

printf("현재날짜와시간: %s\n", asctime(d));

}

return 0;

}

날짜 수와 요일 계산

기준일(1년 1월 1일)로부터 특정일 사이의 날짜 수의 계싼 함수 total_days

long total_days(int year, int month, int day)

{

int months[]={31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

int i;

long total=0L;

total=(year-1)*365L+(year-1)/4-(year-1)/100+(year-1)/400;

if(!(year%4) && year%100 || !(ytear%400))

months[1]++;

for(i=0;i<month-1;i++)

total += months[i];

total += day;

return total;

}

특정일의 요일 계산

#include<stdio.h>

long total_days(int year, int month, int day);

int main(void)

{

int year, month, day;

char *week_day[] = {"", "", "", "", "", "", ""};

long total;

printf("특정일의요일구하는프로그램\n\n");

printf("입력될숫자는space bar로분리하고\n");

printf("Enter 키를누릅니다.\n");

printf("예로2005 5 1 Enter\n");

printf("년월일입력: ");

scanf("%d %d %d", &year, &month, &day);

total=total_days(year, month, day);

printf("%s 요일입니다.\n", week_day[total%7]);

return 0;

}

long total_days(int year, int month, int day)

{

int months[]={31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

int i;

long total=0L;

total=(year-1)*365L+(year-1)/4-(year-1)/100+(year-1)/400;

if(!(year%4) && year%100 || !(year%400))

months[1]++;

for(i=0;i<month-1;i++)

total += months[i];

total += day;

return total;

}

특정일 사이의 날짜 수를 계산

#include<stdio.h>

long total_days(int year, int month, int day);

int main(void)

{

long total;

total=total_days(2010, 7, 21) - total_days(1987, 4, 16);

printf("두날짜사이의날짜수: %ld\n", total);

return 0;

}

long total_days(int year, int month, int day)

{

int months[]={31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

int i;

long total=0L;

total=(year-1)*365L+(year-1)/4-(year-1)/100+(year-1)/400;

if(!(year%4) && year%100 || !(year%400))

months[1]++;

for(i=0;i<month-1;i++)

total += months[i];

total += day;

return total;

}

반응형
반응형

우선 컴터 초짜인 내가 대충 이해하기로는 Libxml2는 기존의 XML 파서들보다 다양한 인터페이스를 가지고 다양한 언어그룹에서 사용할 수 있는 기능을 포함하고 있는 최신형 XML Parser 인듯 하다. 사용법 또한 간단하여 이번 기회에 공부할 겸 사용해보게 되었다.
설치부터 사용까지 포스팅을 해보아요

-----------------------------------------------------------------------------------------------
설치 환경 - Linux Ubuntu 10.10

libxml2 설치 (명령문 실행 순서대로)

# wget ftp://xmlsoft.org/libxml2/libxml2-2.7.7.tar.gz
# tar xvzf libxml2-2.7.3.tar.gz
# cd libxml2-2.7.3
# ./configure --prefix=/usr/local/xml
# make
# make install
---------------------------------------------------

1. 터미널에서 wget을 통해서 다운을 받아도 되고 직접 FireFox등의 익스플로어를 사용하여 ftp://xmlsoft.org/libxml2/ 에 들어가면
최신버전을 확인하여 다운 받을 수 있다. 참고하자. (해당폴더에 압축파일을 다운 받을 수 있다)



2. 다운 받은 압축파일의 압축을 풀자.
tar 명령어에서 xvzf는 옵션인데 각 옵션의 의미는 아래와 같다.
-------------------------------------------------------- 압축풀때
-x, --extract, --get : 저장된 것에서 풀어낸다.
-v, --verbose : 처리중인 파일을 자세하게 보여준다. (압축푸는 과정 보여줌)
-f, --file [HOSTNAME:]F : 저장 파일 혹은 장치 파일 F에 저장한다.
-z,--gzip, --unzip : gzip으로 압축하거나 푼다.
-j, bzip2 : bzip2 필터를 사용하여 .bz2 파일을 푼다.
-p : 퍼미션을 유지시켜준다.

------------------------------------------------------- 압축할때
-c, --create : 새 저장파일을 만든다.

압축풀기의 예)
1. tar -zvxf (압축대상파일).tar.gz -C (목적폴더위치),
(삭제 명령어 rm -f -R (목적폴더위치)
[출처] 리눅스 tar 명령어 관련 | alan100님 글

3. 어쨋든 압축을 푼 뒤 해당 폴더에 들어간다.
4. ./configure --prefix=/usr/local/xml (여기서 configure는 환경설정, --prefix는 설치시킬 위치를 의미한다)
5. 컴파일 한뒤에 설치까지 끝마친다.

6. 끝 이제 Libxml의 설치를 마쳤다.



libxml2 설치 참고 usr - http://www.ubuntu.or.kr/viewtopic.php?p=73454
libxml2 코드(튜토리얼) 참고 usr - http://kylesoft.springnote.com/pages/5620523
libxml2 라이브러리 참고 usr - http://myweb.bcpark.net/~hosuck/gnu3/?doc=bbs/gnuboard.php&bo_table=PG_JAVASCRIPT&page=1&wr_id=29

gcc -o (dest) (source) -L/usr/local/xml/lib(prefix 한 링크주소) -lxml2(libxml2.so 혹은 a가 있는 주소)

어렵다 -ㅂ-
반응형
반응형

libxml2 란?

개요

제임스 클라크 (James Clark)가 만든 expat 파서를 펄에서 사용할 수 있도록 래리 월 (Larry Wall) 과 클라크 쿠퍼 (Clark Cooper)가 만든 펄 인터페이스 XML::Parse 을 기초로 하여 펄에서 사용되는 대부분의 XML 모듈이 만들어졌다. expat과 XML::Parser 콤비만이 펄 세계에서 유일하게 전 기능을 갖춘 XML 파서는 아니다. 이 기사에서는 대니얼 벨리아드(Daniel Velliard)의 libxml2를 펄에서 사용할 수 있게 해주는 펄 인터페이스 XML::LibXML을 살펴보겠다. 참고로 XML::LibXML는 매트 서전트(Matt Sergeant) 와 크리스챤 글란(Christian Glahn)이 만든 것이다.

또다른 XML 파서가 필요한 이유는 도대체 무엇일까?

물론 expat과 XML::Parser가 훌륭하긴 하지만 그렇다고 해서 아주 단점이 없는 것은 아니다. expat은 XML 파서 중 최초의 그룹에 속했고, 그 결과 작성된 그 시점에서 사용자들의 기대를 반영하는 인터페이스를 가지게 되었다. expat과 XML::Parser가 작성된 시기에는 DOM (Document Object Model), SAX, 또는 XPath 등이 존재하지 않았거나 열띤 논의중이어서 '표준'으로 간주되지 않았기 때문에 이들 언어에 대한 인터페이스를 구현하지 않았다. 그 결과 불행히도 대부분의 펄 XML 모듈들은 XML::Parser의 비표준 내지 표준이라고는 하기 힘든 인터페이스에 기반하여 만들어졌기 때문에 입력이 텍스트로 된 XML 문서로 (파일, 파일핸들, 스트링, 소켓 스트림) 처리하기 전에 파싱이 되어야 한다는 가정을 깔고 있다. 이것들은 단순한 경우에는 대개 잘 작동한다. 그러나 최근의 XML 애플리케이션들은 주어진 문서를 가지고 한가지 이상의 작업을 해야 하므로 처리하는 각 단계마다 문서가 스트링으로 직렬화되고 다음 모듈에 의해 다시 파싱되어야 한다는 단점이 있다.

반면 libxml2는 DOM, XPath, 그리고 SAX 인터페이스가 널리 퍼진 후에 작성되어 이 세 가지 표준을 모두 구현하고 있다. 따라서 lixml로 여러분은 파일, 스트링 등에 저장되었거나 일련의 SAX 이벤트들로부터 만들어진 문서를 파싱하여 메모리에 트리를 만들 수 있다. 이 트리들은 W3C DOM과 XPath 인터페이스를 사용하여 조작할 수도 있고 외부 이벤트 핸들러에 넘겨줄 SAX 이벤트를 만드는데 사용될 수도 있다. 이와 같은 유연성은 요즘의 XML 처리에 대한 기대를 반영하는 것으로 XML::Parser가 차지하고 있는 왕좌의 강력한 도전자로 XML::LibXML을 부상시켜 주었다.

XML::LibXML 사용하기

이 달의 컬럼은 작년 초 필자가 쓴 'Perl/XML Quickstart Guide'란 기사의 속편같이 보일지도 모르겠다. 필자가 그 기사를 쓸 당시에는 XML::LibXML이 아직 성숙하지 않은 상태였기 때문에 Quickstart에서 사용했던 것과 동일한 테스트들을 사용해 XML::LibXML을 시험해 보려고 한다. 테스트 케이스에 대한 자세한 내용은 Quickstart에 대한 첫번째 설치와 관련된 기사를 보면 된다. 요약하자면, 두 테스트는 주어진 XML 모듈에서 제공하는 기능들을 이용해 XML 문서에서 자료를 뽑아내 출력하는 방법과 펄 해시에 저장된 자료로부터 XML 문서를 프로그램적으로 만들고 출력하는지 방법을 보여준다.

읽기

XML 문서에 저장되어 있는 자료에 접근하기 위해 XML::LibXML은 표준 W3C DOM 인터페이스를 제공한다. 문서들은 노드를 가지는 트리로 취급되며 노드가 가지고 있는 자료는 노드 객체에 메소드를 호출하여 접근하게 된다.


use strict;
use XML::LibXML;

my $file = 'files/camelids.xml';
my $parser = XML::LibXML->new();
my $tree = $parser->parse_file($file);
my $root = $tree->getDocumentElement;
my @species = $root->getElementsByTagName('species');

foreach my $camelid (@species) {
    my $latin_name = $camelid->getAttribute('name');
    my @name_node  = $camelid->getElementsByTagName('common-name');
    my $common_name = $name_node[0]->getFirstChild->getData;
    my @c_node  = $camelid->getElementsByTagName('conservation');
    my $status =  $c_node[0]->getAttribute('status');
    print "$common_name ($latin_name) $status \n";
}
XML::LibXML 의 가장 흥미로운 기능 중 하나는 DOM 인터페이스는 물론이고 추가로 XPath 언어를 이용해 노드를 선택할 수 있다는 점이다. 아래의 코드에서는 XPath를 사용하여 원하는 노드를 선택함으로써 앞의 예제와 동일한 결과를 얻을 수 있음을 보여준다.

use strict;
use XML::LibXML;

my $file = 'files/camelids.xml';
my $parser = XML::LibXML->new();
my $tree = $parser->parse_file($file);
my $root = $tree->getDocumentElement;

foreach my $camelid ($root->findnodes('species')) {
    my $latin_name = $camelid->findvalue('@name');
    my $common_name = $camelid->findvalue('common-name');
    my $status =  $camelid->findvalue('conservation/@status');
    print "$common_name ($latin_name) $status \n";
}
위의 코드에서 흥미로운 점은 동일한 노드들의 트리에 DOM 과 XPath 인터페이스의 메소드 중에서 애플리케이션의 필요에 가장 잘 맞는 메소드를 섞어 쓸 수 있다는 것이다.

쓰기

XML::LibXML을 이용해 XML 문서를 만들려면 제공되는 DOM 인터페이스를 사용하기만 하면 된다.

use strict;
use XML::LibXML;

my $doc = XML::LibXML::Document->new();
my $root = $doc->createElement('html');
$doc->setDocumentElement($root);
my $body = $doc->createElement('body');
$root->appendChild($body);

foreach my $item (keys (%camelid_links)) {
   my $link = $doc->createElement('a');
   $link->setAttribute('href', $camelid_links{$item}->{url});
   my $text = XML::LibXML::Text->new($camelid_links{$item}->{description});
   $link->appendChild($text);
   $body->appendChild($link);
}
print $doc->toString;
XML::LibXML과 XML::DOM을 구분하는 중요한 차이점은 libxml2의 객체 모델이 W3C DOM 레벨 2 인터페이스에 맞기 때문에 이것이 XML 네임스페이스를 가지고 있는 문서를 더 잘 다룰 수 있다는 점이다. 따라서 XML::DOM은 아래같이 제한적이다.

@nodeset = getElementsByTagName($element_name);
그리고

$node = $doc->createElement($element_name);
XML::LibXML 은 아래와 같이 할 수도 있다.

@nodeset = getElementsByTagNameNS($namespace_uri, $element_name);
그리고

$node = $doc->createElementNS($namespace_uri, $element_name);
SAX가 제공하는 즐거움

이상으로 우리는 XML::LibXML이 제공하는 DOM과 XPath의 장점을 살펴보았다. 그러나 여기서 이야기가 끝나는 것은 아니다. libxml2 라이브러리는 SAX 인터페이스도 제공하여 SAX 이벤트에서부터 DOM 트리를 만들거나 DOM 트리에서 SAX 이벤트를 만들 수도 있다.

아래의 코드는 XML::SAX::Base에 기반을 둔 SAX 드라이버에서 프로그램을 통해 DOM 트리를 만들어낸다. 이 예제에서 초기 SAX 이벤트들은 CamelDriver 클래스에 구현된 커스텀 드라이버로부터 발생되며 CamelDriver 클래스는 XML::LibXML::SAX::Builder 클래스를 호출하여 DOM 트리를 만든다.


use XML::LibXML;
use XML::LibXML::SAX::Builder;

my $builder = XML::LibXML::SAX::Builder->new();
my $driver = CamelDriver->new(Handler => $builder);
my $doc = $driver->parse(%camelid_links);

# doc is an XML::LibXML::Document object
print $doc->toString;

package CamelDriver;
use base qw(XML::SAX::Base);

sub parse {
  my $self = shift;
  my %links = @_;
  $self->SUPER::start_document;
  $self->SUPER::start_element({Name => 'html'});
  $self->SUPER::start_element({Name => 'body'});

  foreach my $item (keys (%camelid_links)) {
    $self->SUPER::start_element({Name => 'a',
                                   Attributes => {
                                     'href' => $links{$item}->{url}
                                               }
                                });
    $self->SUPER::characters({Data => $links{$item}->{description}});
    $self->SUPER::end_element({Name => 'a'});
  }

  $self->SUPER::end_element({Name => 'body'});
  $self->SUPER::end_element({Name => 'html'});
  $self->SUPER::end_document;

}
1;
XML::LibXML::SAX::Generator를 이용해 기존의 DOM 트리에서 SAX 이벤트를 발생시킬 수도 있다. 아래의 코드에서 camelids.xml 파일을 파싱하여 만들어진 DOM 트리가 XML::LibXML::SAX::Generator의 generate() 메소드에 넘겨지면 XML::Handler::XMLWriter 에 있는 이벤트 핸들러가 호출되어 문서를 STDOUT으로 출력하게 된다.

use strict;
use XML::LibXML;
use XML::LibXML::SAX::Generator;
use XML::Handler::XMLWriter;

my $file = 'files/camelids.xml';
my $parser = XML::LibXML->new();
my $doc = $parser->parse_file($file);
my $handler = XML::Handler::XMLWriter->new();
my $driver = XML::LibXML::SAX::Generator->new(Handler => $handler);

# generate SAX events that are captured
# by a SAX Handler or Filter.
$driver->generate($doc);

SAX 이벤트를 받아들이고 내보낼 수 있는 기능은 최근 이 컬럼에서 논의되었던 비XML 자료에서 SAX 이벤트를 발생시키고 SAX 필터 체인을 작성하는 것과 연관지어 생각해보면 아주 유용하다고 볼 수 있다. 예를 들면 펄로 쓰여진 SAX 드라이버를 이용해 데이터베이스 질의에서 가져온 자료에 기반을 두고 이벤트를 발생시켜 DOM 객체를 만들 수 있다. 이 DOM 객체는 C-space에서 디스플레이를 위해 XSLT와 굉장히 빠른 libxslt 라이브러리 (이것은 libxml2 DOM 객체를 기대한다)를 사용해 변환되고, 변환된 DOM 트리에서 커스텀 SAX 필터를 이용한 추가 처리를 위해 SAX 이벤트를 발생시켜 최종 마무리를 한다. 즉 이 모든 것을 하면서 스트링을 다시 파싱하기 위해 문서를 직렬화할 필요가 한 번도 없다는 이야기다. 정말 놀랍지 않은가!

결론

이상 우리가 살펴본 것처럼 XML::LibXML은 XML 처리에 대해 빠르면서도 최신식의 접근방식을 제공한다. 이것은 제 1세대라 할 수 있는 XML::Parser와 비교해 볼 때 보다 여러 가지 방면에서 뛰어나다고 볼 수 있다. 그러나 필자의 말을 오해하지 말길 바란다. XML::Parser와 그에 의존하는 모듈은 여전히 유용하고, 잘 지원되고 있기 때문에 짧은 시간 안에 사라질 위험이 있을 것 같지는 않다. 하지만 그것이 유일한 방법은 아니며 XML::LibXML이 제공하는 유연성을 생각한다면 다음 번 Perl/XML 프로젝트를 시작하기 전에 다시 한 번 XML::LibXML을 자세히 들여다 보라고 강력히 권하고 싶다.

 

저자: 킵 햄튼(Kip Hampton), 역 정직한
출처 : 네이버 리눅스유저 그룹 까페
원본 주소 - http://cafe.naver.com/linuxcare.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=2145



반응형

+ Recent posts