URI (별칭식 경로 : Uniform Resource Identifier 통합 자원 식별자)
URI는 특정 리소스를 식별하는 경로를 의미한다. 웹 기술에서 사용하는 논리적 또는 물리적 리소스를 식별하는 고유한 문자열 시퀀스다.

URL (절대 경로 : Uniform Resource Locator 또는 통칭 web address)
URL은 흔히 웹 주소라고도 하며, 컴퓨터 네트워크 상에서 리소스가 어디 있는지 알려주기 위한 규약이다. URI의 서브셋이다.

 

POST 방식은 기본 (URL)에 요청할 때 BODY 부분에 데이터를 넘겨주는 방식입니다.

Spring 공부를 하면 해당 API 들을 직접 호출 및 테스트 해볼 수 있게 만들면 좋겠네요.

POST 방식은 코드만 올리겠습니다.

1개 보내는 방식, multipart 로 2개 이상의 다른 데이터 보내는 방식입니다.

using System;
using System.IO;  //Stream
using System.Net; //HttpWebRequest, WebRequest 등 사용
using System.Windows.Forms;

using System.Net.Security; //SSL (Secure Sockets Layer) 관련
using System.Security.Cryptography.X509Certificates; //암호화 된 인증서 관련
using System.Text;

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

            PostRequest();
        }

        private bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            //인증서에 대한 결과를 보려면 여기서 브레이크 걸고 certificate, chain 등을 살펴보면 됩니다.
            //권장하지 않는 방법이지만 인증 기관에 대해 true로 설정해둡니다.
            return true;
        }

        private void PostRequest()
        {
            //****************************** 이 부분은 첫 사용하기 전에만 넣어주면 됩니다. ***********************************
            //웹 통신 시 System.Net.WebException : 기본 연결이 닫혔습니다. 관련 에러 방지용 프로토콜 정의입니다.
            //Secure Sockets Layer, Transport Layer Secure
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls |
                                                   SecurityProtocolType.Tls11 |
                                                   SecurityProtocolType.Tls12 |
                                                   SecurityProtocolType.Ssl3;

            //https (hyper text transfer protocol Secure : 전송 규약 보안) 보안이 올바른지 확인하는 구문입니다. 
            ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(ValidateServerCertificate);
            //****************************************************************************************************************

            //[ContentType] : Body에서 전송되는 콘텐츠의 유형을 서버에 알려줍니다.
            //                MIME (Multipurpose Internet Mail Extensions) 표준을 따릅니다.
            //
            //ex)
            //1. multipart/form-data            2개 이상으로 한 번에 보낼 때 boundary와 함께 사용
            //2. application/json               제이슨 형태
            //3. application/x-www-urlencoded   기본 타입
            //4. image/tiff                     이미지, 확장자
            //   등 등

            //한 개의 데이터를 POST 방식으로 보내기
            PostRequestOne();

            //2개 이상의 데이터를 POST 방식으로 보내기
            //multipart/form-data 방식으로 사용되며 줄 구분을 위한 boundary가 필요   
            PostRequestAll();
        }

        private void PostRequestOne ()
        {
            string uri = textBox_uri.Text;
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);   //요청 준비
            HttpWebResponse response = null;

            try
            {
                request.Method = "POST"; //기본 값은 GET 입니다.
                request.ContentType = "application/x-www-form-urlencoded"; //url encoded 방식 변수=값&변수1=값..

                string userName = "name";
                string password = "1234"; //서버와 협의하여 해쉬 등으로 암호화
                string data = string.Format("userName={0}&password={1}", userName, password);

                //request body에 데이터 채우기
                byte[] buffer = Encoding.UTF8.GetBytes(data);
                Stream requestStream = request.GetRequestStream(); //request에 보낼 데이터 입력용
                requestStream.Write(buffer, 0, buffer.Length);
                requestStream.Close();

                //응답 결과 받기
                response = (HttpWebResponse)request.GetResponse();
                Stream responseStream = response.GetResponseStream();
                StreamReader responseStreamReader = new StreamReader(responseStream);

                //응답 값 받아서 리턴하거나 사용하면 됩니다.
                string result = responseStreamReader.ReadToEnd();

                responseStreamReader.Close();
                responseStream.Close();
                response.Close();
            }
            catch (Exception ex)
            {
                //로그 남기면 좋을 듯
                MessageBox.Show("Request Error" + Environment.NewLine + ex.ToString());
            }
            finally
            {
                if (response != null)
                {
                    response.Close();
                }
            }
        }

        private void PostRequestAll ()
        {
            string uri = textBox_uri.Text;
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);   //요청 준비
            HttpWebResponse response = null;

            try
            {
                request.Method = "POST"; //기본값은 GET
                                         //그 밖에도 timeout, credential 등 다양한 옵션을 줄 수 있습니다.

                //https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition
                //2개 이상 multipart로 보내는 방법
                //보낼 값) field1 = value, 파일 = byte file

                //경계를 통해 데이터 타입 명시 및 다음과 같은 형태로 만들어서 보내면 됩니다.
                /*
                    Content-Type: multipart/form-data; boundary=isBoundary
                
                    --isBoundary
                    Content-Disposition: form-data; name="field1"

                    value
                    --isBoundary
                    Content-Disposition: form-data; name="파일"; filename="myImage.tiff" (filename은 부가적인 부분으로 없어도 된다고 봤네요.)
                    Content-Type: image/tiff (타입에 대한 명시 필요)

                    byte file
                    --isBoundary-- (경계의 끝은 -- 붙여주기)
                 */

                string endLine = "\r\n";            //웹에서 개행을 나타내는 문자열입니다. 
                string boundary = "isBoundary";     //구분자입니다. --isBoundary ~ --isBoundary-- 로 끝나게 됩니다.

                request.ContentType = string.Format("multipart/form-data; boundary={0}", boundary);
                byte[] boundaryBytes = Encoding.ASCII.GetBytes(endLine + "--" + boundary + endLine);

                Stream requestStream = request.GetRequestStream();
                requestStream.Write(boundaryBytes, 0, boundaryBytes.Length);
                //
                //--isBoundary
                //

                string field1 = "field1";
                string value = "value";
                string data = string.Format("Content-Disposition: form-data; name=\"{0}\"{1}{2}{3}", field1, endLine, endLine, value);
                byte[] dataBytes = Encoding.UTF8.GetBytes(data);

                requestStream.Write(dataBytes, 0, dataBytes.Length);
                //[진행 현황]
                //
                //--isBoundary
                //Content-Disposition; form-data; name="field1"
                //
                //value

                requestStream.Write(boundaryBytes, 0, boundaryBytes.Length);
                //[진행 현황]
                //
                //--isBoundary
                //Content-Disposition; form-data; name="field1"
                //
                //value
                //--isBoundary
                //

                string name = "파일";
                string filename = "myImage.tiff";

                string data2 = string.Format("Content-Disposition: form-data; name=\"{0}\" filename=\"{1}\"{2}Content-Type: image/tiff{3}{4}",
                                                name, filename, endLine, endLine, endLine);
                byte[] data2Bytes = Encoding.UTF8.GetBytes(data2);

                requestStream.Write(data2Bytes, 0, data2Bytes.Length);
                //[진행 현황]
                //
                //--isBoundary
                //Content-Disposition; form-data; name="field1"
                //
                //value
                //--isBoundary
                //Content-Disposition: form-data; name="파일" filename="myImage.tiff"
                //Content-Type: image/tiff
                //
                //

                //예시 경로 파일 바이트로 읽어서 보내기
                string imagePath = @"c:\temp\myImage.tiff";
                FileStream fileStream = new FileStream(imagePath, FileMode.Open, FileAccess.Read);
                byte[] imageBuffer = new byte[4096];
                int bytesRead = 0;
                while (true)
                {
                    bytesRead = fileStream.Read(imageBuffer, 0, imageBuffer.Length);
                    if (bytesRead <= 0) //이미지 바이트 다 읽은 경우
                    {
                        break;
                    }
                    requestStream.Write(imageBuffer, 0, bytesRead);
                }
                fileStream.Close();

                byte[] endBoundaryByte = Encoding.ASCII.GetBytes(endLine + "--" + boundary + "--" + endLine);
                requestStream.Write(endBoundaryByte, 0, endBoundaryByte.Length);
                //[진행 현황]
                //
                //--isBoundary
                //Content-Disposition; form-data; name="field1"
                //
                //value
                //--isBoundary
                //Content-Disposition: form-data; name="파일" filename="myImage.tiff"
                //Content-Type: image/tiff
                //
                //byte file
                //--isBoundary--

                response = (HttpWebResponse)request.GetResponse(); //요청 후 결과 반환
                Stream responseStream = response.GetResponseStream();   //요청에 대한 stream 가져오기
                StreamReader responseStreamReader = new StreamReader(responseStream); //해당 stream 읽기 위한 streamReader

                //응답값 받아서 리턴하거나 사용하면 됩니다.
                string result = responseStreamReader.ReadToEnd();

                responseStreamReader.Close();
                responseStream.Close();
                response.Close();
            }
            catch (Exception ex)
            {
                //로그 남기면 좋을 듯
                MessageBox.Show("Request Error" + Environment.NewLine + ex.ToString());
            }
            finally
            {
                if (response != null)
                {
                    response.Close();
                }
            }
        }
    }
}

 

