A프로그램에서 B프로그램에게 요청하여 동작을 할 일이 있었습니다.

B프로그램은 초기화 되며 열려있는 창들이 닫혀야 했는데 이 부분에 대한 구현이 필요했습니다.

 

ex)

Program (B) 기능 -> OpenFileDialog (윈도우 API) Custom Form (사용자Form)

 

Program (B) 가 ShowDialog 로 잡혀있는 상황입니다.

윈도우API 종료는 SendMessage 를 사용해야 하고, Form은 Close를 통해 종료해 줄 수 있습니다.

 

(NamedPipe를 사용하였고, BlockingCollection Queue 를 사용하여 순서유지를 보장하였습니다.

코드가 좀 길어졌지만.. 남겨봅니다!

 

ShowDialog() 종료하는 프로그램

 

A프로그램 (명령 내리는 코드)

using System;
using System.IO;
using System.IO.Pipes;
using System.Windows.Forms;

namespace WindowsFormsApp6
{
    public partial class Form2 : Form
    {
        private const string targetName = "A_Pipe";

        public Form2()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            SendNamedPipe("CloseAPI");
        }

        private void button2_Click(object sender, EventArgs e)
        {
            SendNamedPipe("CloseSubForm");
        }

        private void SendNamedPipe(string message)
        {
            try
            {
                NamedPipeClientStream namedPipeClientStream = new NamedPipeClientStream(".", targetName, PipeDirection.InOut);
                namedPipeClientStream.Connect(10000); //10초

                StreamWriter streamWriter = new StreamWriter(namedPipeClientStream);

                streamWriter.WriteLine(message);
                streamWriter.Flush();

                streamWriter.Close();
                namedPipeClientStream.Close();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }
    }
}

 

 

B프로그램 (ShowDialog 하는 프로그램)

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipes;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form3 : Form
    {
        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

        private const uint WM_CLOSE = 0x0010;
        private const string myName = "A_Pipe"; //이름만 바꿔서 사용 가능

        private BlockingCollection<string> queue = new BlockingCollection<string>();
        private int queueCount = 0;

        private void ProcessQueue()
        {
            //내부적으로 monitor lock 걸려있어 순서 및 단일 실행 보장
            foreach (string message in queue.GetConsumingEnumerable())
            {
                if (message == "CloseAPI")
                {
                    CloseAPI();
                }
                else if (message == "CloseSubForm")
                {
                    CloseSubForm();
                }

                Invoke((MethodInvoker)delegate
                {
                    queueCount++;
                    listBox1.Items.Add(string.Format("{0}_{1}", queueCount, message));
                });
            }
        }

        public Form3()
        {
            InitializeComponent();

            //큐에서 관리
            Task.Run(() =>
            {
                ProcessQueue();
            });

            //네임드파이프 통신
            Task.Run(() =>
            {
                Receive();
            });
        }

        private void Receive()
        {
            while (true)
            {
                try
                {
                    NamedPipeServerStream namedPipeServerStream = new NamedPipeServerStream(myName, PipeDirection.InOut, 10);
                    namedPipeServerStream.WaitForConnection(); //데이터 받을때까지 기다립니다.

                    StreamReader streamReader = new StreamReader(namedPipeServerStream);
                    string message = streamReader.ReadLine();

                    queue.Add(message); //큐에 데이터 쌓아주기

                    streamReader.Close();
                    namedPipeServerStream.Close();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex);
                }
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Title = "불러오기 테스트";

            //테스트하기 위해 띄워주기만 함.
            if (ofd.ShowDialog() == DialogResult.OK)
            {
                //정상 OK 눌렀을 때 실행
            }
            else
            {
                //강제 종료시 이 곳을 실행.
            }
        }

        private void button2_Click(object sender, EventArgs e)
        {
            //테스트용으로 단순 띄워주기만 진행
            new Form1().ShowDialog();
        }

        private void CloseAPI()
        {
            IntPtr hWnd = FindWindow(null, "불러오기 테스트"); //타이틀명으로 찾기
            if (hWnd != IntPtr.Zero)
            {
                SendMessage(hWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero); //종료 메시지 보내기
            }
        }

        private void CloseSubForm()
        {
            List<Form> formList = new List<Form>();

            foreach (Form form in Application.OpenForms)
            {
                // 메인폼 제외
                if (form is Form3 == false)
                {
                    formList.Add(form);
                }
            }

            foreach (Form form in formList)
            {
                Invoke((MethodInvoker)delegate
                {
                    form.Close();
                });
            }
        }
    }
}

