I'm on your side, when times get rough.

2018.12.23

[C/C++] Smart Card Control (NFC)

Filed under: Programming — Peter_KIM @ 09:01

NFC 리더기와 카드를 제어하려면 Win32 API 가운데에서, “SCard”로 시작하는 함수들에 대한 선행 학습이 필요합니다.
보통 아래의 순서대로 함수들을 사용할 것입니다.

0. SCardEstablishContext
—-1. SCardConnect
——–2. SCardBeginTransaction
————3. SCardTransmit
——–4. SCardEndTransaction
—-5. SCardDisconnect
6. SCardReleaseContext

NFC 리더기에 카드를 가까이 가져다 대는 데에는 일정한 시간이 필요합니다. 경우에 따라서 일정 시간이 지나게되면, 그 작업을 취소하기도 해야합니다.
업무적인 관점에서는 특정 시점에 그 기능을 활성화해야 할 필요도 있습니다.
이렇게 NFC 카드를 효과적으로 제어하려면 위 함수들을 별개의 쓰레드에서 처리 해야합니다.
예제 프로그램에서는 아래의 그림과 같이 두 개의 버튼을 만들어서 읽기 준비와 종료를 제어합니다.

아래의 코드에서는 쓰레드를 제어하기 위해서 WaitForSingleObject 함수를 이용합니다.

VOID StartReading()
{
    DisableBuzzer(ThreadParam.ReaderName);
    ResetEvent(ThreadParam.EndEventHandle);
 
    // Create a thread to read from the smartcard.
    ThreadHandle = CreateThread(
        NULL,
        0,
        (LPTHREAD_START_ROUTINE)ReadingProc,
        (LPVOID)(&ThreadParam),
        0,
        NULL);
}
 
VOID EndReading()
{
    if (ThreadParam.EndEventHandle)
    {
        SetEvent(ThreadParam.EndEventHandle);
    }
 
    if (ThreadHandle)
    {
        if (WaitForSingleObject(ThreadHandle, 5000) == WAIT_TIMEOUT)
        {
            TerminateThread(ThreadHandle, 0);
        }
        CloseHandle(ThreadHandle);
        ThreadHandle = NULL;
    }
}
 
DWORD WINAPI ReadingProc(LPVOID pvParam)
{
    SCARDCONTEXT hContext = 0;
    SCARDHANDLE hSmartCard = 0;
 
    LONG nRetCode = 0;
    DWORD dwActiveProtocol = 0;
 
    SCARD_IO_REQUEST oIoRequest = { 0x00 };
 
    BYTE arrSendBuffer[] = { 0xFF, 0xCA, 0x00, 0x00, 0x00 };
    DWORD dwSendLength = sizeof(arrSendBuffer) / sizeof(BYTE);
    BYTE arrRecvBuffer[0x0010] = { 0x00 };
    DWORD dwRecvLength = 0x0010;
 
    nRetCode = SCardEstablishContext(SCARD_SCOPE_USER, NULL, NULL, &hContext);
    if (nRetCode != SCARD_S_SUCCESS)
    {
        ((ScardThreadParam*)pvParam)->RecvCallBack(0, nRetCode);
        return 0;
    }
 
    do
    {
        SCARD_READERSTATE oState = { 0x00 };
        oState.szReader = ((ScardThreadParam*)pvParam)->ReaderName;
 
        nRetCode = SCardGetStatusChange(hContext, 3000, &oState, 1);
        if (nRetCode == SCARD_S_SUCCESS)
        {
            if ((oState.dwEventState & SCARD_STATE_PRESENT) == SCARD_STATE_PRESENT)
            {
                nRetCode = SCardConnect(hContext, ((ScardThreadParam*)pvParam)->ReaderName, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T1, &hSmartCard, &dwActiveProtocol);
                if (nRetCode == SCARD_S_SUCCESS)
                {
                    nRetCode = SCardBeginTransaction(hSmartCard);
                    if (nRetCode == SCARD_S_SUCCESS)
                    {
                        oIoRequest.cbPciLength = 8;
                        oIoRequest.dwProtocol = dwActiveProtocol;
 
                        nRetCode = SCardTransmit(hSmartCard, &oIoRequest, arrSendBuffer, dwSendLength, &oIoRequest, arrRecvBuffer, &dwRecvLength);
 
                        if (nRetCode == SCARD_S_SUCCESS)
                        {
                            RtlCopyMemory(((ScardThreadParam*)pvParam)->RecvBuffer, arrRecvBuffer, min(dwRecvLength, 0x0010));
                            ((ScardThreadParam*)pvParam)->RecvCallBack((WPARAM)min(dwRecvLength, 0x0010), (LPARAM)((ScardThreadParam*)pvParam)->RecvBuffer);
                        }
                        else
                        {
                            ((ScardThreadParam*)pvParam)->RecvCallBack(0, nRetCode);
                        }
                        SetEvent(((ScardThreadParam*)pvParam)->EndEventHandle);
                    }
                    SCardEndTransaction(hSmartCard, SCARD_LEAVE_CARD);
                }
                else
                {
                    ((ScardThreadParam*)pvParam)->RecvCallBack(0, nRetCode);
                }
                SCardDisconnect(hSmartCard, SCARD_UNPOWER_CARD);
            }
        }
        else
        {
            ((ScardThreadParam*)pvParam)->RecvCallBack(0, nRetCode);
            SetEvent(((ScardThreadParam*)pvParam)->EndEventHandle);
        }
 
    } while (WaitForSingleObject(((ScardThreadParam*)pvParam)->EndEventHandle, 10) == WAIT_TIMEOUT);
 
    SCardReleaseContext(hContext);
 
    return 0;
}