URI (별칭식 경로 : Uniform Resource Identifier 통합 자원 식별자)
URI는 특정 리소스를 식별하는 경로를 의미한다. 웹 기술에서 사용하는 논리적 또는 물리적 리소스를 식별하는 고유한 문자열 시퀀스다.

URL (절대 경로 : Uniform Resource Locator 또는 통칭 web address)
URL은 흔히 웹 주소라고도 하며, 컴퓨터 네트워크 상에서 리소스가 어디 있는지 알려주기 위한 규약이다. URI의 서브셋이다.

 

GET 방식은 헤더 부분인 주소(URI)에 변수와 값 을 통해 결과를 얻어오는 방법 입니다.

주소 뒷 쪽에 ? 변수1 = 값1 & 변수2 = 값2

 

식으로 변수와 값을 넘겨줄 수 있습니다. 아래는 구글에 간단하게 hi 를 GET으로 요청한 결과 입니다.

https://google.com/search 에 대해해 뒷 편에 ? q = hi 를 붙인 것이죠.

                                                                    (q는 검색어 변수, hi는 값이 되겠죠)

구글에서 hi 검색한 결과

C#에서 호출 한 소스는 아래와 같습니다.

통신을 위해서 json 을 반환시키기도 하며 200(응답 성공), 400(클라이언트 에러), 500(서버쪽 에러) 를 통해