파일 등을 드래그 & 드롭으로 가져올 수 있는 코드입니다.

dropFiles 는 해당 파일의 경로를 불러오게 됩니다.

 

파일/폴더를 마우스 드래그로 가져올 수 있습니다.

 

using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            listBox1.AllowDrop = true; //drag & drop 허용
        }

        private void listBox1_DragDrop(object sender, DragEventArgs e)
        {
            //드래그 드롭 파일 (여러 개 가능)
            string[] dropFiles = (string[])e.Data.GetData(DataFormats.FileDrop);
            
            foreach (string file in dropFiles)
            {
                listBox1.Items.Add(file);
            }
        }

        private void listBox1_DragEnter(object sender, DragEventArgs e)
        {
            //마우스 커서 표현
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                e.Effect = DragDropEffects.Copy;
            }
        }
    }
}

종료 되었지만 남아있는 tray 아이콘

 

C# 에서 정상적으로 프로그램이 종료되면 Tray 아이콘이 제거 되긴 합니다만,

프로세스 킬, 비정상적인 종료 방법이라면 위와 같이 트레이 아이콘이 남는 경우가 있습니다.

 

해당 아이콘은 mouse hover 이벤트 시 윈도우 내부적으로 해당 프로그램이 동작하고 있는지 판별하기에

mouse move 이벤트를 통해 해당 tray들을 정리할 수 있습니다. 아래는 코드!

 

[생성, 정리 후]

생성 버튼 클릭 (8번)
정리 버튼 클릭

 

 

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        class TrayRefresher
        {
            [StructLayout(LayoutKind.Sequential)]
            struct RECT
            {
                public int Left;
                public int Top;
                public int Right;
                public int Bottom;
            }

            [DllImport("user32.dll")]
            static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, string windowName);

            [DllImport("user32.dll")]
            static extern bool GetWindowRect(IntPtr hWnd, out RECT rect);

            [DllImport("user32.dll")]
            static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);

            const int WM_MOUSEMOVE = 0x0200;
            
            public void RefreshTray()
            {
                /*
                Shell_TrayWnd (하단의 작업 표시줄)
                 ├─ TrayNotifyWnd (시스템 트레이 영역 - 시계, 네트워크 아이콘, 볼륨 조절 등)
                 │    └─ SysPager (아이콘을 스크롤하거나 관리)
                 │         └─ ToolbarWindow32 (보이는 트레이 아이콘들)
                 │
                 └─ NotifyIconOverflowWindow (숨겨진 아이콘 영역) - 숨기기/펼치기 영역
                      └─ ToolbarWindow32 (숨겨진 아이콘들)
                */

                //보여지는 영역
                IntPtr toolbar = FindToolbar("Shell_TrayWnd", "TrayNotifyWnd", "SysPager", "ToolbarWindow32");
                SendMouseMove(toolbar);

                //감춰진 영역도 추가
                toolbar = FindToolbar("NotifyIconOverflowWindow", "ToolbarWindow32");
                SendMouseMove(toolbar);
            }

            private IntPtr FindToolbar(params string[] targets)
            {
                IntPtr hWnd = IntPtr.Zero;

                foreach (string target in targets)
                {
                    hWnd = FindWindowEx(hWnd, IntPtr.Zero, target, null);

                    if (hWnd == null)
                    {
                        break;
                    }
                }

                return hWnd;
            }

            private void SendMouseMove(IntPtr hWnd)
            {
                if (hWnd == null)
                {
                    return;
                }

                if (GetWindowRect(hWnd, out RECT rect))
                {
                    int width = rect.Right - rect.Left;
                    int height = rect.Bottom - rect.Top;
                    int offset = 5; //아이콘 간격

                    for (int x = 0; x < width; x += offset)
                    {
                        for (int y = 0; y < height; y += offset)
                        {
                            //4byte lParam에 2byte씩 마우스 위치를 넣어줍니다.
                            //mouse y 2byte, mouse x 2byte
                            IntPtr lParam = (IntPtr)((y << 16) | (x & 0xFFFF));
                            SendMessage(hWnd, WM_MOUSEMOVE, IntPtr.Zero, lParam);
                        }
                    }
                }
            }
        }

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Task.Run(async () => //테스트용 트레이 아이콘 생성!
            {
                //테스트용 프로그램            
                Process process = Process.Start(@"...\trayTest.exe");
                await Task.Delay(1000);
                process.Kill(); //강제 종료 시 트레이 아이콘이 남습니다.
            });
        }

        private void button2_Click(object sender, EventArgs e)
        {
            new TrayRefresher().RefreshTray(); //트레이 아이콘 정리!
        }
    }
}

