유돌이

calendar

1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31

Notice

2008. 12. 29. 23:35 델파이

GraphEdit에 대한 설명을 위해 1,2편으로 나누었습니다. 그만큼 워낙 DShow에서는 중요한 유틸이라 설명해야할 부분이 많기 때문입니다. 
 
[1] 카메라 영상을 플레이하기.
 
강의를 하려고 보니까 GraphEdit의 왼쪽에서부터 11번째의 단축버튼을 눌렀을때 나오는 윈도우의 명칭을 무엇인가로 지칭하는게 편리할 것 같습니다. 매번 11번째 운운하면서 설명하는 것 보다는 앞으로는 '필터삽입윈도우'라고 부르겠습니다. 자, 필터삽입윈도우에 보시면  Video Capture Source 라는 카테고리가 보이실 것입니다. (계속해서 제가 카테고리라고 표현하는데는 다 이유가 있습니다. 실제로 이것은 프로그래밍 상에서 카테고리로 되어 있으며, 나중에 여러분은 이 카테고리에서 필요한 필터들을 골라내는 로직을 작성해야 할 것입니다.).
만일 여러분의 컴퓨터에 USB 카메라가 설치되어 있다면 이곳에 분명히 1개 이상의 필터가 존재해야 합니다. (DSHow를 공부할 때에는 반드시 USB 카메라가 한대 쯤은 있어야 한다.) 일단 그것을 선택하면 GraphEdit의 메인화면에 필터가 생성되어 질 것입니다. 자.. 이것은 일종의 광의의 소스필터이고, 좁게는 비디오 입력장치라고 부릅니다.

이제 여기서부터 나오는 스트림을 랜더링해야 하는데요, 그러자면 랜더링 필터가 필요합니다. 마찬가지로 필터삽입윈도우에서 이번에는 DirectShow Filters 카테고리에 들어가 봅시다. 여기에는 많은 수의 DirectShow 필터가 존재하고 있습니다. 이 말은 즉 오리지널 DShow 클래스(실은 COM 인터페이스) 내부구조를 하고 있다는 것입니다. 여기에서 Video Renderer 필터를 선택합니다. 조금 아랫쪽에 있을 것이므로 스크롤바를 밑으로 조절하셔야 할 것입니다. 

자, 이제 GraphEdit의 메인화면에는 두개의 필터가 놓여져 있을 것입니다. 왼쪽에 카메라 필터를, 오른쪽에 렌더러 필터를 위치시켜 놓은 다음에 카메라 필터의 오른쪽 꼭지에서 마우스로 콕 집어서 드래그 하여 랜더러 필터의 왼쪽 꼭지점에 갖다가 놓습니다. 이제 두개의 필터가 연결되었을 것입니다. 그런 상태에서  위쪽의 단축버튼 중에서 동영상 실행 버튼을 클릭하여 봅시다. 새로운 윈도우가 뜨면서 카메라로부터 들어온 영상이 렌더링 되는게 보여질 것입니다.   

[2] Video Renderer 필터

Video Renderer 필터의 종류가 3가지가 되는 것을 볼수 있을 것입니다. 이것에 대한 설명을 하자면 장황해 지겠으니, 가능하다면 간략하게 설명해 드리겠습니다. 일단 VMR Renderer 와 두개의 Video Renderer을 보실 수가 있을 것입니다.

이 VMR Renderer은 영상합성과 관련된 아주 중요한 필터입니다. 그래픽카드에서 이 서비스를 지원해 줘야 사용할 수가 있는 필터입니다. 이것이 무슨 말인가 하면... 일반적으로 영상을 합성한다면 버퍼링이 발생하게 됩니다. 320*240사이즈의 버퍼링이라면 문제가 되지도 않겠죠. 하지만 640*480 이상의 영상을 실시간 합성하고자 한다면 버퍼링이 엄청나게 문제가 됩니다.
왜냐하면 영상을 합성하는 알고리즘을 시스템 성능에 따라 조절해야 하기 때문입니다.
즉, 시스템 성능이 낮으면 그에 합당한 간단한 알고리즘을 사용해야 하고, 시스템 성능이 높으면 그에 따라 선명도을 증가시킬 것인가(640*480에서 720*480으로 좀더 선명한 화질의 소스에서 합성할 것인가), 아니면 합성시 거칠게 나타나는 부분을 알고리즘으로 부드럽게 처리하는  로직을 첨가할 것인가를 선택해야 합니다. 이러한 아주 긴밀하고 섬세한 선택은 물론 시스템 단가, 혹은 시장성의 선택과 결과물의 쿼리티와의 긴박한 관계에서 수많은 테스트를 거쳐서 판단하게 될 것입니다.
그러나 결론적으로 말씀을 드리자면 곧 한계가 발생하겠죠. 사람들은 환상적인 허리우드영화에 익숙해져 있기 때문에 웬만한 합성 결과물에는 반응하지 않게 되었기 때문입니다. 비록 그곳에 자신의 모습이 나온다고 할 지라도 말이죠. 

그래서 혹시 들어봤는지 모르겠지만 동영상 편집보드라는 특수한 보드를 사용하는 것입니다. '프리미어'라는 소프트웨어로 한번이라도 영상을 편집해 보신 분들은 아시겠지만, 동영상의 사이즈가 클수록 편집하는데 오랜 시간이 걸린다는 것을 잘 아실 것입니다. 이 편집보드는 가장 싸구려인 매트록스의 70만원짜리부터 수천만원이상가는 고가에 이르기까지 다양하게 시장에 나와있는 것으로 알고 있습니다.

자, 다시 돌아와서, 이러한 상태에서 영상을 합성하는 어떤 기능이 그래픽카드에 존재한다면 얼마나 좋겠습니까. 그렇다면 CPU에서 메모리에 적재된 데이터를 불러들여서  별도로 합성할 이유가 없을 터이죠. 만일 그렇게 된다면 수십만원 짜리의 영상편집보드를 별도로 구입할 필요가 없어지고 상업용 시스템 단가가 그만큼 떨어질수가 있다는 말이 됩니다.   

저희가 한창 영상합성 시스템을 개발할때에 DriectShow는 이 VMR 필터를 지원하지 않았습니다. 그래서 우리는 어쩔 수 없이 CPU를 사용해서 영상을 합성할 수밖에 없었는데요, DirectX SDK의 버전이 9.0을 넘자 VMR이라는게 생긴 것이었습니다. 그당시 얼마나 기뻤는지 모릅니다. 이것을 사용하면 CPU의 부담을 그만큼 줄일 수가 있고, 그렇다면 좀 더 복잡한 합성 알고리즘으로 합성된 영상물의 거친 부분들을 다듬을 수가 있을 터였기 때문이었습니다.

그러나 아쉽게도 그당시 그래픽카드가 VMR을 지원하는 것은 단 하나였고, 그나마 매트록스의 그래픽 카드는 지원이 되지 않았습니다. 매트록스 회사에 전화를 걸어보니 DirectShow 9.0을 완벽히 지원하려면 6개월 이상이 걸릴 것이라는 이야기를  들어야 했습니다. 물론 이 이야기는 아주 오래적의 일입니다.
제가 이런 이야기를 하는 것은 VMR Renderer이란 무엇인가를 아주 효과적으로 여러분에게 납득시킬 수가 있기 때문입니다. 아무튼 우리 회사로서는 반드시 매트록스 그래픽 카드를 사용해야만 할 터였습니다. 모든 시스템이 이 그래픽 카드에 맞춰져 있는 상태였기 때문이죠. 해서, 그당시 VMR Renderer를 결국 포기해야만 했던 경험이 있습니다. 

위의 이야기를 듣고 조금 이상하다고 생각하는 분들도 있을 것입니다. 그 분들은 DShow의 VMR Renderer을 어느정도 알고는 있으시는 분들이겠죠. 그렇습니다. VMR Renderer에서의 영상 합성의 결과물은 소프트 웨어적으로 저장할 수가 없는 것입니다. 왜냐하면 그래픽카드에서 영상이 합성되기 때문에 거기가 종착역이기 때문입니다. 물론, DriectX 의 DirectDraw 평면을 제공하기 때문에 이것을 가져와서 다시 저장할 수는 있습니다. 실제로 VMR Renderer은 이렇게 DirectDraw평명을 억세스하기위한 인터페이스를 제공하는 것으로 알고 있습니다. (하도 오래전 일이라 가물가물 하지만 본 것 같음. 아니면 말고...) 그러나 이런 방식으로 합성된 결과물을 다시 받아서 저장하는 DShow 프로그래밍은 하지를 않을 것이라고 봅니다. 이것은 로컬인 파라독스 DB를 가지고 네크워크 방식이라고 해야할까요, 아무튼 그렇게 사용하는 것과 마찬가지라고 봅니다. 차라리 그럴봐에야 직접 그래픽카드의 DreictDraw 평면을 이용하는 것이 좋지 않을까 싶네요. 어쨌든, 또다시 이야기가 삼천포로 빠졌습니다. 

VMR Renderer에서 합성된 영상을 소프트웨어적으로 저장할 수가 없다는 말은 맞습니다. 그렇다고 저장하지 못한다는 것은 아니고요. 결론적으로 말씀드려서 저장할수가 있습니다. 정확히 말하자면 DSHow의 방식으로 저장을 못한다는 말이고요, DirectDraw 평면을 직접 억세스하는 소프트웨어적이거나 혹은 우회로 돌려서 저장하는 방식이 있습니다. 즉 모니터에서 나오는 영상을 다시 자신의 컴퓨터 입력장치로 보내어 그것을 저장하는 것입니다.  

VMR Renderer을 설명하자면 한도끝도 없습니다. VMR Renderer이전에는 오버레이 믹서라는 필터를 제공하였습니다. 아마 지금도 DirectShow Filters 카테고리에 보시면 찾을 수가 있을 것입니다. 이 Overlay Mixer라는 필터는  마찬가지로 그래픽카드의 오버레이 평면을 이용하여 합성을 하는 방식입니다. 예전에는 동영상의 자막 처리를 이것으로 하였던 적이 있습니다. 지금도 그렇게 하는지는 모르겠습니다.

