반응형
반응형

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;


namespace Dev_Console

{

    class Program

    {


        private static bool CheckPassed(int score)

        {

            if (score >= 60)

                return true;

            else

                return false;

        }


        private static void Print(int value)

        {

            Console.Write("{0} ", value);

        }


        public static void Main(string[] args)

        {

            int[] scores = new int[] { 80, 74, 81, 90, 34 };


            foreach (int score in scores)

            {

                Console.Write("{0} ", score);

            }

            Console.WriteLine();


            Array.Sort(scores);

            Array.ForEach<int>(scores, new Action<int>(Print));

            Console.WriteLine();


            Console.WriteLine("Number of dimensions : {0}", scores.Rank);


            Console.WriteLine("Binary Search : 81 is at {0}", Array.BinarySearch<int>(scores, 81));

            Console.WriteLine("Linear Search : 90 is at {0}", Array.IndexOf(scores, 90));


            Console.WriteLine("Everyone passed ? : {0}", Array.TrueForAll<int>(scores, CheckPassed));


            int index = Array.FindIndex<int>(scores, delegate(int score)

            {

                if (score < 60)

                    return true;

                else

                    return false;

            });


            scores[index] = 61;

            Console.WriteLine("Everyone passed ? : {0}", Array.TrueForAll<int>(scores, CheckPassed));


            Console.WriteLine("Old length of scores : {0}", scores.GetLength(0));


            Array.Resize<int>(ref scores, 10);

            Console.WriteLine("New length of scores : {0}", scores.Length);


            Array.ForEach<int>(scores, new Action<int>(Print));

            Console.WriteLine();


            Array.Clear(scores, 3, 7);


            Array.ForEach<int>(scores, new Action<int>(Print));

            Console.WriteLine();

        }


    }






}

반응형

'Program > C#' 카테고리의 다른 글

c# vb 변환  (0) 2011.10.26
닷넷!! All-In-One Code Framework!!  (0) 2011.03.24
.NET 개발자를 위한 무료 소프트웨어  (0) 2011.03.24
디렉토리 안에 폴더 삭제 하기  (0) 2011.03.24
다중서버관리  (0) 2011.03.24
반응형
http://www.developerfusion.com/tools/convert/vb-to-csharp/


http://www.carlosag.net/Tools/CodeTranslator/


반응형

'Program > C#' 카테고리의 다른 글

Array 배열 예제  (0) 2015.08.19
닷넷!! All-In-One Code Framework!!  (0) 2011.03.24
.NET 개발자를 위한 무료 소프트웨어  (0) 2011.03.24
디렉토리 안에 폴더 삭제 하기  (0) 2011.03.24
다중서버관리  (0) 2011.03.24
반응형

닷넷 하시는 분이라면 아주 유용할 만한 소스인거 같아서 여러분들도 보시라고
올려 드립니다. All-In-One Code Framework 아주 멋지게 잘 만들어 놓은
소스코드네요 ^^

반응형

'Program > C#' 카테고리의 다른 글

Array 배열 예제  (0) 2015.08.19
c# vb 변환  (0) 2011.10.26
.NET 개발자를 위한 무료 소프트웨어  (0) 2011.03.24
디렉토리 안에 폴더 삭제 하기  (0) 2011.03.24
다중서버관리  (0) 2011.03.24
반응형
반응형

'Program > C#' 카테고리의 다른 글

c# vb 변환  (0) 2011.10.26
닷넷!! All-In-One Code Framework!!  (0) 2011.03.24
디렉토리 안에 폴더 삭제 하기  (0) 2011.03.24
다중서버관리  (0) 2011.03.24
파일 백업 툴 FileSyncer  (0) 2011.03.24
반응형

DirectoryInfo dir = new DirectoryInfo(폴더경로);
FileInfo[] files = dir.GetFiles();
foreach(FileINfo F in files)
{
F.Delete();
}
출처 : Tong - centerkjh님의 Visual C#통

폴더 삭제 하기

* 읽기 전용 파일 포함 디렉토리 삭제 방법

using System.IO;
protected void DeleteTempDirectory()
{
DirectoryInfo tempDirInfo = new DirectoryInfo("temp");

if (tempDirInfo.Exists == true)
{
foreach (DirectoryInfo di in tempDirInfo.GetDirectories())
{
foreach (FileInfo fi in di.GetFiles())
{
if ( (fi.Attributes & FileAttributes.ReadOnly) > 0 )
{
fi.Attributes = FileAttributes.Normal;
}
}
}

tempDirInfo.Delete(true);
}
}

반응형

'Program > C#' 카테고리의 다른 글

닷넷!! All-In-One Code Framework!!  (0) 2011.03.24
.NET 개발자를 위한 무료 소프트웨어  (0) 2011.03.24
다중서버관리  (0) 2011.03.24
파일 백업 툴 FileSyncer  (0) 2011.03.24
레지스트리 값 읽고, 쓰기 방법 2  (0) 2011.03.24
반응형



위 그럼 처럼 다중서버 관리를 할수가 있다...

이걸 가지고 여러므로 유용하실 쓸수 있을듯한 느낌이 팍팍? ^^

반응형
반응형



[출처] 파일 백업 툴 FileSyncer 2.0 - C# 소스 공개|작성자 http://wiz.pe.kr/trackback/599 (위즈군의 라이프로그)

몇 년 전에 개인적으로 데이터 백업을 위해 만들어서 사용하던 프로그램의 소스입니다.
생각보다 많은 분들이 소스를 요청 하셔서 공개를 합니다.
처음부터 소스 공개를 목적으로 만든 프로그램이 아니라서 소스의 상태가 깔끔하지는 않습니다.
개인적으로 사용 할 목적이었기 때문에 UI 역시 불편하게 구성되어 있네요.
이 프로그램을 참고해서 더 깔끔하고 좋은 프로그램을 만들어 주시면 좋겠다는 생각입니다.


FileSyncer 2.0

소스다운로드
개발환경 : Microsoft .NET Framework 2.0 / Visual Studio 2003 / C#
실행파일 다운로드 : "개인용으로 개발한 (데이터백업)싱크 프로그램 Wiz FileSyncer 2.0"

프로그램에 대한 소개 내용은 "개인용으로 개발한 (데이터백업)싱크 프로그램 Wiz FileSyncer 2.0"를 참고하세요.
다운로드 시 주의 사항
* 상업적 사용 금지 : 혹시라도 상업적 목적으로 개발 할 경우 미리 연락을 주세요.
* 재배포 금지 : 원본 소스를 재배포하지 말아주세요.
* 수정 프로그램을 배포하는 경우는 꼭 알려주세요. (저도 참고를 하고 싶습니다.)
반응형
반응형

using Microsoft.Win32; // RegistryKey 사용을 위해 추가

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

// 레지스트리 가져오기
private string getReg(string regVal)
{
RegistryKey reg = Registry.LocalMachine;
reg = reg.OpenSubKey("Software\\myProgram", true);
if (reg == null)
return "";
else
return Convert.ToString(reg.GetValue(regVal)); // 값 검색
}

// 레지스트리 쓰기
private void setReg(string regKey, string regVal)
{
RegistryKey reg = Registry.LocalMachine;
reg = reg.CreateSubKey("Software\\myProgram",
RegistryKeyPermissionCheck.ReadWriteSubTree);
reg.SetValue(regKey, regVal, RegistryValueKind.String);
reg.Close();
}

// 등록 버튼
private void button1_Click(object sender, EventArgs e)
{
string regKey = textBox1.Text;
string regVal = textBox2.Text;

setReg(regKey, regVal);

}

// 읽기 버튼
private void button2_Click(object sender, EventArgs e)
{
string regKey = textBox1.Text;
textBox2.Text = getReg(regKey);
}
}
}

반응형
반응형

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Win32; // 레지스트리관련 클래스를 쓰기위해 추가

namespace regiEx1
{
class Program
{
static void Main(string[] args)
{
string regSubkey = "Software\\myTestKey";
// 서브키를 얻어온다. 없으면 null
RegistryKey rk = Registry.LocalMachine.OpenSubKey(regSubkey, true);
// 없으면 서브키를 만든다.
if (rk == null)
{
// 해당이름으로 서브키 생성
rk = Registry.LocalMachine.CreateSubKey(regSubkey);
}
string[] strData = new string[] {"aaa","bbb","ccc"};
// 서브키 아래 값 쓰기
rk.SetValue("test", strData);
// 서브키 아래 값 읽기
string[] regStr = rk.GetValue("test") as string[];

Console.WriteLine(regStr[1]);
Console.ReadLine();

// 서브키 삭제
Registry.LocalMachine.DeleteSubKey(regSubkey);
}
}
}

반응형
반응형

C# 프로그래밍을 하다보면 C++에서 만들어 둔 DLL을 사용해야 할 경우가 많이 있지요.
in 기능의 인수들을 그냥 대충 바꾸면 되는데
out 기능의 포인터를 사용한 Call by Referance 인수들을 참 난감합니다.
그러나 아래와 같이 선언하면 사용이 가능합니다.
참고 하세요.

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace SolarCellDGUI
{
class SCMInterface
{

[DllImport!("P:SolarCell_CTCShellExecDllsSCMBusClient.dll")]
public static extern int ConnectToSCM(string MyIPAddr,
[MarshalAs(UnmanagedType.LPArray)] Int32[] ConnectionID,
[MarshalAs(UnmanagedType.LPArray)] byte[] EquipmentName);

[DllImport!("P:SolarCell_CTCShellExecDllsSCMBusClient.dll")]
public static extern int DisconnectFromSCM(string MyIPAddr, int ConnetionID);

[DllImport!("P:SolarCell_CTCShellExecDllsSCMBusClient.dll")]
public static extern int SendEventToSCM(int ConnectionID, string Source,
string Destination, string EventName, string EventData, int EventDataLen,
[MarshalAs(UnmanagedType.LPArray)] byte[] ResultData,
[MarshalAs(UnmanagedType.LPArray)] Int32[] ResultDataLen);


public bool SendEventSCM(int ConnectionID, string Source, string Destination,
string EventName, string EventData, int EventDataLen, ref string ResultData, ref int ResultDataLen)
{
int res = 0;
byte[] resData = new byte[100];
Int32[] resDataLen = new Int32[1];
char[] tempchr = new char[100];
int i;

res = SendEventToSCM(ConnectionID, Source, Destination, EventName, EventData,
EventDataLen, resData, resDataLen);

if (res == 1)
{
ResultDataLen = resDataLen[0];

for (i = 0; i < 100; i ++)
tempchr[i] = System.Convert.ToChar(resData[i]);
ResultData = new string(tempchr);
return true;
}
else
{
return false;
}

}

public bool ConnectSCM(string MyIPAddr, ref int ConnectionID, ref string EquipmentName)
{
int res = 0;
byte[] eqpName = new byte[80];
Int32[] conID = new Int32[1];
char[] tempchr = new char[80];
int i;

res = ConnectToSCM(MyIPAddr, conID, eqpName);

if (res == 1)
{
ConnectionID = conID[0];

for (i = 0; i < 80; i++)
tempchr[i] = System.Convert.ToChar(eqpName[i]);

EquipmentName = new string(tempchr);
return true;
}
else
{
return false;
}

}

public bool DisconnectSCM(string MyIPAddr, int ConnetionID)
{
int res = 0;

res = DisconnectFromSCM(MyIPAddr, ConnetionID);

if (res == 1)
return true;
else
return false;
}

}
}

반응형

'Program > C#' 카테고리의 다른 글

레지스트리 값 읽고, 쓰기 방법 2  (0) 2011.03.24
레지스트리에 값 읽고, 쓰고, 삭제  (0) 2011.03.24
C#에서 Win32 API 사용하기2  (0) 2011.03.24
웹페이지 자동로그인 구현  (0) 2011.03.24
급여 계산  (0) 2011.03.24
반응형

C#에서 Win32 API 사용하기

개요

Win32 API를 불러올 때, 함수의 명칭, 인자, 리턴 값을 가지고 불러오게 되어 있다. 하지만, C#에서 타입들이 모두 객체(Object)의 형식이며, 일반적인 C 의 데이터 형과 상이한 모양을 가진다. 이러한 문제들을 해결할 수 있는 것이 PInvoke 기능이다.

PInvoke( Platform Invocation Service)는 관리화 코드에서 비관리화 코드를 호출할 방법을 제공한다. 일반적인 용도는 Win32 API의 호출을 위해 사용한다.

namespace PinvokeExample

{

using System;

using System.Runtime.InteropServices; // 반드시 입력해야 한다.

public class Win32

{

[DllImport!(“user32.dll”)]

public static extern int FindWindow(string a, string b);

}

}

위 예제는 FindWindow라는 user32.dll C함수를 사용하는 모습을 보여주고 있다. 실제 FindWindow의 선언은 다음과 같다.

HWND FindWindow(LPCSTR swClassName, LPCSTR swTitle);

HWND는 윈도우 핸들을 표현하는 32비트 정수 이므로, int형으로 치환되고 LPCSTR 형은 NULL로 끝나는 문자열을 표현한다. 이때 PInvoke string을 자동으로 LPCSTR로 치환해 주는 역할을 하게 된다.

이 문서에서는 이처럼 Win32 API 함수의 여러 유형들을 어떻게 C#에서 사용 할 것인지에 대하여 알아보자.

WIN32 데이터형의 치환

Win32 API에서 일반적으로 사용하고 있는 데이터형은 모두 C#의 데이터 형으로 치환될 수 있다.

Win32 API TYPE

C#

BOOL, BOOLEAN

bool

BYTE

byte

CALLBACK

delegate

COLORREF

int

DWORD

int

DWORD_PTR

long

DWORD32

uint

DWORD64

ulong

FLOAT

float

HACCEL

int

HANDLE

int

HBITMAP

int

HBRUSH

int

HCONV

int

(모든 HANDLE 타입) Hxxxx

int

LPARAM

long

LPCSTR

[in] string [out] StringBuilder

LPBOOL

ref bool

이외 LP*

ref 형식

UINT

uint

Uxxxx

unsigned 타입들..

WORD

Short

WPARAM

Uint

Structure 의 전달

예를 들어 POINT 형의 경우,

typedef struct t_Point {

int x;

int y;

} POINT;

이것은 기본적으로 다음과 같이 선언될 수 있다.

[순차적]

[StructLayout(LayoutKind.Sequential)]
public struct Point {
      public int x;
      public int y;
}

[명시적]

[StructLayout(LayoutKind.Explicit)]
public struct Point {
      [FieldOffset(0)] public int x;
      [FieldOffset(4)] public int y;
}

일차적으로 할당되는 메모리 레이아웃이 동일하다면, C#에서 바로 받아 들이 수 있다.

// BOOL SetWindowPos(POINT pos); 이런 함수가 있다고 가정하면… ^^

[DllImport! (“user32.dll”)]

public static extern bool SetWindowPos(Point pos);

사용할 함수 이름 바꾸기

여기서 함수의 이름을 바꿔서 사용하고 싶다면 다음과 같이 변경하면 된다.

// BOOL SetWindowPos(POINT pos);

[DllImport! (“user32.dll”, EntryPoint = “SetWindowPos”)]

public static extern bool ShowAt(Point pos);

레퍼런스형 전달하기

LPPOINT형은 POINT의 포인터 형이므로 ref Point와 같이 사용 할 수 있다. 실제 사용하는 형식은 다음과 같다.

C 언어의 포인터의 경우 레퍼런스로 사용하려고 하면, ref 키워드를 사용하는 방법이 있다.

// BOOL SetWindowPos(HWND hWnd, LPRECT lpRect);

[DllImport!(“user32.dll”)]

public static extern bool SetWindowPos(int hWnd, ref Rect lpRect);

Out형 함수 인자 사용하기

MSDN 같은 곳에서 함수의 선언을 살펴보면 다음과 같은 형식의 함수를 볼 수 있을 것이다. 이러한 형식은 레퍼런스 형으로 결과를 함수의 인자에 보내겠다는 말이다. 이러한 형식은 Win32 API에서 많이 쓰이고 있고, 포인터를 사용하므로, 많은 주의를 기울여야 한다.

BOOL GetWindowRect(
  HWND hWnd,      // handle to window
  LPRECT lpRect   // window coordinates
);

Parameters

hWnd

[in] Handle to the window.

lpRect

[out] Pointer to a RECT structure that receives the screen coordinates of the upper-left and lower-right corners of the window.

여기서 LPRECT는 앞 절에서 설명한 Structure의 전달을 참고하여 치환 될 수 있다.

여기서 lpRect RECT의 포인터이며, GetWindowRect 함수 내에서 이 포인터에 직접 값을 쓰게 되어 있다. 즉 이 포인터는 값을 기록하기 위한 인자이지, 값을 전달하기 위한 인자는 아닌 것이다. 이것은 또 다른 C# 레퍼런스 연산자인 out 키워드를 사용하여 쉽게 해결 할 수 있다.

public static extern bool GetwindowRect(int hWnd, out Rect lpRect);

실제 사용하는 모습은 다음과 같다.

public static extern bool GetWindowRect(int hWnd, out Rect lpRect);

public static void UseFunction() {

Rect _rect; // 값을 대입하지 않아도 된다.

Win32.GetWindowRect(hwnd, out _rect);

}

참고로 ref 키워드는 입력과 출력 둘 다 사용 할 수 있다. 그러나 ref를 사용하는 변수가 값이 설정되어 있다는 가정을 하고 있으므로, 이전에 반드시 어떠한 값을 입력해야 한다.

실제 사용 예는 다음과 같다.

public static extern bool GetWindowRect(int hWnd, ref Rect lpRect);

public static void UseFunction() {

Rect _rect = new Rect(); // 꼭 값을 대입해야 한다.

_rect.top = 20; _rect.left = 30;

_rect.bottom = 50; _rect.right = 60;

Win32.GetWindowRect(hwnd, ref _rect);

}

여기서 잠깐

대중없이 Rect라는 구조체가 나오는데 이는 API에서 RECT형을 C#으로 바꾸어 사용하는 structure이다. 앞의 예제들은 다음과 같은 선언을 하였다고 가정한다.

[StructLayout(LayoutKind.Explicit)]
public struct Point {
      [FieldOffset(0)] public int top;
[FieldOffset(4)] public int left;
[FieldOffset(8)] public int bottom;
[FieldOffset(12)] public int right;

}

CALLBACK 함수의 선언

C 언어에서 콜백 함수는 함수 포인터로 존재하게 된다. 이것은 함수 인스턴스의 포인터로, 함수 자체를 전달하게 되는 방식이다. 대표적으로 사용되는 부분은 EnumWindows 함수이다.

// BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARMAM IParam)

이 함수는 현재 열려 있는 모든 윈도우 핸들을 열거하기 위한 함수로 실제 처리하는 부분은 함수 포인터, 즉 콜백함수인 lpEnumFunc에서 처리하게 되어 있다. WNDENUMPROC 타입의 선언은 다음과 같다.

// typedef BOOL (CALLBACK* WNDENUMPROC)(HWND, LPARAM);

public delegate bool Callback(int hWnd, long lParam);

이러한 콜백 함수 역할을 하는 C#의 프리미티브는 delegate이다. CALLBACK delegate로 지환된다.

결과적으로 다음과 같이 사용하게 된다.

namespace ada.appshare

{

public delegate bool Callback(int hwnd, int lParam);

internal class Win32

{

internal static extern int EnumWindows(CallBack x, int y);

[DllImport!("user32.dll")]

public static bool EnumWindowsCallback(int hWnd, int lParam)
{

System.Console.WriteLine(“” + hWnd);

return true;

}

}

public static void Main(String []args)

{

Win32.Callback call
= new Win32.Callback(Win32.EnumWindowsCallback);

Win32.EnumWindows(call, 0);

}

}

반응형
반응형

1. C# WindowsFormApplication 만들고
2. 참조에 Microsoft.mshtml 추가
3. 폼 코드보기에서 아래 내용으로 변경

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using mshtml;

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

private void Form1_Load(object sender, EventArgs e)
{
webBrowser1.Navigate("http://mdinside.co.kr/", false);
}

private void button1_Click(object sender, EventArgs e)
{
mshtml.HTMLDocument HTMLDoc = new HTMLDocument();
HTMLDoc = (mshtml.HTMLDocument)webBrowser1.Document.DomDocument;

IHTMLElement fhead = (IHTMLElement)HTMLDoc.getElementsByName("fhead").item(null, 0);
IHTMLElement mb_id = (IHTMLElement)HTMLDoc.getElementsByName("mb_id").item(null, 0);
IHTMLElement mb_password = (IHTMLElement)HTMLDoc.getElementsByName("mb_password").item(null, 0);

mb_id.setAttribute("value", "아이디", 0);
mb_password.setAttribute("value", (Object)"비밀번호", 0);

webBrowser1.Document.InvokeScript("fhead_submit", new Object[] { fhead });
}
}
}

반응형

'Program > C#' 카테고리의 다른 글

C#에서 C++ DLL의 Call by Referance out 인수 사용하는 방법  (0) 2011.03.24
C#에서 Win32 API 사용하기2  (0) 2011.03.24
급여 계산  (0) 2011.03.24
체중 관리 프로그램  (0) 2011.03.24
윈도우를 종료  (0) 2011.03.24
반응형

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

public class Pay
{
public int EmployeeNumber { get; set; } // 사원 번호
public int SalaryNumber { get; set; } // 급
public int SalaryClass { get; set; } // 호
public int Allowance { get; set; } // 수당
public int TaxAmountPayable { get; set; } // 지급액
public int Tax { get; set; } // 세금
public int CalculatingTable { get; set; } // 급호봉 계산표
public double TaxRate { get; set; } // 세율
public int MediPay { get; set; } // 조정액
public int ResultPay { get; set; } // 차인지급액
public Pay()
{
// Empty
}
}

public class 급여처리
{
public static void Main(string[] args)
{
Console.Title = "급여 처리 프로그램";
//[1] Input
List<Pay> lst = new List<Pay>(); // 입력데이터, 들어오는 만큼 데이터 저장
Pay pi;
string temp1 =""; // 사번
string temp2 =""; // 급
string temp3 =""; // 호
string temp4 =""; // 수당
string btn = "n";
do
{
pi = new Pay();
Console.Write("사원번호 : _\b");
temp1 = Console.ReadLine();
if (Convert.ToInt32(temp1) >= 1 && Convert.ToInt32(temp1) <= 9)
{
pi.EmployeeNumber = Convert.ToInt32(temp1);
}
else
{
Console.WriteLine("사원 번호는 정수 1자리로 입력하세요");
return;
}
Console.Write("급 : _\b");
temp2 = Console.ReadLine();
if (Convert.ToInt32(temp2) == 1 || Convert.ToInt32(temp2) == 2)
{
pi.SalaryNumber = Convert.ToInt32(temp2);
}
else
{
Console.WriteLine("급 입력은 1급 또는 2급만 입력하세요");
}
Console.Write("호 : _\b");
temp3 = Console.ReadLine();
if (Convert.ToInt32(temp3) >= 1 && Convert.ToInt32(temp3) <= 5)
{
pi.SalaryClass = Convert.ToInt32(temp3);
}
else
{
Console.WriteLine("호 입력은 1~5 사이로 입력하세요");
}
Console.Write("수당 : _____\b\b\b\b\b");
temp4 = Console.ReadLine();
if (Convert.ToInt32(temp4) >= 1000 && Convert.ToInt32(temp4) <= 50000 && Convert.ToInt32(temp4) % 1000 == 0)
{
pi.Allowance = Convert.ToInt32(temp4);
}
else
{
Console.WriteLine("수당은 1,000 ~ 50,000 범위의 정수값 중 1,000의 배수로 입력하세요");
}

//[2] Process
// 급호봉 계산표 급과 호에 해당하는 금액
if (pi.SalaryNumber == 1)
{
switch (pi.SalaryClass)
{
case 1: pi.CalculatingTable = 95000; break;
case 2: pi.CalculatingTable = 92000; break;
case 3: pi.CalculatingTable = 89000; break;
case 4: pi.CalculatingTable = 86000; break;
case 5: pi.CalculatingTable = 83000; break;
default: break;
}
}
else if (pi.SalaryNumber == 2)
{
switch (pi.SalaryClass)
{
case 1: pi.CalculatingTable = 80000; break;
case 2: pi.CalculatingTable = 75000; break;
case 3: pi.CalculatingTable = 70000; break;
case 4: pi.CalculatingTable = 65000; break;
case 5: pi.CalculatingTable = 60000; break;
default: break;
}
}

// 지급액 계산
pi.TaxAmountPayable = pi.CalculatingTable + pi.Allowance;

// 지급액에 따른 세율과 조정액
if (pi.TaxAmountPayable < 70000)
{
pi.TaxRate = 0.0;
pi.MediPay = 0;
}
else if (pi.TaxAmountPayable >= 70000 && pi.TaxAmountPayable <=79999)
{
pi.TaxRate = 0.005;
pi.MediPay = 300;
}
else if (pi.TaxAmountPayable >= 80000 && pi.TaxAmountPayable <= 89999)
{
pi.TaxRate = 0.007;
pi.MediPay = 500;
}
else if (pi.TaxAmountPayable >= 90000)
{
pi.TaxRate = 0.012;
pi.MediPay = 1000;
}
// 세금 구하기
pi.Tax = (int)((double)pi.TaxAmountPayable * pi.TaxRate) - pi.MediPay;

// 차인지급액 구하기
pi.ResultPay = pi.TaxAmountPayable - pi.Tax;

lst.Add(pi); // 컬렉션에 추가
Console.WriteLine("입력(y), 종료(n) :");
btn = Console.ReadLine().ToLower(); // 소문자로
} while (btn == "y" && lst.Count <= 4);
Console.Clear();

//[3] Output
IEnumerable<Pay> q = from p in lst orderby p.EmployeeNumber select p;

Console.WriteLine("사번 급 호 수당 지급액 세금 차인지급액");
foreach (var item in q)
{
Console.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}\t{5}\t{6}", item.EmployeeNumber, item.SalaryNumber, item.SalaryClass,
item.Allowance, item.TaxAmountPayable, item.Tax, item.ResultPay);
}
}
}

반응형

'Program > C#' 카테고리의 다른 글

C#에서 Win32 API 사용하기2  (0) 2011.03.24
웹페이지 자동로그인 구현  (0) 2011.03.24
체중 관리 프로그램  (0) 2011.03.24
윈도우를 종료  (0) 2011.03.24
세가지 Timer 와 그 차이점  (0) 2011.03.24
반응형

using System;
using System.Collections.Generic;
using System.Linq;

public class Weight
{
public int BanNum { get; set; } // 반
public int StuNum { get; set; } // 번호
public int Wei { get; set; } // 몸무게
public int BanHapWei { get; set; } // 반 합계
public int BanAvgWei { get; set; } // 반 평균
public int HapWei { get; set; } // 전제 합계
public int AvgWei { get; set; } // 전체 평균
public int Count { get; set; }
public int BanCount { get; set; } // 반수
public Weight() // 생성자
{
// Empty
}
}

public class 체중관리
{
public static void Main(string[] args)
{
Console.Title = "체중 관리 프로그램";
//[1] Input
List<Weight> lst = new List<Weight>(); // 입력데이터
Weight we;
string temp1; // 입력받은 반 저장
string temp2; // 입력받은 번호 저장
string temp3; // 입력받은 몸무게 저장
string btn = "n";
Console.WriteLine("=====반, 번호, 몸무게를 입력하세요 =====");
do
{
we = new Weight();
Console.Write("반 : _\b");
temp1 = Console.ReadLine();
if (Convert.ToInt32(temp1) >= 1 && Convert.ToInt32(temp1) <=9)
{
we.BanNum = Convert.ToInt32(temp1);
}
else
{
Console.WriteLine("반은 1반에서 9반 사이로 입력하세요.");
return;
}
Console.Write("번호 : _\b");
temp2 = Console.ReadLine();
if (Convert.ToInt32(temp2) >= 1 && Convert.ToInt32(temp2) <= 10)
{
we.StuNum = Convert.ToInt32(temp2);
}
else
{
Console.WriteLine("학생번호는 1부터 10 사이로 입력하세요.");
return;
}
Console.Write("몸무게 : ___\b\b\b");
temp3 = Console.ReadLine();
if (Convert.ToInt32(temp3) >= 40 && Convert.ToInt32(temp3) <=200)
{
we.Wei = Convert.ToInt32(temp3);
}
else
{
Console.WriteLine("몸무게는 40부터 200 사이로 입력하세요.");
}

lst.Add(we);
Console.Write("입력(y), 출력(n) : ");
btn = Console.ReadLine().ToLower(); // 소문자로
} while (btn == "y" && lst.Count <= 9);
Console.Clear();

//[3] Output
IEnumerable<IGrouping<int, Weight>> Query = from p in lst group p by p.BanNum;
we.BanCount = Query.Count();
foreach (IGrouping<int, Weight> g in Query)
{
Console.WriteLine("\r\n" + g.Key + "반\t번호\t몸무게" );

foreach (Weight k in g)
{
Console.WriteLine("{0}\t {1}\t {2}", k.BanNum, k.StuNum, k.Wei);
}
IEnumerable<int> q = from p in lst
where p.BanNum == g.Key
select p.Wei;
we.BanHapWei = q.Sum();
we.Count = q.Count();
we.BanAvgWei = Convert.ToInt32(q.Average());


we.HapWei += we.BanAvgWei;
we.AvgWei = we.HapWei / we.BanCount;
Console.WriteLine(g.Key + "반 평균 :" + we.BanAvgWei);
}

Console.WriteLine("전체평균 : " + we.AvgWei);
}
}

반응형

'Program > C#' 카테고리의 다른 글

웹페이지 자동로그인 구현  (0) 2011.03.24
급여 계산  (0) 2011.03.24
윈도우를 종료  (0) 2011.03.24
세가지 Timer 와 그 차이점  (0) 2011.03.24
WIN32 API를 이용  (0) 2011.03.24
반응형

윈도우를 종료
System.Diagnostics.Process.Start("cmd.exe","ShutDown.exe -s -f -t 00");


윈도우를 재부팅

System.Diagnostics.Process.Start("cmd.exe","ShutDown.exe -r -f -t 00");

특정 폴더 열기
System.Diagnostics.Process.Start("explorer.exe", "C:\Temp");

특정 사이트 열기
System.Diagnostics.Process.Start("explorer.exe", "http://www.naver.com");

도스명령어 실행

System.Diagnostics.Process.Start("cmd.exe","/c dir");

// cmd 옵션에 대해 더 알고싶으면.. c:>help cmd

Process.Start 메서드 사용형식

using System.Diagnostics;

//System.Diagnostics 네임스페이스는 시스템 프로세스, 이벤트 로그 및 성능 카운터와 상호 작용할 수 있는 클래스를 제공합니다.

public bool Start();
//이 Process 구성 요소의 StartInfo 속성으로 지정된 프로세스 리소스를 시작하거나 다시 사용하여 구성 요소에 연결합니다.

Process myProcess = new Process();
string myDocumentsPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
myProcess.StartInfo.FileName = myDocumentsPath + "
\MyFile.doc";
myProcess.StartInfo.Verb = "Print";
myProcess.StartInfo.CreateNoWindow = true;
myProcess.Start();

public static Process Start( ProcessStartInfo startInfo);
// ProcessStartInfo : 파일 이름 및 모든 명령줄 인수를 포함하여 프로세스를 시작하는 데 사용되는 정보
// 시작할 프로세스의 파일 이름 같은 프로세스 시작 정보가 포함된 매개 변수에 의해 지정된
// 프로세스 리소스를 시작하고 해당 리소스를 새 Process 구성 요소에 연결합니다

ProcessStartInfo startInfo = new ProcessStartInfo("IExplore.exe");
startInfo.WindowStyle = ProcessWindowStyle.Minimized;
startInfo.Arguments = "
www.naver.com";
Process.Start(startInfo);

public static Process Start(string fileName);
// fileName : 프로세스에서 실행될 응용 프로그램 파일 이름입니다.

//문서 또는 응용 프로그램 파일 이름을 지정하여 프로세스 리소스를 시작하고 해당 리소스를 새 Process 구성 요소에 연결합니다

Process.Start("IExplore.exe");

public static Process Start(string fileName, string arguments);
// arguments : 프로세스를 시작할 때 전달할 명령줄 인수입니다

//응용 프로그램 이름 및 명령줄 인수 집합을 지정하여 프로세스 리소스를 시작하고 해당 리소스를 새 Process 구성 요소에 연결합니다.

Process.Start("IExplore.exe", "C:\myPath\myFile.htm");
Process.Start("IExplore.exe", "C:\myPath\myFile.asp");

Process 클래스

Process 구성 요소는 컴퓨터에서 실행 중인 프로세스에 대한 액세스를 제공합니다. 간단히 말해 프로세스란 실행 중인 응용 프로그램을 말합니다.

Process 구성 요소는 응용 프로그램의 시작, 중지, 제어 및 모니터링을 위한 유용한 도구입니다.
Process 구성 요소를 사용하면 실행 중인 프로세스의 목록을 얻거나 새로운 프로세스를 시작할 수 있습니다. 또한 Process 구성 요소를 사용하여 시스템 프로세스에도 액세스할 수 있습니다.
Process 구성 요소를 초기화한 후에는 해당 구성 요소를 사용하여 실행 중인 프로세스에 대한 정보를 얻을 수 있으며 그러한 정보에는 스레드 집합, 로드된 모듈(.dll 및 .exe 파일), 프로세스가 사용하고 있는 메모리 양과 같은 성능 정보 등이 포함됩니다.

프로세스 구성 요소는 속성 그룹에 대한 정보를 한 번에 가져옵니다. Process 구성 요소가 특정 그룹의 한 멤버에 대한 정보를 가져올 때 해당 그룹의 나머지 속성 값이 캐싱되므로 Refresh 메서드를 호출하지 않는 한 그룹의 다른 멤버에 대한 새로운 정보를 가져오지 않습니다. 따라서 속성 값이 Refresh 메서드를 마지막으로 호출하여 얻은 속성 값과 같을 수 있습니다. 이러한 그룹 명세는 운영 체제에 따라 다릅니다.

더 자세한 사항은 Microsoft Visual Studio .NET 2003 도움말에서 Process 클래스를 참고하세요.

반응형

'Program > C#' 카테고리의 다른 글

급여 계산  (0) 2011.03.24
체중 관리 프로그램  (0) 2011.03.24
세가지 Timer 와 그 차이점  (0) 2011.03.24
WIN32 API를 이용  (0) 2011.03.24
DB에 이미지저장하고 불러오기  (0) 2011.03.24
반응형

특정 작업을 주기적으로 실행하기 위해 흔히 Timer 객체를 사용합니다

정해진 시간 간격으로 변수를 업데이트 한다던지, 모니터링 한다던지, 로그를 기록 한다던지, 그 작업 내용은 무궁무긴 하겠죠

Timer 객체는 이러한 주기적 작업을 아주 쉽게 처리해 주는, 닷넷 프레임워크에서 제공하는 고마운 객체입니다


그러나 한가지 생각해 볼 문제가 있네요..

닷넷 프레임워크에는 무려 3가지 서로 다른 Timer 를 제공하고 있다는 겁니다. 바로 아래 3가지 Timer 입니다
1. System.WIndows.Forms.Timer
2. System.Threading.Timer

3. System.Timers.Timer

닷넷이 이 3가지 Timer 를 각각 제공하는 이유가 무엇일까요?

필자는 이 문제(?)에 대해, 몇 년전에 의구심을 가졌읍니다만, 당시 의구심만 가진채 그냥 세월을 보내 버렸습니다 --;

그리고는 대략 아는 지식으로 대략 적절 할 것 같은(?) Timer을 사용해 왔던 것 같습니다

게을렀던 거죠. 의구심이 들면 파고들어 정복하는 사람이 성공합니다. ㅋㅋ , 기술이든 인생이든...

예기가 다른 길로 세네요.. ㅎ,

우선 이 세가지 서로다른 Timer 의 msdn 설명을 볼까요


1) System.Windows.Forms.Timer
사용자가 정의한 간격마다 이벤트를 발생시키는 타이머를 구현합니다. 이 타이머는 Windows Forms 응용 프로그램에서

사용할 수 있도록 최적화되었으며 창에서 사용해야 합니다

2) System.Threading.Timer

지정된 간격으로 메서드를 실행하는 메커니즘을 제공합니다

3) System.Timers.Timer

응용 프로그램에 되풀이 이벤트를 생성합니다


msdn 설명을 봐도,
'System.WIndows.Forms.Timer 가 윈도우 응용프로그램에 최적화 되었다' 라는 말 빼고는 거의 차이점을 느낄 수 없네요

물론 msdn은 보다 상세한 내용을 더 기술되어 있습니다만, 이 글에서는 이 세가지 Timer 의 차이점을 크게 두 가지 측면에서
살펴 볼까 합니다

1. 사용법상의 차이점

2. 수행되는 Thread 환경의 차이점

* 사용법의 차이