크롬이나 엣지를 감지하여 따라다니는 프로그램 샘플입니다.

여러 개인 경우는 다르게 처리가 필요합니다 기능이 재미있어서 게시 해둬봅니다!

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class FollowForm : Form
    {
        // EnumWindows 콜백 델리게이트
        private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

        [DllImport("user32.dll")]
        private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);

        //SetLastError 설정하면 아래와 같이 에러 타입 가져올 수 있습니다.
        //int errorCode = Marshal.GetLastWin32Error();
        [DllImport("user32.dll", SetLastError = true)]
        private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern bool SetWindowPos(IntPtr hWnd, 
                                                IntPtr hWndInsertAfter,
                                                int X, 
                                                int Y, 
                                                int cx, 
                                                int cy, 
                                                uint uFlags);

        [DllImport("user32.dll")]
        private static extern bool IsWindowVisible(IntPtr hWnd);

        const uint SWP_NOMOVE = 0x0002;
        const uint SWP_NOSIZE = 0x0001;

        [StructLayout(LayoutKind.Sequential)]
        private struct RECT
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
        }

        //EntryPoint를 통해 C#에서 사용할 이름을 커스텀 할 수 있습니다.
        [DllImport("user32.dll", EntryPoint = "GetWindowRect")]
        private static extern bool GetWindowRect22(IntPtr hWnd, out RECT lpRect);

        private bool isRunning = true;

        public FollowForm()
        {
            InitializeComponent();
        }

        private void FollowForm_Load(object sender, EventArgs e)
        {
            IntPtr myHwnd = this.Handle;

            Task.Run(async () =>
            {
                while (isRunning)
                {
                    await Task.Delay(10);

                    EnumWindows((hWnd, lParam) => //최상위 윈도우 값들을 가져옵니다.
                    {
                        if (IsWindowVisible(hWnd)) //윈도우 보여지는지 여부
                        {
                            StringBuilder title = new StringBuilder(256);
                            GetWindowText(hWnd, title, title.Capacity); //윈도우 텍스트를 가져옵니다.
                            string windowTitle = title.ToString();

                            //크롬이나 엣지를 감지합니다.
                            if (windowTitle.Contains("Chrome") || windowTitle.Contains("Edge"))
                            {
                                Console.WriteLine(windowTitle);

                                SetWindowPos(hWnd, myHwnd, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); //해당 위치를 조정해줍니다.

                                RECT rect;
                                GetWindowRect22(hWnd, out rect); //위치를 가져옵니다. GetWindowRect

                                Invoke((MethodInvoker)delegate
                                {
                                    this.Location = new Point(rect.Left, rect.Top);
                                });
                            }
                        }
                        return true;
                    }, IntPtr.Zero);
                }
            });
        }
        
        private void FollowForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            isRunning = false;
        }
    }
}