결과를 알 수도 있습니다.

 

.NET 에 따라 SecurityProtocol 값이 달라질 수 있습니다. (현재 .NET 4.7.2 로 작성 되었습니다.)

(ServicePointManager 첫 연결 시만 등록해주면 됩니다.)

using System;
using System.IO;  //Stream
using System.Net; //HttpWebRequest, WebRequest 등 사용
using System.Windows.Forms;

using System.Net.Security; //SSL (Secure Sockets Layer) 관련
using System.Security.Cryptography.X509Certificates; //암호화 된 인증서 관련

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

        private void button_request_Click(object sender, EventArgs e)
        {
            //****************************** 이 부분은 첫 사용하기 전에만 넣어주면 됩니다. ***********************************
            //웹 통신 시 System.Net.WebException : 기본 연결이 닫혔습니다. 관련 에러 방지용 프로토콜 정의입니다.
            //Secure Sockets Layer, Transport Layer Secure
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls |
                                                   SecurityProtocolType.Tls11 |
                                                   SecurityProtocolType.Tls12 |
                                                   SecurityProtocolType.Ssl3;

            //https (hyper text transfer protocol Secure : 전송 규약 보안) 보안이 올바른지 확인하는 구문입니다. 
            ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(ValidateServerCertificate);
            //****************************************************************************************************************

            string uri = textBox_uri.Text;
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);   //요청 준비
            HttpWebResponse response = null;

            try
            {
                response = (HttpWebResponse)request.GetResponse(); //요청 후 결과 반환

                Stream stream = response.GetResponseStream();   //요청에 대한 stream 가져오기
                StreamReader reader = new StreamReader(stream); //해당 stream 읽기 위한 streamReader

                string result = reader.ReadToEnd(); //결과 읽어오기

                reader.Close();
                stream.Close();

                textBox_result.Text = result;
            }
            catch (Exception ex)
            {
                textBox_result.Text = "Request Error" + Environment.NewLine + ex.ToString();
            }
            finally
            {
                if (response != null)
                {
                    response.Close();
                }
            }
        }

        private bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            //인증서에 대한 결과를 보려면 여기서 브레이크 걸고 certificate, chain 등을 살펴보면 됩니다.
            //권장하지 않는 방법이지만 인증 기관에 대해 true로 설정해둡니다.
            return true;
        }
    }
}

 

컴파일러서비스를 통해 어디서 누가 호출을 했는지 확인 할 수 있습니다.

로그를 남길 때 별도의 호출 함수 양식을 작성하지 않고도 사용할 수 있는 방법입니다!

 

DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss"); 를 통해

년-월-일 시:분:초

를 정의할 수도 있습니다. 아래는 호출 예제입니다.

 

using System;
using System.Windows.Forms;
using System.Runtime.CompilerServices; //[Caller 사용]

namespace WindowsFormsApp
{
    public partial class Form1 : Form
    {
        Test test = new Test();

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            test.ShowCaller();
            Log("로그 내용");
        }
        
        private void Log (string message, [CallerMemberName] string name = "")
        {
            string log = string.Format("{0} [{1}] : {2}{3}", DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss"), name, message, Environment.NewLine);
            MessageBox.Show(log);
        }
    }

    class Test
    {
        public void ShowCaller([CallerMemberName] string name = "", [CallerFilePath] string path = "", [CallerLineNumber] int line = 0)
        {
            MessageBox.Show("name : " + name + "\n" +
                            "path : " + path + "\n" +
                            "line : " + line);
        }
    }
}

대충 만든 폼! ㅎㅎ

 

test.ShowCaller() 내부의 메시지박스

 

Log("로그 내용") 내부의 메시지 박스

 