먼저 사용법의 차이를 알아보죠

사용법의 차이는 말 그대로 사용법입니다. 이것이 원리는 아니죠.
원리가 다르기 때문에 사용법이 다른 것이지, 사용법이 다르기 때문에 원리가 다른건 아닙니다

그럼에도, 사용법 차이점부터 알아 보는 것은.......... 쉽기 때문이죠 ^^;

(개발자 여러분, 사용법만 익히지 말고 원리를 익힙시다)

1. System.Windows.Forms.Timer 사용법
윈도우 응용프로그램 개발자들에겐 아마 가장 익숙한 Timer 일 것입니다

- 객체 생성

System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();

- 반복 주기 및 작업 설정

timer.Interval = 1000; //주기 설정

timer.Tick += new EventHandler(timer_Tick); //주기마다 실행되는 이벤트 등록

void tmrWindowsFormsTimer_Tick(object sender, System.EventArgs e)
{
//수행해야할 작업

}

- Timer 시작

timer.Enable = true 또는 timer.Start();

- Timer 중지

timer.Enable = false 또는 timer.Stop();

2. System.Threading.Timer 사용법

- 객체 생성

Timer 객체를 생성할 때, 반복적으로 실행하게 될 메서드를 콜백 메서드로 등록해야 합니다

System.Threading.Timer timer = new System.Threading.Timer(CallBack);

- 반복 주기 및 작업 설정

이 Timer 에는 Change 메서드가 있는데, 이 메서드는 dueTime과 period 를 입력받습니다

dueTime은 Timer 가 시작하기 전 대기(지연)시간이며 period는 반복 주기입니다
timer.Change(0, 1000);

그리고 반복 실행 작업이,
윈도우 응용프로그램의 UI Thread와 연관된다면, Cross Thread 문제가 발생하기 때문에 Invoke나 BeginInvoke를

통해 핸들링 해야 합니다.

앞서, Timer 객세 생성시 등록한 콜백 메서드에서 BeginInvoke를 통해 UI 쓰레드를 핸들링 할 수 있습니다

delegate void TimerEventFiredDelegate();

void CallBack(Object state)
{
BeginInvoke(new TimerEventFiredDelegate(Work));
}

private void Work()
{

//수행해야할 작업(UI Thread 핸들링 가능)
}

- Timer 시작

위의 Change 메서드의 dueTime 이 0 이므로 그 즉시 시작된다. Start와 같은 별도의 시작 명령이 존재하지 않음

- Timer 중지
timer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);

dueTime와 period 를 무한대로 잡아서 Timer 가 실행되지 않도록 하는 것이 중지하는 것과 같습니다

3. System.Timers.Timer 사용법

- 객체 생성

System.Timers.Timer timer = new System.Timers.Timer();

- 반복 주기 및 작업 설정
timer.Interval = 1000;
timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed); //주기마다 실행되는 이벤트 등록


이 Timer 역시 UI Thread를 핸들링 하기 위해서 Invoke 나 BeginInvoke를 이용해야 합니다
delegate void TimerEventFiredDelegate();
void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
BeginInvoke(new TimerEventFiredDelegate(Work));
}

private void Work()
{

//수행해야할 작업(UI Thread 핸들링 가능)
}

- Timer 시작

timer.Enable = true 또는 timer.Start();

- Timer 중지

timer.Enable = false 또는 timer.Stop();

결론적으로 보면,

Timer 객체의 사용법 자체는 그리 어렵지 않습니다. 또한 세 가지 Timer 의 사용법은 대동소이 함을 알 수 있습니다

다만 윈도위 응용프로그램에서 Timer 를 사용할 때 System.WIndows.Forms.Timer 를 제외하고는

UI Thread 에서 만들어진 컨트롤에 접근하려면 크로스 쓰레드 문제가 있으므로 마샬링 된 호출(Invoke / BeginInvoke) 를

이용해야 하는 차이점이 있습니다

msdn의 설명처럼 System.Windows.Forms.Timer 는 윈도우 응용프로그램에 최적화 되어 있나 보네요..

그럼 왜 System.Windows.Forms.Timer 는 크로스쓰레드 문제가 발생하지 않을까요?

그리고 정말 사용법 처럼 크게 차이가 나지 않는 걸까요?


다음에 설명할, 두번째 관점인 '수행되는 Thread 환경의 차이점'에서 이를 알아보도록 하죠

* 수행되는 쓰레드(Thread) 환경의 차이

앞서 사용법에서 UI Thread 라는 말을 했습니다

윈도우 응용프로그램을 예로 들어, 버턴이나 각종 컨트롤이 생성되고 핸들링 되는 것은 UI Thread 상에서 이루어집니다

이와 다른 개념이 Work Thread 인데요, 기본 쓰레드(Default Thread) 이외에
개발자가 별도의 쓰레드를 생성하여 작업을 실행한다면 이는 Work Thread(작업자 쓰레드) 라 합니다

또한 UI Thread 입장에서는, 닷넷의 ThreadPool 에 의해 실행되는 쓰레드도 Work Thread 로 볼 수 있습니다

쓰레드가 다르다면 쓰레드의 고유번호도 당연히 다릅니다

System.Threading.Thread.CurrentThread.IsThreadPoolThread 속성은 현재 쓰레드의 고유 식별자 값을 가져 옵니다

우린 이 속성을 통해 Timer 객체가 수행되는 쓰레드를 알아 보도록 하겠습니다

1. System.Windows.Forms.Timer 의 쓰레드 환경

윈도우 응용프로그램에 최적회 되어 있다는 이 Timer 는 윈도우 응용프로그램 기본 쓰레드와 동일한 쓰레드 상에서 동작합니다

이를 확인하기 위해, 다음과 같이 코드 중간에 IsThreadPoolThread 속성을 확인해 봅니다

- 기본 쓰레드의 고유 번호를 확인한다

윈도우응용프로그램 생성자나 기타 이벤트에서 아래 코드를 기입합니다

MessageBox.Show(System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());

- Timer 쓰레드의 고유번호를 확인한다

Timer 의 Tick 이벤트에서 다음의 코드를 기입합니다

void timer1_Tick(object sender, EventArgs e)
{
MessageBox.Show(System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());

//수행해야할 작업
}

이렇게 확인 해 보면 두 쓰레드는 동일한 고유번호를 반환하게 됩니다

이 말은 곧, 윈도우응용프로가램의 기본 쓰레드인 UI Thread 상에서 Timer이 동작한다는 것을 짐작할 수 있습니다

즉 멀티 쓰레드 환경이 아닌 것이죠

예로, Tick 이벤트에 시간이 긴~ 작업이 수행된다면 프로그램은 그 시간 동안 블럭 된 대기 한다는 것입니다

Timer 의 동작이 기본 프로그램 동작과 독립적으로 수행된다고 생각하시면 안됩니다

2. System.Threading.Timer 의 쓰레드 환경

역시 앞서와 같이 기본 쓰레드와 Timer 쓰레드의 고유번호를 확인 해 봅니다

- 기본 쓰레드의 고유 번호를 확인한다

윈도우응용프로그램 생성자나 기타 이벤트에서 아래 코드를 기입합니다

MessageBox.Show(System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());

- Timer 쓰레드의 고유번호를 확인한다

CallBack 메서드에서 다음과 같이 코드를 기입합니다

void CallBack(Object state)
{
MessageBox.Show(System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());

BeginInvoke(new TimerEventFiredDelegate(Work));
}

어떻습니까? 두 쓰레드는 다른 고유번호를 반환하지요

즉 UI Thread 와 다른 쓰레드, 즉 Work Thread(작업자 쓰레드)임을 알 수 있습니다

이는 곧 멀티 쓰레드가 된 셈이죠. 두 쓰레드는 서로 독립적으로 수행될 것입니다

앞서, System.Windows.Forms.Timer 객체와는 달리 CallBack 메서드에 시간이 오래 걸리는 작업을 수행해도

프로그램이 대기상태로 빠지는 않죠. Timer 동작이 기본 프로그램의 동작과는 독립적으로 수행되는 것이죠.

참고로 이 Timer 는 닷넷의 ThreadPool(쓰레드풀) 에서 관리합니다

3. System.Timers.Timer 의 쓰레드 환경

결론부터 말하자만, 이 Timer 는 기본 쓰레드에서 수행될 수도 있고, 작업자 쓰레드에서 수행될 수도 있습니다

만일, SynchronizingObject 속성을 폼객체로 한다면 Timer는 UI 쓰레드 상에서 동작할 것이며

이 속성을 지정하지 않는다면, 작업자 쓰레드 상에서 동작하게 됩니다

아래와 같이 SynchronizingObject 속성의 설정 여부에 따른 ManagedThreadid 값을 확인해 보기 바랍니다

timer.SynchronizingObject = this;

타이머 쓰레드의 고유번호를 알기 위해 Elapsed 이벤트에

MessageBox.Show(System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());

를 확인해 보세요

---

결국 Timer 의 실행이 기본 쓰레드에서 하느냐, 작업자 쓰레드 에서 하느냐에 차이인데요,

앞서, 사용법의 차이를 살펴 봤을 때 System.Windows.Forms.Timer 객체를 제외하고는 윈도우응용프로그램의 UI 컨트롤

핸들링 시 크로스 도메인 문제가 발생했던 원인이 되는 것입니다

* 기타 차이점 및 요약, 참조

아래 표는 msdn magazine에 소개된 세 Timer 의 차이점에 대한 표입니다

우리가 알아 본 내용 이외에도, 쓰레드 안정성(동기화 문제)에 대한 내용도 있습니다

<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

System.Windows.Forms

System.Timers

System.Threading

Timer event runs on what thread?

UI thread

UI or worker thread

Worker thread

Instances are thread safe?

No

Yes

No

Familiar/intuitive object model?

Yes

Yes

No

Requires Windows Forms?

Yes

No

No

Metronome-quality beat?

No

Yes*

Yes*

Timer event supports state object?

No

No

Yes

Initial timer event can be scheduled?

No

No

Yes

Class supports inheritance?

Yes

Yes

No

* Depending on the availability of system resources (for example, worker threads)

마지막으로 msdn의 설명을 옮기며 마칩니다

서버 타이머, Windows 타이머 및 스레드 타이머

Visual Studio 및 .NET Framework에는 세 개의 타이머 컨트롤 즉, 도구 상자구성 요소 탭에서 볼 수 있는 서버 기반 타이머, 도구 상자Windows Forms 탭에서 볼 수 있는 표준 Windows 기반 타이머 및 프로그래밍 방식으로만 사용할 수 있는 스레드 타이머가 있습니다.

Windows 기반 타이머는 Visual Basic 1.0 이상의 버전에 있으며 지금까지 크게 변경되지 않았습니다.
이 타이머는 Windows Forms 응용 프로그램에서 사용하도록 최적화되어 있습니다.
서버 기반 타이머는 일반 타이머를 서버 환경에서 최적으로 실행되도록 업데이트한 것입니다.

스레드 타이머는 이벤트 대신 콜백 메서드를 사용하는 간단한 소형 타이머로서 스레드 풀 스레드에서 제공합니다.

Win32 아키텍처에는 UI 스레드와 작업자 스레드라는 두 종류의 스레드가 있습니다.
UI 스레드는 대부분의 시간을 유휴 상태로 보내며 메시지 루프에 메시지가 도착할 때까지 기다립니다. 메시지가 도착하면
이 메시지를 처리하고 다음 메시지가 도착할 때까지 기다립니다. 이에 비해 작업자 스레드는 백그라운드 처리를 수행하는 데 사용하며 메시지 루프를 사용하지 않습니다.

Windows 타이머와 서버 기반 타이머는 모두 Interval 속성을 사용하여 실행됩니다.
스레드 타이머의 간격은 <?XML:NAMESPACE PREFIX = MSHelp NS = "http://msdn.microsoft.com/mshelp" />Timer 생성자에서 설정됩니다.
스레드에서 타이머를 다루는 방식을 보면 알 수 있듯이 각 타이머의 용도는 서로 다릅니다.

  • Windows 타이머는 UI 스레드가 프로세싱을 수행하는 데 사용하는 단일 스레드 환경을 위해 설계되었습니다. Windows 타이머의 정확도는 55밀리초로 제한되어 있습니다. 이 일반 타이머는 사용자 코드에서 사용할 수 있는
    UI 메시지 펌프가 필요하며 항상 동일한 스레드에서 실행되거나 다른 스레드로 마샬링됩니다.
    이 기능은 COM 구성 요소의 성능을 저하시킵니다.

  • 서버 기반 타이머다중 스레드 환경에서 작업자 스레드와 함께 사용하도록 설계되었습니다. 두 스레드는 서로 다른 아키텍처를 사용하므로 서버 기반 타이머가 Windows 타이머보다 정확합니다.
    서버 타이머는 스레드 사이를 이동하면서 발생한 이벤트를 처리할 수 있습니다.

  • 스레드 타이머는 메시지가 스레드에서 펌프되지 않는 경우에 유용합니다.
    예를 들어, Windows 기반 타이머는 운영 체제의 타이머 지원 기능에 의존하며 스레드에서 메시지를 펌프하지 않을 경우에는 타이머 관련 이벤트가 발생하지 않습니다. 이 경우에는 스레드 타이머가 보다 더 유용합니다.

Windows 타이머는 System.Windows.Forms 네임스페이스에, 서버 타이머는 System.Timers 네임스페이스에 그리고 스레드 타이머는 System.Threading 네임스페이스에 있습니다.

반응형

'Program > C#' 카테고리의 다른 글

체중 관리 프로그램  (0) 2011.03.24
윈도우를 종료  (0) 2011.03.24
WIN32 API를 이용  (0) 2011.03.24
DB에 이미지저장하고 불러오기  (0) 2011.03.24
Thread를 이용한 파일복사하기-프로그래스바  (0) 2011.03.24
반응형

원문 링크 : http://kkommy.com/1170255989

C#에서 WIN32 API를 이용하는 방법을 알아본다.

* 기본적으로 DllImport!를 이용하여 해당 DLL을 직접 import!해서 사용해야 한다.
* 사용할 API에 대해서는 API관련 문서를 참고하도록 한다.
* 아래는 WIN32 API를 이용하여 파일을 열어서 읽고 닫는 클래스이다.
* StreamReader와의 속도면에서 큰 차이가 없으나, 파일을 열때 추가적인 속성 등을 결정할 수 있는 장점이 있다.


빌드시 유의점

* VS.Net 2003의 C#에서 포인터를 사용하는 Win API를 이용하려고 하는 경우 컴파일되지 않는다.
* 프로젝트의 구성 페이지 > 구성속성 > 빌드 > 코드생성 > 안전하지 않은 코드 블록 허용 => true로 변경 후에 컴파일 해야한다.

예제