코드 및 디자이너는 아래와 같습니다.. 디자이너에서 트레이 아이콘 설정을 해주면 완료!

 

+) 추가적으로 보이지 않지만 특정 위치에 파일 다이얼로그, 프린트 등 기능을 보여주기 위해서

위치 값을 조정할 때가 있습니다. WindowState 와 Visible 처리를 통해 해당 위치 제어가 가능!

using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Tray : Form
    {
        public Tray()
        {
            InitializeComponent();

            this.ShowInTaskbar = false; //하단 테스트바에서 보이지 않도록 설정
            this.Visible = false; //화면에 보이지 않도록 설정
            this.WindowState = FormWindowState.Minimized; //최소화
            this.Opacity = 0; //투명하게 하여 타이틀도 안보이게 설정
            this.FormBorderStyle = FormBorderStyle.FixedToolWindow; //Alt + Tab 에서도 보이지 않도록

            //this.종료ToolStripMenuItem.Click += new System.EventHandler(this.종료ToolStripMenuItem_Click);

            //잠시 특정 위치 활성화
            this.WindowState = FormWindowState.Normal;
            this.Visible = true;
            //new OpenFileDialog().ShowDialog();

            //다시 특정 위치 해제
            this.WindowState = FormWindowState.Minimized;
            this.Visible = false;
        }

        private void 종료ToolStripMenuItem_Click(object sender, System.EventArgs e)
        {
            Close();
        }
    }
}

 

notifyIcon 및 contextMenuStrip 을 설정해줍니다.

notifyIcon 설정

 

ContextMenuStrip 설정 (이벤트도 추가 해주기)

 

결과 Tray Icon

 

.NET Framework / .NET 6 이상
[App.config] 파일

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>
  <appSettings>
    <add key="ServerUrl" value="127.0.0.1"/>
    <add key="ServerPort" value="5000"/>
  </appSettings>
</configuration>

//코드에서 호출방법
System.Configuration.ConfigurationManager.AppSettings["ServerUrl"]
System.Configuration.ConfigurationManager.AppSettings["ServerPort"]

==============================================

.NET Core / .NET 5
[appsettings.json] 파일

{
  "ServerUrl": "https://example.com/api",
  "ServerPort": "5"
}

//코드에서 호출
var config = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json")
    .Build();

string apiUrl = config["ServerUrl"];
string apiPort = config["ServerPort"];

프로세스 내부에서의 임계영역 설정 (운영체제에서 관리 x - 빠른 속도) - Monitor, lock

object obj = new object(); //타겟 오브젝트

// 1) Monitor와 같은 역할
lock (obj)
{
  //do something
}

// 2) lock과 같은 역할
Monitor.Enter(obj)
//do something
Monitor.Exit(obj)

 

 

다른 프로세스 간 임계영역 설정 (운영체제에서 관리) - Mutex, Semaphore, EventWaitHendle

1) Mutex, Semaphore (1개면 Mutex와 동일)

using System;
using System.Threading;

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {
            //1) 뮤텍스가 이미 있는지 확인
            bool isExist;
            Mutex mutex2 = new Mutex(true, "Test2", out isExist); //시작이 true면 바로 락 (WaitOne 효과)

            if (isExist)
            {
                //이미 존재하는 뮤텍스...
            }
            else
            {
                //존재하지 않는 뮤텍스..
            }
            mutex2.ReleaseMutex();
            mutex2.Dispose();
            //*******************************************

            //2) 
            Mutex mutex = new Mutex(false, "Test"); //시작이 false면 최초 락 걸지 않음 (WaitOne 필요)

            //************************************
            //(WaitOne 성공 시 true, 다른 곳에서 사용 중이면 false)
            //bool canUse = mutex.WaitOne(5000); //5초 기다리고 바로 실행
            //bool canUse = mutex.WaitOne(0);    //바로 사용 가능한지 확인
            //************************************

            mutex.WaitOne(); //사용 가능할 때까지 대기

            Console.WriteLine("{0} : 임계영역 설정", DateTime.Now);
            Console.ReadLine();

            mutex.ReleaseMutex(); //임계영역 해제
            mutex.Dispose(); //메모리 누수 방지를 위해 Dispose 혹은 using 필요!

            Console.WriteLine("{0} : 임계영역 해제", DateTime.Now);
            Console.ReadLine();
        }
    }
}

 