지금은 훨씬 다양한 방법론이 존재하기 때문입니다. VMR Renderer이 처음 나왔을때, 이것이 환상적이었던 것은 알파브렌딩 합성을 가능하게 해준다는 것이었습니다. 즉 평면의 구조가 RGB24가 아니라 ARGB32였던 것입니다. 여기서 A는 알파채널로서 아마도 포토샵을 조금만 사용해 보셨던 분들은 알파채널이 무엇인가를 알고 계실 것입니다. 이 말은 영상합성에서 그 상대적인 깊이를 마음껏 조절할 수가 있다는 의미인 것이죠. 자... 이쯤에서 VMR Renderer에 대한 설명은 접기로 하겠습니다. 

이제 나머지 두개의 동일한 이름의 Video Renderer에 대해서 설명드리겠습니다. 두개는 이름만 동일할뿐 실제로 랜더링하는 방식은 내부적으로 다릅니다. 하나는 구버전의 Video Renderer이고 하나는 나중에 VMR Renderer의 또다른 모습입니다. 즉 두개의 Video Renderer를 모두 순서대로 선택하여 GraphEdit에 생성해 놓으신다면 하나의 필터에는 입력핀쪽에 VMR 이라는 명칭이 붙어있는 것을 보실수가 있습니다. 그렇다면 대체 VMR Renderer는 무엇이고 요놈은 또 무엇인지 헷갈릴 것인데요. 간단하게 말하자면 Interlace를 받아서 처리하는 놈이라고 보시면 됩니다. 

자, 이제 종합해보겠습니다. DirectShow Fitlers 카테고리에는  렌더러 필터가 3개가 있다. 하나는 VMR Renderer9이고, 나머지 두개는 이름이 동일한 Video Renderer이다. 그렇데 나머지 두놈의 동일한 이름의 필터를 GraphEdit에다 생성해 놓고 보니 하나의 필터에는 입력핀쪽에 VMR이라고 붙어있다. 이것은 VMR Renderer9를 생성시켰을때와 동일하다. 대처 무엇때문에 이따위 짓을 해 놓았는가. 그 이유는 Interlace방식의 입력 스트림을 처리하기 위함이고, Interlace 방식이란 무엇인가는 인터넷을 찾아서 공부하시길 바랍니다. 

[3] 그래프의 저장.

현재까지 실행을 하셨다면 GraphEdit 의 메인화면에는 두개의 필터가 달랑 연결되어 있는 것을 보실수가 있을 것입니다. 이 상태를 그대로 파일로 저장했다가 나중에 다시 열어서 간단히 실행시키실 수가 있습니다. 메뉴에서 Save Graph를 선택하면 저장하기 다이알로그가 뜨는데요, 일반적인 파일저장처럼 하시면 됩니다. 이 기능은 프로그래밍적으로 사용하려고 했습니다만, 이상하게도 DirectShow의 도움말에는 그런식으로 사용하지 말라고 권고하고 있습니다. 이게 무슨 말인가 하면요...

우리는 앞으로 수많은 필터들을 생성해서 수동으로 연결해야 합니다. 적게는 대여섯개에서부터 많게는 20개 정도의 필터까지도, 혹은 그 이상이라도 필요하다면 연결해야 합니다. 그런데 이 과정에서 시간이 조금 걸립니다. 아주 짧은 시간이지만 일반 사용자들은 1초 이상의 짬을 지루해합니다. 그런데 15개 이상의 필터를 생성하여 연결하다보면 심지어 2~3초 정도가 걸리기도 합니다.(시스템 사양이 낮은 컴퓨터에서) 그런데 GraphEdit처럼 이러한 상태를 아예 파일로 저장했다가 불러들여서 실행한다면 굉장히 속도가 빠르지 않을까라고 생각했던 적이 있습니다.
실제로 그러한 코딩이 가능하고, 그러한 예제 샘플까지 존재합니다. 그러나 이상하게도 DirectShow 도움말 파일에서는 그런식으로 프로그래밍적인 사용을 하지 말라고 거의 강권하고 있습니다. 이러한 이유에 대해 개인적인 추측이 가능합니다만, 아무튼 이것은 저의 개인적인 추측일 뿐이이서 이곳에 기술하지는 않겠습니다. 

[4] Connect to Remote Graph

메뉴에 보시면 위의 제목을 한 기능이 있습니다. 이것은 참으로 유익한 기능인데요, 잘만 활용한다면 꽤 훌륭한 뚫어 기능이 됩니다. ^^. 이게 무슨 말인가 하면요, DShow 프로그래밍은 일반적인 DLL 프로그래밍과 마찬가지로 디버깅이 아주 골치가 아픕니다. DShow의 필터 하나하나도 ax 확장자만 가지고 있을뿐 실제는 DLL과 거의 비슷한 사용자 억세스 관점을 가지고 있습니다. 그래서 한번 버그가 발생하면 대체 어디에서 발생하였는지 감을 잡기가 힙듭니다. 

수십개의 필터들을 동적으로 연결시켜 놓았는데요, 그중 어느 한 부분이 연결되지 않은채 굴러간다면, 대체 어느 부분에서, 몇번째 필터에서 연결이 끊겼는지를 직접 확인해야 할 필요성이 있습니다. 여기까지는 다른 방식으로 어떻게든 알아낼 수가 있겠지요. 그런데 문제는 그 다음입니다. 분명히 연결은 제대로 되었는데도 불구하고 작동이 안되는  경우, 정말로 연결이 되었는지 눈으로 직접 보고 싶어질 때가 있습니다. 

이럴때 여러분이 만든 DShow 프로그램을 실행시킨다음 GraphEdit의 Connect to Remote Graph 메뉴를 사용하신다면, 현재 여러분이 만들어서 실행되는 DShow 프로그램의 내부 필터들의 연결을 그대로 GraphEdit에 나타내게 하실 수가 있습니다. 물론 이 기능을 위해서는 여러분의 DShow어플에 약간의 (한줄 정도의) 추가 코딩이 필요합니다. 
여기에 대해서는 뒤에 가서 간단히 실습해 보도록 하겠습니다.  
 
출처 : 델마당  dong (dongsoft) 님의 글
posted by 유돌이
2008. 12. 29. 23:32 델파이

[1]DirectShow를 하기 위한 컴퓨터 셋팅 준비하기. 

 

DShow를 하기 위해서는 준비해야 할 것들이 몇개 있습니다.
가장 중요한 것은 DirectShow SDK이고요, 델파이용 DirectShow.Pas파일을 준비해야 합니다. 그리고 불행하게도 Visual C++도 필요합니다. 그리고 Visual C++을 어느 정도는 다룰 수 있으면 좋습니다. 왜먀하면 워낙 샘플 소스가 풍부하기 때문에 인터넷에서 다운받아서 내용을 참고할 필요가 절실히 있기 때문입니다.

이것을 정리하면 다음과 같습니다.  아래의 준비사항에서 4번 항목의 도서에 대해서는 따로 말씀을 드리겠습니다. 이것은 아직까지 우리나라 유일의 DirectShow 분야의 책이기 때문에 참고할 부분이 적지않게 있기 때문에 어쩔수 없이 구입하셔야 할 항목이라서 이곳에 기재하였습니다.
그러나 COM에 대해 설명한 부분에서는 오히려 혼란을 가중시킨다고 개인적으로 생각하며, 이밖에 이 책을 효율적으로 습득하는 방법에 대해서는 뒤에서 따로 말씀을 드리겠습니다.

  1. DirectX SDK 9.0 이상 
  2. DSPack 2.34 
  3. Visual C++ 버전6이 좋습니다. 아직까지는요.   
  4. DirectShow 멀티미디어 프로그래밍, 저자:신화선, 출판사: 한빛미디어. 
  5. http://www.dshowtech.com/bbs/DirectShow/Home.php : 현재 가장 활성화된 DirectSow 사이트.
  6. DirectSow 한글 도움말 pdf 파일. (찾아서 올려놓겠습니다.) 


[2] DirectX SDK 설치하고 둘러보기.

DirectX SDK를 설치하는 방법에 대해서는 굳이 해설하지 않겠습니다. 델파이에서 DSPack을 가지고 프로그래밍하는데 굳이 SDK가 필요한가라고 의문을 품으시는 분들이 계실수도 있겠습니다만, 먼저도 말씀드렸다시피 오리지날을 한번 살펴보고 비교분석해야할 시점이 반드시 옵니다. 따라서 DirectX SDK는 반드시 설치하라고 권장하고 싶습니다. 또한 이것을 설치하는 이유는 이 속에 들어가있는 내부유틸을 절대적으로 필요로 하기 때문입니다.  

일단 SDK를 설치하시면 C:\ 루트에 DXSDK라는 디렉토리가 생깁니다. C:\DXSDK\Bin\DXUtils 여기의 디렉토리를 살펴보시면 graphedt.exe라는 유틸이 눈에 띄실 것입니다. 이것은 DShow 프로그래밍을 하는데 있어서 없어서는 안될 중요한 프로그램입니다. 따라서 이것을 바탕화면에 바로가기로 등록해 놓습니다. 

자, 이제 한번 SDK의 내부를 둘러보도록 하겠습니다. DirectX SDK에서 우리가 사용할 부분을 살펴봐야 합니다. 
C:\DXSDK\Samples\C++\DirectShow 여기 디렉토리를 보시면 되겠습니다. 앞으로는 이곳을 밥먹듯이 드나들어야 할 것입니다. 그러나 델파이로 DSPack을 가지고 프로그래밍하는  여러분은 그럴 필요가 없을지도 모르겠습니다. 하여튼 중요한 곳입니다. 특히 C:\DXSDK\Samples\C++\DirectShow\Capture\AMCap 이곳 샘플 프로그램을 컴파일하여 실행프로그램을 만들어 놓으면 좋습니다.
이것도 마찬가지로 graphedit.exe와 마찬가지로  바탕화면에 바로가기로 등록해 놓으면 좋을 것입니다. 자... 두가지 명심해야 할 것은 graphedit.exe 와 AMCap.exe 가 필요하다는 것입니다. 그 중에서도 전자의 유틸 프로그램은 반드시 필요하다는 점을 유념해 주시기 바랍니다. 

여러분이 여기까지 읽고 따라하시려고 한다면 아마도 조금 당황하실지 모르겠습니다. 왜냐하면 AMCap이 컴파일 되지 않기 때문입니다. DirectShow의 샘플 프로그램을 컴파일하기 위해서는 조금 번거로운 설정을 해주셔야 합니다. 

이것은 델파이에서 컴포넌트를 설치하였을때 수동으로 디렉토리를 설정해야 하는 것과 마찬가지입니다. 이것에 대한 설명은 뒤에서 신화선님의 참고서적을 해설하면서 언급해 드리겠습니다.  
 
