2026년 6월 17일 수요일

베컴박스(Best Compact Box) 만들기

베컴박스 (Best Compact Box)는 내가 지어낸 말인데..
SBC 싱글보드 컴퓨터가 arm 64비트가 나오고 스펙이 좋아지고 있다.
그리고 요즘에 SK하이닉스에서 SSD 128기가 짜리 2230 벌크가
중국시장에 풀리고 있다.. 가격은 3~4만원대..
그리고  본체는 4기가 램 기준으로 10만원 선이니까 주변기기 까지 포함하면
20만원 안쪽으로 싱글보드 컴퓨터가 완성된다고 볼수 있다.
이렇게 SBC가 성능이 올라가 있고 게다가 SSD까지 받쳐주면
오렌지파이 3B같은 경우는 키오스크로 사용해도 전혀 손색이 없다.
게다가 C# 닷넷 개발자들은 아발로니아라는 좋은 방법이 있기 때문에
우분투위에 프로그램을 돌릴 수 가 있다.. (콘솔은 당연히 되고..)
그래서 그걸 받쳐줄수 있는 폼팩터가 필요한데 그게 베컴박스고
우분투서버 기반에 오픈박스로 메인App과 서브App 두개와 
VNC로 현장 SBC를 모니터링 할수 있으며 파일탐색기 터미널 메모장  
이것만 있는체로 배포되면 날라다닐 수 있다.
(당연한 얘기지만 ssh로 오렌지파이에 접속되면 파일질라로 Host에
sftp://아이피..   그리고 22번 포트로 접속해서 파일 송수신이 가능하다.)
그래서 오늘은 그 베컴박스를 만드는 방법에 대해서 얘기하고자 한다.

<본론>

베컴박스 만들기

기기 인프라 확인

초기 하드웨어 점검: (쿨링패드장착, IO핀, 하이닉스M.2 SSD설치)

맥주소를 확인하여 ipv4주소로 접속한다.
명령어 (nmap -p 22 --open 192.168.0.1/24)
SSH 포트(22) 활성화 확인후 putty로 접속확인
sftp://[기기아이피]로 파일질라를 통해 포트(22) 접속확인

사용자명(U): orangepi
비밀번호(P): orangepi

기기 Config 세팅

명령어(sudo orangepi-config)로 → 타임존, 로케일, 키보드설정

Personal settings 하위메뉴에서
Timezone: Asia / Seoul 설정
Locales: en_US.UTF-8, ko_KR.EUC-KR, ko_KR.UTF-8 설정
KeyBoard: 폰트설정이후 korean 101/104로 변경

초반 OS업데이트 처리

기기 업데이트 및 폴더생성과 바로가기 만들기

sudo apt update (저장소 업데이트)
sudo apt upgrade (업그레이드 시간오래걸림)
sudo apt update (업데이트 다시)
mkdir temp (temp폴더 생성)
mkdir MainApp (MainApp폴더 생성)
mkdir SubApp (SubApp폴더 생성)
touch ~/MainApp/start_main.sh (MainApp 바로가기 만들기)
touch ~/SubApp/start_sub.sh (SubApp 바로가기 만들기)
chmod +x ~/MainApp/start_main.sh (MainApp 바로가기 실행가능 하도록 변경)
chmod +x ~/SubApp/start_sub.sh (SubApp 바로가기 실행가능 하도록 변경)

기초 그래픽 스택 및 Openbox 윈도우 매니저 설치

X-Server 그래픽 엔진과 경량 윈도우매니저 OpenBox 설치

# 패키지 저장소 새로고침
sudo apt update

# X11, 디스플레이 매니저, 오픈박스 설치
sudo apt install xorg lightdm openbox -y

폰트 설치, 한글 입력기, 한글 환경변수 설정

한글폰트 및 한글입력기(fcitx5) 설치 및 환경변수

# 한글 폰트 및 fcitx5 입력기 패키지 설치
sudo apt install fonts-nanum fonts-noto-cjk im-config fcitx5 fcitx5-hangul fcitx5-config-qt fcitx5-frontend-gtk2 fcitx5-frontend-gtk3 fcitx5-frontend-qt5 -y

# 입력기 프레임워크를 fcitx5로 지정
im-config -n fcitx5

# 리눅스 시스템 인코딩 환경에 한국어 언어 팩 생성 및 고정
sudo locale-gen ko_KR.UTF-8
sudo update-locale LANG=ko_KR.UTF-8 LANGUAGE=ko_KR:ko LC_ALL=ko_KR.UTF-8

# LightDM 로그인 세션 환경 변수 설정
nano ~/.xprofile 에 아래 내용 추가 (없으면 새로 생성)
export LANG=ko_KR.UTF-8
export LC_ALL=ko_KR.UTF-8
export XMODIFIERS=@im=fcitx
export GTK_IM_MODULE=fcitx
export QT_IM_MODULE=fcitx

# 환경변수 파일 수정처리
sudo nano /etc/environment

# 제일 하단에 다음을 입력후 저장
LANG=ko_KR.UTF-8
LANGUAGE=ko_KR:ko
LC_ALL=ko_KR.UTF-8

한글 입력기 프로파일 구성

한글입력기(fcitx5) 프로파일을 구성한다.

# 프로파일 내용진입 하여 다음 내용으로 수정
sudo nano ~/.config/fcitx5/profile

[Groups/0]
# Group Name
Name=Default
# Layout
Default Layout=us
# Default Input Method
DefaultIM=keyboard-us

[Groups/0/Items/0]
# Name
Name=keyboard-us
# Layout
Layout=

[Groups/0/Items/1]
# Name
Name=hangul
# Layout
Layout=

[GroupOrder]
0=Default

VNC 서버 설치 및 보안 계정 패스워드 생성

원격지 화면 통제를 위해 VNC 서버를 설치하고 비밀번호를 고정

# x11vnc 패키지 엔진 설치
sudo apt install x11vnc -y

# 계정 홈 디렉토리 내부에 보안 인증서 폴더 생성
mkdir -p ~/.vnc

# 비밀번호 생성 및 저장 (패스워드 2회입력후, 저장여부 Y)
x11vnc -storepasswd ~/.vnc/passwd

LightDM 사용자 자동 로그인 처리

자동 로그인 orangepi로 로그인을 락인 시킴

# 구성 파일 에디터 열기
sudo nano /etc/lightdm/lightdm.conf

# 아래 내용으로 설정한후 저장
[Seat:*]
autologin-user=orangepi
autologin-user-timeout=0
user-session=openbox

(선택사항) HDMI 미장착시 가상 DISPLAY 활성화 설정

콘솔 기반 구동 시 가상 모니터를 생성하여 Headless 상태에서도 해상도 및 VNC 유지

# 가상 그래픽 드라이버 패키지 설치
sudo apt install xserver-xorg-video-dummy -y

# X11 가상 디스플레이 구성 파일 생성
sudo nano /etc/X11/xorg.conf.d/99-dummy.conf

# 아래 설정 내용 복사 후 저장 (1280x1024 HD 지정)
Section "Device"
    Identifier "DummyDevice"
    Driver "dummy"
EndSection

Section "Monitor"
    Identifier "DummyMonitor"
    HorizSync 28.0-80.0
    VertRefresh 48.0-75.0
EndSection

Section "Screen"
    Identifier "DummyScreen"
    Device "DummyDevice"
    Monitor "DummyMonitor"
    DefaultDepth 24
    SubSection "Display"
        Depth 24
        Modes "1280x1024"
    EndSubSection
EndSection

오픈박스 환경 AutoStart 프로필 작성 및 메인앱 자동실행

오픈박스가 로드될때 순차적으로 자동 실행 (이후 재부팅: sudo reboot)

# 오픈박스 스타트업 구성 파일 폴더 및 파일 생성
mkdir -p ~/.config/openbox
sudo nano ~/.config/openbox/autostart

[ 아래 내용으로 설정한 후 저장 ]

# 1. 마우스 커서 기본 모양 설정
xsetroot -cursor_name left_ptr &

# 2. VNC 원격 서버 백그라운드 실행
x11vnc -auth guess -forever -loop -noxdamage -repeat -rfbauth /home/orangepi/.vnc/passwd -rfbport 5900 -shared &

# 3. 절전모드 사용안함
xset s off &
xset -dpms &
xset s noblank &

# 4. fcitx5 한글 입력기 데몬 실행
fcitx5 -d &

# 5. 시스템 안정화 후 메인 앱 자동 구동 (1초 대기 후 실행)
sleep 1
/home/orangepi/MainApp/start_main.sh &

오픈박스 디스플레이구성 작성

오픈박스가 로드될때 마우스와 키보드로 처리할 내용

# 시스템 공용 폴더에서 원본파일을 유저 폴더로 복사
cp /etc/xdg/openbox/rc.xml ~/.config/openbox/rc.xml

# 설정파일을 아래의 xml로 변경처리
# 타이핑 보다는 파일질라로 복사처리 권장

[ 아래의 사항으로 변경 ]
<?xml version="1.0" encoding="UTF-8"?>
<openbox_config xmlns="http://openbox.org/3.4/rc" xmlns:xi="http://www.w3.org/2001/XInclude">
<resistance><strength>10</strength><screen_edge_strength>20</screen_edge_strength></resistance>
<focus><focusNew>yes</focusNew><followMouse>no</followMouse><focusLast>yes</focusLast><underMouse>no</underMouse><focusDelay>200</focusDelay><raiseOnFocus>no</raiseOnFocus></focus>
<placement><policy>Smart</policy><center>yes</center><monitor>Primary</monitor><primaryMonitor>1</primaryMonitor></placement>
<theme>
  <name>Clearlooks</name><titleLayout>NLIMC</titleLayout><keepBorder>yes</keepBorder><animateIconify>yes</animateIconify>
  <font place="ActiveWindow"><name>sans</name><size>8</size><weight>bold</weight><slant>normal</slant></font>
  <font place="InactiveWindow"><name>sans</name><size>8</size><weight>bold</weight><slant>normal</slant></font>
  <font place="MenuHeader"><name>sans</name><size>9</size><weight>normal</weight><slant>normal</slant></font>
  <font place="MenuItem"><name>sans</name><size>9</size><weight>normal</weight><slant>normal</slant></font>
</theme>
<desktops>
  <number>1</number><firstdesk>1</firstdesk><names></names><popupTime>0</popupTime>
</desktops>
<resize><drawContents>yes</drawContents><popupShow>Nonpixel</popupShow><popupPosition>Center</popupPosition></resize>
<margins><top>0</top><bottom>0</bottom><left>0</left><right>0</right></margins>
<keyboard>
  <chainQuitKey>C-g</chainQuitKey>
  <keybind key="W-d"><action name="ToggleShowDesktop"/></keybind>
  <keybind key="A-F4"><action name="Close"/></keybind>
  <keybind key="A-Escape"><action name="Lower"/><action name="FocusToBottom"/><action name="Unfocus"/></keybind>
  <keybind key="A-space"><action name="ShowMenu"><menu>client-menu</menu></action></keybind>
  <keybind key="Print"><action name="Execute"><command>scrot</command></action></keybind>
</keyboard>
<mouse>
  <dragThreshold>1</dragThreshold><doubleClickTime>500</doubleClickTime><screenEdgeWarpTime>0</screenEdgeWarpTime><screenEdgeWarpMouse>false</screenEdgeWarpMouse>
  <context name="Frame">
    <mousebind button="A-Left" action="Press"><action name="Focus"/><action name="Raise"/></mousebind>
    <mousebind button="A-Left" action="Drag"><action name="Move"/></mousebind>
    <mousebind button="A-Right" action="Drag"><action name="Resize"/></mousebind>
  </context>
  <context name="Titlebar">
    <mousebind button="Left" action="Drag"><action name="Move"/></mousebind>
    <mousebind button="Left" action="DoubleClick"><action name="ToggleMaximize"/></mousebind>
  </context>
  <context name="Client">
    <mousebind button="Left" action="Press"><action name="Focus"/><action name="Raise"/></mousebind>
    <mousebind button="Middle" action="Press"><action name="Focus"/><action name="Raise"/></mousebind>
    <mousebind button="Right" action="Press"><action name="Focus"/><action name="Raise"/></mousebind>
  </context>
  <context name="Desktop">
    <mousebind button="Left" action="Press"><action name="Focus"/><action name="Raise"/></mousebind>
    <mousebind button="Right" action="Press"><action name="Focus"/><action name="Raise"/></mousebind>
  </context>
  <context name="Root">
    <mousebind button="Right" action="Press"><action name="ShowMenu"><menu>root-menu</menu></action></mousebind>
  </context>
</mouse>
<menu><file>menu.xml</file><hideDelay>200</hideDelay><middle>no</middle><submenuShowDelay>100</submenuShowDelay><submenuHideDelay>400</submenuHideDelay><showIcons>yes</showIcons><manageDesktops>no</manageDesktops></menu>
</openbox_config>

NTP 시간 동기화 서버 구축 (Chrony)

장비의 물리적 시간 오차방지용 타임데몬 Chrony 설치 및 락인

# 1. 크로니 패키지 설치 및 활성화
sudo apt update
sudo apt install chrony -y
sudo systemctl enable chrony
sudo systemctl start chrony

# 2. 타임 서버 설정 수정 (인터넷 가능 환경 또는 현장 NTP 서버 지정)
sudo nano /etc/chrony/chrony.conf

# [기존 pool 대신에 한국 표준 타임 서버 등록 (지우고 아래로 변경)]
pool time.bora.net iburst
pool time.kornet.net iburst
pool ntp.gwnn.rm.kr iburst

# 3. 크로니 데몬 재시작 및 동기화 상태 현장 검증
sudo systemctl restart chrony
chronyc tracking (현재 시간 동기화 추적 상태 확인)
chronyc sources -v (연결된 외부 타임 서버 리스트 및 상태 확인)

유틸리티 설치

경량 유틸리티 5종 설치

# 정비용 유틸리티 (메모장, 터미널, 탐색기, 파일질라, 이미지뷰어) 설치
sudo apt update
sudo apt install -y mousepad lxterminal pcmanfm filezilla viewnior

오픈박스 마우스 우클릭 메뉴설정

오픈박스 바탕화면에 나올 메뉴 구성 및 sh 스크립트 연동

# 메뉴판 구성 파일 에디터 열기
nano ~/.config/openbox/menu.xml

# [아래의 내용으로 변경]
<?xml version="1.0" encoding="utf-8"?>
<openbox_menu xmlns="http://openbox.org/3.4/menu">
  <menu id="root-menu" label="EQUIPMENT CONTROL">
    <separator label="[ RUN PROGRAM ]" />
    <item label="메인 App">
      <action name="Execute"><command>/home/orangepi/MainApp/start_main.sh</command></action>
    </item>
    <item label="보조 App">
      <action name="Execute"><command>/home/orangepi/SubApp/start_sub.sh</command></action>
    </item>
    <separator label="[ REPAIR TOOL ]" />
    <item label="메모장">
      <action name="Execute"><command>mousepad</command></action>
    </item>
    <item label="터미널">
      <action name="Execute"><command>lxterminal</command></action>
    </item>
    <item label="파일 탐색기">
      <action name="Execute"><command>pcmanfm</command></action>
    </item>
    <separator />
    <item label="장비 재부팅 (Reboot)">
      <action name="Execute"><command>systemctl reboot</command></action>
    </item>
  </menu>
</openbox_menu>

한국어 입력 자판 GUI 설정

한국어 입력 자판 GUI 설정 (configtool 이용)

# 바탕화면 터미널 메뉴로 진입 # fcitx5-configtool로 키보드 설정진입

# 좌측 입력기에 다음 순서로 배치처리
키보드 - Korean - Korean (101/104)
한글(Hangul)

# 이렇게 설정하고 Apply

메인 App 구동 쉘 스크립트 작성 (start_main.sh)

메인 앱 실행 스크립트 작성 (보조도 같은방식으로 처리)

# 메인 앱 디렉토리로 이동 및 스크립트 생성
cd ~/MainApp
nano start_main.sh

# [아래의 내용으로 변경]

#!/bin/bash
export DISPLAY=:0
export XMODIFIERS=@im=fcitx
export GTK_IM_MODULE=fcitx
export QT_IM_MODULE=fcitx

# 프로그램 실행 경로로 이동
cd /home/orangepi/MainApp

# 메인 애플리케이션 실행 (실제 구동할 바이너리나 파이썬 파일 지정)
# 예: python3 main_gui.py 또는 ./.NET바이너리 등
/usr/bin/python3 main_gui.py

# 작성 완료 후 저장(Ctrl+O, Enter, Ctrl+X)

재부팅하여 결과를 확인한다


2026년 6월 12일 금요일

닷넷의 시그널링 기법

.NET에서 async, await, Task가 등장한 이후 비동기 프로그래밍 방식은 크게 변화하였다.

과거에는 스레드 동기화 객체를 사용하여 작업 완료를 기다렸지만, 현재는 Task 기반 비동기 모델을 통해 보다 효율적인 구현이 가능하다.

이번 글에서는 비동기 환경에서 자주 사용되는 시그널링(Signaling) 패턴과 TaskCompletionSource를 활용한 현대적인 구현 방법을 소개한다.

문제 상황

다음과 같은 구조를 생각해보자.

  • F1()은 int 값을 반환한다.
  • 호출자는 await F1()으로 결과를 기다린다.
  • 실제 결과는 F1()이 아니라 F2()에서 생성된다.
  • F2()는 결과 생성 시점에 F1()의 대기를 해제해야 한다.

즉, 결과를 기다리는 주체와 결과를 생성하는 주체가 서로 다른 상황이다.

기존 방식 : ManualResetEvent

private int _result;
private readonly ManualResetEvent _event = new(false);

private async Task<int> F1()
{
    _event.Reset();

    await Task.Run(() => _event.WaitOne());

    return _result;
}

private void F2(int result)
{
    _result = result;
    _event.Set();
}

동작 과정

F1 호출 ↓ WaitOne() ↓ 스레드 블로킹 ↓ F2 호출 ↓ Set() ↓ 대기 해제 ↓ 결과 반환

문제점

  • 대기 중인 스레드를 실제로 점유한다.
  • ThreadPool 자원을 낭비한다.
  • async/await 기반 설계와 잘 맞지 않는다.

현대적인 방식 : TaskCompletionSource

private TaskCompletionSource<int>? _resultTcs;

private Task<int> F1()
{
    _resultTcs = new TaskCompletionSource<int>();

    return _resultTcs.Task;
}

private void F2(int result)
{
    _resultTcs?.TrySetResult(result);
}

호출부

int result = await F1();

동작 과정

F1 호출 ↓ Task 생성 ↓ await 등록 ↓ 스레드 반환 ↓ ... ↓ F2 호출 ↓ TrySetResult() ↓ Task 완료 ↓ await 이후 코드 실행

실무 권장 패턴

실제 서비스 코드에서는 RunContinuationsAsynchronously 옵션을 함께 사용하는 것을 권장한다.

private TaskCompletionSource<int>? _resultTcs;

public Task<int> F1()
{
    _resultTcs =
        new TaskCompletionSource<int>(
            TaskCreationOptions.RunContinuationsAsynchronously);

    return _resultTcs.Task;
}

왜 필요한가?

기본 TaskCompletionSource는 TrySetResult() 호출 시 await 이후 코드가 같은 스레드에서 즉시 실행될 수 있다.

int result = await F1();

DoSomething();
TrySetResult() ↓ DoSomething()

즉, 결과를 전달하려던 코드가 소비자 측 후속 로직까지 실행하게 될 수 있다.

RunContinuationsAsynchronously 적용 후

TrySetResult() ↓ Task 완료 ↓ Continuation Queue 등록 ↓ ThreadPool 실행
  • 예측 가능한 실행 흐름
  • 데드락 위험 감소
  • UI 응답성 향상
  • 생산자와 소비자 역할 분리

결론

ManualResetEvent는 스레드를 점유하는 블로킹 방식이다.

TaskCompletionSource는 스레드 점유 없이 결과 완료 시점을 외부에서 제어할 수 있으며, 현대적인 .NET 비동기 프로그래밍에 가장 적합한 시그널링 기법이다.

2026년 6월 2일 화요일

닷넷의 네이티브 AOT

C# .NET으로 프로그램을 개발하면 EXE이든 DLL이든 기본적으로 IL(Intermediate Language) 형태로 빌드된다.

이 IL 코드는 실행 시 JIT 컴파일러에 의해 기계어로 변환되는데, 문제는 IL 자체가 매우 높은 수준의 중간 언어라는 점이다.

따라서 ILSpy, dnSpy 같은 디컴파일러를 사용하면 상당 부분 원본 소스코드 수준으로 복원이 가능하다.

그동안 이러한 문제를 해결하기 위한 방법은 사실상 코드 난독화 정도가 전부였다.

하지만 .NET 8부터 Native AOT가 본격적으로 지원되면서 상황이 달라졌다.

Native AOT를 사용하면 IL이 아닌 네이티브 기계어로 직접 빌드할 수 있으며, 이를 통해 다음과 같은 장점을 얻을 수 있다.

- 실행 성능 향상
- 빠른 시작 속도
- 소스코드 노출 위험 감소
- 역공학 난이도 증가

물론 Reflection, 런타임 코드 생성 등의 기능 사용에 제약이 생기므로 개발 방식에도 변화가 필요하다.

개인적으로는 프로그램 전체를 Native AOT로 전환하기보다는, 보안이 중요한 핵심 비즈니스 로직만 별도의 DLL로 분리하여 Native AOT로 빌드하는 방식을 추천한다.

이 글에서는 Native AOT DLL을 만드는 방법을 정리해본다.



개발 환경

- .NET 9 이상 권장
- Visual Studio 2022
- Windows 11 SDK
- MSVC Build Tools



1. 프로젝트 생성

클래스 라이브러리 프로젝트를 생성한 후 csproj 파일을 다음과 같이 수정한다.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <PublishAot>true</PublishAot>
    <Platforms>x64</Platforms>
    <PlatformTarget>x64</PlatformTarget>
    <TrimMode>partial</TrimMode>
  </PropertyGroup>
</Project>

여기서 가장 중요한 옵션은 PublishAot 이다.

이 옵션을 활성화하면 Native AOT 빌드가 가능해진다.



2. 외부에서 호출할 함수 작성

예를 들어 문자열과 정수를 입력받고 정수를 반환하는 함수라면 다음과 같이 작성할 수 있다.

using System.Runtime.InteropServices;

public static class NativeExports
{
    [UnmanagedCallersOnly(EntryPoint = "CoreCode")]
    public static int CoreCode(IntPtr a_str, int a_value)
    {
        string? text = Marshal.PtrToStringAnsi(a_str);

        return a_value + (text?.Length ?? 0);
    }
}

UnmanagedCallersOnly 특성을 사용하면 함수가 Native DLL의 Export 함수처럼 동작하게 된다.



데이터 교환 시 주의사항

Native DLL 경계를 넘어 데이터를 주고받을 때는 가능한 한 원시 타입을 사용하는 것이 좋다.

예를 들면 다음과 같은 타입들이다.

int
bool
double
long
IntPtr

반면 다음과 같은 타입은 권장하지 않는다.

class
record
struct
List<T>
Dictionary<TKey,TValue>

물론 Marshal 처리를 통해 사용은 가능하지만, 메모리 관리와 버전 호환성 문제가 발생할 가능성이 높다.

복잡한 데이터를 전달해야 한다면 JSON 문자열을 사용하는 것이 가장 단순하고 안정적이다.

예를 들어

{
  "Id":100,
  "Name":"Kim",
  "Value":1234
}

형태로 JSON 문자열을 반환하고 호출 측에서 역직렬화하여 사용하는 방법을 추천한다.

특히 다른 언어(C++, Python, Delphi 등)와 연동할 경우 JSON 방식은 매우 높은 호환성을 제공한다.



3. 빌드 환경 준비

Native AOT는 내부적으로 네이티브 링커를 사용하기 때문에 C++ 빌드 도구가 필요하다.

Visual Studio Installer에서 다음 항목을 설치한다.

워크로드

- C++를 사용한 데스크톱 개발

개별 구성 요소

- MSVC v143 - VS 2022 C++ x64/x86 빌드 도구
- Windows 11 SDK

설치 후 다음 콘솔을 실행한다.

x64 Native Tools Command Prompt for VS 2022



4. Native AOT DLL 빌드

위 콘솔에서 다음 명령을 실행한다.

dotnet publish -c Release -r win-x64

빌드가 완료되면 publish 폴더에 Native DLL이 생성된다.



5. DLL 사용하기

생성된 DLL을 사용하는 프로젝트에 복사한 후 다음과 같이 P/Invoke로 연결한다.

[DllImport("MyTestAot.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
public static extern int CoreCode([MarshalAs(UnmanagedType.LPStr)] string a_str, int a_value);

이후 일반 함수처럼 호출할 수 있다.

int result = CoreCode("Hello", 100);



마무리

Native AOT는 단순히 실행 속도를 높이는 기술만은 아니다.

.NET 개발자가 C++로 전환하지 않고도 네이티브 바이너리를 생성할 수 있다는 점에서 매우 큰 의미를 가진다.

특히 다음과 같은 영역에 적용하면 효과적이다.

- 라이선스 검증
- 암호화 모듈
- 핵심 비즈니스 로직
- 영상 처리 알고리즘
- AI 후처리 로직
- 독자적인 계산 엔진

프로그램 전체를 Native AOT로 만드는 것보다, 보호가 필요한 핵심 모듈만 Native AOT DLL로 분리하는 것이 현실적인 활용 방법이라고 생각한다.

2026년 5월 18일 월요일

윈도우 wsl로 우분투 실행



1. WSL 기능 켜기
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart


2. 가상 머신 기능 켜기
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart


3. 재부팅


4. 우분투(20.04) 다운로드
Invoke-WebRequest -Uri https://aka.ms/wslubuntu2004 -OutFile Ubuntu.appx -UseBasicParsing

5. 우분투 설치
Add-AppxPackage .\Ubuntu.appx


6. 업데이트
wsl --update


재부팅 이후 프로그램에 보면 우분투가 설치되어 있음..
(sudo apt-get update)



[우분투 종료시 자동종료]

윈도우 실행창에 - notepad %userprofile%\.wslconfig  
명령으로 새 프로파일 ini파일을 만들고 다음을 기제한후 저장

[wsl2]
autoShutdownDelay=60


이후 파워쉘에서 다음명령으로 wsl 한번종료
wsl --shutdown




2026년 5월 17일 일요일

mssql에서 postgresql로 이관(마이그레이션)처리

postgresql은 강력한 mvcc를 지원하고 엔진과 관리툴이 철저히 분리되어 있다.

이 때문에 하드웨어에 의존적인 순수한 속도와 규율적인 제약을 탈피할 수 있고 

OS종속에서도 벗어날수 있으며 도커 컨테이너에도 자유롭게 올라가고 서버엔진과 

클라이언트를 분리해서 클라이언트쪽에서 관리작업 (백업,복구)을 진행할수 있는등

혁신적인 일이 벌어진다.  (어설픈 스크립트 백업이런거 아님) 또한 실시간 메모리 범위

변동이나 update나 insert시 RAM에서 우선처리하고 응답한후 스토리지에는 지연쓰기를 설정할 수 있는 레디스 db속도급 synchronous_commit = off 기능등 활용범위가 

다양하고 자료형도 그래프관련 자료나 기하학관련 AI에서 쓰이는 자료형이나 

json이나 xml처리등을할 수 있으며 지구상에서 sql표준을 가장 잘 구현하고 표준에 부합하는 

데이터베이스다. 이런 혁신적인 구조 때문에 차세대 db엔진으로 많은 사랑을 받고 있다.

그래서 운영하는 시스템도 mssql에서 postgresql로 마이그레이션을 진행하였다.


먼저 이관 순서는 다음과 같이 진행하여야 한다.  (한PC에서 두커넥션다 존재)


1. 기존 mssql자료에 접근할수 있는 로컬환경구축 (로그인과 알멩이가 같이 있어야함)

2. 새로운 postgresql에 접근할수 있는 로컬환경구축 (로그인과 껍데기 db준비)

3. sling으로  양쪽을 전부 접속해서 마이그레이션하는 명령실행

sling은 리눅스 기반이라 윈도우에서는 wsl을 이용하여 우분투를 설치한후 

명령을 실행하는 방식을 취해야한다.  명령줄은 다음과 같다.  (상세내역 아래참조)


./sling run --src-conn 'sqlserver://아이디:비밀번호@아이피:포트?database=DB명&encrypt=false&TrustServerCertificate=true' --src-stream 'dbo.*' --tgt-conn 'postgresql://아이디:비밀번호@아이피:포트/DB명?sslmode=disable' --tgt-object 'public.{stream_table}'



4. postgresql에 public 스키마로 백업된 데이터를 확인하고 

내 입맛에 맞게 테이블을 alter작업 (이부분이 시간이 가장 많이 걸림.. 뷰나 프로시저도 한번은 거쳐야하므로 이때 이관한다. 복원된 

테이블을 변경하고 인덱스도 다시 잡아줌 등등 sql파일에서 작업해서 처리할것)


5. 입맛대로 변경된 postgresql에서 db를 백업한다. (pg_dump 처리로 파일로 export)


6. 운영할 postgresql이 설치된 서버 pc에서 5번 파일로 restore 작업을 진행한다..




[마이그레이션 유의사항 - 리눅스에서 진행할것]



# 슬라잉(sling-cli) 최신 버전 다운로드 
wget https://github.com/slingdata-io/sling-cli/releases/latest/download/sling_linux_amd64.tar.gz 


# 3. 압축 풀기
tar -xzvf sling_linux_amd64.tar.gz


# 슬라잉을 이용하여 마이그레이션 작업
./sling run --src-conn 'sqlserver://아이디:비밀번호@아이피:포트?database=DB명&encrypt=false&TrustServerCertificate=true' --src-stream 'dbo.*' --tgt-conn 'postgresql://아이디:비밀번호@아이피:포트/DB명?sslmode=disable' --tgt-object 'public.{stream_table}'



2026년 5월 16일 토요일

postgresql 사용시 알아야할 내용



[시간관련처리]


현재시간    |    NOW() or CURRENT_TIMESTAMP    |    현재시간을 추출함

형식변환(날짜)    |    NOW()::date    |    '2026-04-17' 형태만 남김

형식변환(파싱)    |    '2024-05-12 08:30:00'::timestamp  |  문자열을 날자로변환

시간차이    |    EXTRACT(EPOCH FROM (후-전))/3600    |    

두시간사이 차이를 초로 반환해서 /3600을 하니까 시간단위로 바뀐것

시간더하기    |    NOW() + INTERNAL '9 MINUTE'    |    특정 시간의 합산처리

특정부분추출    |    EXTRACT(HOUR FROM NOW())    |    시간에서 지정값을 뽑음


※ 시간(timstamp)을 문자로 변환하는것은 TO_CHAR()  하나로 끝남

TO_CHAR(NOW(), 'YYYY-MM-DD HH24:MI:SS.MS')    |   지정 스타일로 변환


[기간조회 공식은 같이 먹힘]

WHERE (sb::timestamp <= pe:timestamp) AND (se::timestamp >= pb::timestamp)

포스트그리SQL만은 기간조회가 아예 함수로도 있음

WHERE (SB, SE) OVERLAPS (@PB, @PE)



[백업 및 복원] - 명령파일 pg_dump.exe 같은걸로 실행해야함


[백업]
pg_dump -h <원격서버_IP> -p <포트번호> -U <권한있는계정> -d <db_이름> -F c -b -v -f "저장할_파일명.dump"


[클린복원]
pg_restore -h <원격서버 IP> -p <포트번호> -U <권한있는계정> -d <db_이름> -c -v "파일명.dump"


테이블만 지정할때는 -t 옵션으로 지정해서 처리해야함..



[프로그래밍 방식] - DO $$로 시작해서  BEGIN .. END $$;로 감싸는 구조임

예를 들어 '2017-05-23 14:23:32' 라는 문자를 파싱해서 timestamp로 만들고
그걸 문자열 6시간과  int 변수인 초값으로 더하고 그 결과를 다시 초로 환산해서
임시테이블로 바꿔서 select하는 예시


do $$
declare
input_str text := '2017-05-23 14:23:32';
target_date timestamp;
result_seconds integer;
add_sec integer := 30;
begin
create temp table if not exists temp_tb (
ori_time text,
cvt_time timestamp,
calc_sec integer
);
truncate temp_tb;
--
target_date := input_str::timestamp;
target_date := target_date + interval '6 hours';
target_date := target_date + (add_sec || ' minutes')::interval;
result_seconds := extract(epoch from target_date)::integer;
--
insert into temp_tb (ori_time, cvt_time, calc_sec)
values (input_str, target_date, result_seconds);
end $$;
--
select ori_time, cvt_time, calc_sec from temp_tb limit 1;



[또는 with와 as의 임시 테이블을 이용하는 방법]


WITH
var_ttb AS (SELECT '2017-05-23 14:23:32' AS input_str, 30 AS add_sec),
cal_ttb AS (SELECT input_str, add_sec, input_str::timestamp + INTERVAL '6 hours' AS base_date FROM var_ttb),
res_ttb AS (SELECT input_str, base_date + (add_sec || ' minutes')::interval AS target_date
 FROM cal_ttb)

-- 

SELECT input_str AS ori_time, target_date AS cvt_time, EXTRACT(EPOCH FROM target_date)::integer AS calc_sec FROM res_ttb;




[접속한 db의 모든 테이블 목록보기]

SELECT * FROM information_schema.tables WHERE table_schema = 'public';


[자동증가 id]

자동증가 id의 경우 자료형을 integer로 해서 초기값과 증가값을 가지는 경우..
묻지도 따지지도 말고 serial 자료형으로 처음부터 등록해야함.
자동증감기라는 오브젝트가 아예 별도로 있어서 뒤로 당겨주는 역할을 별도로 하기때문에..

자동증가 당겨주기는
SELECT setval(pg_get_serial_sequence('mytable', 'id'), COALESCE(max(id), 0)) FROM mytable;
로 처리하면 되는데


자료형을 바꺼버리면 아이디가 새로 부여된다.. 굳이 아이디가 바껴도 상관없다면..
ALTER TABLE mytable DROP COLUMN id;
ALTER TABLE mytable ADD COLUMN id SERIAL PRIMARY KEY;
이렇게 할수도 있지만 id의 값이 바껴버림 (주의)



[pgAdmin4 에서 전체 테이블 create 스크립팅 처리]

데이터베이스 -> 백업 -> 형식(Plain) -> 데이터 옵션(객체종류: Only schemas)
-> 파일경로지정(*.sql) -> 백업버튼



[*.sql 파일처리]

*.sql에 대량의 insert문이나 혹은 내용에 select가 있어서 결과셋을 헤더가 있는 csv파일로
출력할때는 다음처럼 psql 명령을 이용한다.  원래는  콘솔창에서 접속해서 쿼리작업을 위한 용도
이지만 이렇게 처리해도됨.


psql -h 127.0.0.1 -p 5432 -U postgres -d parking_db -v ON_ERROR_STOP=1 -f "D:/input.sql"


이때 결과셋을 출력하는 내용이 input.sql 파일에 다음과 같이 있다면

\copy (SELECT user_no, user_name FROM myuser) TO 'D:/output.csv' WITH CSV HEADER ENCODING 'UTF-8';


실행결과셋이 csv로 출력된다.




[주기적인 청소]

os에 따라서 윈도우 작업 스케줄러 같은게 있음.
postgresql은 바이너리로 reindexdb가 외부실행이 가능하도록 되어 있다.
따라서 그 작업 스케쥴러에 배치파일을 만들어서 다음과 같이 일주일에 한번
예를들어 매주 수요일 새벽 2시 에 한번 다음과 같은 bat파일을 만들어놓고
실행하는것이 좋다.


reindexdb.exe -U [사용자명] -d [DB이름] --concurrently
vacuumdb.exe -U [사용자명] -d [DB이름] --analyze






2026년 5월 1일 금요일

대패삼겹 우유 리조또

대패삼겹 우유 리조또 (1인분 기준)

1. 재료 체크리스트

  • 메인: 대패삼겹살(150g), 우유(300~400ml), 찬밥(1공기)

  • 풍미: 체다치즈(1~2장), 고춧가루(0.5~1큰술), 양파(1/2개), 다진 마늘(1큰술)

  • 간: 간장(1큰술), 굴소스(0.5큰술), 후추 듬뿍


2. 조리 프로세스 (Execution Flow)

STEP 1: 고기 굽기 및 마이야르 반응

  • 달군 팬에 대패삼겹살을 먼저 굽습니다.

  • Tip: 고기가 바삭해질 정도로 구워야 기름(라드)이 충분히 나오고 식감이 좋습니다.
    다 구워진 고기는 따로 접시에 덜어둡니다.

STEP 2: 고추기름 베이스 만들기

  • 팬에 남은 삼겹살 기름에 다진 마늘과 양파를 넣고 볶습니다.

  • 양파가 투명해지면 고춧가루를 넣고 타지 않게 30초 정도만 살짝 볶아 매콤한 향을 입힙니다. 

STEP 3: 리조또 빌드업

  • 우유를 붓고 끓어오르면 찬밥을 넣습니다.

  • 밥알이 우유를 머금어 몽글몽글해질 때까지 중불에서 저어가며 끓입니다.

STEP 4: 간 맞추기 및 치즈 투입

  • 간장과 굴소스를 넣어 기본 간을 합니다.

  • 소스가 어느 정도 꾸덕해지면 체다치즈 1~2장을 넣습니다.
    치즈가 녹으면서 농도가 급격히 진해지니 이때 농도를 잘 체크

STEP 5: 병합 및 마무리

  • 덜어두었던 구운 대패삼겹살을 넣고 가볍게 섞습니다.

  • 그릇에 담은 뒤 후추를 팍팍 뿌려 완성.