(GPT 대답)

  • createdNew는 뮤텍스가 "생성됐는지" 여부
  • WaitOne(0)는 뮤텍스 "소유권을 즉시 획득할 수 있는지" 여부

 

EventWaitHendle 도 있는데 나중에 정리..

32bit 환경에서 2GB 이상 사용하게 되면 out of memory 가 발생되게 됩니다.

기본 플랫폼 대상이 x86 (32bit)

 

c++ 도구 중 editbin.exe 라는 프로그램이 있는데 64bit 환경에서 32bit 프로그램의 메모리 사용량을 4GB 가량으로 늘려주는 기능이 있습니다.

Visual Studio Installer 에서 C++ MSVC를 설치하면..!

 

아래 경로에 editbin.exe 파일이 설치됩니다. (각 버전에 따라 위치가 변경 될 수 있습니다.)
C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\bin\Hostx86\x86\

                                                                           (버전) (타입)                                        (버전)

editbin.exe

 

빌드 후 이벤트에 해당 프로그램을 넣어주어 exe 메모리 사용량 변경이 가능합니다.

프로젝트 빌드 후 이벤트에 추가

 

"C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\bin\Hostx86\x86\editbin.exe" /LARGEADDRESSAWARE "$(TargetPath)"

 

※ $(TargetPath) 는 프로그램 exe 경로입니다.

 

===========================

 

메모리 사용량 확인

디버그 -> 성능 프로파일러 -> 메모리 사용량 -> 시작

비주얼 스튜디오 툴바

 

 

메모리 사용량 체크

 

editbin 메모리 증가

적용 전 (1.2GB 정도 사용 후 다운) 적용 후 (3.0GB 정도 사용 후 다운)

 

Xml 구조 읽는 방법 예제

 

결과

 

using System;
using System.Xml;

class Test
{
    static void Main()
    {
        string testData =
                "<main>" +
                " <sub attribute1=\"SubAttribute\">" +
                "  <field name=\"first\">1</field>" +
                "  <field name=\"second\">" +
                "   <test>" +
                "    2" +
                "   </test>" +
                "  </field>" +
                " </sub>" +
                "</main>";

        XmlDocument xml = new XmlDocument();

        //파일 경로로 읽는 방식
        //xml.Load("파일 경로");

        //string 읽는 방식
        xml.LoadXml(testData);

        //<sub> 가져오기
        XmlNode subNode = xml.SelectSingleNode("main/sub"); //첫 번째 노드 가져옴

        //<sub> attribute1 가져오기
        string attribute1 = subNode.Attributes["attribute1"]?.Value;
        Console.WriteLine($"attribute1 : {attribute1}");

        //<sub><field> 가져오기
        XmlNodeList fieldList = subNode.SelectNodes("field"); //Node의 Node 방식

        //<field> 값들 순회
        foreach (XmlNode node in fieldList)
        {
            string innterText = node.InnerText; //순수 값.
            string innerXml = node.InnerXml; //xml 값 전체

            Console.WriteLine($"innerText: {innterText}, innerXml: {innerXml}");
        }

        //번외) 항상 첫 번째 값을 가져옴 (field가 여러개)
        string x = subNode["field"].Attributes["name"]?.Value; //"first"
        Console.WriteLine($"first node : {x}");
    }
}

Base64는 byte 배열을 읽는 방식을 의미합니다. (보통 우리가 사용하는 UTF8, ASCII 등이 있지요)

Base64는 3byte (24bit) 단위로 자르고, 6bit씩을 취해 해당 값에 매칭되는 base64테이블로 매핑해줍니다.

이 때, 원문이 없는 경우는 "=" 문자열을 넣어 길이를 채워줍니다.

