12.04에서 16.04로 업그레이드 한 이후에 커널을 4버전으로 업그레이드했다.

이후에 apt upgrade할 때면 수시로 /boot 가 용량이 부족하다고 에러.

다른 업데이트도 방해해서 귀찬다.


커널 업데이트가 떴다 하면 /boot 안에 있는 내용을 다른 곳에 옮기고 업그레이드 완료 후에 다시 옮겨서 이전버전을 purge하는 눈물나는 삽질 ㅠ.ㅜ

cd를 구해서 부팅한 다음에 gparted로 boot 파티션을 늘리라는 포스팅들이 대부분인데...


귀...찬...다...




한참 다른 방법 검색하다보니 /boot 를 sda1으로 옮겨버리는 방법이 있다.


1. 설치되어 있는 커널들을 찾아서 오래된 커널들은 지워준다.


sudo dpkg --list | grep linux


sudo apt purge linux-headers-4.15.0-33 linux-headers-4.15.0-33-generic linux-image-4.15.0-33-generic linux-image-extra-4.15.0-33-generic linux-signed-image-4.15.0-33-generic

4.15.0-33 은 리스트에 표시된 지울 버전으로 변경한다.



2. /boot 를 sda1에 복사


sudo cp -a /boot /boot-tmp

sudo umount /boot

sudo rmdir /boot && mv /boot-tmp /boot



3. fstab 수정


sudo vi /etc/fstab


/boot 파티션을 주석 처리한다.



4. grub 업데이트


sudo update-grub


완료되면 잘되길 기원하면서 경건한 마음으로 reboot 해본다 ;;;

어딘가 남아있을 기존 boot 파티션은 머릿속에서 지워버리기로 한다.


개발환경 : Ubuntu 18.04 Desktop



1. Laradock 설치


https://laradock.io/


Quick Overview에는 mysql을 설치하라고 되어 있으나 8.0.4 이후 버전 이슈로 인해 mariadb로 설치한다.

http://yypbd.tistory.com/1784


여러 개의 프로젝트를 하나의 Laradock으로 구동하는 방법으로 설정한다.


내 작업경로는 ~/project/php/



a) laradock 다운로드


git clone https://github.com/laradock/laradock.git



b) .env 파일이 없다면 env-example 파일을 복사해서 .env 파일 생성


cp env-example .env



c) mariadb를 이용할 것이므로

.env 파일에서 PMA_DB_ENGINE 항목을 찾아서 mariadb로 변경


d) db 설정 변경

필요하다면 .env 파일에서 MARIADB 항목을 찾아서 생성할 db, user, password등을 변경한다.

여기에서는 default 설정을 그대로 이용하기로 한다.




2. Laravel 설치


~/project/php/ 경로에 작업할 Laravel 프로젝트를 생성한다.

여기서는 laradock의 예제와 같이 project-1, project-2 을 만들어본다.


composer, php-mbstring, php-xml을 설치해야 할 수도 있다.

docker와 별개로 laravel을 다운받기 위해 필요하다.

내 경우는 php 7.0이 설치되어 있어서 php7.0-mbstring, php7.0-xml, php7.0-mysql을 설치했다.


sudo apt install php7.0-mbstring php7.0-xml php7.0-mysql


composer create-project --prefer-dist laravel/laravel project-1

composer create-project --prefer-dist laravel/laravel project-2




3. hosts 수정


브라우저에서 아래의 url로 테스트하기 위함이다.

http://project-1.test

http://project-2.test


a) hosts 파일 수정


sudo vi /etc/hosts


127.0.0.1  project-1.test
127.0.0.1  project-2.test


위 내용을 추가한다.

저장 완료하고 hosts 적용


sudo /etc/init.d/networking restart



b) 접속 확인

127.0.0.1로 ping이 가는지 확인해본다.


ping project-1.test

ping project-2.test





4. nginx 설정 수정


프로젝트 경로와 테스트 호스트를 연결한다.


laradock/nginx/sites/ 에서 laravel.conf.example을 복사해서 project-1.conf, project-2.conf 파일을 생성한다.


cp laravel.conf.example project-1.conf

cp laravel.conf.example project-2.conf


각각의 conf 파일을 열어서


server_name project-1.test;

root /var/www/project-1/public;

server_name project-2.test;
root /var/www/project-2/public;


와 같이 수정한다.




5. docker 실행


a) docker 설치


https://docs.docker.com/install/linux/docker-ce/ubuntu/#prerequisites



b) docker-compose 설치


https://docs.docker.com/compose/install/#install-compose



c) docker 실행


~/project/php/laradock/ 에서


sudo docker-compose up -d nginx mariadb phpmyadmin redis workspace


docker나 docker-compose의 버전이 낮다면 설치가 제대로 안될 수가 있다.
a)의 문서 보고 가급적 최신 버전으로 설치한다.


6. phpmyadmin 접속 확인

웹브라우저를 열어서 http://localhost:8080  으로 접속해본다.


server : mariadb

username : root

password : root



7. project-1 설정


~/project/php/project-1 에서 작업



cp .env.example .env

vi .env


laradock의 설정을 변경하지 않았으면 기본으로 default database가 생성되나 이 데이터베이스를 이용해본다.

.env 파일을 열어서 아래의 내용과 같이 추가하거나 수정한다.


APP_URL=http://project-1.test


DB_HOST=mariadb

DB_DATABASE=default

DB_USERNAME=default

DB_PASSWORD=secret


REDIS_HOST=redis


QUEUE_HOST=beanstalkd


8. 사이트접속 확인


웹브라우저를 열어서


http://project-1.test/


사이트가 접속이 되는지 확인한다.

키설정을 하지 않았으니 당연히 오류가 발생할 것이다.


php artisan key:generate

env DB_HOST=127.0.0.1 php artisan migrate



다시 접속해서 laravel 초기 페이지가 제대로 열리는지 확인해본다.



Laravel 개발환경 구축하려고 Laradock을 설치했다.

작업환경은 Ubuntu 18.04.




Laradock의 Quick Overview에 있는 내용대로 적용.

docker-compose로 docker 실행하고 Laravel 받아서 설치 완료.


Model 추가해서 db 작업되는 것 확인하고 User 추가해서 로그인하려는데 오류 발생.

SQLSTATE[HY000] [2054] The server requested authentication method unknown to the client



구글링해보니 MySQL 8버전에서 기본인증 부분이 변경되었다고 한다.


https://mysqlserverteam.com/mysql-8-0-4-new-default-authentication-plugin-caching_sha2_password/

https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-4.html


Laradock 설정에서 mysql이 latest로 되어 있어서 8.0.12버전이 설치되었다.





해결방안


1. mysql 8.0.12 버전의 인증 방법을 예전 인증방법으로 변경한다.

https://github.com/laradock/laradock/issues/1392


2. mysql을 예전 버전으로 downgrade


3. mariadb로 변경




'Programming > PHP' 카테고리의 다른 글

Laradock으로 Laravel 개발환경 구축하기  (0) 2018.10.18

오래 사용한 N40L 서버에 설치한 Ubuntu 12.04 Server가 지원기간이 만료되었다 해서 업그레이드 하기로 결심.

안되면 밀어버리자란 마음으로 cron으로 돌려놓은 백업파일들 한번 살펴보고 업그레이드를 시작했다.


do-release-upgrade 명령으로 14.04를 거쳐서 16.04로 쭉 업그레이드.

다행히도 별 다른 오류는 없이 16.04로 설치는 끝났다.


하지만 예상했던대로 이런저런 오류가 발생...



1. apt update 시 GPG apt-key 오류 발생

1) 커널이 3.5버전인지라 4.x대로 업그레이드

https://wiki.ubuntu.com/Kernel/LTSEnablementStack

sudo apt-get install --install-recommends linux-generic-hwe-16.04


커널 업그레이드 하고 재부팅.

apt update하니 오류 메시지가 바꼈다.


2) GPG 키 다운로드


우선 Ubuntu


sudo apt-key adv --recv-keys --keyserver keyserver.ubuntu.com 40976EAF437D05B5

sudo apt-key adv --recv-keys --keyserver keyserver.ubuntu.com 3B4FE6ACC0B21F32


다음은 gitlab

curl https://packages.gitlab.com/gpg.key 2> /dev/null | sudo apt-key add - &>/dev/null




2. mysql 서버


Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock'

메시지 발생


간단하게 재설치로 해결함

sudo apt-get --reinstall install mysql-server




3. python