01.using System;
02.using SystemSystem .Runtime.InteropServices;
03.namespace NameSpaceTestNameSpaceTest
04.{
05. public classclass FileReader
06. {
07. public bool isOpen=false;; // 파일에 대한 엑세스 권한권한
08. const uint GENERIC_READ = 0x80000000; // 읽기 전용
09. const uint GENERIC_WRITE = 0x40000000; // 쓰기 전용
10. const uint GENERIC_EXECUTE = 0x20000000; //// 실행 전용
11. const uint GENERIC_ALL = 0x10000000; // 모든 권한권한
12. // 만약 읽기와 쓰기를 원한다면원한다면 GENERIC_READ | GENERIC_WRITE 를 인자로 넣는다.
13. // 파일의 공유모드
14. const uint FILE_SHARE_READ = 0x00000001; // 읽기 허가
15. const uint FILE_SHARE_WRITE == 0x00000002; // 쓰기 허가
16. const uint FILE_SHARE_DELETE = 0x00000004;0x00000004; // 삭제 허가
17. // 파일의 생성여부
18. const uint CREATE_NEW = 1; // 파일을 만듦 만약만약 있다면 에러 리턴
19. const uint CREATE_ALWAYS = 2; // 항상항상 패일을 새로 만든다. 만약 파일이 존재한다면 해당 파일을 지우고 새로 만든다.만든다.
20. const uint OPEN_EXISTING = 3; // 이미 존재하는 파일을 연다연다 파일이 없다면 에러 리턴
21. const uint OPEN_ALWAYS = 44 ; // 무조건 파일을 연다. 파일이 없다면 파일을 만들고 연다.연다.
22. const uint TRUNCATE_EXISTING = 5; // 파일을 연후연후 크기를 0으로 만든다.
23. const uint FILE_ATTRIBUTE_READONLYFILE_ATTRIBUTE_READONLY = 0x00000001; // 읽기읽기 전용의 파일로 생성한다. 응용 프로그램은 이 파일의 내용을 읽을 수는 있지만있지만 변경하거나 삭제할 수는 없다.
24. const uint FILE_ATTRIBUTE_HIDDEN = 0x00000002; // 숨김 파일로 생성한다. 숨김숨김 파일은 통상적인 방법으로는 보이지 않으므로 목록에 나타나지 않는다.
25. const uint FILE_ATTRIBUTE_SYSTEMFILE_ATTRIBUTE_SYSTEM = 0x00000004; //// 시스템 파일로 생성한다. 시스템 파일은 운영체제에 의해 배타적으로 사용되는 파일이다
26. const uint FILE_ATTRIBUTE_DIRECTORYFILE_ATTRIBUTE_DIRECTORY = 0x00000010;
27. const uint FILE_ATTRIBUTE_ARCHIVE = 0x00000020; // 기록 속성을 설정한다. 파일의 기록 속성은 백업, 리스토어 프로그램에 의해의해 사용되며 이 파일이 백업되어야 함을 알리는 플래그이다.
28. const uint FILE_ATTRIBUTE_DEVICE = 0x00000040;
29. const uint FILE_ATTRIBUTE_NORMAL = 0x00000080; // 아무런 속성도 가지지 않는 파일을 만든다. 이 이 플래그는 단독으로단독으로 사용될 때만 유효하며 다른 플래그와 함께 사용하면 해당 플래그의 속성이 설정되다.설정되다.
30. constconst uint FILE_ATTRIBUTE_TEMPORARY = 0x00000100; // 임시 파일로 생성한다. 임시 파일은 디스크로 곧바로곧바로 입출력을 행하지 않고 가급적이면 메모리상에서 읽기와 쓰기를 수행하기 때문에 일반 파일보다파일보다 입출력 속도가 빠르다는 장점이 있다. 응용 프로그램은 임시파일을 다 사용한 후후 반드시 삭제해 주어야 한다.
31. const uint FILE_ATTRIBUTE_SPARSE_FILE = 0x00000200;
32. const uint FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400;
33. const uint FILE_ATTRIBUTE_COMPRESSED = 0x00000800;
34. const uint FILE_ATTRIBUTE_OFFLINE = 0x00001000; // 데이터가 오프라인 상태이며상태이며 즉시 사용할 수 있는 상태가 아니다. 이 속성은 윈도우즈 20002000 의 계층적 저장 관리자의 원격 저장소에 의해 사용되므로 응용 프로그램이 이 플래그를 직접 사용해서는 안된다.
35. const uint FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000; // 컨텐트 인덱싱 서비스에 대해 인덱스되지인덱스되지 않도록 한다.
36. constconst uint FILE_ATTRIBUTE_ENCRYPTED = 0x00004000; // 파일을 암호화한다. 파일의 경우 파일의 데이터를 암호화하며암호화하며 디렉토리의 경우 이후부터 생성되는 파일과 서브 디렉토리를 암호화하도록 한다. 시스템 파일에는파일에는 적용되지 않는다.
37. SystemSystem .IntPtr handle;
38. // Dll의 이름을 지정한다 kernel32kernel32 .dll은 환경변수에 등록된 폴더 안에 있음으로 이름만 기재한다.
39. // 포인터를 사용하기사용하기 때문에 unsafe 한정자를 추가한다.
40. [System.Runtime.InteropServices.DllImport!.DllImport! ("kernel32", SetLastError = true)]
41. static externextern unsafe System.IntPtr CreateFile(( string FileName,uint DesiredAccess,uint ShareMode,uintuint SecurityAttributes,uint CreationDisposition,uint FlagsAndAttributes,int hTemplateFilehTemplateFile );
42. [[ System.Runtime.InteropServices.DllImport!("kernel32",, SetLastError = true)]
43. static extern unsafe bool ReadFile(System.IntPtr hFile,void*hFile,void* pBuffer,int NumberOfBytesToRead,int* pNumberOfBytesRead,int Overlapped)) ;
44. [System.RuntimeRuntime .InteropServices.DllImport!("kernel32", SetLastError = truetrue )] static externextern unsafe bool CloseHandle(SystemSystem .IntPtr hObject);
45. public bool Open(( string FileName)
46. {
47. handle = CreateFile(FileName,GENERIC_READ,FILE_SHARE_READ,FileName,GENERIC_READ,FILE_SHARE_READ, 0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
48. ifif (handle != System.IntPtr.ZeroZero )
49. {
50. isOpen=true;
51. }
52. elseelse
53. {
54. isOpen=false;
55. }}
56. return isOpen;
57. }
58. public unsafe int Read(byte[] buffer, int index, int count)
59. {
60. int n = 0;
61. fixed (byte* pp = buffer)
62. {
63. ifif (!ReadFile(handle, p + index, count, &n,&n, 0))
64. {
65. return 0;
66. }
67. }
68. return n;n;
69. }
70. public bool Close()
71. {
72. isOpen=falsefalse ;
73. return CloseHandleCloseHandle (handle);
74. }
75. }
76.}
반응형
반응형
 //사진등록 콤보(클릭이벤트)
private void btnADD_Click(object sender, System.EventArgs e)
{

openFile.DefaultExt = "gif"; //다이얼로그 박스 확장자 gif만을 불러오기
openFile.Filter = "Graphics interchange Format (*.gif)|*.gif";
openFile.ShowDialog();

if( openFile.FileNames.Length > 0 )
{
pictureBox1.Image = Image.FromFile // 우선 픽쳐박스에 뿌려준다..
(openFile.FileNames[0]);
}
}

//세이브 버튼을 클릭하셔서 클릭 이벤트를 생성하시고 Save()를 호출합니다.

private void Save()
{
try
{
object strResult = pictureBox1.Image;

//sql 연결 컨넥트
SqlConnection con = new SqlConnection("Server=111.111.111.111;database=testdb;Password=test;User ID=sa;Initial Catalog=dbTableName");
SqlDataAdapter da = new SqlDataAdapter("select IMG from image", con);
SqlCommandBuilder MyCB = new SqlCommandBuilder(da);
DataSet ds = new DataSet("image");

da.MissingSchemaAction = MissingSchemaAction.AddWithKey;


FileStream fs = new FileStream(openFile.FileNames[0] , FileMode.OpenOrCreate, FileAccess.Read);

byte[] MyData= new byte[fs.Length];
fs.Read(MyData, 0, System.Convert.ToInt32(fs.Length));

fs.Close();

da.Fill(ds,"image");

DataRow myRow;
myRow=ds.Tables["image"].NewRow();

myRow["IMG"] = MyData;
ds.Tables["image"].Rows.Add(myRow);
da.Update(ds, "image");

con.Close();
MessageBox.Show("정상적으로 처리되었습니다..","저장되었습니다.",MessageBoxButtons.OK, MessageBoxIcon.Warning);

}
catch(Exception e)
{
MessageBox.Show(e.ToString());
}
}

//폼로드

private void InitializeForm()
{
try
{
string strConn = "User ID=sa;Password=park0515;database=image";
StringBuilder sbSql = new StringBuilder();
DataTable dt = new DataTable();

sbSql.Append("SELECT TOP 1 img FROM IMAGE");

SqlConnection SqlConn = new SqlConnection(strConn);
SqlConn.Open();

SqlCommand cmd = new SqlCommand(sbSql.ToString(),SqlConn);
SqlDataAdapter da = new SqlDataAdapter(cmd);
da.Fill(dt);

byte[] MyData = null;
MyData = (byte[])dt.Rows[0][0];
int ArraySize = new int();
ArraySize = MyData.GetUpperBound(0);
FileStream fs = new FileStream("tmp.gif", FileMode.OpenOrCreate, FileAccess.Write);
fs.Write(MyData, 0,ArraySize+1);
fs.Close();
picPHOTO.Image = new Bitmap("tmp.gif");


}
catch(Exception e)
{
MessageBox.Show(e.ToString());
}
}

이런식으로 하시면 되요..

1. testdb로 생성

2. 테이블명을 dbTableName 명시

3. 데이터 형식을 이미지로 img 표시

그럼 테스트 고고고!!!

반응형
반응형

출처 [가치지향]님의 .NET Brain...C# | 가치지향
원문 http://blog.naver.com/hanbyi/110012603567


이 어플리케이션은 파일을 복사하는 기능을 가지고 있는 프로그램으로 복사하는 진행과정을 표시하기 위해서 프로그래스바(ProgressBar)컨트롤을 이용하였으며, 실제 파일이 복사되는 작업은 스레드(Thread)로 처리하여 복사작업과 진행상태작업이 동시에 이루어지도록 했습니다.

그리고 이프로그램은 그림에서 알 수 있듯이 두 개의 폼으로 구성되어 있습니다. 첫번째 폼(Form1)은 복사할 원본과 대상을 지정하는 폼이고, 두번째 폼(DownDialog)은 복사 진행상황을 보여주는 폼입니다. 다음 소스를 통해서 각 폼에서 처리할 일들을 알아보도록 하겠습니다.

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

// Form1

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

복사버튼을 클릭하면 발생하는 핸들러로서 DownDialog폼을 생성하여 화면에 출력하는 기능을 가진 함수입니다.

private void btnExecute_Click(object sender, System.EventArgs e)
{

// 원본과 대상파일명이 있는 텍스트박스의 Text속성값을 생성자의 전달인자로 사용
DownDialog dlg = new DownDialog(txtSrc.Text, txtDest.Text);
dlg.Show();
}

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

// DownDialog

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

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Threading;
using System.IO;

namespace Thread
{
public class DownDialog : System.Windows.Forms.Form
{

// 스레드 생성과정에서 서로 다른 프로세스에 접근을 위해서 필요한 델리깃 선언
public delegate void SetProgCallBack(int vv); // 진행바 값을 보내기 위한 델리깃
public delegate void SetLabelCallBack(string str); // 진행률을 보내기 위한 델리깃
public delegate void ExitCallBack(); // 복사완료 후 창 종료를 위한 델리깃

private System.Threading.Thread t1; // 스레드 선언
private byte [] bts = new byte[4096]; // 파일스트림으로 주고받을 기본단위
private FileStream fsSrc = null; // 원본파일 스트림
private FileStream fsDest = null; // 대상파일 스트림

public DownDialog(string src, string dest) {
InitializeComponent();

// 해당 파일을 파일 스트림 객체로 생성

fsSrc = new FileStream(src, FileMode.Open, FileAccess.Read); //읽기
fsDest = new FileStream(dest, FileMode.Create, FileAccess.Write); //쓰기
}


private void DownDialog_Load(object sender, System.EventArgs e) {
progressBar1.Maximum = 100; // 진행바의 최대값 100으로 설정

// 스레드 생성

t1 = new System.Threading.Thread(new ThreadStart(DownLoad));
t1.Start(); // 스레드 시작
}

// t1 스레드에서 만들어진 값(vv)을 메인스레드(폼)의 진행바컨트롤에 지정하기 위한 메소드

private void SetProgBar(int vv) {

// 서로다른 프로세스에서 객체를 크로스로 접근하면 예외가 발생하므로 이를 해결하기

// 위해서 Invoke 메소드 사용하게 된다.

// 진행바가 현재 Invoke가 필요한 상태인지 파악하여 필요하다면 대기상태에 있다가

// 접근가능할 때 백그라운드 작업을 진행하고, 필요한 상태가 아니라면

// 진행바의 해당 속성에 바로 대입한다.
if(this.progressBar1.InvokeRequired) {

// 델리깃 생성
SetProgCallBack dele = new SetProgCallBack(SetProgBar);

// 대기상태에 있다가 접근가능한 상태일 때 SetProgBar 간접호출
this.Invoke(dele, new object[] { vv });
}
else
this.progressBar1.Value = vv;
}

// t1 스레드에서 만들어진 값(str)을 메인스레드(폼)의 레이블에 지정하기 위한 메소드

private void SetLabel(string str) {
if(this.label2.InvokeRequired) {
SetLabelCallBack dele = new SetLabelCallBack(SetLabel);
this.Invoke(dele, new object[] { str });
}
else
this.label2.Text = str;
}

// t1 스레드에서 메인스레드(폼)의 종료메소드(Close)를 지정하기 위한 메소드

private void Exit() {
ExitCallBack dele = new ExitCallBack(Close);
this.Invoke(dele); // 복사진행률 창 닫기
}

// 파일 복사 작업 및 진행률 구하여 값 넘기기
private void DownLoad() {
int vv = 1; // 진행바의 진행률을 저장할 변수
int cnt = 0; // 1. 반복 횟수 및 2. 파일스트림 읽거나 저장할 위치 지정 변수


while(true) {
if(vv >= 100) { // 진행률이 100이 되면 무한루프 탈출
break;
}

// 원본파일 읽을 위치지정 및 4096바이트 읽어 bts에 저장
fsSrc.Seek(4096 * cnt, SeekOrigin.Begin); fsSrc.Read(bts, 0, 4096);

// 대상파일에 쓸 위치지정 및 bts에 저장된 값 쓰기
fsDest.Seek(4096 * cnt, SeekOrigin.Begin); fsDest.Write(bts, 0, 4096);


cnt++;

vv = (int)(fsDest.Length * 100 / fsSrc.Length); // 진행률 구하기

// 메인스레드에 값넘기기

SetProgBar(vv);
SetLabel(vv + "%");
}
this.Exit(); // 메인스레드에 종료 알리기
}
}
}

반응형
반응형

C# RegistryKey 클래스로 레지스트리 관리를 지원하고 있습니다.

RegistryKey 클래스를 사용하기 위해서는 Microsoft.Win32 네임스페이스를 사용해야 합니다.

-> using Microsoft.Win32

주로 쓰이는 것은 CreateSubKey(키 생성), SetValue(값 저장), GetValue(값 얻기) 입니다.

아래는 윈도우 폼으로 레지스트리를 사용하여

윈도우 창의 색깔을 랜덤하게 바꾸는 프로그램입니다.

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

using Microsoft.Win32;

namespace RegistryTEST

{

public partial class MainForm : Form

{

private Color _BColor;

public MainForm()

{

InitializeComponent();

}

private void btnREAD_Click(object sender, EventArgs e)

{

RegistryKey reg;

reg = Registry.LocalMachine.CreateSubKey("Software").CreateSubKey("테스트");

int R = Convert.ToInt32(reg.GetValue("RED", -1));

int G = Convert.ToInt32(reg.GetValue("GREEN", -1));

int B = Convert.ToInt32(reg.GetValue("BLUE", -1));

if( R == -1 || G == -1 || B == -1 )

{

lbOUT.Text = "레지스트리가 없습니다";

return;

}

_BColor = Color.FromArgb(R, G, B);

lbOUT.Text = "읽기 : " + R + ", " + G + ", " + B;

}

private void btnWRITE_Click(object sender, EventArgs e)

{

RegistryKey reg;

reg = Registry.LocalMachine.CreateSubKey("Software").CreateSubKey("테스트");

Random r = new Random();

int R = r.Next(0, 255);

int G = r.Next(0, 255);

int B = r.Next(0, 255);

reg.SetValue("RED", R);

reg.SetValue("GREEN", G);

reg.SetValue("BLUE", B);

lbOUT.Text = "쓰기 : " + R + ", " + G + ", " + B;

}

private void btnLOAD_Click(object sender, EventArgs e)

{

if( _BColor.IsEmpty )

{

lbOUT.Text = "레지스트리를 읽어주세요";

return;

}

this.BackColor = _BColor;

}

}

}

레지스트리값은 [HKEY_LOCAL_MACHINE\SOFTWARE\테스트] 입니다

반응형
반응형

윈폼이나 모듈단위에서 특정 웹페이지를 파라메타와 함께 호출하는 방밥을 알아 본다.

1. 아주 심필한 방법
- System.Diagnostics.Process.Start("http://blog.daum.net/starkcb?t__nil_loginbox=blog1");
이것의 단점은 해당 프로세서가 실제로 실행되어 버린다는 것이다.
즉, 브라우저가 열려버린다는 것이다.

2. 브라우저를 열지 않고 웹페이지 호출하기
string url = "http://blog.daum.net/starkcb?t__nil_loginbox=blog1/te.aspx";
WebRequest request = WebRequest.Create(url);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";

//전달할 파라메타
string sendData = "param1=1¶m2=2";

byte [] buffer;
buffer = Encoding.Default.GetBytes(sendData);
request.ContentLength = buffer.Length;
Stream sendStream = request.GetRequestStream();
sendStream.Write( buffer, 0, buffer.Length);
sendStream.Close();

이렇게 하면 윈폼or 모듈단위에서 브라우저 실행없이 웹페이지 호출이 가능해진다.

반응형
반응형

『 비 동기 웹 서비스 호출』

.NET Framework 는 파일에 대한 I/O 및 네트워크 통신 등에 비 동기 호출 메커니즘을 지원해 왔다.

또한 ADO.NET 2.0 에서는 데이터베이스 관련 작업에도 비 동기 호출을 지원하기 시작했다.

물론 ASP.NET XML WebService 에서도 비 동기 호출을 지원한다.

이번 글에서는 웹 서비스에서의 비 동기 호출 방법에 대해 알아보도록 한다

우선 알아두어야 할 것이 비 동기 웹 서비스 호출이 .NET Framework 1.x 2.0 의 차이점이 있다는 것이다.

.NET Framework 2.0에서는 기존의 1.x 에서의 비 동기 호출 보다 직관적이고 이벤트 지향적으로 변경되었다.

1. .NET Framework 1.x 에서의 웹 서비스 비 동기 호출

우선 예전 방식(1.x) 에서의 비 동기 호출 방법에 대해 알아보자.

아래와 같이 간단한 웹 서비스의 HelloWorld 웹 메서드를 만들어 보자.

[WebMethod]

public string HelloWorld(string name)

{

//고의로 약 2초 정도 지연시간을 준다

System.Threading.Thread.Sleep(2000);

return "Hello " + name;

}



이렇게 웹 메서드가 만들어 지고 난 후 클라이언트 프로그램을 만들어 웹 참조(Web References)를 하도록 한다.

웹 서비스를 참조 하면 아래 그림처럼 클라이언트의 웹 참조 항목아래에 보면 References.cs 라는

클래스가 있다 (References.cs 파일은 모든 파일 표시를 해야 나타난다)

이 파일은 웹 서비스를 원격 호출 가능케 하는 Proxy 클래스인데,

파일을 열어 보면 우리가 작성한 웹 메서드인 HelloWorld 에 관련된 메서드가 총 3개 있음을 알 수 있다.

/// <remarks/>

[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/HelloWorld", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]

public string HelloWorld(string name) {

object[] results = this.Invoke("HelloWorld", new object[] {name});

return ((string)(results[0]));

}

/// <remarks/>

public System.IAsyncResult BeginHelloWorld(string name, System.AsyncCallback callback, object asyncState) {

return this.BeginInvoke("HelloWorld", new object[] {name}, callback, asyncState);

}

/// <remarks/>

public string EndHelloWorld(System.IAsyncResult asyncResult) {

object[] results = this.EndInvoke(asyncResult);

return ((string)(results[0]));

}

우리가 작성한 HelloWorld 이외에도 BeginHelloWorld, EndHelloWorld 메서드가 자동으로 추가되어 있다.

이 두 메서드가 바로 1.x 에서 비 동기 웹 서비스 호출을 위해 자동으로 생성되는 메서드 인 것이다.

클라이언트에서 비 동기로 웹 서비스를 호출하는 코드를 보자

//웹 서비스 객체

private localhost.Service1 proxy;

//비동기로 HelloWorld 호출

private void button1_Click(object sender, System.EventArgs e)

{

this.proxy = new localhost.Service1();

proxy.BeginHelloWorld("MKEX",new System.AsyncCallback(CallbackMethod),null);

}

public void CallbackMethod(IAsyncResult ar)

{

string result = proxy.EndHelloWorld(ar);

MessageBox.Show(result);

}

HelloWorld 웹 메서드를 비 동기로 호출하기 위해서는 BeginHelloWorld 을 호출해야 한다.

이때 매개변수를 전달하고 비 동기 작업 완료 시 호출 될 Callback 메서드를 지정한다.

Callback 메서드에서는 다시 EndHelloWorld 를 호출하여 비 동기 작업 상태 및 참조 매개변수를 넘겨준다(있을 경우)

이 샘플 프로젝트를 수행해 보면 웹 메서드를 호출하고 난 뒤 기다리는 시간 동안 블로킹이 되지 않고 다른 작업을

수행할 수 있음을 알 수 있다. 즉 비 동기로 웹 서비스가 호출되는 것이다

2. .NET Framework 2.0 에서의 웹 서비스 비 동기 호출

이제 2.0에서의 비 동기 웹 서비스 호출 방법에 대해 알아보자.

위의 샘플과 동일한 웹 서비스를 만들고 클라이언트에 웹 참조를 한 뒤 Reference.cs 의 코드를 살펴 보자.

/// <remarks/>

public event HelloWorldCompletedEventHandler HelloWorldCompleted;

/// <remarks/>

[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/HelloWorld", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]

public string HelloWorld(string name) {

object[] results = this.Invoke("HelloWorld", new object[] {name});

return ((string)(results[0]));

}

/// <remarks/>

public void HelloWorldAsync(string name) {

this.HelloWorldAsync(name, null);

}

/// <remarks/>

public void HelloWorldAsync(string name, object userState) {

if ((this.HelloWorldOperationCompleted == null)) {

this.HelloWorldOperationCompleted = new System.Threading.SendOrPostCallback(this.OnHelloWorldOperationCompleted);

}

this.InvokeAsync("HelloWorld", new object[] {

name}, this.HelloWorldOperationCompleted, userState);

}

private void OnHelloWorldOperationCompleted(object arg) {

if ((this.HelloWorldCompleted != null)) {

System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg));

this.HelloWorldCompleted(this, new HelloWorldCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState));

}

}

1.x Reference.cs 와 확연히 달라진 코드임을 알 수 있다.

1.x 에서의 BeginXXX, EndXXX 와 같은 메서드는 없어지고 대신 비 동기 완료를 통지 받기 위한 이벤트(HelloWorldCompletedEventHandler)

비 동기 웹 메서드 코드(HelloWorldAsync), 이벤트 호출 코드(OnHelloWorldOperationComplete) 자동으로 생성된 것을 볼 수 있다.

클라이언트에서 비 동기로 웹 서비스를 호출하는 코드를 보자

private void button3_Click(object sender, EventArgs e)

{

//웹 서비스 객체

localhost.Service1 proxy = new WindowsApplication4.localhost.Service1();

//비동기 완료시 통지받을 이벤트 핸들러 등록

proxy.HelloWorldCompleted += new WindowsApplication4.localhost.HelloWorldCompletedEventHandler(proxy_HelloWorldCompleted);

//비동기로 HelloWorld 호출

proxy.HelloWorldAsync("MKEX");

}

//비동기 웹 서비스 호출 완료시 수행되는 이벤트 메서드

public void proxy_HelloWorldCompleted(object sender,localhost.HelloWorldCompletedEventArgs e)

{

MessageBox.Show(e.Result);

}



1.x 와는 달리 보다 직관적이고 이벤트 지향적으로 변경되었음을 알 수 있다.

비 동기 작업 완료시 통지받을 이벤트를 등록하고,

비동기 웹 메서드를 호출하기 위해 XXXAsync 메서드를 호출한다

이 이벤트 핸들러 메서드에서는 전달된 매개변수(HelloWorldCompletedEventArgs)를 통해 웹 메서드의 반환값을 가져올 수

있게 되는 것이다.

※ 주의사항

앞서 샘플 코드에서는 비 동기 작업 완료 통지를 받기 위한 이벤트를 웹 메서드 호출할 때 등록했었는데, 아래처럼..

//비동기 완료시 통지받을 이벤트 핸들러 등록

proxy.HelloWorldCompleted += new WindowsApplication4.localhost.HelloWorldCompletedEventHandler(proxy_HelloWorldCompleted);

//비동기로 HelloWorld 호출

proxy.HelloWorldAsync("MKEX");

여기에 주의사항이 있다.

이벤트는 한번만 등록되어야 한다

만일 윈폼 응용프로그램과 같이 웹 서비스 프록시 객체를 미리 생성하고 난 뒤 그 객체를 계속적으로 사용할 경우

위 처럼 웹 메서드를 호출할 때 마다 완료 이벤트를 등록하면 중복 등록되게 되는 것이다.

따라서 이런 경우라면, 반드시 프록시 객체의 생성하는 곳에서 각 웹 메서드에 해당하는 완료 이벤트를 미리 등록하고 난 뒤

실제 웹 메서드 호출할때는 별도로 등록하지 않도록 해야 한다.

반응형
반응형

Column이 3개인 리스트뷰 추가하기

1. Listview 추가하기
private void AddClient(string strID, object obj, int iMode)
{
ListViewItem li = new ListViewItem();
li.Text = "ID";
li.SubItems.Add("sub1");
li.SubItems.Add("sub2");
li.ImageIndex = 0;
listView.Items.Add(li);// 설정된 리스트뷰에 추가하기
}
2. Listview 수정하기 특정 값을 가지고 있는 리스트뷰값을 변경
private void SetClient(string strmanagerID, string strClientID)
{
for (int i = 0; i < listView_ASmanager.Items.Count; i++)
{
if (listView_ASmanager.Items[i].Text == strmanagerID)
{
listView_ASmanager.Items[i].SubItems[1].Text = "연결";
listView_ASmanager.Items[i].SubItems[2].Text = strClientID;
}
}
}
3. Listview 삭제하기

if (listView1.SelectedIndices.Count > 0)
{
listView1.Items.RemoveAt(listView1.SelectedIndices[0]);
}

4. Listview 열의 사이즈 자동조절
listView_ASmanager.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent); //컨텐츠 내용에 따라 자동변환
listView_ASmanager.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize); // 헤더에 따라 자동변환