using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WindowsFormsApp9
{
    //Microsoft Hook Document
    //https://docs.microsoft.com/en-us/windows/win32/winmsg/hooks

    public partial class Form1 : Form
    {
        private HookManager hookManager = new HookManager();

        public Form1()
        {
            InitializeComponent();

            this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.Form1_FormClosed);
            hookManager.SetHook();
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            hookManager.UnHook(); //프로그램이 종료될 때 반드시 후킹을 꺼줘야합니다.
        }
    }

    public class HookManager
    {
        #region WindowsAPI

        [DllImport("user32.dll")]
        private static extern int SetWindowsHookEx(int idHook, LowLevelKeyboardProc callback, int hInstance, uint threadId);

        [DllImport("user32.dll")]
        private static extern bool UnhookWindowsHookEx(int hInstance);

        [DllImport("user32.dll")]
        private static extern int CallNextHookEx(int idHook, int nCode, int wParam, ref HookStruct lParam);

        [DllImport("kernel32.dll")]
        private static extern int LoadLibrary(string lpFileName);

        private delegate int LowLevelKeyboardProc(int nCode, int wParam, ref HookStruct lParam);
        private LowLevelKeyboardProc lowLevelKeyboardProc = HookProc;

        #endregion

        //구조체 크기를 맞춰줘야 올바른 값을 받을 수 있습니다.
        public struct HookStruct
        {
            public int vkCode;
            int scanCode;
            public int flags;
            int time;
            int dwExtraInfo;
        }

        //WM_Keydown
        //https://wiki.winehq.org/List_Of_Windows_Messages
        const int WH_KEYBOARD_LL = 13; //저수준 특수키 관련 Alt + .. Ctrl + ..
        const int WM_KEYDOWN = 256;
        const int WM_KEYUP = 257;
        const int WM_SYSKEYDOWN = 260;
        const int WM_SYSKEYUP = 261;

        private static int hookId = 0;

        public void SetHook()
        {
            int hInstance = LoadLibrary("User32");
            hookId = SetWindowsHookEx(WH_KEYBOARD_LL, lowLevelKeyboardProc, hInstance, 0);
        }

        public void UnHook()
        {
            UnhookWindowsHookEx(hookId);
        }

        public static int HookProc(int code, int wParam, ref HookStruct lParam)
        {
            if (code >= 0)
            {
                switch (wParam)
                {
                    case WM_KEYDOWN:
                    case WM_KEYUP:
                    case WM_SYSKEYDOWN:
                    case WM_SYSKEYUP:

                        Keys key = (Keys)lParam.vkCode;

                        if ((key == Keys.Tab    && lParam.flags == 32) ||   // Alt+Tab
                            (key == Keys.Escape && lParam.flags == 32) ||   // Alt+Esc
                            (key == Keys.F4     && lParam.flags == 32) ||   // Alt+F4
                            (key == Keys.Escape && lParam.flags == 0)  ||   // Ctrl+Esc
                            (key == Keys.LWin   && lParam.flags == 1)  ||   // Left Windows Key 
                            (key == Keys.RWin   && lParam.flags == 1))      // Right Windows Key 
                        {
                            return 1; //Do not handle key events
                        }
                        break;
                }
            }

            return CallNextHookEx(hookId, code, (int)wParam, ref lParam);
        }
    }
}

 

using System;

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            int a = 12345;

            //C, D, N, F, E, X 등 뒤에 숫자는 표현할 자리를 의미합니다.

            //아래식은 모두 ToString() 형태에서 사용 가능합니다!
            //Console.WriteLine("12345 C5 = {0}", a.ToString("C5"));

            Console.WriteLine("12345 C5 = {0:C5}", a); //통화
            Console.WriteLine("12345 D5 = {0:D5}", a); //정수 (0으로 채워집니다.)
            Console.WriteLine("12345 N5 = {0:N5}", a); //3자리마다 콤마
            Console.WriteLine("12345 F5 = {0:F5}", a); //실수형 (자릿수에서 반올림)
            Console.WriteLine("12345 E5 = {0:E5}", a); //지수형
            Console.WriteLine("12345 X5 = {0:X5}", a); //16진수

            Console.WriteLine();
            Console.WriteLine("12345 2진수  = {0}", Convert.ToString(a, 2));
            Console.WriteLine("12345 8진수  = {0}", Convert.ToString(a, 8));
            Console.WriteLine("12345 16진수 = {0}", Convert.ToString(a, 16));

            Console.WriteLine();
            DateTime dateTime = DateTime.Now;
            Console.WriteLine("DateTime.ToString(yyyy-MM-dd hh-mm-ss) = {0}", dateTime.ToString("yyyy-MM-dd hh-mm-ss"));

            Console.WriteLine();
            float b = 1234.5678f;
            Console.WriteLine("1234.5678f F2 = {0:F2}", b);
        }
    }
}

 

결과값

 Custom URI 를 만들어 웹페이지에서 내 PC에 있는 exe 파일을 실행시키는 방법입니다.

아래와 같이 작동 됩니다.

 