아래는 aB1@ 를 base64로 변환 한 결과입니다.

 

Base64 Table : ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

 

base64 나눠지는 형식

 

 

일단 C# 에서 제공되는 코드가 있는데 내부 구현이 궁금하여 테스트 해보게 되었습니다.

 

[C# base64 기존 제공]

string text = "aB1@"; //원본
byte[] bytes = Encoding.UTF8.GetBytes(plainText); //원하는 인코딩으로 byte 만들기
string base64String = Convert.ToBase64String(bytes); //byte배열 -> base64 텍스트 변환
byte[] base64bytes = Convert.FromBase64String(base64String); //base64 텍스트 -> byte배열 변환
string originText = Encoding.UTF8.GetString(base64bytes); //원본과 일치

 

 

[C# 직접 구현 - 4Byte int 형으로 비트 값 연산에 사용 (GPT 도움..!)]

using System;
using System.Collections.Generic;
using System.Text;

class Test
{
    private const string table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    private const string plainText = "aB1@";

    static void Main()
    {
        byte[] bytes = Encoding.UTF8.GetBytes(plainText);

        string base64Text = Base64Encoding(bytes); //직접 구현
        string base64Text2 = Convert.ToBase64String(bytes); //기존 제공

        Console.WriteLine("base64 텍스트 : " + base64Text);
        Console.WriteLine("base64 텍스트2 : " + base64Text2);

        byte[] base64bytes = Base64Decoding(base64Text); //직접 구현
        byte[] base64bytes2 = Convert.FromBase64String(base64Text2); //기존 제공

        Console.WriteLine("원본 복원 : " + Encoding.UTF8.GetString(base64bytes));
        Console.WriteLine("원본 복원2 : " + Encoding.UTF8.GetString(base64bytes2));
    }

    private static string Base64Encoding(byte[] bytes)
    {
        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < bytes.Length; i += 3)
        {
            int value = 0;
            int paddingCount = 0;

            //3byte씩 합쳐주기
            for (int j = i; j < i + 3; ++j)
            {
                value = value << 8;

                if (j < bytes.Length)
                {
                    value |= bytes[j];
                }
                else
                {
                    paddingCount++;
                }
            }

            //Masking
            //1111 1100 0000 0000 0000 0000 (0x3F << 6 << 6 << 6)
            //0000 0011 1111 0000 0000 0000 (0x3F << 6 << 6)
            //0000 0000 0000 1111 1100 0000 (0x3F << 6)
            //0000 0000 0000 0000 0011 1111 (0x3F)
            int masking = 0x3F << 6 << 6 << 6;

            //3byte를 6bit로 쪼개기 (없다면 패딩처리 '=')
            for (int j = 0; j < 4; ++j)
            {
                if (paddingCount > 3 - j)
                {
                    sb.Append("=");
                }
                else
                {
                    int v = (value & masking) >> 6 * (3 - j);
                    sb.Append(table[v]);
                }

                masking = masking >> 6;
            }
        }

        return sb.ToString();
    }

    private static byte[] Base64Decoding(string text)
    {
        List<byte> byteList = new List<byte>();

        for (int i = 0; i < text.Length; i += 4)
        {
            int value = 0;
            int paddingCount = 0;

            for (int j = i; j < i + 4; ++j)
            {
                value = value << 6;

                if (text[j] != '=')
                {
                    value |= table.IndexOf(text[j]);
                }
                else
                {
                    paddingCount++;
                }
            }

            int masking = 0xFF << 8 << 8;

            for (int j = 0; j < 3; ++j)
            {
                if (paddingCount > (2 - j))
                {
                    break;
                }
                else
                {
                    int v = (value & masking) >> 8 * (2 - j);
                    byteList.Add((byte)v);
                }

                masking = masking >> 8;
            }

            //Console.WriteLine("Processing... " + Encoding.UTF8.GetString(byteList.ToArray()));
        }

        return byteList.ToArray();
    }
}

 

+ Recent posts