반응형
반응형

DllImport! Attribute는 C#안에서 Unmanaged Code를 사용 할 수 있게 한다. Unmanaged Code란 닷넷 환경밖에서 개발된 코드를 가리킨다. DllImport! Attribute는 System.Runtime.InteropServices 네임스페이스 안에 정의 되어 있다. 사용방법은 위치지정 파라미터로 사용 할 DLL 파일을 인자로 넘겨 주면 된다.

using System;

using System.Runtime.InteropServices;

class Test

{

//User32.dll 파일안의MessageBox 함수를불러와서사용하는예이다. DllImport! Attribute를

//이용하여사용할코드가포함되어있는DLL을넘겨주고extern 키워드를통해사용하려고하는

//메소드가외부에있음을알린다. 이렇게하면닷넷환경밖에서개발된코드들도C#안에서쓸수있다.

[DllImport!("User32.Dll")]

public static extern int MessageBox(int h, string m, string c, int type);

static void Main()

{

MessageBox(0, "Hello!", "In C#", 0); //다이얼로그창의타이블이"In C#" 이며내용은"Hello"

}

}

반응형
반응형

http://www.codeproject.com/KB/system/everythingInAD.aspx

Table of Contents

Introduction

When it comes to programmatically accessing Microsoft's Active Directory a lot of people seem to have quite a difficult time tying all the pieces together to accomplish exactly what they want to. There are so many technologies available for communicating with LDAP that many programmers end up with a mix between COM+ ADSI calls and .NET class calls mixed into their code. ADSI code is so difficult to understand and follow that the creator of that code usually owns it for the entirety of it's lifecycle since no one else wants to support it.

This article attempts to tie together the most commonly used elements involved in Active Directory Management in the simplest, most clean manner possible. I interact with Active Directory in nearly all of my applications (web & forms) and I have had to solve a lot of integration issues for many customers. When I was starting out with this technology I had a lot of growing pains so this is an attempt to help those programmers who may have a need to interact with the Directory but do not want to have to become experts in the issue. However, certain rudimentary knowledge and concepts are required in order to utilize the code. You must be familiar with such terms as: distinguishedName, ldap paths, fully qualified domain names, object attributes (single string & multi-string), and general knowledge of ldap schemas.

Background

There is a great collection of sample code available on MSDN's website for the v1.1 System.DirectoryServices assembly but there seems to be a void when it comes to the new functionality available in the v2.0 System.DirectoryServices.ActiveDirectory assembly. Since this article's original publishing, Generics have gained widespread acceptance and I encourage anyone borrowing from this resource to replace the archaic ArrayList collections with List<T> or appropriate generic collections.

Points of Concern

In order to communicate with Active Directory one must take into account network security, business rules, and technological constraints. If you're using Active Directory code from an ASP.NET page you must ensure that the code has the appropriate level of permission to access and interact with the directory. For development purposes or proof of concept you can enable impersonation at the ASP.NET level (in web.config) and the IIS level and if the IIS server and the directory domain controller reside on the same machine this will work. However, if these entities are not co-located on the same server (as they never are in production) you can wrap the code around an impersonation class (such as the Zeta Impersonator which will execute the Directory calls under the token of the impersonated user. It's strongly recommended that you do not do this for security reasons unless absolutely necessary.. The authorized method for granting the ASP.NET application permission to the directory is by way of either a privileged IIS Application Pool running under the identity of a service account or by way of a COM+ entity running under the identity of a service account.

If you plan on running this code from a desktop assembly then you're going to want to ensure that your machine is attached to a domain and can communicate with that domain. The impersonation is not necessary if the user running the code has sufficient privileges on the domain.

Running Code in Batch Processes

It is also important to note that if you plan on running this code from an ASP.NET page in batch, ASP.NET will time out on you if you try to run batch processes from it's primary thread. There are several things to consider in this scenario but be aware that for example, if you're creating x number of accounts through an ASP.NET application (or performing any batch operation in general) that you must plan to use queues, a back-end scheduler, or some other mechanism outside the scope of the page itself to prevent timing out during your processes. As with any ASPNET design, it's never a good idea to use ASPNET itself for anything but the "View" part of the solution. The best architecture would queue tasks into a SQL database or something to that effect and then a back-end windows service or similar application would pick up the tasking and perform the actual Directory operations.

This is typically how I engineer Active Directory management solutions for customers.

A Note on Method Parameters

You will notice that most of the methods require the same parameters. Rather than identify each time I will outline them now:

  • friendlyDomainName: the non qualified domain name (contoso - NOT contoso.com)
  • ldapDomain: the fully qualified domain such as contoso.com or dc=contoso,dc=com
  • objectPath: the fully qualified path to the object: CN=user, OU=USERS, DC=contoso, DC=com(same as objectDn)
  • objectDn: the distinguishedName of the object: CN=group, OU=GROUPS, DC=contoso, DC=com
  • userDn: the distinguishedName of the user: CN=user, OU=USERS, DC=contoso, DC=com
  • groupDn: the distinguishedName of the group: CN=group,OU=GROUPS,DC=contoso,DC=com

A Note on System.DirectoryServices.DirectoryEntry

You'll notice in all the samples that we're binding directly to the directoryEntry and not specifying a server or credentials. If you do not want to use an impersonation class you can send credentials directly into the DirectoryEntry constructor. The impersonation class is helpful for those times when you want to use a static method and don't want to go through the trouble of creating a DirectoryContext object to hold these details. Likewise you may want to target a specific domain controller.

Target Specific Domain Controllers or Credentials

Everywhere in the code that you see: LDAP:// you can replace with LDAP://MyDomainControllerNameOrIpAddress as well as everywhere you see a DirectoryEntry class being constructed you can send in specific credentials as well. This is especially helpful if you need to work on an Active Directory for which your machine is not a member of it's forest or domain or you want to target a DC to make the changes to.

Collapse | Copy Code
//Rename an object and specify the domain controller and credentials directly


public static void Rename(string server,
    string userName, string password, string objectDn, string newName)
{
    DirectoryEntry child = new DirectoryEntry("LDAP://" + server + "/" + 
        objectDn, userName, password);
    child.Rename("CN=" + newName);
}

Managing local accounts with DirectoryEntry

It is important to note that you can execute some of these methods against a local machine as opposed to an Active Directory if needed by simply replacing the LDAP:// string with WinNT:// as demonstrated below

Collapse | Copy Code
//create new local account

DirectoryEntry localMachine = new DirectoryEntry("WinNT://" + 
    Environment.MachineName);
DirectoryEntry newUser = localMachine.Children.Add("localuser", "user");
newUser.Invoke("SetPassword", new object[] { "3l!teP@$$w0RDz" });
newUser.CommitChanges();
Console.WriteLine(newUser.Guid.ToString());
localMachine.Close();
newUser.Close();

Managing local groups with DirectoryEntry

A few configuration changes need to be made to the code but it's pretty straightforward. Below you can see an example of using DirectoryEntry to enumerate the members of the local "administrator" group.

Collapse | Copy Code
DirectoryEntry localMachine = new DirectoryEntry
    ("WinNT://" + Environment.MachineName + ",Computer");
DirectoryEntry admGroup = localMachine.Children.Find
    ("administrators", "group");
object members = admGroup.Invoke("members", null);

foreach (object groupMember in (IEnumerable)members)
{
    DirectoryEntry member = new DirectoryEntry(groupMember);
    Console.WriteLine(member.Name);
}

Managing IIS with DirectoryEntry

In addition to managing local & directory services accounts, the versatile DirectoryEntry object can manage other network providers as well, such as IIS. Below is an example of how you can use DirectoryEntry to provision a new virtual directory in IIS.

Collapse | Copy Code
//Create New Virtual Directory in IIS with DirectoryEntry()


string wwwroot = "c:\\Inetpub\\wwwroot";
string virtualDirectoryName = "myNewApp";
string sitepath = "IIS://localhost/W3SVC/1/ROOT";

DirectoryEntry vRoot = new DirectoryEntry(sitepath);
DirectoryWntry vDir = vRoot.Children.Add(virtualDirectoryName, 
                            "IIsWebVirtualDir");
vDir.CommitChanges();

vDir.Properties["Path"].Value = wwwroot + "\\" + virtualDirectoryName;
vDir.Properties["DefaultDoc"].Value = "Default.aspx";
vDir.Properties["DirBrowseFlags"].Value = 2147483648;
vDir.Commitchanges();
vRoot.CommitChanges();

Active Directory Code

The code below is broken apart logically into usage categories. Again, this is not intended to be a complete library, just the code that I use on a daily basis.

Active Directory Management

Collapse | Copy Code
//These methods require these imports

//You must add a references in your project as well

using System.DirectoryServices;
using System.DirectoryServices.ActiveDirectory;

Translate the Friendly Domain Name to Fully Qualified Domain

Collapse | Copy Code
public static string FriendlyDomainToLdapDomain(string friendlyDomainName)
{
    string ldapPath = null;
    try
    {
        DirectoryContext objContext = new DirectoryContext(
            DirectoryContextType.Domain, friendlyDomainName);
        Domain objDomain = Domain.GetDomain(objContext);
        ldapPath = objDomain.Name;
    }
    catch (DirectoryServicesCOMException e)
    {
        ldapPath = e.Message.ToString();
    }
    return ldapPath;
}

Enumerate Domains in the Current Forest

Collapse | Copy Code
public static ArrayList EnumerateDomains()
{
    ArrayList alDomains = new ArrayList();
    Forest currentForest = Forest.GetCurrentForest();
    DomainCollection myDomains = currentForest.Domains;

    foreach (Domain objDomain in myDomains)
    {
        alDomains.Add(objDomain.Name);
    }
    return alDomains;
}

Enumerate Global Catalogs in the Current Forest

Collapse | Copy Code
public static ArrayList EnumerateDomains()
{
    ArrayList alGCs = new ArrayList();
    Forest currentForest = Forest.GetCurrentForest();
    foreach (GlobalCatalog gc in currentForest.GlobalCatalogs)
    {
        alGCs.Add(gc.Name);
    }
    return alGCs;
}

Enumerate Domain Controllers in a Domain

Collapse | Copy Code
public static ArrayList EnumerateDomainControllers()
{
    ArrayList alDcs = new ArrayList();
    Domain domain = Domain.GetCurrentDomain();
    foreach (DomainController dc in domain.DomainControllers)
    {
        alDcs.Add(dc.Name);
    }
    return alDcs;
}

Create a Trust Relationship

Collapse | Copy Code
public void CreateTrust(string sourceForestName, string targetForestName)
{
    Forest sourceForest = Forest.GetForest(new DirectoryContext(
        DirectoryContextType.Forest, sourceForestName));

    Forest targetForest = Forest.GetForest(new DirectoryContext(
        DirectoryContextType.Forest, targetForestName));

    // create an inbound forest trust

    sourceForest.CreateTrustRelationship(targetForest,
        TrustDirection.Outbound);
}

Delete a Trust Relationship

Collapse | Copy Code
public void DeleteTrust(string sourceForestName, string targetForestName)
{
    Forest sourceForest = Forest.GetForest(new DirectoryContext(
        DirectoryContextType.Forest, sourceForestName));

    Forest targetForest = Forest.GetForest(new DirectoryContext(
        DirectoryContextType.Forest, targetForestName));

    // delete forest trust

    sourceForest.DeleteTrustRelationship(targetForest);
}

Enumerate Objects in an OU

The parameter OuDn is the Organizational Unit distinguishedName such as OU=Users,dc=myDomain,dc=com

Collapse | Copy Code
public ArrayList EnumerateOU(string OuDn)
{
    ArrayList alObjects = new ArrayList();
    try
    {
        DirectoryEntry directoryObject = new DirectoryEntry("LDAP://" + OuDn);
        foreach (DirectoryEntry child in directoryObject.Children)
        {
            string childPath = child.Path.ToString();
            alObjects.Add(childPath.Remove(0,7)); 
                //remove the LDAP prefix from the path

            child.Close();
            child.Dispose();
        }
        directoryObject.Close();
        directoryObject.Dispose();
    }
    catch (DirectoryServicesCOMException e)
    {
        Console.WriteLine("An Error Occurred: " + e.Message.ToString());
    }
    return alObjects;
}

Enumerate Directory Entry Settings

One of the nice things about the 2.0 classes is the ability to get and set a configuration object for your directoryEntry objects.

Collapse | Copy Code
static void DirectoryEntryConfigurationSettings(string domainADsPath)
{
    // Bind to current domain

    DirectoryEntry entry = new DirectoryEntry(domainADsPath);
    DirectoryEntryConfiguration entryConfiguration = entry.Options;

    Console.WriteLine("Server: " + entryConfiguration.GetCurrentServerName());
    Console.WriteLine("Page Size: " + entryConfiguration.PageSize.ToString());
    Console.WriteLine("Password Encoding: " + 
        entryConfiguration.PasswordEncoding.ToString());
    Console.WriteLine("Password Port: " + 
        entryConfiguration.PasswordPort.ToString());
    Console.WriteLine("Referral: " + entryConfiguration.Referral.ToString());
    Console.WriteLine("Security Masks: " + 
        entryConfiguration.SecurityMasks.ToString());
    Console.WriteLine("Is Mutually Authenticated: " + 
        entryConfiguration.IsMutuallyAuthenticated().ToString());
    Console.WriteLine();
    Console.ReadLine();
}

Active Directory Objects

