2023년 12월 8일 금요일

Avalonia ImageButton source code

 github link : https://github.com/kim-dong-hyun/AvaloniaImageButton








2023년 11월 24일 금요일

Rendering Video in Avalonia's NativeControlHost with SDL2.

 Add a NativeEmbeddingControl class that inherits from NativeControlHost in MainWindow.axaml.cs

 namespace AvaloniaDXPlayer  
 {  
   public partial class MainWindow : Window  
   {  
     public MainWindow()  
     {  
       InitializeComponent();  
     }  
     ...  
   public class NativeEmbeddingControl : NativeControlHost  
   {  
     public IntPtr Handle { get; private set; }  
   
     protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)  
     {  
       var handle = base.CreateNativeControlCore(parent);  
       Handle = handle.Handle;  
       Console.WriteLine($"Handle : {Handle}");  
       return handle;  
     }  
   }     
 }  


Add a NativeEmbeddingControl class that inherits from NativeControlHost in MainWindow.axaml.cs

 <Window xmlns="https://github.com/avaloniaui"  
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
     xmlns:nec="clr-namespace:AvaloniaDXPlayer"  
     mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="600"  
     x:Class="AvaloniaDXPlayer.MainWindow"  
     Width="800" Height="600" Title="AvaloniaDXPlayer">  
  <Grid>  
   <Grid.RowDefinitions>  
    <RowDefinition Height="*"/>  
    <RowDefinition Height="100"/>  
   </Grid.RowDefinitions>  
   <Grid Grid.Row="0" Background="Black">  
    <nec:NativeEmbeddingControl x:Name="host" Grid.Row="0" SizeChanged="Host_SizeChanged" />  
   </Grid>  
   <Canvas Grid.Row="1">  
    <TextBox x:Name="textURL" Margin="10" Width="300" Height="20" FontSize="14" Text=""/>  
    <Button x:Name="btnConnect" Content="Connect" Margin="320,10,0,0" />  
    <Button x:Name="btnClose" Content="Close" Margin="400,10,0,0" />  
    <Button x:Name="btnTest" Content="Test" Margin="460,10,0,0" />  
   </Canvas>  
  </Grid>  
 </Window>  
   


Initialize SDL2. During this, insert the Handle of NativeEmbeddingControl as an argument into SDL_CreateWindowFrom

 
#if defined(LINUX)
typedef void*              HWND;  
#endif
 ...  
 int VideoSDLDraw::init(HWND hwnd, int targetWidth, int targetHeight, int srcWidth, int srcHeight)  
 {  
      release();  
   
      DXPRINTF("VideoSDLDraw::init %dx%d, %dx%d\n", targetWidth, targetHeight, srcWidth, srcHeight);  
   
      MUTEX_LOCK(&m_mutex);  
   
      Uint32 pixelFormat = SDL_PIXELFORMAT_UNKNOWN;  
   
      m_pWindow = SDL_CreateWindowFrom(hwnd);  
      if (m_pWindow == NULL) {  
           DXPRINTF("Window could not be created! SDL Error: %s\n", SDL_GetError());  
           goto fail;  
      }  
   
      m_pRenderer = SDL_CreateRenderer(m_pWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);  
      if (m_pRenderer == NULL) {  
           DXPRINTF("Renderer could not be created! SDL Error: %s\n", SDL_GetError());  
           m_nTextureWidth = m_nTextureHeight = 0;  
           goto fail;  
      }  
      else {  
           //Initialize renderer color  
           SDL_SetRenderDrawColor(m_pRenderer, 0x00, 0x00, 0x00, 0xFF);  
           for (int i = 0; i < m_nTextDraw; i++) m_pTextDraw[i]->setSDLRenderer(m_pRenderer);  
      }  
 #if defined(LINUX)  
      if (targetWidth == 0 && targetHeight == 0) {  
           SDL_GetWindowSize(m_pWindow, &targetWidth, &targetHeight);  
      }  
 #endif  
      pixelFormat = SDL_GetWindowPixelFormat(m_pWindow);  
   
      m_pTexture = SDL_CreateTexture(m_pRenderer, pixelFormat, SDL_TEXTUREACCESS_STREAMING, targetWidth, targetHeight);  
      if (m_pTexture == NULL) {  
           DXPRINTF("Unable to create streamable texture! SDL Error: %s\n", SDL_GetError());  
           m_nTextureWidth = m_nTextureHeight = 0;  
           goto fail;  
      }  
   
      m_nTextureWidth = targetWidth;  
      m_nTextureHeight = targetHeight;  
   
      SetRectEmpty(&m_rectTargetLast);  
   
      SDL_RendererInfo rendererInfo;  
      SDL_GetRendererInfo(m_pRenderer, &rendererInfo);  
   
      DXPRINTF("SDL Renderer : %s\n", rendererInfo.name);  
   
      MUTEX_UNLOCK(&m_mutex);  
      return 0;  
   
 fail:  
      MUTEX_UNLOCK(&m_mutex);  
      return -1;  
 }  

