Now I'm A Farmer(Peter-KIM)

2018.06.04

[VC/VC++] Dialog without the Resource File

Filed under: Programming — Peter_KIM @ 08:55

Visual C 또는 C++ 언어에서 Windows GUI 응용 프로그램을 만들 때, 습관적으로 또는 편리함의 이유로 리소스라는 것을 이용하는 것이 일반적입니다. 코딩을 하면서 만들어진 리소스 파일은 리소스 컴파일러에 의하여, 바이너리 값으로 변환되고, 링커(Linker)의하여 출력 파일에 병합됩니다.
대부분 다이얼로그 기반의 GUI 창을 만들 때에 리소스 파일(RC ,텍스트 형식)을 사용하는데, C/C++ 코드에서는 GUI 창을 어떻게 만들 수 있을지 알아보겠습니다.

여가에는 두가지의 방법이 있습니다.
첫번째 방법은 우선 비어 있는 다이얼로그 창을 만들고, 그 창의 메시지 핸들러 가운데, WM_CREATE 이벤트에서 다이얼로그를 구성하는 컨트롤을 만드는 것입니다.

int CreateMyDialog()
{
	HWND hDlg;
	MSG msg;
 	WNDCLASSEX MyDialogClass;
 
	if (GetClassInfoEx( NULL, "#32770", &MyDialogClass ))
	{
		MyDialogClass.cbSize = sizeof(WNDCLASSEX);
		MyDialogClass.lpfnWndProc = DialogProc;
		MyDialogClass.lpszClassName = "MyDialogClass";
		RegisterClassEx( &MyDialogClass );
	}
	hDlg = CreateWindowEx(WS_EX_DLGMODALFRAME | WS_EX_TOPMOST, "MyDialogClass", (LPCSTR)"Dialog From CreateWindowEx", 
		WS_VISIBLE | WS_SYSMENU | WS_CAPTION, 0, 0, 200, 100, hWnd, NULL, hInst, NULL);
	ShowWindow(hDlg, SW_SHOW);
	UpdateWindow(hDlg);
 
	while (GetMessage(&msg, NULL, 0, 0) != 0) 
	{ 
		if(!IsWindow(hDlg) || !IsDialogMessage(hDlg, &msg))
		{ 
			TranslateMessage(&msg); 
			DispatchMessage(&msg); 
		} 
	} 
	return msg.wParam;
}
 
LRESULT CALLBACK DialogProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	INT_PTR nRetVal= (INT_PTR)FALSE;
 	switch(uMsg) 
	{
	case  WM_INITDIALOG:
		CreateWindowEx(0, (LPCSTR)"STATIC", (LPCSTR)"This is Win32 Sample", WS_CHILD | WS_VISIBLE,
			10, 10, 150, 25, hWndDlg, (HMENU)-1, hInst, NULL);
 
		CreateWindowEx(0, (LPCSTR)"BUTTON", (LPCSTR)"OK", WS_CHILD | WS_VISIBLE,
			10, 40, 100, 25, hWndDlg, (HMENU)IDOK, hInst, NULL);
		break;
 	case WM_COMMAND:
		if (LOWORD(wParam) == IDOK)
		{
			nRetVal= (INT_PTR)TRUE;
			EndDialog(hWndDlg, LOWORD(wParam));
			DestroyWindow(hWndDlg);
		}
		break;
 	case WM_CLOSE:
		DestroyWindow(hWndDlg);
		break;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefDlgProc(hWndDlg, uMsg, wParam, lParam);
		break;
	}
	return nRetVal;
}

이 코드에서는 윈도우의 다이얼로그 클래스의 이름 “#32770″에서 클래스의 기본 정보를 얻고, 사용자 다이얼로그의 클래스를 등록하여 새로운 윈도우를 생성합니다. 연결된 메시지 처리 함수에서는 다이얼로그의 자식 컨트롤을 만들고, 이벤트를 처리합니다.
사실 이런 방법은 너무 고전적이면서도 쉬운 것입니다. 이미 10여년 전에 제 블로그에도 이 내용이 있습니다. 아래의 주소에서 전체 코드를 얻을 수 있습니다.
https://1drv.ms/u/s!An5hHO7t37wbgWvZlOnZTyorAY7J

다른 하나의 방법도 고전적이지만 조금은 어렵습니다. 다만, 스릴을 느낄 수 있는 코드를 즐기는 분이라면 강력하게 추천합니다.
아래의 MSDN 문서에도 설명되어 있지만, 조금만 코를 바꾸어도 실행되지 않는 매우 위험한 코드 입니다.
https://msdn.microsoft.com/ko-kr/library/windows/desktop/ms644996(v=vs.85).aspx#modeless_box
이 페이의 아래의 “Creating a Template in Memory” 부분을 보겠습다. 이 코드는 있는 그대로 실행됩니다. 그러나, 코드를 다음과 같이 고쳐쓰면 정상적으로 실행이 되지 않습니다.
Before: nchar = 1 + MultiByteToWideChar(CP_ACP, 0, “OK”, -1, lpwsz, 50);
After: nchar = 1 + MultiByteToWideChar(CP_ACP, 0, “OKAY!”, -1, lpwsz, 50);

Before: nchar = 1 + MultiByteToWideChar(CP_ACP, 0, “Help”, -1, lpwsz, 50);
After: nchar = 1 + MultiByteToWideChar(CP_ACP, 0, “Help!”, -1, lpwsz, 50);
코드를 실행하는 시스템의 환경에 따라서 다르겠지만, 컨트롤을 표시하는 글자의 수를 어느 정도 늘리면 코드는 실행되지 않습니다.

그래서, 아래와 같이 컨트롤의 텍스트를 WM_INITDIALOG 이벤트에서 처리하도록 고쳐보았습니다.

