레이블이 프로그래밍인 게시물을 표시합니다. 모든 게시물 표시
레이블이 프로그래밍인 게시물을 표시합니다. 모든 게시물 표시

WPF Relative Image Path Problem

While I was diving into developing my application, ThePen, I encountered a significant issue with loading images using relative paths in both XAML and code-behind. I suggest a solution for this issue. I cannot guarantee that it will work for you, but I hope it helps. The processes that I couldn't easily resolve, and that you might easily disregard, will be highlighted in red.


In case of XAML, if you want to show static images (not changed in runtime).

1. Place your image on your project directory. The image should truly exist, and also add your project. You can make folder and place them in there.

2. Normally add your image element like:

<Image Source="/Images/pen.png"/>


3. Double click the project name in Solution Explorer, you will see a xml document for project settiong . add following in the middle of the xml:

<ItemGroup>

<Resource Include="Images/pen.png" />

                   .... (add more images as you want)

</ItemGroup>

I should add all images like this and it worked fine.


In case of code behind, if you want to show dynamic images (changed in runtime).

1. Place your image next to ***.exe file. It may be placed in

SolutionDir/PorjectDir/bin/Debug/TargetFramework/***.exe


2. Your code should be like:

var bitmapImage = new BitmapImage();

bitmapImage.BeginInit();

bitmapImage.UriSource = new Uri("pen.png", UriKind.RelativeOrAbsolute);

bitmapImage.CacheOption = BitmapCacheOption.OnLoad;

bitmapImage.EndInit();

MyImage.Source = bitmapImage;



I spent hours to find these solution...... ah.

Read More

WPF .Net 5 이미지 보이지 않을 때 (리소스 추가)

닷넷 프레임워크에서는 프로젝트에 이미지를 포함시키면 자동으로 리소스에 추가가 됐다. 그런데 닷넷 5로 넘어오면서 이미지 리소스를 직접 추가해줘야 한다.



<ItemGroup>

<Resource Include="Images/hat.png" />

<Resource Include="Images/arrow.png" />

<Resource Include="Images/pen.png" />

<Resource Include="Images/eraser.png" />

<Resource Include="Images/clear.png" />

<Resource Include="Images/overlay.png" />

<Resource Include="Images/setting.png" />

<Resource Include="Images/exit.png" />

<Resource Include="Images/info.png" />

</ItemGroup>


솔루션 익스플로러에서 프로젝트를 더블클릭하면 xml 문서가 보인다.

여기에 위와 같이 추가하면 된다.


그래도 안 보이면 솔루션을 다시 빌드한다. 상단메뉴 [빌드] -> [솔루션 정리] 후 다시 실행.



그리고 코드 비하인드에서 동적으로 상대경로 이미지가 나타나지 않을때, https://ladofa.blogspot.com/2024/03/wpf-relative-image-path-problem.html 여기 참조.



Read More

파이썬에서 부울이 아닌 값에 대한 논리연산자

C먼저 확인해보면

C언어에서는 bool 타입이 따로 존재하지 않는다. 0이면 false, 그렇지 않으면 true이다. 심지어는 float도 피연산자로 참여할 수 있다. 그래도 어쨌든 논리연산자의 결과값은 0 아니면 1이 되는 1byte 정수형을 리턴한다.

예를 들어

int a = 10;

float b = 3.14;

printf("%d, %d\n", a && b, sizeof(a && b));

위의 코드에서 출력은 1, 1 이다.


파이썬의 and, or

그런데 파이썬은 조금 더 변태같다.

a = 10

b = 20

print(a and b, a or b)

결과는 20, 10이다. 어째써? and와 or는 다음과 같이 구현되어 있다.

a and b => if a is true, return b, else return a
a or b => if a is true, return a, else return b

and의 경우 a가 참이면 b를 리턴한다. 결과적으로 a and b가 참인지 거짓인지는 b에 달린 상황이다. 만약 a가 거짓이면 그냥 a를 리턴한다. a가 거짓인 줄 알고 리턴하니까 a and b도 거짓이 된다. 이 때 b는 체크하지 않는다.

or의 경우 a 가 참이면 그냥 a를 리턴한다. a가 참이니까 a or b 도 참이다. 이 때 b는 체크하지 않는다. 만약 a가 거짓이면 b를 리턴한다. a or b가 참인지 거짓인지는 b에 달렸다.

한 번 더 정리하자면 a and b 에서 a 가 거짓이면 b를 체크하지 않고 a를 리턴한다. a or b에서 a 가 참이면 b를 체크하지 않고 a를 리턴한다. a and b에서 a가 참이면 b를 리턴하고, a or b에서는 a가 거짓이면 b를 리턴한다.


C언어의 논리연산자는 정수형 입력을 받아서 정수형 출력을 한다. 파이썬의 논리연산자는 입력 타입이 정해져 있지 않다. 그래서 입력 타입이 부울이 아닌 경우 엉뚱한 결과가 발생한다.


bool이 아닌 값들의 참, 거짓

뭐 당연하겠지만 또 웃기는 것은 True의 타입은 <class 'bool'>인데 산술연산에 참여할 때는 정수 1처럼 여겨진다는 것이다.

print(True + True)

결과는 2이다.


또 한 가지 팁이라면 파이썬에서는 None을 거짓으로 취급한다. 반대로 리스트나 기타 오브젝트는 참으로 취급한다. 아래 예제에서

a = [1, 2, 3]

print(a or None)

print(None and True)

결과는 [1, 2, 3], 그리고 None이다.


그런데 비어있는 리스트, 문자열, 딕셔너리, 셋, 튜플 따위는 거짓으로 취급한다.

print( [] or dict() or set() or tuple() or '' or 10 )

결과는 10이다. 10 앞에 나열한 모든 것들은 전부 거짓으로 취급된다.



bool로 변환해야 True가 된다

여기 또 다른 함정이 있다. 예를 들어 10은 bool로 형변환하면 True가 되긴 하지만 그렇다고 1이 True인 것은 아니다.

a = 10

print(a is True)

print(a == True)

결과는 둘 다 False이다. a는 정수 10일 뿐, bool도 아니고 True도 아니다. 


if문을 쓰지 않고 a가 참인지 체크하려면 간단히 bool로 형변환해보면 된다.

print(bool(a))

결과는 당연히 True이다.


마지막으로 아래 식을 확인해보자.

print(type(a and True))

print(type(True and a))

결과는 <class 'bool'>, <class 'int'> 이다. and 연산의 리턴 타입을 보면서 파이썬의 함수는 리턴 타입이 정해져있지 않다는 사실이 새삼 현실로 다가온다.


and, or 응용하기

파이썬의 이러한 특성을 이용하면 변태같은 코딩이 가능하다.


if a < b : func() #이 문장은

a < b and func() #요렇게 바꿀 수 있다.


if not (a < b) : func()

a < b or func()


조건문 대신 and와 or를 쓸 수 있다. 영 and와 or가 어색하다면 and 대신 then, or 대신 otherwise로 읽어보면 조금 더 자연스럽다. 어셈블리어를 해봤으면 매우 자연스러울 수 있다, 점프처럼 생각하면 되니까.

여기에 보태서 None이 거짓으로 인식되는 점을 이용해 None 체크를 할 수 있다.


if a is not None: b = a

else: b = somethingDefault #이 두 문장은

b = a or somethingDefault #요 한 문장으로 바꿀 수 있다.


a를 b에 대입하는데 만약 None이면 미리 정해준 기본값을 대입하도록 한 것이다. 같은 원리로 비어있는 리스트를 체크할 수도 있다.


myList = []

x = min(myList or [-1])

x = min(myList) #에러 발생


만약 myList == [] 즉 비어있는 리스트라면 에러가 발생한다. 이걸 try 로 잡아서 어쩌구 해도 되지만 어차피 비어있을 경우 x에 기본값 -1을 넣을 작정이라면 위와 같이 or를 이용할 수 있다.


결론

당연히 논리연산자의 피연산자는 bool로 묵시적 형변환이 일어나는 줄 알았고, 당연히 출력은 무조건 bool인줄 알았는데 입력 타입을 그대로 살려서 출력한다니 좀 어이없긴 하다. 파이썬의 철학이 단 하나의 목적을 위한 단 하나의 아름다운 코드 아닌가? 이런 변태같은 부분은 파이썬의 기본 철학을 정면으로 부정하는 것이다. 통일성과 간편함을 동시에 추구하는 것이 그렇게 쉬운 일은 아닌 셈이다.

파이썬의 이런 점이 마음에 들지 않는다, 혹은 헷갈려서 공부 못하겠다 싶으면 정수나 기타 이상한 타입은 일절 논리 연산에 참여시키지 말아야 한다. 반드시 bool(a) 또는 a != 0 과 같은 코드를 써서 명시적으로 bool로 변환해주는 것이 좋다. C를 제외한 대부분의 언어, 그러니까 Java와 C#에서는 당연히 이렇게 한다. 논리연산에는 bool만 참여할 수 있기 때문에 반드시 명시적 형변환이 필요하다. 차라리 이게 나은 것 같은데.

Read More

Unity VideoPlayer로 한 프레임씩 동영상 불러오기

Unity에서 동영상을 불러오려면 보통 VideoPlayer를 쓰거나 OpenCV의 VideoCapture를 많이 쓰게 된다. 여기서는 https://forum.unity.com/threads/how-to-extract-frames-from-a-video.853687/ 여기 링크를 참고하여 VideoPlayer를 사용하는 방법을 설명한다.


VideoPlayer는 unity 컴포넌트라서 new VideoPlayer() 식으로 할당하면 안 되고 씬에서 생성한 다음에 넣어줘야 하는데 스크립트에서만 동작시키고 싶으면 Start()와 같은 곳에서 다음과 같이 쓰면 된다.


VideoPlayer videoPlayer = gameObject.AddComponent(typeof(VideoPlayer)) as VideoPlayer;
videoPlayer.Stop();
videoPlayer.renderMode = VideoRenderMode.APIOnly;
videoPlayer.prepareCompleted += Prepared;
videoPlayer.sendFrameReadyEvents = true;
videoPlayer.frameReady += FrameReady;


gameObject가 필요하니까 MonoBehavior 클래스를 상속받아야 한다. 어차피 유니티 모듈이니까 아예 유니티와 상관없이 동작은 안 되는 것이다. 위의 코드는 그냥 예제라서 로컬로 선언했지만 멤버로 선언하는게 맞으르 것이다.

받아오자마자 Stop()부터 하고 APIOnly 모드로 설정한다. 이는 화면에 직접 플레이하는 일은 안 하고 내부적으로만 동작하겠다는 것이다.

preparedCompleted 는 동영상의 헤더를 다 읽어와서 준비가 되면 호출된다. frameReady는 프레임 한 장을 grab 하는 일이 완료되면 불러와진다. 매 프레임마다 불러와지는 것이다.


이제 아래와 같이 주소값을 넣어주고 Prepare를 호출한다.


videoPlayer.url = path;
videoPlayer.Prepare();


여기서 path를 잘 넣어줘야 한다. 아래와 같은 구문을 참고해보자.


string outputPath = System.IO.Path.Combine(Application.streamingAssetsPath, "result.avi");


준비가 완료되었을 때 호출될 함수는 다음과 같다.


void Prepared(VideoPlayer vp)
{        
    vp.Pause();
}


이렇게 포즈를 걸면 0번째 프레임을 불러온다.


void FrameReady(VideoPlayer vp, long frameIndex)
{
    var textureToCopy = vp.texture;

    //initialize
    if (frameIndex == 0)
    {
        images = new List<Mat>();
        texture = new Texture2D((int)vp.width, (int)vp.height, TextureFormat.RGB24, false);
        Debug.Log(vp.width + "" + vp.height);
        Debug.Log(textureToCopy.width + "" + textureToCopy.height);
    }

    if (saveImage)
    {
        Mat mat = new Mat((int)vp.height, (int)vp.width, OpenCVForUnity.CoreModule.CvType.CV_8UC3);
        OpenCVForUnity.UnityUtils.Utils.textureToTexture2D(textureToCopy, texture);
        OpenCVForUnity.UnityUtils.Utils.texture2DToMat(texture, mat);
        images.Add(mat);
    }        

    processRate = frameIndex / (double)videoPlayer.frameCount;
    ProcessRateChanged.Invoke();
    if (frameIndex + 1 == (long)vp.frameCount)
    {
        Debug.Log("INVOKE!!");
        ReadCompleted.Invoke();
    }

    if (frameIndex < frameLimit || (ulong)frameIndex < vp.frameCount - 1 )
    {
        vp.frame = frameIndex + 1;
    }
    else
    {
        ReadCompleted.Invoke();
    }
}


중간에 frameIndex == 0 블럭이랑 saveImage 블럭은 내가 OpenCVForUnity를 활용하기 위해 작성한 것이니 신경쓰지 말자. 하여튼 중요한 것은 textureToCopy가 들어왔다는 것이고, 이것을 이용하여 본인 원하는 작업을 하면 된다.

맨 마지막에 frameLimit는 내가 직접 선언한 것이니 신경쓰지 말고, 중요한 것은 vp.frame에 그 다음으로 읽고 싶은 프레임 번호를 넣는 것이다. vp.frame은 단순한 멤버 변수가 아니고 property이기 때문에 값을 집어넣는 순간 다음 프레임을 읽어오는 동작을 수행한다.

ReadComplete도 내가 직접 만든 것이다. 하여튼 여기에서 다 읽어왔을 때의 동작을 수행하면 된다.


이 방법의 문제점은 Prepare에 실패했을 때 아무런 피드백이 없다는 것이다. Prepare()를 호출하고 얼마간 지났는데도 Prepared 이벤트 함수로 들어오지 않는다면 뭔가 조치를 취할 수 있도록 해야 한다. 우선 아래와 같은 무식한 방법이 있긴 하다.


int count = 0;
while (!videoPlayer.isPrepared && count++ < 100)
{
    Debug.Log("Preparing Video");
    yield return null;
}
Debug.Log("Done Preparing Video");


100 frame이니까 약 3초 정도 기다려보는 것이다. while을 빠져나오고 나면 isPrepared를 다시 검사해서 준비가 되서 빠져나온 건지 아니면 count가 다 되서 빠져나온 건지 확인해보면 된다.

또한 일부 Unity 플러그인에서 레코딩한 파일은 몇 프레임 읽다가 실패한다. 상당수의 비디오 레코더들이 표준 포맷을 지키지 않고 파일을 쓴다. 여타 다른 비디오 플레이어들은 어떻게든 잘못된 포맷 형태를 무시하고 동영상을 플레이해주는데, VideoPlayer는 그들만큼 정성스럽지가 못하다.

유니티에서 공식적으로 지원한다고 밝힌 확장자는 아래와 같다.

https://docs.unity3d.com/Manual/VideoSources-FileCompatibility.html

그런데 여기에 함정이 있다. mp4의 경우, h.264는 잘 불러와지는데 h.265는 따로 코덱을 깔아야 한다. 나는 대충 깔아서 해봤는데 잘 안 되고 신통치 않다;; 같은 avi, mp4 확장자라도 코덱은 천차만별이라 신경 쓰이는 게 한두가지가 아니다. 게다가 윈도우가 아닌 안드로이드로 넘어가면 골치아픔은 두 배가 된다.


Read More

FLOPS 뜻, 그리고 FLOPs와의 차이

 내가 예전에 헷갈려서 Stack Overflow에 문의한 글이 있다.


https://stackoverflow.com/questions/58498651/what-is-flops-in-field-of-deep-learning/62028935#62028935


내용인 즉, FLOPS는 FLoating points OPeration per Second 의 약자인데 이는 컴퓨팅 파워를 나타내는 말이다. 1초에 얼마나 많은 연산을 수행할 수 있는 컴퓨터인가, 그런 뜻이다. 그런데 이 용어가 학습을 마친 어떤 Frozen Network가 얼마나 많은 연산을 요구하는가를 나타내는 용도로 쓰이는 것에 의문을 품고 문의한 것이다. 이런 경우에는 FLO라고 해야 되는 거 아니냐? 그런 뜻인데..

결론적으로 FLOPS랑 FLOPs는 다르단다. 나참 ㅋㅋ

  • FLOPS = Floating point operations per second - 시간당 연산량(GPU 성능 측정)

  • FLOPs = Floating point operations - 총 연산량 (모델의 크기 측정)

그런 거였구만. 무슨 용어를 이렇게 헷갈리게 쓰냐.



Read More

우분투에서 C++ 개발하기 (2) - Make

우분투에서 C++ 개발하기 (1) : https://ladofa.blogspot.com/2018/07/c-1.html


 1탄을 만들어 놓고 몇 년이 흘렀는가 모르겠다. 2탄을 만들게 될 줄도 몰랐다.

하여튼 이어서 makefile에 대해서 알아본다.

C나 C++을 사용하다 보면 수도 없이 컴파일 하고 빌드하고 이런 일이 반복되는데 매번 라이브러리 경로와 관련된 라이브러리 파일과 기타 등등등을 입력하는 것도 번거롭고 새로 수정한 파일이 무엇인지 따라다니면서 컴파일하는 것도 힘들다. 그래서 리눅스에서는 Makefile이란 툴을 쓴다. 윈도우에서 Visual Studio로 개발하면 이런 거 필요없는데.

Makefile은 빌드에 필요한 스크립트를 텍스트 형식으로 저장한 파일이다. 이 파일은 반드시 이름이 Makefile 이어야 한다. 확장자 없이 이름만 Makefile 이면 된다. 무슨 파일 이름이 Makefile이냐. ...

Makefile 속에 있는 스크립트를 해석해서 실행하는 프로그램은 make이다. 프로그램 이름이 make 다. make를 실행하면 실행한 현재 경로에서 Makefile을 찾아내고 요걸 해석해서 빌드를 수행한다.


우선 다음과 같은 예제를 생각해보자.

<my.h>

int my_func(int x);


<my.cpp>

#include "my.h"

int my_func(int x)

{

    return x * x;

}


<main.cpp>

#include <iostream>

#include "my.h"

int main(void)

{

    printf("%d^2 == %d\n", 3, my_func(3));

}


뭐 이렇게 간단히 구성되어 있다고 하자. 요걸 컴파일 하려면 1탄에서 배운 대로 다음과 같이 입력해야 할 것이다.

$ g++ -c my.cpp

$ g++ -c main.cpp

$ g++ -o test main.o my.o


매번 이렇게 하기 귀찮으니까 이제 Makefile을 이용할 차례이다. Makefile 은 타깃의 집합이다. 타깃은 다음과 같이 서술해야 한다.

[타깃이름]: [타깃에 필요한 파일들]

      [타깃 실행 코드]

여기서 타깃 이름은 그냥 아무 이름이 될 수도 있고, 파일명이 될 수도 있다. 실행 코드를 통해 결과 파일 하나가 확실히 나오는 경우에는 타깃 이름을 파일명으로 한다. 예를 들어

<Makefile>

my.o: my.h my.cpp

    g++ -c my.cpp

이와 같이 Makefile을 작성할 수 있다. 달랑 두 줄이다. 여기서 중요한 것은 두 번째 줄 앞에 있는 공간이 탭 하나이다. 지금 블로그를 작성할 때는 어쩔 수 없이 스페이스를 때려 넣었지만 실제로는 반드시 1탭이어야 한다.

이제 아래와 같이 실행한다.

$ make my.o

그 결과로 my.o 파일이 생성된다. 만약 이미 생성되었다면 up to date. 메시지가 뜰 것이다.


하나의 타깃은 다음 타깃의 재료가 될 수 있다. my.o 그리고 main.o 는 링커에서 실행 파일을 만드는 재료가 된다. 혹은 재료가 필요 없는 명령도 있다. 다음 예를 보자.

<Makefile>

my.o: my.h my.cpp

    g++ -c my.cpp


main.o: my.h main.cpp

    g++ -c main.cpp


test: my.o main.o

    g++ -o test main.o my.o


all: test


clear:

    rm -f my.o main.o test


여기서 my.o 와 main.o 는 test의 재료가 된다. 만약 make test를 했는데 my.o가 존재하지 않는다면 해당 타깃부터 만들고 난 뒤에 test를 만들게 된다. 한 번 더 make test를 실행하면 기존에 모든 것이 이미 있으므로 더 이상 작업을 진행하지 않는다. 파일을 수정하고 나면 수정된 파일을 재료로 하는 타깃만 새로 빌드될 것이다.

all 타깃은 재미있게도 실행 명령이 없고 타깃만 명시되어 있다. make all 을 실행하게 되면 그 재료가 되는 test를 만들 것이다. 만들고 나서 특별히 할 일은 없다.

clear는 반대로 타깃 파일이 없다. 보통은 타깃 파일이 존재해야 그 다음으로 명령문을 수행하게 될테지만 요구사항이 되는 타깃이 없으므로 그냥 무조건 명령을 수행하게 된다.

all, clear, install 이 세 가지 타깃은 모든 Makefile에서 관용적으로 쓰이는 것들이다. 모르는 설치파일이라도 우선 make install 부터 실행해보면 된다.


여기까지 이해했으면 이제 위키피디아에서 제공하는 샘플 Makefile을 살펴보자.

맨 윗줄에 있는 것들은 환경변수이다. 환경 변수의 이름 역시 암묵적으로 정해져 있다. 왤케 암묵적인게 많냐... $(OBJ)을 보면 타깃 여러 개를 한 번에 지정하고 있다. 그리고 이상한 기호들이 보이는데 $@는 타깃 이름, $^는 재료 이름, $<는 재료 중 맨 첫번째 항목을 의미한다. 

대충 이 정도면 원리는 이해한 셈이고, 그 밖에 엄청난 규칙들이 많다. 심지어는 요즘도 버전업이 되고 있다. 나머지는  http://doc.kldp.org/KoreanDoc/html/GNU-Make/GNU-Make-4.html 이런 곳에서 자세히 살펴보길 바란다.


요즘은 CMake나 다른 툴을 이용하고 Makefile은 거의 안 쓰기는 하지만 그래도 기본 원리 정도는 어렵지 않으므로 알고 있는 것이 좋다. 임베디드나 기타 작은 소프트웨어에서는 여전히 직접 작성해서 쓰기도 한다.


다음으로는 CMake를 사용한 컴파일 과정을 간단히 살펴볼 것이다.


우분투에서 C++ 개발하기 (3) - CMake
Read More

알아두면 좋은 Visual Studio Code 단축키 (윈도우 기준)

그냥 내가 정리하는 차원에서 하나씩 적어본다.

시시때때로 수정할 예정.

상당수는 Visual Studio 와 겹치는 듯.


터미널(디버그) 창 닫고 편집창으로 바로 커서 이동

    CTRL + ` (1 왼쪽에 있는 키)


스플릿 간 커서 이동

    CTRL + 숫자키


스플릿 내부 탭 파일 간 이동

    ALT + 숫자키

    이건 리눅스 터미널 띄워놓고 이동할 때도 같은 단축키

    CTRL + TAB 도 당연히 된다


같은 파일을 동시에 다른 스플릿에서 보기

    CTRL + 파일 탭(파일명 적힌 윗부분) 드래그


현재 커서의 함수구현/변수선언으로 이동

    F12


이전 커서 위치로 돌아가기

    ALT + 왼쪽 화살표

    F12 눌렀다가 되돌아갈 때 유용함


세로로 멀티 커서 만들기

    CTRL + ALT + 위아래 화살표

    이게 리눅스에서는 ALT + SHIFT 던가..


여러 라인 선택

    ALT + 마우스 클릭


현재 라인 잘라내기 (클립보드로 복사됨)

    SHIFT + DEL

    이건 왠만한 에디터 공통


인덴테이션 등 포맷 자동 맞추기

    CTRL + K, F


현재 라인, 혹은 블럭 씌운 라인 주석

    CTRL + K, C


주석

    CTRL + K, C (생성)

    CTRL + K, U (해제)

    CTRL + / (토글)


왼쪽 Explorer 칼럼에서 현재 위치 탐색기 띄우기

    ALT + SHIFT + R


왼쪽 Explorer 칼럼에서 파일명 고치기

    F2

    이건 데비안 리눅스 공통인 듯


Read More

SSH를 윈도우 탐색기 드라이브로 매핑하기

 ssh를 이용하면 쉘 스크립트를 실행할 수 있을 뿐 아니라 파일도 주고받을 수 있다. 윈도우 사용자들은 대부분의 파일 작업을 탐색기로 하지 뭐 명령어 치고 파일 주소 타이핑하고 그런 거 없다.


일단 기본적으로 SSH 연결이 잘 되는지부터 확인해본다.

cmd 창에서

ssh my_id@ip_address

와 같이 타이핑해서 접속되는지 확인해보자. 비밀번호 방식이든 sshkey 방식이든 상관없는데, 보통은 편리성을 위해 key 방식을 활용한다. 하여튼 접속이 잘 된다고 가정하고 다음 단계로 넘어간다.


여기서 조금 번거롭지만 세 개의 프로그램을 설치해야 한다.

https://github.com/billziss-gh/winfsp

https://github.com/billziss-gh/sshfs-win

여기까지가 sshfs를 윈도우에서 작동시키도독 해주는 프로그램이다.

sshfs 드라이브 목록을 더 쉽게 관리하기 위한 툴까지 설치한다. 요걸 맨 마지막에 설치해야 경로 잡고 어쩌고 하는 귀찮은 일이 없다.

https://www.electronjs.org/apps/sshfs-win-manager



실행해보면 2020년 8월 현재 버전으로 요렇게 생겼다.



Add Connection을 클릭하면 이런 창이 뜬다.



순서대로 어쩌구 저쩌구 입력 다 하고 연결 버튼을 누르면 나처럼 초록색으로 바뀐다.

이제 탐색기로 들어가면 드라이브가 생겨 있다.




Read More

Visual Studio Code : The Collection of Peculiar and Beautiful Themes

Visual Studio Code has so many many themes. I spent few(not few) hours to look up ALL themes exists in the market place. Almost themes were minor copy of Monokai or so colorful. That's very easy way to seems beautiful. I'm going to introduce somethings that is out of common grammar. Something is sensible or even beautiful.

Core
https://marketplace.visualstudio.com/items?itemName=miqh.core-theme
If you are tired of colorful themes, it may be a good choice. there is nothing saturation but just black or white or some grays.





White
https://marketplace.visualstudio.com/items?itemName=arthurwhite.white
If you want more contrast and no gray, select this theme. There is a little gray color but just black and white in this theme. And VSCode has more gray themes but I selected Core and White as the best.





Subway
https://marketplace.visualstudio.com/items?itemName=idleberg.subway
https://marketplace.visualstudio.com/items?itemName=idleberg.subway-dark
This theme's color is based on subway map. It has color sets of 16 cities. Very simple and fresh idea but the result is not fresh leaving much to be desired.





Starry Night
https://marketplace.visualstudio.com/items?itemName=jerry.theme-dark-starry-night
It described the night sky and stars. The black background is not pure-black. I think it is a reflection of our too bright night sky that is not good to see stars. So we should have some problem to see very dimmed comments.




HotDogStand
https://marketplace.visualstudio.com/items?itemName=gerane.Theme-HotDogStand
McDonald.




skGRTT
https://marketplace.visualstudio.com/items?itemName=aud-xie.skGRTT
Is red greatest color? I don't know. But it seems good.





Tireless
https://marketplace.visualstudio.com/items?itemName=gerane.Theme-Tireless
If you want to tune your monitor darker, and if you don't want to do, select it.




VSC Military Style
https://marketplace.visualstudio.com/items?itemName=TechnoNinja.vsc-military-style
Military color set is commonly used for closet, gaming gear, or other things. But I never seen before military skin for programming code. It's not bad.





Ice Kiss & Deep Jungle Theme
https://marketplace.visualstudio.com/items?itemName=stelo.icykiss
So beautiful but not too colorful. Deep Jungle is the best green theme.




Darktooth
https://marketplace.visualstudio.com/items?itemName=Poorchop.theme-darktooth
So beautiful but not too colorful.




Grueling Hare
https://marketplace.visualstudio.com/items?itemName=pureux.grueling-hare
So beautiful but not too colorful. And I will omit same comments.





A Touch of Lilac
https://marketplace.visualstudio.com/items?itemName=alexnho.a-touch-of-lilac-theme





Roseate
https://marketplace.visualstudio.com/items?itemName=endorfina.roseate





Shaizei Light
https://marketplace.visualstudio.com/items?itemName=shahzaibkhalid.shaizei-light





Read More

예술가와 기술자 - 당신은 작품을 만드는가

당신이 만든 그 결과물은 당신의 작품인가?

나는 지금 비록 프로그래머이지만 한 때는 작곡가를 꿈꾸던 음악인이었고, 지금도 다양한 예술 활동을 펼치고 있는 중이다. 나는 엔지니어 이전에 아티스트였고, 그러한 성향이 평소에든 일을 할 때든 많이 드러나곤 했다.

내가 만든 프로그램은 그 누구에게 보여주어야 할 결과물 이전에 나만의 작품이다. 이러한 생각이 일에 도움이 될 때는 누구보다 훌륭한 결과물을 만들어내지만 괜히 내 맘대로 뭘 해보려다가 일을 망치는 경우도 많았다.

예전에 동아리 활동에서 대자보를 만드는 일이 많았는데, 나는 심각한 고민과 구상 끝에 꼼꼼한 크레파스질과 가위질로 예술작품을 만드는 반면에, 다른 후배는 대충(내가 볼 때) 인터넷에서 자료를 참고한 뒤 쓱싹쓱싹 순식간에 우리에게 정말 필요한 것(내가 보기에 아름다운 것이 아니라)을 만들어내는 것이었다. 덕분에 많은 자유시간이 확보됨은 물론 내 스트레스도 사라졌다. 이 일로 해서 내 예술가적 기질이 얼마나 사람들과 나 자신을 피곤하게 하는가에 대해 다시 한 번 생각해보게 되었다.

예술가 테스트 문항


아래는 내가 직접 만든 테스트이다. 아래 항목에 해당하는 경험이 많을 수록 당신은 예술가이다.

1. 교수님이 내는 과제가 시시하다고 생각할 때가 있다.
교수님이 쉬운 과제를 내면 옳다구나 하고 기뻐하면 될 일인데, 예술가는 꼭 상대방의 요구사항에 자신의 생각을 덧붙인다.

2. 나는 가끔 천재적인 기질을 보일 때가 있다.
신이시여, 이 영화를 정녕 제가 만들었단 말입니까? - <벤허>를 감독한 와일러

3. 내가 설치한 윈도우가 아니면 내 컴퓨터가 아니다.
모든 환경은 완벽하고 통제 가능해야 한다.

4. 바탕화면을 고르는데 30분 이상 걸린다.
예술을 위해서는 나만의 작업 공간이 필요하다.

5. 우리 학교에는 하찮은 교수님이 많다.
자존심과 자만심이야말로 예술가의 본성이다.

6. 특별한 이유 없이 예전에 했던 과제나 프로젝트를 이따금씩 꺼내본다.
일을 한 것이 아니라 작품을 만들었기 때문이다.

7. 파이썬은 자바보다 아름답다. (혹은 그 반대)
아름다움을 왜 따짐?

8. 교수님들은 내가 낸 과제의 가치를 제대로 평가하지 않는다.
나만의 관점, 나만이 알 수 있는 아름다움을 제대로 평가받고 싶은 욕구가 많으며, 대체로 이것은 잘 채워지지 않는다.

9. 시시한 논문에는 차라리 내 이름을 빼고 싶다.
예술가는 경력보다 자존심이 먼저다. 아무리 나에게 도움이 되는 일이라도 부끄럽지 않아야 한다.

일할 때 특징


일을 하다 보면, 혹은 일을 시켜보면 예술가 기질의 사람과 기술자 기질의 사람은 몇 가지 차이가 있다. 때로는 큰 차이처럼 느껴지기도 하고, 결국 별거 아니라는 생각도 든다.
아무래도 내가 예술가 기질이기 때문에 약간의 자학 개그를 섞어 보았다. ㅎㅎ

장) 예술가 : 자신만의 아이디어가 풍부하다.
단) 기술자 : 시키는 대로만 한다.
장) 기술자 : 시키는 대로는 잘 한다.
단) 예술가 : 시키는 대로 안 한다.

장) 예술가 : 누구보다 열정적으로 일한다.
단) 기술자 : 보수를 보고 일한다.
장) 기술자 : 보수만큼은 일한다.
단) 예술가 : 보수 이상으로 열심히 하다(기획만 하다) 혼자 퍼진다.

장) 예술가 : 요구사항 외에 여러 가지 필요한 기능을 생각해온다.
단) 기술자 : 요구하는 기능 외에는 프로그램이 어떻게 굴러가든 관심이 없다.
장) 기술자 : 요구하는 기능은 잘 구현한다.
단) 예술가 : 요구사항과 영 상관이 없는 결과물을 가지고 와서 클라이언트를 설득하려고 든다.

장??) 예술가 : 시시한 일에는 관심이 없다.
단) 기술자 : 일이기 때문에 관심을 가진다.
장) 기술자 : 꼭 필요한 일에 집중한다.
단??) 예술가 : 쓸데없는 일에 관심이 많다.

쓸데없는 일이 정말 쓸데없는 지는 아무도 모를 일이다. 예술가는 자기 호기심을 자극하는 여러 가지 일들에 두루 관심이 많기 때문에 쓸데없는 것에 시간을 잡아먹는 것 같기도 하지만, 더 좋은 결과물과 방향을 제시할 때가 많다. 하여튼 시키는 대로 할 생각이 없다. ㅋㅋㅋ


성격을 보완할 부분



예술가든 기술자든 그 성격이 다듬어지고 완성되면 결국 비슷해진다. 성격과 상관없이 직무에 있어서 요구되는 사항은 똑같은데 개선할 방향이 다르다.

예술가는 자기 하고 싶을 때만 열심히 하는 것처럼 보인다. 자기 주장이 강하고, 때로는 자기 자존심만 챙기려 들 때가 많다. 또한 완벽주의 성격이 다른 사람을 힘들게 한다.
 - 하기 싫은 일은 결국 해야 한다. 얼른 하는 것이 스트레스를 줄이는 최선이다.
 - 내 자존심보다 다수의 행복이 중요하다.
 - 클라이언트보다 우리 회사 직원이 더 중요하다.
 - 일단 시키는 대로 해보는 것도 시킨 사람에 대한 예의다.
 - 상대방의 언어로 얘기하는 것도 능력이다.
 - 싸우지 말자.

기술자는 시키는 대로만 하고 결과를 책임지지 않는 것처럼 보인다. 자기개발에 소홀히 할 때가 많다. 타인의 감정과 형편을 이해하지 못할 때가 많다.
 - 이번 일을 잘 끝내야 다음 일도 타올 수 있다.
 - 뒤쳐지지 않는 가장 좋은 길은 최고가 되려고 노력하는 것이다.
 - 내가 도움을 주면 언젠가 남도 나를 도울 일이 있다.
 - 특별한 사람들을 특별하게 생각해주면, 특별한 일을 해낸다.



Read More

git 튜토리얼 (1)

들어가면서

Git을 자주 쓰는 사람들은 Git이 대세라고 확신할테고, 심한 경우에는 "요즘 Git도 안 쓰고 개발이 되냐"고 말하기도 한다. 그러나 아직 많은 사람들이 Git에 손을 못 대고 있거니와, 쓰더라도 자세히 모르고 그냥 쓰는 경우도 많다.

Git의 장점은 그냥 강력하다는 것이고, 단점은 개발의 핵심도 아닌 도우미 프로그램 치고는 너무 복잡하다는 것이다. 여기서 한 가지 위안거리가 있는데, Git의 기능 중 한 가지라도 배우면 그건 반드시 유용하게 써먹게 된다. 강력하고 어려운데 쓸데없다면 그게 왠 똥인가 말이다. 그런데 Git은 최소한 다 쓸모있다. 배운만큼 써먹자.

형상 관리 프로그램

형상관리 프로그램을 사용하지 않으면서 버전 관리를 할 수 있는 대표적인 방법은, 프로젝트 통째로 zip으로 압축하고 이름에 날짜를 적어 보관하는 것이다. 과거의 코드를 보고 싶으면 특정한 날짜의 압축을 풀어서 확인하면 된다. 매번 중요한 순간마다 zip으로 압축하는 것이 git에서 커밋(commit)이랑 똑같다.

커밋이란 말은 앞으로 자주 쓸 텐데, 지금은 소스 코드에 대한 청사진, 혹은 스냅샷 정도로 생각하면 좋다. 그냥 소스코드 버전으로 생각해도 된다. 특별히 결정적인 순간마다 저장해서 나중에 두고두고 꺼내보고 비교해볼 수 있다. 한 번 커밋한 내용은 원칙적으로 수정이 불가능하다.

형상관리 프로그램을 이용하면 매번 zip으로 압축해서 소스 코드를 보관할 필요가 없다. 대신 커밋으로 빠르게 코드를 보관하고 복원할 수 있다. 여러 사람이 작업할 때도 간편하게 버전을 관리할 수 있다. 버전 간의 차이점을 확인하고 싶을 때도 편리하게 툴을 이용할 수 있다.

SVN과 차이점

SVN에서는 모든 소스 버전 정보를 서버에서 관리한다. 프로그래머는 서버에 접속해서 특정한 버전의 커밋을 다운받고 편집한 뒤 새 버전으로 서버에 커밋한다. 반면에 Git은 모든 정보를 서버로부터 카피(clone)해서 로컬에 저장한다. 커밋도 로컬에서 한다. 서버와는 단지 sync를 맞출 뿐이다.

SVN이 웹메일이나 클라우드 컴퓨팅처럼 동작한다면, Git은 Google Drive처럼 동작한다.


Git과 GitHub

  GitHub는 Git으로 생성된 저장소를 운영하는 사이트이다. 기존 Git에 사용자 관리, 이슈 관리, 웹 소스 편집 등 기능을 붙여서 만들었다. GitHub 이전에 오픈 소스의 성지로 불리던 곳은 SourceForge인데 SVN으로 운영된다. 사람들은 SourceForge를 즐겨 이용하면서도 한 편으로는 자신만의 저장소를 만들고 싶어했다. 왜냐하면 SourceForge가 없어질 경우 모든 관리 데이터를 잃어버리기 때문이다. 이것은 모든 계란을 한 바구니에 담는 것과 마찬가지이다. 그래서 Google이나 Microsoft 등 굴지의 회사들은 SourceForge 이외에 자신들만의 저장소를 만들어서 운영하고자 했다. 그런데 GitHub가 나타나면서 모든 고민은 사라지고 GitHub 세상이 됐다. 이 세상 거의 모든 오픈소스 프로젝트를 GitHub에 담으면서도 사람들은 예전처럼 불안해하지 않는다. 서버와 동일한 관리 정보가 바로 자신의 컴퓨터에 있기 때문이다.

GitHub를 사용하면서도 한편으로 얼마든지 자신만의 서버를 만들어서 모든 내용을 GitHub와 공유할 수 있다. 로컬 컴퓨터의 내용을 두 군데 서버와 동시에 싱크를 맞추면 그만이다.


도우미 GUI  프로그램

Git은 기본적으로 command line으로 동작하게 만들어졌다. 윈도우 95가 나오면서 GUI 혁명이 이뤄진지 20년이 훨씬 지나가지만 아직도 command line방식을 고수하고 있는 프로그램이 있는데 Git 또한 그렇다고 하겠다.  이를 참다 못한 사람들이 Git API를 이용해서 GUI로 만들어놨다. 이것들은 사용하면 좋긴 한데, 자칫 Git에 대해 오해한 상태에서 계속 쓸 여지도 있다. 하여 우선 본 튜토리얼은 command line으로 진행하고, 나중에 GUI 프로그램도 살펴본다. 핵심 내용만 짚으면 별로 복잡한 명령어도 없다.


지금부터 튜토리얼 시작

이제 진짜 시작이다.

GUI 프로그램을 활용하면 쉬워보이기는 하는데 핵심적인 원리와 내용을 설명하기가 어렵다. 그래서 우선은 커맨드 라인으로 진행하고, 나중에 GUI도 설명할 계획이다. 사실 커맨드 라인으로 다룰 줄 알게 되면, GUI 프로그램은 자동으로 그냥 사용법을 이해하게 된다. 진짜, 뻥 아니고.

위에서 잠깐 언급했듯이 Git은 기본적으로 서버 없이 로컬에서 돌아가는 프로그램이다. 만약 혼자 간편하게 소스코드를 관리할 목적으로 git을 사용한다면 GitHub도 필요 없고, 서버도 필요없이 간단하게 Git만 설치하면 된다.

모든 설명 과정은 윈도우 사용자 기준으로 진행된다. 리눅스 사용자들은 아무래도 알아서 잘 하는 습관이 되있을테고 이런 설명도 필요 없겠지.

Git을 설치하는 방법에는 여러 가지가 있는데, Visual Studio를 설치하다 보면 같이 번들로 설치되기도 한다. 어쨌든 가장 보통의 방법은 직접 다운받는 것이다. 대뜸 깃허브로 들어가면 다운 못 받는다. 거기는 git서비스를 제공하는 곳이고 https://git-scm.com 으로 들어간다. 첫 화면에 바로 다운로드 버튼이 보인다.

설치 과정 중에 default editor를 선택하게 된다. git에서 왠 에디터냐? 파일끼리 병합하고 어쩌고 하는데 에디터가 필요하다. 사실 여기 튜토리얼을 진행할 때는 별 필요가 없으므로 일단 적당히 아무거나 선택하고 넘어가자.

나머지는 그냥 대충 Next를 눌러도 상관이 없을 것 같다.

자, 이제 Git을 시작해보자. Git은 데이터베이스가 필요없다. 모든 소스 관리 정보를 소스코드가 들어있는 그 폴더 속에 저장한다.

커맨드 창(Command Prompt)을 띄우고 대뜸 git이라고 입력해보자.



아무 내용이나 막 나오면 일단 잘 깔렸다. 방금은 git.exe라는 프로그램을 실행한 것이다. 앞으로 이것을 git 프로그램이라 부르겠다.

이제 소스 코드가 들어있는 폴더로 이동한다. 이미 소스 코드가 들어있어도 상관없고, 빈 폴더에서 시작해도 좋다. 어쨌든 폴더로 가야 하는데, 커맨드 입력이 서툴면 아래 그림을 참고한다.



하여튼 드라이브를 옮길 때는 X: 와 같이 콜론을 이용하고, cd 명령어를 통해 폴더로 들어간다. 폴더에서 나오려면 "cd .." 을 입력하면 된다.

우선 나는 빈 폴더를 만들어서 시작할 것이다. 최초의 저장소를 생성하는 명령어는 git init이다. 앞서 말했지만 git init명령은 반드시 빈 폴더에서 할 필요가 없다. 이미 잘 돌아가고 있는 소스를 Git으로 관리하고 싶을 때도 그 시작은 git init이다.





명령이 실행되고 나면 .git 이라는 숨김 폴더가 생성된다. 리눅스에서 '.'으로 시작하는 이름은 무조건 숨김 속성이 되는데, 윈도우에서는 Hidden속성이 따로 부여된다. 폴더 내에 .git 폴더가 있으면 그것은 git으로 관리되는 폴더라고 할 수 있다. git프로그램은 반드시 .git폴더가 보이는 곳에서 실행해야 한다.

.git 폴더에는 Git 관리 정보가 다 들어간다. SVN으로 따지면 서버에 저장되어야 할 모든 정보들이 .git 폴더에 저장되는 것이다. 당연히 용량이 상당한데, 보통 관리되는 소스코드의 1/2 정도 크기이다. 나름의 방식으로 압축이 된다.

Git으로 관리되는 소스 코드를 카피할 때, .git 폴더만 카피해도 된다. 모든 커밋 정보가 .git 폴더에 들어있기 때문이다. 실제 소스 코드는 .git속에 들어있는 커밋을 이용해서 복원하면 그만이다. 서버에서 소스를 다운받을 때(clone) 실제로는 .git 폴더를 다운받는다.

현재 저장소의 상태를 확인하는 명령어는 git status이다.



뭐라고 영어로 나오는데 뭔 말인지 하나씩 살펴보자.

On branch master => 현재 branch를 나타낸다. 지금은 branch를 소스코드의 어떤 갈래, 분기점이라고 이해하자. branch가 아예 없으면 안 되니까 기본으로 하나 생성해주는데, 그 이름이 master이다.

No commits yet => 아직 아무 커밋도 없는 백지상태이다.

nothing to commit => commit할 파일이 없음을 나타낸다. 마지막 커밋과 비교해볼때 내가 작업한 내용이 없다는 뜻이다.

이제 이 폴더에 아무 작업이나 해보자. 여기서는 file.txt를 만들고 아무 내용이나 넣어보자. 기왕이면 두 개를 만들자.

이제 git status 를 다시 입력하면 다음과 같이 나타난다.


Untracked files 라는 문구가 있다. 처음 보는 파일, 혹은 관리되지 않는 파일이라는 뜻이다. 소스 코드 내의 모든 파일은 매 커밋 때마다 변화를 추적하는데, 이 파일은 아예 첨 본다는 뜻이다.

이제 새로 만든 파일을 커밋할텐데, 그러려면 파일을 스테이지에 올리는 작업이 필요하다. 이게 무슨 말이냐? 수정된 파일이 엄청 많은데, 그 중에서 필요한 파일만 꼭 커밋하고 나머지는 임시로 만든 것이니 그대로 두고 싶을 수 있다. 그럴 때 필요한 파일만 스테이지에 올려서 커밋하고, 나머지는 그대로 둔다. 대체로는 소스를 빌드해서 생긴 exe파일이나 기타 임시 파일들은 커밋하지 않고 소스 파일만 커밋한다.

여기 예제에서는 file.txt를 커밋하고 싶으니 이것만 스테이지에 올린다. git add 명령을 이용한다.



스테이지에 올라간 파일은 커밋 대기 상태가 되며, 이 순간 파일의 내용은 임시로 저장된다. 만약 파일이 너무 많으면 "git add --all"로 모든 언스테이지 파일을 스테이지에 올릴 수 있다. 참고로 별표(*)를 이용하는 것도 가능하다.

스테이지에 파일을 올렸는데 지우거나 수정하면 어떻게 되나? 직접 해 보면 안다.


스테이지에 올라간 상태를 기점으로 해서 변경된 내용은 언 스테이지 상태가 된다. 만약 이 상태로 커밋하면 스테이지에 올릴 당시 저장되었던 file.txt의 내용이 커밋된다. 하여튼 스테이지에 올린다는 것은, 커밋할 내용을 고르기 + 임시로 저장하기 정도로 생각하면 된다.

만약 스테이지에 올리고 수정 후 또 올리고 .. 를 반복한다면 ? 스테이지가 여러 개 생기느냐? 그건 아니고 그냥 하나의 스테이지가 계속 누적되어 유지된다.

하여튼 원점으로 돌아와서 file.txt를 다시 만들고 add 명령으로 재차 스테이지에 올렸다, 치고. 이제 진짜 커밋을 해보자.

대충

git commit -m "첫 커밋이다!"

라고 입력하면 아래와 같이 뜬다.


이게 뭔 말이냐, 이메일과 이름이 없다는 뜻이다. 커밋할 때는 작성자, 이메일, 메시지(설명), 이 세 가지 정보가 무조건 들어가야 한다.

그러므로 다음과 같이 입력하면 커밋이 되긴 된다.

git commit -m "첫 커밋이다!" --author="myname <myemail@aaa.com>"

여기서 잠깐, 이름과 이메일은 아무거나 넣어도 되나? 네. 그렇다. 깃허브에 올릴 때도? 아 물론이죠. 이름과 이메일을 남기는 것은.. 사실 그냥 주석의 연장선으로 보면 된다.

다만 GitHub를 이용할 경우, 왠만하면 GitHub에서 쓰는 아이디를 넣어주는 것이 좋다. 나중에 연동되는 기능이 있기 때문이고, 그냥 보기에도 좋기 때문이다. 여러 개의 저장소를 동시에 쓴다면? 아 몰랑..~ 이름 여러 개는 넣을 수 없으니 그땐 뭐 알아서 하는 거지.

하여튼...

매번 이름과 이메일을 넣기 귀찮으니 global 속성으로 정해줄 수 있다. 위의 캡쳐 화면처럼 따라서 입력하면 된다. 좀 길지만 한 번만 하면 되는 거니 참자.

그러고 나서 다시 git commit -m "메시지" 를 입력하면 대망의 커밋이 된다.



git status를 입력하면 아까 초록색으로 스테이지 됐던 것이 사라졌다? 어디갔어? 커밋했으니까 이제 없지. 대신 빨간색 언스테이지 파일은 그대로이다. 이제 git log를 입력해보자.



git log를 입력하면 현재까지의 모든 커밋을 쭉 볼 수 있다. 길어질 경우 나가는 키가 'q'다. 자.. 근데 한글이 깨져 있다! ㅜㅜ 해결 하는 방법이 아예 없는 것은 아니지만 괜히 인코딩만 더 꼬일 수 있으므로 그냥 두는게 좋다. 어차피 GUI 툴을 쓰면 다 해결된다.

.
.
.

다음 튜토리얼에서는 커밋을 실용적으로 활용하는 예 - 수정된 부분 확인, 분기, 병합 등을 해볼 것인데.. 과연 안 귀찮을 수 있을지..


Read More

우분투에서 C++ 개발하기 (1) - 컴파일 과정 및 gcc

이 글은 윈도우에서 Visual Studio만 쓰다가 Ubuntu를 생소하게 생각하는, 나와 같은 사람을 위해 쓴다.

글쓰기에 들어가기 전에 Visual Studio와 같은 통합 환경에 비해 리눅스에서 C++과 같은 언어를 개발한다는 것이 얼마나 복잡한 일인가부터 상기시켜야겠다. C++ 자체가 원래 언어차원에서부터 빌드가 영 까다로운 부분이 있다. 리눅스 사용자는 그 문제를 그대로 정면으로 맞이해야 한다.  그나마 편리하게끔 만든 것이 3편에서 나오는 CMake 정도인데, 이것마저도 복잡한 스크립트를 직접 입력해야 한다.

자유롭고 오픈될 수록 불편하고 복잡하다. 자유도가 높다는 것은 그 자유를 누릴 수 있을 수준으로 공부와 노력을 들여야 겨우 좀 사용할만하다는 뜻이 된다. 


하여튼 글 쓰기의 계획은 이렇다.

1. 맨 땅에 삽질하는 심정으로 g++컴파일러를 직접 이용해본다.
2. makefile을 이용해본다.
3. CMake를 이용해본다.
4. vscode를 활용하여 개발환경을 구축해본다.

그 첫번째, 맨 땅에 삽질하기.


1. C++의 컴파일 과정


VS만 쓰다보면 컴파일러 개념이 희박해지는데, 이는 당연한 것이다. VS라는 훌륭한 툴이 있기 때문에 우리는 아무 것도 신경 안 써도 된다. 원래 정치 선진국일수록 국민들이 정치에 무지하다. 그러나 리눅스를 제대로 이용하려면 컴파일에 대해서 좀 자세히 알아야 한다.

컴파일 과정을 모르는 사람을 위해 최대한 자세히 설명해본다.
컴파일 과정은 다음과 같다.

[소스코드] -> [바이너리] -> [실행파일]

소스코드는 사람이 읽을 수 있는 텍스트 파일이다. 이것을 컴퓨터 명령코드(기계어)로 번역하게 되는데, 이렇게 번역된 결과물을 보통 '오브젝트 파일', 혹은 '바이너리 파일'로 부른다. 두 명칭 모두 약간 애매모호한 점이 있어서 설명해본다.

우선 '오브젝트 파일'이란 말은 컴파일의 목적이 된다는 뜻에서 나왔다. 또 다른 말로 '타겟 파일' 혹은 '오브젝트 코드', '타겟 코드' 이런 말들로 불리는데 다 같은 말이다. 소스의 반댓말이 타겟 아니겠는가.

'바이너리 파일'란 말은 본래 '텍스트 파일'의 반댓말로서, 아스키 코드로 작성되어 텍스트로 읽을 수 있는 파일이 아닌 것들의 집합이다. 즉, 사람이 읽을 수 없는 파일이란 뜻이다. 소스코드는 사람이 읽을 수 있는 텍스트 파일의 일종이다. 반대로 기계어로 번역된-컴파일된 파일은 사람이 읽을 수 있는 텍스트 파일이 아니다. 이런 의미에서 컴파일된 결과물을 바이너리라고 부르는 것이다.

파일은 두 가지로 나뉜다. 프로그램과 프로그램이 아닌 것. 프로그램은 실행이 가능한 명령어로 구성된 것이고, 그렇지 않은 문서파일, MP3등은 프로그램이 아니다. 여기서 바이너리는 큰 의미에서 프로그램으로 봐야 한다. 얘기하다 보니 점점 산으로 간다.

하여튼 우리가 작성한 C++코드는 바이너리(프로그램)으로 번역된다. 바이너리는 여러 개일 수 있다. 그런데 이들은 바로 실행할 수 없다. 이들을 묶어서 OS가 실행할 수 있는 실행파일로 만들어주는 작업이 링크이다. 바이너리는 다시 말해서 프로시져(함수)들의 묶음이다. 이 함수들 중에 main이란 놈이 있다면, 이것을 시작점(엔트리 포인트)으로 해서 프로그램을 실행한다. 그래서 main 함수는 무조건 하나 있어야 하며, 한 개만 있어야 한다.

main이 없으면 바로 실행할 수는 없지만 라이브러리는 될 수 있다. 정적 라이브러리, 동적 라이브러리, 이런 친구들이 될 수 있다는 것이다. 이런 라이브러리들은 main이 없다.

결론적으로 컴파일 작업을 수행하는 컴파일러와 링크 작업을 수행하는 링커는 엄연히 다른 존재다. 그러나 보통 컴파일러라고 하면 링커의 기능도 들어있다.


2. 컴파일러 잡설, gcc


컴파일러는 소스코드를 기계어로 번역하는 작업을 한다고 했다. 그런데 우리는 한 가지 기계(컴퓨터, CPU)만 가지고 있는 것이 아니다.

재미삼아 미약한 지식을 동원하여 컴퓨터의 역사를 살펴보면 옛날에는 컴퓨터란 놈이 표준이 없고 우후죽순으로 각자 자기만의 컴퓨터를 개발해서 쓰곤 했다. 완전히 다른 컴퓨터들이었기 때문에 서로 명령어와 구조가 달랐고, 그래서 각자 컴파일러를 따로 가지고 있었고, 프로그램 호환도 전혀 안 됐다. 그러다 IBM에서 자신들의 CPU 아키텍쳐를 오픈하면서 시장을 거의 점령했고, 이 IBM 구조의 CPU 시장을 점령한 회사가 인텔, 그리고 AMD이다(약 80년대 후반부터). 이 IBM호환 기종에서도 약간 변종이 있는데, 80386 CPU 시절 개발된 x86(32bit) 구조, 그리고 AMD에서 개발한 x86-64(64bit) 구조가 있다. 현재는 이 두 가지 정도 쓰이고 있는데 점점 64bit로 점령되는 추세이다. x86시리즈는 대부분 윈도우 운영체제가 탑재되며 리눅스도 많이 쓰인다. MacOS도 x86으로 갈아탔다.

여기까지는 일반적인 데스크톱 컴퓨터의 얘기인데, 임베디드 - 소형화된 컴퓨터에서는 사정이 좀 다르다. 아직도 다양한 경쟁사들이 저마다의 CPU 아키택쳐로 경쟁하고 있는 형국이며, 그 중에서도 단연 1위는 ARM이다. 스마트폰에 들어가는 CPU가 대부분(내가 알고 있는 전부는) ARM으로 되어 있고 라즈베리파이도 ARM이다. 운영체제는 전부 리눅스(안드로이드)이다. ARM은 버전 6부터 시작해서 현재 8까지 나와있다. 

컴파일러는 CPU구조, 그리고 운영체제에 따라서 달라진다. 그 말인 즉슨, 프로그램을 하나 만들었을 때, 이 프로그램이 실행될 수 있는 CPU, 그리고 운영체제가 정해져 있다는 것이다. 

여러분들이 CPU를 하나 만들었다고 하자. 그러면 그 CPU를 동작시킬 수 있는 예제 코드가 필요할 것이다. 그리고 그 코드는 보통(100%) C언어가 된다. C언어로 Hello World를 만든 뒤, 이것을 내가 만든 CPU 명령어로 번역할 수 있는 컴파일러가 필요하다. 그래서 CPU를 만들면 반드시 컴파일러도 같이 만들어줘야 한다. 이 컴파일러 프로그램을 따로 만들어서 사람들에게 나눠줄 수도 있지만, 그냥 다들 컴파일에는 gcc를 쓰고 있으니, 내 CPU에 대한 컴파일 기능을 gcc에 추가한다.

gcc는 모든 리눅스에서 공통으로 쓰는 컴파일러 모음을 뜻한다. 더 멀리는 원래 GNU프로젝트를 시작하기 위해 만들었다는데, 알게 뭐냐. 하여튼 컴파일러 = gcc이다. gcc는 리눅스 운영체제위에서 실행된다는 가정아래 x86, x86-64, armv6, v7, v8 모두를 커버할 수 있다. 그 외에도 듣도 보도 못한 CPU까지 커버한다. 앞서 설명했듯이 모든 CPU 제조사가 gcc에 자기 CPU를 추가하기 때문이다. 아래 링크를 참고하자. 


그래서 결론 : 컴파일러에는 gcc만 있는 것은 아니지만 그런데 gcc면 왠만큼 다 해결이 된다는 것이다.

크로스 컴파일이란 빌드 머신과 타깃 머신이 다른 경우를 말한다. 빌드 머신이란 현재 컴파일이 수행되고 있는 컴퓨터이다. 타깃 머신이란 컴파일 결과물이 실행될 컴퓨터이다. 안드로이드의 경우가 가장 대표적이다. 안드로이드 폰에서 돌아가는 프로그램을 안드로이드에서 개발하는 경우는 거의 없다. 휴대폰에서 엄지손가락으로 코딩하고 그걸 빌드하는 사람이 있겠는가? 보통은 리눅스나 윈도우에서 개발을 해서 apk까지 빌드하고, 이 파일을 안드로이드에 보낸다. apk가 실행되는 타깃머신은 안드로이드인데, apk를 만들어낸 개발 머신은 윈도우이다. 이런 경우를 가리켜 크로스 컴파일이라고 한다. 라즈베리 파이나 그보다 작은 소형 임베디드 보드는 거기서 직접 컴파일 하고 디버깅하고 어쩌구 하기가 매우 불편하니까 대개 크로스컴파일을 하게 된다.

그럼 자바, C#, 파이썬은 어떻게 동작하는가? 이들 언어는 동작하는 방식이 C와 꽤 다르다. 너무 산으로 가면 안 되니 생략.

g++은 gcc의 일부분으로서 C++언어의 컴파일러이다. 요즘 나오는 우분투에는 기본으로 깔려 있다. 여기서 재미있는 사실은 gcc는 C언어의 컴파일러인데 이건 C++로 만들었다. 마인크래프트에 보면 손으로 나무 깎아서 나무 곡괭이 만들고 그걸로 돌캐서 돌곡괭이 만들고 그걸로 철 캐서 철도끼 만들고 철도끼로 나무 캐고 그렇게 하듯이 컴퓨터 언어의 세계도 비슷하다. 포트란으로 C컴파일러 만들고 C컴파일러로 C++컴파일러 만들고 그걸로 다시 C컴파일러 만들고 그걸로 파이썬 만들고 파이썬으로 파이썬 컴파일러 만들고 ...

만약 gcc가 안 깔려있으면 깔아줘야 한다. 우분투에서는 C 언어 프로그래밍 개발에 필요한 것들을 패키지로 묶어서 배포한다.


$sudo apt install build-essential

요렇게 입력하면 gcc랑 make랑 cmake 등등 필요한 것들이 설치될 것이다.

3. 간단한 빌드

드디어 잡설을 끝내고!

본격적으로 개발에 들어가보자.

대충 폴더 하나를 만들고, 그 속에 main.cpp 파일을 작성한다.

<main.cpp>
#include <iostream>
int main()
{
    std::cout << "U**** F***** UBUNTU!\n";
    return 0;
}

다음과 같이 실행해본다.


g++ -c main.cpp #main.o 파일 생성

이렇게 하면 main.o 파일이 자동으로 생성된다. main.o는 앞서 말한 오브젝트 파일 : 바이너리다. 바이너리 파일은 바로 실행할 수 없다. 메인 함수가 물론 포함되어 있지만 그래도 곧바로 실행할 수는 없고, 리눅스가 실행할 수 있는 형식의 파일로 만들어야 한다.


g++ -o test main.o #main 파일 생성
./test #실행

main.o 파일이 바이너리 파일이고, 확장자가 없는 test 파일이 실행파일이다.

하나의 c/cpp 파일은 하나의 바이너리를 생성한다. 그런데 만약 파일이 여러개라면 어떻게 될까? 아래와 같이 파일을 만들어보자.

<my.h>
int myfunc(int val);

<my.cpp>
#include "my.h"
int myfunc(int val)
{
    return val + 1;
}

<main.cpp>
#include <iostream>
#include "my.h"
int main()
{
    std::cout << "calling up myfunc : " << myfunc(3) << std::endl;
    return 0;
}

이제 다음과 같이 명령어를 입력한다.


g++ -c main.cpp

컴파일러는 main.cpp파일을 들여다보고 my.h 파일을 현재 디렉토리에서 찾아낸다. my.h에는 myfunc에 대한 정의가 있으므로 컴파일에는 문제가 없다. main.o 파일이 생성된다.


g++ -c my.cpp

myfunc의 구현이 컴파일되어 my.o 에 담긴다. 이제 main.o와 my.o를 묶어서 test라는 실행 프로그램을 만들면 된다.


g++ -o test main.o my.o

이 실행의 결과로 test파일이 생성된다. 오브젝트 파일이 더 많을 경우에는 줄줄이 다 갖다 붙이면 된다.

여러 개의 바이너리 파일 중 main함수는 단 한개만 있어야 한다. 두 개 있거나 없으면 실행파일이 안 만들어진다.


이 쯤 되서 헤더파일이 무엇인지, 컴파일과 링크가 무엇인지 다시 한 번 생각해보자. 헤더 파일은 함수의 스펙을 적어놓는 것이다. myfunc 함수를 이용해야겠는데, 그 함수가 입력값이 어떤지 출력은 어떤 타입인지 알 수가 없다. 그래서 이 함수는 이렇소이다~ 하고 소개해놓은 것이 헤더파일이다. main.c에서는 헤더 파일의 정보만 보고 프로그램이 잘 돌아갈 지 생각해본다. 헤더에 의하면 입력은 int 한 개이고 출력도 int이다. 문법적으로 문제가 없으면 일단 OK, main은 컴파일이 가능하다.

컴퓨터 조립을 할 때, 부품을 하나의 공장에서 생산하지 않는다. CPU는 인텔에서 만들고 메인보드는 MSI에서 만들 때, 서로 통일된 스펙과 인터페이스만 맞춰놓고 각자 만든다. 나중에 컴퓨터 조립을 할 때, 각자 만든 부품을 연결해서 완성시킨다. 컴파일 과정도 마찬가지인데, 두 개의 바이너리가 각자 빌드된다(컴파일). 서로의 인터페이스에 대한 정보는 헤더파일을 참고한다. 나중에 링커를 통해서 그 둘을 연결(링크)하면 비로서 하나의 프로그램이 된다.


4. 라이브러리 참조


우리가 앞서서 #include <iostream> 이라고 작성했을 때, 무슨 일이 일어났는지 다시 생각해보자. iostream은 헤더 파일 이름이다. (확장자가 아예 없다.) 이 파일은 내 시스템 어딘가에 위치해 있는데, 보통은 /usr/include/c++/4.xxx/ 디렉토리에 들어있다. 본래 어떤 헤더 파일이든지 include 하려면 그 헤더파일이 들어있는 경로를 컴파일러에 알려줘야 한다. 하지만 왠만한 프로그램에서 다 쓰는 이런 스탠다드 라이브러리는 미리 디폴드 경로가 등록되어있다. 이것을 확인하고 싶으면 다음과 같이 명령어를 입력해본다.


g++ -v

혹은


export CPLUS_INCLUDE_PATH

이제 iostram파일을 포함하여 컴파일 하는 데는 문제가 없다. 하지만 링크 과정에서 iostream에 있는 함수와 클래스들이 어디에 어떤 파일로 로 구현되어있는지를 알려줘야 한다. 이것도 디폴트로 등록되어 있다.
우선 라이브러리 파일(바이너리)의 이름은 libstdc++.so.6 이고 파일의 위치는 대체로 /usr/lib 이다. 이 디렉토리는 환경변수 $LD_LIBRARY_PATH에 등록되어 있다.

지금까지 봐왔던 .o 파일이 아니라 .so 인 이유는 동적 라이브러리이기 때문이다. 동적 라이브러리는 윈도우로 치면 dll파일이다. 정적 라이브러리는 링크할 때 직접 그 내용이 실행 파일 안에 복사된다. 그래서 바이너리 파일 크기만큼 실행 파일의 크기가 늘어난다. 반면에 동적 라이브러리는 프로그램에 포함되지 않는다. 대신 프로그램 실행할 때 위치만 알려주면 된다. 그래서 실행파일의 크기가 늘어나지는 않지만, 실행파일 단독으로 프로그램 실행이 안 되고, 꼭 동적 라이브러리를 옆에 붙여줘야 한다. 여러 프로그램에서 동시에 쓰는 바이너리 파일이 있다면 동적 라이브러리가 되는게 유리하다.

만약에 다른 라이브러리를 추가하고 싶으면 다음 세 가지를 컴파일러에 알려줘야 한다.

*관련 헤더파일의 검색 경로
*관련 라이브러리 검색 경로
*관련 라이브러리 파일 이름

헤더파일의 경로에는 -I(대문자 아이) 옵션,  라이브러리 검색 경로에는 -L옵션, 라이브러리 파일 이름에는 -l(소문자 엘) 옵션을 준다. 누구야 첨에 이거 만든 사람 왤케 헷갈리게 I랑 l이랑 섞어놨어..

myfunc을 라이브러리화 해서 추가한다면 다음과 같이 명령어를 입력한다.

#my를 컴파일하여 my.o를 생성한다.
g++ -c my.cpp


#my.o를 묶어서 static library로 만든다. (libmy.a 생성)
ar rcs libmy.a my.o

여기서 ar은 오브젝트 파일을 정적 라이브러리로 만들어주는 툴이다. 오브젝트 파일과 정적 라이브러리는 사실상 차이가 없으며 형식상 약간 다를 뿐이다. 정적 라이브러리란 오브젝트 파일들의 집합이라고 표현할 수도 있으므로 zip으로 묶어주는 것과 비슷한 개념으로 생각해도 좋다.
또한 여기서 라이브러리 이름에 lib라고 붙인 것은 gcc에서 관습적으로 쓰는 표현인데 꼭 지켜야 한다. 해당 파일이 라이브러리임을 나타낸다. 확장자가 a라는 것만 봐도 라이브러리임이 명확한데 굳이 파일 이름에 prefix까지 붙이는 건지 나로서는 이해할 수가 없다.

이제 다음과 같이 입력한다.

#실행파일 'test' 생성
g++ -o test main.cpp -lmy -L./

여기서 -l, -L옵션과 뒤에 나오는 인자 사이에 공백을 두지 않음에 유의한다. libmy.a 파일에서 앞의 lib와 확장자 .a는 빼야 한다. 기호 "./"는 현재 경로라는 뜻으로서 libmy.a 파일이 들어있는 경로를 밝혀줘야 한다. l은 숫자 1이 아니라 소문자 알파벳 k다음에 나오는 l 이다.

동적라이브러리 만드는 과정은 더 쉽다. 그건 알아서 찾아보자.. ㅎㅎ

라이브러리 한 두개 정도는 이런 식으로 추가가 가능하지만 일반적인 프로젝트는 수십개의 오브젝트파일과 수십개의 라이브러리를 묶어서 컴파일하기 마련이다. 이러한 컴파일 옵션을 간소화하기 위해 makefile이 생겨났고, makefile도 작성하기 힘들어서 CMake와 같은 빌드 툴이 개발됐다. 다음 화에서 차차 살펴볼 것이다.


우분투에서 C++ 개발하기 (2) - Make

우분투에서 C++ 개발하기 (3) - CMake
Read More
Powered by Blogger.