After initialization as described above, render the decoded video data on m_pTexture with SDL2 rendering function. 
The result is as shown in the figure below.

Linux







Windows





2023년 11월 6일 월요일

2가지 예제를 통한 간단한 WPF 바인딩 원리


바인딩의 기본 개념은 위 그림과 같다. 
바인딩 타겟은 항상 DependencyProperty 여야하고 바인딩 소스는 DependencyProperty 나 일반 프로퍼티(.NET 프로퍼티)나 상관없다.
여기서

OneWay 바인딩은 소스 -> 타겟 
OneWayToSource 바인딩은 소스 <- 타겟
TwoWay 바인딩은 소스 <-> 타겟

으로 바인딩 된다.


1. TextBox 바인딩

화면을 위아래로 나눠서 상단에 텍스트 박스를 하단에 버튼 1개을 나타낸 간단한 UI를 만든다.

 <Window x:Class="TextBoxBind.MainWindow"  
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
     xmlns:local="clr-namespace:TextBoxBind"  
     mc:Ignorable="d"  
     Title="MainWindow" Height="450" Width="800">  
   <Grid>  
     <Grid.RowDefinitions>  
       <RowDefinition Height="50*"/>  
       <RowDefinition Height="50*"/>  
     </Grid.RowDefinitions>  
     <TextBox Grid.Row="0" Text="{Binding InputText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />  
     <Button x:Name="button" Content="Add Text" HorizontalAlignment="Left" Margin="42,29,0,0" Grid.Row="1" VerticalAlignment="Top" Click="button_Click"/>  
   </Grid>  
 </Window>  

위 xaml 코드에서 TextBox는 InputText 프로퍼티와 바인딩을 하며 Mode는 TwoWay, UpdateSourceTrigger는 PropertyChanged 이다.
즉 양방향 바인딩이고 데이터 변환시 즉시 반영되는 바인딩이다.
code behind 는 아래와 같다.

 using System;  
 using System.Collections.Generic;  
 using System.ComponentModel;  
 using System.Linq;  
 using System.Runtime.CompilerServices;  
 using System.Text;  
 using System.Threading;  
 using System.Threading.Tasks;  
 using System.Windows;  
 using System.Windows.Controls;  
 using System.Windows.Data;  
 using System.Windows.Documents;  
 using System.Windows.Input;  
 using System.Windows.Media;  
 using System.Windows.Media.Imaging;  
 using System.Windows.Navigation;  
 using System.Windows.Shapes;  
   
 namespace TextBoxBind  
 {  
   /// <summary>  
   /// MainWindow.xaml에 대한 상호 작용 논리  
   /// </summary>  
   public partial class MainWindow : Window, INotifyPropertyChanged  
   {  
     private string inputText;  
   
     public string InputText  
     {  
       get { return inputText; }  
       set  
       {  
         if (inputText != value)  
         {  
           Console.WriteLine($"InputText : {value}");  
           inputText = value;  
           OnPropertyChanged("InputText");
         }  
       }  
     }  
   
     public MainWindow()  
     {  
       InitializeComponent();  
       DataContext = this;  
     }  
   
     public event PropertyChangedEventHandler PropertyChanged;  
   
     protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)  
     {  
       PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));  
     }  
   
     private void button_Click(object sender, RoutedEventArgs e)  
     {  
       Thread thread = new Thread(new ThreadStart(() => InputText += "AAA"));  
       thread.IsBackground = true;  
       thread.Start();  
     }  
   }  
 }  
   

