Archive for the ‘development’ Category
Published by
Nineye under
development on
July 1, 2009
글을 올리다보면 가끔 특수 문자로도 표현이 되지 않는 수학 공식과 같은 표현이 필요할 때가 있다. 물론 수학 공식이 표현 가능한 문서 편집 툴에서 만들어, 이미지화 해서 글에 첨부시켜도 되지만 이 작업은 꽤 시간이 많이 걸리는 작업이다. 하나의 글을 작성할 때, 그러한 표현을 만들고 첨부하는데 걸리는 시간이 더 길 수도 있는, 배보다 배꼽이 더 큰 현상도 발생할 수 있다.
따라서 어떻게 하면 이런 작업들을 쉽게 할 수 있을까 생각하다가 찾은 방법에 대해 다음과 같은 순서대로 쓰도록 하겠다.
1. LaTeX 및 관련 툴
2. 웹페이지로의 LaTeX 적용
3. 워드프레스에서의 LaTeX
1. LaTeX 및 관련 툴
TeX는 Donald Knuth가, – 1)일반인들도 쉽게 책을 만들 수 있는 방법을 제시 2)어떤 시스템을 가진 컴퓨터에서든지 동일한 형식으로 보여지는 문서를 작성 – 의 이유를 위해 만든 조판(typesetting) 시스템이며, TeX의 문법이 일반인이 보기에는 너무 어려운 형식으로 되어 있어서 이를 쉽게 표현하기 위해 나온 것이 LaTeX이다(TeX와 LaTeX는 wikipedia에서 찾아보면 잘 설명되어 있다).
요즘은 TeX는 거의 이용되지 않고, 대신 LaTeX가 많이 이용되므로 LaTeX를 사용하기로 한다. LaTeX는 수학 공식 뿐만 아니라, 물리학, 화학, 컴퓨터 공학 등 다양한 학문의 공식 뿐만 아니라, 도표 및 그래프까지 표현 가능하므로 상당히 많은 분야에서 사용되고 있으며, 추가적인 패키지의 설치로 이런 것들을 모두 지원해 줄 수 있다.
과거에는 LaTeX 프로그램으로 tetex를 많이 사용했었는데, tetex는 2006년까지만 진행되고 프로젝트가 중단되었으며, 현재는 texlive 프로젝트가 tetex에 이어 진행되고 있다. 우선, texlive는 설치하기도 까다롭고 엄청난 용량(full 설치가 약1.8gb)때문에 tetex를 설치하였지만, 그 이후에 설치한 몇가지 패키지가 호환이 되지 않아, 결국 texlive를 설치하기로 하였다. 현재는 tetex를 모두 제거하고 texlive를 설치중이다(언제 끝날지는 모르겠지만.. ㅡㅡ;;). texlive의 설치는 http://tug.org/texlive/acquire.html 이 페이지로 가서 “TeX Live installation over the Internet” 부분을 보고 설치하자. 아래는 tetex와 texlive의 설치 모두를 설명하는데, 충돌이 발생할 수도 있다고 하니, 둘 중 하나만 설치하자.
texlive : CTAN에서 제공하는 small network installer package 중, 자신의 os에 맞는 install-tl 파일을 받아서 설치하면 된다. 필자는 install-tl-unx.tar.gz 파일을 받아서 압축을 풀고 install-tl 스크립트를 실행하여서 설치 중이다. 설치가 성공적으로 끝나면 글을 남길 것이다.
tetex package : 기본적인 TeX 프로그램. TeX구문을 dvi(device independent file format) 파일 포맷으로 변환시켜 줌.
tetex-latex package : 기본적인 LaTeX 프로그램. LaTeX구문을 dvi 파일 포맷으로 변환시켜 줌. 또한 기본적인 LaTeX문법도 포함하고 있음.
tetex-dvips : dvi 파일 포맷을 ps(PostScript) 형식으로 변환시켜 주는 dvips명령어를 담고 있음.
ImageMagick package : ps 형식으로 표현된 내용을 이미지로 변환시켜 주는 convert 명령어를 담고 있음. 또한, dvi 파일 포맷으로 표현된 내용을 png나 gif 이미지 파일 포맷으로 변환시켜 주는 dvipng, dvigif 명령어를 담고 있음(정확히 이 package가 담고 있는지는 확인해 보지 않음.. ㅡㅡ;;).
이 정도면 설치해야 할 패키지는 모두 다 설치한 것 같다. 앞서 말했지만, 필자의 os는 CentOS-64이고, yum을 통해 설치할 수 있는 패키지만 나열하였다. 다른 os및 설치 프로그램에서는 다른 방식으로 설치해야 할 것이다. texlive는 앞으로 계속 진행될 프로젝트이기 때문에, 가능하면 texlive 관련 패키지들을 설치하는 것이 좋을 것 같다.
2. 웹페이지로의 LaTeX 적용
여기서는 사용자가 LaTeX 구문을 입력하고, 서버에서는 그 구문에 해당하는 이미지를 보여주는 단순한 내용에 대해서, 필자의 서버 환경에 맞게 적용해 본 예제를 바탕으로 설명한다.
원래는 LaTeX를 어떻게 적용했는지, LaTeX를 이용하여 멋있게 그려보려고 했는데, pgf/tikz 패키지가, 필자가 설치한 tetex와 호환이 되지 않는 것 같아서 걍 글로 나열한다. 이것 때문에 열라 삽질했는데 결국 해결책을 찾지 못했다. tetex 마지막 버전이 2006년이고, pgf/tikz 패키지는 2009년 6월 2일이 마지막 버전이니 호환되지 않으려니.. 하고 넘어간다. 사실 tetex를 지우고 texlive를 설치하려고 했는데, 1gb가 넘는 방대한 용량때문에.. 포기했다. 나중에 수학 공식 이외의 용도로 쓸때에나 설치를 생각해 봐야겠다.
LaTeX의 이용은 다음과 같은 순서로 이용한다.
1) html 페이지의 폼에 LaTeX구문을 입력
2) 입력된 구문과 동일한 구문이 캐쉬에 존재하면 7)번으로 ㄱㄱ 아니면 다음 라인으로..
3) latex 명령을 수행하여, 입력된 구문에 해당하는 dvi 파일을 만듦.
4) dvips 명령을 수행하여, dvi 내용에 해당하는 ps 포맷 파일을 만듦.
5) convert 명령을 수행하여, ps 포맷의 내용을 이미지화 한, png 파일을 만듦.
6) 만들어진 png 파일을 캐쉬 장소에 넣음.
7) 해당 이미지 파일의 url을 사용자에게 제공.
즉, 간단히 설명하면 사용자로부터 입력받은 LaTeX 구문을 분석하고, 그 분석된 내용에 대한 이미지를 생성한 뒤, 사용자가 그 이미지를 웹페이지에서 볼 수 있도록 url을 제공해 주는 것이다. 만들때 마다 서버에 부하는 좀 있을 것 같지만, 같은 구문에 대해서는 이전에 만들어진 이미지를 그대로 제공하니, 속도는 빠를 것 같다.
추가적으로, dvipng 명령을 수행하면, 4)와 5)번의 절차를 대체할 수 있다. ps 파일이 필요없다면 dvipng 명령을 사용하여 바로 이미지를 생성하자.
다음은 필자가 위 내용을 구현한 코드이다. 연습용 코드라 구조화 하지 않았으니 단순히 참고만 해주시길…
<html>
<head>
<title>LaTeX test</title>
</head>
<body>
<form action="latex_test.php" method="post">
<textarea rows="20" cols="60" name="render_text"></textarea><br/>
<input name="submit" type="submit" value="Render"/>
</form>
<?php
if (isset($_POST['submit'])) {
echo '<h1>Result</h1>';
require('render.php');
$text = $_POST['render_text'];
if (get_magic_quotes_gpc()) {
$text = stripslashes($text);
}
echo transform($text);
}
?>
</body>
</html>
사용자가 LaTeX구문을 입력할 수 있는 폼을 제공하고, 사용자가 입력 버튼을 누르면, 다음 소스인 render.php 내의 transform()함수를 호출하여 그 결과를 사용자에게 보여준다.
<?
function transform($text) {
$LATEX_PATH = "/usr/bin/latex";
$DVIPS_PATH = "/usr/bin/dvips";
$CONVERT_PATH = "/usr/bin/convert";
$DVIPNG_PATH = "/usr/bin/dvipng";
$TMP_DIR = "/home/webmain/www/latex_test/tmp";
$CACHE_DIR = "/home/webmain/www/latex_test/cache";
$URL_PATH = "http://nineye.net/latex_test/cache";
preg_match_all("/\[tex\](.*?)\[\/tex\]/si", $text, $matches);
for ($i = 0; $i < count($matches[0]); $i++) {
$position = strpos($text, $matches[0][$i]);
$thunk = $matches[1][$i];
$hash = md5($thunk);
$full_name = $CACHE_DIR . "/" . $hash . ".png";
$url = $URL_PATH . "/" . $hash . ".png";
if (!is_file($full_name)) {
render_latex($thunk, $hash, $LATEX_PATH, $DVIPS_PATH
, $CONVERT_PATH, $TMP_DIR, $CACHE_DIR);
cleanup($hash, $TMP_DIR);
}
$text = substr_replace($text
, "<img src=\"$url\" alt=\"Formula: $i\" />"
, $position, strlen($matches[0][$i]));
}
return $text;
}
function render_latex($thunk, $hash, $bin_latex, $bin_dvips, $bin_conv
, $tmp_dir, $cache_dir) {
$thunk = wrap($thunk);
$current_dir = getcwd();
chdir($tmp_dir);
// create temporary LaTeX file
$fp = fopen($tmp_dir . "/$hash.tex", "w+");
fputs($fp, $thunk);
fclose($fp);
// run LaTeX to create temporary DVI file
$command = $bin_latex . " --interaction=nonstopmode " . $hash . ".tex";
exec($command);
// run dvips to create temporary PS file
$command = $bin_dvips . " -E $hash" . ".dvi -o " . "$hash.ps";
exec($command);
// run PS file through ImageMagick to
// create PNG file
$command = $bin_conv . " -density 120 $hash.ps $hash.png";
exec($command);
// copy the file to the cache directory
copy("$hash.png", $cache_dir . "/$hash.png");
chdir($current_dir);
}
function wrap($thunk) {
return <<<EOS
\documentclass[10pt]{article}\usepackage{amsmath}\usepackage{amsfonts}\usepackage{amssymb}\usepackage{pst-plot}\usepackage{color}\pagestyle{empty}\begin{document}$thunk\end{document}
EOS;
}
function cleanup($hash, $tmp_dir) {
$current_dir = getcwd();
chdir($tmp_dir);
unlink($tmp_dir . "/$hash.tex");
unlink($tmp_dir . "/$hash.aux");
unlink($tmp_dir . "/$hash.log");
unlink($tmp_dir . "/$hash.dvi");
unlink($tmp_dir . "/$hash.ps");
unlink($tmp_dir . "/$hash.png");
chdir($current_dir);
}
?>
각 함수에 대해 설명하면, transform()함수는 서버의 환경에 맞게 변수를 세팅하고,
~
를 하나의 아이템으로 하는 리스트를 만든다. 그리고 각 아이템에 대해서 render_latex()함수를 호출하여 변환을 수행한다. 여기서, 이전에 이미 생성되어서 다시 생성할 필요가 없는 구문은 변환을 수행하지 않고, 단지 이미지 url만 전달한다. 구문의 체크는 md5()함수를 통해 생성된 문자열로 하는데, 사실 구문이 달라도 동일한 문자열이 생성될 가능성이 있기 때문에 100% 정확하다고는 할 수 없다. 다른 방법을 생각하길…
render_latex()함수는 입력된 구문을 각 변환 실행문의 입력으로 넣어서 결과적으로 사용자에게 보여줄 이미지를 생성한다. 위 코드상에는 dvips와 convert 명령을 이용하여 ps포맷의 파일이 생성되는 중간 과정을 거치지만, 앞서 말했듯이 dvipng 명령만을 이용하여 바로 png파일을 생성할 수도 있다.
wrap()함수는 변환을 수행할 때 자주 사용되는 형식들을 지정(preamble이라 부름)하고, 파싱할 문자열을 입력 형식에 맞춰주는 역할을 한다.
cleanup()함수는 변환의 중간 과정에 생성된 파일(latex(.log, .tex, .dvi), dvips(.ps), dvipng, convert(.png))들을 삭제하는 역할을 한다.
자, 이제 모든 준비가 끝났다. 입력폼에 LaTeX 구문을 넣어보자. 혹시 구문에 대한 이미지가 제대로 나타나지 않는다면 tmp디렉토리내의 *.log 파일을 확인하면 상태를 확인할 수 있다.
참고로, 앞에서도 얘기했지만 현 소스는 단순한 참고용으로만 사용하자. 일반적으로 변환된 이미지는 앞으로도 계속 보여져야 하기 때문에 체계적으로 유지가 되어야 한다. 따라서 장기간 이용의 용도로 사용하려면 제대로 설계해서, 개발보다 유지보수가 더 힘드는 상황이 발생하지 않았으면 한다.
3. 워드프레스에서의 LaTeX
워드프레스에서는 LaTeX와 관련된 훌륭한 플러그인들을 제공한다. 사실 필자가 구조화 되지 않은 예제만을 작성해 본 이유도, 실제 적용은 워드프레스 플러그인으로 했기 때문이다. 필자가 사용한 플러그인은 WP_LaTeX라는 플러그인이다. 필요한 부분만 적절하게 잘 구현되어 있는 것 같으며, 많은 사람들이 이용하기 때문에 꽤 안정화 된 느낌이다.
워드프레스에 적용하기 위해서 해야 하는 일은 극히 적다. 플러그인을 설치하고 테스트 해 보면 끝이다. 처음에는 php의 한 모듈로 제공된다고 착각해서 엄청난 삽질을 했었다. 알고 보니 단순히 명령의 실행이 끝이었지만…ㅋ LaTeX 기능을 설치한다고 오늘 하루를 날렸지만 얻은 것은 많은 것 같다.
이로써, 수학공부 준비 끝~! 사실 필자의 블로그에 LaTeX 기능을 넣은 이유는, 수학 공부를 하면서 중요한 내용들을 까먹지 않게, 블로그에 정리해 놓으려고 하는데 수학 공식들은 표현하기 어렵기 때문이었다. 이제 준비도 어느 정도 됐으니… – precalculus 및 calculus 서적도 구입했고(아직 도착하지 않았지만..), 점심 때 노트도 샀고, 중요한 내용을 정리 할 블로그 포스팅 기능도 넣었고… 작심 삼일이 될 수도 있지만 어쨌건 내일부터는 시작해 봐야겠다…ㅋ
Leave a Reply
Published by
Nineye under
development on
April 6, 2009
필자가 만드는 서버의 구조 개선을 하다가, 구조적인 문제로 다음과 같은 상황을 접하게 되었다.
수십 수백 개의 thread들이 경쟁하는 상황에서, lock에 사용되는 mutexA와 mutexB가 있을 때, 두 mutex의 critical section이 아래와 같은 모습이 되면 dead lock이 발생될 수 있을까?
…
Lock(mutexA);
…
Lock(mutexB);
…
Unlock(mutexB);
…
Unlock(mutexA);
…
일단 처음에는 dead lock이 발생할 수 있을 것 같다고 생각했으나, 계속 생각하다 보니, 논리적으로 문제가 없을 것 같다는 생각을 했다.
왜냐면 thread1과 thread2가 있을 때, 어느 thread가 Lock(mutexA); 와 Unlock(mutexA); 사이의 어떤 위치에서 context switching을 하든지 간에, 다른 thread는 이미 mutexA때문에, 먼저 진입한 thread가 critical section을 빠져 나오기 전에는 절대 진입할 수 없기 때문에 안전하다고 생각했다.
다만, 여기서 보장해야 하는 것은, 모든 critical section을 모습이 위와 같다는 것을 보장해야 한다는 것이다. 즉, mutexA의 critical section내에 mutexB의 critical section이 있는 모습과, mutexB의 critical section내에 mutexA의 critical section이 있는 모습이 공존하면 이것은 100% dead lock 발생 가능성이 있는 것이다.
어쨌든 위와 같은 모습을 가지는 구조로 서버를 만들어서 다음과 같은 환경에서 스트레스 테스트를 하였을 때, Dead lock이 발생하지 않았다.
Server :
클라이언트 접속 thread 개수 : 5개 (5개 중에 하나의 thread를 확보하지 못한 클라이언트 접속은 queue에서 대기)
작업 실행 thread 개수 : 3개 (3개 중에 하나의 thread를 확보하지 못한 작업은 queue에서 대기)
풀링 방식 : linux condition signal을 이용
Client :
서버로의 접속 thread 개수 50개(각 thread마다 socket fd를 하나씩 가짐)
작업 내용 :
Thread의 종류와 실행 횟수에 따라 서로 다른, 약 20byte의 메시지를 서버로 보내고 동일한 메시지를 받는 작업.
클라이언트가 받는 메시지가 보낸 메시지와 동일하면 성공으로 체크, 아니면 실패로 체크
각 thread마다 10000번의 메시지를 보내고 받음.
Timeout은 나지 않도록 설정했고, sleep코드는 없음.
위와 같이 테스트 했을 때, 실패로 체크된 작업은 하나도 없었으며, dead lock도 발생하지 않았다.
그러면 위의 중첩적인 critical section이 왜 필요한지 의문을 제기하는 분들이 있을 것 같아, 왜 이렇게 구조가 만들어졌는지 이유에 대해 설명하도록 하겠다.
mutexA : condition signal variable의 동기화를 위한 mutex
mutexB : task queue의 동기화를 위한 mutex라고 했을 때,
우선, condition signal에 대한 연산은, 해당 condition signal variable에 등록되는 thread들에 대해서 기본적으로 mutex를 요구한다. 왜냐면 condition signal variable 자체도 등록된 thread들에 의해, 동기화 문제를 발생시킬 수 있기 때문이다.
또한, 처리할 작업을 담고 있는 queue도, thread들에 의해 공유될 수 있기 때문에 mutex를 요구한다.
여기서, 문제는 두 mutex를 하나로 쓸 수는 없다는 것이다. 왜냐면 condition signal을 위한 mutex는 Condition signal variable에 등록되는 thread들에 대해서만 동기화를 보장해 주면 되나, 작업큐는 condition signal variable에 등록된 thread들(pop()연산을 수행) + main thread 또는 connection pool thread들(push()연산을 수행)에 대해서 모두 동기화를 보장해야 하기 때문이다.
물론 위의 작업큐와 관련된 모든 thread들을 포함하여 하나의 mutex로 동기화 시킬 수도 있으나, 그러면 동기화 될 필요가 없는, condition signal variable에 등록된 thread들과 main thread 또는 connection pool thread들이 모두 동기화 되어 버리는 문제가 있다. 이것은 처리 지연으로 이어질 수 있다.
위의 이유로 mutex는 두 개로 분리되어야 하는데… 그러면 각자의 critical section은 분리될 수 있지 않을까?라는 의문이 생긴다.
예를 들면,
…
Lock(mutexB);
if (!taskq.size()) {
Unlock(mutexB);
Lock(mutexA);
Pthread_cond_wait();
UnLock(mutexA);
// 이 시점이 문제!!!!
Lock(mutexB);
}
Job = taskq.pop();
Unlock(mutexB);
…
Queue의 size체크 때문에 동기화 코드가 좀 복잡해 보이긴 하지만, 중첩된 critical section은 아니다.
Queue의 size체크 전에 queue mutex를 lock하고, size 체크 후에, Queue가 비었다면 queue mutex를 unlock하고, Condition signal mutex를 lock하고, condition signal wait 상태로 들어가게 된다. 그리고, 누군가 condition signal을 보내줬을 때, condition signal mutex를 lock하고 깨어나게 된다.
여기서 critical section이 중첩되게 하지 않으려면 condition signal mutex를 unlock하고, Queue mutex를 lock해줘야 한다.
바로 여기서~ 문제가 발생한다!(이 시점이 문제!!!!라고 적혀있는 곳)
Condition signal을 보냈다는 것은 누군가 비어 있는 queue에 작업을 넣었음을 의미한다. 따라서 그 작업을 처리하기 위해 condition signal wait 상태에서 깨어나게 되고, 깨어나면서 자동으로 condition signal mutex는 lock된 상태이기 때문에, 다시 queue mutex를 lock하기 위해 잠시 condition signal mutex를 unlock하게 된다. 바로 이 시점에서 context switching이 일어나서, 같은 위치에서 context switching이 일어나서 sleep상태에 있던 어떤 다른 thread가 동작하게 되면, 작업을 queue로부터 빼갈 것이고, 이전에 작업이 들어왔다고 깨어난 thread는 자신의 작업을 잃게 된다.
물론 작업이 있다고 깨어났는데 작업이 없을 때 에러 처리를 하면 그만이지만, 이런 문제는 원천적으로 제대로 동작하게 해놓지 않으면 향후 무슨 문제가 일어날 지 예측하기 힘들어지기 때문에, 이렇게 원천적으로 문제를 봉쇄하는 것이 옳다고 생각해서 이런 구조로 만들게 되었다.
필자는 동기화에 대한 구현은 가장 심플하고 명확하게 구현되어야 한다고 생각한다. 하지만 이렇게 서로 다른 동기화를 가져야 하는 thread들의 그룹들이 모일 때는, 위처럼 작업의 수행 지연 현상을 막기 위해서라도 복잡한 동기화가 발생할 수 밖에 없다. 이럴 때는 이러한 복잡한 동기화를 어떻게 가장 심플하게 구현할 수 있을까 고민을 해야한다고 생각한다. 왜냐면 동기화에 대한 구현은 심플하지 않으면 향후 유지보수에서 문제가 발생할 가능성이 크고, 일단 문제가 발생하면 해결하기 가장 어려운 문제중의 하나이기 때문이다.
필자는 이러한 중첩된 동기화에 대해, 객체 자신이 동기화에 대한 보장을 해주는 objectlock이라는 구조를 사용했으며, 다음에 시간이 나면 이 내용에 대해서도 포스팅 하도록 하겠다(시간이 나면…ㅋ).
Leave a Reply
Published by
Nineye under
development on
April 2, 2009
이전 글에서는 dialog에 CHtmlView를 어떻게 붙이는지 알아보았고, 이전 글에서 말했듯이, 이제 CHtmlView에서 html 페이지를 어떻게 컨트롤 하는지 알아보겠다.
사실 필자가 이것을 하게 된 계기는, 이전 글에서 만들려고 했던 html page output 창에서, 스크롤에 특정 기능을 필요로 해서이다. 이 기능이 뭔지 게임의 채팅창을 예로 들면, 게이머는 항상 채팅창에 새로 올라오는 글을 읽고 싶어하는 동시에 스크롤 해서 읽고 있는 부분은 계속 유지하고 싶은 것이다. 따라서 게임의 채팅창처럼 스크롤이 맨 아래로 가 있으면 새로 입력되는 output에 대해, 자동으로 스크롤이 맨 아래로 내려가서 계속 보이도록 하고, 스크롤이 그 이외의 위치에 가 있으면, 새로 입력되는 output이 있어도 현재 스크롤의 위치를 유지하는 것이다.
여기서 필자가 CHtmlView의 성격을 잘 몰라서 삽질을 한 부분이 있는데, 처음에는 일반적인 view처럼, view와 연결된 document의 크기에 따라 스크롤이 생기며, 그 스크롤을 view의 스크롤 이벤트 및 member로 컨트롤 할 수 있을거라고 생각했다. 하지만 이것은 view의 스크롤 사이즈를 조정하니 스크롤이 두 개가 나란히 생기는 것을 목격하고 완전 잘못 생각했다는 것을 알았다. 즉, 가장 오른쪽에 새로 생긴 스크롤은 view의 스크롤이며, 바로 좌측의 처음부터 있던 스크롤은 web browser control의 스크롤이었던 것이다. CHtmlView에 포함되는 document는 ActiveX기반의 webbrowser control이며, 따라서 필자의 어플리케이션에서 document를 컨트롤 하려면 COM과의 통신 interface를 이용해서 컨트롤 해야하는 것이다.
여기까지 알아낸 후, 필자가 원하는 스크롤의 기능을 하는 다음과 같은 코드를 만들게 되었다.
int OutputHtml::scroll_check() {
HRESULT hr;
IDispatch* disp = GetHtmlDocument();
if (!disp) {
return -1;
}
IHTMLDocument2* doc = NULL;
hr = disp->QueryInterface(IID_IHTMLDocument2, (void**)&doc);
if (!SUCCEEDED(hr) || doc == NULL) {
return -1;
}
IHTMLElement2* body = NULL;
hr = doc->get_body(&body);
if (!SUCCEEDED(hr) || body == NULL) {
return -1;
}
long cur_height;
body->get_scrollTop(&cur_height);
long max_height;
body->put_scrollTop(20000000);
body->get_scrollTop(&max_height);
if (cur_height != max_height) {
body->put_scrollTop(cur_height);
}
return 0;
}
흠.. 간단한 코드이다. 요약하면 COM interface를 이용하여 web browser control의 객체를 얻어와서, 그 객체의 member를 이용하여 스크롤바를 컨트롤 하는 것이다. 하지만 여기서 끝날 수가 없지… 여기서 끝났다면 필자는 이 글을 쓰지 않았을 것이다.
문제는 vc6.0에서 빌드를 했는데 IHTMLElement2에 대한 정의가 없다는 컴파일 오류를 내는 것이다. 왜 없을까 찾아보다가, vc6.0이 기본적으로 지원하는 COM interface중, html element 객체에 대한 interface로 IHTMLElement가 있는데, 문제는 IHTMLElement interface에는 구현되지 않은 내용이 많다는 것이다. 예를 들면 위의 소스 중, IHTMLElement2::put_scrollTOP()과 IHTMLElement2::get_scrollTop()은 IHTMLElement에는 없는 내용이며, 궂이 스크롤을 제어하자면 IHTMLWindow2::scrollTo() 함수를 이용하면 스크롤의 위치는 옮길 수 있으나, 현재 스크롤의 위치를 얻어 오는 방법은 없다. 결국 get_scrollTop(), put_scrollTop()을 제공해 주는 IHTMLElement2에 대해 찾아보다가, 이것은 Windows Server 2003 SP1 Platform SDK를 설치하면 들어있다는 것을 알고 설치했는데… 헉!!! 이 SDK는 vs.NET이나 vs2003 이상이다… 우리팀은 구버전의 EVC3.0과 EVC4.0의 개발 환경을 지원해 주기 위해 일부러 vs6.0을 사용하고 있기 때문에… ㅡㅡ;;; 위 SDK의 contents.htm 파일에서는 친절하게도 다음과 같이 설명해 주고 있다.
If you are using Visual C++ 6.0, it is recommended that you upgrade because the import libraries included with this release of the Platform SDK have a different format than that used by Visual C++ 6.0.
뭐, 어떻게 삽질하면 대충 맞출 수 있을 것도 같았지만 필자는 삽질을 싫어하기 때문에… 그것도 m$사의 제품으로 삽질하기는 정말 싫다…
어쨌든 상황이 이러해서 고민하다가 문득 생각난 것이, COM interface에서 제공해 주는 web browser control의 객체들은 html페이지 내에서 javascript로 컨트롤 가능한 객체들이며, 단지 구 버전의 m$ 라이브러리가 모든 COM interface를 매핑시켜 놓지 않았기 때문에 사용하지 못할 뿐이다(vc6.0이 처음 나올 때에는 실제 web browser control에서도 get_scrollTop()이나 put_scrollTop()이 없어서 그랬을 지도 모른다). 어쨌든 웹페이지 내에서 제어를 하게 하고, 검증 프로그램에서는 단지 output에 내용이 추가될 때마다, 웹페이지에 정의해 놓은 제어를 시작만 시켜주면 되지 않을까라는 생각이었다. 곰곰히 생각해 보니 되지 않을 이유가 없었다. 그래서 이 방향으로 문제를 해결하기로 했다.
우선 output창에서 띄울 기본 html파일에 스크롤을 제어하는 다음의 javascript 함수를 추가했다.
<script language="javascript">
last_maxloc = 0;
function scroll_check() {
cur_loc = document.body.scrollTop;
window.scrollBy(0, 20000000);
max_loc = document.body.scrollTop;
if (cur_loc < last_maxloc) {
document.body.scrollTop = cur_loc;
}
last_maxloc = max_loc;
}
</script>
그리고는 class OutputHtml에 javascript함수를 호출할 수 있는 다음과 같은 기능을 가진 함수를 추가했다.
int OutputHtml::js_call(CString& funcname, std::vector<thlib::thString>& args) {
HRESULT hr;
CComPtr<IDispatch> disp = GetHtmlDocument();
if (!disp) {
return -1;
}
CComPtr<IHTMLDocument2> doc = NULL;
hr = disp->QueryInterface(IID_IHTMLDocument2, (void**)&doc);
if (!SUCCEEDED(hr)) {
return -1;
}
// IDispatch for java script objects
CComPtr<IDispatch> js_disp = NULL;
hr = doc->get_Script(&js_disp);
if (!SUCCEEDED(hr)) {
return -1;
}
//find dispid for given function in the object
CComBSTR cbstr(funcname);
DISPID dispid = NULL;
hr = js_disp->GetIDsOfNames(
IID_NULL, &cbstr, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
if(!SUCCEEDED(hr)) {
return -1;
}
// put parameters
int arg_cnt = args.size();
DISPPARAMS d_arg;
memset(&d_arg, 0, sizeof(d_arg));
d_arg.cArgs = arg_cnt;
d_arg.rgvarg = new VARIANT[d_arg.cArgs];
d_arg.cNamedArgs = 0;
CString cs_str;
for (int i = 0; i < arg_cnt; i++) {
cs_str = args[arg_cnt - 1 - i].c_str();
CComBSTR bstr = cs_str;
bstr.CopyTo(&d_arg.rgvarg[i].bstrVal);
d_arg.rgvarg[i].vt = VT_BSTR;
}
EXCEPINFO excpt;
memset(&excpt, 0, sizeof(excpt));
CComVariant cv_rlt;
UINT arg_err = (UINT)-1; // initialize to invalid arg
// call java script function
hr = js_disp->Invoke(dispid, IID_NULL, 0, DISPATCH_METHOD
, &d_arg, &cv_rlt, &excpt, &arg_err);
delete [] d_arg.rgvarg;
if (!SUCCEEDED(hr)) {
return -1;
}
// if you want the result value of java script function, use cv_rlt
return 0;
}
이제 이 js_call함수는 첫 번째 인자인 funcname을 함수명으로 가지고, 두 번째 인자인 args std::vector를 인자로 가지는 javascript 함수를 호출하게 된다. 함수 마지막 부분의 CComVariant cv_rlt;를 이용하면 호출한 javascript함수가 리턴한 값을 얻어올 수 있다. 하지만 필자에게 리턴값은 중요치 않아서 사용하지 않았다.
이렇게 하니 처음에 하고자 했던 스크롤의 기능이 잘 작동하였다. 한 번 javascript 함수를 호출하는데 맛들이니, 다양한 기능들을 javascript로 구현해서 결국 처음에 띄우는 html파일의 내용이 엄청 커졌다는…
그리고 메모리상의 html페이지에 output내용을 추가하는 다음의 함수를 만들었다.
int OutputHtml::add_out(thlib::thString& content) {
IDispatch* disp = GetHtmlDocument();
// only if a resource or web page is loaded can we get the document
if (disp != NULL) {
IHTMLDocument2* htmldoc;
HRESULT hr = disp->QueryInterface(
__uuidof(IHTMLDocument2), (void**)&htmldoc);
disp->Release();
if (SUCCEEDED(hr)) {
// get access to the internal document
IHTMLElement *el;
hr = htmldoc->get_body(&el);
if (!el) {
return -1;
}
if (SUCCEEDED(hr)) {
USES_CONVERSION;
BSTR bs_wh, bs_add;
CString cs_wh(_T("beforeEnd"));
CString cs_add;
cs_add = content.c_str();
bs_wh = SysAllocString(T2OLE(cs_wh));
bs_add = SysAllocString(T2OLE(cs_add));
el->insertAdjacentHTML(bs_wh, bs_add);
SysFreeString(bs_wh);
SysFreeString(bs_add);
el->Release();
}
}
}
scroll_check();
check_warnning(content);
return 0;
}
처음에는 IHTMLElement::get_innerHTML()로 body의 내용을 가져와서 거기다가 추가할 문장을 더하고 IHTMLElement::put_innerHTML()로 다시 body의 내용을 넣었었는데, body의 내용이 많아지면 많아질수록 엄청나게 비효율적일 것 같아서 더 찾아보니, IHTMLElement::insertAdjacentHTML()이란 함수가 있었다. 이것은 선택된 element의 특정 위치에 내용을 추가하는 함수이다. 과연 이 함수가 얼마만큼의 부하를 가지는지는 모르겠지만, 적어도 전체 body의 내용을 가져와서 다시 그 body를 html페이지에 넣는 부하는 없을테니 더 나을거라는 생각이 든다. 어쨌든 결국은 별의 별 기능을 다 가지는 output창을 만들었다…ㅋㅋ
Leave a Reply
정말 좋은 솔루션을 알게되어서 감사 합니다.
혹시, 라텍스코드->div->png 로 만들때
png 이미지 안에 한글도 들어 가는건지.. 가능할까요?
안녕하세요~ 들러주셔서 감사합니다~
latex에 한글을 적용하는 방법은 여러가지가 있는데요, 우선 제가 아는 방법 중 하나는 kotex 패키지를 사용하는 방법입니다.
kotex관련해서 검색해 보시거나 ktug.or.kr 사이트에 가보면 kotex 패키지 사용을 위한 패키지 설치를 설명한 곳이 있는데 ,
kotex을 설치한뒤, 한글을 넣을 latex 소스의 문서 정의부에 \usepackage{kotex} 으로 kotex 패키지의 사용을 선언한 후, 한글을 작성하시고 div->png로 만드시면 됩니다.
저도 홈페이지에 공식 같은 걸 많이 올리는데 올릴때 마다 너무 귀찮고 힘들었는데 이런 좋은 방식이 있군요.
근데 웹 호스팅에서는 사용할 수 없는 건가요? 전 웹호스팅인데..
웹 호스팅을 사용하신다면 웹 호스팅 업체에 LaTeX관련 프로그램이 서버에 설치되어 있는지.. 그리고 관련 명령어들을 사용할 수 있는지 문의를 해봐야 할 것 같네요. 만약 그게 안된다면 워드프레스를 비롯한 여러 사이트에서 이런 기능을 제공해 주는 사이트가 있는데 그런 사이트들 중, 믿을 수 있는 사이트를 이용하는 것도 한 방법일 것 같습니다.