Collapse | Copy Code
//These methods require these imports

//You must add a references in your project as well

using System.DirectoryServices;

Check for the Existence of an Object

This method does not need you to know the distinguishedName, you can concat strings or even guess a location and it will still run (and return false if not found).

Collapse | Copy Code
public static bool Exists(string objectPath)
{
    bool found = false;
    if (DirectoryEntry.Exists("LDAP://" + objectPath))
    {
        found = true;
    }
    return found;
}

Move an Object from one Location to Another

It should be noted that the string newLocation should NOT include the CN= value of the object. The method will pull that from the objectLocation string for you. So object CN=group,OU=GROUPS,DC=contoso,DC=com is sent in as the objectLocation but the newLocation is something like: OU=NewOUParent,DC=contoso,DC=com. The method will take care of the CN=group.

Collapse | Copy Code
public void Move(string objectLocation, string newLocation)
{
    //For brevity, removed existence checks

    DirectoryEntry eLocation = new DirectoryEntry("LDAP://" + objectLocation);
    DirectoryEntry nLocation = new DirectoryEntry("LDAP://" + newLocation);
    string newName = eLocation.Name;
    eLocation.MoveTo(nLocation, newName);
    nLocation.Close();
    eLocation.Close();
}

Enumerate Multi-String Attribute Values of an Object

This method includes a recursive flag in case you want to recursively dig up properties of properties such as enumerating all the member values of a group and then getting each member group's groups all the way up the tree.

Collapse | Copy Code
public ArrayList AttributeValuesMultiString(string attributeName,
     string objectDn, ArrayList valuesCollection, bool recursive)
{
    DirectoryEntry ent = new DirectoryEntry(objectDn);
    PropertyValueCollection ValueCollection = ent.Properties[attributeName];
    IEnumerator en = ValueCollection.GetEnumerator();

    while (en.MoveNext())
    {
        if (en.Current != null)
        {
            if (!valuesCollection.Contains(en.Current.ToString()))
            {
                valuesCollection.Add(en.Current.ToString());
                if (recursive)
                {
                    AttributeValuesMultiString(attributeName, "LDAP://" +
                    en.Current.ToString(), valuesCollection, true);
                }
            }
        }
    }
    ent.Close();
    ent.Dispose();
    return valuesCollection;
}

Enumerate Single String Attribute Values of an Object

Collapse | Copy Code
public string AttributeValuesSingleString
    (string attributeName, string objectDn)
{
    string strValue;
    DirectoryEntry ent = new DirectoryEntry(objectDn);
    strValue = ent.Properties[attributeName].Value.ToString();
    ent.Close();
    ent.Dispose();
    return strValue;
}

Enumerate an Object's Properties: The Ones with Values

Collapse | Copy Code
public static ArrayList GetUsedAttributes(string objectDn)
{
    DirectoryEntry objRootDSE = new DirectoryEntry("LDAP://" + objectDn);
    ArrayList props = new ArrayList();

    foreach (string strAttrName in objRootDSE.Properties.PropertyNames)
    {
        props.Add(strAttrName);
    }
    return props;
}

Get an Object DistinguishedName: ADO.NET search (ADVANCED)

This method is the glue that ties all the methods together since most all the methods require the consumer to provide a distinguishedName. Wherever you put this code, you must ensure that you add these enumerations as well. This allows the consumers to specify the type of object to search for and whether they want the distinguishedName returned or the objectGUID.

Collapse | Copy Code
public enum objectClass
{
    user, group, computer
}
public enum returnType
{
 distinguishedName, ObjectGUID
}

A call to this class might look like:

myObjectReference.GetObjectDistinguishedName(objectClass.user, returnType.ObjectGUID, "john.q.public", "contoso.com")

Collapse | Copy Code
public string GetObjectDistinguishedName(objectClass objectCls,
    returnType returnValue, string objectName, string LdapDomain)
{
    string distinguishedName = string.Empty;
    string connectionPrefix = "LDAP://" + LdapDomain;
    DirectoryEntry entry = new DirectoryEntry(connectionPrefix);
    DirectorySearcher mySearcher = new DirectorySearcher(entry);

    switch (objectCls)
    {
        case objectClass.user:
            mySearcher.Filter = "(&(objectClass=user)
        (|(cn=" + objectName + ")(sAMAccountName=" + objectName + ")))";
            break;
        case objectClass.group:
            mySearcher.Filter = "(&(objectClass=group)
        (|(cn=" + objectName + ")(dn=" + objectName + ")))";
            break;
        case objectClass.computer:
            mySearcher.Filter = "(&(objectClass=computer)
            (|(cn=" + objectName + ")(dn=" + objectName + ")))";
            break;
    }
    SearchResult result = mySearcher.FindOne();

    if (result == null)
    {
        throw new NullReferenceException
        ("unable to locate the distinguishedName for the object " +
        objectName + " in the " + LdapDomain + " domain");
    }
    DirectoryEntry directoryObject = result.GetDirectoryEntry();
    if (returnValue.Equals(returnType.distinguishedName))
    {
        distinguishedName = "LDAP://" + directoryObject.Properties
            ["distinguishedName"].Value;
    }
    if (returnValue.Equals(returnType.ObjectGUID))
    {
        distinguishedName = directoryObject.Guid.ToString();
    }
    entry.Close();
    entry.Dispose();
    mySearcher.Dispose();
    return distinguishedName;
}

Convert distinguishedName to ObjectGUID

Collapse | Copy Code
public string ConvertDNtoGUID(string objectDN)
{
    //Removed logic to check existence first

    DirectoryEntry directoryObject = new DirectoryEntry(objectDN);
    return directoryObject.Guid.ToString();
}

Convert an ObjectGUID to OctectString: The Native ObjectGUID

Collapse | Copy Code
public static string ConvertGuidToOctectString(string objectGuid)
{
    System.Guid guid = new Guid(objectGuid);
    byte[] byteGuid = guid.ToByteArray();
    string queryGuid = "";
    foreach (byte b in byteGuid)
    {
        queryGuid += @"\" + b.ToString("x2");
    }
    return queryGuid;
}

Search by ObjectGUID or convert ObjectGUID to distinguishedName

Collapse | Copy Code
public static string ConvertGuidToDn(string GUID)
{
      DirectoryEntry ent = new DirectoryEntry();
      String ADGuid = ent.NativeGuid;
      DirectoryEntry x = new DirectoryEntry("LDAP://{GUID=" + ADGuid + ">"); 
          //change the { to <>

      return x.Path.Remove(0,7); //remove the LDAP prefix from the path

}

Publish Network Shares in Active Directory

Collapse | Copy Code
 //Example

 private void init()
{
    CreateShareEntry("OU=HOME,dc=baileysoft,dc=com",
        "Music", @"\\192.168.2.1\Music", "mp3 Server Share");
    Console.ReadLine();
}

//Actual Method

public void CreateShareEntry(string ldapPath,
    string shareName, string shareUncPath, string shareDescription)
{
    string oGUID = string.Empty;
    string connectionPrefix = "LDAP://" + ldapPath;
    DirectoryEntry directoryObject = new DirectoryEntry(connectionPrefix);
    DirectoryEntry networkShare = directoryObject.Children.Add("CN=" + 
        shareName, "volume");
    networkShare.Properties["uNCName"].Value = shareUncPath;
    networkShare.Properties["Description"].Value = shareDescription;
    networkShare.CommitChanges();

    directoryObject.Close();
    networkShare.Close();
}

Create a New Security Group

Note: by default if no GroupType property is set, the group is created as a domain security group.

Collapse | Copy Code
public void Create(string ouPath, string name)
{
    if (!DirectoryEntry.Exists("LDAP://CN=" + name + "," + ouPath))
    {
        try
        {
            DirectoryEntry entry = new DirectoryEntry("LDAP://" + ouPath);
            DirectoryEntry group = entry.Children.Add("CN=" + name, "group");
            group.Properties["sAmAccountName"].Value = name;
            group.CommitChanges();
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message.ToString());
        }
    }
    else { Console.WriteLine(path + " already exists"); }
}

Delete a group

Collapse | Copy Code
public void Delete(string ouPath, string groupPath)
{
    if (DirectoryEntry.Exists("LDAP://" + groupPath))
    {
        try
        {
            DirectoryEntry entry = new DirectoryEntry("LDAP://" + ouPath);
            DirectoryEntry group = new DirectoryEntry("LDAP://" + groupPath);
            entry.Children.Remove(group);
            group.CommitChanges();
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message.ToString());
        }
    }
    else
    { 
        Console.WriteLine(path + " doesn't exist"); 
    }
}

Active Directory Users Tasks

Collapse | Copy Code
//These methods require these imports

//You must add a references in your project as well

using System.DirectoryServices;

Authenticate a User Against the Directory

Per John Storer, thanks for sharing.

Collapse | Copy Code
private bool Authenticate(string userName,
    string password, string domain)
{
    bool authentic = false;
    try
    {
        DirectoryEntry entry = new DirectoryEntry("LDAP://" + domain,
            userName, password);
        object nativeObject = entry.NativeObject;
        authentic = true;
    }
    catch (DirectoryServicesCOMException) { }
    return authentic;
}

Add User to Group

Collapse | Copy Code
public void AddToGroup(string userDn, string groupDn)
{
    try
    {
        DirectoryEntry dirEntry = new DirectoryEntry("LDAP://" + groupDn);
        dirEntry.Properties["member"].Add(userDn);

        dirEntry.CommitChanges();
        dirEntry.Close();
    }
    catch (System.DirectoryServices.DirectoryServicesCOMException E)
    {
        //doSomething with E.Message.ToString();

    }
}

Remove User from Group

Collapse | Copy Code
public void RemoveUserFromGroup(string userDn, string groupDn)
{
    try
    {
        DirectoryEntry dirEntry = new DirectoryEntry("LDAP://" + groupDn);
        dirEntry.Properties["member"].Remove(userDn);
        dirEntry.CommitChanges();
        dirEntry.Close();
    }
    catch (System.DirectoryServices.DirectoryServicesCOMException E)
    {
        //doSomething with E.Message.ToString();

    }
}

Get User Group Memberships of the Logged in User from ASP.NET

Collapse | Copy Code
public ArrayList Groups()
{
    ArrayList groups = new ArrayList();
    foreach (System.Security.Principal.IdentityReference group in
        System.Web.HttpContext.Current.Request.LogonUserIdentity.Groups)
    {
        groups.Add(group.Translate(typeof
            (System.Security.Principal.NTAccount)).ToString());
    }
    return groups;
}

Get User Group Memberships

This method requires that you have the AttributeValuesMultiString method earlier in the article included in your class.

Collapse | Copy Code
public ArrayList Groups(string userDn, bool recursive)
{
    ArrayList groupMemberships = new ArrayList();
    return AttributeValuesMultiString("memberOf", userDn,
        groupMemberships, recursive);
}

Create User Account

Collapse | Copy Code
public string CreateUserAccount(string ldapPath, string userName, 
    string userPassword)
{
    try
    {
        string oGUID = string.Empty;
        string connectionPrefix = "LDAP://" + ldapPath;
        DirectoryEntry dirEntry = new DirectoryEntry(connectionPrefix);
        DirectoryEntry newUser = dirEntry.Children.Add
            ("CN=" + userName, "user");
        newUser.Properties["samAccountName"].Value = userName;
        newUser.CommitChanges();
        oGUID = newUser.Guid.ToString();

        newUser.Invoke("SetPassword", new object[] { userPassword });
        newUser.CommitChanges();
        dirEntry.Close();
        newUser.Close();
    }
    catch (System.DirectoryServices.DirectoryServicesCOMException E)
    {
        //DoSomethingwith --> E.Message.ToString();

    }
    return oGUID;
}

Dealing with User Passwords

There are some specifics to understand when dealing with user passwords and boundaries around passwords such as forcing a user to change their password on the next logon, denying the user the right to change their own passwords, setting passwords to never expire, to when to expire, and these tasks can be accomplished using UserAccountControl flags that are demonstrated in the proceeding sections. Please refer to this great MSDN article: Managing User Passwords for examples and documentation regarding these features. (thanks to Daniel Ocean for identifying this resource)

Collapse | Copy Code
//Add this to the create account method

int val = (int)newUser.Properties["userAccountControl"].Value; 
     //newUser is DirectoryEntry object

newUser.Properties["userAccountControl"].Value = val | 0x80000; 
    //ADS_UF_TRUSTED_FOR_DELEGATION

All UserAccountControl flags

Collapse | Copy Code
CONST   HEX
-------------------------------
        SCRIPT 0x0001
        ACCOUNTDISABLE 0x0002
        HOMEDIR_REQUIRED 0x0008
        LOCKOUT 0x0010
        PASSWD_NOTREQD 0x0020
        PASSWD_CANT_CHANGE 0x0040
        ENCRYPTED_TEXT_PWD_ALLOWED 0x0080
        TEMP_DUPLICATE_ACCOUNT 0x0100
        NORMAL_ACCOUNT 0x0200
        INTERDOMAIN_TRUST_ACCOUNT 0x0800
        WORKSTATION_TRUST_ACCOUNT 0x1000
        SERVER_TRUST_ACCOUNT 0x2000
        DONT_EXPIRE_PASSWORD 0x10000
        MNS_LOGON_ACCOUNT 0x20000
        SMARTCARD_REQUIRED 0x40000
        TRUSTED_FOR_DELEGATION 0x80000
        NOT_DELEGATED 0x100000
        USE_DES_KEY_ONLY 0x200000
        DONT_REQ_PREAUTH 0x400000
        PASSWORD_EXPIRED 0x800000
        TRUSTED_TO_AUTH_FOR_DELEGATION 0x1000000

Enable a User Account

Collapse | Copy Code
public void Enable(string userDn)
{
    try
    {
        DirectoryEntry user = new DirectoryEntry(userDn);
        int val = (int)user.Properties["userAccountControl"].Value;
        user.Properties["userAccountControl"].Value = val & ~0x2; 
            //ADS_UF_NORMAL_ACCOUNT;

        user.CommitChanges();
        user.Close();
    }
    catch (System.DirectoryServices.DirectoryServicesCOMException E)
    {
        //DoSomethingWith --> E.Message.ToString();

    }
}

Disable a User Account

Collapse | Copy Code
public void Disable(string userDn)
{
    try
    {
        DirectoryEntry user = new DirectoryEntry(userDn);
        int val = (int)user.Properties["userAccountControl"].Value;
        user.Properties["userAccountControl"].Value = val | 0x2; 
             //ADS_UF_ACCOUNTDISABLE;

        user.CommitChanges();
        user.Close();
    }
    catch (System.DirectoryServices.DirectoryServicesCOMException E)
    {
        //DoSomethingWith --> E.Message.ToString();

    }
}

Unlock a User Account

Collapse | Copy Code
public void Unlock(string userDn)
{
    try
    {
        DirectoryEntry uEntry = new DirectoryEntry(userDn);
        uEntry.Properties["LockOutTime"].Value = 0; //unlock account

        uEntry.CommitChanges(); //may not be needed but adding it anyways

        uEntry.Close();
    }
    catch (System.DirectoryServices.DirectoryServicesCOMException E)
    {
        //DoSomethingWith --> E.Message.ToString();

    }
}

Alternate Lock/Unlock Account

It's hard to find code to lock an account. Here is my code to lock or unlock an account. dEntry is class variable already set to a user account. Shared by dextrous1.

Collapse | Copy Code
/// <summary>
/// Gets or sets a value indicating if the user account is locked out
/// </summary>
public bool IsLocked
{
    get { return Convert.ToBoolean(dEntry.InvokeGet("IsAccountLocked")); }
    set { dEntry.InvokeSet("IsAccountLocked", value); }
}

Reset a User Password

Collapse | Copy Code
public void ResetPassword(string userDn, string password)
{
    DirectoryEntry uEntry = new DirectoryEntry(userDn);
    uEntry.Invoke("SetPassword", new object[] { password });
    uEntry.Properties["LockOutTime"].Value = 0; //unlock account

    uEntry.Close();
}

Rename an Object

Collapse | Copy Code
public static void Rename(string objectDn, string newName)
{
    DirectoryEntry child = new DirectoryEntry("LDAP://" + objectDn);
   child.Rename("CN=" + newName);
}

Conclusion

I would have liked to include a sample project but my professional code is so tightly integrated with customer proprietary code that it was not feasible at this time.

UPDATE

If you would like to see an extremely simple implementation of some of this code check out the DirectoryServicesBrowserDialog I posted some time ago. This should demonstrate to those of you who are having trouble adding the proper references or having other difficulties.

I hope this helps out all those programmers that were like me and had to get up to speed really quickly and lost countless hours studying the System.DirectoryServices assembly trying to dig up answers on how to do AD tasks.

If you have some additional segments of code that are small but efficient that you'd like to include send them to me and I'll add them to this document.

History

  • Originally submitted - 22 March 2007
  • Added GetUsedAttributes() method - 24 March 2007
  • Added EnumerateOU() method - 25 March 2007
  • Added CreateShareEntry() - 27 March 2007
  • Added CommitChanged() call in UnlockUserAccount()
  • Cleaned up article, added a few new methods - 03 Apr 2007
  • Added note on DirectoryEntry - 04 Apr 2007
  • Added note on working with local accounts - 12 Apr 2007
  • Added code for creating/deleting groups - 20 Apr 2007
  • Added John Storer's code for authenticating users against the directory
  • Added UserAccountControl flags section - 06 Jun 2007
  • Added Managing IIS with DirectoryEntry() section - 12 Jun 2007
  • Created table of contents - 09 Jul 2007
  • Added Dealing with User Passwords Section - 21 May 2008
  • Added Alternate Lock/Unlock Account by dextrous1 - 21 May 2008
반응형
반응형
아직까지 어트리뷰트를 많이 사용하지 않았다. 그 필요성을 느끼는 단계까지 나의 내공이 부족한 걸까? 아니면 환경이??
모... 불평하거나 위측하지 않으련다.

CodeProject 에서 자료를 찾다가 남의 소스를 봤는데 희한한게 있었다. Category, Description 어트리뷰트가 바로 그것이다. 남의 코드와, 오픈소스를 많이 봐두면 좋다는 말.. 많이 들었는데 비로서 피부로 느낀다.

Category, Description 어트리뷰트는 Property에 사용된다. 일반적으로 WinForm 컨트롤을 사용자 환경에 맞게 서브클래싱 할 때 VisualStudio 의 도움을 받을 수 있도록 사용한다.
다음과 같이 Button 컨트롤 클래스를 상속받은 MyButton 클래스를 정의한다.


프로퍼티를 구현한다.