[3]Visual C++을 설치하고 델파이의 DSPack 컴포넌트를 설치하시기 바랍니다. 

특히 델파이의 DSPack의 버전에 유의하시기 바랍니다. DSPack은 버전에 따라서 내부 소스가 조금 다릅니다. 예를들어서 레코드 선언에서 동일한 구조인데도 이름을 약간 변경해 놓거나 하였습니다. 따라서 이왕이면 제가 사용하려는 DSPack2.34버전을 구해서 설치하시면 좋겠습니다. 제가 시간나는데로 자료실이나 이곳에 올려 놓겠습니다. 그것을 사용하시면 되겠습니다.  (이것을 다른말로 하면 DSPack은 버전별 호환성이 없다라고 말씀드릴 수가 있습니다.

즉, DSPack의 다른 버전으로 개발하셨다면 약간의 수정을 해야 함을 의미합니다. 이 사소한 번거로움이 이것을 만드신 분의 엄청난 수고에는 티클 만큼의 흉이 되지 않음을 아셔야 할 것입니다. 우리나라에 이같은 분이 없다는 것이 정말로 아쉬운 뿐입니다. 
DSPack은 DirectShow 뿐만이 아니라 델파이에서 참으로 참고할 만한 가치가 많은 방대한 분량의 참고소스입니다. 이정도 수준의 프로그래머가 국내에도 많이 계셨으면 하는데요, 국내의 깜깜한 현실에서 이런분이 몇분이나 될지를 생각해보면... 쩝, 안타깝기 그지없습니다. "우리는 Coder다. 아니라는 증거가 있을까?")


[4]http://www.dshowtech.com/bbs/DirectShow/Home.php 

이곳 사이트를 또한 좌절과 공포감이 엄습해 올때마다 들러서 검색을 하거나 질문을 올리거나 해야 합니다. 특히 이곳 운영자인 신화선씨에게 저는 처음 DShow 프로그래밍을 할때 이메일로 몇번의 도움을 직접 받아본 적이 있습니다.

DShow 프로그래밍의 선도자적인 개척을 하신 분으로 나이는 저보다 어릴지언정 프로그래머로서 존경을 받기에는 충분하다고 생각합니다. 불행하게도 대부분의 소스가 Visual C++로 되어있고, 델파이 진영이 있기는 있으나 거의 죽어 있는 상태입니다. 제가 조금 나서볼까 하다가, 이곳 델마당에 이렇게 강좌를 올리게 된 것인데요, 아무튼 중요한 곳입니다. 익스플로러의 즐겨찾기에 반드시 등록해 놓으시길 바랍니다. 사이트 주소가 길어서 외어놓기가 힘들거든요.

아참, 이곳에서 주의하실 점이 있습니다. 그것은 이 동호회 사이트가 과거 한번의 개편을 맞으면서 일괄적으로 통합된 것이 아니라는 점입니다. 즉, 과거의 자료는 '프리백'이라는 메뉴에 보시면 찾을 수가 있습니다. 이곳에도 유용한 정보들이 있기 때문에 그냥 '질문방'이나 '강좌'란에 들어가 찾다가 없다고 서둘러 빠져나오시지 말았으면 합니다. 

'프리백'에도 예상외의 정보가 있을 수 있기 때문입니다. 이 '프리백'은 과거 사이트의 모든 정보를 그대로 옮겨놓은 곳입니다.

[5]신화선님의 DirectSow 멀티미디어 프로그래밍이라는 책에 대하여.

제가 조금 비판적인 입장에서 이 책을 설명드린다고 저를 비난하지는 말았으면 합니다. 왜냐하면 이 책이 비판받아야할 문제점과 이 책이 우리나라 DShow 프로그래밍에 있어서의 소중한 위치와는 전혀 별개의 문제이기 때문입니다.

예전에 저는 Visual C++을 공부하면서 이상엽님의 책을 보고 참으로 많은 좌절을 하였던 때가 있었습니다. 그런데  그당시 나뿐만이 아니라 상당히 많은 사람들이 그랬던 경험을 털어 놓더군요. ^^. 아무튼 좋은 책과 선도적인 책과의 구분은 분명히 있어야 한다고 생각합니다. 그당시 이상엽님의 책은 분명히 선도적인 책이었던 것은 분명합니다.
그러나 좋은 책이었다고는 말하기가 힘들다고 봐야할 것입니다. 제가 이렇게 거창하게 설명하는 이유는 바로 여러분들을 위하기 때문입니다. 즉, 이 책을 읽고서 절대로 좌절하지 말기를 바랍니다. 왜냐하면 이 책이 선도적인 책인 것은 분명하고 그것의 존재 자체를 감사해야하는 점은 명확한 사실이나, 분명 이해하기 쉬운 좋은 책은 아니기에 여러분에게 얼마간의 좌절감을 안겨드린다는 것또한 사실이기 때문입니다. 


여담입니다만 실제로 우리나라 프로그래밍의 역사에서 좋은 책은 정말로 눈씻고 찾아보기가 힘듭니다. 제가 이제껏 보아왔던 가장 좋은 책은 김용성님의 Visual C++ 6 완벽가이드라는 책입니다. 이 책의 데이타베이스 해설부분을 보시면 알겠지만, 참으로 이렇게 쉽고 간결하면서 완성도있게 표현하기가 쉽지 않음을 절실하게 느낍니다. 여러분에게 이책을 반드시 구입하라고 권하고 싶을 정도입니다.
특히 COM에 대해서 자신이 없는 분이라면 이곳에 해설된 COM 부분을 일주일 정도만 보면 대략적인 이해를 하실 정도까지 된다는 사실에 놀라실 것입니다. 제가 책장수는 아니지만, 좋은 책은 권장해 드리고 싶습니다. 그러나 분명한 것은 선도적인 책과 좋은 책을 구분해야 한다는 점입니다. 특히 번역서의 경우는 좋은 책이 되기 힘든 이유가 여기에 있습니다.

말이 많이 빗나갔습니다. DShow 공부를 하기 위해서는 신화선님의 책이 반드시 필요하다고 생각합니다. 그리고 이제부터 이 책의 내부를 조금씩 해부해 보겠습니다.  

이책을 읽으실 경우 주의하실 점은 방금전에도 언급하였지만 COM부분의 해설에 대해서는 그냥 참고수준으로 넘기라고 권하고 싶습니다. 이해가 안되신다면 억지로 몇번이고 반복해서 읽으실 필요가 없다는 것입니다. (제가 그랬다가 끝까지 이해가 안되서 엄청 고생하였습니다.) 이 COM의 해설부분은 차라리 김용성님의 Visual C++6라는 책을  구입해서 그곳에 해설된 부분을 읽으시는게 훨씬 도움이 될 것입니다.  만일 여러분이 좀더 시간이 있으시다면 COM 부분의 완성도 있는 책을 구입해서 읽으시면 좋을 터인데요, 좋은 책이 하나 있는데 지금은 절판된 상태입니다. 쩝...

아무튼 COM 뿐만이 아니라도 그렇습니다. 이 책을 읽으면서 굳이 이해가 안가신다고 좌절하거나 여러분들 이마에 주름살 새겨가면서 공부하실 필요는 없다고 생각합니다. 오히려 그 시간에 도움말 파일이나 SDK의 예제소스나 제가 앞서 말씀드렸던 사이트의 질답란을 봐가면서 연구하는 편이 훨씬 유익하다고 생각합니다. 그렇다면 대체 이 책을  왜 사라고 권하는가... 

첫째, 일단 전체를 봐야하기 때문입니다. DirctShow란 대체 무엇인가에 대한 의문에 대하여 숲을 볼수있게 해줍니다.
즉 GraphEdit.exe의 사용법, 결국 DirectShow가 필터들의 연결이라는 점, 소스필터, 변환필터, 랜더링필터 등 각각의 필터들의 특성과 그 필요성 등등에 대하여, 전체적인 모습을 관망해 볼수 있게 해줍니다. 

둘째,  이곳에 부록으로 제공하는 CD에는 Visual C++로 필터만들기에 아주 쉬운 프레임을 제공해준다는 점입니다. 
이 마법사를 사용하면 간단한 기본필터를 그냥 만들어 주기 때문에 참고하는데 아주 유용합니다. 반드시 한번은 설치하셔서 만들어 보시길 권해드립니다.  

세째, 앞에서 말씀드렸던 DirectShow SDK 필터를 컴파일하는데 있어서 환경설정을 하는 방법이 이곳에 나와 있습니다. 제가 별도로 강좌에 쓸수도 있겠습니다만, 이책 204Page에 보면 자세히 나와 있기 때문에 생략하기로 하겠습니다. 간단히 말씀드리자면 C:\DXSDK\Samples\C++\DirectShow\BaseClasses 이곳을 먼저 컴파일 하여야 하며 라이브러리 등록을 해주셔야 한다는 점입니다.

[6]한글 도움말 파일 

마지막으로 한글 도움말 파일이 있습니다. 뭐 영문도 있습니다만 굳이 한글번역판이 있는데 영어공부하고자 영문판을 읽으라고 권하지는 않겠습니다. 하지만 제가 처음 시작할 때에는 영문판 밖에 없었기 때문에 가독하는데 시간이  걸려 애를 먹었던 적이 있었습니다. 간단한 인터페이스 하나 찾는데도 일일이 이마에 주름살 새겨가며 읽어야 했던 경험을 그닥 권장하고 싶지는 않습니다. 영어공부는 별도로 하시는게...^^

이 도움말 파일이 반드시 필요한 이유는, 여러분이 어느정도 DShow의 구조에 익숙해졌다면 그 다음부텨는 스스로  인터페이스를 찾아서 코딩해야만 하기 때문입니다. 즉 DShow SDK에서 제공하는 수많은 인터페이스 중에서 자신이 원하는 것을 찾아서 유용하게 사용할 줄 알아야 한다는 점이죠. 이 때문에 이 한글 도움말 파일은 상당히 유익한 것입니다. 영문이라면 시간이 좀 걸릴 터이기 때문이죠. 


일단 2부 준비할 사항에 대해서는 이정도로 마치겠습니다. 하지만 강의 도중에 종종 필요한 부분이 있다면 추가적으로 게시할 것입니다. 
 
 
출처 : 델마당  dong님의 글(dongsoft)