기존에 ppa로 설치했던 버전 삭제 후 재설치

virtualenvwrapper 설치하고

.bashrc .zshrc에 실행 경로 지정

 

export WORKON_HOME=~/py-envs

source /usr/local/bin/virtualenvwrapper.sh

export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3



우선 이정도에서 서버가 진정한 것 같으니 다른 오류가 발생하는지는 더 살펴보도록 하자.


'Programming > Ubuntu' 카테고리의 다른 글

Ubuntu boot 파티션 용량 부족시  (0) 2018.10.18


1. Android Studio에서 NDK 설치

  Setting->Android SDK->SDK Tools에서

  CMake, LLDB, NDK 항목 설치



2. cygwin 설치

  https://cygwin.com/install.html


  a) 설치할때 wget 추가해서 설치


  b) cygwin 실행후 apt-cyg 설치

wget raw.github.com/transcode-open/apt-cyg/master/apt-cyg

chmod +x apt-cyg

mv apt-cyg /usr/local/bin


  c) 필요한 package들 설치

apt-cyg install vim bash gcc-core gcc-g++ gzip make unzip zip automake cmake



3. FFmpeg 다운로드

https://www.ffmpeg.org/download.html

최신 버전 다운로드하니 3.3.3이군.


$NDK/sources/ffmpeg-3.3.3 에 압축해제했다.



4. FFmpeg 컴파일하기 위한 configure 및 스크립트 수정작업

http://blog.naver.com/just4u78/220628698165

에 정리가 잘되어 있어서 참조했는데 1년이 넘어서 그런지 수정할 부분이 좀 있었다.


$NDK/sources/ffmpeg-3.3.3 폴더에 빌드에 필요한 파일을 작업한다.


  a) configure 파일 수정

    파일을 복사해서 configure_android를 하나 생성하고 수정함

#ln_s_default="ln -s -f"

ln_s_default="cp -f"

  ln은 생성된 so 파일의 심볼릭 링크 문제 때문에 윈도우는 cp로 변경해줘야 된다.

  강좌를 보니 경로나 다른 부분도 수정할게 있다고 하는데 안해도 문제는 없는 것 같다.


  b) build_android 스크립트 작성


build_android_so.sh 파일 작업

#!/bin/bash

NDK=d:/devel/sdk/android/ndk-bundle

SYSROOT=$NDK/platforms/android-26/arch-arm/

TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64

CUR=`cygpath -m $(pwd)`

TEMPDIR=`cygpath -m /tmp`

TMP=`cygpath -m /tmp`

TMPDIR=`cygpath -m /tmp`

function build_one

{

./configure_android \

    --prefix=$PREFIX \

    --enable-shared \

    --disable-static \

    --disable-doc \

    --disable-ffmpeg \

    --disable-ffplay \

    --disable-ffprobe \

    --disable-ffserver \

    --disable-avdevice \

    --disable-symver \

    --cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \

    --target-os=android \

    --arch=arm \

    --cpu=armv7-a \

    --enable-cross-compile \

    --sysroot=$SYSROOT \

    --extra-cflags="-Os -fpic $ADDI_CFLAGS" \

    --extra-ldflags="$ADDI_LDFLAGS" \

    $ADDITIONAL_CONFIGURE_FLAG

make clean

make -j4

make install

}

CPU=arm

PREFIX=$CUR/android/$CPU

ADDI_CFLAGS="-marm"

build_one


    강좌에서는 static 으로 빌드 후에 shared로 빌드하라고 되어 있는데 딱히 그럴 필요는 없는 것 같다.

    경로 부분 확인해서 환경과 맞도록 세팅

    윈도우의 편집기에서 작업했다면 줄바꿈이 CRLF이므로 LF로 변경한다.

    cygwin에서 dos2unix를 이용하던지 notepad++에서 바꾸기로 변경.


    빌드하고 열심히 일하겠지? 하고 작업관리자를 열어봤는데 cpu가 놀고 있네.

    make -j4 옵션 추가해서 cpu를 막 굴려주자.

    사양이 좋다면 숫자를 더 늘려주도록.


    다른 플랫폼의 so를 빌드하고 싶다면

    https://developer.android.com/ndk/guides/standalone_toolchain.html

    참조해서 toolchain, cpu 변경해서 빌드


5. Build 

    cygwin을 실행하고 ffmpeg 폴더로 이동해서

    ./build_android_so.sh 실행


    빌드 시작시의 파일 없다는 메시지는 위에서 수정한 심볼릭 링크 경로 문제이니 무시.

    정상적으로 성공했다면 ffmpeg 폴더 내에 android/arm 폴더가 생성되고 그 안에 header 파일 및 so 파일이 생성된다.



'Programming > Android' 카테고리의 다른 글

Android NDK r7b 설치 및 Sample 돌려보기  (0) 2012.02.21

지난번 프로젝트에 자바랑 데이터 교환할 일이 있었는데 UInt64 형을 넘겨받았다.


Little Endian으로 바꿔야 하는데 msdn 뒤져보니 htonll 함수가 winsock2에 있는데 Windows 8부터 지원이라고 써있네.


그때 급해서 간단하게 만들어 쓰다가 Visual Studio 2013 Community 깔아서 winsock2.h 찾아보니 소스로 구현이 되어있네.


나중에 쓸일이 있을까 해서 관련 소스 포팅...






msdn에 ntohll 함수 return 값이 설명이 잘못되어 있는 걸 찾았다.


https://msdn.microsoft.com/ko-kr/library/windows/desktop/jj710202%28v=vs.85%29.aspx


return이 u_long인데 winsock2.h에는 unsigned __int64로 되어있다.



유닛 소스는 여기에


https://github.com/yypbd/yypbd-Delphi-Libs/blob/master/lib/Winsock2Addon.pas



mORMot는 Synopse 사의 Client-Server ORM/ODM SOA MVC framework이다.

집에 있는 ubuntu서버에 써먹어 볼 수 있을까 싶어 테스트해본다.



1. 준비물

  1) mORMot 다운로드

  http://synopse.info/files/mORMotNightlyBuild.zip

  latest stable version에서는 지원하지 않으니 Nightly Build로...

  적당한 폴더에 압축해제


  2) mingw 다운로드

  http://sourceforge.net/projects/mingw/files/

  다운로드 후 설치



2. Sample 소스 Build


  1) Build

mORMot\SQLite3\Samples\02 - Embedded SQLite3 ORM\

폴더 내의 Project02.lpr 프로젝트를 Build해보자.



sqlite3.o, libkernel32.a, libgcc.a 가 없다고 나온다.



  2) obj 파일 생성

   1)의 빌드에 필요한 obj 파일을 만들기 위해 mORMot\SQLite3\ 폴더 내의 c-fpcmingw.bat 를 편집한다.



mingw를 설치한 경로를 지정해주고 mingw의 gcc폴더에 들어가서 버전을 확인해서 변경해주고 저장


c-fpcmingw.bat 를 실행하면 아래와 같이 obj 파일을 생성한다.



완료 후 mORMot\fpc-win32 폴더에 Build에 필요한 세 파일이 생성되었다.



  3) 다시 Build...

라자루스에서 빌드하면 Project02.exe 파일이 생성된다.

여전히 괴랄한 17mb짜리 실행파일...

디버그 정보를 제거하면 용량이 확 줄어든다.




3. Project02.exe 샘플 실행



실행하면 project02.db3 파일이 생성된다.

Add&Find 테스트...


질 되는듯 하다가 한글을 입력해보니 제대로 안들어간다.

sqlitespy로 db파일을 살펴보니 Delphi쪽에서 생성한 db3파일의 한글은 제대로 들어가는데 Lazarus쪽에서 생성한 db3파일은 한글이 깨져서 들어가 있다.

Unit1.pas 소스에서 StringToUTF8, UTF8ToString 함수를 빼고 나니 정상 작동한다.

뭐지 -_-?



이제 VirtualPC에 우분투 깔아서 console 프로그램 만들어봐야겠구나.

언제가 될진 모르지만 ㅠ.ㅜ


1. ServerContainerUnit

  a. ServerContainer1 Name

    제어판 서비스 속성의 서비스 이름 변경

    

  b. ServerContainer1 DisplayName

    제어판 서비스 속성의 표시 이름 변경


  c. 서비스 Description 추가 - AfterInstall 이벤트에 추가

    제어판 서비스 속성의 설명 변경




