Few months ago I tried to learn a bit about GPU programming and I took notes a started to write this post. I am publishing this now even though it's not complete, however being too busy, I am not sure whether I will have the time to get back to this later.

Since couple years CUDA (*2007) and OpenCL (*2008) have established themselves as standard frameworks for parallel programming on the GPU. In 2011 new framework came to the landscape of GPGPU programming, which does not compete on the same level, but rather represents new abstraction between the GPU and the developer.

AMP stands for Accelerated Massive Parallelism and it is an open C++ standard. The implementation of AMP provided by Microsoft consists of three main features:

  • C++ language features and compiler
  • Runtime
  • Programming framework containing classes and functions to facilitate parallel programming

As well as OpenCL and unlike CUDA, AMP is a standard not only for GPU programming but for any data-parallel hardware. Typical example of such a hardware is the vector unit of standard CPU, capable of executing SIMD (Single Instruction Multiple Data) instructions. Or in the near future a cloud based cluster. AMP is an abstraction layer which can be used to target different devices from different vendors.

The following diagram depicts how AMP C++ fits to the world of GPGPU programming.

Microsoft has implemented AMP on top of DirectCompute technology and it is the only production ready implementation. Currently there are also two Proof Of Concepts AMP implementations Shelvin park project done by Intel and open source modification of the LLVM compiler (more information here). Needless to say that the adoption of such standard will depend a lot on whether these implementations will be taken further and whether we will have real implementation based on CUDA.

First look at the code

Without further introduction let's take a look at the classical "Hello World" of GPGPU programming: Addition of elements of two vectors (here without the boiler plate code).

parallel_for_each(e,[=](index<2>idx) restrict(amp){
 c[idx] = a[idx] + b[idx];
});

If you have ever tried out OpenCL or CUDA, you should find this piece of code less verbose and quite easy to read. That is the result of the main ideas behind AMP C++:

  • Full C++ - unlike CUDA or OpenCL, AMP is not a C like dialect.
  • Minimalist API - AMP hides as much as it can from the developer.
  • Future proofed - the API is designed for heterogeneous programming. In the future GPU should be only one of it's usages. It aims to be a standard for programming on distributed systems.

Before going futher with more examples we have to provide basic description of GPU architecture as it is crutial to understanding certain parts of AMP

GPU architecture

Here I will give a high level overview of the GPU architecture. If you are already familiar with the GPU architecture, feel free to skip this section.

Currently there are two main producers of GPU chips: NVidia and ATI (AMD). A little behind comes Intel. Each of them constructs graphic cards with different architecture. Moreover the architectures change significantly with the realease of any new version. Nevertheless certain concepts are shared by all of them. I will describe here shortly the NVidias Fermi and ATIs Cypress architecture. The succesor of Fermi is called Kepler.

Processor architecture

The GPU is composed of hundreds of pipelined cores. The cores are grouped to computation units. NVidia calls these units Symmetric Multiprocessors. Each computation unit is assigned a unit of work.

ATI uses slightly different abstraction. Similary to NVidia the "cores" are grouped to Symmetric Multiprocessors, however each core is called Thread Processor and is a capable of executing VLIV (Very Long Instruction Word) instructions. It has therefore 4 arithmetic logical units and one Special Function Unit. The compiler has to find independent operations and construct the VLIW instruction. If the compiler is not able to find these independent operations than one or more of the ALUs will not be performing any operation.

Memory access

The GPU has a global memory which can be accessed by all cores. This access is quite costly (hundreds of cycles). Each simetric multiprocessor in turn has a small scratch-pad memory which is called usually local memory. This memory can be accessed only by the threads running on the given SM. The access to local memory is much cheaper (around 10 cycles). This memory can either take a role of a cache or can be managed by the developer directly. In addition each core has its own general purpose registers, used to perform the computations.

GPUs usually run computations on data which is accessed only once, unlike CPUs where the same memory pages are often used repetively. For this reason it is better for the developer to control the local memory. That's why historically the local memory did not act as cache on GPUs. However this will probably change in the future, and some form of caching will be provided by the GPU directly.

Scheduling

Scheduling is essential because it allows the effective usage of the processor. Since the memory access is expensive, other operations might be executed while the processor is waiting for the data to come. NVidia processors have "costless" context switching, so while one thread is blocked other can take over. Therefore the number of threads scheduled might affect the computation performance, while if not enough threads are available for scheduling some might be waiting for the memory page loads.

The programming models comparison:

These architectural concepts are used by NVIDIA and ATI. Each of the 3 GPU programming models (CUDA, OpenCL, AMP) can has its own dialects and namings. The following table shows the terms used by all three technologies. We will discuss the AMP terms used in this table later in the article.

Term CUDA AMP C++ OpenCL
Basic unit of work thread thread work-item
The code executed on one item kernel kernel kernel
The unit processing one group of working units Streaming multiprocessor - Compute unit
Cache-style memory accessible by grouped threads shared memory tile static memory local memory
Group of working units sharing local memory warp tile work-group

The tools offered by each framework

Now when the architecture of GPU has been described we can define the tools which each framework needs to provide. In the next part of the article I will try to describe how AMP adresses these issues.

  • Tell the compiler which part of the code will be offloaded to the GPU
  • Provide constructs to work with multidimensional vectors
  • Provide a way to transfer the data between the GPU and the CPU
  • Give the developer tools to write efficient programs. That is to address GPU specific problems such as memory access

AMP detailed description and more examples

Here is again the first example with the little of boiler plate code that we have to write to make it work:

void vectorsAddition_Parallel(vector<float> vA, vector<float> vB, vector<float>vC, int M, int N){
 extent<2> e(M,N);
 array_view<float,2> a(e, vA), b(e,vB);
 array_view<float,2> c(e, vC);


 //capture of the data using array_view -> results in the copy of the data into the accelerators memmory.
 parallel_for_each(e,[=](index<2>idx) restrict(amp){
  c[idx] = a[idx] + b[idx];
 });
}

The code sent and executed on the GPU is called "kernel" (here one line of code). The kernel is passed to the GPU in the form of lambda expression through the parallel_for_each method. This static method is the entry point to AMP. This method takes two parameters parallel_for_each(extent, delegate). The extend parameter describes the dimensionality of the data. The delegate encapsulates the logic which will be executed. The logic is usually defined as an anonymous function which takes the index<N> as parameter. The computation is expressed using this index on previously defined arrays. In the aboved sample the c[idx] = a[idx] + b[idx] simply means, that for each index (and index goes from 0,0 to N,N since it is two dimensional index) the elements at these positions of the arrays will be added and stored in the array c. Of course that this code is not executed sequentially, but instead defined as a set of vector operations which are scheduled on the GPU

The extent as well as the index parameter are templated. The index identifies one unique position in N dimensional array. The extent describes the dimensions of the computation domain.

The array_view class takes care of the copy of the data to and from the GPU. When the computation is finished, the synchronized method is called on the vC array_view. This call will synchronize the C vector with the array_view. To give the complete information, note also that there is an array class which behaves similary, having few inconvenience and advantages. This post gives a good comparison of these two classes.

The following example ilustrates some of the dificulties which we can have when writing parallel code. The code simply sums all the elements in an array in parallel way. The parallelization of addition of items requires a bit of engineering, even though the sequential solution is evident. Here I am using a technique which is not really efficient but demonstrates the principles of parallelization. Several techniques to parallelize the task are described in this article.

float sumArray_NaiveAMP(std::vector<float> items){
 auto size = items.size();
 array_view<float, 1> aV (size, items);
 
 for(int i=1;i<size;i=2*i){
  parallel_for_each(extent<1>(size/2), [=] (index<1> idx) restrict(amp)
  {
   aV[2*idx*i] = aV[2*idx*i] + aV[2*idx*i+i];
  });
 }

 return aV[0];
}

The algorihtm adds each two neighbouring items and stores the result in the first item. This has to be repeated until the addition of the whole array is stored in the first position in the array. As described in the mentioned article this approach is not memory efficient and optimizations exists. Note also that the synchronize method is not called on the array_view at the end of the computation. That is because, we don't want the modified data to be copied back from the GPU to the main memory, we are only interested in the sum of the elements.