posted by 유돌이
2008. 12. 29. 23:31 델파이
예전부터 한번은 DirectShow 강좌를 하고자 마음먹었습니다. 그러다가 이렇게 설을 앞두고서 일감이 없어서(간단하게 놀고 있어서 시간이 남아 돌기 때문에) 강좌를 하게 되었습니다.

욕심 같아서는 필터만들기까지 하고싶지만, 시간이 어떻게 될지는 모르겠습니다. 그러나 우선 강좌를 시작하기에 앞서서 DirectShow 프로그래밍을 가지고 어떠한 것들을 할수 있는가, 또한 DirectShow가 왜 꼭 필요한가에 대한 궁금증과 의문점을 하나하나 살펴보도록 하겠습니다.
 
[1] 시작하기에 앞서서 알아야할 백그라운드 필수 개념

저는 델파이 프로그래밍 1년차가 끝나기도 전에 DirectShow프로그래밍을 하게 되었습니다. 그때 제가 무슨 정신으로 할 수 있다라고 오버액션을 취했는지 알다가도 모를일이었습니다. 그당시 저에게는 COM은 두말할 것도 없고, 그보다 더 기초적인 OOP에 대한 개념조차도 머릿속에 들어가 있지 않았기 때문입니다.

그냥 c언어를 남들보다 조금 잘하니까 부딪혀보면 해답이 나오겠지하고 시작하였고요, 장님 코끼리 뒷다리 만지는 식으로 더듬더듬 더듬어나갔습니다.

그러나 만일 여러분이 DirectShow를 시작하고자 한다면 저처럼 이렇게 오랜 길을 장님처럼 주물럭거리며 느려터지게 발전해 나가지 않았으면 합니다. 오히려 OOP에 대한 이해를 먼저하고, 그 다음에 COM에 대한 이해를 하고서 시작한다면 더 빠르게 목적하고자하는 목표에 다다를 수가 있을 것이라고 감히 충고합니다.

OOP와 COM의 이해는 1년 정도의 완숙도를 가질 필요가 있다고 봅니다. 즉, 사전적인 이해가 아니라 실무에서의 한 두 프로젝트의 경험에 의한 '감'이 있고나서 시작하면 좋겠다고 생각합니다.
책을 통해서 익히는데는 풀타임으로 약 2달 정도면 가능하겠지만, 이것만 가지고는 턱도 없이 부족할 것이기 때문에 OOP와 COM에 눈을 뜨고서 한 1년 정도의 필드 경험을 익힌 다음에 시작하는 것이 좋지 않을까 하는 것입니다.

왜냐하면 저의 경우에 어느날 갑자기 DirectShow필터의 소스가 한눈에 확 들어온 적이 있었는데, 그토록 갈망했던 그 구조가 그렇게 쉽게 머릿속에 다가오자 오히려 맥이 빠질 정도였습니다.
그러나 뒤돌아 생각해보면 다 그럴만한 이유가 있었고, 저처럼 OOP와 COM의 전반적인 이해가 없이 무턱대고 대양에 빠져서 허우적대는 식으로 갈팡질팡하는 것 보다는 오히려더 실무 경력 1년 정도의 시간을 두고나서 시작하는 것이 훨씬 더 효과적이라는 생각이 들기 때문입니다.
마치 구구단을 완전히 암기하지 못한 사람이 미적분을 공부하는 것과 같은 것이라고 할까요...

물론 이것은 궁극적인 목표가 Filter개발이라는 점을 염두에 두고 한 말입니다. 만일 여러분이 일반적이고 간단한 동영상 플레이어를 조작하는 어플에 만족한다면 그보다 훨씬 저렴한 노력으로 DirectShow를 익힐 수도 있을 것이라고 격려해주고 싶습니다.
그러나 분명한 것은 DirectShow는 필터를 개발하지 못하면 그 성능을 절반도 제대로 사용할 수 없다라는 점입니다. 그러므로 최종목표는 항상 필터개발이라는 사실을 명심하도록 하세요.

[2] DirectShow의 특징. 버퍼공유

DirectShow의 특징이라면 한두개가 아닐 것이다. COM으로 이뤄진 것이라는 것, 내부적으로 DirectDraw를 사용했다는 것, 등등... 그러나 가장 중요한 것은 DirectShow의 구조적인 특성일 것이고, 거기서 가장 중요한 점이 바로 버퍼를 공유한다는 점이다. 이 사실에 밑줄을 쫙 거주길 바랍니다.

사실 윈도우에서 동영상을 제어하는 방법이 꼭 DirectShow만 있는 것은 아닙니다. 예전의 API를 사용해서 어플을 만들었던 VFW(Video for Window)라는 방식도 있지요.
즉, DirectShow와 VFW는 윈도우즈 운영체제에서 동영상을 제어하는 어플을 만드는 두개의 유일한 방식이라고 할수가 있습니다. 좀더 엄밀하게 말하면 VFW가 먼저 나왔고 그 다음으로는 마이크로 소프트사가 하는 방식이 원래 그렇듯이 DirectShow가 VFW를 흡수통합하는 형식으로 나왔다는 점입니다.

그렇다면 어째서 마이크로소프트(이하 마소)는 DirectShow를 별도로 제시하여야 했을까요. DirectShow(이하 DShow)는 DirectX의 일 부분으로서 SDK에 포함되어 있습니다.
즉 초기의 DirectX에는 DirectShow가 포함되어 있질 않았다는 것이죠. 마소는 절대적으로 DShow가 필요했고, 그러한 필요성을 절실히 느낄 수 밖에 없었을 터입니다. 그 이유가 바로 버퍼공유라는 단하나의 이유 때문이라도 말이죠.

우리가 동영상을 로딩하면 일반적으로 카메라에서 BT878류의 화면캡쳐보드나 혹은 USB를 통해서 어디론가 연속적인 이미지가 쌓일 수밖에 없습니다.
일단 USB 종류의 설명은 제외하기로 하지요. 왜냐하면 제가 USB 드라이버를 다뤄본적이 없기 때문에 여기서는 일단 BT878이라는 화상 캡쳐보드 혹은 일반적으로 TV수신카드)를 예로 설명하겠습니다.
(사실 직접 디바이스 드라이버를 제작한적은 없습니다. 그러나 드라이버 제작자와 함께 작업을 하면서 상당히 많은 사실을 접할 수가 있었습니다.)

카메라는 일반적으로 감시용카메라(아날로그)가 되겠습니다. 이것을 BT878카드에 꼽아서 DShow 어플을 제작하는 것인데요, 주로 산업현장이나 주정차 시스템같은 곳에 많이 연계되어 사용하고 있습니다.
USB 카메라를 이용하는 화상통신 프로그램도 완전히 동일한 DShow 응용 어플이라고 할수 있겠습니다. 왜냐하면 WDM 디바이스 드라이버라면 그 위에서 작동하는 DShow는 아무튼 동일한 것이기 때문이죠. 어쨌든 좀더 복잡한 예로 BT878을 예로 들어 보겠습니다.

감시용 카메라는 아날로그 신호를 만듭니다. 이것을 BT878보드에 보내게 되고요, 이 보드에서 아날로그 신호를 디지털화 작업을 하게 됩니다. 그리고 이 디지털 데이터가 윈도우의 첫번째 버퍼링 대상이 되는데요, 윈도우는 이 데이터를 가장 신속하게 다뤄질 수가 있는 시스템 메모리에 적재합니다. 엄밀히 말하면 윈도우가 그렇게 하는 것이 아니라 WDM 디바이스 드라이버가 그렇게 한다고 볼수가 있겠죠.
아무튼... 이 시스템 메모리는 일반적인 응용 어플에서 엑세스 할수가 있는 곳이 아니고, 또한 그 때문에 굉장히 빠르고 안전한 우선권이 있다는 점만 말해주고 싶습니다. 따라서 어플에서 이 데이터를 사용하기 위해서는 다시 일반적인 메모리에 복사(버퍼링)하는 작업이 필요하고요, 이것이 다시 그래픽카드의 메모리로 전송되게 됩니다.

이것을 다시 간단히 말하면...
BT878에서 만들어진 디지탈 그래픽 데이터는 WDM 드라이버에 의하여 윈도우의 시스템 메모리에 한번의 버퍼링이 되어지고, 여기서 다시 일반 어플이 사용하는 메모리에 복사(버퍼링)이 되며 또다시 이것이 그래픽카드의 메모리로 보내지게 된다는 점입니다.
이때 그래픽카드로 전송하는 방식은 AGP 몇배속인가 하는 방식으로 보내지기 때문에 속도가 엄청나게 빠르다는 사실을 참고하시면 되겠습니다. 그래픽카드를 꽃는 슬롯을 눈여겨 보시면 다른 슬롯보다 조금 다른 형태임을 보실수가 있을 것입니다. 이것을 AGP라고 합니다. PCI방식보다 훨씬 빠르다고 하죠.

결론적으로 말씀을 드리면 이렇듯 간단한 동영상 어플을 만드는데에도 영상의 버퍼링이 두번이상이나 필요하게 됩니다. 만일 어플에서 영상을 가공처리한다면 또다시 몇번의 버퍼링이 필요한 터이겠죠. 이 버퍼링이 얼마나 어마어마한 것인가를 먼저 느끼셔야 합니다. 그렇지 않다면 DShow는 아무짝에도 소용이 없습니다. 동영상의 버퍼링은 정말로 무시무시한 괴물 같은 것입니다. 특히 영상의 크기가 배가 될수록 버퍼링의 크기는 그것의 제곱비례하게 됩니다.

이 말이 무슨 말인가 하면... 640*480의 동영상을 받아서 처리한다고 해보죠. 초당 30프레임이고요, RGB24를 기본으로 사용한다고 생각해봅신다.(일반적으로는 YUV지만 생각의 편리를 위해서). 1프레임에 약 1M의 메모리가 필요하다고 한다면(640*480*3 Byte이므로 거의 1M라고 하자),  1초에 약 30M의 데이타가 전송되어야 합니다. 만일 버퍼링이 응용어플에서 3회에 걸쳐 이뤄졌다고 해봅시다. 그렇다면 컴퓨터는 내부적으로 초당 약 90M의 버퍼링을 처리해야 할 것입니다. 이것은 어마어마한 양의 데이터입니다. 아마도 CPU점유율이 그리 낮지는 않으리라고 봅니다.

만일 우리가 동영상 프로그램을 개발하면서 버퍼공유를 하지 못한다면, 우리는 계속적으로 버퍼링을 해야만 할 것이고 이것은 곧 엄청난 시스템 자원을 필요로하게 됨을 의미합니다.
마소가 굳이 DShow의 서비스를 제공할 수 밖에 없는 이유가 되는 것입니다.

