github link : https://github.com/kim-dong-hyun/AvaloniaImageButton
2023년 12월 8일 금요일
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; }
Linux
Windows
2023년 10월 30일 월요일
내가 이해한 Dependency Property
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(); }));
}
}
2022년 7월 7일 목요일
C# 유용한 ini 파일 읽기/쓰기 라이브러리 소스코드
다운로드 : IniFile.cs
c# 에서 ini 파일 읽기/쓰기를 간편하게 할 수 있는 소스코드이다.
오랫동안 유용하게 사용했는데 링크를 잊어버렸다.
< 사용법 >
...
public void LoadConfig()
{
string myExeDir = (new System.IO.FileInfo(System.Reflection.Assembly.GetEntryAssembly().Location)).Directory.ToString();
IniFile ini = new IniFile(Path.Combine(myExeDir, "Config.ini"));
int value1 = ini.GetInt32("SectionName", "Value1", 0);
string value2 = ini.GetString("SectionName", "Value2", "default");
}
public void SaveConfig(int value1, string value2)
{
string myExeDir = (new System.IO.FileInfo(System.Reflection.Assembly.GetEntryAssembly().Location)).Directory.ToString();
IniFile ini = new IniFile(Path.Combine(myExeDir, "Config.ini"));
ini.WriteValue("SectionName", "Value1", value1);
ini.WriteValue("SectionName", "Value2", value2);
}
2021년 7월 20일 화요일
WPF WindowChrome Window Maximized 했을때 화면 잘림 현상 해결
Window 에 아래 코드 입력
private void Window_Loaded(object sender, RoutedEventArgs e)
{
IntPtr handle = (new WindowInteropHelper(this)).Handle;
HwndSource.FromHwnd(handle).AddHook(new HwndSourceHook(WindowProc));
}
private static IntPtr WindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case 0x10:
//Console.WriteLine("Close reason: Clicking X");
break;
case 0x11:
case 0x16:
//Console.WriteLine("Close reason: WindowsShutDown");
break;
case 0x0024:
WmGetMinMaxInfo(hwnd, lParam);
handled = true;
break;
case 0x0046:
NativeMethods.WINDOWPOS pos = (NativeMethods.WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(NativeMethods.WINDOWPOS));
if ((pos.flags & (int)(NativeMethods.SWP.NOMOVE)) != 0)
{
return IntPtr.Zero;
}
Window wnd = (Window)HwndSource.FromHwnd(hwnd).RootVisual;
if (wnd == null)
{
return IntPtr.Zero;
}
bool changedPos = false;
if (pos.cx < wnd.MinWidth) { pos.cx = (int)wnd.MinWidth; changedPos = true; }
if (pos.cy < wnd.MinHeight) { pos.cy = (int)wnd.MinHeight; changedPos = true; }
if (!changedPos)
{
return IntPtr.Zero;
}
Marshal.StructureToPtr(pos, lParam, true);
handled = true;
break;
case 0x112:
if (((ushort)wParam & 0xfff0) == 0xf060)
{
// close (alt + F4)
handled = true;
}
break;
default:
break;
}
return IntPtr.Zero;
}
private static void WmGetMinMaxInfo(System.IntPtr hwnd, System.IntPtr lParam)
{
NativeMethods.MINMAXINFO mmi = (NativeMethods.MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(NativeMethods.MINMAXINFO));
// Adjust the maximized size and position to fit the work area of the correct monitor
int MONITOR_DEFAULTTONEAREST = 0x00000002;
IntPtr monitor = NativeMethods.MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
if (monitor != System.IntPtr.Zero)
{
NativeMethods.MONITORINFO monitorInfo = new NativeMethods.MONITORINFO();
NativeMethods.GetMonitorInfo(monitor, monitorInfo);
NativeMethods.RECT rcWorkArea = monitorInfo.rcWork;
NativeMethods.RECT rcMonitorArea = monitorInfo.rcMonitor;
mmi.ptMaxPosition.x = Math.Abs(rcWorkArea.left - rcMonitorArea.left);
mmi.ptMaxPosition.y = Math.Abs(rcWorkArea.top - rcMonitorArea.top);
mmi.ptMaxSize.x = Math.Abs(rcWorkArea.right - rcWorkArea.left);
mmi.ptMaxSize.y = Math.Abs(rcWorkArea.bottom - rcWorkArea.top);
}
Marshal.StructureToPtr(mmi, lParam, true);
}
public class NativeMethods
{
[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
// Define the Win32 API methods we are going to use
[DllImport("user32.dll")]
internal static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
internal static extern bool InsertMenu(IntPtr hMenu, uint wPosition, uint wFlags, UIntPtr wIDNewItem, string lpNewItem);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
internal static extern int ModifyMenu(IntPtr hMenu, uint uPosition, uint wFlags, UIntPtr wIDNewItem, string text);
[DllImport("user32.dll")]
internal static extern bool DeleteMenu(IntPtr hMenu, uint uPosition, uint uFlags);
[DllImport("user32")]
public static extern bool GetMonitorInfo(IntPtr hMonitor, MONITORINFO lpmi);
[DllImport("User32")]
public static extern IntPtr MonitorFromWindow(IntPtr handle, int flags);
#region Win32 Imports
internal const UInt32 MF_BYCOMMAND = 0x00000000;
internal const UInt32 SC_SIZE = 0xF000;
internal const UInt32 SC_MOVE = 0xF010;
internal const UInt32 SC_MINIMIZE = 0xF020;
internal const UInt32 SC_MAXIMIZE = 0xF030;
internal const UInt32 SC_NEXTWINDOW = 0xF040;
internal const UInt32 SC_PREVWINDOW = 0xF050;
internal const UInt32 SC_CLOSE = 0xF060;
internal const UInt32 SC_VSCROLL = 0xF070;
internal const UInt32 SC_HSCROLL = 0xF080;
internal const UInt32 SC_MOUSEMENU = 0xF090;
internal const UInt32 SC_KEYMENU = 0xF100;
internal const UInt32 SC_ARRANGE = 0xF110;
internal const UInt32 SC_RESTORE = 0xF120;
internal const UInt32 SC_TASKLIST = 0xF130;
internal const UInt32 SC_SCREENSAVE = 0xF140;
internal const UInt32 SC_HOTKEY = 0xF150;
internal const UInt32 SC_DEFAULT = 0xF160;
internal const UInt32 SC_MONITORPOWER = 0xF170;
internal const UInt32 SC_CONTEXTHELP = 0xF180;
internal const UInt32 SC_SEPARATOR = 0xF00F;
/// Define our Constants we will use
public const Int32 WM_SYSCOMMAND = 0x112;
public const Int32 MF_SEPARATOR = 0x800;
public const Int32 MF_BYPOSITION = 0x400;
public const Int32 MF_STRING = 0x0;
public enum SWP : uint
{
NOSIZE = 0x0001,
NOMOVE = 0x0002,
NOZORDER = 0x0004,
NOREDRAW = 0x0008,
NOACTIVATE = 0x0010,
FRAMECHANGED = 0x0020,
SHOWWINDOW = 0x0040,
HIDEWINDOW = 0x0080,
NOCOPYBITS = 0x0100,
NOOWNERZORDER = 0x0200,
NOSENDCHANGING = 0x0400,
}
/// <summary>
/// POINT aka POINTAPI
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
/// <summary>
/// x coordinate of point.
/// </summary>
public int x;
/// <summary>
/// y coordinate of point.
/// </summary>
public int y;
/// <summary>
/// Construct a point of coordinates (x,y).
/// </summary>
public POINT(int x, int y)
{
this.x = x;
this.y = y;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct MINMAXINFO
{
public POINT ptReserved;
public POINT ptMaxSize;
public POINT ptMaxPosition;
public POINT ptMinTrackSize;
public POINT ptMaxTrackSize;
};
/// <summary>
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public class MONITORINFO
{
/// <summary>
/// </summary>
public int cbSize = Marshal.SizeOf(typeof(MONITORINFO));
/// <summary>
/// </summary>
public RECT rcMonitor = new RECT();
/// <summary>
/// </summary>
public RECT rcWork = new RECT();
/// <summary>
/// </summary>
public int dwFlags = 0;
}
[StructLayout(LayoutKind.Sequential)]
public struct WINDOWPOS
{
public IntPtr hwnd;
public IntPtr hwndInsertAfter;
public int x;
public int y;
public int cx;
public int cy;
public int flags;
}
/// <summary> Win32 </summary>
[StructLayout(LayoutKind.Sequential, Pack = 0)]
public struct RECT
{
/// <summary> Win32 </summary>
public int left;
/// <summary> Win32 </summary>
public int top;
/// <summary> Win32 </summary>
public int right;
/// <summary> Win32 </summary>
public int bottom;
/// <summary> Win32 </summary>
public static readonly RECT Empty = new RECT();
/// <summary> Win32 </summary>
public int Width
{
get { return Math.Abs(right - left); } // Abs needed for BIDI OS
}
/// <summary> Win32 </summary>
public int Height
{
get { return bottom - top; }
}
/// <summary> Win32 </summary>
public RECT(int left, int top, int right, int bottom)
{
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}
/// <summary> Win32 </summary>
public RECT(RECT rcSrc)
{
this.left = rcSrc.left;
this.top = rcSrc.top;
this.right = rcSrc.right;
this.bottom = rcSrc.bottom;
}
/// <summary> Win32 </summary>
public bool IsEmpty
{
get
{
// BUGBUG : On Bidi OS (hebrew arabic) left > right
return left >= right || top >= bottom;
}
}
/// <summary> Return a user friendly representation of this struct </summary>
public override string ToString()
{
if (this == RECT.Empty) { return "RECT {Empty}"; }
return "RECT { left : " + left + " / top : " + top + " / right : " + right + " / bottom : " + bottom + " }";
}
/// <summary> Determine if 2 RECT are equal (deep compare) </summary>
public override bool Equals(object obj)
{
if (!(obj is Rect)) { return false; }
return (this == (RECT)obj);
}
/// <summary>Return the HashCode for this struct (not garanteed to be unique)</summary>
public override int GetHashCode()
{
return left.GetHashCode() + top.GetHashCode() + right.GetHashCode() + bottom.GetHashCode();
}
/// <summary> Determine if 2 RECT are equal (deep compare)</summary>
public static bool operator ==(RECT rect1, RECT rect2)
{
return (rect1.left == rect2.left && rect1.top == rect2.top && rect1.right == rect2.right && rect1.bottom == rect2.bottom);
}
/// <summary> Determine if 2 RECT are different(deep compare)</summary>
public static bool operator !=(RECT rect1, RECT rect2)
{
return !(rect1 == rect2);
}
}
#endregion
}
2021년 7월 15일 목요일
c# convert short/int value to byte array in bigendian
short val = 0x1234;
sendData[0] = (byte)(val >> 8);
sendData[1] = (byte)val;
int val2 = 0x12345678;
sendData[0] = (byte)(val2 >> 24);
sendData[1] = (byte)(val2 >> 16);
sendData[2] = (byte)(val2 >> 8);
sendData[3] = (byte)val2;
2021년 6월 24일 목요일
C# 라디오버튼/체크박스 이미지 버튼 처리
1. Resources.resx 를 열어서 "리소스 추가"->"기존 파일 추가" 해서 .png/.jpg 등 이미지 파일을 추가한다.
예) check.png, uncheck.png
2. 디자이너에서 라디오버튼 or 체크박스 속성중
Appearance->Button 선택
AutoSize False 선택
BackgroundImage 클릭해서 1번에서 추가한 이미지 선택(프로젝트 리소스 파일)
BackgroundImageLayout 원하는 속성선택
FlatAppearance->BorderSize 0 입력
FlatAppearance->CheckedBackColor = Transparent 입력
FlatAppearance->MouseDownBackColor = Transparent 입력
FlatAppearance->MouseOverBackColor = Transparent 입력
FlatStyle->Flat 선택
Size->이미지 사이즈 입력
3. 버튼 이벤트 CheckedChanged 핸들러 메소드에서 버튼 이미지 변환 코드 입력
private void checkButton_CheckedChanged(object sender, EventArgs e)
{
if (checkButton.Checked)
{
checkButton.BackgroundImage = Properties.Resources.check;
}
else
{
checkButton.BackgroundImage = Properties.Resources.uncheck;
}
}
2021년 4월 30일 금요일
C# 멀티캐스트 소켓 생성
protected Socket socket;
...
void CreateMulticastSocket(string multicastIP, int port, bool exclusive)
{
sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
socket.Blocking = false;
socket.ExclusiveAddressUse = exclusive;
if (!exclusive) socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, port);
socket.Bind(endpoint);
IPAddress ip = IPAddress.Parse(multicastIP);
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(ip, IPAddress.Any));
}
2021년 4월 8일 목요일
C# 시리얼 통신 데이터 파싱
private SerialPort serial = new SerialPort();
private byte[] serialRecvBuffer = new byte[1024]; // 수신버퍼
private int serialRecvBufferIndex = 0; // 수신버퍼 인덱스 - 현재까지 수신한 데이터 길이와 같음
...
serial.DataReceived += Serial_DataReceived;
...
private void Serial_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
// 시리얼 데이터 수신
int size = serial.BytesToRead;
byte[] bytesRead = new byte[size];
int ret = serial.Read(bytesRead, 0, size);
// 수신버퍼에 복사
Buffer.BlockCopy(bytesRead, 0, serialRecvBuffer, serialRecvBufferIndex, size);
serialRecvBufferIndex += size;
while (serialRecvBufferIndex >= 20) // 수신버퍼 크기가 20보다 크거나 같으면 데이터 처리
{
ProcessSerialData(); // 데이터 처리
// 데이터 처리 후 뒤에 남은 데이터들 앞으로 복사
Buffer.BlockCopy(serialRecvBuffer, 20, serialRecvBuffer, 0, serialRecvBufferIndex - 20);
serialRecvBufferIndex -= 20;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
serialRecvBufferIndex = 0;
}
}
2020년 11월 27일 금요일
C# 빅엔디안 처리 - C# Convert big-endian to variable
버퍼에 빅엔디안으로 데이터가 있고 이것을 변수에 담아야 할때 아래 코드를 사용하면 된다.
private uint ConvertBigEndianUInt(byte[] buffer, int offset)
{
uint ret = (uint)(buffer[offset] << 24);
ret |= (uint)(buffer[offset + 1] << 16);
ret |= (uint)(buffer[offset + 2] << 8);
ret |= (uint)(buffer[offset + 3]);
return ret;
}
private ushort ConvertBigEndianUShort(byte[] buffer, int offset)
{
ushort ret = (ushort)(buffer[offset] << 8);
ret |= (ushort)(buffer[offset + 1]);
return ret;
}
private long ConvertBigEndianLong(byte[] buffer, int offset)
{
long ret = (long)(buffer[offset] << 56);
ret |= (long)(buffer[offset + 1] << 48);
ret |= (long)(buffer[offset + 2] << 40);
ret |= (long)(buffer[offset + 3] << 32);
ret |= (long)(buffer[offset + 4] << 24);
ret |= (long)(buffer[offset + 5] << 16);
ret |= (long)(buffer[offset + 6] << 8);
ret |= (long)(buffer[offset + 7]);
return ret;
}
2019년 7월 10일 수요일
c# 시스템 메모리 사용량 구하기
겨우 찾아낸 이 방법이 제일 나은것 같다.
출처 - https://ash84.net/2012/03/03/c-wmi-eb-a5-bc--ec-9d-b4-ec-9a-a9-ed-95-9c--ed-98-84-ec-9e-ac--eb-a9-94-eb-aa-a8-eb-a6-ac--ec-82-ac-ec-9a-a9-eb-9f-89--ea-b5-ac-ed-95-98-ea-b8-b0/
프로세스 메모리 사용량이 아닌 작업관리자에 나오는 시스템 메모리 사용량 체크 소스이다.
private uint GetTotalUsedMemory()
{
ManagementClass cls = new ManagementClass("Win32_OperatingSystem");
ManagementObjectCollection instances = cls.GetInstances();
foreach (ManagementObject info in instances)
{
int total_physical_memeory = int.Parse(info["TotalVisibleMemorySize"].ToString());
int free_physical_memeory = int.Parse(info["FreePhysicalMemory"].ToString());
int remain_physical_memory = total_physical_memeory - free_physical_memeory;
Console.WriteLine("Memory Information ================================");
Console.WriteLine("Total Physical Memory :{0:#,###} KB", info["TotalVisibleMemorySize"]);
Console.WriteLine("Free Physical Memory :{0:#,###} MB", info["FreePhysicalMemory"]);
Console.WriteLine("Memory Usage Percent = {0} %", 100 * remain_physical_memory / total_physical_memeory);
Console.WriteLine("Remain Physical Memory : {0:#,###}", remain_physical_memory);
return (uint)(remain_physical_memory / 1000);
}
return 0;
2019년 7월 4일 목요일
c# unix timestamp local DateTime 으로 변환
DateTime dateTimeTimestamp;
...
dateTimeTimestamp = new DateTime(1970, 1, 1).AddSeconds(timestamp);
dateTimeTimestamp += TimeZone.CurrentTimeZone.GetUtcOffset(dateTimeTimestamp);
2019년 7월 2일 화요일
c# simple thread-safe Log Writer
로그파일은 날짜별로 생성되고 각 로그 시간을 함께 찍어준다.
프로그램 시작시 StartDeleteLog / 종료시 StopDeleteLog 한다.
로그파일은 디폴트로 30일이 넘으면 삭제된다.
1: using System;
2: using System.Collections.Generic;
3: using System.IO;
4: using System.Linq;
5: using System.Text;
6: using System.Threading;
7: using System.Threading.Tasks;
8:
9: namespace MyLog
10: {
11: public enum LogType
12: {
13: Info,
14: Exception,
15: Error
16: }
17:
18: public class LogWriter
19: {
20: public delegate void LogWriteHandlerFunc(LogType logType, DateTime logTime, string logMessage);
21: public LogWriteHandlerFunc LogWriteHandler;
22:
23: private static LogWriter instance = new LogWriter();
24: public static LogWriter Instance { get { return instance; } }
25:
26: private object objLogLock = new object();
27:
28: private const string LOG_PATH = @"C:\Log\";
29: private const string LOG_FILENAME = @"log";
30:
31: private Thread threadDeleteLog;
32: private bool bThreadDeleteLogRun = false;
33: private int logDeleteDays = 30;
34:
35: public void StartDeleteLog(int days)
36: {
37: if (!bThreadDeleteLogRun)
38: {
39: logDeleteDays = days;
40: bThreadDeleteLogRun = true;
41: threadDeleteLog = new Thread(new ThreadStart(ThreadDeleteLog));
42: threadDeleteLog.IsBackground = true;
43: threadDeleteLog.Start();
44: }
45: }
46:
47: public void StopDeleteLog()
48: {
49: if (threadDeleteLog != null)
50: {
51: bThreadDeleteLogRun = false;
52: threadDeleteLog.Join();
53: }
54: }
55:
56: private void ThreadDeleteLog()
57: {
58: CheckLogPath(LOG_PATH);
59:
60: DateTime lastTime = DateTime.MinValue;
61:
62: while (bThreadDeleteLogRun)
63: {
64: DateTime now = DateTime.Now;
65: double diff = (now - lastTime).TotalHours;
66: if (diff < 0) diff = 0;
67:
68: if (diff > 24)
69: {
70: try
71: {
72: string[] filepaths = Directory.GetFiles(LOG_PATH, "*.txt");
73: DeleteLogFiles(now, filepaths);
74: }
75: catch (Exception ex)
76: {
77: WriteLog(LogType.Exception, ex.ToString());
78: }
79:
80: lastTime = now;
81: }
82:
83: Thread.Sleep(100);
84: }
85: }
86:
87: private void DeleteLogFiles(DateTime now, string[] filepaths)
88: {
89: foreach (string filepath in filepaths)
90: {
91: try
92: {
93: DateTime date = File.GetCreationTime(filepath);
94: if ((now - date).TotalDays > logDeleteDays)
95: {
96: File.Delete(filepath);
97: }
98: }
99: catch (Exception ex)
100: {
101: WriteLog(LogType.Exception, ex.ToString());
102: }
103: }
104: }
105:
106: public void WriteLog(LogType logType, string log)
107: {
108: try
109: {
110: lock (objLogLock)
111: {
112: Console.WriteLine(log);
113:
114: string filepath = LOG_PATH;
115:
116: CheckLogPath(filepath);
117:
118: DateTime dateTime = DateTime.Now;
119: string strDate = dateTime.ToString("yyyyMMdd");
120: string strDateTime = dateTime.ToString("yyyy-MM-dd HH:mm:ss.fff");
121:
122: filepath = string.Format("{0}{1}_{2}.txt", filepath, LOG_FILENAME, strDate);
123: string logMessage = string.Format("{0} [{1}] {2}", strDateTime, logType, log);
124:
125: using (StreamWriter sw = File.AppendText(filepath))
126: {
127: sw.WriteLine(logMessage);
128: sw.WriteLine("---------------------------------------------------------------------------------------\r");
129: sw.Flush();
130: }
131:
132: if (LogWriteHandler != null) LogWriteHandler(logType, dateTime, log);
133: }
134: }
135: catch (Exception ex)
136: {
137: Console.WriteLine(ex.ToString());
138: }
139: }
140:
141: private void CheckLogPath(string path)
142: {
143: try
144: {
145: if (!Directory.Exists(path))
146: Directory.CreateDirectory(path);
147: }
148: catch (Exception ex)
149: {
150: Console.WriteLine(ex.ToString());
151: }
152: }
153: }
154: }
155:
private LogWriter logWriter = LogWriter.Instance;
...
logWriter.WriteLog(LogType.Info, "로그 시작...");
2019년 6월 3일 월요일
C# BeginInvoke/Invoke 동작 원리
간단한 동작원리이지만 개발자들이 의외로 잘 모르고 사용하는 경우가 많다.
먼저 아래 링크에 아주 잘 설명되어 있다.
https://www.codeproject.com/Articles/10311/What-s-up-with-BeginInvoke
위 링크에서 하는 설명의 결론을 말하자면 컨트롤에서 BeginInvoke/Invoke 를 호출하는 순간 UI 메시지큐에 사용자 정의 메시지가 push 되고 해당 메시지에 대해 실행되는 메시지 핸들러 함수가 등록되는 원리이다.
외부 쓰레드에서 UI 쓰레드에서 실행되는 delegate 를 등록하여 비동기 실행을 하도록 한다는 것이다.
아래 폼을 보자.
폼 위에 여러가지 UI 요소들이 있다. 여기에 UI 쓰레드가 몇 개가 돌까?
정답은 1개 이다. 특별히 쓰레드를 생성해서 폼을 생성하지않는한 UI 쓰레드는 언제나 한 개이다.
이 말은 win32 API 로 프로그램을 개발했다고 가정했을때 메시지 큐를 처리하는 메시지 루프가 한 개 돌고있다는 말이다.
BeginInvoke를 호출할때 굳이 btnOpen.BeginInvoke 니 textStatus.BeginInvoke 등 처럼 UI 컨트롤 변수를 지정해줄 필요가 없으며 의미도 없다는 말이된다. (WPF 의 Dispatcher 도 마찬가지이다)
그냥 this.BeginInvoke 혹은 BeginInvoke 로 호출하면된다.
Invoke 의 경우 쓰레드 동기화를 위해 이를 호출한 외부 쓰레드에서 delegate 타입의 이벤트 핸들러 함수가 끝날때까지 Sleep 상태로 기다렸다가 함수 실행이 끝나면 다음 코드가 진행되는 방식이다.
Invoke 함수의 경우 폼이 종료된 후 호출되면 UI 쓰레드가 종료된 이후이기 때문에 데드락에 빠지는 경우가 생긴다. 따라서 분명한 이유가 없으면 Invoke보다 BeginInvoke 사용을 권장한다.
2019년 5월 20일 월요일
C# 프로젝트에 ocx 수동 삽입하기 - Inserting ocx into c# project manually
ocx 파일명 : DXMediaPlayer.ocx
참조 : AxInterop.DXMediaPlayerLib, Interop.DXMediaPlayerLib
위 2개의 참조경로를 따라면 보통 obj\Release(Debug) 아래 경로에 두 dll을 참조하고 실행경로에 복사되는데 이것은 좋은 방법이 아니다.
보통 obj 경로는 git 으로 관리하지않기 때문에 경우에 따라 참조파일을 잃어버릴수 있고 실행경로에 두 dll 파일이 없으면 실행파일이 ocx 를 사용할 수 없는 예외가 발생한다.
따라서 아래와 같은 방법으로 ocx 를 수동으로 추가하는 방법을 사용할 수 있다.
1. 시작 -> 모든 프로그램 -> Visual Studio 20XX -> Visual Studio Tools -> Developer Command Prompt for VS 20XX 실행
2. ocx 파일 경로로 이동
3. 경로>aximp /source DXMediaPlayer.ocx 실행
4. AxDXMediaPlayerLib.cs 파일과 DXMediaPlayerLib.dll, AxDXMediaPlayerLib.dll 3개의 파일이 생성된다
5. 이 중 AxDXMediaPlayerLib.cs, DXMediaPlayerLib.dll 두 개의 파일을 삽입을 원하는 프로젝트 경로로 복사
6. 프로젝트 참조에 DXMediaPlayerLib.dll 추가, 소스에 AxDXMediaPlayerLib.cs 추가
7. 빌드 후 Assembly.cs 파일에 버전에러 발생하면 해당 라인 주석처리
이렇게 추가하면 실행경로에 dll 파일이 없어도 실행가능하다.
2018년 10월 18일 목요일
C# 간단한 4방향 A*(A Star) 알고리즘 구현 - Simple 4-way A* Algorithm implementation in C#
위 링크를 따라가보면 c#을 이용해서 동서남북으로 이동하는 A*(A 스타) 알고리즘을 간단히 구현한 소스가 있는데 버그가 있다.
첫째로 G 값을 계산하는 루틴에서 g++ 과 같이 매 스탭마다 무조건 노드의 G 값을 증가시키는데 이렇게 되면 제대로 최단경로를 찾을 수 없다.
두번째로 GetWalkableAdjacentSquares 함수에서 이웃한 노드를 찾을때 Location 을 무조건 새로 생성하는데 만약 openList 에 해당 노드가 존재한다면 openList 에 존재하는 노드의 값을 비교하는것이 아니라 새로 생성한 노드와 비교하기 때문에(모든값이 0) 역시 엉뚱한 경로를 찾게된다.
따라서 위 코드는 아래와 같이 수정되어야한다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AStarPathfinding
{
class Location
{
public int X;
public int Y;
public int F;
public int G;
public int H;
public Location Parent;
}
class Program
{
static void Main(string[] args)
{
Console.Title = "A* Pathfinding";
// draw map
string[] map = new string[]
{
"+------------+",
"| X |",
"| X X B|",
"| X X |",
"| X X |",
"|A X X |",
"| |",
"+------------+",
};
var start = new Location { X = 1, Y = 5 };
var target = new Location { X = 12, Y = 2 };
int SLEEP_TIME = 100;
foreach (var line in map)
Console.WriteLine(line);
// algorithm
Location current = null;
var openList = new List<Location>();
var closedList = new List<Location>();
int g = 0;
// start by adding the original position to the open list
openList.Add(start);
while (openList.Count > 0)
{
// get the square with the lowest F score
var lowest = openList.Min(l => l.F);
current = openList.First(l => l.F == lowest);
// add the current square to the closed list
closedList.Add(current);
// show current square on the map
Console.SetCursorPosition(current.X, current.Y);
Console.Write('.');
Console.SetCursorPosition(current.X, current.Y);
System.Threading.Thread.Sleep(SLEEP_TIME);
// remove it from the open list
openList.Remove(current);
// if we added the destination to the closed list, we've found a path
if (closedList.FirstOrDefault(l => l.X == target.X && l.Y == target.Y) != null)
break;
var adjacentSquares = GetWalkableAdjacentSquares(current.X, current.Y, map, openList);
g = current.G + 1;
foreach(var adjacentSquare in adjacentSquares)
{
// if this adjacent square is already in the closed list, ignore it
if (closedList.FirstOrDefault(l => l.X == adjacentSquare.X
&& l.Y == adjacentSquare.Y) != null)
continue;
// if it's not in the open list...
if (openList.FirstOrDefault(l => l.X == adjacentSquare.X
&& l.Y == adjacentSquare.Y) == null)
{
// compute its score, set the parent
adjacentSquare.G = g;
adjacentSquare.H = ComputeHScore(adjacentSquare.X, adjacentSquare.Y, target.X, target.Y);
adjacentSquare.F = adjacentSquare.G + adjacentSquare.H;
adjacentSquare.Parent = current;
// and add it to the open list
openList.Insert(0, adjacentSquare);
}
else
{
// test if using the current G score makes the adjacent square's F score
// lower, if yes update the parent because it means it's a better path
if (g + adjacentSquare.H < adjacentSquare.F)
{
adjacentSquare.G = g;
adjacentSquare.F = adjacentSquare.G + adjacentSquare.H;
adjacentSquare.Parent = current;
}
}
}
}
Location end = current;
// assume path was found; let's show it
while (current != null)
{
Console.SetCursorPosition(current.X, current.Y);
Console.Write('_');
Console.SetCursorPosition(current.X, current.Y);
current = current.Parent;
System.Threading.Thread.Sleep(SLEEP_TIME);
}
if (end != null)
{
Console.SetCursorPosition(0, 20);
Console.WriteLine("Path : {0}", end.G);
}
// end
Console.ReadLine();
}
static List<Location> GetWalkableAdjacentSquares(int x, int y, string[] map, List<Location> openList)
{
List<Location> list = new List<Location>();
if (map[y - 1][x] == ' ' || map[y - 1][x] == 'B')
{
Location node = openList.Find(l => l.X == x && l.Y == y - 1);
if (node == null) list.Add(new Location() { X = x, Y = y - 1 });
else list.Add(node);
}
if (map[y + 1][x] == ' ' || map[y + 1][x] == 'B')
{
Location node = openList.Find(l => l.X == x && l.Y == y + 1);
if (node == null) list.Add(new Location() { X = x, Y = y + 1 });
else list.Add(node);
}
if (map[y][x - 1] == ' ' || map[y][x - 1] == 'B')
{
Location node = openList.Find(l => l.X == x - 1 && l.Y == y);
if (node == null) list.Add(new Location() { X = x - 1, Y = y });
else list.Add(node);
}
if (map[y][x + 1] == ' ' || map[y][x + 1] == 'B')
{
Location node = openList.Find(l => l.X == x + 1 && l.Y == y);
if (node == null) list.Add(new Location() { X = x + 1, Y = y });
else list.Add(node);
}
return list;
}
static int ComputeHScore(int x, int y, int targetX, int targetY)
{
return Math.Abs(targetX - x) + Math.Abs(targetY - y);
}
}
}
위 코드의 결과를 이전 코드와 비교하면 다른것을 확인할 수 있다.
2018년 9월 4일 화요일
C# 간단한 쓰레드 안전 제네릭 큐 - C# Simple Thread-Safe Generic Queue Source Code
protected class MyQueue<T>
{
private int MAX_COUNT = 100;
private Queue<T> queue = new Queue<T>();
public MyQueue(int count)
{
MAX_COUNT = count;
}
public bool Push(T info)
{
lock (queue)
{
if (MAX_COUNT == 0 || queue.Count < MAX_COUNT)
{
queue.Enqueue(info);
return true;
}
return false;
}
}
public T Pop()
{
lock (queue)
{
if (queue.Count > 0) return queue.Dequeue();
return default(T);
}
}
public void Clear()
{
lock (queue)
{
queue.Clear();
}
}
public int Count { get => return queue.Count; }
}
2017년 11월 17일 금요일
c# 윈폼 사용시 주의할 점 - 메모리릭 방지
MyForm form = new MyForm();
if (form.ShowDialog() == DialogResult.OK)
{
...
}
위 코드는 별 문제 없어보이지만 사실은 메모리릭이 발생하는 코드이다.
폼 클래스는 IDisposable 을 구현하기 때문에 자원을 해제하려면 반드시 Dispose() 를
호출해줘야한다.
폼 내부에서 Dispose()를 호출하는 방법도 있지만 아래와같이 using 으로 묶어주면 깔끔하다.
using (MyForm form = new MyForm())
{
if (form.ShowDialog() == DialogResult.OK)
{
...
}
}
MyForm 객체를 멤버변수로 선언해서 재사용해서 쓰는것도 방법이겠지만 간단한 다이얼로그를 멤버변수로 두면 코드가 불필요하게 복잡해진다. 따라서 간단하게 using 으로 묶어주는것이 좋다.
위와같이 Dispose 를 해주지않고 반복해서 폼을 생성하면 "Win32Exception - error creating window handle" (윈도우 핸들을 생성할 수 없습니다) 와 같은 예외를 만나게된다.
2017년 9월 28일 목요일
ActiveX <-> C# 포인터 매개변수 전달 - Passing pointer parameters between activex and c#
1. c# -> activex 메소드 호출
2. activex -> c# 이벤트 호출
두 가지 경우가 있다. activex가 c#과 연동하기 위해서는 activex 에서 적절한 메소드를 만든다음 커맨드창에서
aximp /source MyActiveX.ocx
해서 만들어진 인터페이스용 cs 소스파일과 dll 을 사용한다.
이때 메소드의 매개변수를 c++의 BYTE* 와같이 포인터로해도 c#에서는 포인터를 매개변수로 사용할 수 없으므로 만들어진 cs 파일에는 ref byte 타입으로 변경되어 포인터값을 전달할 수 없다.
2의 경우도 마찬가지로 aximp 커맨드를 통해 생성되는 인터페이스용 cs 파일에는 포인터 타입이 들어가지않는다.
activex가 아닌 dll 인 경우 c# 함수 프로토타입 선언에서 byte[] 타입으로 매개변수를 지정해서 쓰면되지만 activex(ocx)는 aximp 로 생성된 소스를 사용해야하므로 난감한 상황이 발생한다.
결론적으로 포인터는 결국 32(혹은 64)비트 정수값일 뿐이며 사용하기에 따라 BYTE*, LONG* 등으로 캐스팅해서 사용할 뿐이다. (결국 해석의 차이)
따라서 매개변수를 굳이 포인터 타입으로 할 이유가 없다. activex 쪽에서 LONGLONG 타입(64비트 프로세스인 경우 대비해서 LONG보다 LONGLONG 권장)으로 매개변수를 지정한다음 포인터값(주소)을 넘겨주거나 넘겨받으면 그만이다.
1. 포인터 매개변수 전달 예 - activex
void MyActiveXCtrl::SetIntArrayData(LONGLONG pData)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
// TODO: Add your dispatch handler code here
int *data = (int *)pData;
...
}
aximp /source MyActiveX.ocx => cs 파일 생성
1. 포인터 매개변수 전달 예 - c#
int[] data = new int[10];
for (int i = 0; i < data.Length; i++) data[i] = i;
IntPtr ptr = Marshal.AllocHGlobal(sizeof(int) * data.Length);
Marshal.Copy(data, 0, ptr, data.Length);
myAxCtrl.SetIntArrayData((long)ptr); => c# long은 vc++ LONGLONG 이므로
Marshal.FreeHGlobal(ptr);
2. 포인터 매개변수 전달 예 - activex
void MyActiveXCtrl::OnDataEvent(LONG size, LONGLONG pData)
{
FireEvent(eventidOnDataEvent, ...., pData);
}
2. 포인터 매개변수 전달 예 - c#
myAxCtrl.OnDataEvent += axAxCtrl_OnDataEvent;
private void axAxCtrl_OnDataEvent(_AXMyActiveXCtrlEvents_OnDataEvent e)
{
byte[] btaImage = new byte[e.size];
unsafe
{
byte* p = (byte*)e.pData;
Marshal.Copy((IntPtr)p, btaImage, 0, e.size);
}
1번의 경우 c# -> activex 로 포인터를 전달하는데 원하는 데이터(여기서는 int 배열)를 IntPtr 변수에 복사한다음 포인터값을 전달한다. 이때 포인터를 받는 c++에서는 LONGLONG 타입을 int* 로 타입캐스팅해서 사용하면된다.
2번의 경우 activex -> c# 으로 포인터를 전달하는데 포인터 size를 지정한다음 LONGLONG 타입(64비트) 정수에 포인터 주소값을 넣어서 c# 쪽으로 전달한다.
c#에서는 전달받은 long 타입(64비트) 데이터를 byte* 으로 캐스팅한다음 c#에서 쉽게 사용하도록 byte 배열에 복사한다.