LRESULT DisplayMyMessage(HINSTANCE hinst, HWND hwndOwner, LPSTR lpszMessage)
{
	HGLOBAL hgbl;
	LPDLGTEMPLATE lpdt;
	LPDLGITEMTEMPLATE lpdit;
	LPWORD lpw;
	LPWSTR lpwsz;
	LRESULT ret;
	int nchar;
 
	hgbl = GlobalAlloc(GMEM_ZEROINIT, 1024);
	if (!hgbl)
		return -1;
	lpdt = (LPDLGTEMPLATE)GlobalLock(hgbl);
 
	// Define a dialog box.
	lpdt->style = WS_POPUP | WS_BORDER | WS_SYSMENU | DS_MODALFRAME | WS_CAPTION;
	lpdt->cdit = 3;         // Number of controls
	lpdt->x = 10;  lpdt->y = 10;
	lpdt->cx = 100; lpdt->cy = 100;
 
	lpw = (LPWORD)(lpdt + 1);
	*lpw++ = 0;             // No menu
	*lpw++ = 0;             // Predefined dialog box class (by default)
 
	lpwsz = (LPWSTR)lpw;
	nchar = 1 + MultiByteToWideChar(CP_ACP, 0, "My Dialog", -1, lpwsz, 50);
	lpw += nchar;
 
	//-----------------------
	// Define an OK button.
	//-----------------------
	lpw = lpwAlign(lpw);    // Align DLGITEMTEMPLATE on DWORD boundary
	lpdit = (LPDLGITEMTEMPLATE)lpw;
	lpdit->x = 10; lpdit->y = 70;
	lpdit->cx = 80; lpdit->cy = 20;
	lpdit->id = IDOK;       // OK button identifier
	lpdit->style = WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON;
 
	lpw = (LPWORD)(lpdit + 1);
	*lpw++ = 0xFFFF;
	*lpw++ = 0x0080;        // Button class
	lpw += 2;
	*lpw++ = 0;             // No creation data
 
	//-----------------------
	// Define a Help button.
	//-----------------------
	lpw = lpwAlign(lpw);    // Align DLGITEMTEMPLATE on DWORD boundary
	lpdit = (LPDLGITEMTEMPLATE)lpw;
	lpdit->x = 55; lpdit->y = 10;
	lpdit->cx = 40; lpdit->cy = 20;
	lpdit->id = ID_HELP;    // Help button identifier
	lpdit->style = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON;
 
	lpw = (LPWORD)(lpdit + 1);
	*lpw++ = 0xFFFF;
	*lpw++ = 0x0080;        // Button class atom
	lpw += 2;
	*lpw++ = 0;             // No creation data
 
	//-----------------------
	// Define a static text control.
	//-----------------------
	lpw = lpwAlign(lpw);    // Align DLGITEMTEMPLATE on DWORD boundary
	lpdit = (LPDLGITEMTEMPLATE)lpw;
	lpdit->x = 10; lpdit->y = 10;
	lpdit->cx = 40; lpdit->cy = 20;
	lpdit->id = ID_TEXT;    // Text identifier
	lpdit->style = WS_CHILD | WS_VISIBLE | SS_LEFT;
 
	lpw = (LPWORD)(lpdit + 1);
	*lpw++ = 0xFFFF;
	*lpw++ = 0x0082;        // Static class
 
	for (lpwsz = (LPWSTR)lpw; *lpwsz++ = (WCHAR)*lpszMessage++;);
	lpw = (LPWORD)lpwsz;
	*lpw++ = 0;             // No creation data
 
	GlobalUnlock(hgbl);
	ret = DialogBoxIndirect(hinst,
		(LPDLGTEMPLATE)hgbl,
		hwndOwner,
		(DLGPROC)DialogProc);
	GlobalFree(hgbl);
	return ret;
}
 
INT_PTR CALLBACK DialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	UNREFERENCED_PARAMETER(lParam);
 	switch (uMsg)
	{
	case WM_INITDIALOG:
		SetWindowText(GetDlgItem(hDlg, IDOK), _T("OKAY!"));
		SetWindowText(GetDlgItem(hDlg, ID_HELP), _T("HELP!"));
		return (INT_PTR)TRUE;
	case WM_COMMAND:
		{
			switch (LOWORD(wParam))
			{
			case IDOK:
			case IDCANCEL:
				EndDialog(hDlg, LOWORD(wParam));
				return (INT_PTR)TRUE;
			case ID_HELP:
				EnableWindow(GetDlgItem(hDlg, IDOK), FALSE);
				break;
				break;
			}
			break;
		}
	}
	return (INT_PTR)FALSE;
}

한가지 더, X64 모드에서는 포인터가 8바이트이므로, lpwAlign 함수의 코드도 아래와 같이 바꾸어 주어야 합니다.

#ifdef _M_IX86
typedef ULONG UINTEGER;
#else
typedef ULONGLONG UINTEGER;
#endif
 
LPWORD lpwAlign(LPWORD lpIn)
{
	UINTEGER ul = 0;
	ul = (UINTEGER)lpIn;
	ul ++;
	ul >>=1;
	ul <<=1;
	return (LPWORD)ul;
}

사용된 예제 코드는 아래의 주소에서 내려받을 수 있습니다.
https://1drv.ms/u/s!An5hHO7t37wbhiDBz3I_VpeWqgV9

Advertisements

2018.05.28