2. ServerMethodsUnit

  a. ServerMethods1 Name 변경

    클라이언트가 서비스에 접속할때 이용하는 TDSProviderConnection 컴포넌트의 ServerClassName에 설정할 이름





ShowMessage, MessageDlg를 쓰면 버튼, 캡션이 영문으로 나온다.


  ShowMessage( 'asdf' );

  MessageDlg( '가나다라', mtInformation, mbYesNo, 0 );






예전에는 메시지창을 만들어서 쓰곤 했었는데 귀...귀찬....


MessageDlg의 인자로 Winapi의 MessageBox를 쓰도록 Wrapping하려고 했으나...

Winapi의 MB_ 항목들에는 YesToAll, NoToAll, Abort 등 버튼이 없다.

MessageBox로 Wrapping하다가 델파이에 있는 기능이 더 많은데?라는 생각에 Stop!!


문자열을 바꿔버리면 될까나? 라는 생각이 들어서 Dialogs 유닛 확인~

Dialogs 유닛을 보니 Consts유닛에 있는 resourcestring 문자열들을 이용하고 있다.



작업


우선 Consts 유닛을 다른 이름으로 저장한 후에 수정했다.

우선은 메시지 창에서 사용하는 부분 정도만 수정


  SMsgDlgWarning = '경고';

  SMsgDlgError = '오류';

  SMsgDlgInformation = '알림';

  SMsgDlgConfirm = '확인';

  SMsgDlgYes = '예(&Y)';

  SMsgDlgNo = '아니오(&N)';

  SMsgDlgOK = '확인';

  SMsgDlgCancel = '취소';

  SMsgDlgHelp = '도움말(&H)';

  SMsgDlgHelpNone = '도움말 없음';

  SMsgDlgHelpHelp = '도움말';

  SMsgDlgAbort = '취소(&A)';

  SMsgDlgRetry = '재시도(&R)';

  SMsgDlgIgnore = '무시(&I)';

  SMsgDlgAll = '전부(&A)';

  SMsgDlgNoToAll = '전부 아니오(&o)';

  SMsgDlgYesToAll = '전부 예(&A)';

  SMsgDlgClose = '닫기(&C)';


그러고는 저장한 파일을 프로젝트에 추가~

(꼭 추가하지 않더라도 같은 경로에 넣어도 된다.)



위의 소스를 다시 돌려보자




최종 결과물




오홋!!! 됐다~

파일 저장해놨다가 프로젝트 할때 추가하면 될듯하다.

메시지창 말고 다른 메시지도 한글로 바꿔서 적용하면 국내 프로그램 만들 때 편하게 쓸 수 있을 것 같다.



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


ps. 테스트를 해보니 따로 저장한 파일을 library path에 추가되어 있는 경로에 넣어도 된다.

 그러면 프로젝트에 넣지 않아도 한글로 표시된다.


그러므로 난 앞으로 메시지는 모두 한글로 보여줄래!! 라고 한다면 Consts유닛을 델파이의 라이브러리 경로에 추가되어 있는 폴더에 넣으면 되고

필요한 경우에만 쓰고자 한다면 Consts 유닛을 따로 보관하다가 필요한 경우 프로젝트 경로에 복사해 넣으면 되겠다.


c로 된 헤더 포팅해보다가 ...을 발견했다.

예를 들자면 printf가 있겠지?

printf




헛??? array of const로 포팅해야 하나 하고 보다가 varargs 라는 걸 찾았다.

루아 dll에 비슷한 함수가 있어서 가져와봤다.

헤더에는 이렇게 선언되어 있다.

lua_pushfstring


포팅해보자면




혹시나 델파이 내에서 함수도 저렇게 만들어서 쓸 수 있나 했는데 안된다 -_-;
c쪽 헤더 포팅할때나 쓸 수 있는듯
구현할때는 array of const 를 쓰자~




MSDN에 있는 What's New in the Windows API 보다가 VHD 관련한 API가 보여서 포팅해봤다.

VHD 관련 API는 Windows 7 이상에서만 동작한다.
서버는 2008 R2 이상부터

포팅한 헤더 이름은 VirtDisk.h
헤더의 SDK는 v7.0의 것을 이용했다.
MSDN의 예제 링크는 여기
MSDN에 관련 VHD 페이지는 VHD Reference를 참고하면 되겠다.


Example


아아아~ 귀차니즘으로 인한 콘솔보다 못한 UI라니 ㅠ.ㅜ

1. Create

예제에서는 C:\aaa.vhd의 경로에 파일을 생성한다.
현재 예제에서는 사이즈가 256mb로 지정되어 있다.



2. Attach & Detach

VHD를 시스템에 Attach시킨다.
딸깍하는 Windows 효과음이 나면서 처음이라면 가상드라이브 관련 드라이버도 설치가 진행된다.
누르고 자시고 할 것도 없이 그냥 진행된다 -_-;
탐색기에서는 변화가 없으니 제어판의 디스크 관리 항목을 열어보자.


디스크 1이라는 항목이 추가되어 있다.
디스크 1이라고 씌여진 부분에서 마우스 우클릭 후 디스크 초기화를 하면 온라인으로 상태가 변화된다.
256 MB가 씌여진 부분에서 우클릭 후 새 단순볼륨를 누르고 포맷을 진행하면 드라이브가 추가된다.



포맷이 완료되면 위 그림처럼 드라이브가 생성되고 파일을 읽고 쓸 수 있게 된다.
물론 탐색기에서 드라이브처럼 이용할 수 있다.
Detach하면 드라이브가 제거되며 다시 Attach하면 위에서 설정된 드라이브로 바로 마운트된다.


3. 기타 API

CompactVirtualDisk와 MergeVirtualDisk는 예제에서도 실행이 잘 안되서 확인을 못했고 나머지 api는 Attach 혹은 Detach 여부에 따라 동작이 가능했다.



PS

1. MINENUMSIZE 까먹어서 테스트 하다가 삽질 -_-;
http://docwiki.embarcadero.com/RADStudio/en/Delphi_Compiler_Directives_(List)_Index 
컴파일러 지시자 링크

2. MSDN보니 Windows 8에서 API가 몇개(AddVirtualDiskParent 등) 추가됐다 -_-;;;
 어..어이~ 마소~ Windows7도 얼른 지원해주라구.


예전 버전들에서는 압축 관련 작업할때

1. 7zip dll
http://www.progdigy.com/?page_id=13 

2. TSevenZipVCL
http://www.rg-software.de/rg/index.php?option=com_content&task=view&id=29&Itemid=51 

3. SynZip
http://synopse.info/forum/viewtopic.php?pid=163 

JCL에도 있다는데 이것저것 깔리는게 많으니 패쓰~





Delphi XE2버전에는 Zip라이브러리가 포함되어 있다.

사용예를 보자면

1. uses추가
  uses에 System.Zip을 추가한다.


2. 압축하기


3. 압축풀기




압축이나 해제시 스트림이나 바이트 배열도 지원해서 사용하기는 편리한데 압축/해제 Progress를 Notify할 수 있는 프로퍼티가 없다.

소스가 다 있으니 프로퍼티 추가해서 써도 같기도 하고 -_-;;

뭐 다음 버전에 추가되리라 믿어야지.

우선은 큰 파일 말고 작은 파일 처리할때 써야 할 듯 하다.

추가로 Encryption 관련 부분도 없다 -_-;;;

MSDN Shell관련 API를 찾다가 PathMakeUniqueName 함수를 찾았다.

지난번 프로젝트에서 파일을 새로 만들때 유니크한 파일명을 구하려고 생쑈했던 기억이 나서 살펴봤다.


[MSDN PathMakeUniqueName]
http://msdn.microsoft.com/en-us/library/windows/desktop/bb776476(v=vs.85).aspx 
상세 파라미터나 리턴값은 여기서~


[Delphi]



[C++]



[테스트]


경로에 메일서식.txt 파일 있는 경우
D:\메일\메일서식 (1).txt 리턴

경로에 메일서식.txt, 메일서식 (1).txt가 있는 경우
D:\메일\메일서식 (2).txt 리턴

경로에 메일서식.txt, 메일서식 (1).txt, 메일서식 (3).txt가 있는 경우
D:\메일\메일서식 (2).txt 리턴


[ETC]
PathMakeUniqueName 함수는 인자중 Template를 이용한 방법이고 실제 존재하는 파일을 기반으로 유니크한 파일명을 얻어내는 PathYetAnotherMakeUniqueName 함수도 있다.