웹페이지에서 아이디 비밀번호와 같은 입력 값을 넘겨주며 .exe 파일을 실행시킵니다.
실행 버튼을 누른 결과가 출력됩니다.

 (주의 사항이 하나 있는데 위와 같이 변환 된 결과를 보면 웹 url에서 특수 문자가 다르게 취급됩니다.

UTF-8 과 System.Net.WebUtilty.UrlDecode 기능을 잘 섞어서 필요한 내용으로 만들어주면 되겠습니다.)

 

 

 먼저 레지스트리에 해당 파일을 실행할 수 있도록 등록해주어야 합니다.

 exe파일 실행 시 Main(string[] args) 부분에 대한 처리가 필요합니다.

 차근차근 해봅시다.

 

  1. 윈도우키 + R을 눌러 "실행" 창을 띄워줍니다. (혹은 시작->실행 검색)

  2. regedit 입력을 해줍니다.

실행 창에서 regedit 입력 후 확인

  3. HKEY_CLASSES_ROOT 오른쪽 클릭 후 새로 만들기 -> 키 를 선택해줍니다.

   이름은 "Test"로 설정해주겠습니다.

  4. 아래와 같이 키를 생성해줍니다.

    Test에서 shell, shell 에서 open, open 에서 command까지 생성해줍니다.

  5. Test 에서 새로 만들기 -> 문자열 값 을 통해 새 값을 만들어줍니다.

   해당 값의 이름을 URL Protocol 로 바꿔줍니다.

  6. 이번엔 command 쪽의 기본 값을 수정해줍니다. (기본 값을 더블 클릭 해줍니다.)

  아래와 같이 문자열 편집이 뜹니다. exe파일의 경로가 되겠습니다.

  "C:\Test\test.exe" "%1"

 C드라이브 테스트 폴더의 test.exe 실행을 하고, %1은 실행 시 매개변수 값을 하나 줄 수 있다는 의미입니다.

  7. 비주얼 스튜디오에서 다음과 같은 코드를 작성하고 exe 파일을 생성합니다.

using System;

namespace ConsoleApp5
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("받아 온 값");

            if (args.Length > 0)
            {
                Console.WriteLine(args[0]); //받아온 값을 보여줍니다.
            }

            Console.ReadLine(); //값을 보기 위해 대기
        }
    }
}

 

  8. 해당 파일을 C:\Test\Test.exe 로 옮겨줍시다.

  9. 웹에서 실행합니다. 간단하게 url 창에서 실행해보겠습니다.

  test://hihi ook

  원하는 값을 입력해줍니다. 그리고 엔터를 치면

  10. 짜잔 실행이 됩니다.

    저 띄어쓰기 부분은 %20 으로 나오고 맨 마지막에도 / 가 자동으로 붙게 됩니다.

   우선 간단하게 값을 받아오고 실행에 성공을 하였습니다. 

받아온 값을 원하는 값으로 변경해서 사용해주면 됩니다!

 

 설명이 너무 길어져서 나머지는 예제 파일로 남겨두겠습니다.

비주일 스튜디오 2019로 작업한 파일과 레지스트리 생성, test.exe 파일이 들어있습니다.

웹에서 어떻게 호출하는지와 C#에서 값을 어떻게 받아오는지도 있으니 필요하면 확인해보시길!

커스텀 URI 만들기.zip
0.83MB

 

코드 정리도 따로 올립니다.

html 코드

 

[파일명]

custom uri.html

<!DOCTYPE html>
<html>
 <head>
  <meta charset="utf-8">
  <script>
   
   function play ()
   {
    let id = document.getElementById("ID").value;
    let pw = document.getElementById("PW").value;

    let url = "Test://" + id + "&" + pw + "&"; // 레지스트리에 등록 된 Test:// 호출 부분입니다.
    //마지막에 &를 추가해줬는데 끝 부분에 / 와 같은 문자가 붙을 수 있어 경계 탐지용으로..
    let exec = document.createElement("a");
    exec.setAttribute("href", url);
    exec.click();
   }   

  </script>
 </head>
 <body>
  <div> 아이디 <input id="ID" type="text" value="testID!@"></div>
  <div> 비밀번호 <input id="PW" type="text" value="!@#$%^&*()"></div>
  <div> <button id="call" onclick="play()"> 실행 </button> </div>
 </body>
</html>

 

WindowsForm

[Program.cs]

using System;
using System.Windows.Forms;

namespace Custom_uri_Example
{
    static class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
         
            if (args.Length > 0) //외부 호출하여 인자값이 있는 경우
            {
                Application.Run(new Form1(args));
            }
            else //바로 .exe 실행
            {
                Application.Run(new Form1());
            }
        }
    }
}

 

[Form1.cs]