예제 프로그램에서는 “ACS ACR1252” 기종의 카드 리더를 사용했습니다.
전체 프로그램은 아래의 주소에서 내려받을 수 있습니다.
https://1drv.ms/u/s!An5hHO7t37wbhiTT9LTEoLiSHBdM

Advertisements

2018.11.29

[C#] Detect the Architecture of a Process

Filed under: Programming — Peter_KIM @ 08:41

Win32 API 함수를 이용하면, 단순히 정수 또는 포인터의 크기를 비교하는 알고리즘보다 좀 더 상세한 실행 중인 프로세스의 아키텍처의 정보를 알 수 있습니다.

internal struct SYSTEM_INFO
{
    internal ushort wProcessorArchitecture;
    internal ushort wReserved;
    internal uint dwPageSize;
    internal IntPtr lpMinimumApplicationAddress;
    internal IntPtr lpMaximumApplicationAddress;
    internal IntPtr dwActiveProcessorMask;
    internal uint dwNumberOfProcessors;
    internal uint dwProcessorType;
    internal uint dwAllocationGranularity;
    internal ushort wProcessorLevel;
    internal ushort wProcessorRevision;
}
 
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern IntPtr LoadLibrary(string fileName);
 
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true, ThrowOnUnmappableChar = true, BestFitMapping = false)]
internal static extern IntPtr GetProcAddress(IntPtr module, string procName);
 
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern void GetSystemInfo(ref SYSTEM_INFO lpSystemInfo);
 