http://msdn.microsoft.com/en-us/library/windows/desktop/bb776479(v=vs.85).aspx 


Revision 7b(r7b)를 설치해보기로 했다.

Eclipse, Android SDK와 ADT가 설치되어 있다고 가정한다.


1. NDK및 Cygwin 설치

1). Android NDK 다운로드 및 설치

http://developer.android.com/sdk/ndk/index.html

다운로드하고 원하는 경로에 압축을 해제한다.
(경로가 간단하면 cybwin에서 찾아들어갈때 편하다)


2). Cygwin 설치

http://www.cygwin.com/
 

setup.exe를 다운받아 설치를 진행한다.
기본 설정으로 c:\cygwin에 설치했다.

설치 진행시에 국내의 미러 사이트를 입력하면 빠르게 진행된다.
ftp://ftp.kreonet.re.kr
ftp://ftp.kaist.ac.kr
두 곳중에 빠른 곳을 이용하면 되겠다.

패키지 선택시에 devel 항목 밑에 있는 make를 추가한다.

설치가 완료되면 Cygwin Terminal을 실행하고 make -v를 실행해서 설치가 제대로 됐는지 확인한다.



2. hello-jni Sample 돌려보기

1) Cygwin Terminal 실행


2) 샘플 폴더의 hello-jni 로 경로를 이동

cd /cygdrive/d/Component/Java/android-ndk-r7b/

cd samples/hello-jni/jni



3) ndk-build로 빌드

ndk-build
D:\Component\Java\android-ndk-r7b에 있으므로

../../../ndk-build


4) libs 폴더 확인

libs 
폴더에 armeabi\libhello-jni.so가 생성되었는지 확인한다.


5) Android Project 생성 및 실행

Eclipse를 실행하고 File->New->Android Project로 프로젝트를 생성한다.


Create project from existing source를 선택하고