using System.Windows.Forms;

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

        public Form1(string[] args) : this()
        {
            textBox_all.Text = args[0];

            string data = args[0].Replace("test://", ""); //앞에 불필요한 문자열은 지워줍니다.

            string[] separate = data.Split('&'); //사전에 약속 된 구분자로 나눠줍니다.

            string utf_id = separate[0];
            string utf_pw = separate[1];

            textBox_utf_id.Text = utf_id;
            textBox_utf_pw.Text = utf_pw;

            //웹에서 받아온 문자 중 특수문자가 다른 경우가 있을 때 WebUtility를 사용해줍니다.
            //System.Net 사용이 안되면 참조 추가에서 System.Net을 추가해주면 됩니다.
            string web_id = System.Net.WebUtility.UrlDecode(separate[0]);
            string web_pw = System.Net.WebUtility.UrlDecode(separate[1]);

            textBox_web_id.Text = web_id;
            textBox_web_pw.Text = web_pw;
        }
    }
}

C#은 기본적으로 StructLayout 가 정해져 있습니다.

클래스는 [StructLayout(LayoutKind.Auto, Pack = 0)]

구조체는 [StructLayout(LayoutKind.Sequential, Pack =0)]

 

평소엔 신경쓰지 않아도 되지만 C, C++ 혹은 서버와 통신 등 메모리를 맞춰줘야 될 때

사용되는 기능이니 이런 것이 있다는 것만 알면 될거 같습니다.

아래 코드는 C#으로 C, C++ 포인터를 흉내내어 메모리 참조를 해본 코드입니다.

 

using System;
using System.Runtime.InteropServices; // [StructLayout ] 선언에 필요합니다.

//참고 설명 https://www.csharpstudy.com/DevNote/Article/10

//*****************************************************************
//
//  기본값
//  class  : [StructLayout(LayoutKind.Auto, Pack = 0)]
//  struct : [StrcutLayout(LayoutKind.Sequential, Pack = 0)]
//
//  Pack : 0이면 자동 세팅에 따릅니다. (0, 1, 2, 4, 8, 16... 128 까지 2의 배수만 가능합니다.)
//         1 이상이면 Pack과 타입 크기 중 가장 큰 단위에 따라 패딩을 넣어줍니다.
//      
//  ex)
//      [StructLayout(LayoutKind.Sequential, Pack = 0)]
//      struct MyStruct
//      {
//          public int i;     // 4
//          public double d;  // 8
//          public byte b;    // 1
//      }
//
//      가장 큰 사이즈에 맞추어 크기가 조절 됩니다.
//      Pack = 0 이면 가장 큰 사이즈 byte
//      sizeof(MyStruct) == 24
//      
//      i  = (0~3)
//    패딩 = (4~7)
//      d  = (8~15)
//      b  = (16)
//    패딩 = (17~23)
//
//      Pack = 1 이면 1에 가까운 타입에 맞추어집니다. 즉 byte 중심이 되며
//      i = (0~3), d = (4~11), b = (12) 가 되어 0~12까지 size = 13이 됩니다.
//
// LayoutKind.Sequential    : 메모리상에 순차적으로 저장합니다.
//            Auto          : 힙 영역에 자동으로 저장합니다.
//            Explicit      : 직접 메모리 영역을 지정해줍니다.
//                            변수에 [FieldOffset()] 식으로 지정해주어야 합니다.
//
//****************************************************************


namespace StructLayout
{
    class Program
    {
        [StructLayout(LayoutKind.Explicit, Pack = 1)] //int형 4에 맞춰집니다.
        public struct Test
        {
            [FieldOffset(0)]
            public int value; // 4 (0000 0000, 0000 0000, 0000 0000, 0000 0000)

            [FieldOffset(0)]
            public byte value1; // (0000 0000 ____ ____ ____ ____ ____ ____)

            [FieldOffset(1)]
            public byte value2; // (____ ____ 0000 0000 ____ ____ ____ ____)

            [FieldOffset(2)]
            public byte value3; // (____ ____ ____ ____ 0000 0000 ____ ____)

            [FieldOffset(3)]
            public byte value4; // (____ ____ ____ ____ ____ ____ 0000 0000)
        }

        //c언어의 포인터를 흉내낼 수 있습니다!
        static unsafe void Main(string[] args) //unsafe : 포인터 사용지 설정이 필요합니다.
        {
            Test test = new Test();

            Console.WriteLine("sizeof(TEST) : {0}", sizeof(Test));

            //c, c++언어의 포인터
            int* startAddress = (int*)&test;

            //1100 1100, 0000 0100, 0000 0001, 0000 0001
            test.value = (1 << 31) | (1 << 30) | (1 << 28) | (1 << 27) | //1100 1100 ____ ____ ____ ____ ____ ____
                         (1 << 18) |                                     //1100 1100 0000 0100 ____ ____ ____ ____
                         (1 << 8)  |                                     //1100 1100 0000 0100 0000 0001 ____ ____
                         1;                                              //1100 1100 0000 0100 0000 0001 0000 0001

            Console.WriteLine("test.value : {0}", Convert.ToString(test.value, 2)); //2진수로 출력
            Console.WriteLine("test.value1 : {0}", Convert.ToString(test.value1, 2)); //2진수로 출력 0000 0001
            Console.WriteLine("test.value3 : {0}", Convert.ToString(test.value2, 2)); //2진수로 출력 0000 0001
            Console.WriteLine("test.value2 : {0}", Convert.ToString(test.value3, 2)); //2진수로 출력 0000 0100
            Console.WriteLine("test.value4 : {0}", Convert.ToString(test.value4, 2)); //2진수로 출력 1100 1100
        }
    }
}

 

 