Add Text 버튼클릭시 쓰레드로 InputText 프로퍼티의 값을 추가한다.
양방향 바인딩이므로 TextBox에 문자를 입력하면 InputText 값이 바뀌고 버튼을 클릭하면 TextBox 출력문자가 변경된다.('AAA'추가)

여기서 InputText의 setter 부분에 있는 OnPropertyChanged("InputText") 
를 주석처리하면 버튼을 클릭해도 TextBox에 수정내용 즉 InputText 값이 반영되지 않는다.

이유는 바인딩을 하는 순간 TextBox는 
public event PropertyChangedEventHandler PropertyChanged; 
델러게이트 이벤트 핸들러를 구독하게되고 이벤트 발생시 InputText의 getter를 통해 현재 값을 가져와서 UI에 반영(Text 프로퍼티에)하기 때문이다.

Mode=OneWayToSource 를 하면 바인딩 타겟(TextBox) -> 바인딩 소스(InputText) 로만 데이터 전달이 되는데 이 경우는 TextBox가 바로 InputText의 setter 를 호출하게 되는데 이때는 OnPropertyChanged("InputText") 호출이 필요없다.(다른 이유에서 필요할 수 있으므로 호출하는것이 좋다)


2. DataGrid 바인딩

화면을 좌우로 나눠서 왼쪽에 DataGrid 를 오른쪽에 버튼과 기타 텍스트박스 컨트롤을 배치했다.

 <Window x:Class="ListBindTest.MainWindow"  
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
     xmlns:local="clr-namespace:ListBindTest"  
     mc:Ignorable="d"  
     Title="MainWindow" Height="450" Width="800">  
   <Grid>  
     <Grid.ColumnDefinitions>  
       <ColumnDefinition Width="*"/>  
       <ColumnDefinition Width="*"/>  
     </Grid.ColumnDefinitions>  
     <DataGrid Grid.Column="0" ItemsSource="{Binding People}" SelectedItem="{Binding SelectedItem}" SelectionMode="Single" />  
     <Canvas Grid.Column="1">  
       <TextBlock x:Name="textBlock" Canvas.Left="41" TextWrapping="Wrap" Text="Name" Canvas.Top="35"/>  
       <TextBlock x:Name="textBlock_Copy" Canvas.Left="41" TextWrapping="Wrap" Text="Age" Canvas.Top="87" HorizontalAlignment="Center" VerticalAlignment="Top"/>  
       <TextBox x:Name="textName" Canvas.Left="101" Text="{Binding SelectedItem.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Canvas.Top="35" Width="120"/>  
       <TextBox x:Name="textAge" Canvas.Left="101" Text="{Binding SelectedItem.Age, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Canvas.Top="87" Width="120" HorizontalAlignment="Center" VerticalAlignment="Top"/>  
       <TextBlock x:Name="textBlock_Copy1" Canvas.Left="41" TextWrapping="Wrap" Text="Name" Canvas.Top="183" HorizontalAlignment="Center" VerticalAlignment="Top"/>  
       <TextBlock x:Name="textBlock_Copy2" Canvas.Left="41" TextWrapping="Wrap" Text="Age" Canvas.Top="235" HorizontalAlignment="Center" VerticalAlignment="Top"/>  
       <Canvas x:Name="canvasTest">  
         <TextBox x:Name="textName_Copy" Canvas.Left="101" Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Canvas.Top="183" Width="120" HorizontalAlignment="Center" VerticalAlignment="Top"/>  
         <TextBox x:Name="textAge_Copy" Canvas.Left="101" Text="{Binding Age, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Canvas.Top="235" Width="120" HorizontalAlignment="Center" VerticalAlignment="Top"/>  
       </Canvas>  
       <Button x:Name="button" Content="button" Canvas.Left="22" Canvas.Top="297" Click="button_Click"/>  
       <Button x:Name="button1" Content="button1" Canvas.Left="78" Canvas.Top="297" Click="button1_Click"/>  
     </Canvas>  
   </Grid>  
 </Window>     