Location을 샘플폴더인 \android-ndk-r7b\samples\hello-jni로 설택한다.
(내 경우는 D:\Component\Java\android-ndk-r7b\samples\hello-jni

에뮬레이터나 기기를 연결해서 hello-jni.c에서 리턴한 문자열이 제대로 출력되는지 확인한다.

혹시 Problems에 빨간 글씨로 뜨는게 있다면 ADT나 Android SDK를 업데이트한다.



아래 사진처럼 나오면 성공~


 


'Programming > Android' 카테고리의 다른 글

Windows에서 Android용으로 FFmpeg 빌드해보기  (2) 2017.08.22
문제발생

NDK 샘플 컴파일해서 돌려보려는데 안드로이드 SDK도 업데이트를 해야한단다.

겸사겸사 Eclipse도 업데이트하려는데...

이런 에러창이 떴다.

An internal error occurred during: "Install download0".

Comparison method violates its general contract!


확인창을 누르면 Install Download1 이런식으로 번호가 증가했다.


해결방법

Eclipse가 설치되어 있는 폴더내의 eclipse.ini 파일에 아래의 옵션을 추가한다.

-Djava.util.Arrays.useLegacyMergeSort=true


내 경우에는 업데이트한 후에 다시 옵션을 제거했다.

'Programming > Java' 카테고리의 다른 글

Java 개발툴 JBuilder 2008 R2 Turbo 설치기  (2) 2009.05.12

C#으로 작성된 xls파일을 생성하는 라이브러리가 있길래 Delphi로 포팅해봤다.

라이브러리 & 예제


 
C# 소스 출처http://www.codeproject.com/KB/office/biffcsharp.aspx

BIFF 포맷 문서 : http://sc.openoffice.org/excelfileformat.pdf


C# 소스 작성자 이름이 익숙하길래 검색해보니 예전에 잘 이용하던 psvDialogs를 만든 사람이네.

그런데 이제 C#만 개발한다고... 아 안타깝다... ㅠ.ㅜ


라이브러리를 사용하는 예제 코드는 아래와 같다.




생성된 파일을 엑셀에서 열어보면 아래와 같다.

 

 


ListView에서 Theme를 켜고 GroupView를 True로 세팅하면 위와같이 Group으로 볼수 있다.

Group의 순서를 바꾸려면 아래와 같이 작업한다.

2010버전에서는 Group 정렬이 따로 빠져나와있지 않아서 CommCtrl을 참조했다.


1. 정렬시 전달할 사용자 데이터 구조체를 선언

type

  PGroupSortData = ^TGroupSortData;

  TGroupSortData = record

    ListView: TListview;

    ColumnIndex: Integer;

    Ascend: Boolean;

  end;



2. 정렬시 호출할 콜백 함수 구현

function ListView_GroupSort(Group1_ID: Integer; Group2_ID: Integer; pvData: Pointer): Integer; stdcall;

var

  GroupSortData: PGroupSortData;

  Str1, Str2: string;

begin

  GroupSortData := PGroupSortData(pvData);


  Str1 := GroupSortData.ListView.Groups.Items[Group1_ID].Header;

  Str2 := GroupSortData.ListView.Groups.Items[Group2_ID].Header;


  Result := CompareText( Str1, Str2 );

  if not GroupSortData.Ascend then

    Result := -Result;

end;


그룹 헤더값을 비교해서 결과값을 리턴한다.



3. uses에 CommCtrl 선언



4. 정렬 실행

var

  GroupSortData: TGroupSortData;

begin

  GroupSortData.ListView := ListView1;

  GroupSortData.Ascend := FAscend;

  GroupSortData.ColumnIndex := Column.Index;


  ListView_SortGroups( ListView1.Handle, ListView_GroupSort, @GroupSortData );


  FAscend := not FAscend;
end; 


LVM_SORTGROUPS 메시지를 쏘는 것으로 CommCtrl.pas를 참조한다.

VC의 CommCtrl.h에 있는 매크로와 동일하다.


5. exe demo & source


지난해에 약간의 Java 코드를 델파이로 포팅하던 도중에 StringBuilder를 String에다가 더하는 코드로 바꾸느라 손꾸락이 아팠었다.
우연찮게 SysUtils 유닛을 살펴보다보니 TStringBuilder 클래스가 있네 -_-;

1. 추가
Append, AppendLine, AppendFormat으로 추가한다.
Append에는 다양한 타입을 쓸수 있으니 간편하다.

2.  삽입
Append와 마찬가지로 다양한 타입을 지원하는 Insert가 있다.

3. 삭제
전체를 지우는 Clear와 구간을 삭제할 수 있는 Remove가 있다.

4. 변경
Replace함수를 이용한다.

5. 최종 결과물
ToString을 쓰면 String으로 리턴해준다.


var

  Str: TStringBuilder;

begin

  Str := TStringBuilder.Create;


  try

    Str.Append( 100 );

    Str.Append( sLineBreak );

    Str.AppendLine( '가나다라' );


    ShowMessage( Str.ToString );

  finally

    Str.Free;

  end;


아... 신규 버전들 나오면 추가된 내용을 한번 쭉 살펴봐야 하거늘 ㅠ.ㅜ
괜한 삽질로 고생만 했네.


ps. 성능 이슈
String을 쓰는 것과 TStringBuilder를 쓰는 것에 대해 비교를 해놓은 링크가 있다.

http://blog.marcocantu.com/blog/not_so_fast_tstringbuilder.html

http://www.monien.net/blog/index.php/2008/10/delphi-2009-tstringbuilder
 


흠.. 그런데 성능 이슈라면 TMemoryStream을 쓰는게 제일 낫지 않나...

TStringBuilder는 쉽게 문자열을 구성할때 쓰는게 좋을듯 싶은데.

Delphi 2009 에서 Generic이 추가되면서 유용한 클래스들이 추가되었다.
특히나 특정 Key와 쌍을 이루는 값을 저장할때 TDictionary는 참 유용하게 쓰인다.
일반 데이터 형을 보관할 때는 상관이 없으나 Class를 보관할때는 메모리 해제에 주의해야 한다.

uses
  Generics.Collections;

type
  TTempKey = class
  end;

  TTempValue = class
  end;

1. TDictionary
procedure TForm2.Button1Click(Sender: TObject);
var
  Dic: TDictionary<Integer,TTempValue>;
  Key: Integer;
begin
  Dic := TDictionary<Integer,TTempValue>.Create;
  try
    Dic.Add( 1, TTempValue.Create );

    for Key in Dic.Keys do
      Dic.Items[Key].Free;
  finally
    Dic.Free;
  end;
end;

Key와 매칭되는 Value를 저장할 수 있다.
여기서는 Key의 형을 Integer, Value의 형은 TTempValue 클래스로 설정했다.

Dic만 해제하면 TDictonary의 Values에 저장된 TTempValue 인스턴스가 붕 떠서 Memory Leak이 발생한다.
반드시 Dic를 해제하기 전에 Dic의 아이템들을 해제해야 한다.


2. TObjectDictionary

예전부터 Contnrs 유닛에 있던 TObjectList와 비슷하다.
생성시에 OwnsObject를 True로 설정해서 자동으로 아이템의 메모리를 해제했던 기억이 있을 것이다.

(1) Key와 Value 가 모두 Class형인 경우

procedure TForm2.Button2Click(Sender: TObject);
var
  Dic: TObjectDictionary<TTempKey,TTempValue>;
begin
  Dic := TObjectDictionary<TTempKey,TTempValue>.Create( [doOwnsKeys, doOwnsValues] );

  try
    Dic.Add( TTempKey.Create, TTempValue.Create );
  finally
    Dic.Free;
  end;
end;

인스턴스 생성시에 생성자의 인자를 보면 TDictionaryOwnerships라는 집합형을 넘긴다.
TObjectList의 OwnsObject를 생각하면 된다.
Key와 Value가 모두 클래스 형이므로 둘다 소유하는 것으로 설정했다.


(2) Value만 Class형인 경우

procedure TForm2.Button3Click(Sender: TObject);
var
  Dic: TObjectDictionary<Integer,TTempValue>;
begin
  Dic := TObjectDictionary<Integer,TTempValue>.Create( [doOwnsValues] );

  try
    Dic.Add( 1, TTempValue.Create );
  finally
    Dic.Free;
  end;
end;

Value만 클래스 형인 경우 Key를 소유하라고 doOwnsKeys를 설정하면 익셉션이 발생한다.
익셉션 클래스는 EInvalidCast, 메시지는 Invalid class typecast.

문제점 발견

프로그램에 스킨을 입히거나 특정 용도가 있는 경우 Border를 bsNone으로 하여 상단 Caption을 숨길 때가 있다.
일반 폼이라면 정상동작하나 bsNone인 폼이라면 폼 최대화 기능 구현을 위해 WindowState를 wsMaximized로 세팅하면 작업표시줄까지 덮어버린다.
그래서 보통은 아래와 같이 작업표시줄을 제외한 작업영역을 구해서 그 크기만큼 폼의 크기를 세팅하고 한다.

// 폼을 최대화하고 Align을 설정하거나
begin
  Form.WindowState := wsMaximized;
  Form.Align := alClient;
end;

// 혹은 작업 영역을 구해서 폼의 크기를 맞춰준다.
var
  WorkRect: TRect;
begin
  Form.WindowState := wsMaximized;        

  SystemParametersInfo( SPI_GETWORKAREA, 0, @WorkRect, 0 );
  Form.SetBounds( WorkRect.Left, WorkRect.Top, WorkRect.Right - WorkRect.Left, WorkRect.Bottom - WorkRect.Top );
 end;


문제는 듀얼모니터인 경우이다.
프로그램을 두번째 스크린으로 옮기고 최대화를 실행하면 폼이 보이지 않게 되어버린다.
어디로 날라간 것일까 -_-;

폼이 다른 모니터에 있는 경우 위치를 제대로 얻어오지 못한다.


해결방법

Forms에 있는 Screen 전역 객체를 이용하여 폼이 위치한 모니터를 얻어내고 작업 영역을 구하면 된다.

1. 폼이 위치한 곳의 TMonitor 객체를 얻어내보자
var
  Monitor: TMonitor;
begin
  Monitor := Screen.MonitorFromWindow( Form.Handle );
end;

2. TMonitor에는 API를 래핑한 여러가지 프로퍼티가 존재한다.
작업 영역을 얻기 위해서는 TMonitor의 WorkareaRect를 이용하자.
작업 영역을 구해보자
var
  WorkRect: TRect;
begin
  WorkRect := Monitor.WorkareaRect;
end;


전체 코드

var
  Monitor: TMonitor;
  WorkRect: TRect;
begin
  Form.WindowState := wsMaximized;

  Monitor := Screen.MonitorFromWindow( Form.Handle );
  WorkRect := Monitor.WorkareaRect;
  Form.SetBounds( WorkRect.Left, WorkRect.Top, WorkRect.Right - WorkRect.Left, WorkRect.Bottom - WorkRect.Top );
end;


PS
더 자세한 사항은 Forms 유닛의 TScreen, TMonitor 클래스를 참조하기 바란다.
화면과 관련한 Windows API 사용법이 자세히 작업되어 있다.

맨날 TOpenDialog로 코딩할때 이런식으로 했었다.
혹시나 사용자가 이름을 막 넣을 수도 있기 때문에 파일이 있는지 꼭 확인하곤 했었다.


if OpenDialog1.Execute then
begin
  if FileExists(OpenDialog1.FileName) then
  begin
    // Todo...
  end;
end;

사람이 머리가 나쁘면 손발이 고생이고
프로그래머가 머리가 나쁘면 코딩량이 늘어난다. ㅠ.ㅜ

TOpenDialog의 Property를 보면 Options라는 집합 프로퍼티가 보인다.

 ofReadOnly
 Causes the Read Only check box to be selected initially when the dialog box is created. This flag indicates the state of the Read Only check box when the dialog box is closed.
 ofOverwritePrompt  덮어쓰기 할것인지 확인한다.
 Save as 다이얼로그에서 이용한다.
 ofHideReadOnly  읽기 전용 체크박스를 숨긴다.
 
 동작 확인 불가
 ofNoChangeDir  사용자가 다를 폴더를 탐색하더라도 current directory가 변경되지 않도록 원래의 값으로 복원한다.
 ofShowHelp  도움말 버튼이 보이도록 한다.
 ofNoValidate  파일명이 시스템에 유효한 파일명인지 검사하지 않는다.
 ofAllowMultiSelect  여러개의 파일을 선택할수 있도록 한다.
 Files 프로퍼티에 선택한 파일명이 넘어온다.
 ofExtensionDifferent Specifies that the user typed a file name extension that differs from the extension specified by lpstrDefExt. The function does not use this flag if lpstrDefExt is NULL.

무슨 말인지 모르겠다 -_-;
 ofPathMustExist  폴더가 반드시 존재해야 한다.
 ofFileMustExist  파일이 반드시 존재해야 한다.
 파일이 없는 경우는 경고 메시지가 출력된다.
 이 플래그가 활성화되면 ofPathMustExist도 활성화된다.
 ofCreatePrompt  사용자가 입력한 파일명이 존재하지 않는 경우 생성할 것인지 물어보는 다이얼로그를 띄운다.
 ofShareAware Specifies that if a call to the OpenFile function fails because of a network sharing violation, the error is ignored and the dialog box returns the selected file name. If this flag is not set, the dialog box notifies your hook procedure when a network sharing violation occurs for the file name specified by the user. If you set the OFN_EXPLORER flag, the dialog box sends the CDN_SHAREVIOLATION message to the hook procedure. If you do not set OFN_EXPLORER, the dialog box sends the SHAREVISTRING registered message to the hook procedure.
 ofNoReadOnlyReturn  파일을 포함하고 있는 폴더가 읽기전용이거나 읽기 전용 파일을 반환하지 않도록 한다.
 ofNoTestFileCreate  다이얼로그가 닫히기 전에 파일이 생성되지 않도록 한다.
 ofNoNetworkButton  Network 버튼을 숨긴다.
 ofNoLongNames  8.3의 옛날 스타일로 보이게 한다.
 ofOldStyleDialog가 설정되어 있어야 한다.
 ofOldStyleDialog  다이얼로그가 고전 스타일로 보이도록 한다.
 ofNoDereferenceLinks  바로가기 파일(.LNK)의 원래 경로를 받을 수 있도록 한다.
 ofEnableIncludeNotify  폴더를 열었을때 CDN_INCLUDEITEM을 보낼것인지 설정한다.
 (델파이에는 OFN_ENABLEHOOK을 설정하는 옵션이 없는데 어떻게 사용할수 있는지는 모르겠음.)
 ofEnableSizing  다이얼로그 사이즈 변경 가능하도록 한다.
 ofDontAddToRecent  선택한 파일이 CSIDL_RECENT 즉 최근 폴더에 추가되지 않도록 한다.
 ofForceShowHidden
 hidden, system 파일을 보여준다.
 주의 : 파일이 hidden과 system 속성을 모두 가지고 있다면 보이지 않는다.

주황색 부분은 무슨 내용인지 확인 불가.
뭐 크게 필요하지는 않은 옵션같다.
TOpenDialog는 기본으로 ofHideReadOnly, ofEnableIncludeNotify 옵션이 켜져있다.


MSDN에 관련 내용이 더 있으니 확인하면 된다.
http://msdn.microsoft.com/en-us/library/ms646839%28VS.85%29.aspx

CFileDialog 도움말
http://msdn.microsoft.com/en-us/library/43xtah3y.aspx

1. TArray 클래스는 ?

Generics.Collections 유닛의 맨 위에 TArray 클래스가 있다.

주의) TArray<T>형이 아니다 -_-;
TArray<T>는 System 유닛에 array of T라고 선언되어 있다.