여러분이 간단한 동영상을 편집해 보았다면 이 위력이 얼마나 큰 것인지를 실감하실 수가 있을 것입니다. 동영상의 데이터는 일반 어플의 데이터와는 상상할 수도 없을만큼 비대합니다. 따라서 버퍼링이 곧 죽음이고, 그만큼 마소에서는 버퍼공유가 절실히 필요했다고 할수가 있을 터입니다.

이즈음에서 저의 경험담을 이야기해보겠습니다.
저는 몇년전에 동영상 합성시스템을 개발한적이 있었습니다.
그당시 컴퓨터 사양이 CPU 1.7 ~ 2.5G 정도였습니다. 640*480영상을 백그라운드와 자신의 모습, 그리고 앞에서 움직이는 이펙트영상, 이렇게 세가지를 동시에 크로마 합성하고자 하였으나 결국은 실패하였습니다.
왜냐하면 그당시 컴퓨터 사양으로는 절대무리였다는 사실을 깨닭기까지 저의 기초지식은 너무나 터무니 없었기 때문입니다.

우리는 동영상을 생각할때 버퍼링과 함께 또다른 괴물도 마주하고 있음을 깨닭아야 합니다. 그것은 바로 압축이라는 메두사입니다. 그 알고리즘만 봐도 돌이 되어 버린다는 괴물이죠. 결국 그당시 저는 480*480의 형태로 세가지 영상을 실시간 합성해내는데까지 성공하였습니다. 그러나 아직도 640*480의 3가지영상을 실시간 합성 해내는 것은 쉬운일이 아닙니다.  
왜냐하면 컴퓨터가 1초당 내부적으로 버퍼링할 수 있는 크기는 제한적이기 때문입니다. 아직까지 우리의 PC는 1초당 수G바이트씩 버퍼링을 하지 못합니다. 이것은 CPU의 속도와는 다른 마더보드의 속도와 관련이 있습니다.

아무튼 가장 중요한 것은 DShow의 가장큰 특징이자 존재이유가 바로 버퍼공유라는 것이며, 이것은 동영상의 어플을 제작할때 성능차원에서 기본적으로 다뤄져야할 매우 중요한 요소중의 하나라는 점입니다.

[3]동영상 프로그래밍을 할때 주의할 점.

이 사항을 기고해야 하는지, 아니면 나만의 아픈 기억으로 담고 있어야 하는지 모르겠습니다. 그러나 만일 나와 비슷한 경험을 하는 분이 있다면 조금 유의하라는 점에서 드리고 싶은 말씀이 몇가지 있습니다.

일단 동영상 프로그래밍은 상당히 재미가 있지만, 경제적인 측면에서는 상당히 그렇지 않다는 점을 알려 드리고 싶습니다. 그 이유는 몇가지가 있겠으나 일단 우리나라의 속물적인 소프트웨어 개발현실에서 깊은 알고리즘에 대한 이해를 요하는 연구스타일의 코딩은 설 자리가 없기 때문입니다.
이게 무슨 말인가 하면, 동영상은 상당히 고급 프로그래밍 기법에 속합니다. 필터제작은 기본이고 사실 Mpeg압축 알고리즘까지는 들어가야 제대로된 사업용 어플을 생산해 낼 수가 있다고 봅니다.
그러나 우리나라의 개발 현실은 그렇지 않습니다. 무조건 뚝딱 만들어 내라고 강요하고, 그러다보니 제대로된 소프트웨어가 개발될 리가 없습니다. 여기까지는 아마도 이해가 쉬우리가 봅니다. 왜냐하면 이정도는 일반적인 우리나라 개발현실이기 때문입니다. 그러나 슬프게도 동영상쪽은 여기서 한발 더 들어가 악조건에 파묻혀야 합니다.

동영상에 대한 아이템을 가지고 개발을 의뢰하는 사람들은 대부분 어떤 환상에 사로잡혀 있습니다. 시스템이 뒷바침되어 있질 않음에도 불구하고, 실상은 몇백만원짜리 그래픽편집카드가 필요한 시스템 구성을 단순하게 응용 어플로 개발해달라는 식입니다. 이들은 대부분 한탕 크게 해보자는 식이고, 히트해서 떼돈을 벌 것이라는 몽상에 사로잡혀  있는 경우가 대부분입니다. 

먼저 이들을 조심해서 피해나가야 할 것입니다. (이들은 결과물이 만족하지 않을 경우에는 개발비를 지급하지 않는 경향이 있다. 턴키가 아니더라도 직원으로서 개발한다고 하더라도 급여가 제대로 나오기가 힘들다.
왜냐하면 이들이 생각하는 개발기간과 실제 개발기간이 너무 차이가 나기 때문에 어느정도 급여가 나오다가 중간에 조금만 더, 조금만 더 하는 식으로 버티는 꼴이 되기 쉽기 때문이다. 이 즈음에는 이미 준비해뒀던 실탄이 바닥나 있을 터이다.)

두번째로 조심해야 할 것은 어두운 쪽의 그룹들이다. 이들은 주로 야시시한 것들, 음난한 것들 이거나 혹은 조폭들과 연계된 체인점 사업 아이템 등등이다. 이들과 거래를 하거나 이러한 회사에 입사할 때에는 조심하는 것이 좋다. 왜냐하면 어떠한 약점을 잡아서 회사에 목메 달려고 할지 모르기 때문이다. 사내 도청을 조심해야 하고, 특히 계약관계에서 계약서를 면밀히 살펴봐야 한다. 잘못 코끼면 일여년동안 거의 노예처럼 노동을 강요당하기 쉽다.

동영상계열의 프로그래밍은 우리나라에서는 참으로 지저분한 분야가 되어버렸다. 그 이유는 일단 개발회사들이 영세하고, 두번째로 프로그래밍에 대한 이해부족으로 SI처럼 생각하기 때문이다. 당장 눈앞에서 삐가번쩍하게 돌아가야 고개를 끄덕여주는 클라이언트에게 알고리즘개발이란 엄두도 낼수가 없는 거창함일 뿐이다.

따라서 나는 동영상 프로그래밍을 그리 추천하지는 않는다. 이제껏 동영상 프로그래밍을 하면서 받아야 할 인건비의 사분의 일도 제대로 받을 수가 없었기 때문이다. 그러나 내가 이렇게 여기에 강좌를 하는 것은 동영상 프로그래밍이 전문적인 영상합성이나 편집분야가 아니더라도 얼마든지 작은 부분으로서 유익하게 다뤄져야할 필요성이 있을 터이기 때문이다. 

다이렉트쇼의 서론은 이쯤에서 접는 편이 나을것 같습니다. 그러나 강좌를 진행하다가 계속해서 서론부분에 해주고 싶은 말이 있을 때에는 추가하는 방식으로 진행해 나가겠습니다. 
 
출처 : 델마당  dong님의 글(dongsoft)

posted by 유돌이
2008. 12. 29. 23:30 델파이

MouseDown 이벤트에 아래와 같이 코딩을 하면 된다.

 

ex)

  ReleaseCapture;
  PostMessage(self.Handle,WM_SYSCOMMAND, $F012, 0);


posted by 유돌이
2008. 12. 29. 23:30 델파이
unit xSMSSuremCall;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, Menus, StdCtrls, Buttons, IniFiles, xEdit, DB, ApoDSet, Registry,
  OleServer, ShellAPI, SMSCALLLib_TLB;


type
  TFormSMSSuremCall = class(TForm)
    Image2: TImage;
    MemoMessage: TMemo;
    ImageMessageLoad: TImage;
    ImageSendOK: TImage;
    ImageSendCancel: TImage;
    ImageMessageSave: TImage;
    GroupBox1: TGroupBox;
    Label20: TLabel;
    xEditID: TxEdit;
    Label22: TLabel;
    xEditPASS: TxEdit;
    SpeedButtonLogin: TSpeedButton;
    GroupBox2: TGroupBox;
    RadioButtonS0: TRadioButton;
    RadioButtonS1: TRadioButton;
    xEditDate: TxEdit;
    Label1: TLabel;
    xEditTime: TxEdit;
    Label2: TLabel;
    CheckBoxAuto: TCheckBox;
    CheckBoxShow: TCheckBox;
    GroupBox4: TGroupBox;
    SpeedButtonCashRefer: TSpeedButton;
    SpeedButtonClose: TSpeedButton;
    SpeedButtonHelp: TSpeedButton;
    SpeedButtonMessage: TSpeedButton;
    Label3: TLabel;
    Label4: TLabel;
    LabelCost: TLabel;
    LabelCount: TLabel;
    ApolloDataSetSLSMS: TApolloDataSet;
    xEditTo: TxEdit;
    xEditFrom: TxEdit;
    SpeedButtonSystem: TSpeedButton;
    LabelMessage: TLabel;
    ListBoxNames: TListBox;
    CheckBoxDupe: TCheckBox;
    objSMS: TSMSCALLMSG;
    Label5: TLabel;
    LabelDanga: TLabel;
    SpeedButtonPayMent: TSpeedButton;
    SpeedButtonResult: TSpeedButton;
    SpeedButtonJoin: TSpeedButton;
    procedure SpeedButtonCloseClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormShow(Sender: TObject);
    procedure CheckBoxShowClick(Sender: TObject);
    procedure SpeedButtonMessageClick(Sender: TObject);
    procedure ImageMessageLoadClick(Sender: TObject);
    procedure ImageMessageSaveClick(Sender: TObject);
    procedure ImageSendCancelClick(Sender: TObject);
    procedure ImageSendOKClick(Sender: TObject);
    procedure SpeedButtonCashReferClick(Sender: TObject);
    procedure SpeedButtonHelpClick(Sender: TObject);
    procedure SpeedButtonSystemClick(Sender: TObject);
    procedure MemoMessageChange(Sender: TObject);
    procedure SpeedButtonLoginClick(Sender: TObject);
    procedure SpeedButtonJoinClick(Sender: TObject);
    procedure SpeedButtonPayMentClick(Sender: TObject);
    procedure SpeedButtonResultClick(Sender: TObject);
  private
    { Private declarations }
    g_UserCode,
    g_UserPass,
    g_DeptCode,
    g_DeptName,
    g_UserName,
    g_ReqPhone1, g_ReqPhone2, g_ReqPhone3 : WideString;

    g_TotPrice,
    g_CallPrice : LongInt;

    TeleArray : array[1..19999,1..2] of string;
    TeleIndex : integer;

    procedure DllRegister;
    procedure Button2Disabled;
    procedure Button2Enabled;
    procedure SaveInformation;
    procedure PassWordShow;
    procedure ShowCash;
    procedure SendMessage;
    procedure SendMessageOne(pFPhone,pMemo,pRDate,pRTime:string);
    procedure SendMessageAll(pFPhone,pMemo,pRDate,pRTime:string);
    procedure Mobile3Number(pOrgNumber:string; var rP1,rP2,rP3:string);

    function  LoginProgress(pMessageShow:Boolean):Boolean;    
    function  CheckFound(pTele,pName:string):Boolean;
  public
    { Public declarations }
    pFromOK : Boolean;
  end;