MyButton 컨트롤을 폼에 올려 놓았을 때, 컨트롤의 속성창을 보면 기타 라는 항목에 정의한 프로퍼티 목록이 보안다.
솔직히 속성 창에 보이는 것 만으로도 신기했다. 후후..


속성 창 맨 아래 보면 설명이 나오는데, 프로퍼티에 정의한 XML 주석의 내용이 나올줄 알았다. 너무 쉽게 생각했나?!
아무튼 아무것도 나오지 않는다.

MyButton 에 정의한 프로퍼티에 다음과 같은 어트리뷰트를 추가한다.

Category 어트리뷰트는 속성창에서 해당 프로퍼티가 위치할 항목을 지정한다.


Description 어트리뷰트는 해당 속성의 출력할 설명을 지정한다.



이 밖에도 Browsable, DesignerSerializationVisibility 프로퍼티가 사용되어지는 것 같은데 자료 찾기가 좀 어렵다.. 아직 MSDN이 어색하다..
반응형
반응형

명작 도서 More Effective C#: 50 Specific Ways to Improve Your C#를 읽다 보니 Control.Invoke를 캡슐화 한 ControlExtensions 이라는 클래스에 대한 이야기가 나왔습니다.

원서에는 멀티 스레딩이나 크로스 스레드 등에 대한 배경 설명 없이 달랑 몇 줄의 코드 정도만 제시되어 있는데 (이것이 바로 이 책의 매력 –반어적 의미 아님- 입니다.),

여기에 앞 뒤 설명을 붙이고, 간단한 예제를 만들어 보았습니다.


매 1초 마다 현재 시각을 표시하는 간단한 시계를 만든다고 합시다.

보통은 System.Timers.Timer 나 System.Threading.Timer, 혹은 System.Windows.Forms.Timer 를 사용하겠지만, 여기서는 Thread.Sleep 메서드를 이용하여 매 1초 마다 시간을 표시하는 방법을 사용해 보겠습니다.

실행하면, 시작하자 마자 바로 화면이 먹통이 되어버리는 걸 알 수 있습니다.

Thread.Sleep 메서드가 현재 메서드(UI가 생성된 메서드)를 잡고 있는 이른바 UI 블로킹이 일어난 것인데요. 이를 해결하기 위해서는 이 부분을 별도의 스레드에서 실행하여야 합니다.

1초를 기다린 후 라벨에 시각을 표시하는 로직이 별도의 메서드(StartNewThread)로 빠졌고, 이 메서드는 이제 메인 스레드(UI 스레드)와는 별개의 스레드에서 실행됩니다.

실행을 해 봅시다. 디버깅하지 않고 시작(CTRL + F5)을 실행하면 (운이 좋다면) 문제 없이 시계가 동작하는 걸 볼 수 있지만, 디버깅 시작(F5)을 실행하면 label1.Text = DateTime.Now.ToString();

에서 InvalidOperationException가 발생합니다.

일명 크로스 스레드 예외라고 하는데요.

label1.Text = DateTime.Now.ToString(); 에서 라벨 컨트롤에 접근을 하려고 하는데, 문제는 이 코드가 실행되는 스레드가 라벨 컨트롤이 생성된 스레드(메인 스레드, UI 스레드)가 아니라는 것입니다.


Control.Invoke를 호출하는 방법과 BackgroundWorker를 사용하는 두 가지 방법이 있을텐데, 대부분의 경우에는 BackgroundWorker 가 좋은 선택이 될 것입니다.

스레드 간에 상태를 공유하고, 진행상황을 보고하고, 작업을 중지하는 등 스레드와 관련한 대부분의 작업이 이미 구현되어 있기 때문에 편리하게 사용할 수 있습니다.

다만 BackgroundWorker에는옥의 티랄까, 한 가지 알려진 버그가 있습니다.

이 포스트의 주제가 BackgroundWorker가 아니고, 또 BackgroundWorker에는 곁다리로 슬쩍 이야기할 수 있는 수준 이상의 논점이 많으니까, 이 포스트에서는 Control.Invoke에 대해서만 이야기하도록 하겠습니다.


Control.Invoke를 호출하여 크로스 스레드 문제를 해결하는 코드는 다음과 같습니다.

먼저 라벨의 InvokeRequired 를 체크하여 Invoke가 필요한지를, 즉 컨트롤에 접근하는 스레드와 컨트롤이 생성된 스레드가 다른 스레드인지를 체크합니다.

굳이 Invoke가 필요하지 않다면 (비록 미미하더라도) 비용이 드는 Invoke를 호출할 필요가 없을 것입니다.

Control.Invoke의 시그니처는 다음과 같습니다.

첫번째 매개변수가 추상 클래스인 Delegate입니다.

따라서 label1.Invoke(new Delegate(DisplayDateTime)); 과 같이 대리자 인스턴스를 생성할 수가 없습니다.

대신 DisplayDateTimeHandler 라는 대리자 형식을 정의한 후 이 인스턴스를 전달하여야 합니다.

이제 실행(디버깅)을 하여 보면 크로스 스레드 문제 없이 잘 동작합니다.


크로스 스레드 문제가 해결되었으니 여기서 포스트가 끝나야 할 것 같지만, 이 포스트를 서야겠다고 생각한 이유는 사실은 지금 부터 시작 합니다.

Control.Invoke를 호출하는 위 코드를 Action 대리자와 확장 메서드를 사용하여 필드에서 사용할 만한 라이브러리로 만들어 봅시다.


닷넷 프레임웍 2.0에 추가된 두 가지 제네릭 대리자를 사용하면 대부분의 경우에는 대리자를 작성할 필요가 없습니다.

Func과 Action 대리자가 그것인데요. Func은 반환값이 있지만 Action은 반환값이 없다는(void) 점 외에는 동일하며, 두 대리자 모두 매개변수가 0개 ~ 4개인 오버로드가 각각 준비되어 있습니다.

(정확하게 이야기하자면, 매개변수가 0개인 Action 대리자의 형은 void Action() 이므로 제네릭 대리자는 아닙니다.)

그래서 매개 변수가 4개가 넘지 않는 시그니처를 가지는 대리자는 이 두 대리자로 표현할 수가 있는 것입니다.

위 코드에서도 DisplayDateTimeHandler 대리자를 따로 정의하지 않고 제네릭 대리자를 사용할 수 있습니다.

반환값이 없고 매개변수도 없으니까, 제네릭이 아닌 Action 대리자를 사용하면 되겠습니다.


이번에는 확장 메서드를 사용하여 위 코드를 캡슐화 해봅시다.

ControlExtensions라는 static 클래스를 만들고 아래와 같은 확장 메서드를 추가합니다.

(여기서는 매개변수가 0개 ~ 1개인 오버로드만 보이는데, 2개 ~ 4개인 오버로드도 정의해두면 편리합니다.)

이제 StartNewThread 메서드는 아래와 같이 간단해 집니다.

람다식이나 익명 메서드를 이용하면 DisplayDateTime 메서드도 따로 정의할 필요가 없어 코드가 좀 더 간단해 집니다.


연습 삼아 코드를 약간 고쳐 봅시다.

DisplayDateTime 메서드를 매개 변수를 가지는 형태로 다음과 같이 수정합니다.

그렇다면 이제 라벨의 Invoke 메서드를 호출할 때 현재 시각을 매개 변수로 넘겨야 합니다.

ControlExtensions.InvokeIfNeeded 오버로드 중,

가 호출되는데, 이는 다시 control.Invoke(action, arg); 를 호출합니다.

Control.Invoke의 두 번째 매개변수는 params object[] 이기 때문에, action 대리자의 매개변수의 갯수가 형에 상관없이 호출이 가능합니다.


http://kimgwajang.tistory.com/193

반응형
반응형

.NET Framework 에서는 Configuration File 의 구조를 구조적으로 만들 수 있도록 ConfigurationSection 이나 ConfigurationSectionGroup 과 같은 클래스를 제공해 줍니다. 하지만 이러한 구조적인 Configuration 구조를 만들기 위해서는 엄청난 노가다가 필요하죠. 모든 Config Element 와 Attribute, Collection 등을 매핑하는 클래스를 만들어야 한다는 것이죠. 이런 귀차니즘 때문에 XML API 를 직접 이용하기도 하였지만, CodePlex 에 Configuration Classes 를 쉽게 만들 수 있는 DSL 이 공개가 되어 있네요.

Configuration Section Designer 
http://csd.codeplex.com/

개발 하는 중이라면 Configuration 구조가 변할 수 도 있기 때문에 디자이너를 통해 작업을 하면 구조의 변경에 쉽게 대처할 수 있고, 디자이너의 데이터로 T4 Template 으로 자동으로 소스 코드를 생성해 주어 편리하게 사용하고 있습니다. 더불어 Sample Configuration File 과 XSD File 도 자동으로 만들어 주기 때문에 활용도 측면에서는 OK 입니다.

하지만 현재까지는 ConfigurationSectionGroup 과 NameValueCollection 등을 지원하지 않고 있어서 복잡한 Configuration 구조는 partial 클래스를 활용하거나 코드를 조금 수정해주어야 하기 때문에, 현재까지는 이것이 약간은 미완성 느낌을 지울 수 가 없네요. 
   

TIP! 한글 Visual Studio 에서는 안되네요~?

기본적으로 설치하면 Item Template 이 영문 Visual Studio 에서만 보이도록 LCID 가 1033 폴더에 Item Template 이 존재합니다. 이것을 LCID 가 1042 인 폴더로 옮겨주시면 됩니다.

Item Template 폴더의 영문 템플릿 폴더의 ConfigurationSectionDesigner.zip 을 한글 템플릿 폴더로 복사합니다. 
C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\ItemTemplates\CSharp\1033\ConfigurationSectionDesigner.zip 파일을
 
C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\ItemTemplates\CSharp\1042 폴더로 복사

   

프로젝트 템플릿도 함께 복사해주면 좋겠죠~?

C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\ProjectTemplates\CSharp\Windows\1033\ConfigurationSectionProject.zip 파일을 
C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\ProjectTemplates\CSharp\Windows\1042 폴더로 복사    

마지막으로 Visual Studio Command Line 에서 devenv /setup 을 실행해야 합니다. 이것은 Visual Studio 는 이러한 Template 을 Caching 해놓고 사용하기 때문에 /SETUP 옵션으로 Caching 을 업데이트 해 주어야 합니다.    

반응형
반응형
오늘은 비동기 ADO.NET 을 알아볼까 합니다.
아티클을 이해하기 위해서는 비동기 호출에 대한 지식이 약간 필요합니다.
 
우리는 다음과 같은 시간이 오래 걸리는 쿼리를 날릴것입니다.
WAITFOR DELAY '00:00:03'; SELECT * FROM Titles
WAITFOR DELAY '00:00:05'; SELECT * FROM Titles
WAITFOR DELAY '00:00:10'; SELECT * FROM Titles
 
위의 WAITFOR DELAY 는 명령문이 암시하듯 이유없는 대기를 하라는 명령입니다.
3초후에 SELECT, 5초후에 SELECT, 10초후에 SELECT 쿼리를 날려 시간이 오래걸리는 작업을 대체하기 위해서 이지요~
 
다음의 쿼리를 쿼리분석기를 통해 실행시켜 보겠습니다.
 
예상대로 18초가 걸리는군요~
하나하나의 쿼리가 종료해야만이 다음 쿼리를 실행할 수 있죠~
이런방식을 동기 방식이라고 합니다.
C#  lock 과 같은 키워드가 여러 개의 쓰레드의 요청이 있더라도, 마치 일렬로 줄을 세워 한번에 하나씩 처리하는 것과 비슷하다고 생각하시면 됩니다.
 
그렇다면, 위의 쿼리를 비동기 방식으로 호출하였을 때 어떤 결과가 나올까요?
그럼, 그 의문을 하나하나씩 풀어보기로 합니다.
 
우선 전체 소스를 보면서 비밀을 하나하나씩 파헤쳐 보겠습니다.
using System;
using System.Data;
using System.Data.SqlClient;
using System.Threading;
 
namespace ConsoleTest
{
         class Program
         {
                  // 비동기로연결하기위해Asynchronous Processing 를true 로설정해놓은커넥션Connection String
                  public static string GetConnection
                  {
                           get
                           {
                                   return "Asynchronous Processing=true;server=localhost;database=pubs;uid=sa;password=xxxx";
                           }
                  }
 
                  static void Main(string[] args)
                  {
                           SqlConnection cn1 = new SqlConnection( GetConnection );
                           SqlConnection cn2 = new SqlConnection( GetConnection );
                           SqlConnection cn3 = new SqlConnection( GetConnection );
 
                           SqlCommand cmd1            = new SqlCommand("WAITFOR DELAY '00:00:03'; SELECT * FROM Titles", cn1);
                           SqlCommand cmd2            = new SqlCommand("WAITFOR DELAY '00:00:05'; SELECT * FROM Titles", cn2);
                           SqlCommand cmd3            = new SqlCommand("WAITFOR DELAY '00:00:10'; SELECT * FROM Titles", cn3);
 
                           cn1.Open();
                           cn2.Open();
                           cn3.Open();
 
                           // DataAccess 시작시간을기록한다.
                           DateTime startDate = DateTime.Now;
 
                           // 비동기작업을시작한다.
                           IAsyncResult result1       = cmd1.BeginExecuteReader();
                           IAsyncResult result2       = cmd2.BeginExecuteReader();
                           IAsyncResult result3       = cmd3.BeginExecuteReader();
 
                           WaitHandle[] waitHandle    = new WaitHandle[3];
                           string[] resultStr                 = new string[3];
 
                           waitHandle[0]                      = result1.AsyncWaitHandle;
                           waitHandle[1]                      = result2.AsyncWaitHandle;
                           waitHandle[2]                      = result3.AsyncWaitHandle;
 
                           for (int i = 0; i < waitHandle.Length; i++)
                           {
                               // 배열의핸들이모두신호를받을때까지기다립니다.
                               int endIndex           = WaitHandle.WaitAny( waitHandle, 20000, false );
 
                               switch (endIndex)
                               {
                                   case 0:
                                       cmd1.EndExecuteReader(result1);
                                       resultStr[0]   = DateTime.Now.ToString();
                                       break;
                                   case 1:
                                       cmd2.EndExecuteReader(result2);
                                       resultStr[1]   = DateTime.Now.ToString();
                                       break;
                                   case 2:
                                       cmd3.EndExecuteReader(result3);
                                       resultStr[2]   = DateTime.Now.ToString();
                                       break;
                               }
                           }
 
 
                           cn1.Close();
                           cn2.Close();
                           cn3.Close();
 
                           // DataAccess 작업완료시간을기록한다.
                           DateTime endDate = DateTime.Now;
 
                           for( int i=0; i<resultStr.Length;i++)
                                   Console.WriteLine( " Result {0} : {1}", i, resultStr[i] );
 
 
                           // 두시간의시간차를구하여출력한다.
                           TimeSpan timeSpan = endDate - startDate;
 
                           Console.WriteLine("총작업시간은: {0}", timeSpan.ToString() );
                  }
         }
}
 
 
우선 가장 눈에 띄게 차이 나는 것이 ConnectionString 입니다.
Asynchronous Processing=true;server=localhost;database=pubs;uid=sa;password=xxxx
Asynchronous Processing=true는 비동기로 DataBase 에 연결하기 위해 반드시 추가해 주어야 합니다.
 
또한 비동기 데이터베이스 작업을 하기 위해 SqlCommand 클래스는 세가지의 비동기 메서드가 존재합니다.
대부분의 비동기 메서드들이 Begin 으로 시작하는 네이밍을 갖는 것과 마찬가지로, BeginExecuteNonQuery(), BeginExecuteReader(), BeginExecuteXmlReader()가 바로 그것입니다.
이 메서드는 각각 EndExecuteNonQuery(), EndExecuteReader(), EndExecuteXmlReader() 가 호출됨으로써 비로서 비동기 작업의 콜백을 받을 수 있습니다.
위의 경우는 구현되지 않았지만, DataReader 의 경우 콜백이 완료된 후가 되어야지만 비로서 Read 작업을 수행할 수 있는것입니다. 물론 다른 비동기 메서드로 마찬가지입니다.
 
이 구문은 비동기 ADO.NET 작업의 가장 중요한 부분입니다.
waitHandle[0]                      = result1.AsyncWaitHandle;
waitHandle[1]                      = result2.AsyncWaitHandle;
waitHandle[2]                      = result3.AsyncWaitHandle;
WaitHandle.WaitAny( waitHandle, 20000, false );
 
우리는 waitHandler 배열에 비동기 작업이 끝나는데 사용되는 AsyncWaitHandle 를 넣었습니다.
AsyncWaitHandler 는 비동기 작업이 끝나기를 기다리는 핸들러 입니다.
물론 WaitAny 메서드의 3가지 Overload 는 모두 첫번째 인자를 배열로만 받습니다.
WaitWay 메서드는 우리가 넣은 배열의 요소중에 먼저 끝낸 배열의 Index 를 return 합니다.
waitHandler[2] 의 작업이 제일 먼저 끝나게 되면 배열의 인덱스인 2 를 리턴하게 됩니다.
만약 짧은 시간동안 어느 작업도 끝나지 않게 되면, WaitAny 는 두번째 인자에 적은 시간(밀리초)만큼 무한 대기 하게 됩니다.
 
배열에 개수만큼 루프를 돌면서, 비동기 작업이 끝나는 순서대로 SqlCommand 의 작업을 하게 됩니다.
때문에 결과 화면의 DateTime.Now 의 시간은 각각 달라질 수 있는 것입니다.
 
그럼 실행화면을 보겠습니다.
 
보는대로 총 소요시간은 10초가 되었습니다.
위의 동기작업의 쿼리 수행 결과보다 훨씬~ 빨라졌지요?
각가 쿼리 수행이 완료된 시간도 한번 눈여겨 볼만 하네요~
 
긴 시간이 소요되는 작업이나 여러 번 반복적으로 실행되야 하는 작업등에 적용해 보면 상당히 만족할만할 결과가 아닐 수 없네요~
물론 ASP.NET 의 웹 환경에서도 적용이 가능합니다.
반응형

'Program > C#' 카테고리의 다른 글

크로스 스레드와 Control.Invoke  (0) 2011.03.08
Configuration Section Designer..  (0) 2011.01.31
Attribute(어트리뷰트) System.ComponentModel  (0) 2011.01.29
System.ComponentModel Attribute  (0) 2011.01.29
C# ?? 연산자  (0) 2010.12.27
반응형
어트리뷰트(Attribute)

 

