안녕하세요. 방수철입니다.
이번 글부터는 <algorithm> 에 추가된 기능들에 대해서 설명드리겠습니다.
STL 알고리즘 일반론
STL 알고리즘은 다양한 자료구조에 적용할 수 있기 때문에 일반화 되어있다고 합니다. vector나 list같은 STL에 포함된 자료 구조뿐만 아니라 특정 알고리즘의 요구조건을 충족시키는 자료구조나 배열 또한 다룰 수 있습니다. STL 알고리즘은 반복자를 통해 간접적으로 자료구조의 각 요소에 접근함으로써 이런 수준의 일반성을 달성합니다.
STL 알고리즘은 일반적으로 시작이나 끝 위치로 특정되는 반복자를 처리하게 되는데 참조되는 범위들은 반드시 유효해야 합니다. 또한 범위의 모든 위치는 반드시 역참조가능 해야하며, 각 범위의 역속된 요소들에서 처음부터 마지막 위치까지 반드시 증가 연산으로 도달 가능하여야 합니다.
STL 알고리즘은 각 STL 자료구조의 연산자와 멤버 함수에 의해 지원되는 동작들을 확장하고, 동시에 다른 종류의 자료구조들 간에 동작하도록 합니다. 알고리즘의 목적을 전달하기 위해서 두가지 접미사가 사용되고 있습니다.
'_if' 접미사는 해당 알고리즘이 함수 객채와 같이 사용되는 것을 알려줍니다. 예를 들어 find와 find_if를 비교해보겠습니다. find는 조건으로 주어지는 값과 일치하는(설명의 편의상 일치하는 경우를 예를 들었습니다.) 요소를 찾는 알고리즘 입니다. 하지만 find_if를 사용할 경우 함수 객체를 인수로 전달하여, 그 함수에 의해서 참으로 처리되는 요소를 찾아 낼 수 있습니다. ( 예 : 짝수 찾기 )
'_copy' 접미사는 해당 알고리즘이 자료 구조의 요소들을 다룰 뿐만 아니라 변경된 값을 목적 범위에 복사함을 알려줍니다. 예를 들어, reverse 알고리즘은 주어진 범위의 값들의 순서를 뒤집는 기능을 하지만 reverse_copy 알고리즘은 reverse의 기능과 함께 주어진 반복자의 위치에 뒤집힌 순서의 자료구조를 복사하게 됩니다.
STL 알고리즘은 종종 목적이나 필요조건을 가리키는 그룹으로 분류됩니다. 그 중 한가지 분류법으로, 자료 구조의 내용이 변경되는 것과 그렇지 않은 것으로 나누는 것 입니다.
'변형시키는 알고리즘'은 값은 바꾸지 않지만 순서를 바꾸는 알고리즘입니다.
'제거하는 알고리즘'은 일정 범위의 요소들을 지우게 됩니다.
'정렬 알고리즘' 범위안의 요소들을 다양한 방법으로 재배치 하며,
'정렬된 범위 알고리즘'은 정렬된 범위에서만 동작하는 알고리즘 입니다.
참고로, 수치 연산을 제공하는 STL 알고리즘은 따로 <numeric>이라는 헤더 파일이 존재하며, 함수 객체와 어댑터가 정의된 <functional> 헤더 파일이 있습니다. boolean을 반환하는 함수 객체는 predicate로 알려져 있습니다. 기본 바이너리 predicate는 비교연산자 < 이다. 일반적으로, 정렬되는 요소들은 대소비교가 가능하여 동등함이 판별될 수 있어야 합니다.
처음 뵙게 되었네요. 이미 DX11에 글을 올리고 있는 조진현님의 협박과 강요에 힘입어 오늘부터 DX 11입문 강좌를 연재하게 되었습니다.
이미 DX11의 변경점이나 중요한 개념들은 앞서 연재하시는 분이 두분이나 계시기에… 저는 기술적인 이야기보다는 실제로 예제들을 만들어보고, 분석하면서 DX11을 배워가는 내용을 연재하기로 결정하였습니다.,
그리고 단지 변경된 점뿐만 아니라, 실제로 DX11을 이용하여 3D 그래픽스 프로그래밍의 기초도 배울 수 있는 내용으로 연재하려고 합니다. DX 11을 배우기 위해서는 3D 그래픽스에 대한 이해는 당연한 필수 조건이니까요. ^^
강좌 연재 목적은 무엇인가요
이 연재의 목적은 여러 예제 및 튜터리얼을 통하여 실제로 DirectX 11을 사용하여 개발을 하면서 익히는 것이 목적입니다. 사실 아직 DirectX 11은 주류로 자리잡지는 않고 있습니다만,분명 2년 이내에 주류로 자리 잡을 것은 확실합니다. (DX10은 비스타와 같이 묻혀버렸죠…)
현재 주류로 자리잡고 있는 DX9과 DX11은 상당히 많은 차이를 보이고 있습니다. 미리미리 DX11을 공부해두어야만 차후에 DX11이 주류로 자리 잡았을 때 당황하지 않고 개발할 수 있을것입니다. 간단히 말해서 미리 배워두면 도움이 되었으면 되었지, 손해 보지는 않을거란 이야기입니다. :)
어떤 사람들에게 도움이 될까요
일단 초보자 대상의 내용으로 연재를 진행할 생각입니다. 입문편이니까요(^^). 하나 하나 예제를 차근 차근 따라오고 설명하면서 기본적인 부분부터 배워나갈 예정입니다. 그래서! 혼자서 DX11 도움말을 보면서 배울수 있는 중고급 개발자분들에게는 크게 도움이 되지 않을 수도 있습니다. (차후에 여유가 생기면 좀더 고급스런 예제들을 다루는 '중급편'이나 '고급편'도 연재해 볼 생각입니다. )
ASP.NET MVC 는 가볍고 확장성이 넓으며 , 응용이 무한한 가능성을 지닌 새로운 솔루션이다. 사람들은 ASP.NET 의 ViewState 의 무게에 좌절했으며 , 스마트폰의 보급으로 인해 예전의 후퇴한 하드웨어 스팩에서는 가벼움만이 대세였다. URL과 html 소스코드는 점차 경량화 되어갔으며 , 그 과정에서 라이트 하며 , 서로간의 결합도가 낮은 어떠한 솔루션을 찾는건 필연적이었다. 그러한 배경에서 등장한 ASP.NET MVC 는 이제 출시된지 2년을 넘긴 닷넷의 주력상품중에 하나가 되었다. 그럼에도 불구하고 MVC 는 여전히 높은 진입 장벽을 가지고 있다. 특히 생산성이 강조되는 인트라넷 시스템에서는 여전히 기존 ASP.NET이 선호되고 있으며 , MVC 는 여러 장점에도 불구하고 선호도의 우선순위가 떨어지고 있는 형국이다. 그러나 이러한 원인들은 사실 선입견에 불과하며 , MVC 를 사용해볼수록 그 놀라운 확장성에 놀라게 된다.
이러한 장점에도 불구하고 , 이 MVC 는 항상 컨트롤러 - 액션으로 불리는 콤보가 존재해야 웹페이지를 생성할수 있는 단점이 존재한다. 이는 필자의 관점에서는 오히려 asp.net 보다 퇴보한 것으로써 , aspx 페이지가 비하인드 코드 없이 생성 가능 한것에 비해 상당히 큰 제약으로 다가오게 된다. 예를 들어 사용자에게 회사 대표 이사를 소개하는 페이지를 작성한다고 해보자. 이 경우 이 페이지의 컨텐츠는 적어도 대표이사가 변경되지 않는 이상 거의 변하지 않을것이라고 기대할수 있다. 이런경우에 이 페이지에 어떠한 비하인드 코드가 들어가는것은 낭비라고 생각된다. 기존에 asp.net 은 이 부분을 html 혹은 비하인드 코드를 제거한 aspx 페이지를 이용함으로써 컴파일링 없이 손쉽게 페이지가 생성가능했다. 필자는 이부분을 MS 어필하고 싶었으나 , 여러 복잡한 사유로 인해(귀차니즘...) 전달하지 못하였었다. 그렇다면 , MVC를 이용하면서 이러한 퇴보는 받아들여져야 하는것인가? 결론을 말하자면 그렇지 않다. 이번 글에서 어떻게 Controller - Action 콤보를 피해서 자유롭게 페이지를 생성할수 있는지에 대해 설명하도록 하겠다.
사실 이 요구 사항은 MVC가 처음 세상에 소개되었을때부터 점진적으로 요청되어 왔다고 보인다. 일단 몇가지 알려진 해결방법을 소개 하도록 하겠다.
1. mvc가 최초로 소개되었을때 소개되었던 방법이다. mvc2에서도 여전히 유효하며 , 내 생각엔 가장 널리 쓰이는 방법인것으로 보인다.
AvoidController.cs
public class AvoidController : Controller
{
protected override void HandleUnknownAction(string actionName)
{
this.View(actionName).ExecuteResult(this.ControllerContext);
}
}
해당 함수가 호출되면 해당 컨트롤러 안에 액션이 존재하지 않더라도 ActionName 으로 이루어진 View 를 실행시켜준다. 아주좋다. 처음 서술했던 대부분의 이슈가 소량의 코드로 해결된다. 그렇지만 여전히 이슈는 남아있다. 이 코드는 보다시피 컨트롤러를 상속받아서 실행된다. 기본적으로 MVC는 다음과 같은 순서로 view 페이지를 탐색한다.
[컨트롤러가 존재할때의 페이지 서치]
[컨트롤러가 존재하지 않을때의 페이지 서치]
컨트롤러가 존재하지 않는다면 해당 페이지 역시 탐색해낼수 없다. http://www.loveciel.com/Policy/EURA 와 같은 페이지가 생성된다고 가정했을때 , Policy라는 컨트롤러를 반드시 만들어야 한다.
2.
GhostController.cs
public class GhostController : Controller
{
public ActionResult ShowPage(String folder , String view)
{
return View("~/views/" + folder + "/" + view + ".aspx");
}
}
global.asax.cs
routes.MapRoute(
"AnyView" ,
"pages/{folder}/{view}",
new {controller="Ghost" , action="ShowPage"}
);
드디어 우리는 컨트롤러와 Action 콤보에서 해방됬다. 맨앞에 'pages' 라는 페이지 주소를 항상 붙여야 한다는 사실을 제외하면 그렇다는 얘기다. 또한 이것 역시 html 파일은 읽어내지 못한다는 단점이 있다.
Here is my solution
/* 완벽한 분리를 위해서는 조금 많은 준비가 필요하다. 솔직히 말하면 거의 새로운 MVC 템플릿이라고 봐도 무관할것 같다. IoC 를 통해 컨트롤러를 동적으로 감지하고 , MasterPage 를 사용하기위한 몇가지 아이디어를 추가했다. 또한 최근 유행인 IoC 컨테이너를 적극적으로 활용하였다.(이번 예제에서는 Unity 를 사용하였다.) */
먼저 mvc가 파일을 찾는 구조에 대해 생각해보도록 하자.
controller/
shared/
위와 같이 이루어져 있다.
내가 설계한 개념도는 위와 같다.
[컨트롤러가 존재할때의 페이지 서치]
[컨트롤러가 존재하지 않을때의 페이지 서치]
이 설계에서는 총 3곳에 위치한 aspx , html 파일을 읽어오도록 디자인 하였다.
1. view 폴더안의 controller 에 대응되는 폴더 안에 위치한 aspx 파일
2. view 폴더안의 controller 에 대응되는 폴더 안에 위치한 html 파일
3. 지정된 폴더안에 path1 ~ 5/파일명1 에 대응되는 파일명
ex) http://www.abc.co.kr/path1/path2/path3/test.html 의 경우 지정된루트폴더\path1\path2\path3\test.html 에 있는 파일을 검색함.
Ghost&Cache Controller 는 페이지로 디자인 되지 않은 부분이기 때문에 , 해당 컨트롤러에 대응되는 폴더가 있으면 되지 않는다. 그렇기 때문에 , 해당 컨트롤러에 대응 되는 페이지는 shared 폴더에 위치시킨다
그후에 모든 임의의 것을 담을 cacheController 를 코딩한다.이 페이지는 다음과 같은 역할을 한다
페이지 파일을 stream 으로 읽음 ㅡ> 읽은 stream 을 ViewData 를 통해 html페이지로 노출시킨다.
이제 페이지 준비는 끝났다. 해당 파일을 읽어올수 있도록 모든 컨트롤러를 상속시킬 대분류 컨트롤러를 만든다
예제 1에서 보았던 HandleUnknownAction 을 확장 하였다.
처음 foreach 문 까지는 view 폴더 내부의 aspx , html 을 찾기위함이다. 이곳에서 찾아진 파일이 있을경우 , 그에 맞는 처리를 행한다.
여 기서 특이점은 OriginalControllerName 프로퍼티인데 , 컨트롤러를 검색할때 최초에 호출한 컨트롤러를 담기위해 사용한다. 이는 이동한 컨트롤러를 정의할때 판별되므로, 컨트롤러 개체 생성시에 값을 넘겨주어야 한다. 이부분은 이후의 코드에서 다시 설명하도록 하겠다. 이후에 view 폴더에서 파일을 찾지 못했을경우는 , 이후에 정의된 폴더로 이동해서 추가적으로 html 파일을 찾는다. 굳이 이 옵션을 추가한 이유는 , 해당 파일은 디자이너에 의해 코딩된 순수 html 일 가능성이 높기 때문에 , 개발자가 코드를 관리하는 영역과 분리하고 싶었기 때문이다. 추가적으로 이러한 html 파일은 cdn 등으로 별도의 관리가 가능한것도 이유라고 할수 있겠다.
UnityControllerFactory.cs
public override IController CreateController(RequestContext context, string controllerName)
{
Type type = GetControllerType(context, controllerName);
/*
* 작성자 : 김시원
* 업데이트 : 10/03/08
* 내용 : 컨트롤러가 없을때 Cache 컨트롤러로 보낸다.
*/
if (type == null)
{
type = GetControllerType(context, "Cache");
//throw new InvalidOperationException(string.Format("Could not find a controller with the name {0}", controllerName));
}
IUnityContainer container = GetContainer(context);
Illusion.IllusionController resultController = (Illusion.IllusionController)container.Resolve(type);
resultController.OriginalControllerName = controllerName;
return (IController)resultController;
}
Unity Controller 를 이용해서 컨트롤러 객체를 생성하는 모습이다. 이곳에서는 두가지 작업을 행한다 첫번째는 컨트롤러 개체를 생성해서 생성된 개체가 없을때는 무조건 Cache 컨트롤러를 생성하는것이고 두번째는 처음 호출이 의도되었던 컨트롤러 명을 넘겨주는것이다. 이곳에서 컨트롤러를 넘기면 , 조금전 코드에서 보았던 코드가 올바르게 동작하는것을 확인할수 있을것이다.
global.asax.cs
private void RegisterRoutes(RouteCollection routes)
{
.........
/*등록되지 않은 엑션은 전부 여기서 처리*/
routes.MapRoute(
"CacheRoute1",
"{action}/{param1}", // 5자리 까지 가능
new { controller = "Cache", action = "Result", id = UrlParameter.Optional }
);
routes.MapRoute(
"CacheRoute2",
"{action}/{param1}/{param2}", // 5자리 까지 가능
new { controller = "Cache", action = "Result", id = UrlParameter.Optional }
);
routes.MapRoute(
"CacheRoute3",
"{action}/{param1}/{param2}/{param3}", // 5자리 까지 가능
new { controller = "Cache", action = "Result", id = UrlParameter.Optional }
);
routes.MapRoute(
"CacheRoute4",
"{action}/{param1}/{param2}/{param3}/{param4}", // 5자리 까지 가능
new { controller = "Cache", action = "Result", id = UrlParameter.Optional }
);
.......
}
라 우터에서 정의되지 않은 모든 라우팅 요청은 해당 라우터로 들어가게 된다. 모든 경로는 CacheController 의 Result Action 을 호출하도록 되어 있다.이 부분에서 우리는 우리의 컨트롤러를 동작시키고자 싶어할수도 있다. 그렇다면 그 경우 해당 컨트롤러를 하나씩 등록시켜 주면 된다.
Summary
ASP.NET MVC는 아직 완벽하지 않다. MVC는 있는 그대로 쓰기에는 우리가 구현해야 하는 요소가 너무 많은것이 사실이다. 그러나 그 댓가로 개발자는 좀더 가볍고 , SEO 친화적인 결과물을 얻을수 있다. 이에 더해 asp.net 에 있던 개념을 하나라도 더 사용할수 있다면 , 더욱더 많은 사용자가 asp.net mvc 에 도전할것이다. 결론적으로 말하면 asp.net MVC 는 매력이 있으며 도전할 가치가 있다. 이 글이 asp.net mvc 베이스의 개발에 조금이나마 도움이 되었으면 하는 바램이다.
저희가 주변에서 흔히 접할 수 있는 .NET 트랙의 C#, ASP.NET MVC, WCF 세미나도 재미있는 내용으로 알차게 준비를 했습니다. 그리고 흔히 접하기 힘든 Enterprise 트랙과 Native 트랙도 가뭄의 단비와도 같은 트랙입니다.
원래 웹 타임 교육센터는 각 자리에 교육 PC 가 비치된 환경으로 책상 위에 컴퓨터 모니터와 키보드, 마우스가 비치되어 있었으나, 세미나 참가자의 쾌적한 환경을 위해 오전 10시부터 책상 위의 모니터를 치우는 작업을 하였답니다. 그리하여 아래와 같이 깨끗한 책상이 되었군요^^
이 날, 저희 팀에서 세미나를 진행하기 위해 많은 요원들이 일찍 모여 준비하고 세미나를 준비하고 접수를 받고 있습니다.
즐거운 토요일 주말에 비가 오다마다를 반복하는 짓궂은 날씨에서 불구하고 세미나에 참석해 주신 여러분들을 위해 푸짐한 경품도 준비를 하였습니다.
경품은
엄준일 ALM MVP 님의 MSDN Subscription 1년 구독권 2매
김병진 ALM MVP 님의 무선 마우스 3개, 무선 키보드 3개
Microsoft Korea 의 강성재 차장님과 Visual Studio 2010 에서 Visual Studio 2010 Professional 정품 1개
누가 경품을 가져갈 진 모르겠지만, 기뻐하실 분들을 생각하면 벌써부터 가슴이 설레입니다.^^
오홋.. 드디어 세미나가 오후 2시부터 시작 되었습니다.
.NET Track : [1] 그것이 알고싶다 - C# 4.0의 변화, 그 진실은 무엇인가. 희망인가? 또 다른 혼란인가? - 강보람 C# MVP
재미있는 블로그 아티클과 세미나 진행으로 이번 세미나에서도 유감없이 C# 4.0 의 내용을 재미있게 풀어주셨습니다. 현재는 집필 활동에 주력하고 계시는군요.
PDC 2008에 울려 퍼진 C# 4.0의 소식. 그 소식을 듣고 많은 사람들은 기대와 혼란을 가지게 되었다. C#은 분명히 정적 언어인데, 동적 언어에나 있을 법한 기능을 추가한다니? 이제 와서 뒷북일 수도 있는 C# 4.0의 변화에 대한 진실, 그 마지막 시리즈가 이제 시작된다. :)
.NET Track : [2] 좋은 프레임워크 있으면 소개시켜줘 - ASP.NET MVC - 박세식
ASP.NET MVC 를 실무적으로 사용하기 위해 정말 쉽고 재미있는 내용으로 채워졌습니다.
그 동안 아주 미묘하게 아쉬웠던 ASP.NET. 가려운 곳을 긁어줄 대안의 프레임워크가 나타났다. 웹 개발자들 한테 참~ 좋은데, 웹 개발자들 한테 정말 좋은데, 이걸 말로 그냥 할 수 없어서, 이번 기회에 소개한다.
.NET Track : [3] Beginnig WCF - 오태겸
"WCF 는 어렵다!!!" 라는 선입관을 깨주신 오태겸 님의 세미나를 들으신다면 'WCF 는 쉽구나^^' 라고 느끼실 겁니다.
WCF는 서비스 지향 프로그래밍을 위해 마이크로소프트에서 개발 및 지원하는 기반 기술이며, 기존의 .NET 웹 서비스에 비해 유연성과 확장성이 뛰어나 최근 많은 관심을 받고 있습니다. 본 세션에서는 WCF가 무엇인지? 어떤 장점이 있는지? 그리고, WCF 를 이용하기 위해선 무엇이 필요한지? 에 대해 함께 알아보고, 마지막으로, WCF의 활용 예를 알아보도록 하겠습니다.
Native Track : [1] Visual Studio 2010 : C++0x와 Windows 7 - 최성기
NCsoft 에 근무하시는 최성기님의 세션입니다. 여러 매체를 통해서 C++0x 와 Windows 7 의 기술을 전파하셨고, Windows 7 with C++0x 에 경험도 풍부하십니다.
그 동안 .NET 영역으로 적잖이 편중되었던 Visual Studio의 버전업에 비해 이번 2010 버전에서는 Native Code 개발환경에서도 많은 변화가 찾아왔다. C++0x 표준 반영에 의한 문법의 변화, 새로운 라이브러리 제공(Concurrency Runtime Library), Windows 7의 최신 기능들을 제어하기 위한 SDK의 업데이트 등이 그것이다. 본 세션을 통해 C++의 문법적인 변화와 Windows 7 기능 구현을 위한 SDK의 업데이트 사항들을 정리해본다.
Native Track : [2] 비주얼 스튜디오 2010 의 Concurrency Runtime 을 이용한 멀티 코어 제대로 활용하기 - 임준환
임준환님은 좋은 내용을 여러분들에게 전해드리려고 일찍부터 나오셔서 리허설도 진행하신 투혼(^^) 을 발휘해 주셨답니다.
요즘 가정의 PC 에 멀티 코어 프로세서가 많이 보급되어 있습니다. 하지만 실제로 PC 에 설치된 코어들을 모두 사용하는 애플리케이션들은 많지 않습니다. 이렇게 낭비되는 자원을 C++ 개발자가 쉽게 사용할 수 있도록 도와주는 Concurrency Runtime 을 비주얼 스튜디오 2010에서 제공합니다. 이 Concurrency Runtime 을 어떻게 시작해야 할지 알아보겠습니다.
Native Track : [3] DirectX11 을 기다리며… - 조진현
클라이언트 게임 프로그램을 개발하고 계신 조진현님의 DX11 에 대한 세션입니다. KGC 등 여러 세미나 경험을 가지고 계시지요.
조금씩 정보가 공개되면서 많은 변화를 예고하고 있는 DirectX11 에 대해서 살펴 볼 것입니다. 특히나 Tessellation, DirectCompute, Multi-threading 을 위한 기본 개념과 작업들에 대해서 체크해 볼 것입니다.
Enterprise Track : [1] VS Team Foundation Server 2010 의 새로운 변화 - 김병진 ALM MVP
Team Foundation Server 2010 을 이용하여 컨설팅과 실무에서 많은 경험을 토대로 세션을 진행하였습니다. 웹 타임 교육센터와 큰 기업 등에서 교육을 했던 경험도 있으시답니다.
Visual Studio Team Foundation Server 2010의 혁신적인 변화와 개선 부분, 프로젝트 및 형상관리와 Agile의 Scrum 을 이용한 방법론을 알아보고, 단지 소스 체크인/아웃만 하는 Visual Source Safe에서 업그레이드 하는 방법에 대하여 알아봅니다.
Enterprise Track : [2] 소프트웨어 품질 향상을 위한 다양한 테스트 기법 - 엄준일 ALM MVP
소프트웨어 개발의 이전의 사례를 바탕으로 테스팅의 중요성과 그 기법과 방법을 공부하면서 경험한 내용을 전달하였습니다. 소프트웨어 개발 프로세스 중 테스팅의 매력에 푹 빠져 있답니다.
소프트웨어는 개발 및 릴리즈 과정까지 수 많은 과정을 겪는데, 소프트웨어가 점진적으로 진화함에 따라 결함의 발생률이 증가합니다. 이를 개선하기 위한 테스트 기법 중 단위 테스트, WhiteBox 테스트, 화면 테스트, 성능 테스트, 부하 테스트 등 다양한 테스트 기법을 알아봅니다.
Enterprise Track : [3] SharePoint 2010 Enterprise 솔루션 개발 - 정홍주 SQL Server MVP
SharePoint 2010 으로 엔터프라이즈 환경에 필요한 실무 사례와 경험을 이 세션에서 전달하였습니다. 웹 타임교육 센터의 전임 강사이십니다.^^
SharePoint 2010은 기업 협업 플랫폼으로 개발자들은 VS 2010을 이용하여 더 생산성 있고 효과적인 SharePoint 2010 개발을 진행할 수 있습니다. 본 세션에서는 SharePoint 2010 개발에 대한 가장 필요한 내용을 구체적으로 알아보며 이를 통해 가장 많은 요구사항에 대한 실무 솔루션을 구성하는 방법에 대한 내용을 알아보겠습니다.
드디어 경품 추첨 시간
더 많은 분들에게 경품을 드리고 싶었지만, 자비로 경품을 준비하느라 많은 분들에게 드리지 못해서 아쉽습니다.^^ 다음엔 더 비싸고(^^), 풍성하고, 유용한 경품을 많이 준비할게요^^
경품은 이 날, 세미나를 위해 발표해 주신 스피커 분들께서 추첨을 통해 전달해 드렸습니다.
MSDN Subscription 1년 구독권 2매
이 경품으로 MSDN 을 통해서 Windows 7, Windows Server 2008, SQL Server 2008, Team Foundation Server 2010, Visual Studio 2010 Ultimate, Office 2010 등의 제품을 모두 모두 사용할 수 있답니다.
무선 키보드 3개
무선 키보드 3개
대망의 Visual Studio 2010 Professional 정품 1개 ^^
Visual Studio 2010 Professional 정품은 Microsoft Korea 의 강성재 차장님과 Microsoft Korea 의 Visual Studio 제품 관련 팀에서 후원해 주신 오늘 쵝오의 경품입니다. 강성재 차장님께서 직접 추첨을 해 주셨습니다.
Visual Studio Camp #1 을 마치며
저희 팀의 많은 분들께서 이 날 행사와 좋은 내용의 세미나를 준비해 주셔서 무사히 세미나를 마치게 되었습니다. Native 트랙은 C++ 개발자와 게임 개발자에게 정말 단비와도 같은 세미나였고, 많은 분들의 좋은 피드백을 받았습니다. .NET 트랙과 Enterprise 트랙도 여러 분들의 기술 트랜드가 뒤쳐지지 않도록 부지런히 기술 전파를 위해 팀 블로그를 통해 노력했고, 이번 세미나를 통해 저희 Visual Studio 팀도 매우 기뻤고, 더 많은 용기를 얻은 것 같습니다.
특히 저희 한국 Visual Studio 팀에서는 아무도 먼저 가보지 않은, 아무도 접해보지 않은 많은 기술과 트랜드의 홍수 속에서 항상 긴장감을 늦추지 않고 노력해 주셔서 오늘의 세미나가 있지 않았나 생각합니다. 앞으로도 저희 Visual Studio Korea 팀의 많은 응원 부탁 드립니다.
더 좋은 내용을 블로그 내용과 세미나, 각종 매체를 통해 여러분들에게 다시 찾아 뵙도록 하겠습니다.
앞선 시간을 통해서 ID3DXPatchMesh 를 이용하면
간단하게 테셀레이션이 적용된 메시를 만들 수 있음을 언급했었습니다.
실제로 D3DX 유틸리티 클래스들이 테셀레이션을 손쉽게 적용할 수 있도록 구비가 되어있습니다.
그렇다는 것은 실제로는 DirectX 내부적으로 코어한 API가 있다는 얘기입니다.
테셀레이션과 관련한 DirectX 에서 코어한 API가 바로
IDirect3DDevice9::DrawTriPatch() 와 IDirect3DDevice9::DrawRectPatch() 입니다.
API 이름에서 쉽게 이해할 수 있듯이 전자는 삼각형과 관련한 것이고 후자는 사각형과 관련한 것입니다.
두 함수의 원형은 다음과 같습니다.
그런데 조금 생소한 구조체 정보를 함수 인자로 받습니다.
이 두 API들은 함수 이름에서도 알 수 있듯이 실제로 렌더링을 수행하는 API 입니다.
테셀레이션을 위해서는 테셀레이션을 위한 정보들이 존재해야 합니다.
이들에 대한 설정 작업이 이루어져야 하는데,
이를 위한 구조체가 세번째 인자인 D3DTRIPATCH_INFO와 D3DRECTPATCH_INFO 입니다.
사각형과 관련한 작업은 삼각형과 유사하기 때문에 지금부터는 삼각형에 국한에서 글을 진행하겠습니다.
이 구조체는 버텍스 버퍼처럼 오프셋과 버텍스 갯수를 먼저 설정합니다.
D3DBASISTYPE 은 고차원 패치( high-order patch )의 기본 타입을 설정합니다.
삼각형의 경우에는 D3DBASIS_BEZIER 만 설정할 수 있습니다.
D3DDEGREETYPE 는 고차원 패치의 차수 정도를 설정하게 됩니다.
즉, 곡선을 표현하는 방정식의 차수를 표현하는데,
높은 차수를 선택할 수록 당연히 연산량이 많아질 것입니다.
이들에 대한 종류는 다음과 같습니다.
종류
버텍스 갯수
D3DDEGREE_CUBIC
10 ( 3차 방정식 )
D3DDEGREE_LINEAR
3 ( 1차 방정식 )
D3DDEGREE_QUADRATIC
N/A ( 지원되지 않음 ) ( 2차 방정식 )
D3DDEGREE_QUINTIC
21 ( 4차 방정식 )
아래의 그림은 Cubic Bézier 방식의 삼각형 패치를 보여주고 있습니다.
중간 중간에 생성된 정점을 기준으로 테셀레이션 작업이 수행될 것입니다.^^
이런 테셀레이션과 관련한 API들이 DirectX9 에 있었지만, 사실 거의 사용되지는 못했습니다.
왜냐하면, 정말이지 많은 연산을 필요로 하기 때문이겠죠? ^^
즉, DirectX9의 테셀레이션 작업은 소프트웨어적으로 에뮬레이션 되는 테셀레이션입니다.
안녕하세요. Visual C# MVP 남정현입니다. 가을을 재촉하는 비가 여기저기 내리면서 무더웠던 날씨가 한풀 꺾이는듯 합니다. 오늘은 Windows Azure Update와 Side-by-Side로 SQL Azure Update에 대한 이야기를 진행해보고자 합니다. :-)
SQL Azure의 주요 기능이 우리가 생각하는것과 같이 상당한 수준에 있기 때문이거나, SQL Azure에 대해서 아직까지 많은 내용이 전달되지 않았기 때문일 수 있지만 SQL Azure 만의 고유한 기능을 다루는 섹션도 필요하다고 생각하여 진행하고 있으니 역시 많은 Feedback을 부탁드립니다.
Dynamic Management View란?
Dynamic Management View (이하 DMV)는 SQL Azure Team Blog에서 SQL Azure Service Update 1과 함께 제공한 새로운 기능으로, 이 글을 작성하는 현 시점에서는 매우 요긴하게 사용될 수 있고, 데이터베이스에 대한 정확한 사용량 추이를 파악할 수 있는데 도움을 주는 관리 API의 일종입니다. DMV는 시스템 뷰의 일종으로 다음과 같이 제공됩니다.
sys.dm_exec_connections: 최근에 접속한 모든 클라이언트들의 정보들을 조회합니다.
sys.dm_exec_requests: 최근에 실행된 Query의 상태와 실행 정보들을 조회합니다.
sys.dm_exec_sessions: 현재 연결된 클라이언트 및 세션에 대한 정보들을 조회합니다.
sys.dm_tran_database_transactions: 데이터베이스 수준의 트랜잭션들에 대한 정보를 조회합니다.
sys.dm_tran_active_transactions: 현재 활성화된 트랜잭션들에 대한 정보를 조회합니다.
sys.dm_db_partition_stats: 데이터베이스의 파티션 상태 및 각 파티션 별 사용량 정보를 조회합니다.
DMV의 데이터를 살펴보는 방법
DMV의 데이터는 관리를 위한 목적으로 활용하기에 알맞습니다. 또한, 데이터베이스 관리자가 별도의 응용프로그램을 사용하지 않고 손쉽게 상태를 파악할 수 있기 위해서는 Codename: Houston과 같은 SQL Azure 전용 데이터베이스 관리 도구를 활용하는 것이 편리합니다. (이 도구를 사용하면 별도의 방화벽 설정을 추가함으로 인해서 발생할 수 있는 노출 영역의 확대를 회피할 수 있기 때문입니다.)
Service Update 1 이후부터는 이미 만들어진 데이터베이스의 상품 크기를 동적으로 관리 차원에서 변동을 줄 수 있습니다. 다음의 Query를 관리자 권한으로 SQL Azure Database에 접속한 후 실행하면 원하는 데이터베이스 크기를 설정하고 그 이후부터 변경된 데이터베이스 인스턴스 크기에 따른 요금을 자동으로 과금받을 수 있습니다.
그리고 SQL Azure에서 세션 타임 아웃은 현재 30분으로 연장된 상태이며, 기본적으로 SQL Server 2008 R2에서 제공하는 장기 실행 트랜잭션 (혹은 교차 잠금 상태의 트랜잭션)을 찾아내서 정리하는 알고리즘이 좀 더 최적화되어있다는 것이 Service Update 1 이후의 변경 사항임을 알아두시면 유용할 것 같습니다.
모두 이렇게 아무것도 없는 상태로 프로젝트를 만들어 주시기 바랍니다.
지금부터 아무 것도 없는 화면에서 동적으로 컨트롤들을 생성해서 배치할 것입니다.
즉, 애플리케이션 실행 중에 컨트롤을 만들 것입니다.
가장 단순한 것이 버튼 컨트롤일 것입니다.
동적으로 버튼 컨트롤을 생성하기 위해서는 당연한 얘기지만,
폰 페이지가 생성되는 시점에 컨트롤을 만들어주는 코드를 추가해 주면 됩니다.
*.cs 파일을 열어서 다음과 같이 내용을 추가해 줍니다.
이들은 블렌드 툴에서 컨트롤 속성을 설정하는 부분을,
프로그램 소스 상에서 속성을 제어하는 부분입니다.
속성 정보는 중에 제가 현재 이벤트를 연결한 부분이 있습니다.
pStartCtrl.Click += OnStartButtonClick 와 pEndCtrl.Click += OnEndButtonClick 부분입니다.
이벤트도 코드 상에서 이렇게 연결해 줄 수 있습니다.
현재 예제에서는 간단히 메시지 박스를 출력하는 것은 마무리 했습니다.
대부분의 컨트롤들은 위의 방법과 같이 동적으로 생성해 줄 수 있습니다.
여기까지가 예제 샘플 작업의 끝입니다.^^
그런데 한가지 생소한 부분이 있을 것이라 생각을 합니다.
바로 ContentPanel.Children.Add( XXX ) 부분입니다.
어딘가에 현재 우리가 생성한 컨트롤들을 추가해주고 있습니다.
과연 이 패널은 무엇일까요?
프로그램 작성을 마치고, 다시 디자인 폼 수정 화면으로 돌아옵니다.
위의 화면을 확인해 보실 수 있겠죠?
앞에서 언급한 낯선 이름이 보입니다.
'ContentPanel' 보이시죠?
여기에 버튼을 하나 만들어 봅니다.
이번에는 소스 코드 레벨이 아니라, 디자인 폼 상에서 버튼을 바로 배치해 봅니다.
디자인 폼 상에서 버튼을 배치시키면,
바로 ContentPanel 의 하위 계층에 추가되어지는 것을 확인할 수 있습니다.
즉, 앞선 언급했던 ContentPanel.Children.Add( XXX ) 소스 코드는 바로
이 작업을 해준 것입니다.
조금 더 정확히 얘기하자면,
윈도우폰7의 레이아웃은 크게 TitlePanel과 ContentPanel 로 나눠져 있습니다.
그래서 이들 패널에 컨트롤을 추가했던 것입니다.
패널( Panel )이 무엇인지 모르시겠다구요?
패널은 이후의 시간에 언급될 기회가 있을 것입니다만,
지금은 단순히 컨트롤들을 그룹화 시켜서
자동으로 관리시켜주는 것 정도로 생각하시면 될 것입니다.^^
아마도 이 글이 Asynchronous Agents Library( 이하 AAL ) 에 관련된 마지막 글이라고 생각이 됩니다. 더 추가적으로 적을 내용이 생기면 더 적어 보도록 하겠습니다.
이전 글까지 AAL 에서 제공하는 message block 들을 알아보았지만, 제공되는 것들이 마음에 들지 않는 분들은 직접 message block 을 만들어 사용할 수 있습니다.
직접 message block 을 만들어 사용하는 것이 그리 간단하지만은 않기 때문에 이번 글이 좀 길어질 것 같습니다. 천천히 읽어주시기 바랍니다.
Message block 의 종류와 인터페이스
우선 message block 정의에 앞서 message block 의 종류에 대해서 알아보는 것이 좋겠습니다. 이미 이전 글들에서 본 제공되는 message block 들을 보시면서 아셨겠지만, 한 번 더 짚고 넘어가도록 하겠습니다.
Message 를 보내는 역할을 하는 message block 을 source block 이라 하고, message 를 받는 역할을 하는 역할을 하는 message block 을 target block 이라고 합니다.
이 두 가지 종류의 block 들의 기본적인 행동들을 알리기 위해서 source block 들은 ISource 인터페이스를, target block 들은 ITarget 인터페이스를 상속받아 인터페이스 메소드들을 정의해야 합니다.
Message block 의 기본 클래스들
위에서 언급한 ISource 인터페이스나 ITarget 인터페이스를 직접 상속받아 정의해도 되지만, AAL 에서는 그 인터페이스를 상속받아 정의하기 쉽도록 해주는 기본 클래스 3 가지를 제공합니다.
source_block – ISource 인터페이스를 상속받고, 다른 block 에 message 를 보냅니다.
target_block – ITarget 인터페이스를 상속받고, 다른 block 으로부터 message 를 받습니다.
propagator_block – ISource 인터페이스와 ITarget 인터페이스를 상속받고, 다른 block 들과 message 를 보내고 받습니다.
AAL 에서는 사용자 정의 message block 을 정의할 때, 인터페이스를 직접 상속받기 보다는 위의 기본 클래스들을 상속받아 정의하는 것을 권고하고 있습니다.
연결 관리 및 message 관리를 위한 템플릿 매개변수에 사용할 수 있는 클래스 템플릿
위의 3가지 기본 클래스들은 클래스 템플릿으로, message block 들간의 연결을 어떻게 관리할 것인지, message 들을 어떻게 관리할 것인지에 대한 정보를 템플릿 매개변수를 통해 지정할 수 있습니다.
AAL 은 연결 관리를 위한 2가지의 클래스 템플릿을 제공하고 있습니다.
single_link_registry – source 나 target 의 하나의 연결만 허용.
multi_link_registry – source 나 target 의 여럿의 연결을 허용.
예를 들면, AAL 에서 제공하는 transformer 는 출력을 하나의 연결만 허용하는 single_link_registry 를 사용하고, 입력은 여럿의 연결을 허용하는 multi_link_registry 를 사용하고 있습니다.
template<class _Input, class _Output>
class transformer : public propagator_block<single_link_registry<ITarget<_Output>>, multi_link_registry<ISource<_Input>>>
[ 코드1. transformer 에서 사용되는 single_link_registry 와 multi_link_registry ]
또한 message 관리를 위한 하나의 클래스 템플릿을 제공합니다.
ordered_mesage_processor – message 를 받는 순서대로 처리하도록 허용.
재정의해야 할 멤버 함수들
위에 말한 기본 클래스들을 상속한 클래스는 다음과 같은 멤버 함수를 재정의해야 합니다.
source_block 을 상속한 클래스가 재정의해야 할 멤버 함수들
propagate_to_any_targets()
accept_message()
reserve_message()
consume_message()
release_message()
resume_propagation()
propagate_to_any_targets()
입력 message 나 출력 message 를 비 동기 또는 동기적으로 처리하기 위해 런타임으로부터 호출됩니다.
target block 은 message 를 제공받을 때와 message 를 나중에 사용하기 위해 예약해야 할 때 reserve_message() 를 호출합니다.
target block 은 하나의 message 를 예약한 후에, message 를 사용하기 위해 consume_message() 를 호출하거나 예약을 취소하기 위해 release_message() 를 호출할 수 있습니다.
consume_message() 는 accept_message() 와 함께 사용되어 message 의 소유권을 보내거나 message 의 복사본을 보내도록 할 수 있습니다.
예를 들어, unbounded_buffer 와 같은 message block 은 오직 하나의 target 에만 message 를 보냅니다. 즉, message 의 소유권을 target 에 보냅니다. 그렇기 때문에 보낸 후, message block 내에는 보낸 message 가 남아있지 않습니다.
반면에 overwrite_buffer 와 같은 message block 은 각 연결된 target 에 각각 message 를 제공합니다. 즉, message 의 복사본을 보냅니다, 그래서 보낸 후, message block 내에 보낸 message 가 여전히 존재하게 됩니다.
target block 이 예약된 message 를 사용하거나 취소한 후에, 런타임은 resume_propagation() 을 호출합니다. resume_propagation() 은 큐( queue )의 다음 message 를 시작으로 message 의 전달을 계속해서 진행시킵니다.
target_block 을 상속한 클래스가 재정의해야 할 멤버 함수들
propagate_message()
send_message() ( option )
propagate_message(), send_message()
런타임은 다른 block 으로부터 현재의 block 으로 message 를 비 동기적으로 받기 위해 propagate_message() 를 호출합니다.
send_message() 는 비 동기적이 아니라 동기적으로 message 를 보내는 것을 제외하면 propagate_message() 와 비슷합니다.
기본적으로 send_message() 의 정의는 모든 입력 message 를 거부하도록 구현되어 있습니다.
만약 message 가 target block 에 설정된 필터 함수를 통과하지 못하면 send_message() 나 propagate_message() 를 호출하지 않습니다.
propagator_block 을 상속한 클래스가 재정의해야 할 멤버 함수들
propagator_block 은 source_block 과 target_block 의 특징을 모두 상속하므로 두 클래스에 대해 재정의해야 할 멤버 함수들을 모두 재정의해야 합니다.
예제: priority_buffer
priority_buffer 는 우선 순위를 기준으로 순서를 정하는 사용자 정의 message block 입니다. 여럿의 message 들을 받을 때 우선 순위대로 처리할 때 사용하기 유용합니다.
Message 큐를 가지고 있고, source 와 target 의 역할을 하며, 여럿의 source 와 여럿의 target 을 연결할 수 있기 때문에 unbounded_buffer 와 비슷합니다.
그러나 unbounded_buffer 는 message 를 받는 순서를 기반으로 message 를 전달한다는 것이 다릅니다.
priority_buffer 는 우선 순위와 데이터를 같이 전달해야 하므로 우선 순위 타입과 데이터의 타입을 포함하는 tuple 타입의 message 를 전달합니다.
priority_buffer 는 2개의 message 큐를 관리합니다. 입력 message 를 위한 std::priority_queue 와 출력 message 를 위한 std::queue 입니다.
priority_buffer 의 정의를 위해 propagator_block 을 상속하고 7개의 멤버 함수들을 정의해야하고, link_target_notification() 과 send_message() 를 재정의해야 합니다.
또한 2개의 public 멤버 함수인 enqueue() 와 dequeue() 를 정의합니다. private 멤버 함수로 propagate_priority_order() 를 정의합니다.
코드
#include <agents.h>
#include <queue>
// 우선 순위를 비교할 비교 함수 객체.
namespace std
{
template< class Type, class PriorityType >
struct less< Concurrency::message< tuple< PriorityType, Type > >* >
{
typedef Concurrency::message< tuple< PriorityType, Type > > MessageType;
bool operator()( const MessageType* left, const MessageType* right ) const
{
// message 가 tuple 이므로 get() 을 사용.
return ( get< 0 >( left->payload ) < get< 0 >( right->payload ) );
}
};
template< class Type, class PriorityType >
struct greater< Concurrency::message< tuple< PriorityType, Type > >* >
{
typedef Concurrency::message< tuple< PriorityType, Type > > MessageType;
bool operator()( const MessageType* left, const MessageType* right ) const
{
return ( get< 0 >( left->payload ) > get< 0 >( right->payload ) );
}
};
}
namespace Concurrency
{
// Type - 데이터 타입, PriorityType - 우선 순위 타입, PredicatorType - 비교 함수 객체
// source 와 target 역할을 하므로 propagator_block 을 상속받고, 다중 입력 연결과 다중 출력 연결을 허용하므로 모두 multi_link_registry 를 사용.
template< class Type, typename PriorityType = int, typename PredicatorType = std::less< message< std::tuple< PriorityType, Type > >* > >
class priority_buffer
: public propagator_block< multi_link_registry< ITarget< Type > >, multi_link_registry< ISource< std::tuple< PriorityType, Type > > > >
{
public:
~priority_buffer()
{
// 연결을 모두 해제하기 위해 propagator_block 의 remove_network_links() 를 사용.
this->remove_network_links();
}
priority_buffer()
{
// source 와 target 을 초기화 하기 위해 propagator_block 의 initialize_source_and_target() 을 사용.
this->initialize_source_and_target();
}
priority_buffer( filter_method const& filter )
{
this->intialize_source_and_target();
// 필터 함수를 등록하기 위해 target_block 의 register_filter() 를 사용.
this->register_filter( filter );/
}
priority_buffer( Scheduler& scheduler )
{
this->initialize_source_and_target( &scheduler );
}
priority_buffer( Scheduler& scheduler, filter_method const& filter )
{
this->initialize_source_and_target( &scheduler );
this->register_filter( filter );
}
priority_buffer( ScheduleGroup& schedule_group )
{
this->initialize_source_and_target( nullptr, &schedule_group );
}
priority_buffer( ScheduleGroup& schedule_group, filter_method const& filter )
{
this->initialize_source_and_target( nullptr, &schedule_group );
this->register_filter( filter );
}
// 공개 멤버 함수들.
bool enqueue( Type const& item )
{
return Concurrency::asend< Type >( this, item );
}
Type dequeue()
{
return Concurrency::receive< Type >( this );
}
protected:
// message 처리하기 위해 런타임으로 부터 호출됨. message 를 입력 큐에서 출력 큐로 이동하고, 전달.
virtual void propagate_to_any_targets( message< _Target_type >* )
{
message< _Source_type >* input_message = nullptr;
{
critical_section::scoped_lock lock( this->input_lock );
// 보낼 message 를 우선 순위 큐에서 꺼냄.
if( this->input_messages.size() > 0 )
{
input_message = this->input_messages.top();
this->input_messages.pop();
}
}
if( nullptr != input_message )
{
// 입력된 message 는 우선 순위와 데이터를 같이 가지고 있으므로 데이터만 가진 message 로 가공하여 출력 큐에 넣음.
message< _Target_type >* output_message = new message< _Target_type >( get< 1 >( input_message->payload ) );
this->output_messages.push( output_message );
delete input_message;
if( this->output_messages.front()->msg_id() != output_message->msg_id() )
{
return;
}
}
// message 보내기.
this->propagate_priority_order();
}
// target block 이 message 를 받기 위해 호출함. 이 코드에서는 출력 큐에서 message 를 제거해서 소유권을 이전.
virtual message< _Target_type >* accept_message( runtime_object_identity msg_id )
{
message< _Target_type >* message = nullptr;
if( !this->output_messages.empty() && this->output_messages.front()->msg_id() == msg_id )
{
message = this->output_messages.front();
this->output_messages.pop();
}
return message;
}
// target block 이 제공된 message 를 예약하기 위해 호출. 이 코드에서는 전달 가능 여부를 확인.
virtual bool reserve_message( runtime_object_identity msg_id )
{
return ( !this->output_messages.empty() && this->output_messages.front()->msg_id() == msg_id );
}
// target block 이 제공된 message 를 사용하기 위해 호출. 이 코드에서는 message 전달.
virtual message< Type >* consume_message( runtime_object_identity msg_id )
{
return this->accept_message( msg_id );
}
// target block 이 예약된 message 를 취소하기 위해 호출. 이 코드에서는 아무 역할을 하지 않음.
virtual void release_message( runtime_object_identity msg_id )
{
if( this->output_messages.empty() || this->output_messages.front()->msg_id() != msg_id )
{
throw message_not_found();
}
}
// 예약된 message 처리 후, 계속해서 진행.
virtual void resume_propagation()
{
if( this->output_messages.size() > 0 )
this->async_send( nullptr );
}
// 새로운 target 이 연결되었음을 알림.
virtual void link_target_notification( ITarget< _Target_type >* )
{
// 이미 예약된 message 가 있으면 전달하지 않음.
if( this->_M_pReservedFor != nullptr )
return;
// message 보내기.
this->propagate_priority_order();
}
// 비 동기적으로 전달. propagator_block 의 propagate() 에 의해 호출됨.
virtual message_status propagate_message( message< _Source_type >* message, ISource< _Source_type >* source )
{
message = source->accept( message->msg_id(), this );
if( nullptr != message )
{
{
critical_section::scoped_lock lock( this->input_lock );
this->input_messages.push( message );
}
this->async_send( nullptr );
return accepted;
}else
return missed;
}
// 동기적으로 전달. propagator_block 의 send() 에 의해 호출됨.
virtual message_status send_message( message< _Source_type >* message, ISource< _Source_type >* source )
{
message = source->accept( message->msg_id(), this );
if( nullptr != message )
{
{
critical_section::scoped_lock lock( this->input_lock );
this->input_messages.push( message );
}
this->sync_send( nullptr );
return accepted;
}else
return missed;
}
private:
// message 를 보내는 함수.
void propagate_priority_order()
{
// 이미 예약된 message 가 있으면 전달하지 않음.
if( nullptr != this->_M_pReservedFor )
return;
// 출력 큐의 모든 message 를 보냄.
while( !this->output_messages.empty() )
{
message< _Target_type >* message = this->output_messages.front();
message_status status = declined;
// 연결된 target 을 순회하면서 message 를 전달.
for( target_iterator iter = this->_M_connectedTargets.begin();
nullptr != *iter;
++iter )
{
ITarget< _Target_type >* target = *iter;
status = target->propagate( message, this );
if( accepted == status )
break;
if( nullptr != this->_M_pReservedFor )
break;
}
if( accepted != status )
break;
}
}
private:
std::priority_queue<
message< _Source_type >*,
std::vector< message< _Source_type >* >,
PredicatorType > input_messages;
std::queue< message< _Target_type >* > output_messages;
critical_section input_lock;
private:
priority_buffer const& operator=( priority_buffer const& );
priority_buffer( priority_buffer const& );
};
}
[ 코드2. priority_buffer 구현 ]
재정의하는 함수들이 많고, 어디서 어떻게 호출되는지 알기 어려울 수 있습니다. 간단하게 설명하자면 외부로부터 보내는 함수, send() 또는 asend() 에 의해서 message 가 전달될 경우, target block 의 역할을 하게 됩니다. 이 때는 propagate_message() 나 send_message() 중 하나가 호출됩니다.
반대로 외부로부터 받는 함수, receive() 또는 try_receive() 에 의해서 message 를 전달해야 하는 경우, source block 의 역할을 하게 되고, 나머지 함수들이 호출되게 됩니다.
이 예제로 완벽하게는 아니더라도 어떻게 사용자 정의 message block 을 구현해야 하는지 아셨을 것이라 생각됩니다.
사용 코드
#include <ppl.h>
#include <iostream>
#include "priority_buffer.h"
using namespace Concurrency;
using namespace std;
int main()
{
priority_buffer< int > buffer;
parallel_invoke(
[ &buffer ] { for( unsigned int i = 0; i < 25; ++i ) asend( buffer, make_tuple( 1, 12 ) ); },
[ &buffer ] { for( unsigned int i = 0; i < 25; ++i ) asend( buffer, make_tuple( 3, 36 ) ); },
[ &buffer ] { for( unsigned int i = 0; i < 25; ++i ) asend( buffer, make_tuple( 2, 24 ) ); } );
for( unsigned int i = 0; i < 75; ++i )
{
wcout << receive( buffer ) << L' ';
if( ( i + 1 ) % 25 == 0 )
wcout << endl;
}
}
[ 코드3. priority_buffer 를 사용하는 예제 ]
parallel_invoke() 를 사용하기 때문에 순서를 입력되는 순서를 보장할 수 없습니다. 하지만 receive() 를 사용하여 message 를 출력해 보면 우선 순위가 큰 순서대로 출력되는 것을 알 수 있습니다.
[ 그림1. priority_buffer 를 사용하는 예제 결과 ]
마치는 글
사용자 정의 message block 을 구현하는 것을 마지막으로 AAL 에 관련된 글을 마무리합니다. AAL 에 관련해 새로운 소식이 있으면 그 때 다시 AAL 에 관련된 글을 작성하도록 하겠습니다.
다음 글은 AAL 을 포함하는 Concurrency Runtime 에서 제공하는 작업 스케줄러에 대해서 작성해 볼 예정입니다. 작업 스케줄러는 AAL 의 message block 과 함께 유용하게 사용할 수 있으므로 AAL 에 관심이 많으신 분들도 기대하셔도 좋을 것 같습니다.
Windows Azure Platform에서 사용하는 서비스의 유형 중에서, BLOB, Table, Queue를 다루기 위해서는 Windows Azure Storage 서비스를 신청하고 여기에 직접 접근하는 API를 호출해야 하는 데, 이 때 최초에 사용자 인증을 완료하기 위하여 제일 먼저 활용하는 클래스가 바로 CloudStorageAccount 클래스입니다. 이 클래스에는 다음과 같은 멤버들을 포함하고 있습니다.
[static] FromConfigurationSetting: Windows Azure의 Worker - 또는 - Web Role의 개별 환경 설정 파일에 들어있는 설정 파일로부터 Storage 연결 문자열을 가져와 CloudStorageAccount 객체를 생성하는 Factory Method입니다.
[static] Parse: 형식과 규칙에 맞는 연결 문자열이 있을 경우 문자열을 해석하여 CloudStorageAccount 객체를 생성하는 Factory Method입니다. 만약 분석에 실패할 경우 예외를 발생시킵니다.
[static] SetConfigurationSettingPublisher: Windows Azure Management API 등을 통해서 동적으로 설정 내역이 바뀌었을 경우 이를 전달하고 시스템 내부에 반영하는 통지 이벤트 처리기를 등록합니다.
[static] TryParse: 기본적으로 Parse 메서드와 하는 일이 동일하지만, 예외를 직접 던지지 않고 실패할 경우 반환값으로 false를 반환하도록 설계되어있어 조건문 만으로 예외 상황을 처리할 수 있도록 도와주는 도우미 메서드입니다.
위의 Static Member들 중에서도 특별히 굵게 강조 표시한 메서드 2개는, 기본적으로 제공되는 프로젝트 템플릿 상에 별도의 언급도 없으며, 이에 대한 구체적인 문서를 쉽게 발견하기 힘들지만, 이 글에서 소개하는 내용을 적용하지 않았을 경우 다음의 문제가 발생할 수 있습니다.
FromConfigurationSettings 메서드를 호출할 때 예외가 발생합니다.
FromConfigurationSettings 메서드를 사용하지 않기 위하여 Role의 자체적인 Configuration File을 사용하도록 구성한 경우 설정 변경을 위하여 매번 Role을 새로 업데이트해야 하는 비효율적인 상황에 직면할 수 있습니다.
문제 해결 방법
이러한 문제를 해결하고, Storage API의 설정이 매 순간 관리 도구에 의하여 편집되고 정확히 반영될 수 있도록 하려면 SetConfigurationSettingPublisher 정적 메서드를 호출하여 이벤트 핸들러를 하나만 지정하면 됩니다. 아래의 코드를 WebRole.cs 파일 - 또는 - WorkerRole.cs 파일의 내용 중 OnStart 메서드에 추가하시면 됩니다.
Windows Azure SDK Simulator 위에서 실행 중이거나 실제 Windows Azure Platform 위에서 실행 중일 때 이 Static Property의 반환값은 항상 True이며, 그 외에 단순히 일반 ASP.NET 웹 사이트로 기동 중일 때에는 항상 False가 됩니다. [본문으로]
매개 변수인 _FWaitAll 은 지정된 event 들이 모두 설정될 때까지 기다릴 것인지 여부입니다. false 를 지정하면 하나의 event 라도 설정되면 기다리는 것을 멈추고 계속 진행됩니다.
마지막 매개 변수인 _Timeout 은 최대 시간입니다. 기본 매개 변수인 COOPERATIVE_TIMEOUT_INFINITE 는 무한대를 나타냅니다.
매개 변수 중 _FWaitAll 을 false 로 지정하고, 하나의 event 가 설정되었을 때, 설정된 event 의 _PPEvents 로 지정된 배열의 인덱스가 반환됩니다.
_FWaitAll 을 true 로 지정했을 경우에 모든 event 가 설정되었을 때에는 COOPERATIVE_WAIT_TIMEOUT 이 아닌 값이 반환됩니다.
Windows API 의 이벤트 객체를 사용할 때 함께 사용하는 WaitForMultipleObject() 와 유사합니다.
예제
Windows API 의 이벤트 객체와 어떻게 다른지 알아볼 수 있는 예제를 구현해보겠습니다.
시나리오
우선 최대 2개의 작업이 동시에 수행될 수 있도록 설정합니다.
그리고 하나의 event 를 생성한 후, 5개의 작업을 병렬로 처리합니다. 각 작업마다 생성한 event 가 설정될 때까지 기다리도록 합니다.
메인 스레드에서 1 초 후에 그 event 를 설정합니다.
같은 작업을 Windows API 의 이벤트 객체를 사용해서 구현합니다.
코드
// 코드의 출처는 msdn 입니다.
#include <windows.h>
#include <concrtrm.h>
#include <ppl.h>
#include <iostream>
#include <sstream>
using namespace Concurrency;
using namespace std;
// Demonstrates the usage of cooperative events.
void RunCooperativeEvents()
{
// An event object.
event e;
// Create a task group and execute five tasks that wait for
// the event to be set.
task_group tasks;
for (int i = 0; i < 5; ++i)
{
tasks.run([&] {
// Print a message before waiting on the event.
wstringstream ss;
ss << L"\t\tContext " << GetExecutionContextId()
<< L": waiting on an event." << endl;
wcout << ss.str();
// Wait for the event to be set.
e.wait();
// Print a message after the event is set.
ss = wstringstream();
ss << L"\t\tContext " << GetExecutionContextId()
<< L": received the event." << endl;
wcout << ss.str();
});
}
// Wait a sufficient amount of time for all tasks to enter
// the waiting state.
Sleep(1000L);
// Set the event.
wstringstream ss;
ss << L"\tSetting the event." << endl;
wcout << ss.str();
e.set();
// Wait for all tasks to complete.
tasks.wait();
}
// Demonstrates the usage of preemptive events.
void RunWindowsEvents()
{
// A Windows event object.
HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, TEXT("Windows Event"));
// Create a task group and execute five tasks that wait for
// the event to be set.
task_group tasks;
for (int i = 0; i < 5; ++i)
{
tasks.run([&] {
// Print a message before waiting on the event.
wstringstream ss;
ss << L"\t\tContext " << GetExecutionContextId()
<< L": waiting on an event." << endl;
wcout << ss.str();
// Wait for the event to be set.
WaitForSingleObject(hEvent, INFINITE);
// Print a message after the event is set.
ss = wstringstream();
ss << L"\t\tContext " << GetExecutionContextId()
<< L": received the event." << endl;
wcout << ss.str();
});
}
// Wait a sufficient amount of time for all tasks to enter
// the waiting state.
Sleep(1000L);
// Set the event.
wstringstream ss;
ss << L"\tSetting the event." << endl;
wcout << ss.str();
SetEvent(hEvent);
// Wait for all tasks to complete.
tasks.wait();
// Close the event handle.
CloseHandle(hEvent);
}
int wmain()
{
// Create a scheduler policy that allows up to two
// simultaneous tasks.
SchedulerPolicy policy(1, MaxConcurrency, 2);
// Attach the policy to the current scheduler.
CurrentScheduler::Create(policy);
wcout << L"Cooperative event:" << endl;
RunCooperativeEvents();
wcout << L"Windows event:" << endl;
RunWindowsEvents();
}
[ 코드1. event 와 Windows API 이벤트 객체와의 차이 ]
event 의 경우 5 개의 작업 중 동시에 2개가 수행되도록 하여 2 개의 작업이 기다리고 있을 때, 기다리는 스레드 자원은 다른 작업을 하게 됩니다. 이것이 바로 협력적인 스케쥴링입니다.
반면에 Windows API 의 이벤트 객체를 사용할 경우, 2 개의 작업이 다시 시작될 때까지 스레드 자원은 낭비를 하게 됩니다.
[ 그림1. event와 Windows API 이벤트 객체와의 차이 예제 결과 ]
마치는 글
Concurrency Runtime 에서 제공하는 동기화 객체인 event 까지 알아보았습니다. 이렇게 해서 제공하는 모든 동기화 객체를 알아보았습니다.
동기화 객체도 알아보았으니 이제 사용자 정의 message block 을 구현할 준비가 다 된 것 같습니다.
다음 글에서는 제공하는 message block 이 외에 사용자가 구현사여 사용하는 message block 에 대해서 알아보겠습니다.
모두들 안녕하시죠~? 예년과 다르게 자주 발생하는 열대야 떄문에 다들 고생하시고 있을거라 생각됩니다. 벌써 8월도 중반이 넘어가고 있으니깐요~ 곧 이 더위도 물러날거라 생각됩니다 ㅎㅎ
다들 조금만 더 참아보면 기분 좋은 가을이 찾아오겠죠~ ㅎ
이번 아티클부터는 WCF 의 Security 부분과 관련한 주제로 이야기를 이어가볼까 합니다.
그래서~ 제목에서 확인하셨겠지만, SSL 프로토콜을 이용한 보안 설정을 먼저 다루어 보려 합니다. SSL(Secure Sockets Layer)은 네트워크를 통해 전달되는 정보의 안전한 전송을 위해 넷스케이프사에서 정한 인터넷 통신규약 프로토콜을 말합니다.
최근에는 SSL을 여러 부분에서 많이 사용하고 있기 떄문에 다들 한번 이상은 들어보았을거라 생각됩니다.
먼저, Visual Studio 2010에서 WCF 응용 프로그램 템플릿을 이용해 새로운 솔루션을 만듭니다.
이번 내용도 WCF 서비스가 어떤 기능을 수행하는지에 대한건 그리 중요하지 않으니깐 기본적으로 만들어 지는 코드를 그대로 사용해 보겠습니다. ㅎ
SSL 을 사용하기 위해선 인증서가 필요하기 때문에 서비스에 SSL을 적용하기 전에 우선 인증서를 만들어야 합니다.
SSL 인증서는 공식 인증 기관에서 생성을 해줘야 신뢰할 수 있는 인증서를 만들 수 있지만, 우리는 테스트가 목적이니깐 그렇게까진 필요없고, 윈도우에서 자체 서명된 인증서를 만들어 주면 될 것 같습니다.
다음 그림과 같이 IIS 관리자를 실행 시키고, "서버 인증서" 아이콘을 더블 클릭 합니다.
다음, 오른쪽 "작업" 메뉴에서 "자체 서명된 인증서 만들기..." 메뉴를 클릭합니다. 그러면 다음과 같은 창이 뜨는데 여기서 인증서 이름을 적고 확인 버튼을 클릭하면 새로운 인증서를 만들 수 있습니다.
다음으로 우리가 만든(비록 Visual Studio에서 다 만들어 준 것이지만,,^^;;) WCF 서비스를 IIS에 올리는 작업을 수행합니다. 이 작업은 생략해도 다들 아실거라 생각합니다,, 혹시 모르시는 분이 계시면 댓글로 남겨주세요~ ^^
다음은, IIS 관리자를 통해 WCF 서비스를 호스팅하는 사이트에 바인딩을 추가할 필요가 있습니다. SSL을 사용하는 사이트는 https 로 시작하는 url을 사용하는데, 이에 대한 바인딩을 추가해주어야 하는 것이죠~
IIS 관리자에서 WCF 서비스를 호스팅하는 사이트를 선택하고 작업 메뉴에 있는 "바인딩..." 메뉴를 클릭합니다.
그리고, 새로운 사이트 바인딩을 추가하기 위해 추가 버튼을 클릭하고 다음 그림과 같이 https를 사용하도록 하고 포트 번호를 설정해 줍니다. 또한, SSL 인증서에서 위에서 생성한 인증서를 선택해주고 확인 버튼을 클릭합니다.
자~ 이렇게 하면 WCF 서비스를 호스팅하는 사이트는 SSL을 이용할 수 있게 됩니다.
다음으로는 WCF 서비스에서 SSL을 이용하기 위한 몇 가지 설정을 해주어야 합니다.
앞에서도 얘기했지만 SSL을 사용하는 사이트는 https로 시작하는 url을 사용하기 때문에 이에 대한 설정을 해주어야 합니다. 그리고 전송계층에서의 보안을 설정해주기 위한 작업도 필요합니다.
위의 설정을 보면 아시겠지만 SSL을 사용하기 위한 URL을 base address로 등록을 하고 basicHttpBinding을 사용하는 엔드 포인트를 등록합니다. 그리고 이 엔드 포인트에 사용하는 basdicHttpBinding에 전송계층에서의 보안 설정을 위해 security 태그와 transport 태그가 적용되어 있습니다.
transport 태그의 clientCredentialType 속성은 클라이언트의 인증을 위해 어떤 방식을 사용할 것인지에 대한 것을 설정할 수 있는데 이에 대한 내용은 다음 포스팅에서 설명을 하겠습니다. 이번 서비스는 따로 인증을 하지 않기 때문에 None으로 적용하였습니다.
이제 모든 설정은 끝이 났습니다.
간단한 콘솔 어플리케이션을 새로 만들어서 이 서비스를 참조해보도록 하겠습니다.
콘솔 어플리케이션을 만들고 언제나 그랬듯, "서비스 참조 추가"를 수행하고 서비스 참조 추가 창에서 주소에 IIS에서 설정해 주었던 url을 입력합니다. 저의 경우에는 "https://localhost:4948/Service1.svc"입니다.
그리고, 확인 버튼을 클릭하면 서비스를 찾다가 다음과 같은 화면이 뜹니다.
대충 내용을 보면, 인증서에 포함된 호스트와 주소가 일치하지 않기 때문에 신뢰할 수 없다는 내용인 듯 합니다. 그냥 여기서 "예" 버튼을 클릭해주면, 다음 그림 처럼 아무런 이상없이 서비스를 찾을 수 있고, 참조를 할 수 있습니다.
이렇게 참조를 해주면 아무 문제없이 서비스를 사용할 수 있겠죠~ ㅎ
콘솔 어플리케이션으로 서비스를 이용하는 부분은 생략하도록 하겠습니다~ 별 내용이 없으니깐요,, ㅎ
보안 부분은 개인적으로 제일 어려운 부분이면서 가장 부족한 부분이라 생각하기 때문에 오늘의 내용에 잘못된 부분도 있을거라 생각됩니다. 혹시 잘못된 내용이 있으면 댓글로 알려주세요~ ^^;;
다음 포스팅에서는 SSL을 사용하면서 사용자 인증을 하는 방법에 대해서 포스팅을 해볼까 합니다.
정말이지, 테스팅 그거 아무나 하는거 아니죠. 특히 저처럼 게으른 놈은 발을 들여놓기가 무서울때도 있습니다.
고객분들은 빠른 결과물을 얻길 원하시고, 그 고객이 여럿이면 모두가 자기의 일이 우선이니 빨리 좀 해달라고 아우성 거릴때가 많습니다. 가뜩이나 개발로도 벅찬 시간인데, 테스트라뇨.. 에잇!
하지만, 그렇게 작업을 한 후 스테이징(Staging Server - 라이브 서버에 반영하기 전 배포하여 테스트하는 서버입니다^^) 에 적용해놓으면 테스트팀에서는 온갖 방법으로(정말 어처구니 없는 입력값으로 마구 공격(?)해 들어오시죠) 테스트를 한 후 결과물들을 전달해주시죠. 그것 예외처리하는 것으로 인해 또한번의 시간이 소비되고 다시 테스트하고 다시 결과물 받고, 계속 반복되는거죠. 그렇게되면, 처음에 빨리 개발했던 시간이 무용지물이 되어버리는 것이죠.
신입시절에(지금도 신입 짬밥이죠;;) 과장님 한 분이, '야, 어떻게 너는 일을 주면 생각없이 컴퓨터로 바로 달려들어 개발하냐?' 라고 한 말씀 해주셨습니다. 먼저 그림을 그려보라고, 이면지 많잖아. 없어? 내가 줄까? 그러시며, 흐름을 생각하며, 어떤식으로 해야할지 먼저 그림을 그리라고요.
테스팅하는 것도 먼저 그림을 그려보는 작업 같습니다. 이렇게 개발을 하면 원하는 결과값이 나오겠지하며, 테스트와 병행하며 원하는 결과값이 맞게 나오는지 확인해가며 개발을 해나가는 거죠.
단위 테스트 프레임워크를 사용해보자
닷넷 프레임워크를 위한 테스트 프레임워크가 많습니다. 다들 아시는 NUnit, xUnit, MbUnit 등이 있죠. 하지만, 저 게으른 것 다들 아실겁니다. 그래서! 비주얼 스튜디오 단위 테스트를 이용하겠습니다. 위 단위 테스트 사이트들 들어가서 다운받고 설치하고, 에휴~^^;
ASP.NET MVC 프로젝트를 생성하게 되면 아래의 화면과 같이 단위 테스트를 할지 여부를 묻는 대화상자가 나옵니다. 예(Yes)를 선택하면, 프로젝트가 생성될때 테스트 프로젝트도 같이 생성하게 됩니다.
테스트 프로젝트에는 Controllers라는 폴더가 있고, 그 안에는 샘플프로젝트의 Home컨트롤러와 Account컨트롤러를 테스트하기 위한 HomeControllerTest.cs와 AccountControllerTest.cs 가 있습니다. HomeControllerTest를 열어보면 다음과 같습니다.
Microsoft.VisualStudio.TestTools.UnitTesting 네임스페이스가 임포트 되어 있는 것이 보이고, [TestClass], [TestMethod] 가 눈에 띄네요. 주석을 보니,
TestClass : 테스트 메서드가 포함된 클래스를 식별하는데 사용됩니다.
TestMethod : 테스트 메서드를 식별하는데 사용됩니다.
이렇다네요.^^
테스트와 함께 하시겠습니까?
다음은 TelDirController 소스입니다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace UsingTest.Controllers
{
public class TelDirController : Controller
{
//
// GET: /TelDir/
public ActionResult Index()
{
return View();
}
//
// GET: /TelDir/Details/5
public ActionResult Details(int id)
{
return View();
}
}
}
아무것도 처리하지 않은 깨끗한(?) 소스입니다. Index(), Details() 라는 두 액션메쏘드가 있고, 두 메쏘드 모두 View를 리턴하고 있습니다.
그럼, TelDirController를 위한 테스트를 생성해보겠습니다.
테스트 만들기
테스트 프로젝트의 Controllers를 오른쪽 버튼으로 클릭한 후 추가 -> 새 테스트를 선택합니다.
테스트하려는 컨트롤러 이름 뒤에 Test 만 붙여서 이름을 만들겠습니다. TelDirControllerTest 처럼요.^^;
확인 버튼을 클릭하면 다음의 소스가 보이실겁니다^^
지금으로선 불필요한 것들을 모두 닫아놓긴 했는데요, 물론 삭제하셔도 됩니다. 이런 것도 싫다 하시면, Controllers 폴더에서 클래스를 생성한 후 TestClass와 TestMethod 속성, 그리고 UnitTesting 네임스페이스를 임포트시키시면 됩니다.
맛보기 테스트
정말 맛만 보여드리겠습니다.^^;
TelDirController.Details 메쏘드가 정말 "Details" 뷰를 리턴하는지 테스트 해보도록 하겠습니다.
먼저 테스트 메쏘드를 만들어보겠습니다.
[TestMethod]
public void DetailsTest()
{
var controller = new TelDirController();
var result = controller.Details(1) as ViewResult;
Assert.AreEqual("Details", result.ViewName);
}
Assert.AreEqual 은 비교대상의 두 값이 같은지 여부를 나타냅니다. 같으면 성공, 다르면 실패.
ViewResult.ViewName은 컨트롤러에서 리턴받은 뷰명을 나타냅니다.
자~ 테스트를 진행해볼까요?
'솔루션의 모든 테스트 실행' 버튼을 클릭합니다. 단축키는 Ctrl+R,A 라고 친절히 알려주네요^^ 물론 지금은 테스트 케이스가 하나라 이렇게 하지만 모든 테스트 실행 좌측에 있는 버튼인 '현재 컨텍스트의 테스트 실행'은 현재 선택받은 곳, 즉 커서가 위치한 곳의 테스트를 진행합니다.
엥?! 실패하였습니다. 예상 값이 Details 인데 실제값은.. 실제값은.. 없네요;;
TelDirController의 Details 메쏘드를 살펴보죠.
public ActionResult Details(int id)
{
return View();
}
이렇게 되어 있네요. 잘못된 것은 없어보이는데요.
Details 뷰를 리턴하려면 두 가지 방법이 있습니다. 위처럼 Details 액션 메쏘드에서 return View() 를 하는 방법(이것은 액션 메쏘드의 이름에서 유추가 됩니다), 다른 하나는 명시적으로 return View("Details") 와 같은 방법입니다.
그럼, 또다른 방법을 택해서 테스트를 돌려보죠.
public ActionResult Details(int id)
{
return View("Details");
}
결과는
통과~.
이래서, 아~ 테스트시에는 뭐든지 확실하게 명시적으로 표현해줘야하는구나~ 라는 것을 알 수 있습니다.^^
마무리요
이번 포스팅은 간단하게 테스트하는 방법에 대해서 알아봤는데요, 다음은 이 테스팅에 대해서 좀더 자세하게 알아보는 시간을 가져보겠습니다.
위 내용에서 확인할 수 있듯이, 기존 웨어하우스 데이터베이스를 삭제하면 자동으로 다시 만들어준다고 합니다. 하지만 위와 같은 오류가 나면 삭제도 안됩니다. 다른 데이터베이스 이름을 사용하기도 싫군요^^; 선뜻 데이터베이스를 삭제하기도 그렇고, 백업 오류도 발생하여 Hyper-V 의 스냅샷을 찍어두고 데이터베이스를 삭제하고 다시 만들어 보았습니다.
1. SQL Analysis 데이터베이스를 정지합니다.
2. 아래의 폴더로 이동한 후, Tfs_Analysis.0.db 폴더의 이름을 변경합니다. %ProgramFiles%\Microsoft SQL Server\MSAS10.MSSQL2008\OLAP\Data
3. 다시 MSSQL Analysis 서비스를 시작합니다.
4. Tfs_Analysis 데이터베이스에서 마우스 오른쪽 버튼을 클릭한 후, 삭제를 선택합니다.
5. 개체 삭제에서 확인을 클릭합니다.
6. Team Foundation Administration Console 의 Application Tier-Reporting 메뉴로 이동한 후, Edit 를 클릭합니다.
7. 각 탭 메뉴에서 Test Connection 을 클릭하면, '기존의 데이터베이스가 없는 경우 새로 생성된다는' 메시지와 함께 테스트가 성공합니다.
8. 모든 구성을 완료하였으면, OK 버튼을 클릭합니다. 그럼, 새로운 Analysis Database 가 생성이 되는 작업을 진행합니다.
9. MSSQL Analysis 서버에 Tfs_Analysis 데이터베이스가 새로 생성된 것을 확인할 수 있습니다.
10. Team Foundation Administration Console 의 Reporting 메뉴에서 Start Job 과 Start Rebuild 메뉴를 차례로 클릭해 줍니다.
11. 모든 웨어하우스와 관련된 서비스가 정상적으로 동작하는 것을 확인하고 작업을 완료합니다.
이번 시간에는 간단하게 페이지 간의 전환을 해보려고 합니다.
그러기 위해서는 최소 2개 이상의 페이지가 존재해야 합니다.
그래서 첫 작업은 페이지를 추가하는 작업입니다.
역시나 이번에도 VisualStudio가 아닌, 블렌드를 실행합니다.
그리고 프로젝트에서 아래 그림과 같이 새로운 아이템을 추가합니다.
이렇게 하면 새로운 페이지가 추가되어져 있을 것입니다.
그리고 첫번째 페이지에 간단히 버튼을 추가합니다.
버튼은 Asset 패널에 컨트롤 메뉴에 있습니다.
버튼을 배치하면, 각종 속성을 제어할 수 있도록 화면 우측에 여러 윈도우가 활성화 되는 것을 볼 수 있습니다.
버튼만 화면에 있으니깐 너무 단순한 것 같아, 이미지를 화면에 배치시키겠습니다.
이미지를 위해서 프로젝트 내부에 폴더를 만듭니다.
그렇게 폴더가 만들어진 후에는 'Add Existing Item...' 메뉴를 통해서 이미지를 추가합니다.
이 때, 이미지 파일 어느 곳에 있든 상관이 없습니다.
왜냐하면, 현재 작업중이 프로젝트로 이미지 파일을 새롭게 복사를 해오기 때문입니다.
현재 작업 중인 프로젝트를 열어서 확인해 보시기 바랍니다.
이미지 파일이 자동적으로 생성되어서 복사되었음을 확인할 수 있을 것입니다.
이제 이미지를 배치해 보겠습니다.
이미지 역시 컨트롤이기 때문에 Assets 탭에서 Image 컨트롤을 선택해서 적당히 배치하시기 바랍니다.
같은 방법으로 두번째 페이지에도 추가해 주시면, 결과 확인이 쉽겠죠? ^^
이미지의 경우에는 컨트롤만 연결되었지, 아무런 변화가 없을 것입니다.
즉, 이미지 리소스를 연결해 주어야 합니다.
아래 그림처럼 현재 연결 가능한 리소스 리스트에서 하나를 선택해서 연결해 주시면 됩니다.
각 페이지마다 이미지가 잘 등장했으리라 예상이 됩니다.
혹시나 컨트롤이 잘 선택이 안되어지는 경우에는 아래와 같은 패널을 이용하시기 바랍니다.
이 패널은 컨트롤을 가장 확실하게 선택할 수 있습니다..^^
이제는 속성 창에 대한 설명을 뒤로 한채 간단하게 저 버튼들에 이벤트를 연결해 보겠습니다.
아래 그림에서 빨간색 타원 영역이 보이십니까?
그 영역에서 번개 모양의 아이콘을 클릭하면 이벤트를 연결할 수 있습니다.
버튼이 선택되어진 상태에서 해야, 버튼에 관련한 이벤트를 연결할 수 있습니다.
번개 모양의 아이콘을 클릭하면 다음과 같은 이벤트 속성 창이 등장합니다.
저는 Click 속성에 Next 를 입력한 후에 엔터키를 쳤습니다.
그러면 Next 라는 이벤트용 함수가 만들어 집니다.
각각의 상황을 모두 제어하고 싶다면,
바로 이렇게 이벤트 속성에서 함수명을 입력하고 제어하면 되는 것입니다.
아래와 같이 친절하게 함수를 만들어 줍니다.^^
페이지 전환을 위해서 저는 아래와 같이 코드를 추가했습니다.
두번째 페이지에서도 동일한 방법으로 페이지 파일명만 교체해서 추가해 주었습니다.
제가 만든 결과는 다음과 같습니다.^^
버튼을 클릭할 때마다, 페이지 이동이 발생합니다.^^
실행 프로젝트는 첨부를 하니, 참고해 주시기 바랍니다.^^
이번 시간에 Uri가 무엇인지, NavigationService 가 무엇인지 등 코드에 대한 설명하지 않습니다.^^
실제 코드 개발 관련 내용은 다른 분들이 다루어 주실 것입니다.
이번 개발 툴 버전의 가장 큰 특징 중 하나는
'Microsoft Expression Blend for Windows Phone Beta'의 포함입니다.
지난 버전까지는 별도의 애드인 작업을 거쳐야 했는데, 이번 버전부터는 포함되어 있습니다.
윈도우폰7 개발에 많은 관심이 있으신 분들 중에는
Expression Blend( 이하 블렌드 ) 라는 소프트웨어에 대해서 굉장히 생소할 수 있습니다.
이 소프트웨어는 마이크로소프트에서 개발되었으며,
실버라이트나 WPF 와 같은 마이크로소프트 플랫폼에서 손쉬운 그래픽 디자인을 위해서 탄생했습니다.
처음 공개적으로 배포된 것이 2007년 1월이니, 정말 최신 소프트웨어라고 할 수 있습니다.
( 현재는 4.0까지 출시되었으며, 이것이 2010년 6월의 일입니다. )
알려진 바와 같이 윈도우폰7 개발 방법은 크게 2가지로 나누어져 있습니다.
실버라이트를 기반으로 한 일반 애플리케이션 분야와
XNA 를 기반으로 한 게임 분야입니다.
아주 불행하게도 XNA와 블렌드는 연동되지 않습니다.
( 이것은 블렌드를 조금이라도 아는 분들이라면, 정말 불행한 일이라고 생각하실 것입니다. )
무슨 말이 더 필요하겠습니까?
지금 당장 윈도우폰 개발 베타 버전을 설치하신 후에 Visual Studio가 아닌,
Microsoft Expression Blend for Windows Phone Beta 실행하시기 바랍니다.
아마 위와 같은 화면이 실행되어졌을 것입니다.
실행 후에는 이 블렌드를 통해서 윈도우폰7 프로젝트를 생성합니다.
적당한 경로에 자신이 원하는 프로젝트 명칭으로 설정해주면 됩니다.
그리고 OK를 클릭하면, 프로젝트가 생성됩니다.
기존의 디자인툴에 익숙하신 분들이라면, 친숙한 모습일 것입니다.
블렌드는 사실 기능이 너무 많습니다.
개인적인 생각이지만, 포토샵 + 플래시 + 드림위버 같은 느낌이였습니다.
여러분들은 어떠신가요? ^^
그러면 코딩은 어디서 해야 할까요?
의구심이 드시겠죠? ^^
실제 코딩은 블렌드 내부에서도 가능하고, Visual Stduio를 별도로 띄워서도 가능합니다.
이것이 제가 Visual Studio 가 아닌, 블렌드를 실행시키라는 진정한 의미입니다.
즉, 개발과 그래픽 디자인이 모두 하나의 툴에서 이루어지는 것입니다.
'Projects' 탭에서 아래 그림처럼 해보시기 바랍니다.
트리 메뉴에서 MainPage.xaml.cs 에서 마우스 오른쪽을 누르시면 됩니다.
위와 같이 실행하면, 별도로 Visual Studio가 실행됩니다.
물론 Visual Studio에서 수정하면, 즉시 블렌드쪽에 결과가 반영이 됩니다.
*.cs 파일을 더블 클릭하면, 블렌드 상에서 바로 수정할 수 있는 창이 실행됩니다. ( 완전 좋다는.... )
*.xaml 파일을 더블 클릭하면, 다시 디자인 폼 수정 상태로 이동할 수 있습니다.
실행은 다음과 같이 수행합니다.
이번 베타버전에서는 빌드 성능도 꽤 많이 좋아졌습니다.
에뮬레이터 모양도 굉장히 예뻐졌습니다..^^