code behind 는 아래와 같다.
 using System;  
 using System.Collections.Generic;  
 using System.Collections.ObjectModel;  
 using System.ComponentModel;  
 using System.Linq;  
 using System.Runtime.InteropServices;  
 using System.Text;  
 using System.Threading;  
 using System.Threading.Tasks;  
 using System.Windows;  
 using System.Windows.Controls;  
 using System.Windows.Data;  
 using System.Windows.Documents;  
 using System.Windows.Input;  
 using System.Windows.Media;  
 using System.Windows.Media.Imaging;  
 using System.Windows.Navigation;  
 using System.Windows.Shapes;  
   
 namespace ListBindTest  
 {  
   /// <summary>  
   /// MainWindow.xaml에 대한 상호 작용 논리  
   /// </summary>  
   public partial class MainWindow : Window  
   {  
     private MainViewModel viewModel = new MainViewModel();  
     private Person personTest = new Person();  
   
     public MainWindow()  
     {  
       InitializeComponent();  
   
       DataContext = viewModel;  
   
       personTest.Name = "Test";  
       personTest.Age = 100;  
       canvasTest.DataContext = personTest;  
     }  
   
     private void button_Click(object sender, RoutedEventArgs e)  
     {  
       Person p = new Person();  
       p.Name = personTest.Name;  
       p.Age = personTest.Age;  
   
       Thread thread = new Thread(new ThreadStart(() =>  
       {  
         this.Dispatcher.BeginInvoke(new Action(() =>  
         {  
           viewModel.People.Add(p);
         }));  
       }));  
       thread.IsBackground = true;  
       thread.Start();  
     }  
   
     private void button1_Click(object sender, RoutedEventArgs e)  
     {  
       Thread thread = new Thread(new ThreadStart(() =>  
       {  
         Person p = viewModel.SelectedItem;  
         if (p != null) p.Age++;  
       }));  
       thread.IsBackground = true;  
       thread.Start();  
     }  
   }  
   
  public class Person : INotifyPropertyChanged  
   {  
     private string _name;  
     private int _age;  
   
     public string Name  
     {  
       get { return _name; }  
       set  
       {  
         _name = value;  
         OnPropertyChanged(nameof(Name));  
       }  
     }  
   
     public int Age  
     {  
       get { return _age; }  
       set  
       {  
         _age = value;  
         OnPropertyChanged(nameof(Age));  
       }  
     }  
   
     public event PropertyChangedEventHandler PropertyChanged;  
   
     protected virtual void OnPropertyChanged(string propertyName)  
     {  
       PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));  
     }  
   }  
   
   public class MainViewModel : INotifyPropertyChanged  
   {  
     private ObservableCollection<Person> _people;  
     private Person _selectedItem;  
   
     public MainViewModel()  
     {  
       _people = new ObservableCollection<Person>();  
       _people.Add(new Person { Name = "Alice", Age = 25 });  
       _people.Add(new Person { Name = "Bob", Age = 30 });  
       _people.Add(new Person { Name = "Charlie", Age = 35 });  
     }  
   
     public ObservableCollection<Person> People  
     {  
       get { return _people; }  
       set  
       {  
         _people = value;  
         OnPropertyChanged(nameof(People));  
       }  
     }  
   
     public Person SelectedItem  
     {  
       get { return _selectedItem; }  
       set  
       {  
         _selectedItem = value;  
         OnPropertyChanged(nameof(SelectedItem));  
       }  
     }  
   
     public event PropertyChangedEventHandler PropertyChanged;  
   
     protected virtual void OnPropertyChanged(string propertyName)  
     {  
       PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));  
     }  
   }  
 }  
   

button을 클릭하면 DataGrid 에 아이템이 추가되는데 여기서 주의해야할 점은 바인딩된 ObservableCollection 에 아이템을 추가할때 반드시 UI 쓰레드에서 추가해야한다는 점이다.

UI 쓰레드로 추가하지 않으려면 BindingOperations.EnableCollectionSynchronization 메소드를 사용하는 방법이 있다.

 namespace ListBindTest  
 {  
   /// <summary>  
   /// MainWindow.xaml에 대한 상호 작용 논리  
   /// </summary>  
   public partial class MainWindow : Window  
   {  
     private MainViewModel viewModel = new MainViewModel();  
     private Person personTest = new Person();  
   
     public MainWindow()  
     {  
       InitializeComponent();  
   
       DataContext = viewModel;  
   
       personTest.Name = "Test";  
       personTest.Age = 100;  
       canvasTest.DataContext = personTest;  
     }  
   
     private void button_Click(object sender, RoutedEventArgs e)  
     {  
       Person p = new Person();  
       p.Name = personTest.Name;  
       p.Age = personTest.Age;  
   
       Thread thread = new Thread(new ThreadStart(() =>  
       {  
 #if false  
         this.Dispatcher.BeginInvoke(new Action(() =>  
         {  
           viewModel.People.Add(p);  
           //viewModel.People.Add(personTest);  
         }));  
 #else  
         viewModel.People.Add(p);  
 #endif  
       }));  
       thread.IsBackground = true;  
       thread.Start();        
     }  
   
     private void button1_Click(object sender, RoutedEventArgs e)  
     {  
       Thread thread = new Thread(new ThreadStart(() =>  
       {  
         //personTest.Name = personTest.Name + "X";  
         Person p = viewModel.SelectedItem;  
         if (p != null) p.Age++;  
       }));  
       thread.IsBackground = true;  
       thread.Start();  
     }  
   }  
   
  public class Person : INotifyPropertyChanged  
   {  
     private string _name;  
     private int _age;  
   
     public string Name  
     {  
       get { return _name; }  
       set  
       {  
         _name = value;  
         OnPropertyChanged(nameof(Name));  
       }  
     }  
   
     public int Age  
     {  
       get { return _age; }  
       set  
       {  
         _age = value;  
         OnPropertyChanged(nameof(Age));  
       }  
     }  
   
     public event PropertyChangedEventHandler PropertyChanged;  
   
     protected virtual void OnPropertyChanged(string propertyName)  
     {  
       PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));  
     }  
   }  
   
   public class MainViewModel : INotifyPropertyChanged  
   {  
     private ObservableCollection<Person> _people;  
     private Person _selectedItem;  
   
     private object objLock = new object();  
   
     public MainViewModel()  
     {  
       _people = new ObservableCollection<Person>();  
       _people.Add(new Person { Name = "Alice", Age = 25 });  
       _people.Add(new Person { Name = "Bob", Age = 30 });  
       _people.Add(new Person { Name = "Charlie", Age = 35 });  
   
       BindingOperations.EnableCollectionSynchronization(_people, objLock);  
     }  
   
     public ObservableCollection<Person> People  
     {  
       get { return _people; }  
       set  
       {  
         _people = value;  
         OnPropertyChanged(nameof(People));  
       }  
     }  
   
     public Person SelectedItem  
     {  
       get { return _selectedItem; }  
       set  
       {  
         _selectedItem = value;  
         OnPropertyChanged(nameof(SelectedItem));  
       }  
     }  
   
     public event PropertyChangedEventHandler PropertyChanged;  
   
     protected virtual void OnPropertyChanged(string propertyName)  
     {  
       PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));  
     }  
   }  
 }  

그리고 button1을 클릭하면 Age 값이 1증가하는데 이것은 Person 클래스가 INotifyPropertyChanged 를 구현해서 OnPropertyChanged(nameof(Age)) 를 호출하여 역시 1번 예제에서 설명한데로 public event PropertyChangedEventHandler PropertyChanged 를 구독중인 DataGrid 에게 변경여부를 알려주기 때문이다.

2023년 11월 1일 수요일

간단한 CA(Certificate Authority) 인증서 검증 원리

 https 등에서 인증서를 검증하는 원리에 대해 이해하려면 공개키/비밀키(비대칭키) 암호화 원리에 대해 간략하게 이해를 해야한다.

먼저 전자서명과 동작원리는 아래 그림을 보면 간단하게 이해할 수 있다.




위 그림에서 기존의 비대칭키 암호화 과정과 다른점은 암호화-복호화 과정이 공개키-비밀키가 아닌 비밀키-공개키가 사용된다는 것이다. 
이것은 평문을 복호화하는것이 아닌 평문의 지문(해쉬값)을 복호화하기 때문이다.(설명이 조금 부족한데 그림으로 이해하도록)