var
  FormSMSSuremCall: TFormSMSSuremCall;

implementation

uses xModule, xServer, xLibComm, xProgressTerm, xSMSMessage, xSMSMsgItem, xSMSSuremLib;

{$R *.dfm}


procedure TFormSMSSuremCall.FormCreate(Sender: TObject);
begin
    xReadPosition(Self,'SureM',170,130);
end;

procedure TFormSMSSuremCall.FormShow(Sender: TObject);
begin
    DllRegister;
    
    CheckBoxAuto.Checked  := xIF(xReadString('SureM','UserAuto','N')='Y',True,False);;
    CheckBoxShow.Checked  := xIF(xReadString('SureM','UserShow','N')='Y',True,False);;
    CheckBoxDupe.Checked  := xIF(xReadString('SureM','UserDupe','N')='Y',True,False);;

    xEditID.Text          := LibSuremUserID;
    xEditPASS.Text        := LibSuremUserPASS;

    xEditDate.Text        := FormatDateTime('YYYY.MM.DD',Now);
    xEditTime.Text        := FormatDateTime('HH:MM',Now);

    if not pFromOK then  xEditFrom.Text := xReadString('SureM','SENDFROM',xINISPACE);

    LabelCost.Caption      := '0';
    LabelCount.Caption     := '0';

    Button2Disabled;

    if CheckBoxAuto.Checked then LogInProgress(False);

    if SpeedButtonCashRefer.Enabled then ShowCash;
end;

procedure TFormSMSSuremCall.FormClose(Sender: TObject; var Action: TCloseAction);
begin
    xWritePosition(Self,'SureM',Top,Left);

    xWriteString('SureM','UserAuto',xIF(CheckBoxAuto.Checked,'Y','N'));
    xWriteString('SureM','UserShow',xIF(CheckBoxShow.Checked,'Y','N'));
    xWriteString('SureM','UserDupe',xIF(CheckBoxDupe.Checked,'Y','N'));

    if not pFromOK then xWriteString('SureM','SENDFROM',xEditFrom.Text);
end;

//------------

procedure TFormSMSSuremCall.Mobile3Number(pOrgNumber:string; var rP1,rP2,rP3:string);
begin
    if Length(pOrgNumber) = 11 then   // 010 7655 1713
       begin
           rP1 := COPY(pOrgNumber,1,3);
           rP2 := COPY(pOrgNumber,4,4);
           rP3 := COPY(pOrgNumber,8,4);
       end else
    if Length(pOrgNumber) = 10 then  // 017 205 1713
       begin
           rP1 := COPY(pOrgNumber,1,3);
           rP2 := COPY(pOrgNumber,4,3);
           rP3 := COPY(pOrgNumber,7,4);
       end
    else
       begin
           rP1 := COPY(pOrgNumber,1,3);
           rP2 := COPY(pOrgNumber,4,3);
           rP3 := COPY(pOrgNumber,7,length(pOrgNumber)-6);
       end;
end;

procedure TFormSMSSuremCall.DllRegister;
var Registry: TRegistry;
    S: string;
begin
    Registry:=TRegistry.Create;
    Registry.RootKey := HKEY_CLASSES_ROOT;
    Registry.OpenKey('SMSCALL.SMSCALLMSG',False);
    S := Registry.ReadString('');
    Registry.Free;

    if Length( Trim(s) ) < 10 then begin
       xMSG('SMSCall.DLL 이 레지스터에 등록되어있지 않습니다.'+#13+
            '확인버튼을 누르면 레지스터 등록작업을 수행합니다.');

       xRegister(ExtractFilePath(ParamStr(0))+'SMSCall.DLL');
    end;
end;

procedure TFormSMSSuremCall.SendMessage;
{ 문자전송 }
var sRDate     : string;
    sRTime     : string;
    sFPhone    : string;
    sMemo      : string;
begin
    if RadioButtonS0.Checked then
       begin
           sRDate := '00000000';
           sRTime := '000000';
       end
    else
       begin
           sRDate := COPY(xEditDate.Text,1,4)+COPY(xEditDate.Text,6,2)+COPY(xEditDate.Text,9,2);
           sRTime := COPY(xEditTime.Text,1,2)+COPY(xEditTime.Text,4,2)+'00';
       end;

    sFPhone := xNumOnly(xEditFrom.Text);

    sMemo   := xMemo2String(MemoMessage,16);

    if Length(sMemo) > 80 then sMemo := COPY(sMemo,1,80);

    if xEmpty(sFPhone) then begin
       xMsg('오류 : 보낼사람 전화번호가 공백이면 안됩니다');

       Exit;
    end;

    if xEmpty(MemoMessage.Text) then begin
       xMsg('오류 : 보낼 메세지가 공백이면 안됩니다');

       Exit;
    end;

    if COPY(xEditTo.Text,1,8) = '단체발송' then SendMessageAll(sFPhone,sMemo,sRDate,sRTime)
                                           else SendMessageOne(sFPhone,sMemo,sRDate,sRTime);

    ShowCash;
end;

procedure TFormSMSSuremCall.SendMessageAll(pFPhone,pMemo,pRDate,pRTime:string);
var MyProgress : TFormProgressTerm;
    inc        : integer;
    sTPhone    : string;
    sTName     : string;
    nDupe      : integer;
    nFail      : integer;
    nRet       : integer;
    nSocket    : integer;

    sTP1, sTP2, sTP3 : string;
    sFP1, sFP2, sFP3 : string;
begin
    nDupe := 0;      // 중복된 갯수
    nFail := 0;      // 실패한 갯수

    TeleIndex := 0;  // 전체전송할 자료갯수

    //-- 단체발송에서 중복제외
    if CheckBoxDupe.Checked then
       for inc := 1 to ListBoxNames.Items.Count do begin
           sTPhone := COPY(ListBoxNames.Items[inc-1],10,16);
           sTPhone := xNumOnly(sTPhone);

           sTName  := COPY(ListBoxNames.Items[inc-1],26,Length(ListBoxNames.Items[inc-1])-25);

           // 중복된 번호
           if CheckFound(sTPhone,sTName) then begin
              nDupe := nDupe + 1;

              ListBoxNames.Items[inc-1] := '*'+COPY(ListBoxNames.Items[inc-1],2,Length(ListBoxNames.Items[inc-1]));
           end;
       end
    else
       for inc := 1 to ListBoxNames.Items.Count do begin
           sTPhone := COPY(ListBoxNames.Items[inc-1],10,16);
           sTPhone := xNumOnly(sTPhone);

           TeleArray[inc,1] := sTPhone;
           TeleArray[inc,2] := COPY(ListBoxNames.Items[inc-1],26,Length(ListBoxNames.Items[inc-1])-25);

           TeleIndex := inc;
       end;


    nSocket := objSMS.SMSConnect();

    If nSocket < 0 then begin
       xMsg('문자메세지 서버에 접속이 안됩니다.'+#13+#13+
            '잠시후에 다시 시도하십시오.');

       Exit;
    end;

    MyProgress := TFormProgressTerm.Create(Application);
    MyProgress.Show;
    MyProgress.Title('단체발송 처리중입니다.');
    MyProgress.MaxValue(TeleIndex);

    for inc := 1 to TeleIndex do begin
        MyProgress.Progress( inc );
        MyProgress.UpDate;

        if MyProgress.StatTerm then Break;


        Mobile3Number(pFPhone         ,sFP1,sFP2,sFP3);
        Mobile3Number(TeleArray[inc,1],sTP1,sTP2,sTP3);

        nRet:= objSMS.SMSSend(nSocket, TeleIndex, inc, inc, g_UserCode, g_UserName, g_DeptCode, g_UserName,
                              sTP1, sTP2, sTP3, inttostr(inc)+') '+Trim(TeleArray[inc,2]),
                              sFP1, sFP2, sFP3,
                              pMemo, pRDate, pRTime, g_TotPrice, g_CallPrice);

        If nRet <> 1 then nFail := nFail + 1;
    end;

    MyProgress.Free;

    ShowCash;

    xMsg('문자메세지 전송을 완료하였습니다'+#13+
         xIF(nDupe>0,IntToStr(nDupe)+'개의 자료가 중복되어 발송에서 제외되었습니다','')+#13+
         xIF(nFail>0,IntToStr(nFail)+'개의 자료가 전송시에 오류가 발생하였습니다','')+#13+
        '성공여부는 [전송결과조회]에서 확인이 가능합니다');
end;

procedure TFormSMSSuremCall.SendMessageOne(pFPhone,pMemo,pRDate,pRTime:string);
var sTPhone : string;
    sTName  : string;
    nRet    : integer;

    sTP1, sTP2, sTP3 : string;
    sFP1, sFP2, sFP3 : string;