TArray 클래스는 class function으로만 이루어져 있다.
Sort 함수와 BinarySearch 함수이다.


2. 초기화

함수의 테스트를 위해 배열을 선언하고 초기화해보자.

  private
    FMyArray: TArray<string>;

  SetLength( FMyArray, 5 );
  FMyArray[0] := 'ccc';
  FMyArray[1] := 'ddd';
  FMyArray[2] := 'aaa';
  FMyArray[3] := 'eee';
  FMyArray[4] := 'fff';


3. Sort

인자들의 비교를 위한 TComparer를 선언한다.
인자가 string이니 선언도 string으로 한다.

type
  TMyStringComparer = TComparer<string>;


1) 함수를 이용한 방법

TComparer의 Construct 클래스 메소드를 이용해서 IComparer인터페이스를 생성해서 넘겨준다.
어차피 인터페이스를 넘기는 것이므로 메모리 해제는 신경쓸 필요가 없다.

function MyStringComparison(const Left, Right: string): Integer;
begin
  Result := CompareStr(Left, Right);
end;

procedure TForm2.Button1Click(Sender: TObject);
begin
  TArray.Sort<string>( FMyArray, TMyStringComparer.Construct(MyStringComparison) );
end;


2) Anonymous Method를 이용한 방법

동일한 방법이나 Delphi 2009에서부터 추가된 Anonymous Method를 이용해서 정렬하는 방법이다.

자세한 설명은 Dr.Bob에서 보면 되겠다.

http://www.drbob42.com/examines/examinA5.htm

procedure TForm2.Button2Click(Sender: TObject);
begin
  TArray.Sort<string>( FMyArray, TMyStringComparer.Construct(
    function (const Left, Right: string): integer
    begin
      Result := CompareStr(Left, Right);
    end
  ) );
end;


4. BinarySearch

Sort와 동일하다.
FoundIndex 인자에 찾은 Index를 알려준다.

procedure TForm2.Button3Click(Sender: TObject);
var
  FoundIndex: Integer;
begin
  TArray.BinarySearch<string>( FMyArray, 'ccc', FoundIndex, TMyStringComparer.Construct(MyStringComparison) );

  ShowMessage( IntToStr(FoundIndex) );
end;


5. 결론

괜히 코드로 처리하느라 힘빼지 말고 배열의 정렬과 검색, 이제는 간단하게 처리하자.
대신 원리 파악을 위해 반드시 TArray 클래스의 소스를 보고 동작 원리는 파악하도록 한다.

먼저 private와 protected를 모르겠다면 OOP의 encapsulation에 관해 공부를... -_-a


Delphi의 Class는 멤버 접근에 독특한 특징이 있다.

private이나 protected로 지정이 되어있다고 하더라고 같은 유닛에 있는 클래스라면 접근이 가능하다.

사용할때는 편하지만 설계할때는 대략 좋지 않다.

  TClass1 = class
  private
    FField1: string;
    FField2: string;
  end;

  TClass2 = class
  public
    procedure Test;
  end;

procedure TClass2.Test;
var
  Class1: TClass1;
begin
  Class1 := TClass1.Create;

  ShowMessage( Class1.FField1 );
  ShowMessage( Class1.FField2 );

  Class1.Free;
end;


TClass1과 TClass2가 같은 Unit에 있다면 위의 소스는 컴파일이 성공한다.

문제는 두 Class가 서로 다른 Unit에 있다면 E2261 Cannot access private symbol 에러가 발생한다.

Class를 분리하거나 하면 웃기게 된다.


확실한 구분을 위해 private앞에 strict를 추가해보자.

  TClass1 = class
  strict private
    FField1: string;
    FField2: string;
  end;


컴파일해보면

E2261 Cannot access private symbol TClass1.FField1
E2261 Cannot access private symbol TClass1.FField2

에러가 발생한다.


명확한 OOP를 위해서 확실하게 strict 를 지정해주자.

추가된 문법은 팍팍 이용해 주자~
보통 Delphi에서는 외부 공개용 const 를 interface 아래에 놓고 처리한다.

그런데 가끔 보면 한 유닛안에 몽땅 다 때려넣어서 꽤나 복잡해지는 경우가 있다.

이런때는 const를 연관된 Class내에 넣어버리면 명확하게 구분하여 사용할 수 있다.


unit MainLib;

interface

const
  CLASS_NAME = 'TMainClass';
  ARRAY_COUNT = 6;

위와 같이 CLASS_NAME const 상수가 있을때 MainLib를 uses에 추가한다면 ctrl+space로 code insight를 불러내면 바로 보여서 어느 유닛에 속해 있는지 한눈에 보이지 않게 된다.

게다가 아래에 다른 클래스가 있다면 이 const 상수가 어느 클래스와 연관이 있는지 찾기가 힘들다.


const 상수를 클래스에 아래와 같이 넣어본다.

type
  TMainClass = class
  const
    CLASS_NAME = 'TMainClass';
    ARRAY_COUNT = 6;
  end;


값을 쓰고 싶을때는 클래스 생성하지 않고도 불러다 쓸수 있다.

  ShowMessage( TMainClass.CLASS_NAME );



ps.
상속받은 클래스의 선언부에서 부모의 const를 사용하려고 하면 ARRAY_COUNT 상수를 찾을수 없다고 컴파일이 되지 않는다.

type
  TSubClass = class(TMainClass)
  strict private
    FIntegerArray: array[0..ARRAY_COUNT - 1] of Integer;
  end;

TMainClass.ARRAY_COUNT 로 변경하면 된다.
TSubClass의 구현부에서는 그냥 불러다 써도 된다.
아래의 예제를 참고한다.

type
  TSubClass = class(TMainClass)
  strict private
    FIntegerArray: array[0..TMainClass.ARRAY_COUNT - 1] of Integer;
  public
    procedure PrintMessage;
  end;

implementation

procedure TSubClass.PrintMessage;
begin
  ShowMessage( IntToStr(ARRAY_COUNT) );
end;

이제 const도 관련 Class안으로 넣어버리자!!!!!


Delphi 2009부터 추가된 Generic이 있다.
간단하게 말하자면 C++의 Template라고 생각하면 되겠다.
이제 자바나 C++ 포팅할때도 충분히 쉽게 포팅할수 있게 되었다.
Generics.Collections를 보면 List 등의 클래스를 쉽게 포팅할수 있는 좋은 클래스가 많이 있다.

자바쪽 소스를 포팅하다보니 클래스에 ToArray라는 함수가 있는데 열어봐도 없길래 Generics.Collections 유닛을 열어보니 주석처리가 되어있다.
이유는 pending compiler support 라고 씌여있다.

흠... 없다면 상속해서 만들어주면 되겠군.
TObject를 담을 때 유용한 TObjectList를 상속받아서 하나 만들어보자.

type
  TMyObjectList<T: class> = class( TObjectList<T> )
  public
    function ToArray: TArray<T>;
    class procedure FreeArray( AMyArray: TArray<T> );
  end;

implementation

class procedure TMyObjectList<T>.FreeArray(AMyArray: TArray<T>);
var
  I: Integer;
begin
  for I := 0 to Length(AMyArray) - 1 do
    FreeAndNil( AMyArray[I] );

  SetLength( AMyArray, 0 );
end;

function TMyObjectList<T>.ToArray: TArray<T>;
var
  i: Integer;
begin
  SetLength(Result, Count);
  for i := 0 to Count - 1 do
    Result[i] := Items[i];
end;



type
  TMyItem = class
  public
    Data: string;
  end;

List에 담을 Item 클래스를 하나 선언하고 테스트 코드를 넣어봤다.