Attribute(어트리뷰트, 속성)는 어셈블리, 모듈, 클래스, 구조체, 열거형변수, 생성자, 메소드, 프로퍼티, 필드, 이벤트, 인터페이스, 파라미터, 반환값, 델리게이트 같은 프로그래밍 요소에 대해 메타정보를 포함 시킬수 있는 기술이다. 프로그래밍요소에 추가정보를 기술을 통해서 런타임용코드를 설명하거나 런타임에서 응용프로그램 동작에 영향을 주거나  CLR에게 추가정보를 제공한다. 어트리뷰트 정보는 어셈블리의 메타데이터 내에 저장되며 리플렉션을 통해서 실행시간에 추출가능하다. 또 표준어트리뷰트(내장어트리뷰트)와 사용자정의 어트리뷰트로 나뉜다. 교과서 적인 이야기를 막 했는데 코드를 읽는데 어트리뷰트를 모르면 눈에 걸리적 거리고 신경 쓰여서 가독성과 이해력이 떨어지므로 알아야 한다. 사용자 정의 클래스 등을 만들 때 설명을 포함시키거나 디버그 정보 활용, API함수 사용 정도로 흔히 사용한다.

 

 

 

1. 어트리뷰트 구문

 

[attribute(positional_parameters, name_parameter = value, ...)] element

[attribute명(위치매개변수 , 명명된 매개변수=값,...)]  프로그래밍요소

 

어트리뷰트 역시 클래스의 일종으로 '[]' 를 제외하고는 일반적인 함수와 사용법이 유사하다. 파라미터는 필수항목이고 인수의 순서를 지켜야 하는 위치지정 파라미터(positional_parameter)와 사용자의 필요에 따라서 선택적으로 사용되고 매개변수 이름과 함께 사용하는 명명 파라미터(name_parameter)가 있다. 파라미터 이름과 값을 같이 사용할 경우 위치에 상관없이 사용할 수 있다. 
 

 

 

 

2. 어트리뷰트의 종류

 .NET 에서 제공되는 내장 어트리뷰트(Predefined Attributes)는 General attributes, COM interoperability attributes, Transaction handling attributes, Visual designer component building attributes 로 나뉜다.

 

2.1 General attributes

일반어트리뷰트(General attributes)는  Conditional Attribute와 DllImport Attribute가 있다.

 

2.1.1 Conditional Attribute

Conditional Attribute는 디버깅을 위해 사용된다. 다음 예 처럼 사용자가 정의한 symbol이 있으면 해당 메서드를 실행 한다. Conditional Attribute는 클래스나 구조체 안에 있는 메소드 에서만 사용 할 수 있고 리턴 타입이 void 타입 이어야 한다.

 

#define DEBUGGING    

// #undef DEBUGGING     //어튜리뷰트 사용 해제
using System;  
using System.Diagnostics;
namespace CSharpStudy  
{  
       class  MainClass  
       {  
           [Conditional("DEBUGGING")]  
           public static void DebugPrint()  
           {  
              Console.WriteLine("DEBUGGING");  
           }  

           [STAThread]  
           static void Main(string[] args)  
           {  
              DebugPrint();  
           }
       }
}

다음의 코드 처럼 메서드에 대해 여러 Conditional Attribute를 붙이게 되면 그 중 하나만 위치지정 파라미터가 정의 되어 있어도 그 메소드를 실행 시킨다. 

 

#define SOUNDCARD
#define SPEAKER

using System;
using System.Diagnostics;

namespace CSharpStudy
{

    class Test
    {

        static void Main()
        {
            isSound();

            Console.ReadLine();
        }

        [Conditional("SOUNDCARD"), Conditional("SPEAKER")]
        static void isSound()
        {
            isSpeaker();
        }

        static void isSpeaker()
        {
            Console.WriteLine("음악을 들을 수 있습니다...");
        }


    }

}

 

 

2.1.2 DllImport 어트리뷰트
DllImport Attribute는 C#안에서 Unmanaged Code를 사용 할 수 있게 한다. Unmanaged Code란 .Net 환경 밖에서 개발된 Win32 API 같은 코드를 가리킨다. DllImport Attribute는 System.Runtime.InteropServices 네임스페이스 안에 정의 되어 있다. 사용방법은 위치지정 파라미터로 사용 할 DLL 파일을 인자로 넘겨 주면 된다.

[DllImport("MyDLL.dll", EntryPoint="MessageBox")]
public static extern int MyFunction(string param1);

 


using System;
using System.Runtime.InteropServices;

class Test
{
        //User32.dll 파일안의 MessageBox 함수를 불러와서 사용하는 예이다. DllImport Attribute를
        //이용하여 사용할 코드가 포함되어 있는 DLL을 넘겨주고 extern 키워드를 통해 사용하려고 하는
        //메소드가 외부에 있음을 알린다. 이렇게 하면 닷넷 환경 밖에서개발된 코드들도 C#안에서 쓸수 있다.
        [DllImport("User32.Dll")]
        public static extern int MessageBox(int h, string m, string c, int type);

 

        static void Main()
        {
                MessageBox(0, "Hello World!", "타이틀", 0);  

        }
}

 

 

 

2.2 Visual Designer Component-Building 어트리뷰트
사용자정의 컨트롤을 비주얼디자이너에서 속성(Property)이나 클래스를 어떻게 표시할지를 정한다.
Browseable 

프로퍼티나 이벤트가 비주얼 디자이너의 속성창에 나타나야하는지 명시
DefaultEvent

컨트롤의 기본 이벤트를 지정
DefaultProperty

컨트롤의 기본 속성으로 지정
DefaultValue

컨트롤의 기본값 지정
Persistable

속성이 저장되어야 하는지 프로퍼티 저장되어야 하는지, 저장되어야 한다면 어떻게 저장 되어야하는지 명시
Category

속성창에서 프로퍼티나 이벤트가 어느 카테고리에 들어가야 하는지 명시
Description

프로퍼티나 이벤트가 선택 되었을 때 프로퍼티 창 밑에 나오는지 설명을 정의
Localizable

프로퍼티가 폼이 지역화되었을 때 리소스 파일에 저장되어야함을 명시
Persistable

프로퍼티가 저장되어야 하는지, 저장되어야 한다면 어떻게 저장되어야 하는지 명시  

 

 

 

2.3 COM Interoperability 어트리뷰트
ComRegisterFunction

.Net 어셈블리를 COM이 사용할 수 있도록 레지스트에 등록할 때 호출되는 함수임을 나타낸다.
ComImport

클래스나 인터페이스의 정의가 COM 타입 라이브러리로부터 가져온 것을 나타낸다.
ComUnregisterFunction

.Net  어셈블리를 레지스트리로부터 삭제할 때 호출 되는 함수임을 나타낸다.
InterfaceType

관리된 인터페이스가 COM에 노출 되었을 때 그 인터페이스가 IDispatch인지 IUnknown인지 또는 dual인터페이스인지 나타낸다.
Displd

함수(메소드)나 속성에 사용될 Dispatch ID를 나타낸다.
In

필드나 파라미터가 input값으로 사용됨을 나타냄
Out

해당 인자 값을 통해서 불려진 함수가 결과 값을 리턴할  때 마샬링을 해줘야 함을 나타낸다.
Progld

클래스를 사용하기 위한 Prog ID를 나타낸다.
MarshalAs

데이터가 COM과 관리(managed)되는 사이에서 어떻게 마샬링되는지 정보를 나타낸다.
HasDefaultInterface

클래스가 디폴트 COM 인터페이스를 가지고 있음을 명시적으로 나타낸다.

 

 


2.4 Transaction handling attributes
COM+ 환경에서 트랜젝션 관리를 위해 사용된다.

[Transaction(TransactionOption.Required)]
public class MyTransactionalComponent
{
    ...
}

 

 

 

 

 

3 어트리뷰트 사용 패턴

 

3.1 Multiple Attribute의 사용
한 개의 프로그래밍 요소에 한 개 이상의 어트리뷰트를 사용할 수 있으며 어트리뷰트정의에서 허용한다면 같은 어트리뷰트를 여러번 사용할 수도 있다.

 

[Transaction(TransactionOption.Required)]
[DefaultProperty("Balance")]
public class FinancialComponent: System.WinForms.UserControl
{

   public long Balance
    {
        ...
    }

    ...
}

 

// 어트리뷰트는 기본적으로 한 어트리뷰트에 한 개의 인스턴스만 사용 가능하지만

// AllowMultiple을 true로 지정하면 위에서와 같이 같은 어트리뷰트를 여러 개 사용 가능하다.

[CustomAttribute.DeveloperInfo("한개발", Date="12-12-2000"),
 CustomAttribute.DeveloperInfo("C# 개발자", Date="11-11-2000")]
public class AttTest

{

    ...
}


[AdditionalInfoAttribute(“jclee”,”2004-01-01”, Download=http://www.oraclejava.co.kr)]
[AdditionalInfoAttribute(“tatata”,”2004-02-01”, Download=http://www.oraclejava.co.kr)]
class OracleJava { … }

 

3.2 어셈블리관련 어트리뷰트

using절 다음에 위치하며 어떤 code 보다 앞에 존재해야 한다. 어셈블리의 어트리뷰트임을 명시(assembly:)한다.

using System.Reflection;

[assembly: AssemblyTitle("C# How-To: Multithreading")]
[assembly: AssemblyDescription("Microsoft C# .NET How-To: Multithreading")]
[assembly: AssemblyCompany("Microsoft Corporation")]
[assembly: AssemblyProduct("Microsoft C# .NET How To: 2002")]
[assembly: AssemblyCopyright("Copyright  2002 Microsoft Corporation.  All rights reserved.")]
[assembly: CLSCompliant(true)]
[assembly: AssemblyVersion("1.0.0.0")]
 

 

 3.3 기타 어트리뷰트 사용 예

 

[WebService(Namespace = "http://www.abcworld.or.kr/DssWeb")]
[WebServiceBinding(Name = "LocalBinding", ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
public class Main : System.Web.Services.WebService
{
       //
}

 

[WebMethod(Description = "SQL구분자와 2개의 문자열 파라미터를 받고 DataTable 로 리턴함.",

                   EnableSession = true)]
public DataTable Get2PamTbl(String QryTitle, String strStdDate, String strEndDate)
{
       // String UserIP = (String)Session["UserIP"];
}

 


[Serializable]  클래스가 serialize될 수 있음을 나타냅니다.
Serialization(직렬화):직렬화는 객체를 저장매체(파일이나 메모리와 같은)에 저장하거나 바이너리(이진) 형태로 네트워크를 통해 전달하기 위해 저장하는 과정이다. 이(객체를 직렬화한) 연속된 바이트나 포맷은 다시 원래 객체가 가지고 있던 상태로 객체를 재생성하는데 사용할 수 있다. 이렇게 객체를 직렬화하는 과정은 디플레이팅(deflating) 또는 마샬링(marshalling)이라고도 부른다. 직렬화와는 반대로 연속된 바이트로부터 어떤 데이터 구조를 추출해내는 과정을 역직렬화(deserialization)라고 한다. 역직렬화는 인플레이팅(inflating) 또는 언마샬링(unmarshalling)이라 불리기도 한다.

 

 

 

 

 

 

4. 사용자정의 Attribute

 

4.1 Custom  Attribute 정의

   어트리뷰트 역시 클래스라고 도입부에 이야기했다. 사용자 정의 어트리뷰트를 만든다는 것은 결국 새 클래스를 만든다는 것이다. 클래스 선언 방법은 보통 클래스 선언하는 것과 동일한 방법으로 선언하고 다만 System.Attribute로 부터 상속을 받으면 그 클래스가 어트리뷰트가 된다. 일반 클래스와 구별하기 위해 클래스 이름 뒤에 접미사로 ‘Attribute’를 붙이는 것이 좋다. 어트리뷰트도 하나의 클래스이므로 생성자나 멤버변수(필드), 메소드등을 가질수 있으며 상속도 가능하다. 어트리뷰트 또한 일반적인 클래스처럼 생성자가 명시적으로 써주지 않아도 컴파일러에 의해 묵시적으로 디폴트 생성자를 가질 수 있다.

 

4.1.1 어트리뷰트의 범위 지정

Conditional Attribte는 메서드에만 붙일 수 있다. 마찬가지로 사용자 정의 어트리뷰트도 특정한 타겟을 지정할 수 있다.

AttributeUsage 라는 어트리뷰트를 사용하여 사용자가 정의한 데이터 타입에만 어트리뷰트를 붙일 수 있다. AttributeUsage의 파라미터는 System.AttributeTargets enum 타입으로 정의 되어 있다. AttrbuteTargets.All, AttrbuteTargets.Class, AttrbuteTargets.Delegate, AttrbuteTargets.Interface, AttrbuteTargets.Propert, AttrbuteTargets.Construct 등이 있으며 여러 개의 데이터 형에 붙일려면 ‘|’를 이용하면 된다.


[AttributeUsage(AttrbuteTargets.Method)]
public class AdditionalInfo { … }
[AttributeUsage(AttrbuteTargets.Interface | AttrbuteTargets.Delegate)]
public class AdditionalInfo { … }

 

 

 

4.1.2 어트리뷰트 예제

클래스, Enum, 구조체에 대해서 작성자, 작성일자를 표시하는 Custom 어트리뷰트다. AttributeUsage 어트리뷰트를 사용하여 적용할 타입을 지정하고 있으며 AllowMultiple 속성을 이용하여 같은 어트리뷰트를 반복 사용이 가능하도록 했다. 클래스 명을 DeveloperInfoAttribute로 주었는데 일반클래스와 구분하기 위해 접미사 Attribute를 사용하였다. 또한 System.Attribute 클래스를 상속하고 있다. 생성자에서 한개의 파라미터를 받고 있으므로 어트리뷰트를 사용할 때는 필수파라미터로 적용된다.

 

using System;

namespace CustomAttribute
{

    /// <summary>
    ///    이 클래스는 커스텀어트리뷰트의 예입니다.
    ///    클래스, Enum, 구조체를 타겟으로 작성자(개발자) 정보를 표시합니다.
    /// </summary>
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Struct, AllowMultiple = true)]
    public class DeveloperInfoAttribute : System.Attribute
    {
        private string developerName;
        private string dateCreated;

        // 생성자 : 개발자명을 유일한 필수파라미터로 가지고 있음
        // 따라서 이 attribute의 필수 인수는 하나
        public DeveloperInfoAttribute(string developerName)
        {
            this.developerName = developerName;
        }

        // 작성자 명을 리턴하는 속성(Property)
        public string Developer
        {
            get
            {
                return developerName;
            }
        }

        // 작성일자를 세팅하거나 리턴하는 속성(Property)
        // 이 attribute의 선택적 인수
        public string Date
        {
            get
            {
                return dateCreated;
            }

            set
            {
                dateCreated = value;
            }
        }
    }
}

 

 

 

4.2.1  Custom  Attribute 사용

[CustomAttribute.DeveloperInfo("한개발", Date="12-12-2000"),
 CustomAttribute.DeveloperInfo("C# 개발자", Date="11-11-2000")]
public class AttTest

{
    [CustomAttribute.DeveloperInfo("고성능")]
    public enum DaysOfWeek
    {
        Monday,
        Tuesday,
        Wednesday,
        Thursday,
        Friday,
        Saturday,
        Sunday
    }

    [CustomAttribute.DeveloperInfo("이로직")]
    public struct Book
    {
        public string author;
        public string title;
        public int copyright;

        public Book(string a, string t, int c)
        {
            author = a;
            title = t;
            copyright = c;
        }
    }

}

 

 

 4.2.2 사용자정의 어트리뷰트의 처리 과정

   - 어트리뷰트 클래스를 찾는다.
   - 어트리뷰트의 범위를 체크 한다.
   - 어트리뷰트의 생성자를 체크 한다.
   - 객체의 인스턴스를 생성 한다.
   - 명명 파라미터들을 체크 한다.
   - 명명 파라미터 값으로 필드나 프로퍼티의 값을 설정 한다.
   - 어트리뷰트 클래스의 현재 상태를 저장한다.

 
 

 

4.3   Custom  Attribute 맴버확인

클 래스에 어트리뷰트를 붙였다면 반대로 클래스로부터 어떤 어트리뷰트들이 붙어 있는지 아는 하는 방법도 있어야 할 것이다. 바로 전 예제에서 Test라는 클래스에 어트리뷰트를 붙였는데 반대로 Test라는 클래스로부터 어트리뷰트 정보를 알아내는 방법을 예제를 통해 보도록 하자.

 

4.3.1 클래스 메타 데이터
여 기서는 Reflection을 이용하여 값을 얻어오는 것을 살펴 보도록 하자. 닷넷 프레임 웍에서는 메타데이터를 검사 할 수 있는 클래스들이 포함된 System.Reflection 이라는 네임스페이스를 제공 하고 있다. 이 Reflection 네임스페이스 안에 있는 MemberInfo 라는 클래스는 클래스의 어트리뷰트를 알아볼 때 유용하게 사용 될 수 있다. System.Type 객체의 GetMembers 라는 메소드를 적절히 구사하여 클래스 메타데이터 를 조사 할 수 있다.


System.Reflection.MemberInfo[] members;
Members = typeof(MyClass).GetMembers();

 


4.3.2 어트리뷰트의 정보 얻기
MemberInfo 객체는 GetCustomAttributes 라는 메소드를 가지고 있다. 이 메소드는 모든 어트리뷰트의 정보를 배열로 알아 낼 수 있다. 이렇게 알아낸 정보를 반복문을 이용하여  어트리뷰트를 조사 할 수 있다.

 

    public static void Main()
    {
        System.Reflection.MemberInfo attrInfo;
        attrInfo = typeof(AttTest);
        object[] attrs = attrInfo.GetCustomAttributes(false);

        CustomAttribute.DeveloperInfoAttribute developerAttr;
        developerAttr = (CustomAttribute.DeveloperInfoAttribute)attrs[0];
        Console.WriteLine("개발자: {0}\t작성일: {1}", developerAttr.Developer, developerAttr.Date);
        developerAttr = (CustomAttribute.DeveloperInfoAttribute)attrs[1];
        Console.WriteLine("개발자: {0}\t작성일: {1}", developerAttr.Developer, developerAttr.Date);


        Console.WriteLine(" \t\t");

        foreach (Attribute attr in attrInfo.GetCustomAttributes(true))
        {
            CustomAttribute.DeveloperInfoAttribute info = attr as CustomAttribute.DeveloperInfoAttribute;

            if (info != null)
            {
                Console.WriteLine("개발자: {0}     작성일: {1} \t", info.Developer, info.Date);
            }
        }

        Console.ReadLine();
    }

반응형

+ Recent posts

반응형