begin
    sTPhone := xNumOnly(xEditTo.Text);

    if xEmpty(sTPhone) or ( Length(sTPhone) < 10 ) then begin
       xMsg('오류 : 받을사람 전화번호가 올바르지 않습니다');

       Exit;
    end;

    Mobile3Number(pFPhone,sFP1,sFP2,sFP3);
    Mobile3Number(sTPhone,sTP1,sTP2,sTP3);

    //-- 리스트박스에 수신자의 이름이 넘겨온 경우인지
    if ListBoxNames.Items.Count = 0 then sTName := xEditTo.Text
                                    else sTName := COPY(ListBoxNames.Items[0],26,Length(ListBoxNames.Items[0])-25);

    nRet:= objSMS.SMSSendUnit(0, g_UserCode, g_UserPass, g_DeptCode, pFPhone,
                              sTP1, sTP2, sTP3, sTName,
                              sFP1, sFP2, sFP3,
                              pMemo, pRDate, pRTime, g_TotPrice, g_CallPrice);
    case nRet of
         1 : xMsg('문자메세지 전송을 완료하였습니다.'+#13+#13+
                  '전송내역은 홈페이지(www.surem.com)에서 확인이 가능합니다');
        11 : xMsg('전송실패: 전화번호이상');
        21 : xMsg('전송실패: Connect 실패');
        23 : xMsg('전송실패: 데이터 Send 실패');
        40 : xMsg('전송실패: Ack Receive 실패');
        67 : xMsg('전송실패: 잔액부족');
        68 : xMsg('전송실패: 고객사 코드 이상');
        73 : xMsg('전송실패: 미등록회원');
        77 : xMsg('전송실패: 메세지의 내용이 없음');
        78 : xMsg('전송실패: 전화번호에 문제가 있음');
        80 : xMsg('전송실패: 이동통신사번호이상');
        82 : xMsg('전송실패: 700, 800 금지업체');
        84 : xMsg('전송실패: 예약일자이상');
        85 : xMsg('전송실패: 호출 URL 이상');
        99 : xMsg('전송실패: 데이타포맷오류');

        else xMsg('문자메세지 전송이 실패하였습니다.'+#13+#13+
                  '전송내역은 홈페이지(www.surem.com)에서 확인이 가능합니다');
    end;

    ShowCash;
end;

function  TFormSMSSuremCall.CheckFound(pTele,pName:string):Boolean;
var inc    : integer;
    rFound : Boolean;
begin
    rFound := False;

    for inc := 1 to TeleIndex do begin
        if pTele = TeleArray[inc,1] then begin
           rFound := True;

           Break;
        end;
    end;

    if rFound = False then begin
       TeleIndex := TeleIndex  + 1;

       TeleArray[TeleIndex,1] := pTele;
    end;

    Result := rFound;
end;

procedure TFormSMSSuremCall.ShowCash;
var nRet: integer;
    szType, szBasicYN: WideString;
    nBasicCnt, nBasicLeft: LongInt;
begin
    szType:='s';

    nRet:= objSMS.CashCheck(g_UserCode, g_UserPass, g_DeptCode, szType,
                           g_TotPrice, g_CallPrice, szBasicYN, nBasicCnt, nBasicLeft);

    if nRet = 1 then
       begin
           LabelCost.Caption  := Trim(FormatFloat('###,###',g_TotPrice));
           LabelCount.Caption := Trim(FormatFloat('###,###',g_TotPrice / g_CallPrice));
           LabelDanga.Caption := Trim(FormatFloat('###,###',g_CallPrice));
       end
    else xMsg('현재 잔액조액가 안됩니다. 잠시후에 다시 시도하십시오');
end;

procedure TFormSMSSuremCall.Button2Disabled;
begin
    SpeedButtonCashRefer.Enabled   := False;
end;

procedure TFormSMSSuremCall.Button2Enabled;
begin
    SpeedButtonCashRefer.Enabled   := True;
end;

procedure TFormSMSSuremCall.SaveInformation;
var MyIni    : TIniFile;
    UserDir  : string;
    UserFile : string;
begin
    UserDir  := ExtractFilePath(ParamStr(0))+'LOG';
    UserFile := UserDir+'\SLUSER.INI';

    if not DirectoryExists(UserDir) then CreateDir(UserDir);

    MyIni := TIniFile.Create(UserFile);
    MyIni .Write String('SureM','UserID'  ,xEditID.Text);
    MyIni .Write String('SureM','UserPASS',xEditPASS.Text);
    MyIni.Free;
end;

procedure TFormSMSSuremCall.PasswordShow;
begin
    if CheckBoxShow.Checked then xEditPASS.PasswordChar := #0
                            else xEditPASS.PasswordChar := '*';
end;