자동 해제 옵션을 켜고 사용할때
procedure TForm2.Button2Click(Sender: TObject);
var
  List: TMyObjectList<TMyItem>;
  Item: TMyItem;

  ItemArray: TArray<TMyItem>;
  I: Integer;
begin
  List := TMyObjectList<TMyItem>.Create;

  Item := TMyItem.Create;
  Item.Data := 'first item';
  List.Add(Item);

  Item := TMyItem.Create;
  Item.Data := 'second item';
  List.Add(Item);

  ItemArray := List.ToArray;

  for I := 0 to Length(ItemArray) - 1 do
    ShowMessage(ItemArray[I].Data);

  // 주의! 리스트가 해제될때 아이템도 같이 해제된다.
  // Free한 이후에 ItemArray는 사용이 불가하다.
  List.Free;
end;

자동 해제 옵션을 끄고 사용할때
procedure TForm2.Button1Click(Sender: TObject);
var
  List: TMyObjectList<TMyItem>;
  Item: TMyItem;

  ItemArray: TArray<TMyItem>;
  I: Integer;
begin
  List := TMyObjectList<TMyItem>.Create(False);

  Item := TMyItem.Create;
  Item.Data := 'first item';
  List.Add(Item);

  Item := TMyItem.Create;
  Item.Data := 'second item';
  List.Add(Item);

  ItemArray := List.ToArray;

  // 생성할 때 AOwnsObjects 옵션을 False로 설정했기 때문에 List를 해제하더라도 Item은 해제되지 않는다.
  List.Free;

  for I := 0 to Length(ItemArray) - 1 do
    ShowMessage(ItemArray[I].Data);

  // Array의 사용을 마치면 배열을 해제하여 Item을 해제한다.
  // 해제하기 전까지는 ItemArray를 계속 이용할 수 있다.
  TMyObjectList<TMyItem>.FreeArray( ItemArray );
end;


예전에는 TObjectList를 이용하더라도 Item을 형변환해서 사용하거나 상속받아서 새로 만들고 했었는데 Generic을 이용하니 너무나 편하다.
C++의 Template가 이제 가물가물하니 시간내서 Delphi와 비교 좀 해봐야겠다.


어디선가 받은(?) 라이브러리를 컴파일할 일이 있어서 간만에 C++ Builder를 만지게 되었다.
그런데 컴파일 도중 이상한 에러가 나왔다.
에러는 아래와 같다.

E2203 Goto bypasses initialization of a local variable


goto를 지나서 지역변수를 선언한게 있다는 말인데...
VC++에서는 잘 컴파일 되는 라이브러리란다.
흠... goto... goto...
VC++와 C++ Builder로 간단한 테스트 프로그램을 작성해보았다.


VC ++

int _tmain(int argc, _TCHAR* argv[])
{
    int a = 5;

    if( a == 0 )
        goto exit;

    int b = 5;

exit:
    return 0;
}

== 컴파일 결과 ==

1>------ Build started: Project: LocalVariable, Configuration: Debug Win32 ------
1>Compiling...
1>LocalVariable.cpp
1>Build log was saved at "LocalVariable\Debug\BuildLog.htm"
1>LocalVariable - 0 error(s), 0 warning(s)
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========


C++ Builder


int _tmain(int argc, _TCHAR* argv[])
{
    int a = 5;

    if( a == 0 )
        goto exit;

    int b = 5;

exit:
    return 0;
}

== 컴파일 결과 ==
[BCC32 Error] File1.cpp(15): E2203 Goto bypasses initialization of a local variable
[BCC32 Warning] File1.cpp(21): W8004 'b' is assigned a value that is never used
Failed


C++ Builder에서는 E2203 에러가 발생한다.

해결책은 간단하다.
지역변수 선언을 goto 위에서 하면 된다.

int _tmain(int argc, _TCHAR* argv[])
{
    int a = 5, b;

    if( a == 0 )
        goto exit;

    b = 5;

exit:
    return 0;
}

== 컴파일 결과 ==
[BCC32 Warning] File1.cpp(21): W8004 'b' is assigned a value that is never used
Success

b를 이용한 곳이 없다는 Warning이 나오고 컴파일은 성공한다.



goto를 return과 try finally로 변경하는 삽질을 하다가 집에 와서 느긋하게 테스트해봤는데 아무래도 내일 가서 모두 바꿔야 할듯 싶다 -_-;
아 삽질하기전에 테스트 좀 해보자.
머리가 나쁘면 손발이 고생이군.


그나저나 보기에는 VC++ 방식도 별 문제는 없어 보이는데 C++ Builder에서는 왜 에러로 처리할까나?
궁금해져서 구글링을 해보았으나 별다른 답변을 찾지 못했다.
내가 작업하는 소스면 goto를 안쓰던지 주의해서 쓰면 되는데 받아온 라이브러리이니 난감하기 짝이 없다.
뭔가 옵션이 있으려나...

엠바카데로의 docwiki에는 아래와 같은 짤막한 설명만 있군...
http://docwiki.embarcadero.com/RADStudio/en/E2203_Goto_bypasses_initialization_of_a_local_variable_%28C%2B%2B%29

우선은 컴파일이 급하니 위쪽에 선언하는 걸로 해결하고 옵션이나 다른 해결방법이 있는지는 더 조사해봐야겠다.

===============================================
20110919 수정사항 추가

    else if Str1[Pos1] = #0 then
    begin
      Result := 0;
      Exit;
    end

에셔 결과값 0을 -1로 변경해야함.

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


---------
a - 1
a - 10
a - 99
a - 9
a - 10000
---------
위와 같은 데이터가 있을때

델파이의 CompareStr로 비교하면
---------
a - 1
a - 10
a - 10000
a - 9
a - 99
---------
로 나옵니다.

윈도우탐색기에서 입력해보면
--------
a - 1
a - 9
a - 10
a - 99
a - 10000
--------
으로 정렬됩니다.


출처는 http://www.scottandmichelle.net/scott/code/index2.mv?codenum=100 입니다.
씨 소스로 되어있길래 포팅해봤습니다.

function NaturalOrderCompareString( const A1, A2: string; ACaseSensitive: Boolean ): Integer;
var
  Str1, Str2: PChar;
  Pos1, Pos2: Integer;
  EndPos1, EndPos2: Integer;