결과 값 2진수로 표현하였고 앞의 0은 사라집니다.

 

FormBorderStyle = None 을 주고 사용자 지정 타이틀을 만들다보면 최대화 시켰을 때 맨 아래 작업 표시줄이 가려집니다.

 

(왼쪽이 평상시 상태, 오른쪽은 가려진 형태)

 

이에따라 현재 스크린에 따른 최대화 사이즈 지정이 필요합니다.

 

using System;
using System.Drawing;
using System.Windows.Forms;

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

        private void button1_Click(object sender, EventArgs e)
        {
            if (this.WindowState == FormWindowState.Normal)
            {
                Size size = new Size();

                //현재 폼이 가장 가까운 모니터에 대한 작업 영역 크기를 가져옵니다.
                size.Width = Screen.FromControl(this).WorkingArea.Width;
                size.Height = Screen.FromControl(this).WorkingArea.Height;

                this.MaximumSize = size;
                this.WindowState = FormWindowState.Maximized;
            }
            else
            {
                this.WindowState = FormWindowState.Normal;
            }
        }
    }
}

 

 

 

 

 

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

이전 코드...

using System;
using System.Drawing;
using System.Windows.Forms;

namespace WindowsFormsApp6
{
    public partial class Form1 : Form
    {
        private Point prePosition;

        public Form1()
        {
            InitializeComponent();
        }

        private void button_close_Click(object sender, EventArgs e)
        {
            Close();
        }

        private void button_sizeChange_Click(object sender, EventArgs e)
        {
            if (this.WindowState == FormWindowState.Normal)
            {
                SetMaximize();
            }
            else
            {
                SetNormal();
            }
        }

        private void SetNormal ()
        {
            this.Location = prePosition; //크기 변경 시 원 위치로 복귀
            this.WindowState = FormWindowState.Normal;
        }

        private void SetMaximize ()
        {
            Screen[] screens = Screen.AllScreens;
            int length = screens.Length;
            int area;

            for (area = 0; area < length; ++area) //모든 모니터 영역 확인
            {
                if (screens[area].WorkingArea.Contains(this.Location))
                {
                    break;
                }
            }

            //화면 밖인 경우 첫번째 모니터로 해줍니다.
            if (length <= area) 
            {
                area = 0;
            }

            prePosition = this.Location; //노멀화 시킬 때 이동시킬 좌표값.

            //화면 기준점으로 이동 후 최대화 시켜주어야 정상적인 결과를 얻습니다.
            this.Location = new Point(screens[area].Bounds.X, screens[area].Bounds.Y);

            this.MaximizedBounds = new Rectangle(MaximizedBounds.X,
                                                 MaximizedBounds.Y,
                                                 screens[area].WorkingArea.Width,  //width 와 height를 바꿔줍니다.
                                                 screens[area].WorkingArea.Height);

            this.WindowState = FormWindowState.Maximized; //최대화
        }
    }
}

인덱서는 클래스에 [ ] 를 통해 어떠한 값을 설정하거나 가져오는 방법입니다.

 

프로퍼티처럼 get, set 설정을 해줘야 합니다.

 

public 리턴타입 this [타입 변수명]  으로 시작합니다. (int, string 모두 가능합니다!)

 

(복잡해질 수 있어 안쓰겠거니 했는데 많은 변수에 값을 할당할 때

 

일일히 값 체크를 하며 하드코딩 하는 형태가 되는데 사용해보니 조금 더 머리가 덜 아픈 느낌이 듭니다.)

 

 

using System;

namespace ConsoleApp2
{
    class A
    {
        public int a;
        public int b;
        public int c;