function TFormSMSSuremCall.LogInProgress(pMessageShow:Boolean):Boolean;
var nRet: LongInt;
begin
    if xEmpty(xEditID.Text+xEditPASS.Text) then begin
       xMsg('오류 : 아이디/패스워드가 공백이면 안됩니다');

       Result := False;

       Exit;
    end;

    g_UserCode   := Trim(xEditID.Text);
    g_UserPass   := Trim(xEditPASS.Text);
    g_DeptCode   := 'K9-HPY-PC';
    g_DeptName   := 'swmake';

    nRet:= objSMS.SMSLogin(g_UserCode, g_UserPass, g_DeptCode,
                           g_UserName, g_ReqPhone1, g_ReqPhone2, g_ReqPhone3, g_TotPrice, g_CallPrice);

    Button2Disabled;

    case nRet of                                                   
         1 : begin
                 Button2Enabled;

                 LabelCost.Caption  := Trim(FormatFloat('###,###',g_TotPrice));
                 LabelCount.Caption := Trim(FormatFloat('###,###',g_TotPrice / g_CallPrice));
                 LabelDanga.Caption := Trim(FormatFloat('###,###',g_CallPrice));

                 if pMessageShow then xMSG(#13+'로그인에 성공하였습니다.'+#13+#13);
             end;
        21 : xMsg('로그인: Connent 실패');
        22 : xMsg('로그인: 데이터 Send 실패');
        23 : xMsg('로그인: Ack Receive 실패');
        40 : xMsg('로그인: SMS 서버 이상');
        41 : xMsg('로그인: 아이디 미등록 사용자');
        99 : xMsg('로그인: 패스워드가 틀립니다');
        else xMsg('로그인: 로그인을 실패하였습니다');
    end;

    Result := xIF(nRet = 1,True, False);
end;

procedure TFormSMSSuremCall.CheckBoxShowClick(Sender: TObject);
begin
    PasswordShow;
end;

procedure TFormSMSSuremCall.SpeedButtonLoginClick(Sender: TObject);
begin
    LogInProgress(True);
end;

procedure TFormSMSSuremCall.SpeedButtonHelpClick(Sender: TObject);
{ 도움말 }
begin
    xHelp('u_cow122');
end;

procedure TFormSMSSuremCall.SpeedButtonMessageClick(Sender: TObject);
{ 이모티콘등록 }
begin
    with TFormSLSMSMessage.Create(Application) do begin
         ShowModal;
         Free;
    end;
end;

procedure TFormSMSSuremCall.SpeedButtonCashReferClick(Sender: TObject);
{ 사이버캐쉬조회 }
begin
    ShowCash;

    xMsg('사용 가능한 금액 : '+LabelCost.Caption+#13+#13+
         '건당 금액 : '+LabelDanga.Caption+#13+#13+
         '사용 가능한 횟수 : '+LabelCount.Caption);
end;

procedure TFormSMSSuremCall.SpeedButtonSystemClick(Sender: TObject);
{ 레지스터등록 }
var Registry: TRegistry;
    S: string;
begin
    Registry:=TRegistry.Create;
    Registry.RootKey := HKEY_CLASSES_ROOT;
    Registry.OpenKey('SMSCALL.SMSCALLMSG',False);
    S := Registry.ReadString('');
    Registry.Free;

    if Length( Trim(s) ) > 10 then begin
       if MessageDlg('SMSCall.DLL - 레지스터에 이미 등록되어있습니다.'+#13+#13+
                     '클라스가 등록되어있지 않습니다. 라는 오류가 나타난 경우라면'+#13+
                     '[ 예/Yes ]를 선택하여 등록작업을 다시 하십시오.'+#13+
                     '클라스와 관련된 오류가 아니라면 이 작업을 다시 할 필요가'+#13+
                     '없으며 고객지원실로 문의바랍니다.'+#13+#13+
                     '위의 내용을 무시하고 레지스터 등록작업을 다시 하시겠습니까 ?'
                     ,mtConfirmation, [mbYes,mbNo], 0) = mrYes then begin
          xRegister(ExtractFilePath(ParamStr(0))+'SMSCall.DLL');
       end;
    end;
end;

procedure TFormSMSSuremCall.SpeedButtonCloseClick(Sender: TObject);
{ 작업종료 }
begin
    SaveInformation;

    Close;
end;

procedure TFormSMSSuremCall.ImageMessageLoadClick(Sender: TObject);
begin
    with TFormSLSMSMsgItem.Create(Self) do begin
         if ShowModal = mrOK then MEMOMessage.Text := rMessage;
         Free;
    end;
end;

procedure TFormSMSSuremCall.ImageMessageSaveClick(Sender: TObject);
begin
    if xEmpty(MemoMessage.Text) then begin
       xMsg('메세지창에 내용이 비워있으므로 저장이 안됩니다.');

       Exit;
    end;

    OpenTable_SLSMS(ApolloDataSetSLSMS,True);

    with ApolloDataSetSLSMS do begin
         Append;
         FieldByName('SMSGRP').AsString := '';
         FieldByName('SMSMSG').AsString := xMemo2String(MemoMessage,0);
         FieldByName('SMSMEM').AsString := MemoMessage.Text;
         Commit;
    end;

    with ApolloDataSetSLSMS do begin
         Close;
    end;

    xMsg('저장이 완료되었습니다.');
end;

procedure TFormSMSSuremCall.ImageSendCancelClick(Sender: TObject);
{ 전송취소 }
begin
    MEMOMessage.Text := '';

    xEditTo.Text := '';
end;

procedure TFormSMSSuremCall.ImageSendOKClick(Sender: TObject);
{ 문자전송 }
begin
    ImageSendOK.Cursor  := crHourGlass;
    ImageSendOK.Enabled := False;

    //-- 로그인이 안되어있으면 로그인 먼저하고
    if g_DeptName <> 'swmake' then
       begin
           if LogInProgress(False) then SendMessage;
       end
    else SendMessage;

    ImageSendOK.Enabled := True;
    ImageSendOK.Cursor  := crHandPoint;
end;

procedure TFormSMSSuremCall.MemoMessageChange(Sender: TObject);
var sMemo : string;
    nSize : integer;
begin
    sMemo := Trim(MemoMessage.Text);

    //-- 메모장을 문자열로 전환하여 길이를 계산한다
    nSize := Length(xMemo2String(MemoMessage,16));

    if nSize > 80 then LabelMessage.Font.Color := clRed
                  else LabelMessage.Font.Color := clBlack;

    LabelMessage.Caption := IntToStr(nSize)+' / 80';
end;

procedure TFormSMSSuremCall.SpeedButtonJoinClick(Sender: TObject);
{ 회원가입 }
var html : string;
begin
    html := 'http://smscorp.surem.com/client/softmake/regist.asp';

    ShellExecute(Application.Handle,'open',pChar(html), nil,nil,SW_SHOW)
end;

procedure TFormSMSSuremCall.SpeedButtonPayMentClick(Sender: TObject);
{ 충전 }
var html : string;
begin
    html := 'http://smscorp.surem.com/client/softmake/cash.asp?usercode='+g_UserCode;

    ShellExecute(Application.Handle,'open',pChar(html), nil,nil,SW_SHOW)
end;

procedure TFormSMSSuremCall.SpeedButtonResultClick(Sender: TObject);
{ 전송결과조회 }
var html : string;
begin
    html := 'http://smscorp.surem.com/client/softmake/result.asp?usercode='+g_UserCode;

    ShellExecute(Application.Handle,'open',pChar(html), nil,nil,SW_SHOW)
end;

end.


============================================================================

function xPADC(pStr:string;pLength:integer):string;
{ xPADC('1234',10) = 'bbb1234bbb' }
var rem : integer;
begin
    rem := ( pLength - length(pStr) ) div 2;
    Result := xSpace(rem)+pStr+xSpace(rem);
end;
 
function xPADR(pStr:string;pLength:integer;pChar:Char):string;

{ xPADR('1234',6,'0') = '123400' }
var tStr  : string;
    tChar : char;
begin
    tStr  := pStr;
    if pChar = '' then tChar := ' '
                  else tChar := pChar;

    if length(tStr) > pLength then
       Result := copy(tStr,1,pLength)
    else begin
       while true do
       begin
         if length(tStr) < pLength then tStr := tStr + tChar
                                   else break;
       end;
       Result := tStr;
    end;
end;

function xPADL(pStr:string;pLength:integer;pChar:Char):string;

{ xPADL('1234',6,'0') = '001234' }
var tStr  : string;
    tChar : char;
    rChar : string;
begin
    tStr := pStr;
    if pChar = '' then tChar := ' '
                  else tChar := pChar;

    if length(tStr) > pLength then
       Result := copy(tStr,1,pLength)
    else begin
       rChar := '';
       while true do
       begin
         if length(tStr) < pLength then tStr := tChar + tStr
                                   else break;
       end;
       Result := tStr;
    end;
end;

function xSpace(pLen:Integer):String;

{ xSpace(4) = '   ' }
var inc : integer;
    ret : String;
begin
    ret := '';
    for inc := 1 to pLen do ret := ret + ' ';
    Result := ret;
end;

========================================================================================

 

function xString2Memo(pStr:string;pLength:integer):string;
{ xString2Memo('...') = '...'
            문자열을 받아서 메모장에 들어가게 변경한다.
                   한글때문에 적당한 위치에서 라인이 넘어가게 }
var tStr   : string;
    tRet   : string;
    tRem   : string;
    inc    : integer;
    Cnt    : integer;
begin
    tStr   := '';
    tRem   := pStr;
    tRet   := '';

    while True do begin
          //- 처리할 문자가 없거나 실수로 길이 파라미터를 1보다
          //-- 작게 넘겼다면 오류이므로 종료한다
          if xEmpty(tRem) or ( pLength < 1 ) then Break;

          if Length(tRem) < pLength then begin
             tRet := tRet + tRem;

             Break;
          end;

         //-- 지정된 길이만큼 잘라서
          tStr := COPY(tRem,1,pLength);  

          //-- 문자열에서 아스키값이 122(z)보다 큰게 몇개인지
          //-- 카운트해서 홀수개인 경우는 한글이 짤린경우이므로
          //-- 지정된 길이에서 앞글자 까지만 자르도록 한다.
          cnt := 0;
          for inc := 1 to length(tStr) do begin
              if ord(tStr[inc]) > 122 then cnt := cnt + 1;
          end;

          //-- 마지막글자의 아스키값이 122(z)보다 크면
         //-- 한글이 반 짤린것이므로
          //-- 앞의 글자까지만 짜르고 줄을 넘긴다
          if cnt mod 2 = 0 then
             begin
                 tRet := tRet + tStr;            //-- 메모장에 붙이고
                 tRet := tRet + #13 + #10;       //-- 라인을 넘기고
                //-- 나머지를 저장하고
                 tRem := COPY(tRem,pLength+1,Length(tRem)-pLength); 
             end
          else
             begin
                 tStr := COPY(tRem,1,pLength-1);
                 //-- 지정된 길이에서 앞글자까지만 잘라서

                 tRet := tRet + tStr;            //--  메모장에 붙이고
                 tRet := tRet + #13 + #10;       //-- 라인을 넘기고
                 tRem := COPY(tRem,pLength,Length(tRem)-pLength+1);  //-- 나머지를 저장하고
             end;
    end;

    Result := tRet;
end;


posted by 유돌이
2008. 12. 29. 23:28 델파이
function ReverseString(s: String): String;
var
  i: integer;
  s2: string;
begin
  s2 := '';
  for i := 1 to Length(s) do
    s2 := s[i] + s2;
  Result := s2;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  ShowMessage(ReverseString('역순으로 나타낼 문자열'));
end;

posted by 유돌이
2008. 12. 29. 23:27 델파이
function NextToken(var s:string; Separator:char):string;
var
  Sep_Pos : byte;
begin
  Result := '';
  if length(s)>0 then begin
   Sep_Pos := pos(Separator, s);
   if Sep_Pos >0 then begin
    Result := copy(s, 1, Pred(Sep_Pos));
    Delete(s,1,Sep_Pos);
   end
   else begin
     Result := s;
     s := '';
   end;
  end;
end;

// 예제
while length(TheString) > 0 do
begin
  NextParam := NextToken(TheString, ',');
  // etc..
end;

'델파이' 카테고리의 다른 글

HTTP CONNECTION  (0) 2008.12.29
문자열 역순으로 출력하기  (0) 2008.12.29
OleVariant 형을 스트링으로 변환하는 방법  (0) 2008.12.29
쿠키 읽고/쓰기(GetCookie, SetCookie)  (0) 2008.12.29
파일 찾기  (0) 2008.12.24
posted by 유돌이
2008. 12. 29. 23:26 델파이

uses
Variants; 

* 헤더 부분에 추가

 

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


function VarToStr(const V: Variant): string;
-> str := VarToStr(Variant);


function VarToStrDef(const V: Variant; const ADefault: string): string;
-> str := VarToStr(Variant, 'String');

function VarAsType(const V: Variant; AVarType: TVarType): Variant;
-> str := VarAsType(Variant, varString);


'델파이' 카테고리의 다른 글

문자열 역순으로 출력하기  (0) 2008.12.29
구분자(delimiter)를 사용한 문자열 파싱(parsing)  (0) 2008.12.29
쿠키 읽고/쓰기(GetCookie, SetCookie)  (0) 2008.12.29
파일 찾기  (0) 2008.12.24
실행파일 삭제  (0) 2008.12.24
posted by 유돌이
2008. 12. 24. 18:04 델파이

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    procedure ListFiles(D,Name,SearchName : String);
  end;

var
  Form1: TForm1;

implementation
{$R *.DFM}

procedure TForm1.ListFiles(D, Name, SearchName: String);
var
  SR: TSearchRec;
begin
  if D[Length(D)] <> '' then
    D := D + '';

  if FindFirst(D+Name, faAnyFile, SR) = 0 then
    repeat
      if (SR.Attr <> faDirectory) and (SR.Name[1] <> '.') then
        if AnsiUpperCase(SR.Name) = AnsiUpperCase(SearchName) then
          ListBox1.Items.Add(D+SR.Name); {파일을 찾으면 label1.Caption에 디렉토리를 표시}
    Until (FindNext(SR)<>0);
  FindClose(SR);

  if FindFirst(D+'*.*', faDirectory, SR) = 0 then
  begin
    repeat
      if ((Sr.Attr and faDirectory) = faDirectory) and
         (SR.Name[1]<>'.')
      then
        ListFiles(D+SR.Name+'', Name, SearchName); // 재귀적 호출을 한다
    until (FindNext(SR) <> 0);
  end;
  FindClose(SR);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  // c: 부터 하위 디렉토리에서 delphi32.exe 파일을 찾는다
  ListFiles('c:','*.*','project1.exe');
end;

end.

 

 

출처 : http://www.howto.pe.kr/


'델파이' 카테고리의 다른 글

OleVariant 형을 스트링으로 변환하는 방법  (0) 2008.12.29
쿠키 읽고/쓰기(GetCookie, SetCookie)  (0) 2008.12.29
실행파일 삭제  (0) 2008.12.24
키보드 이벤트[keybd_event  (0) 2008.12.24
레지스트리 값 읽고 쓰기  (0) 2008.12.24
posted by 유돌이
2008. 12. 24. 18:03 델파이

//자기 자신을 삭제하는 로직//

 

procedure DeleteMe;
var
BatchFile:TextFile;
BatchFileName:String;
ProcessInfo:TProcessInformation;
StartUpInfo:TStartupInfo;
begin
//
BatchFileName:=ExtractFilePath(application.exename)+'$$336699.bat';

AssignFile(BatchFile, BatchFileName);
Rewrite(BatchFile);

Writeln(BatchFile, ':try');
Writeln(BatchFile, 'del "' + application.exename + '"');
Writeln(BatchFile, 'if exist "' + application.exename + '"' + ' goto try');
Writeln(BatchFile, 'del "' + BatchFileName + '"');
CloseFile(BatchFile);

FillChar(StartUpInfo, SizeOf(StartUpInfo), $00);
StartUpInfo.dwFlags := STARTF_USESHOWWINDOW;
StartUpInfo.wShowWindow := SW_HIDE;

if CreateProcess(nil, PChar(BatchFileName), nil, nil,False, IDLE_PRIORITY_CLASS,
nil, nil, StartUpInfo, ProcessInfo) then
begin
CloseHandle(ProcessInfo.hThread);
CloseHandle(ProcessInfo.hProcess);
end;
//Close;
end;


'델파이' 카테고리의 다른 글

쿠키 읽고/쓰기(GetCookie, SetCookie)  (0) 2008.12.29
파일 찾기  (0) 2008.12.24
키보드 이벤트[keybd_event  (0) 2008.12.24
레지스트리 값 읽고 쓰기  (0) 2008.12.24
웹에 있는 파일 다운로드 받기(UrlDownloadToFile)  (0) 2008.12.24
posted by 유돌이
prev 1 2 3 4 next