begin
  Str1 := PChar(A1);
  Str2 := PChar(A2);

  Pos1 := -1;
  Pos2 := -1;

  while True do
  begin
    Inc( Pos1 );
    Inc( Pos2 );

    if (Str1[Pos1] = #0) and (Str2[Pos2] = #0) then
    begin
      Result := 0;
      Exit;
    end
    else if Str1[Pos1] = #0 then
    begin
      Result := -1;
      Exit;
    end
    else if Str2[Pos2] = #0 then
    begin
      Result := 1;
      Exit;
    end;

    if (Str1[Pos1] >= '0') and (Str1[Pos1] <= '9') and
       (Str2[Pos2] >= '0') and (Str2[Pos2] <= '9') then
    begin
      EndPos1 := Pos1;
      repeat
        Inc(EndPos1);
      until not ((Str1[EndPos1] >= '0') and (Str1[EndPos1] <= '9'));

      EndPos2 := Pos2;
      repeat
        Inc(EndPos2);
      until not ((Str2[EndPos2] >= '0') and (Str2[EndPos2] <= '9'));

      while True do
      begin
        if EndPos1 - Pos1 = EndPos2 - Pos2 then
        begin
          // 이부분이 숫자비교임. StrToInt 한 다음에 빼도 될 것임
          Result := CompareStr( Copy(Str1, Pos1+1, EndPos1 - Pos1),  Copy(Str2, Pos2+1, EndPos1 - Pos1) ) ;

          if Result = 0 then
          begin
            Pos1 := EndPos1 - 1;
            Pos2 := EndPos2 - 1;
            Break;
          end
          else
          begin
            Exit;
          end;
        end
        else if EndPos1 - Pos1 > EndPos2 - Pos2 then
        begin
          if Str1[Pos1] = '0' then
            Inc(Pos1)
          else
          begin
            Result := 1;
            Exit;
          end;
        end
        else
        begin
          if Str2[Pos2] = '0' then
            Inc( Pos2 )
          else
          begin
            Result := -1;
            Exit;
          end;
        end;
      end;
    end
    else
    begin
      if ACaseSensitive then
        Result := CompareStr( Copy(Str1, Pos1, 1), Copy(Str2, Pos2, 1) )
      else
        Result := CompareText( Copy(Str1, Pos1, 1), Copy(Str2, Pos2, 1) );

      if Result <> 0 then
        Exit;
    end;
  end;
end;



리스트뷰 OnCompare에 델파이의 CompareStr, NaturalOrderCompareString 함수, Windows 유닛 내의 CompareString 함수로 정렬해서 비교한 간단한 예제가 있으니 비교해보시기 바랍니다.

정렬부분 소스는
procedure TForm3.ListView1Compare(Sender: TObject; Item1, Item2: TListItem;
  Data: Integer; var Compare: Integer);
var
  CompareResult: Integer;
begin
  case ComboBox1.ItemIndex of
    0:
    begin
      // Na
      Compare := NaturalOrderCompareString( Item1.Caption, Item2.Caption, True );
    end;
    1:
    begin
      // 일반 문자열 비교
      Compare := CompareStr( Item1.Caption, Item2.Caption );
    end;
    2:
    begin
      // Window API
      CompareResult := CompareString( LOCALE_USER_DEFAULT, 0, PChar(Item1.Caption), Length(Item1.Caption), PChar(Item2.Caption), Length(Item2.Caption) );

      case CompareResult of
        CSTR_LESS_THAN:     Compare := -1;
        CSTR_GREATER_THAN:  Compare := 1;
        CSTR_EQUAL:         Compare := 0;
      end;
    end;
  end;
end;


eclipse로 간단한 어플리케이션 작업하는데 단축키가 무지하게 답답했다. ㅠ.ㅜ
JBuilder가 Turbo 버전으로 무료버전이 있어서 설치해보기로 했다.
마지막으로 써본 JBuilder 버전이 7.0이었던가 -_-a
eclipse 기반으로 바꼈다고 들었는데 얼마나 바꼈을런지~

우선 홈페이지에 들어가서
 

엠바카데로 홈페이지에 가서 Product -> JBuilder 를 선택한다.
Download Trial을 선택해서 다운로드 항목으로 간다.
http://www.embarcadero.com/
http://www.embarcadero.com/products/jbuilder/




Linux, Mac OS X, Windows 플랫폼을 지원하니 원하는 항목을 다운로드한다.
http://downloads.embarcadero.com/free/jbuilder




DOWNLOAD를 누르면 다운이 시작되면서 페이지가 바뀐다.
계정이 없다면 설명에 맞춰 계정을 등록하고 로그인한다.
로그인이 성공하면 가입한 메일 계정으로 인증 코드를 받을 수 있다.




Serial Number가 발급되니 메일을 따로 보관한다.




다운로드받은 파일의 압축을 해제하고 install_windows.exe를 실행한다.
물론 윈도우에서이다. :)






원하는 경로등을 설정해서 설치한다.



설치가 완료되고 JBuilder를 실행했는데 이런 -_-;
에러가 난다.







설치된 폴더에 있는 JBuilder.ini 파일을 찾아서 메모장으로 연다.
eclipse에서 했던대로 -Xmx896m-Xmx256m 으로 수정했다.
이런건 똑같이 따라가지 말고 좀 고치란 말이닷! -_-+




수정하고 나니 잘 실행된다.




workspace를 지정해주고 실행한다.




처음 실행하면 라이센스 등록 화면이 나온다.
아까 메일로 받았던 Serial Number와 Login ID/Password를 입력하면 웹에 접속해 라이센스 체크한다.




정상적으로 라이센스 입력이 완료되면 간단한 설정 화면이 나타난다.
계속 진행한다.




모두 완료되어 실행한 JBuilder 메인 화면이다.




간단한 프로젝트를 생성해서 실행시켜 보았다.
잘돌아간다~!


근데 eclipse를 기반으로 만든건데 라이센스비를 준건가?
IDE 최강자였었는데 무료툴에 어쩔수 없이 밀렸나보다.
하기사 워낙에 비싼데다가 개발툴이 무겁기도 했으니...
잘 받아들인 것 같기도 하고 웬지 씁쓸하기도 하고...
찾아보니 Eclipse Ganymede 3.4.1을 기반으로 하고 있다고 한다.

JBuilder 2008 R2 Release Note
What's New in Eclipse Ganymede 3.4


볼랜드.. 아니 코드기어.. 아니 엠바카데로 -_-;;; Turbo 버전들 꽤나 쓸만했다.
JBuilder Turbo도 역시 좋아보이는군.
플러그인 설치되어있는 eclipse가 용량이 300mb정도인데 JBuilder는 1.2기가쯤 되는걸 보니 추가된게 많나 보다.
우선 간단한 테스트 어플하고 JBuilder 라이브러리들 테스트해봐야겠다.

'Programming > Java' 카테고리의 다른 글

Eclipse 업데이트시 오류 - Install Download0  (1) 2012.02.17
http://yypbd.com/mediawiki/index.php/JQuery

웹서핑을 하다가 jQuery란 라이브러리를 보게 됐다.

자바스크립트를 예술로 만들었더만 -_-;

벌써 꽤나 많은 사이트에서 이용하고 있단다.

사용해보니 잡다한 코드를 라이브러리화시켜버렸다.

문서한번 읽어봐야겠다.

테스트로 만든 페이지는 이거 -_-;

http://yypbd.com/jquery.php

설치

마소에서 Express 버전을 무료로 배포했다.
개인, 기업에서도 무료일 뿐만 아니라 상용어플리케이션 제작까지 허용되기 때문에 부담없이 쓸수 있다.
Microsoft Express 위 사이트에 가면 Visual Studio 2008와 SQL Server 2008 다운로드받을수 있다.
단 사용자 등록을 해서 라이센스키를 발급받아야한다.
어차피 MFC 프로그래밍은 거의 하지 않을 예정이고 컨솔 프로그램이나 dll 혹은 테스트용 어플리케이션을 만드는게 주된 사용처라 이용해보기로 했다.
간단한 Console나 Windows Application으로 테스트 해보니 잘 돌아간다.

배포문제 발생

문제는 여기부터 -_-;; LGPL 소스를 쓸일이 있어서 dll을 만들어서 돌렸다.
어차피 라이브러리에서 MFC쓰는 것이 없으니 Use Standard Windows Libraries를 선택하고 모듈을 릴리즈 모드로 컴파일했다.
그런데 아무것도 깔리지 않은 PC에서 실행해보니 오류가 발생한다. 기존 VC6같은 경우에는 특정 MFC DLL이 없다고 오류를 내뱉어 줬었는데 이번에는 아무 에러 메시지가 없이 DLL이 LoadLibrary시에 실패해 버린다.
GetLastError로 확인해보니 오류 코드는 126(ERROR_MOD_NOT_FOUND). The specified module could not be found. 란다.
기존의 VC 모듈 배포시에 MFC DLL을 같이 배포했던 기억이 나서 디펜던시 워커 ( http://www.dependencywalker.com ) 로 결과물 DLL을 확인해보았다.
MSVCR90.DLL, MSVCP90.DLL이 필요하단다.
이 DLL에서도 MSCOREE.DLL이 필요하다고 해서 같이 넣고 돌려보았으나 역시나 실행시 LoadLibrary 실패.
Debug, Release 모드 변경 및 라이브러리 옵션을 변경해가면서 수많은 삽질 시작함... ㅠ.ㅜ

해결

혹시나 해서 모듈을 깔아주어야 하는게 있나 하고 검색해보니 재배포 패키지 다운로드 이런 재배포 패키지가 있다. -_-;
이 패키지를 깔아도 Debug 모드로 컴파일한 파일은 실행할 수 없다.
정식 버전도 그런지 모르지만 static 모드로 모듈을 포함할 수도 없다.
결국 LGPL 소스 및 작업하던 DLL 모두를 Release 모드, Use Standard Windows Libraries 로 컴파일하고 재배포 패키지 설치하니 동작했다.

결론

1. 배포 파일은 무조건 Release모드로 컴파일할것. Debug모드에서는 에러가 안나는데 Release 모드에서 에러난다면 99% 이상 프로그래머 잘못임. 2. 디버그 모드와 릴리즈 모드의 Output 저장 폴더를 반드시 구분할 것
3. 디버깅한다고 모듈의 Output 경로를 함부로 변경하지 않는다.
사용할 곳에서 .lib를 추가하고 dll만 복사해서 사용한다.
4. 다른 라이브러리(.lib)를 include시 디버그, 릴리즈 모드의 경로 설정을 정확하게 설정할 것.

+ Recent posts