Another example here is the computation of the standard deviation of the values in an array. First step is the computation of the avarage of the array. To obtain the avarege we have to first add the elements in the array (using the previous example). Having the average, we can obtain the distance of each element from the average. Once we have the distance of each element, we have to make another addition before taking the final square root and obtaining the standard deviation.

float standatd_deviation(vector<float> vA) {
 
 float size = vA.size();
 
 extent<1> e((int)size);
 vector<float> vDistance(size);
 
 array_view<float, 1> a(e, vA);
 array_view<float, 1> distance(e,vDistance);
 
 float dispertion = 0;
 float total = 0; 
 total = sumArray_NaiveAMP(vA);
 float avg = total/size;

 parallel_for_each(
  e, 
  [=](index<1> idx) restrict(amp) {
    distance[idx] = (a[idx] - avg)*(a[idx] - avg);

 });

 distance.synchronize();
 dispertion = sumArray_NaiveAMP(vDistance);
 return sqrt(dispertion/size);
}

This algorithm has 3 parallelized parts: the two sums at the beginning and at the end, and than the calculation of the distance of each element.

Looking at both of the preceding examples, you might be wondering why the code is so complex and you might think that the sum of the elements in the array could be just written as:

float sum = 0;
parallel_for_each(
 e, 
 [=](index<1> idx) restrict(amp) {
   sum+=a[idx];

});

However if you think a bit more, you should understand that the code in the parallel_for_each runs essential in the same time. All the parallel threads would like to increment the sum variable at the same time. In addition to that, this code would not even compile, while the modifications of variables captured "by value" are not allowed and in this example the sum variable is captured by value. If you are not familiar with the different capture types refer to this page.

Here is one more example which ilustrates how index and extent work, it is the second hello world of parallel computing: matrix multiplication. This example come from this MSDN page.

void matrixMultiplicationWithAMP(vector<float> &vC, vector<float> vA, vector<float> vB, int M, int N) {
 
 extent<2> e(M,N);

 array_view<float, 2> a(e, vA);
 array_view<float, 2> b(e,vB);
 array_view<float, 2> product(e, vC);
 

 parallel_for_each(
  product.extent, 
  [=](index<2> idx) restrict(amp) {
   int row = idx[0];
   int col = idx[1];
   for (int inner = 0; inner < M; inner++) {
    product[idx] += a(row,inner) * b(inner,col);
   }
 }
 );

 product.synchronize();
}

Note that the resulting vector vC i passed to the method as reference, since it's content is modified by the synchronize call. Also note, that this example assumes that the vectors passed to the function contain two dimensional array of size (N,N). Since AMP supports multidimensional indexes, AMP runs over all the columns and all the rows automatically, just by iterating over the two-dimensional index. The inner loop just sums the multiplications of the elements in the current row of the left matrix and the current column of the right matrix.

Moving the data between GPU and CPU

As mentioned before, the array_view and array classes are used to transfer the data between the CPU and GPU. The array class directly copies the data to the GPUs global memory. However the data from the array has to be then sent manually back to the CPUs memmory. On the other hand the array_view class works as a wrapper. The vector passed to the array_view will in the background copy the data from and to the vector which is passed in as parameter.

Memory access and manipulation on AMP

As described above, the developer has to address the GPU and adapt the algorithm to the architecture. This basically means minimize the access to global memmory and optimize the threads to use the local memmory. This process is called tiling in the AMP's parlance and the local memmory is called tile-static memory.

If the developer does not define any tiling, the code will be tiled by default. In order to use the local memory efficiently, algorithm has to be tiled manualy. Parallel_for_each method has a second overload which accepts tile_extent as a parameter and the code receives tiled_index in the lambda. Similary as the extend the tile_extend specifies the dimensionality of the computation domain, but also separates the whole computation domain into several tiles. Each tile is than treated by one symetrical multiprocessor, therefor all the threads in the tile can share the local memory and benefit from the fast memory access. If you want to read a bit more about tiling visit this page.

About the future of AMP

As said at the beginning AMP is a standard and as any standard it is dependent of it's implementations. Currently there are two existing implementations of the AMP standard. Microsoft implemented AMP on top of Direct Compute technology. Direct Compute is a part of Microsoft's DirectX suite, which was originally suited only to multimedia programming. Microsoft added DirectComputed to the DirectX suite in order to enable GPGPU programming and with AMP C++ provides an easy way to manipulate the API. The second implementation is very recent and was developed by Intel under the code name Shelvin Park. This implementation builds on top of OpenCL.

Summary

Clearly the success of the standard depends on whether other implementations targeting CUDA and OpenCL will emerge. Microsoft cooperated with NVidia and AMD during the development of the API. The idea of having a clear C++ standard to define the parallel computation is great. Latest C++ is quite modern language and provides nice constructs, so actually the programming using AMP C++ is quite fun and not that much pain.

Links

Introduction and few samples Parallel Programing in Native Code - blog of the AMP team

CUDA architecture evolution

GeForce GTX 680 Kepler Architecture

Comparison between CUDA and OpenCL (though little outdated 2011-06-22)

http://msdn.microsoft.com/en-us/library/hh553049.aspx

Introduction to Tiling

Implementation of LLVM & Clang to support AMP C++ on NVidia

블로그 이미지

맨오브파워

한계를 뛰어 넘어서..........

,

Introduction

Visual Studio Macro 몇가지 예제를 통해 활용에 관한 내용을 다뤄 봅니다.

보시다시피 VS 매크로는 초딩 정광짱도 하는 VB 스크립트로 되어 있습니다.

Visual Studio .NET 2003을 기준으로 설명하겠습니다.

 

.h/.cpp 파일 전환

    ' if this file is a .cpp then open up the .h 
    '  or vice versa
    Sub OpenPartnerFile()
        Dim filename As String
        Dim partnerFilename As String
        filename = DTE.ActiveDocument.FullName
        If (filename.EndsWith(".h")) Then
            partnerFilename = filename.Substring(0, filename.Length() - 2) + ".cpp"
        End If
        If (filename.EndsWith(".cpp")) Then
            partnerFilename = filename.Substring(0, filename.Length() - 4) + ".h"
        End If
        DTE.ItemOperations.OpenFile(partnerFilename)
    End Sub

 

이 매크로는 Visual Assist Alt+O와 동일한 기능입니다.

'DTE'는 스크립트를 통해 Visual Studio IDE와 통신할 수 있는 자동화 객체입니다.

위의 예제에서 DTE.ActiveDocument.FullName는 현재 열린 파일의 풀패스를 가리킵니다.

DTE.ItemOperations.OpenFile 메써드로 지정한 파일을 열 수 있습니다.

 

다음과 같이 사용자 정의 매크로를 추가할 수 있습니다:

1. 도구->매크로->매크로 IDE(Alt+F11)

2. 프로젝트 탐색기에서 MyMacros를 선택합니다.

3. 오른쪽 버튼을 눌러서 추가->모듈 추가를 선택합니다. 모듈 이름은 알아서 입력합니다.

4. 위의 예제를 복사치기하고 저장합니다.

5. 매크로 탐색기에서 추가한 매크로(OpenPartnerFile)를 더블클릭하여 실행할 수 있습니다.

 

다음과 같이 매크로의 단축키를 지정할 수 있습니다:

1. 도구->사용자 지정->키보드

2. 그림과 같이 지정할 매크로를 선택합니다.

3. 바로 가기 키 누르기를 클릭하고 단축키를 입력합니다.

4. 할당 버튼을 눌러서 등록합니다.

 

Macros for Calling External Tools

간혹 현재 열린 파일을 아구먼트로 넘겨서 외부 프로그램을 실행해야할 경우가 있습니다. 있다고 칩시다.