[DllImport("kernel32.dll", SetLastError = true)]
[returnMarshalAs(UnmanagedType.Bool)]
internal static extern bool FreeLibrary([InIntPtr module);
 
private delegate bool IsWow64ProcessDelegate(IntPtr hProcess, out bool bWow64Process);
 
private String GetCurrentProcessArchitecture()
{
    String sProcArchitecture = "Unknown";
 
    IntPtr hModule = LoadLibrary("kernel32.dll");
    try
    {
        if (hModule != IntPtr.Zero)
        {
            var procAddress = GetProcAddress(hModule, "IsWow64Process");
            if (procAddress == IntPtr.Zero)
            {
                sProcArchitecture = "X86";
            }
            else
            {
                var funcPointer =
                    (IsWow64ProcessDelegate)Marshal.GetDelegateForFunctionPointer(procAddress,
                        typeof(IsWow64ProcessDelegate));
                var bWow64Process = false;
                if (funcPointer(Process.GetCurrentProcess().Handle, out bWow64Process))
                {
                    if (bWow64Process)
                    {
                        sProcArchitecture = "X86";
                    }
                    else
                    {
                        var lpSystemInfo = new SYSTEM_INFO();
                        GetSystemInfo(ref lpSystemInfo);
                        switch (lpSystemInfo.wProcessorArchitecture)
                        {
                            case 0:
                                sProcArchitecture = "X86";
                                break;
                            case 5:
                                sProcArchitecture = "ARM";
                                break;
                            case 6:
                                sProcArchitecture = "IA64";
                                break;
                            case 9:
                                sProcArchitecture = "X64";
                                break;
                            case 12:
                                sProcArchitecture = "ARM64";
                                break;
                            case 0xFFFF:
                                sProcArchitecture = "Unknown";
                                break;
                            default:
                                sProcArchitecture = (string)null;
                                break;
                        }
                    }
                }
            }
        }
    }
    finally
    {
        if (hModule != IntPtr.Zero)
            FreeLibrary(hModule);
    }
    return sProcArchitecture;
}

2018.11.28

[C#] RGB TO HSL (Hue-Saturation-Luminosity)

Filed under: Programming — Peter_KIM @ 07:14

어떤 기능을 개발하는데 있어서, 알고리즘을 모두 잘 알고 있다면 스스로 모든 코드를 만들 수 있지만, 요즘의 프로그램 개발에 있어서는 복잡하고 어려운 모든 코드를 스스로 만들기 보다는 누군가에 의하여 만들어진 코드를 이용하는 때가 더 자주 일어납니다. 물론, 라이선스에서 이야기하는 것을 어기지 않아야 합니다.
.NET Framework 위에서 만들어지는 모든 EXE, DLL 파일들은 dotPeek(JetBrains) 같은 프로그램을 이용하면, 다시 원본 소스 코드를 얻을 수 있습니다.

RGB 색상의 값을 HSL(Hue-Saturation-Luminosity) 값으로 바꾸는 함수가 필요하여 열심히 찾아보았습니다.
이런 기능을 그냥 .NET Framework 라이브러리로 제공하면 좋겠습니다만, 다행히도 Visual Studio SDK 파일들 중에 이 기능을 구현한 함수가 있었습니다.
https://docs.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.modeling.diagrams.hslcolor?view=visualstudiosdk-2017
그런데, 응용 프로그램에서 관련 DLL 파일을 참조하여 배포하는 것이 제한적이라서, 해당 파일이 가진 소스 코드를 이용하여 클래스를 가져왔습니다.

// Decompiled with JetBrains decompiler
// Type: Microsoft.VisualStudio.Modeling.Diagrams.HslColor
// Assembly: Microsoft.VisualStudio.Modeling.Sdk.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// MVID: 849EB318-2AE3-4A32-81E2-661A61D298B8
// Assembly location: C:\Windows\Microsoft.NET\assembly\GAC_MSIL\Microsoft.VisualStudio.Modeling.Sdk.14.0\v4.0_14.0.0.0__b03f5f7f11d50a3a\Microsoft.VisualStudio.Modeling.Sdk.14.0.dll
 
using System;
using System.Drawing;
 
[Serializable]
public class HslColor
{
    private int hue;
    private int luminosity;
    private int saturation;
 
    public HslColor(int hue, int saturation, int luminosity)
    {
        Hue = hue;
        Saturation = saturation;
        Luminosity = luminosity;
    }
 
    public HslColor()
    {
    }
 
    public int Hue
    {
        get { return hue; }
        set { hue = Math.Min(240Math.Max(0value)); }
    }
 
    public int Saturation
    {
        get { return saturation; }
        set { saturation = Math.Min(240Math.Max(0value)); }
    }
 
    public int Luminosity
    {
        get { return luminosity; }
        set { luminosity = Math.Min(240Math.Max(0value)); }
    }
 
    public static HslColor Black { get; } = new HslColor(16000);
 
    public static HslColor White { get; } = new HslColor(1600240);
 
    public Color ToRgbColor()
    {
        var red = 0;
        var green = 0;
        var blue = 0;
        if (luminosity != 0)
        {
            if (saturation == 0)
            {
                int num;
                red = num = luminosity * byte.MaxValue / 240;
                green = num;
                blue = num;
            }
            else
            {
                var m2 = luminosity > 120
                    ? luminosity + saturation - (luminosity * saturation + 120/ 240
                    : (luminosity * (240 + saturation) + 120/ 240;
                var m1 = 2 * luminosity - m2;
                red = HueToRgb(m1, m2, hue + 80);
                green = HueToRgb(m1, m2, hue);
                blue = HueToRgb(m1, m2, hue - 80);
            }
        }
 
        return Color.FromArgb(red, green, blue);
    }
 
    private static int HueToRgb(int m1, int m2, int hue)
    {
        if (hue < 0 && hue + 240 >= hue)
            hue += 240;
        if (hue > 240 && hue - 240 <= hue)
            hue -= 240;
        return ((6 * hue >= 240
                    ? 2 * hue >= 240 ? 3 * hue >= 480 ? m1 : m1 + ((m2 - m1) * (160 - hue) + 20/ 40 : m2
                    : m1 + ((m2 - m1) * hue + 20/ 40* byte.MaxValue + 120/ 240;
    }
 
    public static HslColor FromRgbColor(Color color)
    {
        var hslColor = new HslColor();
        var num1 = (intMath.Max(Math.Max(color.R, color.G), color.B);
        var num2 = (intMath.Min(Math.Min(color.R, color.G), color.B);
        hslColor.Luminosity = ((num1 + num2) * 240 + byte.MaxValue) / 510;
        if (num1 == num2)
        {
            hslColor.Saturation = 0;
            hslColor.Hue = 160;
        }
        else
        {
            hslColor.Saturation = hslColor.Luminosity > 120
                ? ((num1 - num2) * 240 + (510 - num1 - num2) / 2/ (510 - num1 - num2)
                : ((num1 - num2) * 240 + (num1 + num2) / 2/ (num1 + num2);
            var num3 = ((num1 - color.R) * 40 + (num1 - num2) / 2/ (num1 - num2);
            var num4 = ((num1 - color.G) * 40 + (num1 - num2) / 2/ (num1 - num2);
            var num5 = ((num1 - color.B) * 40 + (num1 - num2) / 2/ (num1 - num2);
            var num6 = (int) color.!= num1
                ? (int) color.!= num1 ? 160 + num4 - num3 : 80 + num3 - num5
                : num5 - num4;
            hslColor.Hue = num6 >= 0 ? num6 <= 240 ? num6 : num6 - 240 : num6 + 240;
        }
 
        return hslColor;
    }
}

2018.10.01

[C#] Excel To DataSet with OpenXML

Filed under: Programming — Peter_KIM @ 02:56

Excel Sheet 문서의 행과 열을 DataSet 형태로 변환하는 소스 코드는 이미 몇몇의 사이트에 공개되어 있습니다.
많은 기술들 중에서 가장 좋지 않은 것이 바로, OLE 방식으로 Excel 프로그램과 직접적인 통신으로 데이터를 변환하는 것입니다. 시대가 바뀌고, 응용 프로그램의 기술이 발전한 상황에서 이런 구 시대의 기술을 이용하는 것은 매우 불합리한 경우가 대부분입니다.
OpenXML 기술을 이용하면, Office 문서들을 쉽게 프로그램 코드에서 사용할 수 있는 형태로 변환할 수 있습니다.
https://github.com/OfficeDev/Open-XML-SDK
https://docs.microsoft.com/en-us/office/open-xml/open-xml-sdk

아래의 주소에 이미 OpenXML 기술을 이용하여, Excel 문서를 DataTable 형태로 변환하는 코드가 있습니다.
https://dotnetthoughts.net/read-excel-as-datatable-using-openxml-and-c/

이 코드를 확장하고, 수정하여 Excel 문서에 있는 여러 개의 Sheet 데이터를 처리할 수 있도록 코드를 수정해 보았습니다.

public DataSet OpenExcelToDataSet(string sFileName)
{
    DataSet ds = null;
    using (SpreadsheetDocument oSpreadSheetDoc = SpreadsheetDocument.Open(sFileName, false))
    {
        WorkbookPart oWorkbookPart = oSpreadSheetDoc.WorkbookPart;
 
        OpenXmlElementList lstOpenXmlElements = oSpreadSheetDoc.WorkbookPart.Workbook.ChildElements;
        foreach (Sheets vSheets in lstOpenXmlElements.OfType<Sheets>())
        {
            ds = new DataSet();
 
            foreach (Sheet vSheet in vSheets)
            {
                DataTable dt = new DataTable();
                dt.Name = vSheet.Name;
                String vRelationId = vSheet.Id.Value;
                WorksheetPart oWorksheetPart = (WorksheetPart) oWorkbookPart.GetPartById(vRelationId);
                Worksheet oWorksheet = oWorksheetPart.Worksheet;
 
                OpenXmlElementList lstOpenXmlChildElements = oWorksheet.ChildElements;
                foreach (SheetData vSheetData in lstOpenXmlChildElements.OfType<SheetData>())
                {
                    IEnumerable<Row> enumRows = vSheetData.Descendants<Row>();
                    if (enumRows.Count() == 0)
                        continue;
                    
                    var vFirstRow = enumRows.First();
 
                    // Make Columns with first row...
                    IEnumerable <Cell> enumFirstRowCells = vFirstRow.Descendants<Cell>();
                    for (int i = 0; i < enumFirstRowCells.Count(); ++i)
                    {
                        Cell vCell = enumFirstRowCells.ElementAt(i);
                        String sCellValue = vCell.CellValue?.InnerXml;
                        if ((vCell.DataType != null)
                            && (vCell.DataType.Value == CellValues.SharedString))
                        {
                            sCellValue = oWorkbookPart.SharedStringTablePart.SharedStringTable.ChildElements[Int32.Parse(sCellValue)].InnerText;
                        }
                        dt.Columns.Add(sCellValue);
                    }
 
                    foreach (Row vRow in enumRows)
                    {
                        DataRow dtRow = dt.NewRow();
 
                        IEnumerable<Cell> enumCells = vRow.Descendants<Cell>();
                        for (int i = 0; i < enumCells.Count(); ++i)
                        {
                            Cell vCell = enumCells.ElementAt(i);
                            String sCellValue = vCell.CellValue?.InnerXml;
                            if ((vCell.DataType != null)
                                && (vCell.DataType.Value == CellValues.SharedString))
                            {
                                sCellValue = oWorkbookPart.SharedStringTablePart.SharedStringTable.ChildElements[Int32.Parse(sCellValue)].InnerText;
                            }
                            dtRow[i] = sCellValue;
                        }
                        dt.Rows.Add(dtRow);
                    }
                    dt.Rows.RemoveAt(0);
                    ds.Tables.Add(dt);
                }
            }
        }
    }
    return ds;
}

예제 프로젝트는 아래의 주소에서 내려받을 수 있습니다.
https://1drv.ms/u/s!An5hHO7t37wbhiKsWmpK7m4GemOd

[Windows] IP Setting with Command Line (BATCH)

Filed under: Programming — Peter_KIM @ 02:35
@ECHO OFF
IF '%1' == 'S' GOTO STATIC
IF '%1' == 's' GOTO STATIC
IF '%1' == 'D' GOTO DHCP
IF '%1' == 'd' GOTO DHCP
 
GOTO END
    
:DHCP
    netsh interface ipv4 set address name="Ethernet" source=dhcp
    netsh interface ipv4 set dnsserver "Ethernet" dhcp
    GOTO END
 
:STATIC
    netsh interface ipv4 set address name="Ethernet" source=static addr=192.168.100.32 mask=255.255.255.0 gateway=192.168.100.1
    netsh interface ipv4 add dnsservers "Ethernet" address=8.8.8.8 index=1
    netsh interface ipv4 add dnsservers "Ethernet" address=8.8.4.4 index=2
    GOTO END
:END

 

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

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
Next Page »

Create a free website or blog at WordPress.com.

%d bloggers like this: