레이블이 c#인 게시물을 표시합니다. 모든 게시물 표시
레이블이 c#인 게시물을 표시합니다. 모든 게시물 표시

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년 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년 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 파일 읽기/쓰기를 간편하게 할 수 있는 소스코드이다.

오랫동안 유용하게 사용했는데 링크를 잊어버렸다.


< 사용법 >

using Utilities;
...
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# 시리얼 통신 데이터 파싱

시리얼 통신 데이터는 TCP와 유사하게 전송되는 바이트 길이만큼 수신되지 않는다. 

만약 송신자->수신자 에게 주기적으로 20 바이트를 전송한다고 했을때 수신측에서 시리얼 통신 데이터를 read 했을때 정확하게 20 바이트를 수신하지 않고 5+15 와 같이 잘려서 수신될 수 있다는 말이다. 
연속해서 데이터를 수신할때는 더욱 다양한 상황이 발생할 수 있다. 20, 20 바이트를 연속해서 보냈을때 25+15로 받을 수도 있고 1+15+15+9 와 같이 받을 수도 있다. 

이런 모든 경우의 수에 대해 올바르게 처리하려면 전송되는 데이터 단위가 고정길이(20 바이트)라고 가정했을때 수신 버퍼를 크게 할당한 다음 20 바이트씩 끊어서 처리해야 한다. 간단한 소스는 다음과 같다.

 
         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 으로 변환

int timestamp;    // unix timestamp
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.StartDeleteLog(30);
...
logWriter.WriteLog(LogType.Info, "로그 시작...");
...
logWriter.StopDeleteLog();



2019년 6월 3일 월요일

C# BeginInvoke/Invoke 동작 원리

c#으로 개발한 UI 기반 프로젝트에서 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

c# 프로젝트(윈폼/WPF)에 ocx 파일을 삽입할때 보통 UI 에서 추가하는데 이렇게 하면 참조에 아래 2개의 dll 이 자동 추가된다.

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#

http://gigi.nullneuron.net/gigilabs/a-pathfinding-example-in-c/#comment-51778

위 링크를 따라가보면 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# 윈폼 사용시 주의할 점 - 메모리릭 방지

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#

vc++로 작성된 activex 와 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);
    }
    // now you can use btaImage data
    ...
}


1번의 경우 c# -> activex 로 포인터를 전달하는데 원하는 데이터(여기서는 int 배열)를 IntPtr 변수에 복사한다음 포인터값을 전달한다. 이때 포인터를 받는 c++에서는 LONGLONG 타입을 int* 로 타입캐스팅해서 사용하면된다.

2번의 경우 activex -> c# 으로 포인터를 전달하는데 포인터 size를 지정한다음 LONGLONG 타입(64비트) 정수에 포인터 주소값을 넣어서 c# 쪽으로 전달한다.
c#에서는 전달받은 long 타입(64비트) 데이터를 byte* 으로 캐스팅한다음 c#에서 쉽게 사용하도록 byte 배열에 복사한다.