https 로 웹사이트 접속 후 해당 웹사이트로부터 인증서를 수신하고 이것을 검증하여 해당 사이트가 신뢰할 수 있는지 검증하는 방법은 다음과 같다.

먼저 웹사이트는 CA(인증기관)에 인증서 발급을 신청한다. CA는 해당 웹사이트를 검증한 후 신뢰할만하다고 판단되면
인증서를 발급하는데 다음의 과정을 거친다.(전자서명 원리와 같다)

1. 인증서 내용을 해쉬하여 지문을 생성한다.
2. 생성된 지문을 자신의 비밀키로 암호화하여 전자서명값을 생성한다.
3. 인증서 파일 내부에 인증서 내용, 지문, 전자서명값을 넣은 후 웹사이트에 전달한다.

이렇게 생성된 인증서는 아래 그림과 같이 구성된다.



여기서 인증서 내용은 대략 아래와 같다.

(1) 인증서의 소유자 이름, 
(2) 인증서 소유자의 공개 키 (당연히 비밀 키는 소유자가 가지고 있다), 
(3) 인증서의 유효 기간, 
(4) 고유한 UID
(5) 인증서의 기타 모든 값들을 해시화한 값 => 지문


여기서 가장 중요한 것이 (5)번 "지문"이다. 이 지문을 통해 전자서명을 검증하는것이다.



브라우저는 웹사이트로부터 인증서를 전달받으면 내장되어 있는 CA 기관의 공개키를 가지고 전자서명값을 복호화한 후 이것을 인증서 내용의 해쉬값(지문)과 비교하여 같으면 CA 기관이 인증한 것이라고 인식하는 것이다.

인증서가 검증이 되면 인증서 내용에 포함된 웹사이트의 공개키를 신뢰할 수 있으므로 이 공개키로 https 접속을 진행한다.

2023년 10월 30일 월요일

내가 이해한 Dependency Property

구글링을 하면 WPF의 Dependency Property에 대한 자료가 많이 나온다. 
차근차근 읽어보면 대략적으로 이해가 가는데 이것을 설명을 하자면 조금 답답한 감이 있다보니 개념을 나름데로 정리해봤다.

Dependency Property 를 간단하게 코드로 표현하면 아래와 같다.

public partial class ColorPickerCtrl : UserControl
{
...
public Brush SelectedColor
{
    get { return (Brush)GetValue(SelectedColorProperty); }
    set { SetValue(SelectedColorProperty, value); }
}
public static DependencyProperty SelectedColorProperty =
         DependencyProperty.Register("SelectedColor", typeof(Brush), typeof(ColorPickerCtrl), new UIPropertyMetadata(null));
...  
}

여기서 ColorPickerCtrl은 DependencyObject를 상속받는 클래스이며 Dependency Property를 소유하려면(has-a)
반드시 DependencyObject 를 상속받아야 한다.
찾아보면 Dependency Property 를 사용하는 이유는 아래와 같다.

메모리 절약, 값 상속, 변경값 noti, 기타(데이터 바인딩, 스타일 적용, 리소스 적용, 에니메이션 등등)

여기서부터 본인이 공부하는 입장이 아니라 WPF의 설계자(앤더스 헤일즈버그?)라고 가정하고 어떻게 구현할지 생각해보자.

위 기능들을 구현하려면 일반적인 프로퍼티(CLR Property)로는 구현이 불가능하다. 
프로퍼티이면서 일종의 "동적으로 관리되는" 프로퍼티라는 개념이 필요하다. 
"동적으로 관리된다"는 것의 의미는 컴파일 타임에 프로퍼티가 결정되는 것이 아니라 런타임에 프로퍼티가 결정된다는 것을 의미한다.

프로퍼티를 관리하려면 해당 프로퍼티에 대한 정보, 좀 더 구체적으로 말하면 프로퍼티의 메타 데이터가 필요하다.
그리고 이 프로퍼티 메타 데이터를 저장할 저장공간도 필요하다. 프로퍼티 메타 데이터를 프로퍼티 메타 데이터 저장소에 저장하려면 프로그램 실행 초기이든 실행 중간이든 programmatic하게 등록(저장)하는 과정이 당연히 필요하다.

위 코드에서 DependencyProperty.Register static 메소드가 이 역할을 하는 것이다.
SelectedColor 라는 객체(클래스를 인스턴스화 한것)의 프로퍼티에 대한 메타정보, 즉 SelectedColorProperty는 따라서 당연히 static이 되어야한다. 왜냐면 프로퍼티 메타정보는 인스턴스화 한 객체의 정보가 아니라 클래스의 정보이기 때문이다.

그리고 SelectedColor 프로퍼티의 get/set 은 일반 프로퍼티처럼 고정된 특정 변수를 엑세스하는것이 아니라 역시 programmatic하게 구현되어야 한다. 왜냐하면 SelectedColor의 메타데이터 즉, SelectedColorProperty가 programmatic하게 구현되었기 때문이다.
따라서 GetValue/SetValue 가 사용되는 것이다.

2023년 8월 23일 수요일

밑바닥부터 시작하는 딥러닝 4장 simpleNet 클래스와 numerical_gradient 함수 분석

아래 소스에서 빨간색 사각형 부분을 보면



























람다 함수 f는 인수로 w를 받는데 실제로 사용을 하지않는다.
그리고 이 f 함수를 numerical_gradient 함수에 인수로 넘긴다.

그리고 numerical_gradient 함수를 보면























위와 같이 f(x) 를 호출하는데 f는 위에서 언급한 인수를 사용하지않는 람다 함수이므로
f(x) 를 호출해도 x 값은 사용되지않는다.

그러면 실제 numerical_gradient 함수는 어떻게 해서 편미분값 grad 를 구하는가를 따라가보면





























f 호출 -> simpleNet.loss 호출 -> simpleNet.predict 호출로 진행되는데
predict 함수에서 입력변수 x([0.6, 0.9])와 가중값 self.W 를 행렬곱을 한다.
여기서 self.W 는 numerical_gradient 함수에 인수로 넘긴 값인데 위에 위에 그림에서
numerical_gradient 함수의 인수 x에 해당한다.
파이썬에서 넘파이 배열은 call by reference 이므로 
x[idx] = float(tmp_val) + h
x[idx] = tmp_val - h 
와 같이 x[idx] 에 값을 대입하면 simpleNet 클래스의 인스턴스(net)의 멤버변수 W가 변경된다.

결국 

x[idx] = float(tmp_val) + h => W[idx] = float(tmp_val) + h
x[idx] = tmp_val - h => W[idx] = tmp_val - h

이므로 W에 h가 적용된 변환값이 들어가서 정상적으로 계산할 수 있다.


2023년 3월 14일 화요일

C# UI 쓰레드 새로 생성해서 폼 띄우기/닫기

 UI 쓰레드를 새로 생성해서 폼을 Show 하고 Close 하는 소스코드


   public partial class MainForm : Form  
   {  
     private static KeyboardForm keyboardForm = null;      
   
     private static void ThreadKeyboard()  
     {  
       keyboardForm = new KeyboardForm();  
   
       Application.Run(keyboardForm);  
       keyboardForm = null;  
     }  
   
     public static void OpenKeyboard()  
     {  
       if (keyboardForm != null) return;  
   
       Thread newThread = new Thread(new ThreadStart(ThreadKeyboard));  
       newThread.SetApartmentState(ApartmentState.STA);  
       newThread.Start();  
     }  
   
     public static void CloseKeyboard()   
     {  
       if (keyboardForm != null)  
       {  
         keyboardForm.BeginInvoke(new Action(() => { keyboardForm.Close(); }));  
       }  
     }  

2023년 3월 13일 월요일

automatic quick format command

입력 프롬프트 없이 한번에 퀵포맷하는 cmd 명령

format <드라이브명>: /FS:NTFS /Y /V:<볼륨이름> /Q

예) format d: /FS:NTFS /Y /V:Storage1 /Q

2023년 2월 23일 목요일

버추얼박스 느려졌을때 설정 변경

관리자 권한으로 cmd 실행 후 아래 실행 후 재부팅 

bcdedit /set hypervisorlaunchtype off 

2023년 1월 25일 수요일