        public int this [int index] //인덱서 설정 indexer
        {
            get
            {
                switch (index)
                {
                    case 0:
                        return a;
                    case 1:
                        return b;
                    case 2:
                        return c;
                    default:
                        return 0;
                }
            }
            set
            {
                switch (index)
                {
                    case 0:
                        a = value;
                        break;
                    case 1:
                        b = value;
                        break;
                    case 2:
                        c = value;
                        break;
                    default:
                        break;
                }
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            A a = new A();
            int[] values = { 33, 28, 90 };

            for (int i = 0; i < 3; ++i)
            {
                a[i] = values[i]; //indexer set

                Console.WriteLine(a[i]); //indexer get
            }

            Console.WriteLine("a = {0}, a[0] = {1}", a.a, a[0]);
            Console.WriteLine("b = {0}, a[1] = {1}", a.b, a[1]);
            Console.WriteLine("c = {0}, a[2] = {1}", a.c, a[2]);
        }
    }
}

 

해당 변수를 인덱스화 시켜서 사용하는 방법! 결과입니다.

참고할거라서 먼저 코드부터 올리겠습니다 ' - '!  (설명은 밑에 간단히)

 

(+ 빈 값을 자동으로 제거해주는 방법도 있습니다

JsonConvert.SerializeObject(data,  //데이터
                            Formatting.Indented, //줄 바꿈 여부
                            new JsonSerializerSettings
                            {
                              DefaultValueHandling = DefaultvalueHandling.Ignore, //기본 값 제거
                              NullValueHandling = NullValueHandling.Ignore //NULL 값 제거
                            };

/*
Formatting.None  : 줄 바꿈 없음 
ex) { "a"="asd"}

Formatting.Indented : 줄 바꿈
ex)
{
  "a" = "asd"
}

DefaultvalueHandling.Ignore : 문자 = null, 숫자 = 0 제거
NullValueHandling.Ignore    : 문자 or 숫자 = null 제거

*/

 

예제 코드

using System;
using System.Collections.Generic;
using Newtonsoft.Json;                  //JsonProperty, JsonConvert
using Newtonsoft.Json.Linq;             //JObject, JArray
using Newtonsoft.Json.Serialization;    //Serialize, Deserialize

namespace ConsoleApp2
{
    class Program
    {
        class TestClass
        {
            //이름이 동일한 곳으로 파싱됩니다.
            //[JsonProperty("a")] 
            public int a;

            //제이슨에서 xx변수를 b라고 호칭합니다.
            [JsonProperty("b")]
            public int xx;

            //리스트(배열) 가능!
            public List<string> c = new List<string>();
        }

        static void Main(string[] args)
        {
            //*********************직렬화*************************

            TestClass test = new TestClass();
            test.a = 1;
            test.xx = 2;
            test.c.Add("hi");
            test.c.Add("ok");

            string serialized = JsonConvert.SerializeObject(test);
            string message = "{\"a\":1,\"b\":2,\"c\":[\"hi\",\"ok\"]}";
            
            //xx가 JsonProperty에 의해 c로 변환 됩니다.
            Console.WriteLine("serialized : {0}", serialized);
            Console.WriteLine("message : {0}", message);

            //*****************  파싱/ 역직렬화 ********************

            //오브젝트 형태로 파싱하여 사용하기
            JObject jObject = JObject.Parse(serialized);
            JArray jArray = jObject["c"].ToObject<JArray>(); //JArray로 변환

            Console.WriteLine("JObject : {0} {1}", jObject["a"], jObject["b"]);
            Console.WriteLine("JArray : {0} {1}", jArray[0], jArray[1]);

            //역직렬화 후 사용하기
            TestClass testClass = JsonConvert.DeserializeObject<TestClass>(message);
            Console.WriteLine("JsonConvert.DeserializeObject a:{0} b:{1}", testClass.a, testClass.xx);
            Console.WriteLine("JsonConvert.DeserializeObject c[0]:{0} c[1]:{1}", testClass.c[0], testClass.c[1]);

            //역직렬화 후 사용하기 (중간에 값이 빠져도 역직렬화 가능합니다!)
            string message2 = "{ b:1 }";
            TestClass testClass2 = JsonConvert.DeserializeObject<TestClass>(message2);
            Console.WriteLine("JsonConvert.DeserializeObject a:{0} b:{1}", testClass2.a, testClass2.xx);
        }
    }
}

직렬화 / 파싱 / 역직렬화 결과

 

JSON : Java Script Object Notation    키와 값을 갖는 문자열 형태입니다.

ex)  { name : "이름" , age : "33" } 

 

웹상에서 데이터를 주고 받기 편하고 많이 사용합니다!

위의 코드에 기본적인 사용법은 적혀 있으니 참고하시기 바랍니다.

 

JObject 형태로 사용하거나 class로 직렬화/역직렬화 가능합니다.

 

클래스 없이도 JProperty 형태로 삽입 만들 수도 있답니다. (아니면 위의 message처럼 직접 써도 되구요~)

JObject obj = new JObject();
obj.Add(new JProperty("oo", "!!"));
Console.WriteLine(obj["oo"]); // !!

 

 

설치법

1. 비주얼스튜디오 "솔루션 NuGet 패키지 관리" 에서 다운 받을 수 있습니다.

2. Json 홈페이지에서 .dll 다운도 가능합니다.

 

 도구 -> NuGet 패키지 관리자 -> 솔루션용 NuGet 패키지 관리

 

찾아보기 -> Json 검색 -> 오른쪽에서 설치 -> 솔루션 탐색기 참조에 생성됩니다.

using Newtonsoft.Json을 사용 가능해집니다!

 

+ Recent posts