다음 예제에서 메모장을 실행하는 방법을 알아 보겠습니다.

 

    Sub OpenNotepadCurFile()
        Dim filename As String = DTE.ActiveWindow.Document.FullName
        Dim commandString = "notepad.exe """ + filename + """"
        'MsgBox(commandString)
        ChDir("d:\windows\system32")
        Shell(commandString, AppWinStyle.NormalFocus, True, 100)
    End Sub

 

ChDir("d:\windows\system32")는 Notepad.exe가 있는 시스템 폴더로 이동합니다.

Shell 명령으로 메모장을 실행합니다. 파라메터는 보면 그냥 압니다.

 

Macros that Communicate with the Code Editor

소스 편집기의 내용을 조작하는 예제를 보시겠습니다.

다음은 선택 영역을 중괄호로 감싸고 들여쓰기하는 예제입니다.

 

    ' Takes the current selection, indents it, and surrounds with braces.
    Sub CreateCodeBlock()
        CreateCodeBlock_Helper("{", "}")
    End Sub

 

    ' Takes the current selection, indents it, and surrounds it with startOfBlock and endOfBlock
    Sub CreateCodeBlock_Helper(ByVal startOfBlock As String, ByVal endOfBlock As String)
        Dim sel As TextSelection = DTE.ActiveDocument.Selection
        Dim objAnchor As VirtualPoint = sel.AnchorPoint
        Dim bottomPoint As VirtualPoint = sel.BottomPoint

        Dim trimmedString As String = sel.Text.TrimStart()
        Dim numChars As Integer = sel.Text.Length - trimmedString.Length
        Dim spaceString As String = sel.Text.Substring(0, numChars)

        objAnchor.CreateEditPoint().Insert(spaceString + startOfBlock + vbCrLf)
        bottomPoint.CreateEditPoint().Insert(spaceString + endOfBlock + vbCrLf)
        sel.Indent()

        'sel.LineUp()
        'sel.Text = "{" + vbCrLf + sel.Text + "}" + vbCrLf
    End Sub

 

다음은 이름과 날짜를 입력하는 예제입니다:

 

    ' Inserts name and date
    Sub Signature()
        Dim sel As TextSelection = DTE.ActiveDocument.Selection
        sel.Insert("// Seungwoo Oh ")
        sel.Insert(Format(Date.Today, "yyyy-MM-dd"))
        sel.Insert(".")
    End Sub

 

다음은 수평 라인 주석을 입력하는 예제입니다:

 

    Sub InsertHorizontalLine()
        Dim lineString = "//----------------------------------------------------------------------------" + vbCrLf
        Dim sel As TextSelection = DTE.ActiveDocument().Selection()
        sel.Text() = lineString
    End Sub

 

System.Guid 객체로 GUID를 생성할 수도 있습니다.

 

    REM Insert a new Guid at whatever the cursor has selected
    Sub InsertGuid()
        Dim ts As TextSelection = DTE.ActiveDocument.Selection
        Dim guid As System.Guid = System.Guid.NewGuid()
        ts.Insert(guid.ToString().ToUpper())
    End Sub


Conclusion

Visual Studio는 매우 융통성있는 매크로 언어를 제공합니다.

파일 및 프로젝트 작업시 IDE의 내부 지식은 상당히 효과적입니다.

 

 

출처: Visual Studio Macros for the Game Programmer by Mike Cline

블로그 이미지

맨오브파워

한계를 뛰어 넘어서..........

,

다음은 "윈도우 환경에서 특정 포트를 리슨하고 있는, 프로세스의 정보 조회 방법"에 대해서 설명하고 있습니다.

#pragma comment(lib, "ws2_32.lib")
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <string.h>

#if defined NTDDI_VERSION && (NTDDI_VERSION >= NTDDI_LONGHORN)
#  include <Iphlpapi.h>
/* Windows Vista Higher                 */
#else

#  ifndef ANY_SIZE
#    define ANY_SIZE 1
#  endif

    /* for Windows 2K, Windows XP, Windows 2003 */
    typedef struct _MIB_TCPROW_OWNER_PID
    {
        DWORD       dwState;
        DWORD       dwLocalAddr;
        DWORD       dwLocalPort;
        DWORD       dwRemoteAddr;
        DWORD       dwRemotePort;
        DWORD       dwOwningPid;
    } MIB_TCPROW_OWNER_PID, *PMIB_TCPROW_OWNER_PID;

    typedef struct _MIB_TCPTABLE_OWNER_PID
    {
        DWORD                dwNumEntries;
        MIB_TCPROW_OWNER_PID table[ANY_SIZE];
    } MIB_TCPTABLE_OWNER_PID, *PMIB_TCPTABLE_OWNER_PID;

    typedef struct _MIB_TCP6ROW_OWNER_PID
    {
        UCHAR           ucLocalAddr[16];
        DWORD           dwLocalScopeId;
        DWORD           dwLocalPort;
        UCHAR           ucRemoteAddr[16];
        DWORD           dwRemoteScopeId;
        DWORD           dwRemotePort;
        DWORD           dwState;
        DWORD           dwOwningPid;
    } MIB_TCP6ROW_OWNER_PID, *PMIB_TCP6ROW_OWNER_PID;

    typedef struct _MIB_TCP6TABLE_OWNER_PID
    {
        DWORD                   dwNumEntries;
        MIB_TCP6ROW_OWNER_PID   table[ANY_SIZE];
    } MIB_TCP6TABLE_OWNER_PID, *PMIB_TCP6TABLE_OWNER_PID;

    typedef struct _MIB_UDPROW_OWNER_PID {
        DWORD dwLocalAddr;
        DWORD dwLocalPort;
        DWORD dwOwningPid;
    } MIB_UDPROW_OWNER_PID, *PMIB_UDPROW_OWNER_PID;

    typedef struct _MIB_UDPTABLE_OWNER_PID
    {
        DWORD                   dwNumEntries;
        MIB_UDPROW_OWNER_PID    table[ANY_SIZE];
    } MIB_UDPTABLE_OWNER_PID, *PMIB_UDPTABLE_OWNER_PID;

    typedef struct _MIB_UDP6ROW_OWNER_PID {
        UCHAR           ucLocalAddr[16];
        DWORD           dwLocalScopeId;
        DWORD           dwLocalPort;
        DWORD           dwOwningPid;
    } MIB_UDP6ROW_OWNER_PID, *PMIB_UDP6ROW_OWNER_PID;

    typedef struct _MIB_UDP6TABLE_OWNER_PID
    {
        DWORD                   dwNumEntries;
        MIB_UDP6ROW_OWNER_PID   table[ANY_SIZE];
    } MIB_UDP6TABLE_OWNER_PID, *PMIB_UDP6TABLE_OWNER_PID;

    typedef enum _TCP_TABLE_CLASS {
        TCP_TABLE_BASIC_LISTENER,
        TCP_TABLE_BASIC_CONNECTIONS,
        TCP_TABLE_BASIC_ALL,
        TCP_TABLE_OWNER_PID_LISTENER,
        TCP_TABLE_OWNER_PID_CONNECTIONS,
        TCP_TABLE_OWNER_PID_ALL,
        TCP_TABLE_OWNER_MODULE_LISTENER,
        TCP_TABLE_OWNER_MODULE_CONNECTIONS,
        TCP_TABLE_OWNER_MODULE_ALL
    } TCP_TABLE_CLASS, *PTCP_TABLE_CLASS;

    typedef enum _UDP_TABLE_CLASS {
        UDP_TABLE_BASIC,
        UDP_TABLE_OWNER_PID,
        UDP_TABLE_OWNER_MODULE
    } UDP_TABLE_CLASS, *PUDP_TABLE_CLASS;


    typedef enum {
        MIB_TCP_STATE_CLOSED     =  1,
        MIB_TCP_STATE_LISTEN     =  2,
        MIB_TCP_STATE_SYN_SENT   =  3,
        MIB_TCP_STATE_SYN_RCVD   =  4,
        MIB_TCP_STATE_ESTAB      =  5,
        MIB_TCP_STATE_FIN_WAIT1  =  6,
        MIB_TCP_STATE_FIN_WAIT2  =  7,
        MIB_TCP_STATE_CLOSE_WAIT =  8,
        MIB_TCP_STATE_CLOSING    =  9,
        MIB_TCP_STATE_LAST_ACK   = 10,
        MIB_TCP_STATE_TIME_WAIT  = 11,
        MIB_TCP_STATE_DELETE_TCB = 12,
    } MIB_TCP_STATE;

#endif

typedef struct _MIB_TCPROW_EX
{
    DWORD dwState;
    DWORD dwLocalAddr;
    DWORD dwLocalPort;
    DWORD dwRemoteAddr;
    DWORD dwRemotePort;
    DWORD dwProcessId;
} MIB_TCPROW_EX, *PMIB_TCPROW_EX;

typedef struct _MIB_TCPTABLE_EX
{
    DWORD dwNumEntries;
    MIB_TCPROW_EX table[ANY_SIZE];
} MIB_TCPTABLE_EX, *PMIB_TCPTABLE_EX;

typedef struct _MIB_TCP6ROW_EX {
    UCHAR ucLocalAddr[16];
    DWORD dwLocalScopeId;
    DWORD dwLocalPort;
    UCHAR ucRemoteAddr[16];
    DWORD dwRemoteScopeId;
    DWORD dwRemotePort;
    DWORD dwState;
    DWORD dwProcessId;
} MIB_TCP6ROW_EX, *PMIB_TCP6ROW_EX;

typedef struct _MIB_TCP6TABLE_EX {
    DWORD dwNumEntries;
    MIB_TCP6ROW_EX table[ANY_SIZE];
} MIB_TCP6TABLE_EX, *PMIB_TCP6TABLE_EX;

typedef struct _MIB_UDPROW_EX
{
    DWORD dwLocalAddr;
    DWORD dwLocalPort;
    DWORD dwProcessId;
} MIB_UDPROW_EX, *PMIB_UDPROW_EX;

typedef struct _MIB_UDPTABLE_EX
{
    DWORD dwNumEntries;
    MIB_UDPROW_EX table[ANY_SIZE];
} MIB_UDPTABLE_EX, *PMIB_UDPTABLE_EX;

typedef struct _MIB_UDP6ROW_EX {
    UCHAR ucLocalAddr[16];
    DWORD dwLocalScopeId;
    DWORD dwLocalPort;
    DWORD dwProcessId;
} MIB_UDP6ROW_EX, *PMIB_UDP6ROW_EX;

typedef struct _MIB_UDP6TABLE_EX {
    DWORD dwNumEntries;
    MIB_UDP6ROW_EX table[ANY_SIZE];
} MIB_UDP6TABLE_EX,  *PMIB_UDP6TABLE_EX;

typedef DWORD (WINAPI *GetExtendedTcpTable_t)(PVOID pTcpTable, PDWORD pdwSize, BOOL bOrder, ULONG ulAf, TCP_TABLE_CLASS TableClass, ULONG Reserved);
typedef DWORD (WINAPI *GetExtendedUdpTable_t)(PVOID pUdpTable, PDWORD pdwSize, BOOL bOrder, ULONG ulAf, UDP_TABLE_CLASS TableClass, ULONG Reserved);
typedef DWORD (WINAPI *AllocateAndGetTcpExTableFromStack_t)(PVOID *ppTcpTable, BOOL bOrder, HANDLE hHeap, DWORD dwFlags, DWORD dwFamily);
typedef DWORD (WINAPI *AllocateAndGetUdpExTableFromStack_t)(PVOID *ppUDPTable, BOOL bOrder, HANDLE hHeap, DWORD dwFlags, DWORD dwFamily);
int NetStat();
int NetStatEx();
BOOL IsVista();

int main(int argc, char **argv)
{
    return ( IsVista() ) ? NetStatEx() : NetStat();    
}

BOOL IsVista()
{
    OSVERSIONINFO osver;

    osver.dwOSVersionInfoSize = sizeof( OSVERSIONINFO );
    
    if ( GetVersionEx (&osver) )
    {
        if ( osver.dwPlatformId == VER_PLATFORM_WIN32_NT && (osver.dwMajorVersion >= 6 ) )
            return TRUE;
    }

    return FALSE;
}

int NetStatEx()
{
    DWORD i = 0, dwRet = 0;
    DWORD dwTcpSize = 0, dwTcp6Size = 0;
    DWORD dwUdpSize = 0, dwUdp6Size = 0;
    
    MIB_TCPTABLE_OWNER_PID  *pTcp  = NULL;
    MIB_TCP6TABLE_OWNER_PID *pTcp6 = NULL;
    MIB_UDPTABLE_OWNER_PID  *pUdp  = NULL;
    MIB_UDP6TABLE_OWNER_PID *pUdp6 = NULL;

    GetExtendedTcpTable_t GetExtendedTcpTable;
    GetExtendedUdpTable_t GetExtendedUdpTable;

    HMODULE hDLL = NULL;
    
    if ( (hDLL = LoadLibrary("Iphlpapi.dll")) == NULL )
    {
        printf("fail to LoadLibrary 'Iphlpapi.dll'\n");
        return -1;
    }

    GetExtendedTcpTable = (GetExtendedTcpTable_t)GetProcAddress(hDLL, "GetExtendedTcpTable");
    GetExtendedUdpTable = (GetExtendedUdpTable_t)GetProcAddress(hDLL, "GetExtendedUdpTable");

    dwRet = GetExtendedTcpTable(NULL, &dwTcpSize, TRUE, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0);
    dwRet = GetExtendedTcpTable(NULL, &dwTcp6Size, TRUE, AF_INET6, TCP_TABLE_OWNER_PID_ALL, 0);
    
    dwRet = GetExtendedUdpTable(NULL, &dwUdpSize, TRUE, AF_INET, UDP_TABLE_OWNER_PID, 0);
    dwRet = GetExtendedUdpTable(NULL, &dwUdp6Size, TRUE, AF_INET6, UDP_TABLE_OWNER_PID, 0);
        
    pTcp  = (MIB_TCPTABLE_OWNER_PID  *)malloc(dwTcpSize);
    pTcp6 = (MIB_TCP6TABLE_OWNER_PID *)malloc(dwTcp6Size);
    pUdp  = (MIB_UDPTABLE_OWNER_PID  *)malloc(dwUdpSize);
    pUdp6 = (MIB_UDP6TABLE_OWNER_PID *)malloc(dwUdp6Size);

    dwRet = GetExtendedTcpTable(pTcp, &dwTcpSize, TRUE, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0);
    dwRet = GetExtendedTcpTable(pTcp6, &dwTcp6Size, TRUE, AF_INET6, TCP_TABLE_OWNER_PID_ALL, 0);
    dwRet = GetExtendedUdpTable(pUdp, &dwUdpSize, TRUE, AF_INET, UDP_TABLE_OWNER_PID, 0);
    dwRet = GetExtendedUdpTable(pUdp6, &dwUdp6Size, TRUE, AF_INET6, UDP_TABLE_OWNER_PID, 0);

    for ( i = 0; i < pTcp->dwNumEntries; i++ )
    {
        printf("IPv4 TCP Port %-10d ", ntohs( (u_short)pTcp->table[i].dwLocalPort));
        printf("Owner process id %d\n",  pTcp->table[i].dwOwningPid);
    }

    for ( i = 0; i < pTcp6->dwNumEntries; i++ )
    {
        printf("IPv6 TCP Port %-10d ", ntohs( (u_short)pTcp6->table[i].dwLocalPort));
        printf("Owner process id %d\n",  pTcp6->table[i].dwOwningPid);
    }

    for ( i = 0; i < pUdp->dwNumEntries; i++ )
    {
        printf("IPv4 UDP Port %-10d ", ntohs( (u_short)pUdp->table[i].dwLocalPort));
        printf("Owner process id %d\n",  pUdp->table[i].dwOwningPid);
    }

    for ( i = 0; i < pUdp6->dwNumEntries; i++ )
    {
        printf("IPv6 UDP Port %-10d ", ntohs( (u_short)pUdp6->table[i].dwLocalPort));
        printf("Owner process id %d\n",  pUdp6->table[i].dwOwningPid);
    }

    if ( pTcp )
        free(pTcp);

    if ( pTcp6 )
        free(pTcp6);

    if ( pUdp )
        free(pUdp);

    if ( pUdp6 )
        free(pUdp6);

    FreeLibrary(hDLL);

    return 0;   
}

int NetStat()
{
    DWORD i = 0;
    HMODULE hDLL = NULL;
    HANDLE hHeap = NULL;
    PMIB_TCPTABLE_EX pTcp = NULL;
    PMIB_UDPTABLE_EX pUdp = NULL;
    PMIB_TCP6TABLE_EX pTcp6 = NULL;
    PMIB_UDP6TABLE_EX pUdp6 = NULL;
        
    AllocateAndGetTcpExTableFromStack_t AllocateAndGetTcpExTableFromStack = NULL;
    AllocateAndGetUdpExTableFromStack_t AllocateAndGetUdpExTableFromStack = NULL;

    hHeap = GetProcessHeap();

    if ( (hDLL = LoadLibrary("iphlpapi.dll")) == NULL )
        return -1;

    if ( (AllocateAndGetTcpExTableFromStack = (AllocateAndGetTcpExTableFromStack_t)GetProcAddress(hDLL, "AllocateAndGetTcpExTableFromStack")) == NULL )
        return -1;

    if ( (AllocateAndGetUdpExTableFromStack = (AllocateAndGetUdpExTableFromStack_t)GetProcAddress(hDLL,"AllocateAndGetUdpExTableFromStack")) == NULL )
        return -1;

    if ( AllocateAndGetTcpExTableFromStack((PVOID *)&pTcp, TRUE, hHeap, 0, AF_INET) == NO_ERROR )
    {
        for ( i = 0; i < pTcp->dwNumEntries; i++ )
        {
            printf("IPv4 TCP Port %-10d ", ntohs( (u_short)pTcp->table[i].dwLocalPort));
            printf("Owner process id %d\n",  pTcp->table[i].dwProcessId);
        }
    }

    if ( AllocateAndGetTcpExTableFromStack((PVOID *)&pTcp6, TRUE, hHeap, 0, AF_INET6) == NO_ERROR )
    {
        for ( i = 0; i < pTcp6->dwNumEntries; i++ )
        {
            printf("IPv6 TCP Port %-10d ", ntohs( (u_short)pTcp6->table[i].dwLocalPort));
            printf("Owner process id %d\n",  pTcp6->table[i].dwProcessId);
        }
    }

    if ( AllocateAndGetUdpExTableFromStack((PVOID *)&pUdp, TRUE, hHeap, 0, AF_INET) == NO_ERROR )
    {
        for ( i = 0; i < pUdp->dwNumEntries; i++ )
        {
            printf("IPv4 UDP Port %-10d ", ntohs( (u_short)pUdp->table[i].dwLocalPort));
            printf("Owner process id %d\n",  pUdp->table[i].dwProcessId);
        }
    }

    if ( AllocateAndGetUdpExTableFromStack((PVOID *)&pUdp6, TRUE, hHeap, 0, AF_INET6) == NO_ERROR )
    {
        for ( i = 0; i < pUdp6->dwNumEntries; i++ )
        {
            printf("IPv6 UDP Port %-10d ", ntohs( (u_short)pUdp6->table[i].dwLocalPort));
            printf("Owner process id %d\n",  pUdp6->table[i].dwProcessId);
        }
    }
        
    if ( pTcp )
        HeapFree(hHeap, 0, pTcp);
    
    if ( pUdp )
        HeapFree(hHeap, 0, pUdp);
    
    if ( pTcp6 )
        HeapFree(hHeap, 0, pTcp6);
    
    if ( pUdp6 )
        HeapFree(hHeap, 0, pUdp6);
    
    FreeLibrary(hDLL);
    
    return 0;
}

※ 설명의 편의상 예외처리를 거의 하지 않았으니, 가져다 쓰실때에는 꼭 예외처리 추가하는 센스 !!!  잊지마세요~~

블로그 이미지

맨오브파워

한계를 뛰어 넘어서..........

,

[출처] http://www.imaso.co.kr/?doc=bbs/gnuboard.php&bo_table=article&wr_id=35826

 

MVC 역사

 

MVC 자체는 오랜 역사를 가지고 있다. Triage Reenskaug가 제록스 팔로 알토(Xerox Palo Alto)리서치 센터에서

스몰토크(Smalltalk)와 관련된 그룹에 참여하는 동안 고안한 것이 MVC 패턴이다. 1978년 MVC와 관련된 첫 번째 문서가 완성됐는데,

MVC 패턴을 ‘Thing Model View Editor’패턴으로 불렀다. 하지만 오래지 않아 이름을 ‘Model View Controller’로 바꿔 부르게 됐다.

창안한 사람이 스몰토크와 관련된 일을 하고 있어선지 MVC 패턴은 가장 먼저 스몰토크-80의 클래스 라이브러리의 일환으로

활용됐으며 그래픽 유저 인터페이스(GUI)를 만들기 위한 설계패턴으로도 이용됐다.

 

하지만 MVC가 가장 적극적으로 사용되기 시작한 것은 웹 애플리케이션들이 활성화 되는 시점이었다.

웹이라는 환경 자체가 짧은 시간에 많은 변화를 이끌어냈고 이를 위한 기술과 함께 문제점도 함께 도출됐다.

웹 분야에서는 짧은 시간에 많은 기능들을 구현하는 것이 비즈니스 경쟁력과 직결됐지만

체계적인 문제해결에 대한 필요성이 요구되면서 이때부터 MVC가 적극적으로 도입하기 시작한 것이다.

MVC 패턴은 Ruby on Rails, Merb, .NET, Java, PHP 등 다양한 언어로 구현돼 그 유용성을 입증하고 있다.

 

 

MVC 패턴이란?

MVC는 모델(Model), 뷰(View), 컨트롤러(Controller)로 나누어지는 구조로 소프트웨어를 설계하는 패턴이다.

 

 

                    <그림 1> MVC 패턴의 구성요소

 

  1) Model  

      모델은 애플리케이션의 유효성 검증이나 비즈니스로직 그리고 데이터에 대한 모든 설계사항을 포함하고 있다.

 

  2) View 

     MVC에서의 뷰를 쉽게 설명하면 사용자 인터페이스라고 이해해도 좋다. (웹 프로그래밍의 경우 뷰를 통해서 HTML이 생성된다.)

  3) Controller

     뷰와 모델 사이에서 데이터나 로직의 흐름을 제어하는 역할을 한다.

 

MVC 패턴에서 구성요소인 모델, 뷰, 컨트롤러는 서로 연관돼 작업을 수행하지만, 각각의 역할구분은 명확히 나눠져 있으며

서로의 역할에 대해서 독립성도 확실하게 보장한다. 다시 말해, 잘 설계된 모델 안에는 필요한 기능들이 대부분 구현돼 있고

이를 잘 보여주기 위해 뷰를 설계하며 모델과 뷰를 통제하기 위해서 컨트롤러가 설계된다.

MVC 패턴으로 잘 설계된 애플리케이션은 어떠한 핵심 로직도 건드리지 않고 해당 애플리케이션을 재설계 할 수 있는 장점이 있다.

또 잘 정의된 요구사항이 있다면 모델, 뷰, 컨트롤러를 각기 다른 사람이 한 번에 개발할 수 있다. 

예를 들어 어떤 애플리케이션을 HTML기반의 웹 애플리케이션으로 개발하고 있다가

갑자기 Silverlight를 이용한 RIA기반으로 요구사항이 바뀌어 전환해야 할 경우,

일반적인 방법으로 설계했다면 엄청난 코드의 수정이 요구된다.

 

하지만 MVC 패턴으로 작성된 코드라면 뷰 부분만 Silverlight에 맞추어서 재작성하면 된다.

 

이와 같이 MVC는 외부의 변화에 대해서 비교적 유연하게 반응할 수 있기 때문에 가장 널리 사용되는 패턴 중에 하나가 됐다.

물론 MVC 모델이 유연하기 때문에 각광 받고 있지만 단순히 유연함이 모든 장점은 아니다.

소프트웨어가 복잡해지면 복잡해질수록 소프트웨어 품질에 대한 관리가 필요하다.

일반적으로 제품을 제조하는 공장의 경우도 완성된 제품을 테스트해서 불량 여부를 가려내듯

소프트웨어에서 품질관리를 위한 가장 기본은 바로 테스트라고 할 수 있다.

 

특히 소프트웨어 개발에서 TDD(Test Driven Development)란 이름으로 테스트의 중요성이 더욱 부각되고 있는 시점에서

MVC처럼 명확한 구조는 테스트를 좀 더 쉽게 할 수 있으며, 테스트 과정을 통해 좀 더 쉽게 오류를 찾을 수 있게 해준다.

블로그 이미지

맨오브파워

한계를 뛰어 넘어서..........

,

패턴이란 특정 컨텍스트(패턴이 적용되는 상황. 반복적으로 일어날 수 있는 상황) 내에서 주어진 문제

(해당 컨텍스트 내에서 이루고자 하는 목적 또는 제약조건)에 대한

해결책(일련의 제약조건 내에서 목적을 달성할 수 있는 일반적인 디자인)이다.

"어떤 컨텍스트 내에서 일련의 제약조건에 의해 영향을 받을 수 있는 문제에 봉착했다면,

그 제약조건 내에서 목적을 달성하기 위한 해결책을 찾아낼 수 있는 디자인을 적용한다."

1. 범주별 분류

 생성 관련 패턴 (싱글턴, 추상 팩토리, 팩토리 메소드, 빌더, 프로토타입)
    객체 인스턴스 생성을 위한 패턴으로, 클라이언트와 그 클라이언트에서 생성해야 할 객체 인스턴스 사이의 연결을 끊어주는 패턴.

 행동 관련 패턴 (템플릿 메소드, 커맨드, 어터레이터, 옵저버, 스테이트, 스트래티지)
   클래스와 객체들의 상호작용 하는 방법 및 역할을 분담하는 방법관 관련된 패턴.

 구조 관련 패턴 (데코레이터, 컴포지트, 프록시, 퍼사드, 어댑터)
   클래스 및 객체들을 구성을 통새허 더 큰 구조로 만들수 있게 해주는 것과 관련된 패턴.

2. 클래스, 객체 분류

■  클래스 패턴 (템플릿 메소드, 팩토리 메소드, 어댑터)
    클래스 사이의 관계가 상속을 통해서 어떤 식으로 정의되는지를 다룬다. 클래스 패턴에서는 컴파일시에 관계가 결정.

■  객체 패턴 (Composite, Decorator, Proxy, Strategy, Bridge, Facade, Command, iterator, Observer, Prototype, State,

                     Factory, Singleton)

    객체 사이의 관계를 다루며, 객체 사이의 관계는 보통 구성을 통새허 정의. 객체 패턴에서는 일반적으로 실행중에 관계가 생성되기

    때문에 더 동적이고 유연함.

3. 패턴으로 생각하기

■  최대한 단순하게
    "이 문제에 어떤 패턴을 적용할까?"가 아닌 "어떻게 하면 단순하게 해결할 수 있을까?"에 초점.
     가장 단순하고 유연한 디자인을 만들기 위해서 패턴을 사용해야 한다면 그때 패턴을 적용하면 된다.

■  디자인 패턴은 만병 통치약이 아니다
    패턴을 사용할 때는 그 패턴을 사용했을 때 설계한 디자인의 다른 부분에 미칠수 있는 영향과 결과에 대해 주의 깊게 생각해 봐야 한다.

■  패턴이 필요한 경우
    구상중인 디자인상에 적용이 적합하다 확신이 들 경우.
    시스템의 어떤 부분이 변경될 것이라고 예측할 수 있는 경우.

■  리팩터링과 패턴
    행동이 아닌 구조를 개선하는데 목적.

■  패턴 제거
    시스템이 점점 복잡해지면서 처음에 기대했던 유연성이 전혀 발휘되지 않게 되는 경우 패턴을 과감하게 제거해 버리는게

    최선책일 수 있다. 즉, 패턴을 사용하지 않은 간단한 해결책이 더 나을 것 같다 싶을 때 패턴을 제거하면 된다.

 필요한 경우에만 적용하라
   지금 당장 변화에 대처하기 위한 디자인을 만들어야 한다면 패턴을 적용해서 그 변화에 적응해야 한다.

   하지만 꼭 필요하지 않음에도 불구하고 괜히 패턴을 추가하는 것은 피해야 한다.

   괜히 시스템만 복잡해지고 사용률도 현저히 낮거나 없을 수 있다.

4. 패턴을 대하는 마음가짐

   초보자들은 언제나 패턴을 사용하는 경향이 있다. 그 과정에서 패턴을 쓰는 연습을 하면서 다양한 경험을 쌓을 수 있는 측면에서는

    좋다. 하지만 그러다 보면 반드시 그렇지만은 않다는 것을 배우게 된다. 어떤 디자인이든 될 수 있으면 단순하게 만들어야 한다는 것을

    터득하게 된다. 반드시 확장성이 필요한 경우에만 패턴을 써서 조금 복잡하게 만드는 것이 좋다.

    경험이 늘어 중급자 수준에 오르게 되면 어떤 상황에서 패턴이 필요하고 어떤 상황에서 패턴이 필요하지 않은지를 파악할 수 있다.

    여전히 잘 맞지 않는 패턴을 억지로 적용하려는 경향이 보이긴 하지만, 그 과정에서 정형적인 패턴이 적합하지 않은 상황에서는 패턴을

    적당히 변형시켜서 써야 한다는 것을 깨닫게 된다.

   달인의 경지에 오르면 패턴을 자연스럽게 구사할 수 있다. 더 이상 패턴을 사용하는 것에 얽매이지 않는다. 문제를 가장 효과적으로

    해결할 수 있는 해결책을 찾는데 주안점을 둘 뿐이다. 이는 객체지향 원칙들을 종합적으로 고려함이다. 자연스럽게 패턴이 필요한

    상황에 이르면 패턴을 적당히 변형시켜서 적용한다. 그리고 달인들은 유사한 패턴 사이의 관계를 확실히 파악하여 관련된 패턴들의

    용도 사이의 미묘한 차이점을 잘 이해하고 활용한다. 달인의 마음가짐은 초보자의 마음자짐이기도 하다. 디자인 과정에서 의사 결정을

    할 때 패턴에 대한 지식이 그리 큰 영향을 끼치지 않기 때문이다.

5. 주요 패턴 정리

블로그 이미지

맨오브파워

한계를 뛰어 넘어서..........

,

서울특별시 종로 5가 왕복 8차선 도로에 교통사고가 발생했다는 신고가 관계기관에 접수되었다고 가정하자.

그럼 신고를 받은 관계기관은 신속히 해당 사고지점으로 출동할 것이다.

아마도, 사고 유형이 '교통사고' 이기 때문에, 병원, 소방서, 경찰 등의 관계기관이 출동할 것이다.

 

그리고 옵저버 패턴에 대해서 잘 모르는 상태에서, 이를 구현해 보면 아마 아래와 비슷할 것이다.

void 119()
{
    while ( NULL != (신고유형 = 사고접수()) )
    { 
        switch ( 신고유형 )
        {
        case 교통사고:
            Notify(경찰);
            Notify(소방서);
            Notify(병원);
            break;
        case 응급상황:
            Notify(병원);
            break;
        case 간첩신고:
            Notify(경찰);
            break;
        default:
            break;
        }
    }
}

지금도 깔끔해 보이지만, 옵저버 패턴을 사용하게 될 경우 더 깔끔해진다.

그리고 아래는 위에서 언급한 예제를 옵저버 패턴을 사용하여 구현한 예제 이다.

void 119()
{
    교통사고.registerObserver(경찰, 소방서, 병원);
    화재신고.registerObserver(소방서, 병원);
    간첩신고.registerObserver(경찰);
    응급신고.registerObserver(병원);

    while ( NULL != (신고유형 = 사고접수() )
    {
        Observers *observers = GetObservers(신고유형);
        foreach ( o in observers )
            o->Notify();
    }
}

즉, 옵저버 패턴은 한 객체의 상태가 바뀌면(혹은 관심 주제가 변경되면) 

그 객체에 의존하는 다른 객체들한테 연락이 가게됨으로서

자동으로 내용이 갱신되는 방식으로 일대다(one-to-many) 의존성을 정의할 때

종종 사용되어지는 패턴이다. 

 

 

끝으로, 위에서 언급한 옵저버 패턴 예제에서 각각의 클래스들을 좀 더 구체화 시키면 아래와 같다.

 

class 소방서 : public CObserver {}

class 경찰 : public CObserver {}

class 병원 : public CObserver {}

 

class 교통사고 : public Subject {  private: std::vector<CObserver *> m_vObservers; }

class 화재신고 : public Subject {  private: std::vector<CObserver *> m_vObservers; }

 

여기서 주의깊게 살펴봐야 할 사항은 서로 다른 객체사이에 통신을 하기 위해서

Observer 라는 클래스를 사용하고 있다는 점이다. (느슨한 연결 + 역활분리)

 

[생각할 꺼리]

 

1. 옵저버 패턴에서 서로 다른 클래스 사이에서 통신을 위해서 사용하는 CObserver 클래스를

   사용하는 이유는 무엇일까?

블로그 이미지

맨오브파워

한계를 뛰어 넘어서..........

,

프로그래밍시에 종종 사용하는 switch ~ case 구문의 가장 큰 단점은

case 구문이 늘어남에 따라 소스코드 가독성이 떨어진다는데에 있다.

물론 각각의 case 구문을 보기좋게 함수화 하거나 함수포인터를 적절히 활용한다면

어느정도 해결은 가능하다.

 

그리고 지금부터 소개할 Command Pattern을 사용해도 기존의 switch ~ case 구문을

보기 좋게 만들수 있다. 뿐만아니라 일련의 명령어들을 일관성있게 실행시키는 용도로도

커맨드 패턴은 자주 사용되어지는 편이다.

 

그렇다고 커맨드 패턴이 장점만 있는건 아니다.

예를 들면 switch ~ case 구문에서 case 구문이 몇 가지 안되는 경우에

커맨드 패턴을 사용하게 되면 오히려 프로그램의 복잡성만 증가한다는 단점이 있다.

 

#define CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#include <iostream>
#include <map>

using namespace std;

typedef enum ComamndId
{
    NullCommand = 0,
    LoginCommand,
    LogoutCommand
} CommandId;

class ICommand
{
public:
    ICommand(){}
    virtual ~ICommand(){}

    virtual void Execute() = 0;
    virtual CommandId GetCommandId() = 0;
};

class CNullCommand : public ICommand
{
public:
    CNullCommand(){}
    virtual ~CNullCommand(){}
    
    CommandId GetCommandId() { return NullCommand; }

    void Execute()
    {
        cout << "Null Comamnd " << endl;
    }
};

class CLoginCommand : public ICommand
{
public:
    CLoginCommand(){}
    virtual ~CLoginCommand(){}

    CommandId GetCommandId() { return LoginCommand; }

    void Execute()
    {
        cout << "Login " << endl;
    }
};

class CLogoutCommand : public ICommand
{
public:
    CLogoutCommand(){}
    virtual ~CLogoutCommand(){}

    CommandId GetCommandId() { return LogoutCommand; }
    
    void Execute()
    {
        cout << "Logout " << endl;
    }
};

ComamndId DummyRequest()
{
    return static_cast<CommandId>(rand() % 3);
}

void InitCommands(map<ComamndId, ICommand *> &cmds)
{
    typedef map<ComamndId, ICommand *>::value_type Cmd;

    cmds.insert(Cmd(NullCommand, static_cast<ICommand *>(new CNullCommand)));
    cmds.insert(Cmd(LoginCommand, static_cast<ICommand *>(new CLoginCommand)));
    cmds.insert(Cmd(LogoutCommand, static_cast<ICommand *>(new CLogoutCommand)));
    
    // 나중에 명령어가 추가되면 이곳에 명령어를 추가함.
}

void UnInitCommands(map<ComamndId, ICommand *> &cmds)
{
    map<CommandId, ICommand *>::iterator iter = cmds.begin();

    for ( ; iter != cmds.end(); iter++ )
    {
        ICommand *pCmd = static_cast<ICommand *>(iter->second);
        delete pCmd;
    }

    cmds.clear();
}

ICommand *GetCommand(map<ComamndId, ICommand *> &cmds, CommandId cmdId)
{
    map<CommandId, ICommand *>::iterator iter = cmds.find(cmdId);

    if ( iter == cmds.end() )
    {
        iter = cmds.find(NullCommand);
    }

    return static_cast<ICommand *>(iter->second);
}

void TestCommandPattern()
{
    int i = 0;
    map<ComamndId, ICommand *> cmds;

    InitCommands(cmds);
    
    for ( i = 0; i < 4; i++ )
        GetCommand(cmds, DummyRequest())->Execute();

    UnInitCommands(cmds);
}

int main(int argc, char **argv)
{
    _CrtMemState before, after, diff;

    _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
    _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDOUT);
    _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
    _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDOUT);
    _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE);
    _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDOUT);

    _CrtMemCheckpoint(&before);

    TestCommandPattern();

    _CrtMemCheckpoint(&after);
    if ( _CrtMemDifference(&diff, &before, &after) )
    {
        _CrtMemDumpAllObjectsSince(NULL);
        _CrtMemDumpStatistics(&diff);

        system("pause");
    }

    return 0;
}

[생각할 꺼리]

 

1. switch ~ case 구문은 컴파일 시점에 모든 동작이 정의되어 있어야 하지만,

   Command Pattern은 동적으로 동작을 추가하거나 삭제할 수 있다.

   이와 같은 동작이 왜 가능한지 생각해보자.

 

2. Command Pattern을 switch ~ case 구문을 대체하는 용도로만 사용한다고 생각하고 있으면 안된다.

   그럼, Command Pattern 의 또 다른 응용에는 어떤 것들이 있을지 생각해보자.

 

   [힌트] 배치 명령어, 실행취소

 

블로그 이미지

맨오브파워

한계를 뛰어 넘어서..........

,

싱글턴 패턴(Singleton pattern)이란 어떤 클래스의 인스턴스를 단 하나만 허용하고,

이 인스턴스에 접근할 수 있는 전역적인 접근을 가능하게 하는 패턴이다.

 

즉, 응용프로그램내에서 단 하나뿐인 전역 클래스를 만들고 싶을 때,

싱글턴 패턴을 사용하게 된다.

 

나 같은 경우 주로 프로그램 실행 초기에,

전역적인 환경설정 값을 로드하는 시점에서

싱글턴 패턴을 많이 사용하는 편이다.

 

#include <iostream>
#include "Singleton.h"

using namespace std;

int main ( int argc, char **argv )
{   
    CSingleton *pInstance = CSingleton::GetInstance();

    pInstance->m_nAge = 100;
    pInstance->m_strName = "James";

    CSingleton *p = CSingleton::GetInstance();

    cout << "age " << p->m_nAge << endl;
    cout << "name " << p->m_strName << endl;

    return 0;
}

////////////////////////////////////////////////////////////////////////////////

#ifndef SINGLETON_H
#define SINGLETON_H

#include <string>

using namespace std;

class CSingleton  
{
private:
    CSingleton(){};
    virtual ~CSingleton(){};
    void Init();

public:
    static CSingleton *GetInstance();   

public:
    int m_nAge;
    string m_strName;

private:
    static CSingleton *m_pInstance;
};

#endif   // end of SINGLETON_H

////////////////////////////////////////////////////////////////////////////////

#include "Singleton.h"

CSingleton *CSingleton::m_pInstance = 0;

CSingleton* CSingleton::GetInstance()
{
    if ( !m_pInstance )
    {
        m_pInstance = new CSingleton;
        m_pInstance->Init();
    }

    return m_pInstance;
}

void CSingleton::Init()
{
    m_nAge = 0;
    m_strName = "";
}

 

[생각할 꺼리들]

 

1. 멀티쓰레드 환경에서 Instance가 생성되어 있지 않은 시점에서 동시에 GetInstance()를 호출했을 때의 문제점

2. 인스턴스에 할당된 메모리가 릴리즈 되는 시점

블로그 이미지

맨오브파워

한계를 뛰어 넘어서..........

,

 

블로그 이미지

맨오브파워

한계를 뛰어 넘어서..........

,

다음 내용은 Windows 환경에서 OpenSSL을 직접 컴파일 하여 설치하는 방법에 대해서 설명하고 있습니다. 


※ 들어가기에 앞서 ...

    설명의 편의상 아래와 같은 가정을 전제하고 읽어주세요~ ^^

    openssl-0.9.8k.tar.gz 파일은 H:\openssl-0.9.8k 디렉토리에 압축이 풀렸음.
    OpenSSL 설치 디렉토리는 C:\OpenSSL 이라고 가정함.

1. OpenSSL 다운로드

    http://www.openssl.org/source/openssl-0.9.8k.tar.gz


2. ActivePerl 설치

    openssl 라이브러리를 컴파일 하기 위해서는 perl 이 필요합니다.

    http://downloads.activestate.com/ActivePerl/Windows/5.10/ActivePerl-5.10.0.1004-MSWin32-x86-287188.msi

    다운로드 받은 ActivePerl을 설치합니다. (※ 설치시 Next 버튼만 계속 누르면 됩니다. ^^)


3. 전반적인 컴파일 환경 구축

    압축이 해제된 디렉토리로 이동하여, 아래와 같이 명령어를 입력합니다.

 

    1) x86 static library

        perl Configure VC-WIN32 --openssldir=C:\OpenSSL-x86 no-shared no-asm threads no-idea no-mdc2 no-rc5

 

    2) x86 static debug library

        perl Configure debug-VC-WIN32 --openssldir=C:\OpenSSL-x86-debug no-shared no-asm threads no-idea no-mdc2 no-rc5

 

    3) x64 static library

        perl Configure VC-WIN64A --openssldir=C:\OpenSSL-x64 no-shared no-asm threads no-idea no-mdc2 no-rc5

 

    4) x64 static debug library

        perl Configure debug-VC-WIN64A --openssldir=C:\OpenSSL-x64-debug no-shared no-asm threads no-idea no-mdc2 no-rc5

 

    ※ 참고로 --openssldir=C:\OpenSSL 과 같이 입력하면 "C:\OpenSSL" 디렉토리에 라이브러리가 설치됩니다.

    ※ no-idea no-mdc2 no-rc5 는 암호화 알고리즘 라이센스 문제로 인하여 빌드에서 제외시킵니다.

   

    


4. 구체적인 컴파일 환경 구축

    컴파일 환경은 다음과 같이 3가지 유형중 한 가지를 선택하여 사용이 가능합니다.

 

    1) x86

        - 어셈블리어를 사용하지 않는 경우       ms\do_ms.bat          

        - NASM 어셈블리어를 사용하는 경우    ms\do_nasm.bat    

        - MASM 어셈블리어를 사용하는 경우   ms\do_masm.bat

 

    2) x64

        - 어셈블리어를 사용하지 않는 경우     ms\do_win64a.bat

 

    3) Itanium
        - 어셈블리어를 사용하지 않는 경우 ms\do_win64i.bat

 

 

    만약, 어셈블리어를 사용하지 않을 경우에는, 아래와 같이 명령어를 입력합니다.

   


5. 소스 수정

    윈도우 환경에서는 아래와 같은 파일을, OpenSSL을 컴파일 하기 이전에 수정해 주어야 합니다.

    (아마 다음 OpenSSL 버전에서는 필요없는 과정이 될 수도 있겠군요.. ^^;;)


    1) openssl-0.9.8k\crypto\x509v3\v3_pci.c

        소스파일 최상단에 위치한 주석문에 특수문자가 포함되어 있어서 컴파일 오류가 발생하기 때문에,

        소스파일 가장 위에 주석문을 제거하고 저장함.


    2) openssl-0.9.8k\crypto\x509v3\v3_pcia.c

        소스파일 최상단에 위치한 주석문에 특수문자가 포함되어 있어서 컴파일 오류가 발생하기 때문에,

        소스파일 가장 위에 주석문을 제거하고 저장함.


    3) openssl-0.9.8k\crypto\cversion.c

        cversion.c 105 번째 라인을 아래와 같이 수정함.

        [수정 전] return "OPENSSLDIR: \"" OPENSSLDIR "\"";

        [수정 후] return "OPENSSLDIR: \" OPENSSLDIR \"";


    4) openssl-0.9.8k\crypto\cryptlib.h

        cryptlib.h 84~86번째 라인을 아래와 같이 수정함.

        


    5) openssl-0.9.8k\crypto\opensslconf.h 

        opensslconf.h 107~108번째 라인을 아래와 같이 수정함.

       

 

 

6. Makefile 수정

    디버그 모드로 빌드할 때만..

   

    


 

 

7. 컴파일 및 설치

    정적 라이브러리 빌드 인 경우 : nmake -f ms\nt.mak install

    동적 라이브러리 빌드 인 경우 : nmake -f ms\ntdll.mak install

    만약, 동적 라이브러리 컴파일 및 설치를 위해서는 아래와 같이 명령어를 입력합니다.

   

 

 

8. 설치 결과

    첨부된 "OpenSSL.zip"파일은 윈도우 환경에서 OpenSSL을 컴파일한 결과물을 압축한 파일입니다.

 

     OpenSSL.zip

 

 

9. 주의사항

 

    I've compiled a program under Windows and it crashes: why?


    This is usually because you've missed the comment in INSTALL.W32.

    Your application must link against the same version of the Win32 C-Runtime against which your openssl libraries were linked.

    The default version for OpenSSL is /MD - "Multithreaded DLL".

 

    If you are using Microsoft Visual C++'s IDE (Visual Studio), in many cases,

    your new project most likely defaulted to "Debug Singlethreaded" - /ML.

 

    This is NOT interchangeable with /MD and your program will crash, typically on the first BIO related read or write operation.

 

    For each of the six possible link stage configurations within Win32,

    your application must link against the same by which OpenSSL was built.

 

    If you are using MS Visual C++ (Studio) this can be changed by:


    1. Select Settings... from the Project Menu.
    2. Select the C/C++ Tab.
    3. Select "Code Generation from the "Category" drop down list box
    4. Select the Appropriate library (see table below) from the "Use run-time library" drop down list box. 

       Perform this step for both your debug and release versions of your application

       (look at the top left of the settings panel to change between the two)

       Single Threaded               /ML       -  MS VC++ often defaults to this for the release version of a new project.
       Debug Single Threaded     /MLd     -  MS VC++ often defaults to this for the debug version of a new project.
       Multithreaded                   /MT
       Debug Multithreaded         /MTd
       Multithreaded DLL             /MD      -  OpenSSL defaults to this.
       Debug Multithreaded DLL   /MDd

 

       ※ Note that debug and release libraries are NOT interchangeable.

       If you built OpenSSL with /MD your application must use /MD and cannot use /MDd.


       As per 0.9.8 the above limitation is eliminated for .DLLs.

       OpenSSL .DLLs compiled with some specific run-time option [we insist on the default /MD] can be deployed with application   

       compiled with different option or even different compiler.

 

       But there is a catch! Instead of re-compiling OpenSSL toolkit, as you would have to with prior versions,

       you have to compile small C snippet with compiler and/or options of your choice.

 

       The snippet gets installed as <install-root>/include/openssl/applink.c and

       should be either added to your application project or simply #include-d in one [and only one] of your application source files.

 

       Failure to link this shim module into your application manifests itself as fatal "no OPENSSL_Applink" run-time error.

 

       An explicit reminder is due that in this situation [mixing compiler options]

       it is as important to add CRYPTO_malloc_init prior first call to OpenSSL.

블로그 이미지

맨오브파워

한계를 뛰어 넘어서..........

,