VC++ Unicode 와 MBSC 겸용으로 빌드시 MultiByteToWideChar/WideCharToMultiByte 함수 처리(리눅스 포함)

 VC++에서 Unicode/MBCS(Multi-Byte Character Set) 겸용으로 빌드시에 MultiByteToWideChar/WideCharToMultiByte 함수를 아래와 같이 처리하면 문제없이 빌드가 가능하다. 윈도우가 아닌 경우(리눅스 등)에도 빌드 처리가 된다.


#ifdef WIN32
#include <tchar.h>
#endif

#ifdef WIN32
#define STRNCPY _tcsncpy
#define STRLEN _tcslen
#ifdef UNICODE
#define MULTIBYTETOWIDECHAR MultiByteToWideChar
#define WIDECHARTOMULTIBYTE WideCharToMultiByte
#define SNPRINTF swprintf
#define SSCANF swscanf
#else
#define MULTIBYTETOWIDECHAR(CodePage, dwFlags, lpMultiByteStr, cbMultiByte, lpWideCharStr, cchWideChar) \
_snprintf(lpWideCharStr, cchWideChar, "%s", lpMultiByteStr)

#define WIDECHARTOMULTIBYTE(CodePage, dwFlags, lpWideCharStr, cchWideChar, lpMultiByteStr, cbMultiByte, lpDefaultChar, lpUsedDefaultChar) \
_snprintf(lpMultiByteStr, cbMultiByte, "%s", lpWideCharStr)

#define SNPRINTF _snprintf
#define SSCANF sscanf
#endif
#else
#define MULTIBYTETOWIDECHAR(CodePage, dwFlags, lpMultiByteStr, cbMultiByte, lpWideCharStr, cchWideChar) \
snprintf(lpWideCharStr, cchWideChar, "%s", lpMultiByteStr)

#define WIDECHARTOMULTIBYTE(CodePage, dwFlags, lpWideCharStr, cchWideChar, lpMultiByteStr, cbMultiByte, lpDefaultChar, lpUsedDefaultChar) \
snprintf(lpMultiByteStr, cbMultiByte, "%s", lpWideCharStr)

#define STRNCPY strncpy
#define STRLEN strlen
#define SNPRINTF snprintf
#define SSCANF sscanf

#define TCHAR char
#define TEXT(quote) quote
#define _T(quote) quote
#define __T(quote) quote
#endif

< 사용예 >
TCHAR filepathPrefix[260] = { 0 };
TCHAR fileExtension[260] = { 0 };

MULTIBYTETOWIDECHAR(CP_ACP, 0, m_szFilePathPrefix, sizeof(m_szFilePathPrefix), filepathPrefix, sizeof(filepathPrefix));

MULTIBYTETOWIDECHAR(CP_ACP, 0, m_szFileExtension, sizeof(m_szFileExtension), fileExtension, sizeof(fileExtension));

char filepathChar[256];
WIDECHARTOMULTIBYTE(CP_UTF8, 0, filepath, sizeof(filepath), filepathChar, sizeof(filepathChar), NULL, NULL);


2023년 1월 13일 금요일

vc++ 메모리 릭 감지

프로그램 시작 부분에 아래 코드 넣고 실행 후 정상종료하면 메모리릭 발생시 출력창에 메모리릭 출력 


#ifdef _DEBUG
#include <crtdbg.h>
#endif

int main()
{
#ifdef _DEBUG
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif

간단한 strtok_s 사용법

 원본 문자열을 ',' 로 분할하고 2차원 배열에 복사하는 소스이다.


#ifdef WIN32
#define strtok_r strtok_s    // 윈도우/리눅스 빌드
#endif

char m_szRecordPath[MAX_RECORD_PATH][MAX_PATH];
int m_nRecordPathCount;

void setRecordPaths(char *filepaths)
{
memset(&m_szRecordPath, 0, sizeof(m_szRecordPath));
m_nRecordPathCount = 0;
char *ret_ptr, *next_ptr;

ret_ptr = strtok_r(filepaths, ",", &next_ptr);
while (ret_ptr) {
memcpy(&m_szRecordPath[m_nRecordPathCount], ret_ptr, strlen(ret_ptr));
m_nRecordPathCount++;
ret_ptr = strtok_r(NULL, ",", &next_ptr);
}

for (int i=0; i<m_nRecordPathCount; i++)
printf("record path[%d] : %s\n", i, m_szRecordPath[i]);
}