[C#] ManualResetEvent VS AutoResetEvent

Filed under: Programming — Peter_KIM @ 19:35

EventWaitHandle 클래스를 상속받는 두 클래스는 한가지 동작을 빼고는 동일합니다. AutoResetEvent 클래스에서는 WaitOne 함수로 신호를 기다리다가 이벤트의 신호(Signal)를 받으면 다시 신호를 받지 않은 상태(Non-Signaled)로 되돌리지만, ManualResetEvent 클래스에서는 Reset() 함수에 의하여 수동으로 그 상태를 되돌려야 합니다.
다음의 코드에서 둘의 차이를 확인해 볼 수 있습니다.

using System;
using System.Threading;
 
class Program
{
    static AutoResetEvent gAutoHandle = new AutoResetEvent(false);
    static ManualResetEvent gManualHandle = new ManualResetEvent(false);
 
    static void Main()
    {
        // ManualResetEvent
        Thread oThread1 = new Thread(WorkMethod) { Name = "Manual One" };
        oThread1.Start(gManualHandle);
        if (Console.ReadLine() == String.Empty)
            gManualHandle.Set();
        oThread1.Join();
 
        Thread oThread2 = new Thread(WorkMethod) { Name = "Manual Two" };
        oThread2.Start(gManualHandle);
        if (!gManualHandle.WaitOne(0))
        {
            if (Console.ReadLine() == String.Empty)
                gManualHandle.Set();
        }
        oThread2.Join();
 
        // AutoResetEvent
        Thread oThread3 = new Thread(WorkMethod) { Name = "Auto One" };
        oThread3.Start(gAutoHandle);
        if (Console.ReadLine() == String.Empty)
            gAutoHandle.Set();
        oThread3.Join();
 
        Thread oThread4 = new Thread(WorkMethod) { Name = "Auto Two" };
        oThread4.Start(gAutoHandle);
        if (!gAutoHandle.WaitOne(0))
        {
            if (Console.ReadLine() == String.Empty)
                gAutoHandle.Set();
        }
        oThread4.Join();
 
        Console.ReadKey();
    }
 
    public static void WorkMethod(Object objParam)
    {
        Console.WriteLine($"Thread starting. / {Thread.CurrentThread.Name}");
        while (!((EventWaitHandle)objParam).WaitOne(1000))
        {
            var vVal = new Random().Next(1002000);
            Thread.Sleep(vVal);
            Console.WriteLine($"\tWorking: Val => {vVal:0000}  / {Thread.CurrentThread.Name}");
        }
        //((EventWaitHandle) objParam).Reset();
        Console.WriteLine($"Thread ending. / {Thread.CurrentThread.Name}");
    }
} 

WaitOne 함수에서 인자를 “0”으로 설정하면, 쓰레드를 차단하지 않고 단순히 이벤트 핸들의 신호 상태를 반환합니다. 위의 코드를 실행하면 다음과 같은 출력 결과를 얻을 수 있습니다.

Thread starting. / Manual One
        Working: Val => 0566  / Manual One
        Working: Val => 1983  / Manual One

        Working: Val => 1826  / Manual One
Thread ending. / Manual One
Thread starting. / Manual Two
Thread ending. / Manual Two
Thread starting. / Auto One
        Working: Val => 1403  / Auto One
        Working: Val => 1318  / Auto One

Thread ending. / Auto One
Thread starting. / Auto Two
        Working: Val => 0690  / Auto Two
        Working: Val => 0413  / Auto Two

Thread ending. / Auto Two

이 결과에서 볼 수 있듯이 첫번째 쓰레드(oThread1)를 실행하고, 이벤트(ManualResetEvent)의 신호를 설정하면, 두번째 쓰레드(oThread2)는 시작과 함께 종료됩니다. 그러나, 세번째 쓰레드에서 설정한 이벤트(AutoResetEvent)의 신호는 “Thread ending. / Auto One”를 출력하기에 앞서서 신호 없음(non-signaled) 상태로 바뀌게 되어 네번째 쓰레드의 실행에 아무런 영향을 주지 못합니다.
만일, ManualResetEvent 객체를 사용하면서, WaitOne 함수 뒤에서 Reset 함수를 호출하면, AutoResetEvent 객체와 똑같은 기능을 처리할 수 있습니다.

2018.04.26

[C#, WPF] Writing text to image

Filed under: Programming — Peter_KIM @ 14:49

지난 글에서 색상이 있는 이미지를 반전된 흑백의 이미지로 바꾸는 방법에 대하여 살펴보았습니다. WPF 라이브러리는 기존의 GDI 기반으로 복잡하게 처리해야만 할 수 있었던 것들을 매우 간단하게 할 수 있도록 도와줍니다.
이 글에서는 이미지의 위에 글을 추가하는 방법을 살펴보겠습니다.
윈도우에서 그림, 글을 비롯한 어떠한 것들이라도 화면에 표시하거나 인쇄 장치로 출력하기 위해서는 GDI 기반의 함수들을 사용합니다. 주로 C/C++ 언어에서는 이런 함수를 이용하여 복잡한 그래픽 연산을 처리합니다.
https://msdn.microsoft.com/ko-kr/library/windows/desktop/dd145203(v=vs.85).aspx
C/C++ 언어의 경우, 출력할 글꼴의 종류와 문자열이 결정되면 대부분의 경우, GetTextExtentPoint32 등의 함수를 이용하여 문자열이 장치에 출력될 때 차지하는 가로와 세로의 크기 등 출력에 필요한 서식(Format)을 설정하고, 이 값을 이용하여 TextOut 또는 DrawText 함수로 출력을 수행합니다.
https://msdn.microsoft.com/en-us/library/windows/desktop/dd162491(v=vs.85).aspx

이미 있는 이미지의 위 또는 아래, 옆에 텍스트를 쓰고 다시 이미지로 저장을 하기 위해서는, 먼저 가상의 화면에 바탕이 되는 그림을 그리고, 텍스트를 이미지의 형식으로 그리는 작업이 필요합니다.

WPF 라이브러리에서 출력할 텍스트의 서식을 설정할 때에는 FormattedText 클래스를 이용합니다. 이 클래스를 이용하는 방법은 아래의 주소에 매우 잘 설명되어 있습니다.
https://docs.microsoft.com/en-us/dotnet/framework/wpf/advanced/drawing-formatted-text
서식이 완성되면, DrawingVisual 클래스를 이용하여 그림을 그릴 화면을 만듭니다. 그리고, 그림의 내용을 표현하는 DrawingContext 클래스의 함수들을 이용하여 그림을 그리고, 텍스트를 그립니다. 이렇게 그려진 화면의 그림은 RenderTargetBitmap 클래스의 Render 함수를 이용하여, 이미지로 만들 수 있습니다.

대략적인 코드를 표시하면 아래와 같습니다.

BitmapSource oBmpSource = (BitmapSource)this.imgSource.Source;
FormattedText oText = new FormattedText(.....);
DrawingVisual oDrawingVisual = new DrawingVisual();
......
using (DrawingContext oDrawingContext = oDrawingVisual.RenderOpen())
{
    oDrawingContext.DrawImage(......);
    oDrawingContext.DrawText(oText, ......);
}
RenderTargetBitmap oTargetBitmap = new RenderTargetBitmap(......);
oTargetBitmap.Render(oDrawingVisual);

예제 프로그램의 화면은 다음과 같습니다.
TextToImage
원본 그림을 기준으로 텍스트의 위치와 세로 정렬 방법을 설정할 수 있는데, 이것을 위해서는 낮은 수준의 계산이 필요합니다.
프로그램의 전체 소스는 아래의 주소에서 얻을 수 있습니다.
https://1drv.ms/u/s!An5hHO7t37wbhh4lcvzVcBIBqA99

[C#, WPF] Display Enumerations in XAML

Filed under: Programming — Peter_KIM @ 14:12
<Page x:Class="Example"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:System="clr-namespace:System;assembly=mscorlib">
  <Page.Resources>
    <ObjectDataProvider x:Key="DockEnum" MethodName="GetValues" ObjectType="{x:Type System:Enum}" >
      <ObjectDataProvider.MethodParameters>
        <x:Type TypeName="Dock"/>
      </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>
 
    <ObjectDataProvider x:Key="VerticalAlignmentEnum" MethodName="GetValues" ObjectType="{x:Type System:Enum}" >
      <ObjectDataProvider.MethodParameters>
        <x:Type TypeName="VerticalAlignment"/>
      </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>
 
  </Page.Resources>
 
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="*" />
      <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
 
    <ListBox Grid.Row ="0" ItemsSource="{Binding Source={StaticResource DockEnum}}" />
 
    <ComboBox Grid.Row ="1" ItemsSource="{Binding Source={StaticResource VerticalAlignmentEnum}}" />
  </Grid>
 
</Page>

 

[VBS] Encode VBS/JS Script File

Filed under: Programming — Peter_KIM @ 13:59

스크립트 파일은 모든 실행할 코드가 텍스트 형식으로 노출되는 단점이 있습니다. 코드를 조금 아는 사람이라면, 바로 수정해서 뭔가 원래의 코드에서 의도하지 않은 일을 할 수도 있습니다. Windows 스크립트 런타임에서는 완벽한 해결 방법을 될 수 없지만 이런 문제를 약간은 방지할 수 있도록 스크립트 인코딩을 제공합니다.
아래의 페이지에서 “screnc.exe” 명령에 대해서 설명하고 있는데, “Windows 7″을 비롯한 Windows10 환경에서도 프로그램을 찾아볼 수 없습니다. 아마도 없어진 프로그램 같습니다.
https://msdn.microsoft.com/en-us/library/xw61tsx7.aspx
위 페이지에서 설명하는 것과 같은 작업을 위해서는 직접 프로그램을 작성해야만 합니다. Scripting.Encoder 객체의 EncodeScriptFile 함수를 이용하면, VBS, JS 파일을 인코딩할 수 있습니다. 물론 인코딩이 된 상태에서도 스크립트는 정상적으로 동작합니다.
아래의 사이트에서 훌륭한 예제 프로그램을 내려 받을 수 있습니다.
https://gallery.technet.microsoft.com/Encode-and-Decode-a-VB-a480d74c
이 프로그램을 약간 응용하여, JS 파일도 인코딩이 가능하게 만들어 보았습니다. 디코딩의 경우, 원본 파일을 계속 보관하면 되므로, 그다지 필요하지 않아서 넘어갑니다.

사용법은 명령 창에서 다음과 같이 합니다.
EncodeScript.VBS VBS/JS_FilePath

아래는 EncodeScript.VBS 파일에 있는 모든 코드입니다.

Option Explicit
 
Call Main
 
Sub Main()
    If WScript.Arguments.Count = 0 Then
        Call MsgBox("Usage: " & vbCrLf & vbTab & WScript.ScriptName & " VBS/JS_FilePath"vbOKOnly Or vbInformation"Usage")
        WScript.Quit
    End If
 
    Call ConvertToVbe(WScript.Arguments(0))
End Sub
 
Sub ConvertToVbe(sScriptFilePath)
    On Error Resume Next
 
    Dim oFsooScriptFilearrFileNamePartssScriptFileExt
    Dim oEncoderoStreamsVbsContent
    Dim sEncodedStreamsEncodedFilePathoEncodedFile
 
    Set oFso = CreateObject("Scripting.FileSystemObject")
    Set oScriptFile = oFso.GetFile(sScriptFilePath)
    If Err.Number  0 Then
        Call MsgBox("Exception:" & vbCrLf & "    Error number: " & Err.Number & vbCrLf & "    Error description: '" & Err.Description & vbCrLfvbOKOnly Or vbCritical"Exception")
    Else
        arrFileNameParts = Split(sScriptFilePath".")
        sScriptFileExt = arrFileNameParts(UBound(arrFileNameParts- LBound(arrFileNameParts))
 
        Set oEncoder = CreateObject("Scripting.Encoder")
        Set oStream = oScriptFile.OpenAsTextStream(1'ForReading = 1
 
        sVbsContent = oStream.ReadAll
        oStream.Close
        oScriptFile.Close
 
        sEncodedStream = oEncoder.EncodeScriptFile("." & sScriptFileExtsVbsContent0"")
        If (UCase(sScriptFileExt= "VBS"Then
            sEncodedFilePath = Left(sScriptFilePathLen(sScriptFilePath- Len(sScriptFileExt)) & "VBE"
        ElseIf (UCase(sScriptFileExt= "JS"Then
            sEncodedFilePath = Left(sScriptFilePathLen(sScriptFilePath- Len(sScriptFileExt)) & "JSE"
        End If
 
        Set oEncodedFile = oFso.CreateTextFile(sEncodedFilePath)
        oEncodedFile.Write sEncodedStream
        oEncodedFile.Close
    End If
 
    ' Clear Resource
    If Not (oEncodedFile Is NothingThen
        Set oEncodedFile = Nothing
    End If
 
    If Not (oStream Is NothingThen
        Set oStream = Nothing
    End If
 
    If Not (oEncoder Is NothingThen
        Set oEncoder = Nothing
    End If
 
    If Not (oScriptFile Is NothingThen
        Set oScriptFile = Nothing
    End If
 
    If Not (oFso Is NothingThen
        Set oFso = Nothing
    End If
End Sub

2018.03.06

[C#] Three ways to Install/Uninstall Windows services without “InstallUtil.exe”

Filed under: Programming — Peter_KIM @ 14:55
Tags: , , ,

WIN32 프로그램과 달리 .NET Framework 기반으로 만들어진 서비스 프로그램은 “InstallUtil.exe”라는 프로그램을 이용하여, 서비스 제어 관리자(SCM : Service Control Manager)에 등록 또는 제거 할 수 있습니다.
그렇게하기 위하여, 서비스 프로그램의 프로젝트에 아래와 같이 ProjectInstaller 클래스를 추가합니다.

2018030601

이제 InstallUtil.exe 프로그램을 사용하지 않고, 프로그램에 코드를 추가하여 스스로 서비스 제어 관리자에 등록/해제하는 방법을 살펴보겠습니다.
Program.cs 파일에서 Main() 함수를 아래와 같이 수정해보겠습니다.

static void Main(string[] argc)
{
    if (Environment.UserInteractive)
    {
        switch (argc[0])
        {
            case "/i":
                InstallService();
                break;
            case "/u":
                UninstallService();
                break;
        }
    }
    else
    {
        ServiceBase[] ServicesToRun;
        ServicesToRun = new ServiceBase[]
        {
            new TestService()
        };
        ServiceBase.Run(ServicesToRun);
 
    }
}
private static void InstallService()
{
    throw new NotImplementedException();
}
private static void UninstallService()
{
    throw new NotImplementedException();
}

프로그램을 명령 줄에서 특정한 인자(“/i”, “/u”)와 함께 직접 실행할 때, 서비스 설치와 해제를 수행합니다. InstallService(), UninstallService() 함수를 구현하는 방법은 세가지 입니다.

첫째는 ManagegedInstallerClass.InstallHelper 함수를 이용하는 것입니다.

private static void InstallService()
{
    ManagedInstallerClass.InstallHelper(new[] { "/logfile="Assembly.GetExecutingAssembly().Location });
}
private static void UninstallService()
{
    ManagedInstallerClass.InstallHelper(new[] { "/uninstall""/logfile="Assembly.GetExecutingAssembly().Location });
}

위의 코드에서 볼 수 있듯이 함수의 인자를 문자열 배열로 넣어주어야 하는데, 이 값들에는 일정한 규칙이 있습니다. 명령 창에서 “InstallUtil /?”를 수행하면, 다음과 같이 표시될 것입니다.

  • 사용법: InstallUtil [/u | /uninstall] [option […]] assembly [[[option […]] assembly] […]]

함수의 인자에 설정할 값들은 InstallUtil 프로그램이 사용하는 것과 마찬가지로 순서를 맞추어 주어야 합니다.

둘째는 AssemblyInstaller 클래스를 이용하는 것입니다.

private static void InstallService()
{
    AssemblyInstaller oAssemblyInstaller = new AssemblyInstaller(Assembly.GetExecutingAssembly(), new[] { "/logfile=" });
    IDictionary oSavedState = new Hashtable();
    oSavedState.Clear();
    oAssemblyInstaller.Install(oSavedState);
    oAssemblyInstaller.Commit(oSavedState);
}
private static void UninstallService()
{
    AssemblyInstaller oAssemblyInstaller = new AssemblyInstaller(Assembly.GetExecutingAssembly(), new[] { "/logfile=" });
    oAssemblyInstaller.Uninstall(null);
}

이 때에도 마찬가지로 InstallUtil 프로그램에서 사용하는 인자를 이용하여, 설치와 해제를 조정할 수 있습니다.

셋째는 프로젝트에서 추가한 ProjectInstaller 클래스를 이용하는 것입니다.
우선, ProjectInstaller.Designer.cs 파일의 가장 아래에 있는 두 멤버의 액세스 한정자를 “private”에서 “internal” 또는 “public”으로 바꿉니다.

internal System.ServiceProcess.ServiceProcessInstaller serviceProcessInstaller1;
internal System.ServiceProcess.ServiceInstaller serviceInstaller1;

그리고 다음과 같이 코드를 작성합니다.

private static void InstallService()
{
    ProjectInstaller oProjInst = new ProjectInstaller();
    ServiceInstaller oSvcInstaller = oProjInst.serviceInstaller1;
    oSvcInstaller.Context = new InstallContext();
    oSvcInstaller.Context.Parameters["assemblypath"= Assembly.GetExecutingAssembly().Location;
    IDictionary oSavedState = new Hashtable();
    oSavedState.Clear();
    oSvcInstaller.Install(oSavedState);
    oSvcInstaller.Commit(oSavedState);
}
private static void UninstallService()
{
    ProjectInstaller oProjInst = new ProjectInstaller();
    ServiceInstaller oSvcInstaller = oProjInst.serviceInstaller1;
    oSvcInstaller.Context = new InstallContext();
    oSvcInstaller.Uninstall(null);
}

“assemblypath” 역시 InstallUtil 프로그램이 내부적으로 사용하는 인자로, InstallUtil 프로그램으로 어셈블리를 설치하는 과정의 로그를 확인하면, 볼 수 있습니다.

2018.03.01

[C#, WPF] Invert Black-White Bitmap

Filed under: Programming — Peter_KIM @ 11:56
Tags: , , , , ,

원본 Bitmap 파일을 읽고, 단색의 Bitmap 파일로 변환하여 다시 검정색과 흰색을 뒤집는 코드입니다. 기존의 C# 코드에서는 매우 복잡한 알고리즘을 직접 만들어 사용해야 했으나, WPF 관련 라이브러리에서는 이런 작업을 매우 쉽게 할 수 있게 도와줍니다.

  • Bitmap File ==> BirmapImage  ==> WriteableBitmap ==> PNG File
  • 비트맵의 데이터 부분을 배열로 가져오기 위하여 CopyPixels 함수를 이용합니다.
  • 색을 뒤집기 위해서는 비트맵 파일의 헤더를 제외한 데이터 값들의  “0”과 “1”을 바꾸어 주어야 합니다.  0xFF 값으로 XOR 연산을 수행하면 됩니다.
  • 연산이 완료된 데이터를 WritePixels 함수로 Bitmap 이미지에 다시 기록합니다.
public void PrintBitmap(String sBmpFilePath, String sTargetPngFilePath)
{
    List<Byte> lstBmpPrint = new List<byte>();
    BitmapImage oBmpImg = new BitmapImage(new Uri(sBmpFilePath));
    WriteableBitmap oWriteableBmp = InvertBitmapColor(oBmpImg);
 
    PngBitmapEncoder oPngEnc = new PngBitmapEncoder();
    oPngEnc.Frames.Add(BitmapFrame.Create(oWriteableBmp)); 
    using (FileStream oFs = new FileStream(sTargetPngFilePath, FileMode.Create))
        oPngEnc.Save(oFs);
}
public WriteableBitmap InvertBitmapColor(BitmapImage oSrcBmpImg)
{
    FormatConvertedBitmap o1BppBmp = ConvertBitmapFormat(oSrcBmpImg, PixelFormats.BlackWhite);
    WriteableBitmap oWriteableBmp = new WriteableBitmap(o1BppBmp);
    int nStride = oWriteableBmp.BackBufferStride;
 
    Byte[] arrBitmapData = new byte[nStride * oWriteableBmp.PixelHeight];
    oWriteableBmp.CopyPixels(arrBitmapData, nStride, 0);
    // Invert Color
    for (int i = 0; i < arrBitmapData.Length; ++i)
        arrBitmapData[i] ^= 0xFF;
 
    oWriteableBmp.WritePixels(new Int32Rect(00, oSrcBmpImg.PixelWidth, oSrcBmpImg.PixelHeight), arrBitmapData, nStride, 0);
    return oWriteableBmp;
}
public FormatConvertedBitmap ConvertBitmapFormat(BitmapImage oSrcBmpImg, PixelFormat pf)
{
    FormatConvertedBitmap newFormatedBitmap = new FormatConvertedBitmap();
 
    newFormatedBitmap.BeginInit();
 
    newFormatedBitmap.Source = oSrcBmpImg;
    newFormatedBitmap.DestinationFormat = pf;
 
    newFormatedBitmap.EndInit();
 
    return newFormatedBitmap;
}

2018.01.15

[C# / WinForm] Keyword-based web URL collector

Filed under: Programming — Peter_KIM @ 12:56

google.com, baidu.com 등의 웹 검색 사이트들은 자신들만의 검색 API 함수를 웹에 노출하여 사용자에게 제공하기도 합니다만, 유료 회원으로 등록해야하고 검색량에 따라서 비용도 만만하지 않게 들어가게 됩니다.

웹 브라우저를 이용하여, 웹을 검색하고 검색된 결과 페이지에서 웹 주소들을 수집하는 도구를 만들어 보았습니다. Fiddler 라이브러리는 HTTPS 요청을 처리하기 위하여 사용되었으며, SQLite 라이브러리는 각각의 웹 검색사이트에서 수집된 웹 주소를 저장하기 위하여 사용되었습니다.

이 밖에도 CSV 파일로 결과를 출력하기 위하여, Jonathan Wood 씨의
CsvFileWriter(http://www.blackbeltcoder.com/) 클래스도 사용되었습니다.

프로그램의 Form 클래스와 UserControl 클래스는 확장된 컨트롤을 포함하고 있으므로, Visual Studio 디자인 화면에서 올바르게 표시되지 않을 수 있습니다.
이 프로그램을 사용하려면, SQL Server 또는 SQL Express 인스턴스가 필요합니다. 그리고, 데이터베이스에 연결하기 위한 문자열은 “CollectUrl.exe.config” 파일에서 직접 수정해야 합니다.
먼저 데이터베이스를 새로 만들고, 아래의 쿼리를 이용하여 4개의 테이블을 만듭니다.

USE [master]
GO
 
CREATE DATABASE [CollectionSetting]
GO

CREATE TABLE [dbo].[TBL_Category] (
    [Code] BIGINT         			NOT NULL,
    [Name] NVARCHAR (128) 			NOT NULL,
    PRIMARY KEY CLUSTERED ([Code] ASC)
);
 
CREATE TABLE [dbo].[TBL_Keyword] (
    [Keyword]      NVARCHAR (64) 		NOT NULL,
    [CategoryCode] BIGINT        		NOT NULL,
    PRIMARY KEY CLUSTERED ([Keyword] ASC),
    CONSTRAINT [FK_TBL_Keyword_TBL_KeywordCategory] FOREIGN KEY ([CategoryCode]) REFERENCES [dbo].[TBL_Category] ([Code])
);
 
CREATE TABLE [dbo].[TBL_SearchEngine] (
	[EngineName]		NVARCHAR (50)	NOT NULL,
	[QueryUrl]		NVARCHAR (MAX)	NOT NULL,
	[ResultsCount]		SMALLINT	NOT NULL,
	[PagingStart]		SMALLINT	NOT NULL,
	[PagingEnd]		SMALLINT	NOT NULL,
	[PagingIncrement]	SMALLINT	NOT NULL,
	[SearchPattern]		NVARCHAR (MAX)	NULL,
	CONSTRAINT [PK_TBL_SearchEngine] PRIMARY KEY CLUSTERED ([EngineName] ASC)
);
 
CREATE TABLE [dbo].[TBL_WebUrlInfo] (
	[HashValue]		UNIQUEIDENTIFIER	NOT NULL,
	[CategoryCode]		BIGINT			NOT NULL,
	[Url]			VARCHAR (256)		NOT NULL,
	CONSTRAINT [PK_TBL_WebUrlInfo] PRIMARY KEY CLUSTERED ([HashValue] ASC),
	CONSTRAINT [FK_TBL_WebUrlInfo_TBL_Category] FOREIGN KEY ([CategoryCode]) REFERENCES [dbo].[TBL_Category] ([Code])
);

웹 검색에 사용하는 검색어(TBL_Keyword)는 카테고리(TBL_Category)로 나누어져 관리할 수 있습니다. 예를 들어, “탈 것”이라는 카테고리에 “배”, “비행기”, “버스” 등을 등록할 수 있습니다.
검색 사이트 정보(TBL_SearchEngine)에는 사이트의 주소와 질의 패턴, HTML 응답 페이지 설정을 할 수 있도록 구성했습니다. 웹 주소 정보(TBL_WebUrlInfo)는 분석된 웹 주소에 GUID 값을 연결하여 나중에 빠른 검색을 지원하도록 했습니다.
이렇게 만들어진 테이블에 아래의 쿼리를 이용하여 데이터를 삽입합니다. (응용 프로그램에서 하나하나 집어넣어도 됩니다.)

USE [CollectionSetting]
GO
INSERT [dbo].[TBL_Category] ([Code], [Name]) VALUES (268435457, N'Porn')
GO
INSERT [dbo].[TBL_Keyword] ([Keyword], [CategoryCode]) VALUES (N'anal', 268435457)
GO
INSERT [dbo].[TBL_Keyword] ([Keyword], [CategoryCode]) VALUES (N'Anal sex', 268435457)
GO
INSERT [dbo].[TBL_Keyword] ([Keyword], [CategoryCode]) VALUES (N'Ass', 268435457)
GO
INSERT [dbo].[TBL_Keyword] ([Keyword], [CategoryCode]) VALUES (N'cumshot', 268435457)
GO
INSERT [dbo].[TBL_Keyword] ([Keyword], [CategoryCode]) VALUES (N'dildo', 268435457)
GO
INSERT [dbo].[TBL_Keyword] ([Keyword], [CategoryCode]) VALUES (N'Drug', 268435459)
GO
INSERT [dbo].[TBL_Keyword] ([Keyword], [CategoryCode]) VALUES (N'Facial', 268435457)
GO
INSERT [dbo].[TBL_Keyword] ([Keyword], [CategoryCode]) VALUES (N'Hardcore', 268435457)
GO
INSERT [dbo].[TBL_Keyword] ([Keyword], [CategoryCode]) VALUES (N'milf', 268435457)
GO
INSERT [dbo].[TBL_Keyword] ([Keyword], [CategoryCode]) VALUES (N'Pornstar', 268435457)
GO
INSERT [dbo].[TBL_Keyword] ([Keyword], [CategoryCode]) VALUES (N'Pussy', 268435457)
GO
INSERT [dbo].[TBL_SearchEngine] ([EngineName], [QueryUrl], [ResultsCount], [PagingStart], [PagingEnd], [PagingIncrement], [SearchPattern]) VALUES (N'Baidu', N'http://www.baidu.com/s?wd={Keyword}&pn={Page}&rn={ResultCount}', 100, 0, 500, 100, N'(?:\>)([A-Za-z0-9_-]*(?:[.][A-Za-z0-9_-]+){1,})(?:/)')
GO
INSERT [dbo].[TBL_SearchEngine] ([EngineName], [QueryUrl], [ResultsCount], [PagingStart], [PagingEnd], [PagingIncrement], [SearchPattern]) VALUES (N'Daum', N'http://search.daum.net/search?w=dir&q={Keyword}&lpp={ResultCount}&p={Page}', 100, 1, 51, 10, N'https*(?:://|%3A%2F%2F)([A-Za-z0-9_-]*(?:[.][A-Za-z0-9_-]*){1,})')
GO
INSERT [dbo].[TBL_SearchEngine] ([EngineName], [QueryUrl], [ResultsCount], [PagingStart], [PagingEnd], [PagingIncrement], [SearchPattern]) VALUES (N'Google', N'https://encrypted.google.com/search?q={Keyword}&num={ResultCount}&start={Page}', 100, 0, 500, 100, N'https*(?:://|%3A%2F%2F)([A-Za-z0-9_-]*(?:[.][A-Za-z0-9_-]*){1,})')
GO
INSERT [dbo].[TBL_SearchEngine] ([EngineName], [QueryUrl], [ResultsCount], [PagingStart], [PagingEnd], [PagingIncrement], [SearchPattern]) VALUES (N'Naver', N'http://web.search.naver.com/search.naver?where=webkr&query={Keyword}&start={Page}', 0, 1, 5, 1, N'https*(?:://|%3A%2F%2F)([A-Za-z0-9_-]*(?:[.][A-Za-z0-9_-]*){1,})')
GO
INSERT [dbo].[TBL_SearchEngine] ([EngineName], [QueryUrl], [ResultsCount], [PagingStart], [PagingEnd], [PagingIncrement], [SearchPattern]) VALUES (N'Yahoo', N'https://search.yahoo.com/search?p={Keyword}&n={ResultCount}&b={Page}', 100, 1, 501, 100, N'https*(?:://|%3A%2F%2F)([A-Za-z0-9_-]*(?:[.][A-Za-z0-9_-]*){1,})')
GO

이렇게 데이터베이스를 준비한 뒤에는 아래와 같이, 프로젝트의 “app.config” 파일에서  연결 문자열을 수정해야 합니다.

  <appSettings>
    <add key="DatabaseConnectionString" value="Data Source=.;Initial Catalog=CollectionSetting;Integrated Security=True" />
  </appSettings>

코드를 빌드하면 실행 파일(CollectUrl.exe)이 만들어지는데, 이 파일을 실행하면, 다음과 같은 화면을 만나게 됩니다.
1
확인 버튼을 클릭하고 잠시 기다려면, 다음과 같이 SSL 인증서 추가 정보(경고)를 알리는 창이 나타납니다.
20180114_2
“예”를 클릭해야 앞으로 HTTPS 프로토콜의 검색 사이트에서 웹 주소를 수집할 수 있습니다. 만일 “아니오”를 클릭했다면, HTTP 검색 사이트에서만 작업을 할 수 있습니다.
프로그램의 첫 화면은 아래와 같습니다.
20180114_3
공장 분위기를 물씬 내고 있습니다. (화려함은 디자이너의 몫으로 프로그래머가 하기에는 너무도 버거운 일이 아닐 수 없습니다. 사실 이 GUI 화면을 만드는데에도 Pixel 수 세고, 컨트롤 배치하며 며칠이 걸렸습니다.)
“Next” 버튼을 클릭하고, 화면이 바뀌면 아래와 같이 “Category” 옆의 “Refresh” 버튼을 클릭하고, 목록에서 항목을 선택합니다. 다음, “Keyword” 옆의 “Refresh” 버튼을 클릭하면, 검색어 목록이 나타나게됩니다. “+ – ↑ ↓” 버튼은 각각 항목의 추가, 제거, 순서 조정 기능을 수행합니다.
20180114_4
웹 검색에 필요한 키워드를 모두 선택했다면, 다시 “Next” 버튼을 클릭합니다. 마찬가지로 위에 있는 Refresh 버튼을 클릭합니다. 그리고나면, 검색 사이트와 쿼리 패턴등이 목록에 표시됩니다. 검색 사이트 옆의 “Keyword” 열의 영역을 두번 클릭하면, 이전 화면에서 선택한 키워드를 다시 선택할 수 있도록 창이 나타납니다. 키워드를 선택하고, “OK” 버튼을 클릭합니다.
20180114_5
각각의 웹 사이트에 대한 질의 주소(Query Url)는 웹 브라우저에서 쉽게 얻을 수 있습니다. 반복적으로 검색을 수행하다보면, 일정한 질의 패턴을 파악할 수 있습니다. 또, “Paging” 열을 클릭하면, 다음과 같은 화면이 나타나게됩니다.
20180114_6
검색 패턴은 웹 검색으로 수신된 HTML 문서에서 웹 주소를 얻기 위한, 정규식입니다. 웹 사이트에 따라서, HTML 문서의 형태가 다양하므로, 이 패턴을 얻기 위해서는 많은 시행착오를 해야할 것입ㄴ니다.
검색 사이트와 키워드를 선택하였으면, “Next” 버튼을 클릭합니다. “RUN” 버튼을 클릭하면 검색이 진행됩니다. (google 사이트는, 여러번 반복하면 경고를 만날 수 있으니, 적당히 해야합니다. 또, 한국의 검색 사이트는 성인 인증 등의 이유로 특정 검색어에 대한 결과는 거부 될 수 있습니다.)
20180114_7
검색이 완료되면, “Next” 버튼을 클릭합니다. 사이트 별로 검색 결과가 나타나게 되는데, 검색된 사이트를 선택하고 “Insight” 버튼을 클릭하면, 그 사이트를 다시 한번 더 검색하게 됩니다.
20180114_8
계속하여, 다시 검색된 사이트를 선택하여, “Insight” 버튼을 클릭할 수도 있습니다.
“R” 버튼을 누르면 앞의 검색 동작의 결과로 트리의 항목을 다시 설장하며, “Insight” 검색으로 발견된 웹 사이트는 제거됩니다.
“Candidate” 버튼을 클릭하면, 오른쪽 목록으로 선택된 사이트들이 복사됩니다. 복사된 사이트는 MD5 해시 값을 갖게 됩니다. 이것은 문자열 비교보다 숫자(128비트 정수) 비교가 더 빠르기 때문입니다.
20180114_9
오른쪽에서 “Visit” 항목에 체크하고, 웹 사이트를 클릭하면 웹 브라우저가 실행되고 해당 사이트를 방문하게됩니다. “Verify” 버튼을 클릭하면, 데이터베이스에 저장된 정보를 검색하여 사이트의 카테고리를 표시합니다. 만일, 후보로 선정된 사이트들이 키워드와 관계없는 것들이라면, “-” 버튼으로 제거할 수 있습니다.
“Import”, “Export” 버튼을 누르면, 후보 사이트의 목록을 CSV 파일에서 읽거나, CSV 파일로 쓸 수 있습니다.
“Save” 버튼을 누르면, 검색된 결과를 데이터베이스로 저장할 수 있습니다.
“Next” 버튼을 누르면, 아래의 화면이 나타납니다. 검색된 결과를 SQLite 파일로 저장할 수 있습니다.
20180114_10
프로그램을 종료하면, 아래와 같이 인증서 제거에 대한 경고 화면이 나타납니다. “Yes” 버튼을 클릭하면, 프로그램 시작 때에 등록된 인증서를 제거할 수 있습니다.
20180114_11

사용된 코드에 대한 설명은 나중에 기회가 된다면, 다른 글에서 설명해 보겠습니다. 코드에 많은 주석이 있으므로, 관심있게 본다면 충분히 이해할 수 있을 것으로 보입니다.

전체 코드는 아래의 주소에서 얻을 수 있습니다.
https://1drv.ms/u/s!An5hHO7t37wbhh2YwLP_AB8eTICX

2018.01.11

[C/C++] printf / std::cout with Colour

Filed under: Programming — Peter_KIM @ 09:02

GUI 운영 체제가 대세이긴 하지만, DOS 환경을 겪었던 오래된 프로그래머에게는 명령 프롬프트(터미널) 창이 더 편리한 경우가 많이 있습니다. 리눅스 서버에서 작업하는 경우에는 필수겠지만, 사실 윈도우에서도 가끔은 명령 프롬프트 창이 편리할 때도 많이 있습니다.

Console 창에서 printf  또는  cout 함수를 이용하여, 색깔이 있는 글자를 출력하기 위해서 코드를 만들어 보았습니다.
윈도우의 명령 프롬프트에서는 Escape 문자(ANSI 코드)가 동작하지 않아서, Win32 API 기능을 이용하여, 색깔을 표시할 수 있습니다.
std::cout 함수를 printf 함수로 교체하고, std::ostringstream 함수를 sprintf 함수로 교체하여 손질하면, C 코드에서도 사용할 수 있습니다.

#include <stdio.h>
#include <sstream>
#include <iostream>
 
#ifdef _WIN32
    #include <windows.h> 
#endif
 
#ifdef _WIN32
enum ForeColour {
    Default    = 0x0008,
    Black      = 0x0000,
    Blue       = 0x0001,
    Green      = 0x0002,
    Cyan       = 0x0003,
    Red        = 0x0004,
    Magenta    = 0x0005,
    Yellow     = 0x0006,
    White      = 0x0007,
};
#else
enum ForeColour {
    Default    = 0,
    Black      = 30,
    Red        = 31,
    Green      = 32,
    Yellow     = 33,
    Blue       = 34,
    Magenta    = 35,
    Cyan       = 36,
    White      = 37,
};
#endif
 
void PrintConsole(const char *lpszText, short nColor)
{
#ifdef _WIN32
    HANDLE  hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleTextAttribute(hConsole, nColor | 0x0008);
#else
    std::ostringstream oss;
    oss << "\033[1;" << nColor << "m";
    std::cout << oss.str();
#endif
 
    std::cout << lpszText;
 
#ifdef _WIN32
    SetConsoleTextAttribute(hConsole, 0x0008);
#else
    std::cout << "\033[0m";
#endif
}
 
int main()
{
   PrintConsole("Black"  , ForeColour::Black);
   PrintConsole("Red"    , ForeColour::Red);   
   PrintConsole("Green"  , ForeColour::Green); 
   PrintConsole("Yellow" , ForeColour::Yellow);
   PrintConsole("Blue"   , ForeColour::Blue);  
   PrintConsole("Magenta", ForeColour::Magenta);
   PrintConsole("Cyan"   , ForeColour::Cyan); 
   PrintConsole("White"  , ForeColour::White); 
 
   return 0;
}

2018.01.10

[C++ 11] JSON of The C++ REST SDK (Codename “Casablanca”)

Filed under: Programming — Peter_KIM @ 16:06

C# 또는 JAVA 언어를 사용하는 이유는 현대의 프로그램 개발에 필요한 많은 라이브러리들이 제공되기 때문일 것입니다. 그러나, 예전부터 사용해왔던 C++ 언어는 새롭게 등장하는 언어에 비하면 그 흔한 라이브러리들이 빈약한 것이 사실입니다.
The C++ REST SDK (Codename “Casablanca”) 라이브러리에는C#, JAVA, 더구나 스크립트 언어조차도 제공하는 JSON Parser 코드가 포함되어 있습니다. 이것을 분리하여 조금만 잘 손질하면 대부분의C++ 컴파일러에서 사용할 수 있습니다. JSON 파서만 필요한 상황에서 “Casablanca”를 모두 컴파일 할 필요는 없을 것으로 보입니다. (https://github.com/Microsoft/cpprestsdk)
아래와 같이 “Casablanca”에서 JSON Parser 작성에 필요한 몇몇 파일을 가져옵니다.
asyncrt_utils.h, basic_types.h, cpprest_compat.h, json.h, nosal.h
asyncrt_utils.cpp, json.cpp, json_parsing.cpp, json_serialization.cpp
이 파일들만으로도 훌륭한 Json Parser 라이브러리가 될 수 있지만, 사용자 편의를 위하여 클래스를 만들어 보겠습니다.

#pragma once
 
#if defined(__GNUC__)
    #define __STDC_WANT_LIB_EXT1__ 1
#elif defined(_MSC_VER)
    #define _CRT_SECURE_NO_WARNINGS
#endif /* gcc must compile with "-std=c11" */
 
#include <cstdio>
#include <sstream>
#include <vector>
#include <iterator>
#include "basic_types.h"
#include "json.h"
 
using namespace web;
using namespace web::json;
 
class ParseJson final
{
    public:
        ParseJson();
        virtual ~ParseJson();
 
        void PrintJson(const json::value& jObject, utility::string_t sTab = U(""));
        std::vector<utility::string_t> SplitText(const utility::string_t &sText, utility::char_t chDelimiter);
        const json::value *FindSingleNode(const json::value& jObject, const utility::string_t& sKey);
        const json::value *FindSingleNode(const json::value& jObject, std::vector<utility::string_t>::const_iterator& itKey, std::vector<utility::string_t>::const_iterator& itKeyEnd);
        const json::value *FindDeeply(const json::value& jObject, const utility::string_t& sKeys);
 
        bool AddKeyValue(json::value* pjObject, const utility::string_t& sKey, const json::value& jValue);
        bool SetValue(json::value *pjObject, const utility::string_t& sNodes, const json::value& jValue);
        bool AddChildNode(json::value *pjObject, const utility::string_t& sNodes, const utility::string_t& sKey, const json::value& jValue);
 
        size_t AddToArray(json::value& jObject, const json::value& jValue);
        const bool IsNullJson(json::value& jObject);
    private:
        const json::value m_jNullValue;
};

XML 문서와 JSON 문서 모두 TREE 구조를 갖는 특징이 있으므로, 노드(Node, Tag)를 검색하고 그 값을 변경하기 쉽도록 만든 클래스 입니다. 만들고보니 괜찮은 클래스인 듯합니다.
이 클래스를 사용한 예제 코드는 아래와 같습니다.

#ifdef _WIN32
int wmain(int argc, wchar_t** argv)
#else
int main(int argc, char** argv)
#endif
{
    /* String of Json type. */
    const utility::string_t sJson(
        U("{\"glossary\":{\"title\":\"example glossary\",\"GlossDiv\":{\"title\":\"S\",\"GlossList\":")
        U("{\"GlossEntry\":{\"ID\":\"SGML\",\"SortAs\":\"SGML\",\"GlossTerm\":\"Standard Generalized ")
        U("Markup Language\",\"Acronym\":\"SGML\",\"Abbrev\":\"ISO 8879:1986\",\"GlossDef\":{\"para\"")
        U(":\"A meta-markup language, used to create markup languages such as DocBook.\",")
        U("\"GlossSeeAlso\":[\"GML\",\"XML\"]},\"GlossSee\":\"markup\"}}}}}"));
    /* Json Parser */
    ParseJson oJsonParser;
 
    /* Create a json object from string. */
    json::value objJson = json::value::parse(sJson);
 
    PrintConsole(U("Original: "), ForeColour::Green);
    PrintConsole(objJson, ForeColour::Yellow);
    
    /* Print json object */
    oJsonParser.PrintJson(objJson);
 
    utility::string_t sNode((argc == 2) ? argv[1]: U("glossary/GlossDiv/GlossList/GlossEntry/GlossDef")); 
    /* Retrieve value of Node */
    const json::value *pjValue = oJsonParser.FindDeeply(objJson, sNode);
 
    PrintConsole(U("Find Test: ["), ForeColour::Blue); 
    PrintConsole(sNode.c_str(), ForeColour::Cyan);
    PrintConsole(U("]\n"), ForeColour::Blue);
    if (pjValue == nullptr)
        PrintConsole(U("NULL VALUE!\n"), ForeColour::Red);
    else
        PrintConsole(*pjValue, ForeColour::Yellow);
 
    /* Add a new node */
    bool bAdd = oJsonParser.AddKeyValue((json::value *)pjValue, U("Fruit"), json::value::string(U("Apple")));
    if (bAdd) {
        PrintConsole(U("Add Key&Value:\n"), ForeColour::Blue); 
        PrintConsole(objJson, ForeColour::Yellow);
    } else {
        PrintConsole(U("Cannot add...\n"), ForeColour::Red);
    }
 
    /* Change node's value */
    bool bSet = oJsonParser.SetValue(&objJson, U("glossary/GlossDiv/title"), json::value::number(9322506));
    if (bSet) {
        PrintConsole(U("Changed Value:\n"), ForeColour::Blue);
        PrintConsole(objJson, ForeColour::Green);
    } else {
        PrintConsole(U("Cannot change...\n"), ForeColour::Red);
    }
    
    json::value oJArray;
    oJsonParser.AddToArray(oJArray, json::value::string(U("BUS")));
    oJsonParser.AddToArray(oJArray, json::value::string(U("TRUCK")));
    oJsonParser.AddToArray(oJArray, json::value::string(U("TAXI")));
    oJsonParser.AddToArray(oJArray, json::value::number(1234567890));
 
    /* Change node's value to array */
    bool bReplace = oJsonParser.SetValue(&objJson, U("payload/custglossary/GlossDiv"), oJArray);
    if (bReplace) {
        PrintConsole(U("Replaced with Array:\n"), ForeColour::Blue);
        PrintConsole(objJson, ForeColour::Yellow);
    } else {
        PrintConsole(U("Cannot replace...\n"), ForeColour::Red);
    }
    
    /* Append a child */
    bool bAppend = oJsonParser.AddChildNode(&objJson, U("glossary"), U("vehicles"), oJArray);
    if (bAppend) {
        PrintConsole(U("Append with Array:\n"), ForeColour::Blue);
        PrintConsole(objJson, ForeColour::Green);
    } else {
        PrintConsole(U("Cannot append...\n"), ForeColour::Red);
    }
    return 0;
}

전체 코드는 아래의 주소에서 구할 수 있습니다.

https://1drv.ms/u/s!An5hHO7t37wbhhlmNGns_3FcYS-O

Next Page »

Blog at WordPress.com.

%d bloggers like this: