C#

C# Frequently Asked Questions for C++ programmers

Andy McMullan Last update: 3-Dec-2000

This FAQ tries to address many of the basic questions that C++ developers have when they first come across C#. I recommend that you read the .NET Framework FAQ before reading this one.

If you have any comments, suggestions, corrections or criticisms, please e-mail me at andy@andymcm.com.

Disclaimer: The content of this FAQ is just my interpretation of information gleaned from various sources, including postings to the DOTNET mailing list, various MS documents, and of course practical experimentation with the language. None of the answers are necessarily completely or even partially correct. Even where the answers are correct now, they may change in the future.

Revision history (major updates only):

13-Nov-00 Updated for arrival of Beta 1 of the .NET Framework SDK. 21-Oct-00 Added "C# uses references instead of pointers. Are C# references the same as C++ references?". 31-Aug-00 Added "Does C# have a 'throws' clause?", "How can I process command-line arguments?" and "How can I make sure my C# classes

will interoperate with other .NET languages?" 29-Aug-00 Started revision history 07-Aug-00 to 28-Aug-

Miscellaneous (unrecorded) updates

00 07-Aug-00 Posted first version

1. Introduction

1.1 What is C#?

C# is a programming language designed by Microsoft. It is loosely based on C/C++, and bears a striking similarity to Java in many ways. Microsoft describe C# as follows:

"C# is a simple, modern, object oriented, and type-safe programming language derived from C and C++. C# (pronounced 'C sharp') is firmly planted in the C and C++ family tree of languages, and will immediately be familiar to C and C++ programmers. C# aims to combine the high productivity of Visual Basic and the raw power of C++."

1.2 When will C# be released?

No firm date yet, but it is expected in the second half of 2001.

1.3 How do I develop C# apps?

The .NET SDK contains the C# command-line compiler (csc.exe). The next version of Visual Studio (called Visual Studio 7 or Visual Studio.NET) will have fully integrated support for C# development.

1.4 Where can I download the .NET SDK & Visual Studio 7?

You can download Beta 1 of the SDK from http://msdn.microsoft.com/net. If you are an MSDN Universal subscriber, you can also download Beta 1 of Visual Studio 7.

1.5 Does C# replace Java?

C# is a very Java-like language - the core of both languages have similar advantages and limitations when compared to C++. For example, both languages have garbage collection, but neither language has templates. Microsoft have ceased production of Visual J++, so it's hard not to view C# as Microsoft's alternative to Java.

1.6 Does C# replace C++?

The obvious answer is no. However it's difficult to see C++ as the best choice for new .NET code. For the .NET runtime to function fully, it requires the programming language to conform to certain rules - one of these rules is that language types must conform to the Common Type System (CTS). Unfortunately many C++ features are not supported by the CTS - for example multiple inheritance of classes and templates.

Microsoft's answer to this problem is to offer Managed Extensions (ME) for C++, which allows you to write C++ that conforms to the CTS. New keywords are provided to mark your C++ classes with CTS attributes (e.g. __gc for garbage collection). However, it's difficult to see why ME C++ would be chosen over C# for new projects. In terms of features they are very similar, but unlike C++, C# has been designed from the ground-up to work seamlessly with the .NET environment. The raison d'etre for ME C++ would therefore appear to be porting existing C++ code to the .NET environment.

So, in answer to the question, my suspicion is that C++ will remain an important language outside of the .NET environment, and will be used (via ME) to port existing code to .NET, but I think C# will become the language of choice for one-time C++ developers developing new .NET applications. But only time will tell ...

1.7 What does a simple C# program look like?

Sorry, my imagination has deserted me. Yes, you guessed it, here comes 'Hello, world' ...

class CApplication

{ public static void Main() {

System.Console.Write( "Hello, new .NET world." ); } }

(No, you can't put Main() as a global function - global functions don't exist in C#.)

1.8 Is C# object-oriented?

Yes, C# is an OO language in the tradition of Java and C++.

1.9 Does C# have its own class library?

Not exactly. In common with all .NET languages (e.g. VisualBasic.NET, JScript.NET) C# has access to the .NET class library. C# does not have its own class library.

2. Basic types

2.1 What standard types does C# supply?

C# supports a very similar range of basic types to C++, including int, long, float, double, char, string, arrays, structs and classes. However, don't assume too much. The names may be familiar, but some of the details are different. For example, a long is 64 bits in C#, whereas in C++ the size of a long depends on the platform (typically 32 bits on a 32-bit platform, 64 bits on a 64-bit platform). Also classes and structs are almost the same in C++ -

this is not true for C#.

2.2 Is it true that all C# types derive from a common base class?

Yes and no. All types can be treated as if they derive from object (System.Object), but in order to treat an instance of a value type (e.g. int, float) as object-derived, the instance must be converted to a reference type using a process called 'boxing'. In theory a developer can forget about this and let the run-time worry about when the conversion is necessary, but in reality this implicit conversion can have side-effects that may trip up the unwary.

2.3 So this means I can pass an instance of a value type to a method that takes an object as a parameter?

Yes. For example:

class CApplication

{ public static void Main() {

int x = 25; string s = "fred";

DisplayMe( x ); DisplayMe( s );

}

static void DisplayMe( object o ) { System.Console.WriteLine( "You are {0}", o ); } }

This would display:

You are 25

You are fred

2.4 What are the fundamental differences between value types and reference types?

C# divides types into two categories -value types and reference types. Most of the basic intrinsic types (e.g. int, char) are value types. Structs are also value types. Reference types include classes, interfaces, arrays and strings. The basic idea is straightforward - an instance of a value type represents the actual data (stored on the stack), whereas an instance of a reference type represents a pointer or reference to the data (stored on the heap).

The most confusing aspect of this for C++ developers is that C# has predetermined which types will be represented as values, and which will be represented as references. A C++ developer expects to take responsibility for this decision.

For example, in C++ we can do this:

int x1 = 3; // x1 is a value on the stack int *x2 = new int(3) // x2 is a reference to a value on the heap

but in C# there is no control:

int x1 = 3; // x1 is a value on the stack int x2 = new int(); x2 = 3; // x2 is also a value on the stack!

2.5 Okay, so an int is a value type, and a class is a reference type. How can int be derived from object?

It isn't, really. When an int is being used as an int, it is a value (on the stack). However, when it is being used as an object, it is a reference to an integer value on the heap. In other words, when you treat an int as an object, the runtime automatically converts the int value to an object reference. This process is called boxing. The conversion involves copying the contents of the int from the stack to the heap, and creating an object instance which refers to it. Unboxing is the reverse process - the object is converted back to a stack-based value.

int x = 3; // new int value 3 on the stack object objx = x; // new int on heap, set to value 3 - still have x=3 on stack int y = (int)objx; // new value 3 on stack, still got x=3 on stack and objx=3 on heap

2.6 C# uses references instead of pointers. Are C# references the same as C++ references?

Not quite. The basic idea is the same, but one significant difference is that C# references can be null. So you cannot rely on a C# reference pointing to a valid object. If you try to use a null reference, a NullReferenceException is thrown.

For example, look at the following method:

void displayStringLength( string s ) { Console.WriteLine( "String is length {0}", s.Length ); }

The problem with this method is that it will throw a NullReferenceException if called like this:

string s = null; displayStringLength( s );

Of course for some situations you may deem a NullReferenceException to be a perfectly acceptable outcome, but in this case it might be better to re-write the method like this:

void displayStringLength( string s ) {

if( s == null ) Console.WriteLine( "String is null" ); else Console.WriteLine( "String is length {0}", s.Length ); }

3. Classes and structs

3.1 Structs are largely redundant in C++. Why does C# have them?

In C++, a struct and a class are pretty much the same thing. The only difference is the default visibility level (public for structs, private for classes). However, In C# structs and classes are very different. In C#, structs are value types (stored on the stack), whereas classes are reference types (stored on the heap). Also structs cannot inherit from structs or classes, though they can implement interfaces. Structs cannot have destructors.

3.2 Does C# support multiple inheritance (MI)?

C# supports multiple inheritance of interfaces, but not of classes.

3.3 Is a C# interface the same as a C++ abstract class?

No, not quite. An abstract class in C++ cannot be instantiated, but it can (and often does) contain implementation code and/or data members. A C# interface cannot contain any implementation code or data members - it is simply a group of method names & signatures. A C# interface is more like a COM interface than a C++ abstract class.

The other major difference is that a C# class can inherit from only one class (abstract or not), but can implement multiple interfaces.

3.4 Are C# constructors the same as C++ constructors?

Very similar.

3.5 Are C# destructors the same as C++ destructors?

No! They look the same but they're very different. First of all, a C# destructor isn't guaranteed to be called at any particular time. In fact it's not guaranteed to be called at all. Truth be told, a C# destructor is really just a Finalize method in disguise. In particular, it is a Finalize method with a call to the base class Finalize method inserted. So this:

class CTest

{ ~CTest() {

System.Console.WriteLine( "Bye bye" );

} }

is really this:

class CTest

{ protected override void Finalize() {

System.Console.WriteLine( "Bye bye" ); base.Finalize(); } }

If you don't believe me, try putting a Finalize method and a destructor in a C# class and watch the compiler sheepishly confess its deceit.

3.6 If C# destructors are so different to C++ destructors, why did MS use the same syntax?

Because they're evil, and they want to mess with your mind.

3.7 What is a static constructor?

A constructor for a class, rather than instances of a class. The static constructor is called when the class is loaded.

3.8 Are all methods virtual in C#?

No. Like C++, methods are non-virtual by default, but can be marked as virtual.

3.9 How do I declare a pure virtual function in C#?

Use the abstract modifier on the method. The class must also be marked as abstract (naturally). Note that abstract methods cannot have an implementation (unlike pure virtual C++ methods).

4. Exceptions

4.1 Can I use exceptions in C#?

Yes, in fact exceptions are the recommended error-handling mechanism in C# (and in .NET in general). Most of the .NET framework classes use exceptions to signal errors.

4.2 What types of object can I throw as exceptions?

Only instances of the System.Exception classes, or classes derived from System.Exception. This is in sharp contrast with C++ where instances of almost any type can be thrown.

4.3 Can I define my own exceptions?

Yes, as long as you follow the rule that exceptions derive from System.Exception. More specifically, MS recommend that user-defined exceptions inherit from System.ApplicationException (which is derived from System.Exception).

4.4 Are there any standard exceptions that I can re-use?

Yes, and some of them loosely correspond to standard COM HRESULTs. The table below shows a mapping from HRESULTs to .NET (and therefore C#) exceptions:

Array

Other standard exceptions that you might want to re-use are IndexOutOfRangeException and ArithmeticException .

4.5 Does the System.Exception class have any cool features?

Yes - the feature which stands out is the StackTrace property. This provides a call stack which records where the exception was thrown from. For example, the following code:

using System;

class CApp

{ public static void Main() {

try {

f(); } catch( Exception e ) {

Console.WriteLine( "System.Exception stack trace = n{0}", e.Sta }

}

static void f() { throw new Exception( "f went pear-shaped" ); } }

produces this output:

System.Exception stack trace = at CApp.f() at CApp.Main()

Note, however, that this stack trace was produced from a debug build. A release build may optimise away some of the method calls which could mean that the call stack isn't quite what you expect.

4.6 When should I throw an exception?

This is the subject of some debate, and is partly a matter of taste. However, it is accepted by many that exceptions should be thrown only when an 'unexpected' error occurs. How do you decide if an error is expected or unexpected? This is a judgement call, but a straightforward example of an expected error is failing to read from a file because the seek pointer is at the end of the file, whereas an example of an unexpected error is failing to allocate memory from the heap.

4.7 Does C# have a 'throws' clause?

No, unlike Java, C# does not require (or even allow) the developer to specify the exceptions that a method can throw.

5. Run-time type information

5.1 How can I check the type of an object at runtime?

You can use the is keyword. For example:

using System;

class CApp

{ public static void Main() {

string s = "fred"; long i = 10;

Console.WriteLine( "{0} is {1}an integer", s, (IsInteger(s) ? "" : "not ") ); Console.WriteLine( "{0} is {1}an integer", i, (IsInteger(i) ? "" : "not ") );

}

static bool IsInteger( object obj )

{ if( obj is int || obj is long ) return true; else return false; } }

produces the output:

fred is not an integer 10 is an integer

5.2 Can I get the name of a type at runtime?

Yes, use the GetType method of the object class (which all types inherit from). For example:

using System;

class CTest

{ class CApp {

public static void Main()

{ long i = 10; CTest ctest = new CTest();

DisplayTypeInfo( ctest ); DisplayTypeInfo( i ); }

static void DisplayTypeInfo( object obj ) { Console.WriteLine( "Type name = {0}, full type name = {1}", obj.G } } }

produces the following output:

Type name = CTest, full type name = CTest Type name = Int64, full type name = System.Int64

6. Advanced language features

6.1 What are delegates?

A delegate is a class derived from System.Delegate. However the language has a special syntax for declaring delegates which means that they don't look like classes. A delegate represents a method with a particular signature. An instance of a delegate represents a method with a particular signature on a particular object (or class in the case of a static method). For example:

using System;

delegate void Stereotype();

class CAmerican

{ public void BePatriotic() {

Console.WriteLine( "... <gulp> ... God bless America."); } }

class CBrit

{ public void BeXenophobic() {

Console.WriteLine( "Bloody foreigners ... " ); } }

class CApplication

{ public static void RevealYourStereotype( Stereotype[] stereotypes ) {

foreach( Stereotype s in stereotypes ) s(); }

public static void Main()

{ CAmerican chuck = new CAmerican(); CBrit edward = new CBrit();

// Create our list of sterotypes. Stereotype[] stereotypes = new Stereotype[2]; stereotypes[0] = new Stereotype( chuck.BePatriotic ); stereotypes[1] = new Stereotype( edward.BeXenophobic );

// Reveal yourselves! RevealYourStereotype(stereotypes ); } }

This produces the following result:

... <gulp>... God bless America.

Bloody foreigners ...

6.2 Are delegates just like interfaces with a single method?

Conceptually delegates can be used in a similar way to an interface with a single method. The main practical difference is that with an interface the method name is fixed, whereas with a delegate only the signature is fixed - the method name can be different, as shown in the example above.

6.3 What is the C# equivalent of QueryInterface?

The as keyword. For example:

using System;

interface IPerson { string GetName(); }

interface IPerson2 : IPerson { int GetAge(); }

class CPerson : IPerson

{ public CPerson( string name ) {

m_name = name; }

// IPerson public string GetName() {

return m_name; }

private string m_name; }

class CPerson2 : IPerson2

{ public CPerson2( string name, int age ) {

m_name = name; m_age = age; }

// IPerson2 public string GetName() { return m_name; } public int GetAge() { return m_age; } private string m_name; private int m_age;

}

public class CApp

{ public static void Main() {

CPerson bob = new CPerson( "Bob" ); CPerson2 sheila = new CPerson2( "Sheila", 24 );

DisplayAge( bob ); DisplayAge( sheila ); }

static void DisplayAge( IPerson person )

{ IPerson2 person2 = person as IPerson2; // QueryInterface lives on !!! if( person2 != null )

Console.WriteLine( "{0} is {1} years old.", person2.GetName(), pe else Console.WriteLine( "Sorry, don't know {0}'s age.", person.GetNam } }

Running the program produces the following output:

Sorry, don't know Bob's age.

Sheila is 24 years old.

7. It doesn't work like that in C++ ...

7.1 I 'new'-ed an object, but how do I delete it?

You can't. You are not allowed to call the destructor explicitly, and no delete operator is provided. Don't worry, the garbage collector will destroy your object .... eventually .... probably .... :-)

7.2 I tried to create an object on the stack, but the C# compiler complained. What's going on?

Unlike C++, you cannot create instances of classes on the stack. Class instances always live on the heap and are managed by the garbage collector.

7.3 I defined a destructor, but it never gets called. Why?

A C# destructor is really just an implementation of Finalize, and the runtime doesn't guarantee to call Finalize methods. In particular, the runtime doesn't usually bother calling Finalize methods for objects that are still alive when you exit an application. However, you can politely ask it to reconsider that approach by calling the GC.RequestFinalizeOnShutdown() method.

7.4 Most of the C# basic types have the same names as C++ basic types? Are they the same?

No. A char in C# is equivalent to a wchar_t in C++. All characters (and strings, obviously) are Unicode in C#. Integral values in C# are concrete sizes, unlike C++ (where size depends on processor). For example, a C# int is 32 bits, whereas a C++ int is normally 32 bits on a 32-bit processor and 64 bits on a 64-bit processor. A C# long is 64 bits.

8. Miscellaneous

8.1 String comparisons using == seem to be case-sensitive? How do I do a case-insensitive string comparison?

Use the String.Compare function. Its third parameter is a boolean which specifies whether case should be ignored or not.

"fred" == "Fred" // false System.String.Compare( "fred", "Fred", true ) // true

8.2 I've seen some string literals which use the @ symbol, and some which don't. What's that all about?

The @ symbol before a string literal means that escape sequences are ignored. This is particularly useful for file names, e.g.

string fileName = "c:temptest.txt"

versus:

string fileName = @"c:temptest.txt"

8.3 Does C# support a variable number of arguments?

Yes, using the params keyword. The arguments are specified as a list of arguments of a specific type, e.g. int. For ultimate flexibility, the type can be object. The standard example of a method which uses this approach is System.Console.WriteLine().

8.4 How can I process command-line arguments?

Like this:

using System;

class CApp

{ public static void Main( string[] args ) {

Console.WriteLine( "You passed the following arguments:" ); foreach( string arg in args ) Console.WriteLine( arg ); } }

8.5 Does C# do array bounds checking?

Yes. An IndexOutOfRange exception is used to signal an error.

8.6 How can I make sure my C# classes will interoperate with other .NET languages?

Make sure your C# code conforms to the Common Language Subset (CLS). To help with this, add the [assembly:CLSCompliant(true)] global attribute to your C# source files. The compiler will emit an error if you use a C# feature which is not CLS-compliant.

9. Resources

9.1 Where can I find out more about C#?

The Microsoft .NET homepage is at http://www.microsoft.com/net/

Robert Scoble has compiled an extremely comprehensive list of .NET on-line resources at

http://www.devx.com/dotnet/resources/

There is a very snazzy site dedicated to C# at http://csharpindex.com/

9.2 Sample code and utilities

Peter Drayton has some .NET utilities (with C# source) at http://www.razorsoft.net/

C#

정말 아직 VB6, VC6를 쓰는 사람을 보면, 존경스럽기까지 하다..

물론, 여러가지 문제로 인해 아직 닷넷은 시기상조라는 말을 많이 듣긴 하지만..

사실 VC의 경우는 아무래도, 2003정도는 써주는게 낫지 않을까..

언제까지 98과의 호환성을 염두에 둘필요는 없지 않을까?

아직도, MS-DOS와의 호환이 필요한 몇몇 프로그래머를 제외하고, 일반 사용자 대상의 프로그램을 작성한다고 하면..

Microsoft는 업데이트에 꾸준히 .net framework를 배포중이고, Vista에는 이미 포함되어 있기도 하고..

본인 역시도 Vista에 대해서는 회의적이나, 닷넷에 관해서는 환영이다.

(사실 닷넷보다는, Mono Project때문일지도 모른다)

어쨌거나, 아직 VC6, VB6을 쓴다면 2005로 갈아타는걸 한번 고려해봐야 하지 않을까.

인쇄시장에서 만들어진지 15년이 지난 Quark 3.3k를 아직도 사용하는것처럼..

(최신형 매킨토시를 구입해서 OSX를 밀고 OS9을 설치하는 작업을 한다. 일반 PC로 따지자면, 쿼드코어에 MS-DOS 6.22 설치하는 격이랄까)

그래서 이래저래 설명을 하던중, 의미있는 문서를 찾았다.

 

출처 : http://www.microsoft.com/korea/msdn/msdnmag/issues/06/06/CAtWork/default.aspx

많은 분들이 현재 Visual Studio? 2005로 업그레이드 중일 것으로 생각 됩니다. 그래서 지금 이야 말로 새 컴파일러와 저의 경험담에 대한 글을 쓰기에 좋은 시기인 것으로 생각 되어 이 기사를 쓰게 되었습니다. 왜 이제서야 쓰게 되었냐고요? 늦었다고 생각 될 때가 가장 빠른 것 아니겠습니까?

여러분이 Visual Studio 2005에서 가장 먼저 주목할 것이 바로 버전 관리자 입니다. 이 버전 관리자는 여러분의 프로젝트를 열어본 뒤 어떤 버전을 실행 시킬지를 결정하는 역할을 합니다. Visual Studio 2005를 Visual Studio 2003과 나란히 설치 하실 수도 있습니다. 이 둘은 여러분의 컴퓨터에서 사이 좋게 동거를 하게 되므로 이는 여러분이 한가 할 때 원하는 프로젝트를 따로 업그레이드 하기에 좋겠죠. 2003 버전의 프로젝트를 열면 Visual Studio 2005는 프로젝트를 변환을 하기 전에 복사본을 만들 지를 물어보고 난 이후 변환을 합니다. 변환 중에는 발견되는 모든 문제를 기술한 XML 보고서를 생성 합니다.

언어의 작은 변화

Visual Studio 2005를 간단히 사용해 보기 위해 이전 기사들의 프로젝트 몇 개를 불러와 컴파일 해 보았습니다. Visual Studio 2005는 최신 언어 표준을 따르는 최신 컴파일러 이기 때문에 모든 프로젝트들은 약간의 수정이 필요 했습니다. 대부분의 “새로운” 문법들은 C++ 표준에 올라 간지 꽤 되었으나 Visual Studio는 이제서야 이를 따르게 되었네요.

for loop 안의 지역 변수는 loop 밖에서는 더 이상 유효 하지 않습니다. 전에는 다음과 같이 작성 할 수 있었습니다

for (int i=0; i<max; i++) {

// do something

}

if (i>0) {

// do something else

}

이 예제에서 변수 i 의 scope는 for 문 안으로 되어 있으면서 그 밖에서 다시 사용됩니다. 공식적으로 C++는 이를 허용하지 않으므로 이제는 이 코드를 다음과 같이 수정해야 합니다:

int i; // for loop 밖으로 뺍니다

for (i=0; i<max; i++) {

// do something

}

if (i>0) {

// do something else

}

선언 되지 않은 정적 변수(지역 또는 전역 변수) 들은 더 이상 default로 정수 형을 갖지 않습니다. 이전에는 다음과 같이 쓸 수 있었고

const BUFLEN=255;

컴파일러는 암묵적으로 BUFLEN 에 int 형을 지정했습니다. 암묵적 int 는 이제 허용 되지 않습니다. 반드시 아래와 같이 선언을 해주어야 합니다:

const int BUFLEN=255;

이는 모든 종류의 변수에 적용 됩니다 ? 정적, 전역, 멤버 데이터 그리고 함수의 반환형. int 를 빠트리면 이젠 컴파일러는 “error C4430: 형식 지정자가 없습니다. int로 가정합니다. 참고: C++에서는 기본 int를 지원하지 않습니다.” 라는 에러 메시지를 보냅니다.

C/C++에 대한 변화의 다른 카테고리로 새로운 Safe C 와 Safe C++ 라이브러리를 들을 수 있습니다. 이 라이브러리들은 여러분들이 잘 알고 계시고 애용 하시는 기존의 유행 지난C 런타임(CRT) 함수 보다 안전한 버전을 제공 합니다. strcpy, fopen 등등. Safe C++에 대해서는 다음 기사에서 좀더 다루려고 합니다. 그 때까지 못 기다리시는 분들을 위해 Martyn Lovell 이 쓴 2005년 5월호의 “Safe! Repel Attacks on Your Code with the Visual Studio 2005 Safe C and C++ Libraries”를 소개해 드립니다.

(msdn.microsoft.com/msdnmag/issues/05/05/SafeCandC (영문)).

C++는 그렇다 치고 MFC 는 어떻게 되었을까요? Visual Studio 2005에는 MFC 에는 큰 변화가 없고 이는 제가 전에 말 했던 것처럼 좋은 일이라 할 수 있습니다. 이는 곧 MFC 가 안정적이란 뜻으로 해석 할 수 있습니다. 그러나 CWnd::OnNcHitTest 의 리턴 형이 UINT에서 LRESULT로 바뀌는 등 이과 같은 다른 작은 변화들이 있을 수 있지만 여러분의 기존 MFC 프로그램과 호환이 안 되는 경우는 없을 것으로 보입니다.

Array 페이지 맨 위로

관리 세계로 이동

C+ +와 MFC는 프로그래밍에 있어 이미 안정화된 부분이므로 이 부분에서 업그레이드를 통한 문제가 발생 하리라 생각 되진 않습니다. Visual Studio 2003과 Visual Studio 2005 간의 큰 변화가 들어간 부분은 관리 코드의 영역입니다. 이 변화 중 하나로 여러분들이 많이 읽어 봤을 새 C++/CLI 구문이 있습니다. 이는 Managed Extension 의 버전 V2 라고 생각 하시면 됩니다. 여러분들이 이를 사용하기에 아직 준비가 안 되셨다고 생각하면 /clr:oldSyntax 옵션을 이용해 이전의 Managed Extension 을 그대로 사용하면 됩니다. 이 옵션은 관리 또는 관리/네이티브 프로젝트를 Visual Studio .NET 2003 에서 Visual Studio 2005로 업그레이드 하면 기본으로 사용됩니다.

새 컴파일러에서 관리 코드를 테스트 해보기 위해 지난 2005년 4월호의 기사 "Wrappers: Use Our ManWrap Library to Get the Best of .NET in Native C++ Code"의 ManWrap 라이브러리를 사용 해보았습니다. ManWrap 은 wrapper DLL (regexWrap.dll)과 RegexText, RegexForm 그리고 WordMess 테스트 프로그램으로 구성 되어 있습니다. (그림 1 참조) ManWrap은 작으면서 수행하는 일이 - 관리/비관리(Managed/Unmanaged) 코드를 한 DLL에 넣음으로 - 꽤 복잡한 편이라 이 글에 사용하기에 좋은 예제라 판단이 됩니다. RegexWrap 은 공용 언어 런타임(CLR)의 Regex 클래스를 wrapping 하는 네이티브 DLL 입니다.

Array
그림 1 관리 코드 테스트 하기

기 존의 구문을 그대로 두고 컴파일러를 돌렸습니다. 곧바로 스크린에는 다음과 같은 에러 메시지들이 죽 올라 왔고요. “C3395 ... __declspec(dllexport)를 __clrcall 호출 규칙이 있는 함수에 적용할 수 없습니다.” 뭐시라고?

ManWrap은 순수 네이티브 C++ 코드에 관리 클래스를 wrapping 할 수 있게 해주는 라이브러리 입니다. /clr 옵션 없이도 네이티브 C++ 코드에서 CLR을 호출 할 수 있게 해 주는 것이죠. 예를 들어 Visual C++? 6.0 컴파일러를 사용하는 기존의 프로그램이 있는데 CLR 을 사용하는 특정 기능을 추가 하고자 한다고 합시다. /clr 옵션을 사용하지 않고는 C++에서 관리 클래스를 직접 호출 할 수 없습니다. (Visual C++ 6.0 은 이 옵션을 지원 하지도 않습니다.) 따라서 관리 클래스를 호출 하기 위해서는 이를 네이티브 entry point가 있는 DLL에 wrapping 하는 방법을 사용 해야 합니다.

ManWrap 이 사용하는 꼼수는 _MANAGED 라는 미리 정의된 전처리 심볼을 이용하여 내부 사용이나 외부 사용에 따라 다른 코드를 생성 하게 하는 방법 입니다. 각 wrapper 클래스는 한 개의 데이터 멤버 ? 관리 객체의 핸들 - 를 갖습니다:

#ifdef _MANAGED

# define GCHANDLE(T) gcroot<T*>

#else

# define GCHANDLE(T) intptr_t

#endif

Wrapper 클래스들은 GCHANDLE을 이용해 객체 핸들을 다음과 같이 선언 합니다:

// wrapper for managed Object

class CMObject {

GCHANDLE(Object) m_handle;

};

CMObject가 정의된 헤더 파일은 두 가지 방법으로 컴파일 됩니다. Wapper DLL을 빌드 할 때에는 _MANAGED 가 정의 되도록 /clr 옵션을 주고 컴파일 하면 컴파일러는 m_handle 을 gcroot<Object*>로 판단 하게 됩니다. Wrapper를 호출 하는 네이티브 프로그램을 빌드 할 때에는 _MANAGED 가 정의 되지 않도록 /clr 옵션이 없이 컴파일 하고 이때 컴파일러는 m_handle을 intptr_t로 판단 합니다. 이게 제대로 작동 하는 이유는 gcroot<Object*>가 intptr_t 와 동일한 크기를 갖는다는 것이 보장 되기 때문입니다. Wrapper DLL 만이 그 핸들이 무엇인지 알 수 있습니다. 바깥 (네이티브) 에서 봤을 땐 m_handle 은 HWND, HINSTANCE나 다른 핸들과 비슷한 매직 쿠키에 지나지 않습니다. 단지 여기서 주의 할 점은 복사 생성자와 대입 연산자가 인라인 함수가 아닌 실제 함수이어야 한다는 것입니다. 그래야 실제 wrapper로 호출이 들어가서 그 핸들을 실제 객체로 보고 다루게 됩니다. (intptr_t 핸들을 복사 하면 안되고 gcroot를 통해야 합니다.)

이 핸들을 갖고 있는 것과 더불어 ManWrap은 객체를 생성하고 복사하는 함수들을 정의 합니다. 또한 각 Wrapper 클래스는 ctor 와 -> 연산자를 정의 하여 wrapper가 해당 관리 형으로부터 네이티브 wrapper 객체 들을 생성 하고 접근할 수 있게 금 합니다. 예를 들어 관리 Regex 에서 네이티브 CMRegex를 생성 하는 생성자가 있습니다. 관리 클래스들은 ctor와 -> 연산자를 내부적으로 사용합니다. 그림 2 에서 ManWrap.h의 일부 코드를 보실 수 있습니다. 관리 Method들이 #ifdef _MANAGED 안에 있는 것도 보실 수 있습니다.

그리고 클래스 전체가 WREXPORT로 익스포트 되어 있는데 이 때문에 C3395 에러가 나는 것 입니다. 네이티브 함수와 관리 함수들은 서로 다른 호출 규약을 사용하기 때문에 __declspec(dllexport)를 이용해 관리 method(관리 인자 들을 갖고 있는 method들)들을 익스포트 할 수 없습니다. 충분히 말이 되는 말이죠. 네이티브 DLL에서 관리 함수들을 익스포트 하려는 것 자체가 이상하니까요. 그러나 이는 실제로 익스포트 하는 것이 아닙니다. 이들은 모두 인라인으로 정의 되어 있습니다. 이 관리 method 들은 네이티브 interface에 필요하지도 않고 보이지도 않습니다. 그러나 컴파일러는 이를 모릅니다. Visual Studio 2005가 이전 컴파일러만큼 똑똑 하지 않아 클래스 전체를 익스포트 할 수 있게 허용 한 것으로 보입니다. 아니라면 너무 똑똑 하여 관리 method를 네이트브 코드에서 익스포트 한다는 것 자체가 말이 안된다고 판단 하는 건지.. 어쨌든 Visual Studio 2005는 관리 method를 갖는 클래스가 익스포트 되는 것을 허용하지 않는다는 것입니다.

그럼 이제 어떻게 할까요? 제가 컴파일러에게 명령하고 싶은 것은, “클래스 전체를 익스포트 하되 이 세 method 들만은 제외 시켜라.” 입니다. 다시 말해 특정 method 에 대해서 __declspec(dllexport)를 적용 안되게 하는 방법을 원하는 것이죠. 이런, 그런 옵션은 존재 하지 않는 군요. 따라서 이를 해결 할 수 있는 방법으로 두 가지가 있습니다. : WREXPORT를 클래스 선언에서 제거 하고 이를 각 네이티브 method 앞에 붙이거나 문제를 일으키는 method 들을 완전히 제거 하는 방법입니다. 첫 번째 방법이 더 간단 하여 전 이 방법을 택했습니다. WREXPORT를 method 선언으로 옮기는 것이 좀 반복적이고 새 method를 추가 할 때 WREXPORT를 추가 하는 것을 잊어 버려 실수 할 여지가 많이 보이지만 컴파일러가 알아서 알려 주겠죠.

두 번째 방법으로 정말, 꼭 클래스 전체를 익스포트 하고 싶다면 문제가 되는 관리 복사 생성자와 -> 연산자method 들을 모두 제거 하는 것입니다. 그렇게 하면 -> 연산자를 자동으로 호출 하는 다음과 같은 코드 대신에

// MClass의 내부 wrapper 클래스

(*this)->ManagedMethod();

다음과 같이 코드를 작성 해주셔야 합니다:

(static_cast<MClass*>((Object*)m_handle))->ManagedMethod();

정말 깁니다. 다음과 같은 매크로를 사용하면 타이핑을 좀 덜 수 있겠네요:

THISOBJ(MClass*)->ManagedMethod();

이렇게 해도 약간은 거추장스럽거니와 아직 생성자도 직접 건드려 주셔야 합니다. 지금 설명이 잘 안 와 닿는 다면(많은 독자 분들이 그러시리라 생각 됩니다.) 걱정 마시라. 전 좀더 간단한 방법인 첫 번째 방법을 택했습니다. 그림 3을 보시면 WREXPORT를 모든 method에 추가한 수정된 코드를 보실 수 있습니다. 이렇게 수정 한 후엔 ManWrap은 문제 없이 컴파일 됩니다.

 

ManWrap 실행 하기

코드를 제대로 컴파일 되게 하였는데 이젠 이 코드가 제대로 실행 되게 해야 합니다. 이 부분이 막혔던 부분 입니다. dbgheap.c 속 깊은 곳의 ASSERT 폭탄 입니다:

ASSERTE(_CrtIsValidHeapPointer(pUserData));

으악! 디버그 심볼 없이 시스템 DLL만 잔뜩 보여주는 stack trace는 거의 쓸모가 없었습니다. 이런 버그가 가장 나쁘고 가장 추적하기 힘든 버그입니다. 프로그램이 시스템 코드 깊은 곳 어딘가에서 죽고 무엇이 문제인지 실마리 조차 없는 경우죠. 그래도 잘 찾으면 실마리는 있습니다. Stack trace를 좀더 자세히 들여다 보니 약 50 frame 앞에 제 코드가 g_Allocator라는 정적 ATL 변수를 접근하는 것이 보였습니다. 아하! 이것이 바로 이 문제 해결의 실마리 였습니다.

g_Allocator는 전역으로 선언된 정적 변수 입니다. C++에서 정적 변수 초기화는 언제나 섬세하게 다루어야 하는 일중 하나로 DLL에서 특별히 더욱 그렇습니다. 컴파일러는 DllMain을 호출 하기 전에 정적 변수들을 초기화 하는 CRT 초기화 함수를 호출하도록 코드를 생성 합니다. 네이티브 나라에서는 모든 것이 잘 작동 하나 여러분의 DLL 이 관리 클래스들을 호출 한다면 Loader-lock 문제에 걸릴 수 있습니다: Windows? 는 여러분의 DLL을 로드 하려고 하고 이는 CLR 을 로드 하려고 합니다. 이는 다시 여러분의 DLL을 로드 하려고 하고 ? 즉, loader lock 이라고 알려진 데드락 상황에 처하게 되는 것이죠. 일반적으로 DLL이 로드 될 때에 다른 DLL을 로드 하면 안됩니다. 흔하게 경험할 수 있는 예로는 DllMain 또는 정적 객체 생성자에서 ::MessageBox를 호출하여 진단 메시지를 표시할 경우입니다. 이렇게는 결코 제대로 작동 하지 않죠.

이 loader lock을 피하기 위해 Visual Studio .NET 2003 은 관리 DLL들을 /NOENTRY DLL(DllMain Entry point가 없는 DLL)로 만드는 것을 의무화 했습니다. 그 결과 여러분의 DLL은 _DllMainCRTStartup이 없게 되어 정적 정적 변수들이 초기화가 안됩니다. loader lock은 피 할 수 있지만 벼룩 잡으려다 초가삼간 다 태우는 격이 되어 버린 거죠. 제 2005년 2월 기사에 이 수수께끼에 대한 자세한 설명이 있습니다(msdn.microsoft.com/msdnmag/issues/05/02/CATWork (영문)). MFC와 ATL 에서 정적 객체를 사용 하기 때문에 이는 중요한 문제입니다. DllMain 없이는 ATL 또는 MFC를 사용하는 혼합 DLL 을 만들 수 없으니까요. 이 문제를 해결 하기 위해 친절한 Redmond 사람들은 __crt_dll_initialize 와 __crt_dll_terminate()가 들어 있는 <_vcclrit.h>를 만들어 제공 했습니다.

이 모든 것들이 그냥 거추장스럽게만 들리실 겁니다. 반갑게도 Visual Studio 2005는 혼합 assembly loader-lock 문제를 해결해 출시 되었습니다. 더 이상 _vcclrit.h 나 /NOENTRY 가 없이 혼합 DLL 들을 평소 대로 컴파일 하실 수 있게 되었습니다. 더 자세한 설명은 “http://msdn2.microsoft.com/ko-kr/library/ms173266(vs.80).aspx" 에 “혼합 어셈블리 초기화 " 글을 참조 해보시기 바랍니다.

그러면 왜 ManWrap이 dbgheap.c 에서 죽었을 까요? 이 이유는 이전 프로젝트의 /NOENTRY 가 아직 남아 있었기 때문입니다. 당연히 죽을 상황이 였죠. DllMain도 없었으며 따라서 ATL의 g_Allocator가 초기화 되지 못했습니다. 그래서 바로 /NOENTRY를 없애니, 휴! 제대로 작동 하는 군요.

 

새 모자(hat)로 새로 시작

이 런 어려운 버그들로부터 얻을 수 있는 축복 이라면 그 버그를 고친 후 맛 볼 수 있는 날아 갈 것 같은 좋은 기분 일 겁니다. ManWrap의 세 test 프로그램(RegexTest, RegexForm, and WordMess) 을 성공적으로 컴파일 하고 실행 시킨 후 전 꽤나 싱글 벙글 했죠. 지난 몇 년간을 겨울잠을 자고 오신 분을 위해 설명 드리자면 C++/CLI의 핵심은 ^(hat) 심볼로 표시 되는 tracking handle 이라는 새 타입이라고 할 수 있습니다. 그래서 저는 /clr:oldSyntax(그림 4 참조)를 없애고 ManWrap.h의 * 를 ^ 로 변경 했습니다:

#ifdef _MANAGED

# define GCHANDLE(T) gcroot<T^>

#else

# define GCHANDLE(T) intptr_t

#endif

Array
그림 4 새로운 구문 사용하기

이 렇게 하여 변경하고 컴파일 한 이후 컴파일러가 뱉어 내는 에러를 하나씩 고쳐 나갔습니다. 대부분의 에러는 Mumble *를 Mumble ^로 고치는 정도 였죠. 물론 고쳐야 하는 다른 구문들도 있었습니다. 다음이 ManWarp을 컴파일 하면서 나온 특유의 구문 에러 목록 입니다. C++/CLI 에 능통한 독자들에겐 진부해 보일 지도 모르니 CLI 전문가 이신 분들은 그냥 훑어 보시고 넘기시기 바랍니다.

? 관리 클래스들은 이제 __gc 또는 __value 대신 ref 또는 value로 선언 되어야 합니다. 일반적으로 모든 __managed 키워드들은 문맥에 더 맞는 키워드로 교체 되었습니다.

? Default 인덱서들은 이제 Item 대신 “default” 로 불립니다. 전엔 이랬던 것을:

x = m->Item[name];

이젠 이렇게 해야 합니다:

x = m->default[i];

이는 Regex 라이브러리의 MatchCollection에서처럼 복수의 인덱서들이 있을 때에도 작동 합니다.

MatchCollection* mc;

mc->default[0]; // int

mc->default["alpha"]; // string

? 관리 객체들은 이제 gcnew로 할당 되어야 합니다. 관리 객체를 할당 하는 곳에는 new를 gcnew로 변경 하시기 바랍니다.

? 어떤 변환 들은 더 이상 암시적이지 않습니다. 다음 코드를 보면:

// 네이티브 Entry

void Foo(LPCTSTR lpsz)

{

// 관리 ctor 는 String을 받는다

Mumble *m = new Mumble(lpsz);

}

이전 Managed Extensions 에서는 컴파일러가 Mumble 을 생성 하기 위해 lpsz로 초기화 한 관리 String 객체를 암시적으로 생성 하였습니다. Visual Studio 2005 에서는 다음처럼 String을 명시적으로 할당 해주어야 합니다:

void Foo(LPCTSTR lpsz)

{

Mumble ^m = gcnew Mumble(gcnew String(lpsz));

}

약간의 타이핑이 더 들어 가지만 이전 보다 좀더 의미가 명료 해졌습니다. 저는 언제 어디서 관리 힙에서 할당 하는지 알게 해주기 때문에 gcnew를 좋아 합니다. 암시적 변환은 코드를 단순하고 깨끗하게 보이게 하지만, 경우에 따라서는 오해를 살 수 있을 정도로 간결하기 때문입니다. C++는 상대적으로 low-level 언어에 속하며(전 이점을 좋은 점이라 생각 합니다.) 따라서 코드 뒷면에서 많은 일을 수행 하게 하는 것 보다 눈에 직접 보이게 하는 것이 좋습니다. RegexWrap에서 여기 저기서 String을 생성 하기 때문에 전 타이핑을 덜어주기 위해 다음의 매크로를 작성 했습니다:

#define S(s) (gcnew String(s))

S“Hello, world.” 에서처럼 관리 string의 S modifier처럼 닮아 보이게 만들어 보았습니다. 따라서 이젠 다음처럼 쓸 수 있습니다:

Mumble ^m = gcnew Mumble(S(lpsz));

C++/CLI 는 관리 배열의 구문이 새롭게 바뀌었습니다. 아래의 선언 대신

ManagedType* myarray[];

이젠 이렇게 작성하셔야 합니다:

array <ManagedType^>^ myarray;

전 두 번째 ^ 때문에 처음엔 약간 의아해 했었습니다. 그 것을 빼고 컴파일 하면 “error C3149: 여기에 이 형식을 사용하려면 최상위 '^'이(가) 있어야 합니다.”라는 약간은 애매한 에러가 나옵니다(최상위 뭐??). 그러나 규칙을 이해 하시면 당연히 나야 할 에러죠. 컴파일러는 이 배열이 “array” 키워드의 효능에 의해 관리 된다는 것을 아는데도 왜 최상위 hat이 필요 한지는 저도 확실히 는 잘 모르겠습니다. 그러나 확실한 것은 언어를 만들어 낸 분들이 그렇게 할만한 이유가 있었을 것이고 이 또 말이 되긴 한다는 것이죠. 관리되는 것들은 hat을 갖습니다. 기본 형의 관리 배열에서도 최상위 hat 이 필요 합니다. 예를 들어:

// int 형 관리 배열

array<int>^ foo;

? Count 대신 Length를 이용해 배열의 길이를 얻습니다.

? 관리 NULL 포인터에 대한 검사를 해야 한다면 NULL 대신 nullptr을 사용합니다.

? 템플릿은 관리 클래스와 사용될 때 더 잘 작동 합니다. 이 것이 C++/CLI를 사용해야 하는 가장 중요한 이유 중에 하나 입니다. 관리 클래스와 네이티브 클래스들은 각자 자신만의 구문을 갖게 되고 (overload 된 *를 공유하는 대신) 템플릿 생성자는 이를 쉽게 구분 할 수 있기 때문입니다.

여기서 제가 언급 하지 않은 구문 변화들이 더 있습니다. 이들에 대해선 여러분들도 자연히 알게 되겠죠. 이에 대한 개요로 Stan Lippman이 쓴 "Hello C++/CLI"를 보시는 것을 권해 드립니다. MSDN Magazine의 특별 Visual Studio 2005 판에 있습니다(msdn.microsoft.com/msdnmag/issues/06/00/PureC (영문)). 이 글과 Stan의 다른 기사 "A Baker's Dozen: Thirteen Things You Should Know Before Porting Your Visual C++ .NET Programs to Visual Studio 2005" (msdn.microsoft.com/library/en-us/dnvs05/html/BakerDozen.asp (영문))도 추천 해드립니다.

새로운 구문 때문에 너무 겁 먹지는 마시고요. * 를 ^ 로 /clr:oldSyntac를 /clr로 바꾸기만 하니 나머지는 컴파일러 에러 고치는 수준 이였습니다. ManWrap이 컴파일러를 통과 하고 나니 아무런 문제 없이 작동 하더군요. Redmond 사람들은 이에 대해 자랑스러워 할 만 합니다. 컴파일러가 코드의 정확함을 보장 할 수 있다면 그 보다 좋은 것이 있겠습니까? ^ 로의 변환이 너무 쉽게 끝나 너무 싱글벙글 한 나머지 GCHANDLE을 MANHANDLE로 바꿀까 까지 생각 했지만. 안 하기로 했습니다.

 

두 가지 작은 불평

전체적으로 Visual Studio .NET 2003에서 Visual Studio 2005로의 이동은 큰 문제 없이 진행 되었습니다. 전 Emacs 같은 에디터 이외에는 거들떠 보지도 않는 텍스트 해커 이기 때문에 IDE 에 대해서는 크게 말씀 드릴 것이 없군요. 그러나 Visual Studio 2005에 대해 두 가지 불평이 있습니다. 우선 첫 번째로 “내 문서” 폴더를 제가 필요하지도 않고 쓰지도 않을 폴더로 더럽히더군요. 이것 저것 뒤지다 보니 이 폴더를 다른 곳으로 옮길 수 있는 레지트스리 키를 찾아 대부분의 폴더들을 제 눈에 안 띄는 TEMP 디렉터리로 옮길 수 있습니다. 전 제 컴퓨터의 폴더를 정리 하는데 상당한 노력을 드리는 지라 어떤 권위적인 프로그램이 제 하드디스크를 맘대로 고쳐 버리는 것이 싫더군요. 그래서 경고합니다: 만약 여러분의 프로그램이 폴더를 필요로 한다면 그 폴더들을 어디에 만들지 사용자들이 직접 선택 하게 하십시오.

제 다른 불만은 Visual Studio 2005가 더 이상 sound schemes 을 지원 하지 않는 다는 것입니다. 저도 시끄러운 프로그램을 좋아라 하진 않습니다만 이런 경우엔 유용하더군요. Visual Studio 2003에서는 빌드를 시작시킨 후 다른 창에서 작업을 하러 갑니다. 컴파일이 끝났을 때 나는 소리에 따라 컴파일이 성공 했는지 실패 했는지 바로 알 수 있었으나 Visual Studio 2005 에서는 결과 창을 읽어 봐야 알 수 있죠. 별로 재미 없는 일 중에 하나가 결과 창을 읽어 보는 것 입니다. 경고 하나 더 갑니다: 절대 기존의 기능을 제거 하지 마십시오.

이 두 개의 작은 불만과 /NOENTRY 에러를 제외 하고는 Visual Studio 2005로 업그레이드 하기가 상당히 쉬웠습니다. 아직 업그레이드 하지 않으셨다면 바로 해보세요. 혼합/관리 assembly를 작성 하신다면 더더욱 추천 해드립니다. 새 구문이 더 좋습니다. 물론 어떤 변화에서든 약간의 수정은 필요 합니다. 그러나 한번 적응 되신다면 이보다 좋을 수 없죠.

ManWrap은 MSDN Magazine 웹사이트에서 받으실 수 있습니다. 해당 다운로드는 세 버전으로 구성 되어 있으며 Visual Studio .NET 2003 버전, Visual Studio 2005 버전 용의 이전 C++/CLI 구문을 사용한 버전과 새 C++/CLI 구문을 사용한 버전을 제공 합니다. 즐거운 프로그래밍 하세요~!

C#

출처 : DirectX 9.0 SDK 온라인 도움말
번역 : jacking@korea.com

Tutorial 1: 디바이스의 작성


CreateDevice 튜토리얼 프로젝트에서는, Microsoft? Direct3D?를 초기화 하고, 단순한 청색의 화면을 렌더링하고, 최후에 셧 다운을 한다.

패스

소스의 위치: (SDK 루트)SamplesC#Direct3DTutorialsTutorial1

순서

애플리케이션 윈도우 작성

Microsoft? Windows? 애플리케이션이 실행시에 처음에 꼭 해야되는 것은, 애플리케이션 윈도우의 작성이다. 그 때문에 다음 샘플 코드의 Main() 함수에서는, 애플리케이션에서 정의한 CreateDevice 클래스의 컨스트럭션를 제일 처음에 호출한다. 이 클래스는, 디스플레이 윈도우의 사이즈, 윈도의 외형, 윈도우의 아이콘을 설정한다.

CreateDevice는, Microsoft .NET Framework에서 사용되어지는 System.Windows.Forms.Form 클래스로부터 만들어져, 애플리케이션 내의 윈도우를 나타낸다.

using System;

using System.Drawing;

using System.Windows.Forms;

using Microsoft.DirectX;

using Microsoft.DirectX.Direct3D;

namespace DeviceTutorial

{

    public class CreateDevice : Form

    {

        // Global variables for this project

        Device device = null; // Rendering device

        public CreateDevice()

        {

            // 폼의 크기를 지정

            this.ClientSize = new System.Drawing.Size(400,300);

            // 캡션 지정

            this.Text = "Direct3D Tutorial 01: CreateDevice";

            // 아이콘 지정

            this.Icon = new Icon(this.GetType(), "directx.ico");

        }

        .

        .

        .

    }

    .

    .

    .

}

Direct3D 오브젝트의 초기화

애플리케이션 윈도우를 작성하면, 장면 렌더링에 사용하는 Direct3D 오브젝트의 초기화를 하는 것이 가능하다. 이 처리에서는, 오브젝트를 작성하고, 프리젠테이션 파라메터를 설정하고, 최후에 Direct3D 디바이스를 작성한다.

public bool InitializeGraphics()

{

    try

    {

        // Create a PresentParameters object

        PresentParameters presentParams = new PresentParameters();

        // Don't run full screen

        presentParams.Windowed = true;

        // Discard the frames

        presentParams.SwapEffect = SwapEffect.Discard;

        // Instantiate a device

        device = new Device(0,

        DeviceType.Hardware,

        this,

        CreateFlags.SoftwareVertexProcessing, presentParams);

        return true;

    }

    catch { return false; }

}

상기의 샘플 코드는, 윈도우 표시특성의 설정에 사용하는 PresentParameters 오브젝트에 의해 달라진다. 예를들면, Windowed 프로퍼티를 TRUE로 설정하면, 표시되는 윈도우 사이즈는 풀 스크린보자 작게된다. 이 디폴트의 작은 윈도우 포맷에는, 메뉴와 자식 윈도우는 표시되지 않지만, 윈도우 화한 애플리케이션에 통상 표시 되는, [최소화], [최대화], [닫기] 버튼은 표시된다.
이 경우, 버퍼 메모리를 시스템 메모리로 빠르게 바꾸어 원래대로 되돌리는 기능은, SwapEffect.Discard 플라그에 의해 무효하게 된다. Windowed 프로퍼티가 FALSE의 경우에는, 만들어진 윈도우는, 최전면(最前面 )의 윈도우를 제외한 모든 윈도우 보다도 앞에 배치되어, 윈도우가 액티브로 되지 않았도 최전면 그 대로 표시되어진다.
초기화 순서의 마지막 단계는, Direct3D 디바이스의 작성이다. 이 예에서는, Device(Int32,DeviceType,Control,CreateFlags,PresentParameters)f오의 입력 플라그로, 하드웨어 디바이스를 우선하고, 정점 처리는 소프트웨어로 실행하도록 지정한다. CreateFlags.HardwareVertexProcessing를 설정하고
하드웨어 정점 처리를 사용하도록 시스템에 지시하면, 하드웨어 정점처리를 서포트 하는 비디오 카드에서는 퍼포먼스가 대폭적으로 향상 된다.

Direct3D 오브젝트의 렌더링

애플리케이션은, Application.DoEvents 메소드를 사용하는 루프 내에서 계속 동작한다. 이 경우, 이 메소드는 frm 이라는 이름의 CreateDevice 오브젝트를 인수로 취한다. DoEvents는, 표준적인 Windows 애플리케이션 메시지 루프를 현재의 스레드 위에서 실행한다.

static void Main()

{

    using (CreateDevice frm = new CreateDevice())

    {

        if (!frm.InitializeGraphics()) // Initialize Direct3D

        {

            MessageBox.Show("Could not initialize Direct3D. This tutorial will exit.");

            return();

        }

        frm.Show();

        // While the form is still valid, render and process messages

        while(frm.Created)

        {

            frm.Render();

            Application.DoEvents();

        }

    }

}

만들어진 CreateDevice Form 오브젝트가 유효한 동안은, 애플리케이션에서 정의한 Render 메소드를 호출하고, Direct3D 오브젝트를 렌더링 한다.
먼저, Device.Clear 메소드에서, 뷰 포트(열려 있는 윈도우)를 균일한 청색으로 설정한다. Device,BeginScene 메소드를 사용하여, 장면 렌더링을 시작한다. 렌더링이 끝났다면, EndScene 메소드와 Present 메소드를 계속해서 호출하고, 장면(Scene)을 종료 한다.

private void Render()

{

    if (device == null)

    return;

    //Clear the backbuffer to a blue color (ARGB = 000000ff)

    device.Clear(ClearFlags.Target, System.Drawing.Color.Blue, 1.0f, 0);

    //Begin the scene

    device.BeginScene();

    // Rendering of scene objects can happen here

    //End the scene

    device.EndScene();

    device.Present();

}

[번역] DirectX 9.0( Managed - .Net 기반) Direct3D Tutorial ? 2
출처 : DirectX 9.0 SDK 온라인 도움말
번역 : jacking@korea.com

Tutirial 2: 정점 렌더링


Microsoft? Direct3D?에서 만들었던 애플리케이션은, 정점을 사용하여 지오메트릭 형태를 묘화(描? ) 한다. 각 3차원(3D) Scene은, 이와 같은 지오메트릭 형태를 하나 또는 복수 포함하고 있다. Vertices tutorial 프로젝트에서는, 처음에 “Tutorial 1: 디바이스의 작성”의 초기화 순서를 실행하고, 계속해서 가장 단순한 도형인 삼각형을 만들어 디스플레이에 렌더링 한다.

패스

소스의 장소 : (SDK 루트)SamplesC#Direct3DTutorialsTutorial2

순서

이 tutorial에서는 3개의 정점을 사용하여 2D의 삼각형을 렌더링 한다. 여기서는, 정점 버퍼의 개념을 사용하고 있다. 정점 버퍼는, 통상은 정점을 저장 및 렌더링 할 때에 사용하는 VertexBuffer 오브젝트이다.

정점은, CustomVertex 커스텀 정점 클래스로 이용 가능한 구조체를 사용하여 여러가지 방법으로 정의 할 수 있다. 이 tutorial에서는 정점은 트랜스폼 되어 있으므로, 정점은 2D 윈도우 좌표로 표시되어 있다. 이를 테면 정점(0,0) 은 좌 상단 이고, 양의 x 축은 우방향, 양의 y 축은 아래방향으로 되어 있다. 또 이들 정점은 Direct3D의 라이팅도 사용하지 않지만, 대신에 독자의 디퓨즈(difuse) 색을 제공한다. 이 같은 특징은, 다음의 코드에서 나타나듯이 CustomVertex.TransformedColored 구조체에서 정점 버퍼를 초기화 하는것으로 제공한다.

public class Vertices : Form

{

    // Global variables for this project

    Device device = null; // Rendering device

    VertexBuffer vertexBuffer = null;

    .

    .

    .

    public void OnCreateDevice(object sender, EventArgs e)

    {

        Device dev = (Device)sender;

        // Now create the vertex buffer

        vertexBuffer = new VertexBuffer(typeof(CustomVertex.TransformedColored), 3, dev, 0, CustomVertex.TransformedColored.Format, Pool.Default);

        vertexBuffer.Created += new System.EventHandler(this.OnCreateVertexBuffer);

        this.OnCreateVertexBuffer(vertexBuffer, null);

    }

    public void OnCreateVertexBuffer(object sender, EventArgs e)

    {

        VertexBuffer vb = (VertexBuffer)sender;

        GraphicsStream stm = vb.Lock(0, 0, 0);

        CustomVertex.TransformedColored[] verts = new CustomVertex.TransformedColored[3];

        .

        .

        .

        vb.Unlock();

    }

}

위의 코드에서는, VertexBuffer.Lock 메소드를 호출하고, 정점 버퍼의 데이터에 CPU가 직접 액세스 할 수 있도록 하고 있다. Lock을 호출하는 경우는, 그럴 때마다 뒤에 UnLock를 호출하지 않으면 안된다. Lock와 UnLock에 대한 자세한 사항은 “3. 리소스의 Lock”를 참조 한다.

위의 코드에서 만들고 있는 verts 오브젝트는, 3개의 TransformedColored 구조체의 배열이며, 삼각형 3개의 정점마다 하나의 구조체를 사용한다.
각 정점 구조체의 (X,Y,Z) 필드, Rhw 필드, Color 필드는, 다음 코드에서 초기화 된다. 필요한 ARGB 32 비트형식으로 색 데이터를 제공 할 때에는 ToArgb 메소드를 사용한다.

verts[0].X=150;verts[0].Y=50;verts[0].Z=0.5f; verts[0].Rhw=1;

verts[0].Color = System.Drawing.Color.Aqua.ToArgb();

verts[1].X=250;verts[1].Y=250;verts[1].Z=0.5f;verts[1].Rhw=1;

verts[1].Color = System.Drawing.Color.Brown.ToArgb();

verts[2].X=50;verts[2].Y=250;verts[2].Z=0.5f; verts[2].Rhw=1;

verts[2].Color = System.Drawing.Color.LightPink.ToArgb();

stm.Write(verts);

위의 2개의 코드에서는, GraphicsStream 클래스(stm오브젝트)를 사용했다.
쉐이더를 사용하는 입력 레지스터에 데이터를 바이드 할 때에는, 그래픽스 스트림을 사용한다. 이 코드에서는 GraphicsStream 오브젝트가 정점 버퍼에 직접 액세스 할 수 있도록 하고 있다. 바꿔 말하면, 정점 버퍼의 내용을 그래픽스 스트림에 두고, 스트림 내의 현재의 위치를 정점 버퍼의 길이 만큼 나아가고 있다.

다음 코드에서는, 스트림 소스, 정점 포맷, Device 오브젝트의 프리미티브 렌더링을 초기화 하도록, tutorial 1의 private 메소드 Render을 확장하는 방법을 나타낸다. Tutorial 1의 경우와 같게, 현재의 Vertices 오브젝트(frm)이 유효(有?)한 동안 또는 시스템의 묘화(描?) 이벤트가 트리거 되면, Render는 디스플레이에 렌더링을 한다.

private void Render()

{

    if (device == null)

    return;

    // Clear the back buffer to a blue color (ARGB = 000000ff)

    device.Clear(ClearFlags.Target, System.Drawing.Color.Blue, 1.0f, 0);

    // Begin the scene

    device.BeginScene();

    // New for Tutorial 2

    device.SetStreamSource( 0, vertexBuffer, 0);

    device.VertexFormat = CustomVertex.TransformedColored.Format;

    device.DrawPrimitives(PrimitiveType.TriangleList, 0, 1);

    // End the scene

    device.EndScene();

    device.Present();

}

[번역] DirectX 9.0( Managed - .Net 기반) Direct3D Tutorial ? 3
출처 : DirectX 9.0 SDK 온라인 도움말
번역 : jacking@korea.com

Tutorial 3: 행렬의 사용법


Matrices tutotial 프로젝트에서는, 행렬의 개념과 그 사용법을 나타낸다.
행렬은, 정점의 좌표를 트랜스 폼 하기도 하고, 카메라와 뷰 포트를 설정하는 등에 사용한다.

패스
소스의 위치 : (SDK 루트)SamplesC#Direct3DTutorialsTutorial3

순서

주 : Microsoft? Direct3D? 의 초기화、Microsoft Windows? 메시지의 처리, 렌더링, 셧 다운에 대해서는 ‘Tutorial 1 : 디바이스의 작성’을 참고 바람.

‘Tutorial 2 : 정점 렌더링’에서는, 2D의 정점을 렌더링 했어 삼각형을 묘화 했다. 이 tutorial에서는, 3D 정점 트랜스 폼을 사용하여 삼각형을 회전하는 처리를, Tutorial의 코드에 추가한다.

이 프로젝트에서는 삼각형 오브젝트에 트랜스 폼을 적용하기 위해, Tutorial 2에서 트랜스 폼 한 2D 윈도우 좌표를 사용하는 것이 아닌, 이하의 코드에서 나타내는 것과 같이, CustomVertex.PositionColored 구조체로 정점 버퍼를 초기화 한다.

Device dev = (Device)sender;

// Now create the vertex buffer.

vertexBuffer = new VertexBuffer(typeof(CustomVertex.PositionColored), 3, dev, 0, CustomVertex.PositionColored.Format, Pool.Default);

그 위에, private의 Render 메소드에 대해서, 디바이스의 정점 포맷을 PositionColored 포맷으로 초기화 한다. 지오메트리를 렌더링 하기 전에, 애플리케이션에서 정의 한 SetupMatrices 메소드를 Render에서 호출한다.
이 메소드는, 삼각형 오브젝트의 3D 행렬 트랜스 폼을 작성하고 설정한다.

private void Render()

{

    .

    .

    .

    // Set up the world, view, and projection matrices.

    SetupMatrices();

    device.SetStreamSource(0, vertexBuffer, 0);

    device.VertexFormat = CustomVertex.PositionColored.Format;

    device.DrawPrimitives(PrimitiveType.TriangleList, 0, 1);

    // End the scene.

    device.EndScene();

    device.Present();

}

통상, 3D Scene에는 3 종류의 트랜스 폼이 설정된다. 트랜스 폼은 모두,
Device.Transform 프로퍼티로부터 액세스 하는 Transforms 오브젝트의 프로퍼티로써 정의된다. 어느 것이라도 Direct3D의 표준적인 왼손좌표계를 사용한다. ‘3D 좌표계’를 참조한다.

  1. 월드 트랜스 폼 행렬 : 이 tutorial에서는, 다음의 샘플 코드에서 나타나듯이 Matrix.RotateY 메소드를 호출하는 것으로, y축을 중심으로 했어 삼각형을 회전 시킨다. Matric는 범용의 Microsoft.DirectX 이름공간의 일부로 있다는 것에 주의 한다.
    이 호출에서는, 시스템의 Environment.TickCount 메소드의 값을 스케일링 값으로 나누었던 것을, 라디안 단위의 RotateY 인수로서 사용한다.
    이 순서에 의해, 삼각형은 y축의 주변을 부드럽게 회전하는 것 같이 된다.
  2. 뷰 트랜스폼 행렬 : 뷰 트랜스폼 행렬은, Scene 카메라 뷰를 생성한다. 이 샘플 코드에서는, Matrix.RotateY 메소드를 호출하는 것으로 행한다.
    3개의 Vector3 벡터가, 왼손(LH) 좌표계 뷰 행렬을 작성하는 LookAtLH 메소드의 인수를 형성한다. 3개의 벡터는, 각각 눈의 위치,
    카메라의 주시대상(이 경우는 원점), 현재의 월드의 위쪽을 나타낸다.
  3. 투영 트랜스 폼 행렬 : 투영 트랜스폼 행렬은, 3D 뷰 공간에서 2D 뷰 포트 공간에 지오메트리를 트랜스 폼 하는 방법을 정의 한다. 이 샘플
    코드에서는 , 왼손 좌표계의 PerspectiveFovLH 메소드로부터 반환되는 행렬로부터 형성한다. 메소드에 대하는 인수는, 시야각(라디안 단위), aspect 비( 공간의 높이를 폭으로 나눈 값), 가까운 클립면의 거리, 먼 클립면의 공간이다.

트랜스 폼 행렬을 작성하는 순서는, Scene 내의 오브젝트의 레이 아웃에는 영향을 주지 않는다. 정확히, Direct3D는, 상기의 순서로 Scene에 행렬을 적용한다.

private void SetupMatrices()

{

    // WORLD MATRIX: Just rotate the object about the y-axis.

    device.Transform.World = Matrix.RotationY(Environment.TickCount / 150.0f );

    // VIEW MATRIX: A view matrix can be defined given an eye point,

    // a point to look at, and a direction for which way is up. Here, set the

    // eye five units back along the z-axis and up three units, look at the

    // origin, and define "up" to be in the y-direction.

    device.Transform.View = Matrix.LookAtLH(

    new Vector3( 0.0f, 3.0f,-5.0f ),

    new Vector3( 0.0f, 0.0f, 0.0f ),

    new Vector3( 0.0f, 1.0f, 0.0f ) );

    // PROJECTION MATRIX: Set up a perspective transform (which

    // transforms geometry from 3-D view space to 2-D viewport space), with

    // a perspective divide making objects smaller in the distance. To build

    // a perspective transform, use the field of view (1/4 pi is common),

    // the aspect ratio, and the near and far clipping planes (which define

    // at what distances geometry should be no longer be rendered).

    device.Transform.Projection = Matrix.PerspectiveFovLH( (float)Math.PI / 4, 1.0f, 1.0f, 100.0f );

}

렌더링의 특성은, RenderStates 클래스의 프로퍼티를 설정하는 것으로 제어 한다. 다음의 코드에서 나타나듯이, 이 처리는 애플리케이션에서 정의한 메소드 OnResetDevice에서 하고 있다.

public void OnResetDevice(object sender, EventArgs e)

{

    Device dev = (Device)sender;

    // Turn off culling, so the user sees the front and back of the triangle

    dev.RenderState.CullMode = Cull.None;

    // Turn off Direct3D lighting, since object provides its own vertex colors

    dev.RenderState.Lighting = false;

}

이 경우, 후면 컬링과 Direct3D 라이팅은 어느쪽이라도 off로 되어 있다. 이 설정에 의해 전심도(全深度)로 3D 오브젝트는 오브젝트 자체의 색으로 표신된다.

Tutorial 4: Material과 Light의 사용법


Lights Tutorial 프로젝트에서는, Light와 Material을 추가했어, 보다 리얼한
Microsoft? Direct3D? 오브젝트를 작성한다. Scene 내의 각 오브젝트는,
사용하는 Light타입에 근거를 두고 Light가 주어진다.
Material은, 폴리곤이 ambient light와 difuse light를 반사하는 방법, 폴리곤
의 Specular high light의 표현방법, 폴리곤이 빛을 반사하는가 어떤가를 나타낸다.

위치

소스의 위치 : (SDK 루트)SamplesC#Direct3DTutorialsTutorial4

순서

’Tutorial 3 : 행렬의 사용법”에서는, 오브젝트의 정점을 3D로 트랜스 폼 했었다. 이 Tutorial에서는, Material과 Light의 작성을 Tutorial 3의 코드에
추가한다.

심도 스텐실 초기화

이 프로젝트에서는, 다음 코드에서 나타나듯이, z 버퍼(심도 버퍼)와 심도
스텐실을 사용하도록 하는 초기화 순서도 Tutorial 3의 초기화 처리에 추가
한다. 심도 스텐실을 사용하면, 렌더링 한 Image의 일 부분을 마스크로
가려 표시되지 않는 하는 것이 가능하다. 여기서는 최초에 심도 스텐실을
유효하게 한 후. 포맷을 16비트 z 버퍼 심도로 설정한다.

public bool InitializeGraphics()

{

    .

    .

    .

    // Turn on a depth stencil

    presentParams.EnableAutoDepthStencil = true;

    // Set the stencil format

    presentParams.AutoDepthStencilFormat = DepthFormat.D16;

    .

    .

    .

}

정점버퍼와 렌더링 State의 초기화

Light를 사용하기 위한 조건의 하나는, 각 Surface가 법선 벡터를 가지고 있어야 되는 것 이다. 이 프로젝트에서는, 이제까지 Tutorial과는 다른 커스텀 정점 타입인 PositionNormal 구조체를 사용한다. Direct3D는, 이 구조체에 포함되는 3D 위치와 Surface 법선을 사용하여, 내부적인 라이팅 계산을 한다.

public void OnCreateDevice(object sender, EventArgs e)

{

    Device dev = (Device)sender;

    // Now create the vertex buffer

    vertexBuffer = new VertexBuffer(typeof(CustomVertex.PositionNormal), 100, dev, Usage.WriteOnly, CustomVertex.PositionNormal.Format, Pool.Default);

    vertexBuffer.Created += new System.EventHandler(this.OnCreateVertexBuffer);

    this.OnCreateVertexBuffer(vertexBuffer, null);

}

다음에 나타나듯이, 이 샘플 코드에서는, RenderStates 프로퍼티를 사용하여 Scene 지오메트리를 효율 좋게 저장 할 수 있도록 z 버퍼(심도 버퍼)의 사용을 유효하게 하고, Direct3d 라이팅도 유효하게 하고 있다.

public void OnResetDevice(object sender, EventArgs e)

{

    Device dev = (Device)sender;

    // Turn off culling, so the user sees the front and back of the triangle

    device.RenderState.CullMode = Cull.None;

    // Turn on the z-buffer

    device.RenderState.ZBufferEnable = true;

    device.RenderState.Lighting = true; // Make sure lighting is enabled

}

원주 오브젝트의 작성

디바이스의 초기화( 위에 나타낸 OnCreateDevice)에서는, 애플리케이션에서 정의한 OnCreateVertexBuffer 메소드를 호출하여, 원주 오브젝트를 만들고 있다, 다음 샘플 코드에서 나타나듯이 정점 버퍼를 초기화 하여 원주 포인트를 저장한 후, 원주 위의 각 포인트의 위치와 법선을 정점 버퍼에 로드 하고있다.

public void OnCreateVertexBuffer(object sender, EventArgs e)

{

    VertexBuffer vb = (VertexBuffer)sender;

    // Create and lock a vertex buffer (which will return the structures)

    CustomVertex.PositionNormal[] verts = (CustomVertex.PositionNormal[])vb.Lock(0,0);

    for (int i = 0; i < 50; i++)

    {

        // Fill up the structs

        float theta = (float)(2 * Math.PI * i) / 49;

        verts[2 * i].SetPosition(new Vector3( (float)Math.Sin(theta), -1, (float)Math.Cos(theta)));

        verts[2 * i].SetNormal(new Vector3( (float)Math.Sin(theta), 0, (float)Math.Cos(theta)));

        verts[2 * i + 1].SetPosition(new Vector3( (float)Math.Sin(theta), 1, (float)Math.Cos(theta)));

        verts[2 * i + 1].SetNormal(new Vector3( (float)Math.Sin(theta), 0, (float)Math.Cos(theta)));

    }

    // Unlock (and copy) the data

    vb.Unlock();

}

Tutorial 3에서 나타나듯이, SetupMatrices private 메소드의 월드 트랜스
폼 행열을 사용하여, 원주를 회전시킨다.

Material의 작성

Material은, 지오메트리 오브젝트에 light가 비추어 졌을 때, 지오메트리
오브젝트의 Surface로부터 반사되는 색을 정의 한다. 다음 샘픔 코드에서는,
Material 구조체를 사용하여, 백색의 Material을 만들고 있다.
이 Material의 diffuse 색 프로퍼티와 Ambient 색 프로퍼티는, 백색으로
설정된다. 이 호출을 한 후, 색 프로퍼티에 다른 값을 설정 할 때까지는,
이 Material을 사용하여 각 프리미티브가 렌더링 된다.

private void SetupLights()

{

    System.Drawing.Color col = System.Drawing.Color.White;

    // Set up a material. The material here just has the diffuse and ambient

    // colors set to white. Note that only one material can be used at a time.

    Direct3D.Material mtrl = new Direct3D.Material();

    device.Material = mtrl;

    .

    .

    .

}

라이트의 작성

Direct3D에서 이용 할 수 있는 light는, Point Light, Directional Light, Spot Light의 3 종류가 있다. 이 Tutorial 프로젝트에서는, 빛이 한방향을 비추어주는 Directional light을 만들고, light의 방향을 주기적으로 변화 시킨다. 다음 샘플 코드에서는, Light 오브젝트를 사용하여, 어두운 청록색의 Directional light을 만들고 있다.
Scene 내의 모든 오브젝트는, 낮은 레벨의 모노크롬(클레어)의 Ambient light에 의해서도 비추어진다.

private void SetupLights()

{

    .

    .

    .

    // Set up a colored directional light, with an oscillating direction.

    // Note that many lights may be active at a time (but each one slows down

    // the rendering of the scene). However, here just one is used.

    device.Lights[0].Type = LightType.Directional;

    device.Lights[0].Diffuse = System.Drawing.Color.DarkTurquoise;

    device.Lights[0].Direction = new Vector3( (float)Math.Cos(Environment.TickCount / 250.0f), 1.0f, (float)Math.Sin(Environment.TickCount / 250.0f));

    device.Lights[0].Commit(); // Let Direct3D know about the light

    device.Lights[0].Enabled = true; // Turn it on

    // Finally, turn on some ambient light.

    // Ambient light is light that scatters and lights all objects evenly.

    device.RenderState.Ambient = System.Drawing.Color.FromArgb(0x202020);

    .

    .

    .

    .

Tutorial 5 : 텍스쳐 맵의 사용법


Textures Tutorial 프로젝트에서는, Microsoft? Direct3D? 오브젝트에 텍스쳐를 추가 한다.
light와 material에 의해 Scene는 더욱 리얼하게 되었지만, 가장 리얼함이 증가하는 것은 Surface에 텍스쳐를 추가할 때 이다. 텍스쳐는, Surface에 쏙 입혀진 벽지라고도 생각 할 수 있다. 예를들면, 나무 텍스쳐를 입방체에 붙이는 것에 의해, 그 입방체가 나무와 같이 보이는 것이 가능하다. 이 Tutorial프로젝트에서는, ‘Tutorial 4 : material와 light 사용법’에서 작성한 원주 오브젝트에 ‘바나나 껍질’의 텍스쳐를 추가한다.

위치

소스의 위치 : (SDK 루트)SamplesC#Direct3DTutorialsTutorial5

순서

‘Tutorial 4 : Material와 light 사용법’에서는, Direct3D 오브젝트에 Material과 light를 작성했다. 이 Tutorial에서는, Tutorial 4의 코드에 텍스쳐를 로드 하고, 정점을 설정하고, 텍스쳐를 갖춘 오브젝트를 표시하는 순서를 추가한다.

텍스쳐의 작성

텍스쳐를 사용 할 때에는, 커스텀 정점 포맷으로 텍스쳐의 좌표를 가진 정점 버퍼를 작성할 필요가 있다. 텍스쳐 좌표는, 프리미티브 내의 각 벡터에 대해서 텍스쳐를 배치하는 장소를 Direct3D에 가리킨다. 텍스쳐 좌표의 범위는 0.0 부터 1.0 까지로, (0.0, 0.0)은 텍스쳐의 좌상을 나타내고, (1.0, 1.0)은 텍스쳐의 우하를 나타낸다.

새로운 Texture 오브젝트를 초기화 한 후 , 다음 샘플 코드에서는, PositionNormalTextured 구조체를 사용하여 정점 버퍼 포맷을 설정 하고 있다. 이 초기화를 하면, 정점 버퍼는, (x, y, z) 위치와 (x, y, z) 법선 데이터 및 1 셋트의 (tu, tv) 텍스쳐 좌표를 받아 들이는 것이 가능하도록 된다.

public class Textures : Form

{

    // Our global variables for this project

    Device device = null; // Our rendering device

    VertexBuffer vertexBuffer = null;

    Texture texture = null;

    .

    .

    .

    public void OnCreateDevice(object sender, EventArgs e)

    {

        Device dev = (Device)sender;

        // Now create the vertex buffer

        vertexBuffer = new VertexBuffer(

        typeof(CustomVertex.PositionNormalTextured), 100, dev, Usage.WriteOnly, CustomVertex.PositionNormalTextured.Format, Pool.Default);

        vertexBuffer.Created += new System.EventHandler(this.OnCreateVertexBuffer);

        this.OnCreateVertexBuffer(vertexBuffer, null);

    }

}

텍스쳐 데이터 로드

렌더링 State의 설정(디스플레이 인수)는, Tutorial 4와 거의 같다.
정확하게, Direct3D의 라이팅이 무효하게 되어 있는 원주 오브젝트의 색이 표시되는 것과 TextureLoader.FromFile 메소드를 사용하여 비트맵 파일로부터 “바나나 껍질” 텍스쳐를 로드라고 있는 것이 다르다.

public void OnResetDevice(object sender, EventArgs e)

{

    Device dev = (Device)sender;

    // Turn off culling, so the user sees the front and back of the triangle

    dev.RenderState.CullMode = Cull.None;

    // Turn off Direct3D lighting

    dev.RenderState.Lighting = false;

    // Turn on the z-buffer

    dev.RenderState.ZBufferEnable = true;

    // Now create the texture

    texture = TextureLoader.FromFile(dev,

    Application.StartupPath + @"....banana.bmp");

}

다음의 샘플 코드에서 나타나듯이, 원주 오브젝트의 프로퍼티에서 정점 버퍼를 로드하는 순서는, Tutorial 4의 애플리케이션 정의 메소드 OnCreateVertexBuffer 와 같다.
정확하게, 각 포인트에 대한 텍스쳐 좌표 (tu, tv)가 추가되어 있다. 이 순서에 의해, 렌더링하면 텍스쳐는 원주의 주변을 매끄럽게 둘러싸, 텍스쳐는 원래의 원주에 고정되어진 채로 된다.

public void OnCreateVertexBuffer(object sender, EventArgs e)

{

    VertexBuffer vb = (VertexBuffer)sender;

    // Create a vertex buffer

    // Lock the buffer (which will return the structures)

    CustomVertex.PositionNormalTextured[] verts = (CustomVertex.PositionNormalTextured[])vb.Lock(0,0);

    for (int i = 0; i < 50; i++)

    {

        // Fill up the structures

        float theta = (float)(2 * Math.PI * i) / 49;

        verts[2 * i].SetPosition( new Vector3( (float)Math.Sin(theta), -1, (float)Math.Cos(theta) ) );

        verts[2 * i].SetNormal( new Vector3( (float)Math.Sin(theta), 0, (float)Math.Cos(theta) ) );

        verts[2 * i].Tu = ((float)i)/(50-1);

        verts[2 * i].Tv = 1.0f;

        verts[2 * i + 1].SetPosition( new Vector3((float)Math.Sin(theta), 1, (float)Math.Cos(theta) ) );

        verts[2 * i + 1].SetNormal( new Vector3((float)Math.Sin(theta), 0, (float)Math.Cos(theta) ) );

        verts[2 * i + 1].Tu = ((float)i)/(50-1);

        verts[2 * i + 1].Tv = 0.0f;

    }

}

Scene 렌더링

텍스쳐 스테이지에 의해, 텍스쳐를 렌더링 하는 동작을 정의 할 수 있다. 예를 들면, 복수의 텍스쳐를 하나씩 블렌딩 할 수 있다. 이 Tutorial 프로젝트에서는, Tutorial 4의 private 메소드 Render에서 시작하여, Direct3D 디바이스가 렌더링에 사용하는 텍스쳐 스체이지를 0으로 설정한다. 하나의 디바이스는 최댜 8개의 텍스쳐를 가지는 것이 가능하므로, 최대 스테이지는 7 이다. 정확히 이 Tutorial 프로젝트에서는, 다만 하나의 텍스쳐를 사용하고, 그것을 스테이지 0에 배치한다. 0은, Device.SetTexture 메소드의 stage 인수 및 Device.TextureState 프로퍼티에 대한 배열값 양쪽이 사용되고 있다.

텍스쳐의 렌더링 방법을 제어 할 때 에는, TextureOperation 열거와 TextureArgument 열거의 정수를 사용한다. 예를들면, 이 코드에서의 설정에 의해, 출력 칼라는 텍스쳐 칼라와 diffuse 색을 합한 것이 된다. 텍스쳐에 있어 칼라 블렌딩의 자세한 것에 대해서는 ’텍스쳐와 정점 칼라 블렌드’를 참고 한다.
private void Render()

{

    .

    .

    .

    // Set up our texture. Using textures introduces the texture stage states,

    // which govern how textures get blended together (in the case of multiple

    // textures) and lighting information. In this case, modulate (blend)

    // the texture with the diffuse color of the vertices.

    device.SetTexture(0,texture);

    device.TextureState[0].ColorOperation = TextureOperation.Modulate;

    device.TextureState[0].ColorArgument1 = TextureArgument.TextureColor;

    device.TextureState[0].ColorArgument2 = TextureArgument.Diffuse;

    device.TextureState[0].AlphaOperation = TextureOperation.Disable;

    .

    .

    .

Tutorial 6 : Meshe 사용법


복잡한 지오메트리는 통상 3D 모델링 소프트웨어를 사용하여 모델이 되어지며, 파일에 보존된다. 예를 들어, .x 파일 포맷등이 있다. Microsoft? Direct3D?에서는, Mesh를 사용하여 이런 파일로부터 오브젝트를 로드 한다. Meshes Tutorial 프로젝트에서는, Mesh에 관한 토픽을 설명하고, Mesh를 로드, 렌더링, 언 로드 하는 방법을 나타낸다.

Mesh에는, 복잡한 모델에 대한 데이터가 포함 되어 있다. Meshe는, 텍스쳐랑 Material등의 리소스, 위치 데이터랑 순접성 데이터등의 속성을 보존하고, 추상적인 데이터 콘테이너 이다. Mesh는 약간 복잡하지만, Direct3D에는 Mesh의 사용을 쉽게 하는 함수가 포함되어 있다.

위치
소스의 위치 : (SDK 루트)SamplesC#Direct3DTutorialsTutorial6

순서

”Tutorial 5 : 텍스쳐 맵의 사용법”에서는 Direct3D 오브젝트에 텍스쳐를 만들었다.
이 Tutorial에서는 파일로부터 Mesh를 취급하는 순서를 Tutorial 5의 코드에 추가한다.
한편으로, 정점 버퍼의 작성에 사용되었던 애플리케이션 정의 메소드 OnCreateDevice와 그것에 관련하는 디바이스 작성의 호출은 제거 한다. 여기에서 사용하는 순서는, Mesh 함수의 호출 중에서 비명시적으로 정점 버퍼를 취급하고 있다.

Meshe 오브젝트의 초기화

이 Tutorial 프로젝트에서는, 이하에 나타나듯이, 복수차원의 Material과 텍스쳐 배열을 초기화 한다. System.ComponentModel 이름공간 및 System.IO 이름 공간의 using 선언에도 주의한다.

using System;

using System.Drawing;

using System.ComponentModel;

using System.Windows.Forms;

using System.IO;

using Microsoft.DirectX;

using Microsoft.DirectX.Direct3D;

using Direct3D = Microsoft.DirectX.Direct3D;

public class Meshes : Form

{

    Device device = null; // Rendering device

    Mesh mesh = null; // Mesh object in system memory

    Direct3D.Material[] meshMaterials; // Materials for the mesh

    Texture[] meshTextures; // Textures for the mesh

    PresentParameters presentParams = new PresentParameters();

    .

    .

    .

}

애플리케이션에서 정의한 OnResetDevice 함수는, Material 구조체에 Mesh 파일 데이터를 캡쳐하기 위해서 사용되어지는 ExtendedMaterial 오브젝트를 초기화 한다. 이 함수는, 디렉토리 패스를 Mesh 파일에 설정하고, 디바이스를 초기화 하고, z 버퍼와 흰색의 Ambient 라이트를 유효하게 한다.

public void OnResetDevice(object sender, EventArgs e)

{

    ExtendedMaterial[] materials = null;

    // Set the directory up to load the right data, because the

    // default build location is Bindebug or Binrelease.

    Directory.SetCurrentDirectory(Application.StartupPath + @"....");

    Device dev = (Device)sender;

    // Turn on the z-buffer.

    dev.RenderState.ZBufferEnable = true;

    // Turn on ambient lighting.

    dev.RenderState.Ambient = System.Drawing.Color.White;

    .

    .

    .

}

Meshs 오브젝트의 로드

이하의 코드에서 나타나듯이, OnResetDevice 함수는 다음에 텍스쳐 맵화 된 3D의 호랑이를 표현하는 Mesh를 tiger.x 파일로부터 로드 한다. 이 Mesh.FromFile 함수의 호출에 대해서, MeshFlags 열거의 SystemMemory 정수는, 일반적으로는 디바이스로부터 엑세스할 수 없는 시스템 RAM에 Mesh를 로드하는 것을 나타내고 있다. Mesh가 로드 되어지면, meshMaterial Material 오브젝트는, Mesh 파일로부터 로드 된 Material ExtendedMaterial 오브젝트의 Material 구조체를 멤버에 설정한다. Material에 Ambient 생의 설정도 행하여진다. 마지막으로 TextureLoader.FromFile 함수의 호출에 의해, meshTextures Texture 오브젝트가 텍스쳐와 같이 파일로부터 로드 되어진다. meshMaterial과 meshMaterial는 어느쪽이라도 파일로부터 로드된 materials 구조체의 차원(Length)에 초기화 되어진다.

public void OnResetDevice(object sender, EventArgs e)

{

    .

    .

    .

    // Load the mesh from the specified file.

    mesh = Mesh.FromFile("tiger.x", MeshFlags.SystemMemory, device, out materials);

    if (meshTextures == null)

    {

    // Extract the material properties and texture names.

    meshTextures = new Texture[materials.Length];

    meshMaterials = new Direct3D.Material[materials.Length];

    for( int i=0; i<materials.Length; i++ )

    {

        meshMaterials[i] = materials[i].Material3D;

        // Set the ambient color for the material. Direct3D

        // does not do this by default.

        meshMaterials[i].Ambient = meshMaterials[i].Diffuse;

        // Create the texture.

        meshTextures[i] = TextureLoader.FromFile(dev, materials[i].TextureFilename);

        }

    }

}

Mesh 오브젝트의 렌더링

Mesh를 로드 했다면, private 멤버의 Render를 호출하여 Mesh 오브젝트를 렌더링 한다.
Tutorial 5에서 하였던 것과 같이, 이 함수는, 최초에 Scene를 개시하고, 애플리케이션 정의 함수 SetupMatrices를 호출한다. 렌더링 하기 위해서 Mesh는 로드된 Material마다 하나씩 서브셋에 분할되어져 있다. 다음의 코드에서 나타나듯이, 루프를 실행하고 각 Material 서브셋을 렌더링 한다. 루프에서는, Material마다 이하의 처리를 한다.

  • 디바이스의 Material 프로퍼티에、meshMaterials Material 구조체를 설정한다.
  • 디바이스의 텍스쳐 스테이지 0에、meshTextures Texture 구조체를 설정한다.
  • Material 서브셋을 DrawSubset 함수로 묘화한다. 이 함수는、BaseMesh 클래스로부터 계승한다.

private void Render()

{

    .

    .

    .

    for( int i=0; i<meshMaterials.Length; i++ )

    {

        // Set the material and texture for this subset.

        device.Material = meshMaterials[i];

        device.SetTexture(0, meshTextures[i]);

        // Draw the mesh subset.

        mesh.DrawSubset(i);

    }

    // End the scene.

    device.EndScene();

    device.Present();

}

ps : 부산의 게임개발자분든은 www.bgda.org 로 오세요^^

C#
C# 관리되는, 되지 않는 리소스 사용에 대한 문제점과 해결방법

Written by ProgC

2007-05-31

C#은 CLR에 의해 모든 것이 관리된다. 그로 인해서 메모리 해제를 까먹는다거나 하는 실수를 하지 않게 된다. 실제 필자도 C계열 언어를 10년이 넘게 사용해오면서 메모리 관리가 그다지 쉽지 많은 않다는 것을 피부로 느꼈다. 근래 C#으로 프로그램을 작성하면서 굉장히 많은 이득을 얻을 수 있었는데 그 중 하나가 메모리 관리에 대한 부담이 사라진다는 점이다.
C#에 마법은 없다.
관리되지 않는 리소스를 사용할 경우에 메모리 릭이나 프로그램이 멎어 버리는 현상이 발생하기 때문이다. 이러한 프로그램들이 문제인 것은 프로그램을 실행시켜보고 바로 다운되지는 않는다는 점이다. 단지 서서히 죽어갈 뿐이다.
문제점은 알았으니 문제를 어떻게 풀어 나가야 할 지 다음과 같이 정리했다.
1. 관리되지 않는 리소스란 무엇인가?
2. 리소스가 유출 된다는 사실을 어떻게 알아낼 수 있는가?
3. 해결방법은 무엇인가?

관리되는 리소스는 이미 많은 책들에서 코드와 함께 다루고 있으므로 여기에서는 제외하기로 한다. 우리의 문제는 관리되지 않는 리소스이므로 이것에 대해서만 집중하기로 한다.

관리되지 않는 리소스.

MS의 문서에 의하면 관리되지 않는 리소스는 리소스를 래핑 하는 객체가 소멸될 때 그 소멸자를 명시적으로 호출하지 않을 때 리소스가 해제 되지 않는다고 한다. 즉 A라는 본래 리소스가 있고 B라는 리소스 래핑 객체가 생성되어 A를 사용하고 있다. 내부적으로 A를 가지고 있을 것이며 이것이 소멸될 때 A를 참조하지 않아야 한다. 그렇게 해야만 최종적으로 프로그램이 종료될 때 A라는 객체가 소멸된다. 래핑 객체가 B, C, D로 많아 질 경우에 이러한 래핑 객체는 가비지 컬렉터에 의해서 소멸되지만 그 내부에서 참조하는 것은 소멸되지 않는다. 명시적으로 소멸자를 호출해 주어야 한다. (내부적으로 래핑 객체가 어떻게 구성되었을지 모른다면.)

리소스가 유출 된다는 사실을 어떻게 알아낼 수 있는가?

불행하게도 Visual C# 2005에서 메모리가 새는 방법을 알아내는 방법은 없었다. 대신 Windows Task Manager를 이용하면 된다.

Array

열 선택을 하여 우리가 보고 싶은 정보를 선택한다.

Array

그리고 나서 작업 관리자를 보면 GDI객체나 핸들 수를 알아낼 수 있다.

private void Form1_MouseMove(object sender, MouseEventArgs e)

{

for (int i = 0; i < 10; i++)

{

Bitmap bmp = new Bitmap(100, 100);

Graphics g = Graphics.FromImage(bmp);

g.DrawImage(MouseImg, 50, 50);

Cursor newCursor = new Cursor(bmp.GetHicon());

this.Cursor = newCursor;

g.Dispose();

}

}

위의 코드는 비트맵 마우스 커서를 설정하는 것이고 MouseMove가 움직일 때 설정하게 한 코드이다.

이 코드를 실행하고 작업 관리자의 핸들 수를 보면 MouseMove가 실행될 때 핸들 수가 증가하지만 MouseMove가 끝났음에도 불구하고 핸들은 줄어들지 않는다. 즉 메모리가 유실되고 있는 것이다.

해결 방법은 무엇인가?

MS의 문서에 이미 설명이 되어 있지만 너무 간단하게만 다루고 있다. 관리되지 않는 리소스는 바로 저런 핸들이다.

private void Form1_MouseMove(object sender, MouseEventArgs e)

{

for (int i = 0; i < 10; i++)

{

Bitmap bmp = new Bitmap(100, 100);

Graphics g = Graphics.FromImage(bmp);

g.DrawImage(MouseImg, 50, 50);

// 핸들로 객체를 만들어서 할당한 후에

IntPtr refObj = bmp.GetHicon();

Cursor newCursor = new Cursor(refObj);

this.Cursor = newCursor;

// 핸들을 삭제해 주어야 한다.

DestroyIcon(newCursor.Handle);

g.Dispose();

}

}

물론 DestroyIcon함수를 사용하기 위해서 다음과 같이 코드를 작성해 주어야 한다.

// 아래 2개를 using해주어야 한다.

using Microsoft.Win32;

using System.Runtime.InteropServices;

[System.Runtime.InteropServices.DllImport("user32.dll", CharSet = CharSet.Auto)]

extern static bool DestroyIcon(IntPtr handle);

프로그램을 실행하여 정상 동작하는 프로그램 화면.

Array

마지막.

원칙.

C#은 C#답게 쓰자. 라는 원칙으로 C# 프로그램을 작성하고 있지만 언제까지 MS믿고 C

버전 올려줄 때까지 기다려주지 못한다. 정말 피할 수 없을 때에만 WinAPI코드를 사용하는 것이 좋다.

핫 스팟.

Cursor클래스에서 이미지를 이용해서 커서를 만들 때 핫스팟은 이미지의 중앙을 뜻한다. 그래서 50, 50으로 위치를 이동시켜준 것이다.

C#
초보자들을 위한 공부 가이드 C 언어
0. 소개

C 언어의 중요성에 대해서는 별로 언급할 것이 없겠습니다. 이 글을 읽으시는 분은 이미 그 중요성을 인식한 분일테니까요. 인터넷 검색 엔진에 "the c programming language"라고 쳐 넣을 후 결과를 보면 C 언어가 왜 중요한가에 대한 글이 끝없이 나옵니다. 덤으로 C 언어 강좌도 꽤 많은 것들이 있으니 한번 대충 훑어 보시는 것도 도움이 될 것 같네요.
어 쨌거나 요즘같이 C++, C#, JAVA, Delphi(Pascal), Visual Basic... 등등의 언어가 난무하는 시대에도 여전히 C 언어는 반드시 알아두어야 할 기본 언어입니다. 게다가 기초 문법만 따질 경우 일단 옛날의 베이직을 제외하면 가장 배우기 쉬운(제 생각에) 언어죠. 처음 프로그래머가 되기 위해 결심을 하신 분이라면 다른 언어를 배운다 하더라도 시간을 내어서 꼭 익혀두셔야만 하는 언어입니다.
1. 좋은 책들

1) The C Programming Language, Second Edition
Brian W.Kernighan ,Dennis M.Ritchie
Prentice Hall, 1988
2) C Primer Plus (The Waite Group's), Third Edition
Stephen Prata
Sams Publishing, 1998
3) Teach Yourself C in 21 Days, Fourth Edition
Peter Aitken and Bradley L. Jones
Sams Publishing, 1998
4) Turbo C 정복
가남사
C 를 처음 하시는 분이라면 2), 3)를 권합니다. 번역본도 있으니 그것으로 공부하셔도 전혀 문제 없습니다. 그 외에 다른 분들, 그러니까 한 번이라도 C를 접해본 적이 있는 분들(이미 아무 교재나 한 권 정도는 가지고 있으신 분들)께는 1)을 권하겠습니다. 모든 프로그래머들이 권하는 가장 좋은 책입니다. 1)의 경우에는 번역본을 추천하고 싶지 않습니다. 원래 원서 자체가 영어권 사람들 특유의 문체이면서도 어렵게 작성되어있고 번역본은 그것을 한국적 문체로 살리지 못했기에 읽는 것이 그야말로 고역입니다.
4) 의 경우에는 조금 첨언을 해야겠습니다. 이 책은 그야말로 방대한 내용을 담고 있습니다. Visual Studio의 MSDN에 해당한다고나 할까요. 하지만 Turbo C의 깊은 내용을 알아야 하는 경우가 아니라면 굳이 이 책을 사용하실 필요는 없을 것 같습니다. 이 책을 필요로 하시는 분들은 하드웨어를 직접 설계하시는 분들일 것입니다. 예를 들어서 공장 자동화 로봇의 로봇팔을 제어하는 프로그램을 코딩하여 그 하드웨어에 이식시키기를 원하는 분들, 혹은 Turbo C에서 제공하는 여러 CPU를 위한 크로스 컴파일러들에 대한 정보를 얻기를 원하는 분들에게는 Turbo C에 대한 정보가 필요하실 경우가 있을 겁니다. 그런 분들이 아니라면 이 책을 권하지 않겠습니다. 90년대 초라면 강력 추천 서적이었겠지만요... 어쨌거나 이것과 관련된 정보는 다른 전문가들을 통해서 얻으시는 것이 더 나을 것 같기에 이만 줄이겠습니다.
2. 공부하는 방법에 대한 첫 조언
자유게시판을 검색해 보시면 많은 고수분들의 공부법이 나와있습니다. 자신의 과거 경험이라든지 하는 것들도 함께요. 그것들을 살펴보면 한 가지 공통점이 있습니다. 바로,
"실습"
입 니다. 모든 학문이 그렇지만 자신이 직접 문제를 해결해보지 않는다면 아무런 발전이 없습니다. 프로그래밍 분야도 그것이 매우 심한데 범람하는 문법들과 주변지식들을 모두 외우는 것이 불가능한 이상 한 번이라도 실습을 해 보아야 시간이 지난 후에 다시 보았을 때 기억이 납니다. 혹은 다른 글을 읽을 때 연관을 지을 수도 있구요. 물론 책을 통독한 후에 실습을 하겠다 하시는 것도 좋습니다. 그런 것은 개인 취향이니까요. 단지 제가 드리고 싶은 말씀은 "실습을 통해 사용해 보기 전까지는 그 부분을 안다고 생각하지 말라"라는 것입니다. 여러 챕터의 내용을 뭉뚱그려서 하나의 프로그램을 작성하는 방식을 취하든, 각각의 작은 절에 나오는 사소한 예제들을 작성해 보든 그런 것은 알아서 취향에 맞게 하시기 바랍니다.
3. C 언어의 전체적인 구성

Data Types and Dealing with Data
Operators, Expressions, and Statements
Control Flow : Looping, Branching and Jumps
Fuctions
Arrays and Pointers
Structures
Input and Output
File Input/Output
인 터넷 강좌를 참조하여 나누어 본 분야들입니다. 당연한 얘기지만 C 책들의 차례를 보면 위의 내용들은 꼭 들어가 있습니다. 사실 위의 내용이 C를 구성하는 가장 중요한 문법들입니다. 책을 공부하시면서 자신이 어떤 부분을 공부하고 있는지 꼭 체크하면서 공부하십시오. 전체적인 흐름 속에서 공부하셔야 합니다. 물론 C가 완전히 처음이시라면 아무 생각없이 일단 책을 한 번 통독하는 것도 좋은 방법입니다. ^^
4. 각각의 요소를 공부하며 주의해야 하는 사항들
Data types
데 이터 타입을 공부하시면서 꼭 생각해야 하는 것은 하드웨어의 메모리 혹은 레지스터 상에 저장되는 실제 모습입니다. 많은 데이터 타입들이 있지만 어쨌거나 그것들은 모두 0 아니면 1 로 된 것일 뿐입니다. 컴파일러가 컴파일 할 때 체크를 해 줄 따름이죠. 그러니 데이터의 실제 모습이 무엇인지 손으로(즉 2진 숫자로) 쓰실 수 있다면 데이터 간의 변환을 이해하는 데에 도움이 됩니다.
아주 개인적인 의견이지만 데이터의 타입이나 size 그 자체에는 큰 신경을 쓰지 않으셔도 될 것 같습니다. 대부분의 경우 자신이 자주 쓰는 타입이 있게 마련이고 컴파일러마다, CPU의 종류마다 모두 변하는 속성이기 때문이죠.
Operators, Expressions and Statements
Control Flow
Functions
그 다지 어렵지 않습니다. 어떤 책을 보시든 그 책에서 언급하는 것을 잘 이해하시기 바랍니다. 사실 이 부분들은 긴 설명이 필요하지 않은데 일부 책들은 쓸데없이 내용을 불려서 설명하고 있습니다. 핵심적인 것들만 파악하고 넘어가셔도 크게 문제될 것은 없습니다. 단, 이 내용들이 비록 쉽기는 하여도 익숙해지는 데에는 연습과 시간이 필요하다는 것을 유의해 주시기 바랍니다.
한 가지 사족인데 C 언어의 문법에는 goto 라는 것이 있습니다. 옛날 C 이전의 언어들에서 필수적으로 사용하던 것인데 그 흔적이 남아있는 것이죠.(사실은 어셈블리의 jmp 명령을 매칭시킨 겁니다만...) 이 문법은 무시하시기 바랍니다. 공부하시는 것은 말리지 않겠습니다만 바로 잊어버리세요. goto를 사용해야 하는 상황이 있다면 그것은 프로그램의 구조가 나쁘다는 증거이니까요.
Arrays and Pointers
모 든 사람들이 누구나 동의하는 가장 중요한 부분입니다. 무슨 일이 있어도 완전 정복을 하셔야만 하는 부분입니다. 앞서 Data types에서 데이터의 실제 모습을 항상 떠올리려고 노력하라는 말씀을 드린 바 있습니다. Arrays and Pointers는 그 데이터들이 배열된 모습과 위치를 조작하는 도구들입니다. 이것들을 완전히 이해하여야 비로소 프로그래머가 될 수 있는 가장 기초적인 지식을 얻었다고 하실 수 있습니다. 그러니 철저하게 이해하시기 바랍니다.
Array와 Pointer는 기본적으로 컴퓨터의 CPU와 Memory 사이의 데이터 표현형식 및 전달에 직접적으로 관련되어 있는 부분입니다. 따라서 컴퓨터 분야 자체가 생소하신 분들은 대부분 이 부분을 공부하시면서 어려움을 겪게 됩니다. 하지만 끈기를 가지시기 바랍니다.
특별 히 공부하시는 방법을 알려드리긴 어렵겠습니다. 책의 챕터 뒤에 나와있는 배열 및 포인터 관련 문제들을 남김없이 풀어보시고 틀린 부분이 있으면 꼭 다시 내용을 확인하여 습득하시라는 말 밖에는... 통신상의 여러 질답란의 포인터 관련 내용들을 이해해 보려고 노력하시는 것도 도움이 될 듯... 이 부분과 관련된 좋은 책으로 '성안당'에서 나온 'C 포인터 정복'이라는 책이 있습니다. 사실 배열과 포인터는 일단 이해하고 나면 전혀 어렵지 않은 것이지만 설명하기는 쉽지 않기 때문에 이런 책도 나온 것 갈습니다. 단시간에 배열과 포인터를 정복하고 싶으시다면 위의 책을 구하셔서 하루나 이틀 정도 이 책에만 매달리시면 충분히 이해하고도 남을 겁니다. 단,,, 위 책은 상당히 옛날 책이기 때문에 절판되었을 가능성이 좀 있네요... ㅡ.ㅡa
이 부분을 완전히 이해하고 나면 함수(Function)의 인자로 포인터가 넘어갈 때와 변수값이 넘어갈 때를 구분하실 수 있으셔야 합니다. 그리고 '&'와 '*'의 사용법에도 주의하셔야 하구요. 굳이 첨언하자면 '&'와 '*'는 선언(Declare)할 때와 사용(Expression)할 때의 의미가 각각 서로 반대입니다.(C++에서도 마찬가지...) 무슨 말인지는 직접 공부하시면서 익히시길...
Structures
역시 아주 중요합니다. 기본적인 Data type들을 하나의 주머니에 넣어 한꺼번에 사용하게 만드는 것이 '구조체(Structure)'인데 나중에 C++과 같은 객체지향 언어의 클래스 개념의 기초가 되는 개념입니다. 이 것을 공부하실 때에도 Data type을 공부할 때처럼 실제로 데이터들이 어떤 식으로 배열되어 있는지, 크기는 어떻게 될 것인지를 생각하시면서 공부하시면 도움이 됩니다. 하지만 그런 것들을 생각하면서 공부하는 것이 Data types에서만큼 중요한 것은 아니므로 단순히 사용법만 아셔도 되겠습니다.(물론 하드웨어 제어를 위해 C 언어를 공부하시는 분이라면 무조건 아셔야만 합니다. 덤으로 bit-field 같은 것도 아셔야겠죠.)
I/O, File I/O
이 부분은 처음부터 너무 깊게 공부하실 필요 없습니다. 초보자에게는 어려운 내용이 별로 필요가 없거든요. 사용법들 중 간단한 것들만 실습해 보는 정도로도 충분할 겁니다. 고수준의 I/O는 필요할 때 공부하십시오.
5. 어떤 컴파일러를 사용하여 공부해야 하나?

Turbo C 1.0, 2.0, 2.1 etc...
Borland C++ 3.0, 3.1, etc...
둘 다 볼랜드 사에서 나온 도스(DOS)용 컴파일러 입니다. 인터넷에 불법으로 돌아다니는 것들이 있다고 하더군요. 구해서 공부해 보시길... 한글 번역본 중에 부록으로 들어있다고도 하구요. 설치 후 bin 폴더 안의 TC.exe를 실행시키면 됩니다. Borland C++에서는 파일 확장자를 .c 로 해 주어야 C 언어를 사용했다고 생각하고 컴파일합니다.(C++의 경우에는 확장자가 .cpp여야 합니다.)
Visual C++
아주 좋습니다만 처음 사용하는 분들이 프로그램을 시작해 보면 도대체 어디부터 손을 대야 하는지 모를 수 있습니다. 하지만 이것 밖에는 방법이 없다!라고 하신다면 능력껏 컴파일 방법을 알아내시기 바랍니다. 별로 안 어려워요. ^^
GCC, G++
리 눅스를 사용하시는 분이라면 당연히 이 컴파일러를 사용하셔야죠? Development tools 패키지에서 관련 파일들을 설치하셔야 사용하실 수 있습니다. Turbo C 나 Visual C++ 같은 통합환경이 아니고 명령줄에서 실행하는 것이라는 점에 유의하세요. 이를테면, Xterm을 띄워두고
$ gcc test.c
처럼 하시면 됩니다. 확장자가 .c 이면 C 언어이고 .cpp 혹은 .cc 이면 C++입니다.
리눅스를 사용하여 C를 공부하려면 에디터 사용법을 알아야 합니다. 옛날이라면 VI나 Emacs를 적극 추천했을텐데 요즘 래드헷 패키지를 보면 단순하고 윈도 기반 에디터들도 있더군요. 취향에 맞는 것을 사용하시기 바랍니다.
Borland C++ 5.5
볼랜드 사에서 다운로드 받을 수 있는 C/C++ 컴파일러입니다. 볼랜드 사의 코어 컴파일러로서 가장 최근의 ANSI C/C++(표준)을 완벽하게 지원합니다. 단, DOS용이면서도 Turbo C 같은 통합환경을 제공하지 않으며 gcc/g++과 같이 명령행에서 실행해야 합니다. 사용방법은 gcc/g++과 거의 동일합니다. 개인적인 의견입니다만 이 컴파일러가 상당히 맘에 들더군요. 실제 상용 프로그램을 작성하는 것은 불가능하지만 숙제를 한다던가 공부를 할 때에는 아주 그만입니다. 그리고 여기에 더해서 Turbo Debugger라는 놈이 따라옵니다. 이것은 Turbo C++과 같은 통합환경을 제공하는 디버거인데 Turbo C++에 익숙하신 분들은 전혀 사용에 지장이 없을 겁니다.
6. 공부방법에 대한 두 번째 조언: 전체적인 공부의 흐름

대 부분의 C 책을 펼쳐보면 "Hello, World!" 를 출력하는 셈플 코드를 가지고서 설명을 시작합니다. C를 처음 접하시는 분이라면, 그리고 아직 한번도 컴파일을 해 본 적이 없는 분이시라면 반드시 이것을 컴파일하여 화면에 출력시켜 보시기 바랍니다. 그것이 제대로 된 시작입니다. 프로그램의 작성, 컴파일러를 사용한 컴파일, 프로그램의 실행이라는 3단계를 무사히 마칠 때까지 실패의 원인을 분석하셔야 합니다. 그래야 나중에 실습을 제대로 할 수 있게 될 테니까요. 만약 지금 컴퓨터가 없다면 별 수 없지만 단지 컴파일러가 없어서 못하겠다...라고 하시는 것은 변명이 되지 않습니다. 즉시 인터넷에서 적절한 컴파일러를 구하여 설치하고 사용하십시오. 그런 후에 공부를 진행하세요. 컴파일러의 사용법을 모두 아실 필요도 아직은 전혀 없습니다. 단지 위의 것을 컴파일하고 실행하는 것만 성공시키세요.
그런 후에 주요 내용에 대한 공부와 실습을 병행하십시오. 이것은 취향에 따라서 하실 수 있을 테지만요. 공부하실 내용은 위에 언급한 주제들입니다. 그리고 실습을 하다보면 컴파일러 사용법에 점차 익숙해질 것입니다.
공 부가 한 번 끝나셨다면 마지막 점검을 하셔야겠네요. 혹시 위에 추천된 책들 중에서 1)을 가지고 계시지 않다면 그 책을 사셔야 할 때가 된 것 같습니다. 그리고 그 책의 첫 장(Introduction)을 읽어보십시오. 꼼꼼하게. 이 책의 Introduction에는 C 언어에 대한 전반적인 소개와 중요 개념들이 대부분 언급되어 있습니다. 만약 모두 이해가 가신다면 C 언어의 세계에 성공적으로 들어오신 겁니다.
그런 후에는 간단한 프로젝트를 하나 설계해 보십시오. 윈도 프로그래밍이 아니라 텍스트 모드에 기반한 아주 간단한 것으로요. 그렇지만 많은 분들이 이런 것 생각하시는 것을 귀찮아 하신다거나 혹은 전혀 그 방향을 잡지 못하는 분들도 계시기에 한 가지 예를 들어보자면 이런 것이 있을 수 있겠습니다.
문) 최대 5명의 이름과 전화번호, 메일주소를 입력받아 저장하고 선택적으로 지울 수 있으며 자료의 정렬이 가능해야 한다. 데이터의 저장 공간은 구조체의 배열을 사용하든, 저장 영역을 생성/소멸 시키든 관계없다.
간 단하죠? 텍스트 기반으로 만드는 것이므로 코드는 길지 않습니다. 300-500라인 사이면 되지 않을까 합니다. 텍스트 기반이라고 하여도 만약 사용자 인터페이스를 멋지게 만들고 싶다면 조금 더 길어질 수도 있겠죠. 위의 문제를 나름대로 발전시켜서 30명 정도의 정보를 저장할 수 있는 괜찮은 전화번호부를 만들 수도 있을 겁니다. 의욕이 닿으신다면 File I/O의 기본적인 내용을 적용하여 전화번호부를 저장하는 것도 생각할 수 있겠네요. 여기에다 적당한 인터페이스를 더하면 헨드폰의 전화번호부를 설계하신 것이 됩니다.
어쨌거나 중요한 것은 모든 것이 프로그램 작성자의 상상력과 노력에 달려있다는 겁니다. 반드시 간단한 프로젝트를 하나 해 보시기 바랍니다. ^^
7. 그 외의 것들

어 느 사이엔가 여러분은 C에 익숙해져 있을 겁니다. 이제는 좀 더 심화된 내용들을 살펴봐야 할 것 같습니다. 이와 관련된 내용들은 많은 분들의 의견이 일치하지 않을 것으로 보입니다만 당장 생각나는 것들만 몇 개 나열해 보겠습니다.
헤더파일의 고급 사용법
리눅스나 Borland C++ 5.5를 사용할 경우 makefile을 작성하는 방법
C를 사용하여 OOP적인(혹은 모듈화된) 프로그램을 작성하는 방법
C에서 사용되는 각종 표준 메크로들
Port I/O (Hardware interface)
앞 의 네가지는 아주 밀접한 관계를 가지고 있습니다. 헤더파일의 고급 사용법은 대규모 프로그래밍에서 필수적으로 사용하게 되는 모듈화된 프로그래밍에서 중요한 역할을 합니다. 그다지 많지 않은 내용이지만 잘 익혀두시면 나중에 편합니다.
그리고 헤더파일의 고급 사용법을 익히면서 꼭 만나게 되는 것들이 바로 메크로입니다. 역시 중요한 것들을 꼭 익혀두시길 바랍니다. 쓸데없는 메크로도 많고 중요하지만 초보에게는 그다지 쓸모없는 것들도 많습니다. 그런 것들을 굳이 구분하실 필요는 없으며 일단 한 번 읽어보고 '이런 것이 있구나'하는 정도로 이해하고 넘어가시면 됩니다. 나중에 필요할 때 들춰보면 되니까요.
makefile 이 뭔가 궁금하신 분들이 있을 겁니다. 이것은 프로그램 파일들이 많아질 경우 파일들 사이의 의존 관계 등을 작성해 두는 파일입니다. 그러면 make라는 유틸리티를 사용하여 자동적으로 컴파일을 할 수 있게 됩니다. 각종 의존관계를 생각하며 컴파일 할 필요가 없다는 거죠. Visual C++이나 Borland C++ Builder, Delphi 등등은 모두 기본적으로 make라는 유틸리티와 비슷한 것을 사용하고 있습니다. 단지 그것을 자동적으로 생성하여 사용자들이 생각할 필요가 없게 해 줄 뿐이죠. 리눅스나 볼랜드 코어 컴파일러를 사용할 때 makefile을 사용하는 방법을 익혀 두신다면 도움이 될 때가 있을 겁니다.
끝으로 Port I/O에 관련된 것인데 이것은 컴파일러마다 다릅니다. 필요한 자료는 하드웨어 동호회 같은 곳에서 구하실 수 있을 겁니다. 여기에 관련된 내용은 C의 하드웨어 제어와 직접적으로 연관된 부분들이기 때문에 C 문법과는 전혀 다른 새로운 부분이라고 할 수 있으며, 공부하길 원하시는 분들은 아마도 이미 어셈블리 언어에 대한 이해 혹은 컴퓨터 구조에 대한 이해를 하고 계신 분들이라 생각됩니다. 그러니 더 이상의 설명은 필요없겠죠? ^^;
그 외에도 많겠지만 필요할 때마다 알아서 적당히 공부하시기 바랍니다. ㅡ.ㅡ;
8. 마지막 첨언
여러분께서 영어로 에세이를 하나 작성한다고 생각해 보시기 바랍니다. 기본적인 영문법은 익힌 상태라고 가정하죠. 그러면 이제 단어가 문제가 될 것입니다. 표현하고 싶은 것이 있어도 영어 단어를 모른다면 아무런 소용이 없겠지요.
이 번에는 영어로 된 글을 읽는다고 생각해 보시기 바랍니다. 이제는 단어도 문제이지만 뉴앙스도 문제가 될 겁니다. 글을 쓰는 사람의 의도가 담긴 뉴앙스를 모르면 이해가 어렵게 될 겁니다. 그리고 문법도 또 다시 문제가 됩니다. 직접 글을 쓸 때에는 아는 문법을 짬뽕해서 사용해도 어떻게 적당히 표현이 가능하지만 글을 읽을 때에는 작성자가 사용한 문법 중 모르는 것이 있으면 이해가 처음부터 불가능하게 됩니다. 단어야 사전을 뒤져서 이해한다고 쳐도요...
마지막으로 글을 쓰는 방법을 살펴보죠. 워드를 열고 글을 쓰시려면 계획도 세우고 뼈대를 잡은 후에 글을 쓰고 다 쓴 후에는 퇴고의 과정을 거칩니다. 프로그램의 사용법을 잘 아셔야겠죠?
1에서 소개된 책을 공부하셨다면 여러분은 "문법"만을 공부하신 셈이 됩니다.
여 러분께서 C언어로 프로그램을 작성하다고 생각해 보시기 바랍니다. 기본적인 문법은 익힌 상태겠지요. 그러면 이제 라이브러리 함수가 문제가 될 것입니다. 구현하고 싶은 기능이 있어도 필요한 함수를 작성 못하거나 어떤 함수가 있는지 모른다면 아무런 소용이 없을 것입니다.
이번에는 C로 작성된 프로그램을 읽는다고 생각해 보시기 바랍니다. 라이브러리 함수도 문제지만 프로그램의 흐름과 스타일도 문제가 될 겁니다. 작성자의 흐름이나 스타일을 모르면 이해하기 어려울 것입니다. 그리고 문법도 또 다시 문제가 됩니다. 직접 프로그램을 작성할 때에는 아는 문법을 짬뽕해서 사용해도 어떻게 적당히 작성이 가능하지만 남의 것을 읽을 때에는 작성자가 사용한 문법 중 모르는 것이 있으면 이해가 처음부터 불가능하게 됩니다. 라이브러리 함수야 메뉴얼을 뒤져서 이해한다고 쳐도요...
역시 마지막으로 프로그램 작성 방법을 살펴보죠. 프로그램을 작성하시려면 디자인을 하여야 하고 프로그래밍을 모두 쓴 후에는 디버깅과 평가의 과정을 거칩니다. 당연히 프로그래밍 툴에 대한 이해를 잘 하셔야하겠지요.
이 렇게 보면 중요한 요소들이 나타나게 됩니다. 문법, 라이브러리 함수, 프로그램의 흐름과 스타일, 작성 툴의 사용법 익히기. 여러분은 각 요소들의 가장 기초적인 부분을 끝낸 상태입니다. 이제 실제 프로그래밍을 하면서 살을 붙이고 경험을 쌓아나가며 노하우를 축적하여야 합니다. 이것들은 영어공부를 하는 것과 마찬가지로 노력이 없으면 불가능한 것이겠죠. 여러분의 앞길이 순탄하길 바랍니다. 이 정도에서 첫 가이드를 마치도록 하겠습니다.
참고) 각각의 중요한 요소들에 대한 첨언입니다.
(1) 문법: "The C programming Language"를 구하셔서 Introduction이 잘 이해가 가는지 확인하신 후에 짬이 나실 때마다 부족하다 생각되는 부분을 찾아서 읽으십시오. 책이 어렵기는 하지만 굉장히 압축적으로 써있기 때문에 몇 줄 읽지 않고서도 생각할 것이 많습니다. 곰곰이 생각해보는 과정에서 얻는 것이 많을 것입니다. 이 책의 주옥같은 문제들도 풀어보시면 금상첨화.
(2) 프로그램의 흐름과 스타일: 스타일이란 프로그래밍 컨벤션에 대한 공부를 조금 하면 금방 이해가 가실 겁니다.(공부할 것도 별로 없지만...) 프로그램의 흐름은 프로그램의 종류나 목적마다 많이 다르므로 많이 작성해보고 많이 읽어보면서 이해하셔야만 합니다. 물론 좋은 흐름을 창조하는 방법을 많이 연구해야 함은 물론입니다. 이와 관련된 내용이 바로 "소프트웨어 공학"이라는 분야입니다. 관련 내용을 검색해 보시기 바랍니다.
(3) 라이브러리 함수: 윈도 API나 리눅스의 glibc, winsock 혹은 여러분이 직접 만든 함수들의 집합을 말합니다. 실제 상용 프로그램 수준의 프로그래밍을 하게 되면 이런 라이브러리들을 공부하고 사용할 수밖에 없으며 자신만의 라이브러리를 축적하는 것도 매우 중요합니다. 하지만 윈도 API나 glibc같은 것을 사용하는 것은 따로 시간을 내셔서 차근차근 공부하셔야 합니다. 이것은 문법과는 다른 "단어"의 문제이기 때문입니다. C 언어를 공부하였다고 해서 네트워크 프로그래밍을 할 수 있는 것은 아니지 않겠습니까? 꾸준히 필요한 부분을 공부하시는 것이 가장 좋은 방법일 것입니다.
(4) 툴: Rational Rose(설계 툴), Visual Studio, Turbo C++, Borland C++, GCC, Make utility, 등등... 필요한 것들을 선택하여 일단 하나를 어느 정도 숙지하세요. 특히 컴파일러의 경우에는 디버깅 툴 사용법을 잘 익혀 두시기 바랍니다. (개인적인 생각이지만(그리고 많은 분들이 동의하시지만) 전 디버깅의 실력이 진짜 실력이라고 봐도 무방하다고 생각하거든요.)
C#
1. 전역변수를 사용할 수도 있다. 그러나, 그럴 필요가 없다면 사용하지 말라.2. 절대적인 경우가 아니라면 GOTO 문을 사용하지 말라. 대부분의 경우 거기에 필요한 보다 좋은 방법을 찾을 수 있다.

3. 어떤 변수나 다른 요소가 얼마나 많은 메모리를 필요로 하는 지 모를 때에는, 기록을 위한 배열을 그때그때 할당하라.

연결 리스트(linked list)와 트리 구조를 피하라. 자료 구조는 최대한 단순하게 유지하라.

4. 대부분의 자료 형을 32비트 자료 형으로 하라. 8비트나 16비트 형을 써서 메모리를 아끼려고 하지 말라. 속도가 가장 중요한 것이며,

32비트 데이터가 32비트 프로세서에서 훨씬 빠르다. (참고로 이 글을 발췌한 책은 윈도우즈 게임 프로그래밍 책입니다.)

5. 아주 많은 변수들을 넘기지 말라. 그것들을 구조체로 묶어서 구조체를 가리치는 포인터를 넘겨라.

6. 길고 명확한 변수 이름을 사용하라. 타이핑을 좀 더 하게 되면, 수많은 두통에서 벗어날 수 있다.

7. 모든 것을 객체로 생각하라. 그리고, 객체간의 의사소통을 최소화하라. 코드를 다른 관련 없는 코드에 종속적으로 만들지 말라.

8. 모듈(modular) 고딩 기법을 사용하라 : 헤더파일, define, typedef 등.

9. 언제나 모든 변수들을 초기화하라.

10. 부동소수점 연산을 하지 말라. 정수 계산이 더 빠르다.

11. 코드 속에 주석을 달아라. 모든 라인에 주석을 다는 것이 가장 좋다.

12. 코드를 작성할 때는 최적화하려고 하지 말라. 효율적으로 프로그래밍해야 하지만, 최적화는 나중에 해라.

13. 좋은 알고리즘은 수천 줄의 어셈블리 언어 코드만큼의 가치가 있다. 프로시저가 느리다면, 어셈블리 언어에 의지하기 전에

더 좋은 방법이 없는 지 찾아보라.

14. 언제나 카페인 음료를 구비하라.

15. 자주 호출되는 작은 함수에는 inline 함수를 사용하라.

16. 방어적으로 프로그래밍하라. 시작할 때부터 각 함수들에 대한 에러 로그를 생성하는 에러 시스템 종류를 만들어라.

17. 복잡한 C/C++ 함수를 최소화하라. 필요한 코드를 짤 수 있으면, 짜서 써라.

C/C++ 라이브러리들은 빠른 편이지만, 아주 빠르지는 않다.

18. 작은 데이터의 배열을 할당할 필요가 있고, 배열의 크기가 16 정도에서 256을 넘지 않을 때에는, 배열을 정적(static)으로 만들어라.

그런 작은 자료들에 대해서 메로리 할당과 해제와 같은 복잡성을 가질 필요가 없다.

19. C++ 객체와 클래스를 좋아한다면 사용하라. 그러나, 단지 클래스를 쓰고 싶어서 클래스를 만들지는 말라.

클래스는 간단하고 견고하게 하라. 기억하라, 가장 좋은 프로그램에는 클래스가 없다.

'장난치기' 카테고리의 다른 글

무료 문자보내기  (0) 2008.02.10
IT종사자 프로젝트명 「화려한 휴가」  (0) 2008.02.10
프로그래밍 습관 들이기  (0) 2008.02.07
Resources and Satellite Assemblies 2  (0) 2008.01.28
IRQL NOT LESS EQUAL 에러  (0) 2008.01.28
필리핀 음식정보  (0) 2008.01.28
C#

Fusion 9 : Resources and Satellite Assemblies 2

??¤?????? ???????³¼ ?????± ??´????¸???? ?·¸ ????²??§¸ ????°? ????²???¤.

??? (?°???´?????? ????????¤??¼ ??????) XML Compiled Resources

?§??¸? ?¹??§???? ????????¤ ?????¨??? ??°????°? ??´???????¼???´??? ??´??? ??¸?????´ ????????¤??¼ ?????¨?????? ?²½??° ( ?????¼??¤??´, ?????? ???????§?, ??¤??´??¼?¡??·¸ ?°???¤ ?????? ?º¡??? ????³´??? ?°????)?? ????????? ????????´??¤. ????§??§? ?????´?½?, ?¹???¸?§??³¼ ?°???? ?°???´?????? ????????¤??¼ ?????¨????³? ??¶??? ?²½??°,
?°???´?????? ????????¤??? ??¸?????´?????? ?????? ?°??????? ?????¤????????? ????³?????§? ????????¤. ?°???´?????? ????????¤??¼ ?????´?????? ?????¤??¸ ??¸?½??????? ????????´ ?°???´?????? ?°???? ?????¤??¸ ?°???¼?¡? ?³??????´??¼ ?????¤.Convert ??´?????¤??? ??? ?°???? ???????????¼ ????³?????????°, ToBase64String?³¼ FromBase64String??´ ?·¸?²???´??¤.

?´??????????(Managed)???? XML????????¤??? ??¼?°??????¼?¡? .resx ?????¼?¡? ?????¤??¸ ?????¤.
(?²°??­, ??´ resx ?????¼ ??´??? ??°?????? ??????????? ?°???´?????? ????????¤??¼ ?????¨??? ??? ?????¼??° ??´??¼ Compiled ????????¤?¡? ??´?????¼ ??? ?????±?????? .resources ?????¼??? ????????¤?¡? ?????¨????¼? ????¡???¸??¤??¼ ??¤????????´ ?????? ?²???´??¤.)

Raw ????????? XML ????????¤(.resx)?? ?????¼ ?????± --> (resgen ??´??? ??´?????´ ??´?????¼) --> ??´?????¼ ??? xml ????????¤ ?????¼(*.resources) -->??¤??? ????¡??·¸??¨??? ????????¤?¡? ?????¨????¼? ??´?????¼

??´ ?????¼??? ResxResourceWriter ??´?????¤??¼ ????????´ ??¸ ??? ?????¼??°,?? ResxResourceREader ??´?????¤??¼ ??´?????´??? .resx ?????¼??? ??°??´??°??¼ ??½??? ??? ?????¤.?? ????§??§? ResxResourceWriter ????´?????¤??¼ ??????????????° ???????¹? ?????¼????¡? ??´????²???¼ ?·¸??? .resx?¡? ??´?????¼????³? ResourceManager?¡? ??½??? ??? ?????¤??´ ??¸??? ?²???´??¤.
Resgen ??´??? .txt ?????? .resx ?????¼??? ????????? ?°??????¤??¼ ??? ?????¼??°, ?????¸?¡­?²???? .resources ?????¼??? ????????? ?°???? ??¤??¼?????? ?????¤. ??´ ?²½??° ??´??? .resources ?????¼??? ?????´?????¼?????? .txt ?????? .resx?????¼?¡? ?²°?³¼??? ?????±?????¤.
Resx ?????¼??? ????²???? ??¤??¤?§???¼ ?°??§??³? ?????¼??° ??´ ??¤??¤?§???¼ ?§???¤?§? ?????¼??´ ??´?????¼??´ ????§? ????????¤. Visual Studio??¼ ???????????´ ????¡??????¸??? ??? ??¼??? ?¶??°???? ??? ?§???¤ ????????¼?¡? .resx ?????¼??? ?????±??´ ?¤???¤. Visual Studio??¼ ??????????§? ?????¼??´ ??°????°? ?§???? resx ?????¼??? ?????±??´??¼?§? ?????¤.

??¡ ????????¼?¡?
.resx ?§???¤?¸°(without Visual Studio)

?·¸?????¤?³? resx?????¼??? ?§??????? ?²???´ ?§¤??° ??´??¤??´ ????????? ????????¤ resgen ??´??? ??´?????´??? .resources ?????¼??? ?????´?????¼ ?????? ????¸° ?????¸??´??¤.
?·¸ ?????± ??¨?³???? ?°???¨?????¤. ?¨¼??? ?¹? ?????¤??¸ ?????¼??? ?????±?????¤. ?·¸????³? ????????¤ ?????¼?¡? ??´?????¼ ?????¤. ?§??§??§???¼?¡? .resx ?????¼?¡? ?????´?????¼ ?????¤.

??¼?°?
raw ????????¤ ?????¼ ?????± --> (resgen ??´??? ??´?????´ ??´?????¼) --> ??´?????¼??? ????????¤ ?????¼(.resources)
--> (resgen ??´??? ??´?????´ resx?¡? ?????´?????¼) --> ?????´?????¼??? raw?? XML ????????¤ ?????¼(.resx)



Resgen test.txt

Resgen?? test.resource?? test.resx


??´????²? ?????´ test.resx ??¼??? ??´?????? XML ?????¼??´ ?????±?????¤. ??´ ?????¼??? ?§¤??° ?¸´ ??¼????³¼ ??¤??¤?§?, ?·¸????³? ?¸°?³¸?°???? ?????¨????³? ?????¤. ??¼?????? ????±°????³?,?? ??¤??¤?§???? ????§????????²? ??³??? ?²???´??¤. ?²°?¡??????¼?¡? ????????? ??´?????? resx ?????¼ ??´??? ?¡´?????´??¼ ?????¤.

<?xml version="1.0" encoding="utf-8"?>
<root>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
?? <resheader name="reader">
?????? <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
?? </resheader>
?? <resheader name="writer">
?????? <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
?? </resheader>
</root>


?§??§??§? ??? ??­?ª???? .resx ?????¼??? ??½?³? ??°?????° ???????????? ??´?????¤??¼ ????????¸??¤. ??? ?¤??????? ?²???? resmimetype ??´??¤. ??´??? ??°??´??°??? ?¸°?³¸?????¸ ????????? ????????´??´ ??? ??????????????? text??¼ ?????¨????³? ????????? ????????¸??¤.(text/microsoft-resx)
????????¤ ?????¼??? ??°??´??°??¼ ?¶??°??????? ?²???? ??½??¤. ?°? <data> ??¸?????? <value> ??¸?????¼ ?????¨?????¤. <data> ??¸?????? name ?????±??? ?°??§???° ?¶??°??????¼?¡? type ?????±??? ?°??§? ?????? ?????¤. <value> ??¸?????? ??´?¶? ?????¤??¸??? ?°???´ ??¤??´?°??²? ?????¤.
?§???¼ mimetype??? ?ª????????§? ?????¼??´ <data> ??¸?????? ?¸°?³¸ resmimetype??´ ???????????° ??´??? ?°???´ ??¤??? ?¤? ????????? ????????´??´??¼ ?????¤??? ?²???? ?????¸?????¤.
String, Byte, SByte, Int16, Int32, Int64, UInt16, UInt32, UInt64, Single, Double, DateTime, TimeSpan, Decimal
ResourceManager??? type?³¼ value??? ??½?????¤ ?·¸????³? ?·¸ ?°???? ?????´ ????????? ?????±?????¤. ResourceManager.GetObject??¼ ??´?????´????? ??´?????¼??? ????????¤?¡??¶???° ??­?ª???? ?°???¸??? ??? ?????¤. ??´??? ????????¤ ?ª???? ??¸????¡? ????????´??¼ ?????¤.GetObject??? Object?? ?°??²´??¼ ?????´???????¡? ?????¹??? ????????¼?¡? ?³??????´??¼ ?????¤.
?????¼ ??¤??´ ????????? ?????¤??¸??¼ test.resx ?????¼??? ?¶??°??????¤.

??<data name="Data" type="System.Int32,
mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>42</value>
</data>
</root>

Data ??? 32 ?¹???¸ ????????¼??? ?²???? ????????¸??¤.

??´ ????????¤??¼ ??½??? ?½??????? ?°???¨?????¤.

class App
{
static void Main()
{
Assembly a = Assembly.GetExecutingAssembly();
ResourceManager resources = new ResourceManager("test", a);
object obj = resources.GetObject("Data");
int data = (int)resources.GetObject("Data");
Console.WriteLine("value is {0}", data);
}
}

?·¸????³? .resx??¼ compiled ????????¤?¡? ??´?????¼ ????³?, ????¡???¸??¤ ??´?????¼ , ??¤???
??° ?·¸??¼ ?³´??¤??´ ??´???????¸???!(????³´?¸°??? ?°???´ ??????)

??¡ ?°???´??????
????????¤ ??°??´??° ?¶??°?????¸°

??? ??? ??¸?¸???? ?¸°?³¸ ?????? ??¸??? ????????? ??????????³? ??¶??¤??´, ????????? ?§?????????? ???(BinaryFormatter ?????? SoapFormatter??¼ ??´?????´???) ?·¸ ?²°?³¼??¼ base64?¡? ??¸?½???? ??´??¼ ?????¤.??´????²? ?????? ?°???? ?°???¨??? ?°??²???? ResxResourceWriter??¼ ??´????????? ?²???´??¤.
?????¼ ??¤??´, .cur ?????¼??? ?°??§??³? ????³? ??´ ??¤??? ?????¼??? ??¼??? ??¤????¡? ??´???????³? ??¶??¤?³? ?°?????????¤.
genCursor.cs??¼??? ??´?????? ?½???? ??´???????¼?????????? ?????±????³? ?½??????? ????????? ?°???¤

// First parameter is the name of the icon in the .resx file
// Second parameter is the name of the cursor file
using System; using System.IO;
using System.Resources;
using System.Windows.Forms;
class App
{
static void Main(string[] args)
{
if (args.Length < 2) return;
using (ResXResourceWriter writer
= new ResXResourceWriter(Console.Out))
{
using (FileStream fs = File.OpenRead(args[1]))
{
Cursor cursor = new Cursor(fs);
writer.AddResource(args[0], cursor);
cursor.Dispose();
writer.Generate();
}
}
}
}


??? ?½??????? ResXResourceWriter ??? ?¶??????? ?½?????¡? ????§????(redirect)?????¤. ?·¸?????? ?·¸ ?¶??????? ??¤??? ??¤??? ?????¼?¡? ??°?²°????±°????? ?½?????????? ?·¸??? ?³??????´??? ?????¤.

genCursor Cursor test.cur > test.resx


Test.resx ?????¼??? ??´??´ ????????? ?°???´ ????????¤?°? ?¶??°??????´ ?????¤.

<data name="Cursor" type="System.Windows.Forms.Cursor, System.Windows.Forms" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
AAACAAEAICAAAA8ADwAwAQAAFgAAACgAAAAgAAAAQAAAAAEAAQAAAAAAgAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH4AAAGBgAACAEAABDwgAAjDEAARAggAEg
QIACIIxAAkESQAJCIkACREJAAkiCQAIxBEABIgSAAQQIgACMMQAAQ8IAACAEAAAYGAAAB+AAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAA//////////////////////////////////gf///gB///wAP//4AB//8D
wP/+D8B//h+Af/wfAD/8Pgw//DwcP/w4PD/8MHw//AD4P/4B+H/+A/B//wPA//+AAf//wAP//+AH///4
H/////////////////////////////////8=
</value>
</data>


Mimetype??´ application/x-microsoft.net.object-.bytearray.base64??¼?¡? ??¼??´?§???¤. ??´??? Cursor ?°??²´??¼ ?§???????????????° BinaryFormatter?°? ??????????????¤??? ?????¸??´??¤.

??¡ ?°???´??????
????????¤ ??????????¸°

?²°?³¼ ????????¤ ?????¼(test.resx)??? ??´?????¼ ?????¤.

Resgen test.resx

?????¼ ??´???????¼???´?????? ?????? ?§???¤?³? ????????¤?¡? ?????¨??? ??¤?????¼ ??´??????????¡? ?????¤.

using System;
using System.Resources;
using System.Windows.Forms;
using System.Reflection;
class MainForm : Form
{
MainForm()
{
Assembly a = Assembly.GetExecutingAssembly();
??ResourceManager resources = new ResourceManager("test", a);
?????????? this.Cursor = (Cursor)resources.GetObject("Cursor");
}
static void Main()
{
Application.Run(new MainForm());
}
}


?????¼ ??´???????¼???´??? ??´?????¼
??° ?·¸??¼ ?³´??¤??´ ??´???????¸???!(????³´?¸°??? ?°???´ ??????)
?²°?³¼


??¡ Visual Studio
??¼ ??´?????´ ????????¤ ??????????¸°

?§¤??° ????????´ ?°???¨??´ ?§???¤. ?±°??? ?ª¨??? ????????? Visual Studio?°? ?²??????´ ?¤???¤.
?°???´?????? ?????¼??? ????????¤???(resx ?????¼??? ????????¤?¡? ?¶??°?),?? ????????¤ ?????¼??? ?????? ??´?????¼,?? ????????¤??¼ ??´????¸??????? embed ?????? ?????? ??±

????¡??????¸ ????????? ????????¤ ?????¼ ?¶??°?


?¶??°???? ????????¤ ?????¼(Resource1.resx)??¼ ????????¸ ?ª¨????¡? ??´??´,
??¸?????´/??´??¸?§?/?????´?½?/??? ??±??? ?¹´????³???? ?³? ????????¤??¼ ?????¸??? ??? ?????¤.



??¸?????´ ????????¤ ?????? ?¶??°? : a=1



?°???´?????? ????????¤ ?????? ?¶??°? : ?????¤??? ?????? ?¸°?¡´ ??´??¸?§? ?????¼??? ????????¤?¡? ?¶??°?



??´??¸?§? ?????¼??? ?¶??°??????¼????¡? ??¼?ª½ ?¹´????³??????¼ "??´??¸?§?"?¡? ?³??²½?????´ ?¶??°???? ?????¼??´ ?³´??¸??¤.



????????¤ ??????????¸°

?½??????? ?§¤??° ?°???¨?????¤.
??private void Form1_Load(object sender, EventArgs e)
{
//??¸?????´ ????????¤ ??????
MessageBox.Show(Resource1.a);
//?°???´?????? ????????¤ ??????
this.BackgroundImage = Resource1.KKansoon;
}

??¤???




??¹?³?????²? ????²½ ??°?§? ????????? Visual Studio?°? embedded ????????¤ ???????¡? ??´??¸?§???¼ ??¤??? ??´????¸??????? ?????¨????¼? ?¤???¤.
??? ?³´??´, managed XML ????????¤ ?????¼(resx)??´ ?????¨(embedded)?? ????????¤?¡? ??´????¸??????? ?????¨??¨??? ?????±??¼?¡? ?§????????³? ?????¤.(?¸°?³¸?°?)



??´ ?????¸??? ?³§ Resource1.resx ?????¼ ?????? ?°???´?????? ??°??´??°(base64 ??¸?½??????? ?????¤??¸)?°? ?????¨??? ?²???´?³?, ??´ Resource1.resx?°? ??¤??? ??´????¸????(exe)??? ?????¨??? ?²???´????¡?, ????¡??????¸ ?????? ?¶??°??????´ ?????? ??¤??? ??´??¸?§? ?????¼(?¹????.jpg)??? ??³??´ ??¤??? ??¤??? ??´????¸??????? ?????¨?????? ???????°? ?????¤.
?³§ ??´??¸?§? ?????¼??? Visual Studio?°? ?¹?????³¼?????????,?? Resource1.resx??? ??´?????¼ ?????? Resource1.resources ??¼ ?????±??? ??? ?·¸ ?°???´?????? ??°??´??°??¼ ????³?????¸° ?????´ ??????????¸° ?????? ?²???´??¼ ?§??????´ ?°?????????¤.


???,?? ????????¸ ??????????§? ????????? ????????´??¼???...??¤???????°??????? ???????°? ?????¤???..
??´??¹ ??´??¸?§? ?????¼??? ?¹?????????? ?????±??? ?°???? "??????" ?°???´ ?¸°?³¸??´??¤. (??´?²? ?????¸??? ?§???´ ??·?°???¤ ???????????°???)



??´
??´??¸?§? ?????¼??? "?????¨????????¤"?¡? ?³??²½?????´? ??´??¸ ????????¤ ?????¼??? ?????¨??? ??´??¸?§? ??°??´??°?°? ??¤??? ??°?¡? ??¤??? ??´????¸???? ?????¼?¡? ?????¨???????¡?, ??¸??°?????´ ??¤??? ??´????¸???? ?????´????§? ????°??????¤??? ?²°?³¼??¼ ??³?????¤.
??????????¤?¡? ?????¨??? ??¤??¸ ????????? ?????¼??¤??? ?§??°??°??§? ??¼ ?²???´??¤.


??? ??¤????????¼??? ????????¤?¡?

Visual Studio??¼ ??´?????´??? ??¤??? ?????¼??? ????????¤?¡? ?¶??°??????? ?²???? ?¡°?¸? ????¡??????­??´ ?????¤.(*.cur) ????????¼ ?°¸?¡° ?°????.

http://edndoc.esri.com/arcobjects/9.0/ArcGISDevHelp/DevelopmentEnvs/DotNet/WorkingWithResources.htm

*.cur
??? ????????¤?¡? ?¶??°???? ??? ?·¸??? "?????¼" ???????¡? ?¶??°??????¤. (??¤?????¼??? ?¹´????³???? ??????, ?·¸?????¤?³? ??´??¸?§???? ??????)



?·¸??????
?????? ?²???¼ ??¼??? ??¤??? ?????±??? ?°???´?????? ??°??´??°??¼ ??°?²¨ ?????¼??´ ??????.

this.Cursor = (Cursor)Resouce1.TestCursor;

Visual Studio??¼ ??°?§? ?????? ?°????????¡? resx??¼ ????????¼?¡? ?§???¤??´??? ??´??¹ ??¤??? ?°???´?????? ??°??´??°??¼ ?????´??? ?·¸?±¸ ?¶???? ????¸° ??? ??? ?????¤.
?·¸?????? ????????¼?¡? ????????? resx??¼ ?§???¤??´ ?·¸ ?????? ??¤??? ?°???´????????¼ ?¶??°???? ??¤??? ??´??¹ <data> ??­?ª?(????????¤)??¼ ??´??½?³´????¡? ?³??????????,
Resource1.resx ?????¼ ??´?????? ?§???´ ????????¤.

???)
<data name="TestCursor" type="System.Windows.Forms.Cursor, System.Windows.Forms" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
AAACAAEAICAAAA8ADwAwAQAAFgAAACgAAAAgAAAAQAAAAAEAAQAAAAAAgAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH4AAAGBgAACAEAABDwgAAjDEAARAggAEg
QIACIIxAAkESQAJCIkACREJAAkiCQAIxBEABIgSAAQQIgACMMQAAQ8IAACAEAAAYGAAAB+AAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAA//////////////////////////////////gf///gB///wAP//4AB//8D
wP/+D8B//h+Af/wfAD/8Pgw//DwcP/w4PD/8MHw//AD4P/4B+H/+A/B//wPA//+AAf//wAP//+AH///4
H/////////////////////////////////8=
</value>
</data>


?·¸??¼ "?¸°??? " ?¹´????³??????? ??­?ª???´ ????¸°??´??? ?????? ?²???¼ ???????????¤.



?·¸??° ??? ???

this.Cursor = (Cursor)Resouce1.TestCursor;


????²? ?????´ ??¤?????? ?°??????¤.?? ?·¸?????? ??? ?²??±°?¡­??¤??? resx??¼ ????????¼?¡? ?§??????? ????????? ??´??¼ ?????????.

?????¤??¸ ????²??§¸ ?°??²???? ????°???? ?????¡?¸°?¡? ????????¸ ?°??²???´??¤. CursorConverter??¼ ??´?????´??? ?????¤. ?·¸??? "?????¼" ?¹´????³?????¡? ??¤??? ?????¼??? ????????¤??? ?¶??°???´ ????³?, ???????²???¼ ????????¤ ?°???´?????? ??°??´??°??¼ ??¤????¡? ?³?????????¤.

??CursorConverter cv = new CursorConverter();
this.Cursor = (Cursor)cv.ConvertFrom(Resource1.Cursor1);


????²??§¸?°? ?¡°?¸? ?°???¨??´ ?³´??¸??¤.. ?°¸?³?) ??? ?????? ?°??²???´ ?¡´?????? ?±°??¼ ?³¸??¤.

??¤?????? ????¹???´



C#
시간을 계산해야 할 일이 필요했다.

내가 필요했던 부분은 경매사이트에서 DB에 들어있는 마감시각을 가져와서 현재시각과 계산하여 남은 시간을 계산하기 위해서였다.

C#에서 DateTime이 시간이다. 보통 DB에도 "2007-07-23 오전 12:52"같은 형식으로 들어가는데 그럼 이것을 어떻게 계산을 해야하는가? 일단 그냥 빼기를 해서는 계산이 되지를 않았다.

C#에서는 연산자 오버로드를 할 수 있으므로 연산자 오버로드를 해야하는가 하면서 이것저것 뒤지다가 나온 것인 TimeSpan이다. 둘다 System네임스페이스를 사용한다.

TiemSpan은 시간 간격을 나타내는 구조체이다. DateTime을 계산하기 위해서 사용한다.

일단 소스를 보자.

(Language : cpp)


  1. DateTime end = DateTime.Parse(EndtimeHF.Value.ToString());



  2. TimeSpan t3 = end.Subtract(DateTime.Now);



  3. TimeSpan t2 = DateTime.Now.Subtract(DateTime.Now);



  4. if (t3.CompareTo(t2) > 0)



  5. {



  6. ? ? DeadlineLB.Text = t3.Days + "일 " + t3.Hours + "시간 " + t3.Minutes + "분 " + t3.Seconds + "초 남았습니다.";



  7. }



  8. else



  9. {



  10. ? ? DeadlineLB.Text = EndtimeHF.Value.ToString();



  11. }



따로 또 수정하기 귀찮아서 그냥 내가 쓴 소스를 그대로 잘라왔다. ㅋ

여기선 히든필드에서 값을 가져왔지만 스트링이면 DateTime으로 캐스팅하면 되고 DateTime이면 그냥 쓰면 된다.

Substract가 값을 빼는 메서드이다. end에서 현재의 시간을 빼고 이걸 TimeSpan t3에 저장해서 남은 시간을 계산했다.

여 기에 시간 간격이 0이하일 경우에는 그냥 종료시간만 표시하기 위해서 TimeSpan t2를 썼다. 이것저것 만져봤지만 TimeSpan의 형식을 정확히 알 수가 없어서 크기를 계산할 수가 없어서 나름대로 꼼수를 부렸다. Substract로 현재에서 현재를 빼서 t2에 저장을 했다. 연산속도를 보았을때 0이 나올 것이고 머 꼭 0이 아니더라도 너무 작은 값이므로 큰 상관이 없다.

비교메서드인 CompareTo를 사용해서 t3와 t2를 사용해서 시간간격이 0보다 큰지 아닌지를 비교했다.
  1. kinhana 2009.04.27 01:11 신고

    질문좀 드리겠습니다
    post 로 값을 던졌습니다.....그리고...

    HttpWebRequest를 이용하여 얻어진 쿠키값을 이용하여 WebBrowser를 사용하고 싶습니다.

    HttpWebRequest HTTP = (HttpWebRequest)WebRequest.Create("a-login URL";);
    HTTP.CookieContainer = new CookieContainer();
    HTTP.Method = "POST";
    HTTP.ContentType = "application/x-www-form-urlencoded";
    MemoryStream bufferStream = new MemoryStream();
    byte[] LoginParamattaBytes = System.Text.Encoding.GetEncoding("EUC-KR";).GetBytes("ID=aa&amp;PW=bb";);

    bufferStream.Write(LoginParamattaBytes, 0, LoginParamattaBytes.Length);
    HTTP.ContentLength = bufferStream.Length;
    bufferStream.WriteTo(HTTP.GetRequestStream());
    bufferStream.Close();

    LoginCookie = HTTP.CookieContainer; &lt;== 로그인후 쿠키값 LoginCookie

    위 HttpWebRequest를 이용하여 얻어진 쿠키값을 이용하여 webBrowser1에 접속하고싶습니다.
    webBrowser1.Navigate("로그인 쿠키값이 필요한 URL";);&lt;-이렇게 제응용플에서 저쿠키를
    가져와 로그인유지하며 다른페이지로 가려고 합니다....
    방법좀 알려주시면 대단히 감사하겠습니다

    webBrowser1.Navigate(a-다른페이지, null, null, webBrowser1.Document.Cookie);
    이런식으로는 적용이 안되더라고요;;;;

    제이메일주소는 automute@live.co.kr입니다 곽창수입니다

C#



Article screenshot

Introduction


I've been working with sockets since 2000, using Delphi 5.0 and some third-party libraries (Synapse). My very first socket application just copied files between many clients and one server. The client app checks a folder to see if files exist, asks the server where to copy the files in the network and, after copying the files, flags the database record indicating that a file has been moved. The server listens to the client connections, and both exchange XML messages indicating the state of each file copy. Synapse is a blocking socket implementation, and I needed a thread pooling mechanism that works like an HTTP server, because I couldn't keep the connection open (one thread per connection). My solution was to use some IOCP functions to pool the client requests (code) and close the connection after the message exchange was terminated.

Now, using C#, I decided to write a socket server and client library that helps me to only have to think about the message exchange (the process) and let .NET do the hard job. So, I needed the following features:

  • Asynchronous processing

  • Some encryption and compression capabilities

  • Encapsulate the socket, and encrypt the services in the interfaces and separate them from the host implementation


Socket Connection


The ISocketConnection is the base interface for the socket connections, and describes all the connection properties and the methods. The ConnectionID property defines a unique connection ID using a GUID string. The CustomData property defines a custom object that can be associated with the connection. The Header property is the socket service header used in each message that is encapsulated in a packet message. Only messages with a defined header will be accepted. The LocalEndPoint and RemoteEndPoint are the socket IP end points used in the connection. SocketHandle is the socket handle given by the OS.

The IClientSocketConnection and IServerSocketConnection inherit the ISocketConnection, and each one has special functions. The IClientSocketConnection can reconnect to the server using the BeginReconnect method, and the IServerSocketConnection can communicate with other connections in the server host using the BeginSendTo and BeginSendToAll methods, and can get the ConnectionId using the GetConnectionById method. Every connection knows the host, the encryption, the compression type, and can send, receive, and disconnect itself from the other part. This interface is used in the ISocketService interface to allow the user to interact with socket connections.

Internally, in the library implementation, all the connection interfaces are created using the base connection implementations: BaseSocketConnection, ClientSocketConnection, and ServerSocketConnection.

Socket Service


The ISocketService describes the connection events. These events are fired by the host, and have a ConnectionEventArgs argument which has an ISocketConnection that identifies the connection. In the OnReceived and OnSent events, a MessageEventArgs is passed, which has the sent or received array of bytes. In the OnDisconnected event, a DisconnectedEventArgs is passed; the Exception property indicates if the disconnection has been caused by an exception.

Here is an example of a ISocketService implementation:
Collapse


public class SimpleEchoService : ISocketService
{
public void OnConnected(ConnectionEventArgs e)
{
//----- Check the host!

if (e.Connection.HostType == HostType.htServer)
{
//----- Enqueue receive!

e.Connection.BeginReceive();
}
else
{
//----- Enqueue send a custom message!

byte[] b =
GetMessage(e.Connection.SocketHandle.ToInt32());
e.Connection.BeginSend(b);
}
}

public void OnSent(MessageEventArgs e)
{
//----- Check the host. In this case both start a receive!

if (e.Connection.HostType == HostType.htServer)
{
//----- Enqueue receive!

e.Connection.BeginReceive();
}
else
{
//----- Enqueue receive!

e.Connection.BeginReceive();
}
}

public override void OnReceived(MessageEventArgs e)
{
//----- Check the host!

if (e.Connection.HostType == HostType.htServer)
{
//----- If server, send the data buffer received!

byte[] b = e.Buffer;
e.Connection.BeginSend(b);
}
else
{
//----- If client, generate another

//----- custom message and send it!

byte[] b = GetMessage(e.Connection.SocketHandle.ToInt32());
e.Connection.BeginSend(b);
}
}

public override void OnDisconnected(DisconnectedEventArgs e)
{
//----- Check the host!

if (e.Connection.HostType == HostType.htServer)
{
//----- Nothing!

}
else
{
//----- Reconnect with server!

e.Connection.AsClientConnection().BeginReconnect();
}
}
}


The ISocketService implementation can be done in the same host assembly, or another assembly referenced by the host. This allows the user to separate the host implementation from the socket service, helping the administration in a server or a domain.

Connection Host


With the ISocketService created, you need to host the service and the service connections. Both the server and the client host have the same parent class, BaseSocketConnectionHost, which keeps a list of connections, encrypts and compresses the data buffers, enqueues the service requests and ensures that all data buffer has been sent or received, checks messages headers, and checks for idle connections. The CheckTimeoutTimer, periodically, at IdleCheckInterval, checks if the connections become idle, using the IdleTimeOutValue as the idle timeout. Header is the socket service header used by the host. HostType indicates if a host is a server or a client host. SocketBufferSize defines the size of the socket send and receive buffer. SocketService is the instance of ISocketService that drives the message exchange between the connections.

Encrypt and Compress


Every time you send and receive messages, the host checks if the data must be encrypted and/or compressed, and this work is made by the CryptUtils static class. The CreateSymmetricAlgoritm creates an ISymmetricAlgoritm based on the encryptType parameter. The DecryptData and DecryptDataForAuthenticate are used, respectively, to decrypt the received message and check the hash sign on the authenticate procedure. The EncryptData and EncryptDataForAuthenticate, respectively, encrypt the data to be sent and sign the authenticated message.

The encrypted data buffer is labelled with the service header and the data buffer length, becoming a packet buffer. This packet buffer is controlled by the MessageBuffer class that keeps information about the packet buffer offset, length, the remaining bytes, and the raw buffer.

Enqueuing requests


Every time you call BeginReceive or BeginSend in ISocketService, the host checks if some request has been initiated. If a request is in process, the host enqueues the request. If not, it fires the request.

Send request


In the BeginSend method, the following enqueuing is used:

internal void BeginSend(BaseSocketConnection connection, byte[] buffer)
{
...
//----- Check Queue!

lock (connection.WriteQueue)
{

if (connection.WriteQueueHasItems)
{
//----- If the connection is sending, enqueue the message!

connection.WriteQueue.Enqueue(writeMessage);
}
else
{

//----- If the connection is not sending, send the message!

connection.WriteQueueHasItems = true;

...


When the message is sent, in the send callback, the host checks the queue again and initiates another send process, if needed:

private void BeginSendCallback(IAsyncResult ar)
{
...
//----- Check Queue!

lock (connection.WriteQueue)
{

if (connection.WriteQueue.Count > 0)
{

//----- If has items, send it!

MessageBuffer dequeueWriteMessage =
connection.WriteQueue.Dequeue();
...

}
else
{
connection.WriteQueueHasItems = false;
}

}
...


Receive request


The same technique applies to the receive method: all the calls to BeginReceive are enqueued if the receive method is in action. If no receive process was initiated, the host starts to receive:
Collapse


internal void BeginReceive(BaseSocketConnection connection)
{
...
//----- Check Queue!

lock (connection.SyncReadCount)
{

if (connection.ReadCanEnqueue)
{

if (connection.ReadCount == 0)
{

//----- if the connection is not receiving, start the receive!

MessageBuffer readMessage = new MessageBuffer
(FSocketBufferSize);

...

}

//----- Increase the read count!

connection.ReadCount++;

}

}
...


After that, when the message is received and parsed in the receive callback, the host checks the read queue again, and initiates another receive process, if needed:

private void BeginReadCallback(IAsyncResult ar)
{
...
//----- Check Queue!

lock (connection.SyncReadCount)
{

connection.ReadCount--;

if (connection.ReadCount > 0)
{

//----- if the read queue has items, start to receive!

...

}

}
...


Ensure send and receive


To ensure that all data buffer is sent, the BaseSocketConnectionHost checks the bytes sent, and compares it to the MessageBuffer class. It continues to send the remaining bytes till all the data buffer is sent:

private void BeginSendCallback(IAsyncResult ar)
{
...
byte[] sent = null;
int writeBytes = .EndSend(ar);

if (writeBytes < writeMessage.PacketBuffer.Length)
{
//----- Continue to send until all bytes are sent!

writeMessage.PacketOffSet += writeBytes;
.BeginSend(writeMessage.PacketBuffer, writeMessage.PacketOffSet,
writeMessage.PacketRemaining, SocketFlags.None ...);
}
else
{
sent = new byte[writeMessage.RawBuffer.Length];
Array.Copy(writeMessage.RawBuffer, 0, sent, 0,
writeMessage.RawBuffer.Length);
FireOnSent(connection, sent);
}
}


The same approach is used in the receive data buffers because, to read data, a MessageBuffer is used as the read buffer. When the receive callback is called, it continues to read till all the bytes in the message are read:
Collapse


private void BeginReadCallback(IAsyncResult ar)
{
...
CallbackData callbackData = (CallbackData)ar.AsyncState;

connection = callbackData.Connection;
readMessage = callbackData.Buffer;

int readBytes = 0;
...
readBytes = .EndReceive(ar);
...

if (readBytes > 0)
{
...
//----- Has bytes!

...
//----- Process received data!

readMessage.PacketOffSet += readBytes;
...

if (readSocket)
{

//----- Read More!

.BeginReceive(readMessage.PacketBuffer,
readMessage.PacketOffSet,
readMessage.PacketRemaining,
SocketFlags.None, ...);
}
}
...


Check message header


If the socket service uses some header, all the send and receive processes need to create a packet message indicating the header and the message length. This packet label is created using the following structure:

The first label's part is the socket service header. The header is an array of bytes of any length, and you need some advice here: if you choose a very small header, maybe you can have a message with the same array of bytes somewhere, and the host will lose the sequence. If you choose a very long array of bytes, the host can spend the processor's time to verify if the message header is equal to the socket service. The second part is the packet message length. This length is calculated adding the raw message data buffer length, encrypted and/or compressed, plus the header length.

Sending packets


As said before, every time you send messages, the host checks if the data must be encrypted and/or compressed, and, if you choose to use some header, the raw buffer is controlled by the MessageBuffer class. This class is created using the GetPacketMessage static method:
Collapse


public static MessageBuffer GetPacketMessage(
BaseSocketConnection connection, ref byte[] buffer)
{

byte[] workBuffer = null;

workBuffer = CryptUtils.EncryptData(connection, buffer);

if (connection.Header != null && connection.Header.Length >= 0)
{
//----- Need header!

int headerSize = connection.Header.Length + 2;
byte[] result = new byte[workBuffer.Length + headerSize];

int messageLength = result.Length;

//----- Header!

for (int i = 0; i < connection.Header.Length; i++)
{
result[i] = connection.Header[i];
}

//----- Length!

result[connection.Header.Length] =
Convert.ToByte((messageLength & 0xFF00) >> 8);
result[connection.Header.Length + 1] =
Convert.ToByte(messageLength & 0xFF);

Array.Copy(workBuffer, 0, result,
headerSize, workBuffer.Length);

return new MessageBuffer(ref buffer, ref result);

}
else
{
//----- No header!

return new MessageBuffer(ref buffer, ref workBuffer);
}
}


Receiving packets


The receive process, if you're using some socket service header, needs to check the header, and continues to read bytes till all the packet message is received. This process is executed in the read callback:
Collapse


private void BeginReadCallback(IAsyncResult ar)
{
...

byte[] received = null
byte[] rawBuffer = null;
byte[] connectionHeader = connection.Header;

readMessage.PacketOffSet += readBytes;

if ((connectionHeader != null) && (connectionHeader.Length > 0))
{

//----- Message with header!

int headerSize = connectionHeader.Length + 2;

bool readPacket = false;
bool readSocket = false;

do
{
connection.LastAction = DateTime.Now;

if (readMessage.PacketOffSet > headerSize)
{
//----- Has Header!

for (int i = 0; i < connectionHeader.Length; i++)
{
if (connectionHeader[i] != readMessage.PacketBuffer[i])
{
//----- Bad Header!

throw new BadHeaderException(
"Message header is different from Host header.");
}
}

//----- Get Length!

int messageLength =
(readMessage.PacketBuffer[connectionHeader.Length] << 8) +
readMessage.PacketBuffer[connectionHeader.Length + 1];

if (messageLength > FMessageBufferSize)
{
throw new MessageLengthException("Message " +
"length is greater than Host maximum message length.");
}

//----- Check Length!

if (messageLength == readMessage.PacketOffSet)
{
//----- Equal -> Get rawBuffer!

rawBuffer =
readMessage.GetRawBuffer(messageLength, headerSize);

readPacket = false;
readSocket = false;
}
else
{
if (messageLength < readMessage.PacketOffSet)
{
//----- Less -> Get rawBuffer and fire event!

rawBuffer =
readMessage.GetRawBuffer(messageLength, headerSize);

//----- Decrypt!

rawBuffer = CryptUtils.DecryptData(connection,
ref rawBuffer, FMessageBufferSize);

readPacket = true;
readSocket = false;

received = new byte[rawBuffer.Length];
Array.Copy(rawBuffer, 0, received, 0,
rawBuffer.Length);
FireOnReceived(connection, received, false);
}
else
{
if (messageLength > readMessage.PacketOffSet)
{
//----- Greater -> Read Socket!

if (messageLength > readMessage.PacketLength)
{
readMessage.Resize(messageLength);
}

readPacket = false;
readSocket = true;
}
}
}
}
else
{
if (readMessage.PacketRemaining < headerSize)
{
//----- Adjust room for more!

readMessage.Resize(readMessage.PacketLength + headerSize);
}

readPacket = false;
readSocket = true;
}

} while (readPacket);

if (readSocket)
{
//----- Read More!

...
.BeginReceive(readMessage.PacketBuffer, readMessage.PacketOffSet,
readMessage.PacketRemaining, SocketFlags.None, ...);
...
}
}
else
{
//----- Message with no header!

rawBuffer = readMessage.GetRawBuffer(readBytes, 0);
}

if (rawBuffer != null)
{
//----- Decrypt!

rawBuffer = CryptUtils.DecryptData(connection,
ref rawBuffer, FMessageBufferSize);

received = new byte[rawBuffer.Length];
Array.Copy(rawBuffer, 0, received, 0, rawBuffer.Length);
FireOnReceived(connection, received, true);

readMessage.Resize(FSocketBufferSize);
...


The read callback method first checks if the connection has some header and, if not, just gets the raw buffer and continues. If the connection has some header, the method needs to check the message header against the socket service header. Before doing that, it checks if the packet message length is greater than the connection header length, to ensure that it can parse the total message length. If not, it reads some bytes. After checking the header, the method parses the message length, and checks with the packet length. If the length is equal, it gets the raw buffer and terminates the loop. If the message length is less than that of the packet message, we have the message plus some data. So, the method gets the raw buffer and continues to read using the same MessageBuffer class. If the length of the message is greater than that of the packet message, before reading some data, it just resizes the packet buffer to the message size, ensuring enough room for more read bytes.

Checking idle connections


Using the BeginSend and BeginReceive methods of ISocketConnection doesn't return some IAsyncResult to know if the method was completed or not allowing disconnection after some timeout value. To prevent this, the BaseSocketConnectionHost has a System.Threading.Timer that periodically checks the LastAction property of BaseSocketConnection. If LastAction is greater than the idle timeout, the connection is closed.

Crypto Service


The ICryptoService describes the authentication methods fired when the connection is made to the other part. The OnSymmetricAuthenticate method is fired when EncryptType.etRijndael or EncryptType.etTripleDES is used, and OnSSLXXXXAuthentication is fired when EncryptType.etSSL is used. Like ISocketService, the ICryptService can be done in the same host assembly, or another assembly referenced by the host, so you can have one ICryptoService implementation used in many ISocketService implementations.

SSL authentication


There's a new stream class called SslStream in .NET 2.0 which can authenticate SSL streams. The SslStream's constructor accepts a NetworkStream class, and this stream is created using the Socket class. So, using SslStream, you can send and receive data buffers using socket connections.

Server authentication


The SslStream authentication is done in both the client and the server, but each one has different parameters. In the server side, you need to pass a certificate using the X509Certificate2 class, either finding in the certificate store using X509Store, or by creating it from a certification file (.cer). Also, you can request a client authentication and check the certificate's revocation. The following code is an example of an SSL server authentication using ICryptService:

public void OnSSLServerAuthenticate(out X509Certificate2 certificate,
out bool clientAuthenticate, ref bool checkRevocation)
{
//----- Set server certificate, client

//----- authentication and certificate revocation!

X509Store store = new X509Store(StoreName.My,
StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);

X509Certificate2Collection certs =
store.Certificates.Find(X509FindType.FindBySubjectName,
"ALAZ Library", false);
certificate = certs[0];

clientAuthenticate = false;
checkRevocation = false;

store.Close();
}


Client authentication


On the client side of the SSL authentication, you need to pass the host name of the server certificate, and if this name doesn't match, the authentication fails. You can pass a client certificate collection using X509Certificate2Collection. If the server doesn't request a client authentication, you don't need to pass the collection but, if the server requests it, you can find the certificates using X509Store. You can also request a client certificate's revocation. This is an example of SSL client authentication in ICryptoService:

public void OnSSLClientAuthenticate(out string serverName,
ref X509Certificate2Collection certs, ref bool checkRevocation)
{
serverName = "ALAZ Library";
/*
//----- Using client certificate!
X509Store store = new X509Store(StoreName.My,
StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);

certs = store.Certificates.Find(
X509FindType.FindBySubjectName,
serverName, true);
checkRevocation = false;

store.Close();
*/

}


Certificates


To create certificates, you can use the MakeCert.exe tool found in .NET, and there's a lot of information available about it. You can take a look at John Howard's page, this MS post, and this website.

Symmetric authentication


To implement some symmetric encryption and authentication in this library, I decided to put a post in Microsoft newsgroups. Unfortunately, for the post, but luckily for the knowledge sharing (many thanks to Joe Kaplan, Dominick Baier, and Valery Pryamikov), I decided to use William Stacey's implementation example "A generic method to send secure messages using an exchanged session key". In this code, the symmetric key used in the session is encrypted and signed using RSA key pairs, and the client part needs to know the encrypted server's public key, meaning that this key isn't received from the server in the authentication process. Both the client and the server need to know this key through a manual process. To ensure this, the OnSymmetricAuthenticate needs a RSACryptoServiceProvider class providing the key pair for encryption. You can fill the RSACryptoServiceProvider from an XML string, a file, a CspParameters class, or a certificate. Here is an example of symmetric authentication:
Collapse


public void OnSymmetricAuthenticate(HostType hostType,
out RSACryptoServiceProvider serverKey)
{
/*
* A RSACryptoServiceProvider is needed to encrypt and send session key.
* In server side you need public and private key to decrypt session key.
* In client side you need only public key to encrypt session key.
*
* You can create a RSACryptoServiceProvider from a string
* (file, registry), a CspParameters or a certificate.
*/


//----- Using string!

/*
serverKey = new RSACryptoServiceProvider();
serverKey.FromXMLString("XML key string");
*/


//----- Using CspParameters!

CspParameters param = new CspParameters();
param.KeyContainerName = "ALAZ_ECHO_SERVICE";
serverKey = new RSACryptoServiceProvider(param);

/*
//----- Using Certificate Store!
X509Store store = new X509Store(StoreName.My,
StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);

X509Certificate2 certificate = store.Certificates.Find(
X509FindType.FindBySubjectName,
"ALAZ Library", true)[0];
serverKey = new RSACryptoServiceProvider();

if (hostType == HostType.htClient)
{
//----- In client only public key is needed!
serverKey = (RSACryptoServiceProvider)certificate.PublicKey.Key;
}
else
{
//----- In server, both public and private key is needed!
serverKey.FromXmlString(certificate.PrivateKey.ToXmlString(true));
}

store.Close();
*/

}


The authentication message


The symmetric authentication uses the AuthMessage structure to exchange session keys between the client and the server. The SessionKey and SessionIV properties are, respectively, the symmetric key and the initialization vector of the algorithm. The Sign property is the hash code generated by the client using the sign RSACryptoServiceProvider class created internally, and its public key is exchanged using the SourceKey property. This internal sign key pair is necessary to sign the AuthMessage, and the server can ensure that the AuthMessage is accurate. This process is done using the following code:

Client side


Collapse


...
//----- Sign Message!

private byte[] signMessage = new byte[]
{ <sign message array of bytes for authentication> };
...
protected virtual void InitializeConnection(BaseSocketConnection connection)
{
...

//----- Symmetric!

if (connection.EncryptType == EncryptType.etRijndael ||
connection.EncryptType == EncryptType.etTripleDES)
{
if (FHost.HostType == HostType.htClient)
{
//----- Get RSA provider!

RSACryptoServiceProvider serverPublicKey;
RSACryptoServiceProvider clientPrivateKey =
new RSACryptoServiceProvider();

FCryptoService.OnSymmetricAuthenticate(FHost.HostType,
out serverPublicKey);

//----- Generates symmetric algorithm!

SymmetricAlgorithm sa =
CryptUtils.CreateSymmetricAlgoritm(connection.EncryptType);
sa.GenerateIV();
sa.GenerateKey();

//----- Adjust connection cryptors!

connection.Encryptor = sa.CreateEncryptor();
connection.Decryptor = sa.CreateDecryptor();

//----- Create authenticate structure!

AuthMessage am = new AuthMessage();
am.SessionIV = serverPublicKey.Encrypt(sa.IV, false);
am.SessionKey = serverPublicKey.Encrypt(sa.Key, false);
am.SourceKey =
CryptUtils.EncryptDataForAuthenticate(sa,
Encoding.UTF8.GetBytes(clientPrivateKey.ToXmlString(false)),
PaddingMode.ISO10126);

//----- Sign message with am.SourceKey,

//----- am.SessionKey and signMessage!

//----- Need to use PaddingMode.PKCS7 in sign!

MemoryStream m = new MemoryStream();
m.Write(am.SourceKey, 0, am.SourceKey.Length);
m.Write(am.SessionKey, 0, am.SessionKey.Length);
m.Write(signMessage, 0, signMessage.Length);

am.Sign = clientPrivateKey.SignData(
CryptUtils.EncryptDataForAuthenticate(sa,
m.ToArray(), PaddingMode.PKCS7),
new SHA1CryptoServiceProvider());

//----- Serialize authentication message!

XmlSerializer xml = new XmlSerializer(typeof(AuthMessage));
m.SetLength(0);
xml.Serialize(m, am);

//----- Send structure!

MessageBuffer mb = new MessageBuffer(0);
mb.PacketBuffer =
Encoding.Default.GetBytes(Convert.ToBase64String(m.ToArray()));
connection.Socket.BeginSend(
mb.PacketBuffer, mb.PacketOffSet,
mb.PacketRemaining, SocketFlags.None,
new AsyncCallback(InitializeConnectionSendCallback),
new CallbackData(connection, mb));

m.Dispose();
am.SessionIV.Initialize();
am.SessionKey.Initialize();
serverPublicKey.Clear();
clientPrivateKey.Clear();
}
...
}


On the client side of the symmetric authentication, the OnSymmetricAuthenticate is called, getting the RSACryptoServiceProvider to encrypt the session key generated by the CryptUtils.CreateSymmetricAlgoritm method. The AuthMessage is filled with the encrypted session key, session IV, and the sign public key. To sign the message, the SourceKey, SessionKey, and signMessage are used, and the resulting hash is assigned to the Sign property.

Server side


Collapse


protected virtual void InitializeConnection(BaseSocketConnection connection)
{
...
if (FHost.HostType == HostType.htClient)
{
...
}
else
{
//----- Create empty authenticate structure!

MessageBuffer mb = new MessageBuffer(8192);

//----- Start receive structure!

connection.Socket.BeginReceive(mb.PacketBuffer, mb.PacketOffSet,
mb.PacketRemaining, SocketFlags.None,
new AsyncCallback(InitializeConnectionReceiveCallback), ...);
}
}

private void InitializeConnectionReceiveCallback(IAsyncResult ar)
{
...

bool readSocket = true;
int readBytes = ....EndReceive(ar);

if (readBytes > 0)
{

readMessage.PacketOffSet += readBytes;
byte[] message = null;

try
{
message = Convert.FromBase64String(
Encoding.Default.GetString(readMessage.PacketBuffer,
0, readMessage.PacketOffSet));
}
catch (FormatException)
{
//----- Base64 transformation error!

}

if ((message != null) &&
(Encoding.Default.GetString(message).Contains("</AuthMessage>")))
{

//----- Get RSA provider!

RSACryptoServiceProvider serverPrivateKey;
RSACryptoServiceProvider clientPublicKey =
new RSACryptoServiceProvider();

FCryptoService.OnSymmetricAuthenticate(FHost.HostType,
out serverPrivateKey);

//----- Deserialize authentication message!

MemoryStream m = new MemoryStream();
m.Write(message, 0, message.Length);
m.Position = 0;

XmlSerializer xml = new XmlSerializer(typeof(AuthMessage));
AuthMessage am = (AuthMessage)xml.Deserialize(m);

//----- Generates symmetric algorithm!

SymmetricAlgorithm sa =
CryptUtils.CreateSymmetricAlgoritm(connection.EncryptType);
sa.Key = serverPrivateKey.Decrypt(am.SessionKey, false);
sa.IV = serverPrivateKey.Decrypt(am.SessionIV, false);

//----- Adjust connection cryptors!

connection.Encryptor = sa.CreateEncryptor();
connection.Decryptor = sa.CreateDecryptor();

//----- Verify sign!

clientPublicKey.FromXmlString(Encoding.UTF8.GetString(
CryptUtils.DecryptDataForAuthenticate(sa,
am.SourceKey, PaddingMode.ISO10126)));

m.SetLength(0);
m.Write(am.SourceKey, 0, am.SourceKey.Length);
m.Write(am.SessionKey, 0, am.SessionKey.Length);
m.Write(signMessage, 0, signMessage.Length);

if (!clientPublicKey.VerifyData(
CryptUtils.EncryptDataForAuthenticate(sa, m.ToArray(),
PaddingMode.PKCS7),
new SHA1CryptoServiceProvider(), am.Sign))
{
throw new
SymmetricAuthenticationException("Symmetric sign error.");
}

readSocket = false;

m.Dispose();
am.SessionIV.Initialize();
am.SessionKey.Initialize();
serverPrivateKey.Clear();
clientPublicKey.Clear();

FHost.FireOnConnected(connection);

}

if (readSocket)
{
....BeginReceive(readMessage.PacketBuffer,
readMessage.PacketOffSet,
readMessage.PacketRemaining,
SocketFlags.None,
new AsyncCallback(
InitializeConnectionReceiveCallback), ...);
}

}


On the server side of the symmetric authentication, a MessageBuffer is used to receive the socket buffer. The read callback method continues to read till a completed AuthMessage is received. With this message, the method calls the OnSymmetricAuthenticate to get the RSACryptoServiceProvider to decrypt the session key, session IV, and the sign public key. With all the keys decrypted, the method verifies the Sign property to ensure that the AuthMessage is accurate, using the SourceKey, SessionKey, and signMessage.

Connection Creator


Although BaseSocketConnectionHost can manage ISocketConnection connections, it cannot create them. This job is made by BaseSocketConnectionCreator which creates and initializes ISocketConnections. The CompressionType and EncryptType properties define, respectively, the compression and the encryption types that will be used in the connection. The CryptoService defines the ICrytoService instance used to initialize the connection, if needed. The Host property defines the host of the BaseSocketConnectionCreator; it can be a server or a client host. The LocalEndPoint defines the socket IP end point used in the connection, and it can have different behavior depending on the type of the creator.

SocketServer and SocketListener


The SocketServer and SocketListener are the classes needed to create a socket server. SocketServer is derived from BaseSocketConnectionHost, and manages ISocketConnections. The SocketListener is derived from BaseSocketConnectionCreator, and listens for incoming connections, accepts a connection, and creates a new ISocketConnection to be used. A SocketServer can have as many SocketListeners attached as required, each one assigned to a local port to listen.

SocketServer constructor and methods


In the SocketServer constructor, the socketService parameter defines the ISocketService instance used by the server. The header parameters define the array of bytes used in the message header exchange. The socketBufferSize adjusts the socket buffer size. The messageBufferSize defines the maximum message size of the service. The idleCheckInterval indicates the interval for idle connections checking, in milliseconds. The idleTimeoutValue defines the timeout, in milliseconds, to be compared to each connection LastAction property.

To add SocketListener items in SocketServer, the method AddListener must be used. The localEndPoint parameter defines the local socket IP endpoint used to listen to connections. The encryptType and compressionType defines, respectively, the encryption and compression methods used in the new accepted connection. The cryptoService defines the ICryptoService used to authenticate the encryption method chosen. The backLog limits the listen queue of the OS socket to the defined number, and acceptThreads sets the calling number of the socket's BeginAccept to increase the accepted performance.

HostThreadPool


This library uses asynchronous socket communication which, in turn, uses the .NET ThreadPool. In the .NET 2.0 ThreadPool, the thread number can be controlled using the SetMaxThreads and SetMinThreads methods, and I think there are a lot of improvements in this class. But, if you don't want to use the .NET class, you can use a managed thread pool called HostThreadPool, very similar to Stephen Toub's ManagedThreadPool. HostThreadPool uses a list of managed threads that keeps increasing as more enqueueing tasks are provided. To use this class instead of the .NET ThreadPool in SocketServer, just set the minThreads and maxThreads constructor parameters to non-zero numbers.

Here are some examples of using SocketServer and SocketListener:
Collapse


//----- Simple server!

SocketServer server = new SocketServer(new SimpleEchoService());
//----- Simple listener!

server.AddListener(new IPEndPoint(IPAddress.Any, 8087));
server.Start();
//----- Server with header!

SocketServer server = new SocketServer(new SimpleEchoService(),
new byte[] { 0xFF, 0xFE, 0xFD });
//----- Listener with simple encryption!

server.AddListener(new IPEndPoint(IPAddress.Any, 8087),
EncryptType.etBase64, CompressionType.ctNone, null);
server.Start();
//----- Server with header and buffer

//----- sizes, no hostthreadpool and idle check setting!

SocketServer server = new SocketServer(new SimpleEchoService(),
new byte[] { 0xFF, 0xFE, 0xFD },
2048, 8192, 0, 0, 60000, 30000);
//----- More than one listener each one with different listen port number!

server.AddListener(new IPEndPoint(IPAddress.Any, 8087));
server.AddListener(new IPEndPoint(IPAddress.Any, 8088),
EncryptType.etBase64, CompressionType.ctNone, null);
server.AddListener(new IPEndPoint(IPAddress.Any, 8089),
EncryptType.etRijndael, CompressionType.ctGZIP,
new SimpleEchoCryptService(), 50, 10);
server.AddListener(new IPEndPoint(IPAddress.Any, 8090),
EncryptType.etSSL, CompressionType.ctNone,
new SimpleEchoCryptService());
server.Start();


SocketClient and SocketConnector


The SocketClient and SocketConnector are the classes needed to create a socket client. SocketClient is derived from BaseSocketConnectionHost and, like SocketServer, manages ISocketConnections. The SocketConnector is derived from BaseSocketConnectionCreator, and it connects with the socket server and creates a new ISocketConnection to be used. A SocketClient can have as many SocketConnectors attached as required, each one connecting to a socket server, and they can be assigned to a local address and a local port to start the connection.

SocketClient constructor and methods


The SocketClient constructor has the same parameter signature as the SocketServer class. To add SocketConnector items in SocketClient, the method AddConnector must be used. The remoteEndPoint parameter defines the remote socket IP endpoint used for the connection. The encryptType and compressionType define, respectively, the encryption and compression methods used in the new connection. The cryptoService defines the ICryptoService used to authenticate the encrypted method chosen. The reconnectAttempts and reconnectAttemptInterval define, respectively, the number of reconnect attempts when using BeginReconnect method and the time interval to reconnect. The localEndPoint defines the local socket IP endpoint used to start the connection process to the remote endpoint.

Here are some examples of using SocketClient and SocketConnector:
Collapse


//----- Simple client!

SocketClient client = new SocketClient(new SimpleEchoService());
//----- Simple connector!

client.AddConnector(new IPEndPoint(IPAddress.Parse("10.10.1.1"), 8087));
client.Start();
//----- Client with header!

SocketClient client = new SocketClient(new SimpleEchoService(),
new byte[] { 0xFF, 0xFE, 0xFD });
//----- Connector with simple encryption!

client.AddConnector(new IPEndPoint(IPAddress.Parse("10.10.1.1"), 8087),
EncryptType.etBase64, CompressionType.ctNone, null);
client.Start();
//----- Client with header and buffer sizes,

//----- no hostthreadpool and idle check setting!

SocketClient client = new SocketClient(new SimpleEchoService(),
new byte[] { 0xFF, 0xFE, 0xFD },
2048, 8192, 0, 0, 60000, 30000);
//----- Connector with encryption and reconnect!

client.AddConnector(new IPEndPoint(IPAddress.Parse("10.10.1.1"), 8087),
EncryptType.etSSL, CompressionType.ctGZIP,
new SimpleEchoCryptService(),
5, 30000);
client.Start();
//----- Client with header and buffer sizes,

//----- using hostthreadpool and idle check setting!

SocketClient client = new SocketClient(new SimpleEchoService(),
new byte[] { 0xFF, 0xFE, 0xFD },
4096, 8192, 5, 50, 60000, 30000);
//----- Connector with encryption, reconnect and local endpoint!

client.AddConnector(new IPEndPoint(IPAddress.Parse("10.10.1.1"), 8087),
EncryptType.etSSL, CompressionType.ctGZIP,
new SimpleEchoCryptService(),
5, 30000,
new IPEndPoint(IPAddress.Parse("10.10.3.1"), 2000));
client.Start();
//----- Simple client!

SocketClient client = new SocketClient(new SimpleEchoService());
//----- More than one connector each one with different remote socket servers!

client.AddConnector(new IPEndPoint(IPAddress.Parse("10.10.1.1"), 8087));
client.AddConnector(new IPEndPoint(IPAddress.Parse("10.10.1.2"), 8088),
EncryptType.etBase64, CompressionType.ctNone, null);
client.AddConnector(new IPEndPoint(IPAddress.Parse("10.10.1.3"), 8089),
EncryptType.etRijndael, CompressionType.ctGZIP,
new SimpleEchoCryptService());
client.AddConnector(new IPEndPoint(IPAddress.Parse("10.10.1.4"), 8090),
EncryptType.etSSL, CompressionType.ctNone,
new SimpleEchoCryptService(),
5, 30000,
new IPEndPoint(IPAddress.Parse("10.10.3.1"), 2000));
client.Start();


Echo demo project


There's an Echo demo project available in the article download file, using Console, Windows Forms, and Windows Service hosts and clients, and all them use the same EchoSocketService and EchoCryptService. The demos are divided by their type as follows:

Hosts



  • Console

    1. EchoConsoleClient

    2. EchoConsoleServer



  • Windows Forms

    1. EchoFormClient

    2. EchoFormServer

    3. Echo<code>Form (Forms template)



  • Windows Service

    1. EchoWindowsServiceServer




Services



  • EchoSocketService

  • EchoCryptService


Conclusion


There's a lot going on here, and I think this library can help anyone who wants to write asynchronous sockets with encryption and compression. Any comments will be appreciated.

History



  • May 15, 2006: Initial version

  • May 19, 2006: Some English text corrections (sorry, I'm still learning!), and rechecking the demo sources

  • June 06, 2006: Version 1.2 with the following changes:

    • Minor bugs fixed

    • All "Sended" changed to "Sent" (thanks vmihalj)

    • ReadCanEnqueue now works correctly and with HostThreadPool (thanks PunCha)

    • Added reconnectAttempts and reconnectAttemptInterval to allow client connection reconnects as many times as is needed in a timed interval (thanks Tobias Hertkorn)



  • April 01, 2007: Version 1.3 with the following changes:

    • Fixed rawbuffer = null

    • Fixed BeginAcceptCallback: stops accepting if exception occurs

    • Fixed BeginSendCallback: PacketRemaining bytes should be used

    • Socket config section added in demos

    • New message size (64K)

    • HosThreadPool removed

    • Header changed to Delimiter property with new delimiter options:

      1. dtNone: No message delimiter

      2. dtPacketHeader: Version 1.2 backward

      3. dtMessageTailExcludeOnReceive: Use custom delimiter at end of message (Exclude delimiter on receiving)

      4. dtMessageTailIncludeOnReceive: Use custom delimiter at end of message (Include delimiter on receiving)



    • New connection object properties/methods:

      1. Nagle, Linger and TTL Algorithm options

      2. Host and Creator



    • Encrypt sign message in service class

    • Exception event in service class

    • New Creator name property



  • July 22, 2007: Version 1.4 with the following changes:

    • Connection initialize procedures executing in same thread (not queued in ThreadPool)

    • Connection disconnect now checks windows version and executes the correct disconnect procedure

    • Connection Active checking for Disposed

    • CheckSocketConnections disposed check fixed

    • CryptUtils Flush() method included

    • Client Connection BeginConnect() exception fixed

    • Server Connection BeginSendToAll array buffer fixed

    • New SocketClientSync class for synchronous using (WinForms demo included)



  • September 5, 2007: Version 1.5 with the following changes:

    • SocketClient with proxy authentication (SOCKS5, Basic HTTP)

    • BeginRead bug fixed (messagetail)

    • BeginDisconnect changed (threadpool)

    • BeginSendToAll reviewed (disposed check)

    • New OnSSLClientValidateServerCertificate event to validate server's certificate

    • Idle check interval set to 0 and only created when greater than 0

    • Using Buffer.BlockCopy instead of Array.Copy

    • New Chat Demo





License


This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors may to use can be found here

About the Author







Andre Azevedo
- Living in S?o Paulo, Brazil- Interesting in
* NFL, NBA
* Lockon, Black Shark
* Dream Theater, drumming

- Developing since 1994 with
* Clipper (Summer '87 and 5.02)
* FoxPro (DOS, 2.6), Visual Foxpro (6, 7, 8, 9)
* Delphi (1, 2, 5, 7)
* C# (1.1, 2.0)









Occupation: Software Developer (Senior)
Location: Brazil Brazil

C#



Introduction


I had been trying to include updater block in my application. The quick starts that came along with the application block had some real neat examples, but it took quite some time to figure out what files must be placed where, what settings needed to be made and how, to get a sample of my own working. So, I thought I would post the sample implementation that I generated, along with a step by step guide so that others who will be trying this point onwards need not struggle much.

Note: A good tutorial for updater block version 1.0 can be found here.

The example below tries to show the usage for the version 2.0 of the Updater block.

Prerequisites



  • The Enterprise library and the Updater application block must be downloaded and installed. The enterprise library can be downloaded from here.

  • The updater block can be downloaded from here.


System Requirements



  • Supported Operating Systems: Windows XP.

  • Windows XP Professional.

  • Internet Information Server version 5.0 or later.

  • Microsoft .NET FrameWork version 1.1 SDK.

  • Microsoft Visual Studio 2003.

  • Background Intelligent Transfer Service (BITS) (for the out-of-the-box downloader to function correctly).


Steps



  1. Create a simple Windows form application .

    • Open VS .NET editor and select File -> New Project -> Windows Application.

    • Change the name to SampleApplication.

    • Change the name of the default form generated to SampleForm.cs

    • Add a label on the form SampleForm and set its text to “Version 1.0.0.0”

    • Add a button to the form and change its text to “Exit”. Add the event handler to exit the application - Application.Exit();.

    • Add a listbox called eventList.

    • Open the AssemblyInfo.cs and change the version of the application to 1.0.0.0: [assembly: AssemblyVersion("1.0.0.0")]

    • Change the line in Main () body to Application.Run(new SampleForm());.

    • Add app.config to the application: Add -> New Item -> Application Configuration File.

    • Build the application.



  2. Add the updater application block to the application.

    • The three assemblies that have to be referenced are present under the folder C:Program FilesMicrosoft Patterns & PracticesUpdater Application Block 2.0srccs (if the default settings were chosen during the installation of the updater block).

    • The three DLLs can be found in the bin/debug folders of the ActivationProcessors, Downloaders and Updater projects present under the common folder mentioned above.

    • Add refernce to the three DLLs to the created sample application:

      1. Microsoft.ApplicationBlocks.Updater.ActivationProcessors.dll

      2. Microsoft.ApplicationBlocks.Updater.Downloaders.dll

      3. Microsoft.ApplicationBlocks.Updater.dll



    • Add the code, present at the end of the document to the application that forms the body for handling the download (from the #region Auto-Update Stuff To #endregion).Note: The downloadable source has this content, but what the heck.

    • Add the following code to the constructor of the SampleForm Class to start off the updater manager:

      InitializeAutoUpdater ();





  3. Update the App.Config file using Enterprise library Configuration Tool.

    • Open the tool from Start -> All Programs -> Microsoft Patterns and Practices -> Updater Application Block V2 -> Enterprise Library Configuration Tool.

    • In the Configuration tool console, select File -> Open Application and browse to the App.Config file, select and click Open.

    • A new item “Application” appears under the root “Enterprise Library Configuration”.

    • Rightclick Application and select New ?> Updater Application Block Configuration.

    • Configuration, Cryptography and Updater Application Blocks are added.

    • Update the properties of the application block:

      1. ApplicationID: SampleApplication.

      2. BasePath: C:TempUpdaterTestUAB.

      3. ManifestUri: http://localhost/ApplicationBlockServer/ServerManifest.xml.Note: The ManifestUri will point to a URL that has to be created later.



    • Rightclick Updater Application Block and select New -> Downloaders.

    • Rightclick Downloaders and select New -> BITSDownloader.

    • Save.



  4. Deploy the Client

    • Build the SampleApplication project.

    • Copy the output of the project from the bin/Debug folder to the folder C:TempUpdaterTest. The files to be copied are:

      1. All the DLLs including the following:

        1. Microsoft.ApplicationBlocks.Updater.ActivationProcessors.dll

        2. Microsoft.ApplicationBlocks.Updater.dll

        3. Microsoft.ApplicationBlocks.Updater.Downloaders.dll



      2. The application exe: SampleApplication.exe

      3. The config file: SampleApplication.exe.config



    • Copy the files updaterconfiguration.config and securitycryptographyconfiguration.config present in the root folder of the project to the folder UpdaterTest.



  5. Change the project to create the server application.The server application will be a newer version of the application deployed at c:temp UpdaterTest.

    • Open the SampleApplication solution in VS.NET editor.

    • Open SampleForm in designer mode and make noticeable changes on the form. Change the label text to “Version 1.0.0.1” and the form text to “Sample Form New Version”.

    • Open AssemblyInfo.cs and change the assembly version to 1.0.0.1: [assembly: AssemblyVersion("1.0.0.1")].

    • Build the application.



  6. Deploy the Server Application.The new version of the application will be deployed at the server, from where it can be downloaded.

    • Create a folder called ApplicationBlockServer under C:inetpubwwwroot.

    • Open IIS manager (Start -> Run -> Inetmgr).

    • In the IIS manager, navigate to Default Web Site.

    • Rightclick on Default Web Site and select “New -> Virtual Directory”.

    • Create a virtual directory with alias ApplicationBlockServer and the folder pointing to ApplicationBlockServer folder created above.

    • Create a new folder called 1.0.0.1 under the folder c:inetpubwwwroot ApplicationBlockServer.

    • Copy the file SampleApplication.exe built in the previous step to the newly created folder 1.0.0.1.



  7. Build the Manifest file for the server

    • Open the Manifest Editor tool that was also installed along with the updater block (Start -> All Programs -> Microsoft Patterns and Practices -> Updater Application Block V2 -> Manifest Editor Tool.

    • Under Manifest Properties Tab, click the Generate button and generate the manifest id. Check the Mandatory check box.

    • Enter the description “Sample Application Version 1.0.0.1”.

    • In the application properties tab, enter the application id as “SampleApplication” and location as “.”

    • The entry point is “SampleApplication.exe”.

    • The File URI is http://localhost/ApplicationBlockServer/1.0.0.1.

    • The source folder is “C:InetpubwwwrootApplicationBlockServer1.0.0.1”.

    • Click the “Add” button and select SampleApplication.exe from the 1.0.0.1 folder. The file must appear in the Files list.

    • In the activation process tab, enter the process name as AppDeploy and processor type as “Application Deploy” and press “Add”.

    • Enter process name as “WFE” and processor type as “Wait For Exit” and press Add. A new window prompts to add a process name. Press Cancel.

    • Click the Validate button to validate the generated manifest.

    • Click the Save button to Save the manifest at “C:InetpubwwwrootApplicationBlockServer” as “ServerManifest.xml”.



  8. Run the application at “C:TempUpdaterTest”.The application, after some time, will pops up a dialog box telling that a newer version is available and do you want to download it. Click yes. The newer version will be downloaded which will be displayed in the list box eventList. After downloading, it will prompt you to restart the application. Close the application and start it again. The version 1.0.0.1 will open up.

    Addendum:

    • Once the update of the application happens, a folder called UAB gets created in the UpdaterTest folder. This folder gives the information about the latest version present.

    • From what I could understand, the manifest ID is maintained at the server and if an update occurs for the client, the manifest ID is placed in the UAB folder. If the manifest ID of the server is different from that of the client (or if the client does not have the UAB folder, meaning no updates have yet taken place), the updater block swings into action.

    • To deploy a newer version of the application on the server, create a new folder 1.0.0.2 and dump all the files at that location (the naming convention is just a convention for convenience, not a rule) and generate a new server manifest file with a new Manifest ID and replace the old manifest file.

    • I have not checked the auto update feature, i.e. the client can be scheduled to ping the server at regular intervals. But this must be straight forward as setting some property somewhere.

    • The code below has a thread to ping the server every 10 seconds. That part can be removed.




Hey, this was just a vanilla implementation.

Auto Update Code to be pasted in the application
Collapse


#region Auto-Update Stuff

private Thread pollThread = null;
private int manifestDownloaded = 0;

private void CheckAndUpdate()
{
try
{
// Get the updater manager

ApplicationUpdaterManager updater =
ApplicationUpdaterManager.GetUpdater();

// Subscribe for various events

updater.DownloadStarted +=
new DownloadStartedEventHandler(updater_DownloadStarted);
updater.DownloadProgress +=
new DownloadProgressEventHandler(updater_DownloadProgress);
updater.DownloadCompleted +=
new DownloadCompletedEventHandler(updater_DownloadCompleted);
updater.DownloadError +=
new DownloadErrorEventHandler(updater_DownloadError);
updater.ActivationInitializing +=
new ActivationInitializingEventHandler(updater_ActivationInitializing);
updater.ActivationStarted +=
new ActivationStartedEventHandler(updater_ActivationStarted);
updater.ActivationInitializationAborted +=
new ActivationInitializationAbortedEventHandler(
updater_ActivationInitializationAborted);
updater.ActivationError +=
new ActivationErrorEventHandler(updater_ActivationError);
updater.ActivationCompleted +=
new ActivationCompletedEventHandler(updater_ActivationCompleted);

// Loop till the updates are available


Manifest[] manifests = null;
while(true)
{
manifests = updater.CheckForUpdates();
if(manifests.Length > 0)
{
// Prompt user if he wants to apply the updates

if( MessageBox.Show(this,
"Update for Auto Inproc Application is available,"+
" do you want to apply the update?",
"Update",MessageBoxButtons.YesNo)== DialogResult.Yes)
{
foreach(Manifest m in manifests)
{
m.Apply = true;
}
// update the application as per manifest details.

updater.Download( manifests, TimeSpan.MaxValue );
if(manifestDownloaded == manifests.Length)
{
updater.Activate( manifests );
manifestDownloaded = 0;
}
break;
}
else
{
Thread.Sleep(10000);
}
}
else
{
Thread.Sleep(10000);
}
}
}
catch(ThreadAbortException)
{
// Do nothing if the thread is being aborted,

//as we are explicitly doing it

}
catch(Exception ex)
{
MessageBox.Show(this,ex.Message,"Error",
MessageBoxButtons.OK,MessageBoxIcon.Error);
}
}

private void exitButton_Click(object sender, System.EventArgs e)
{
Application.Exit();
}

private void updater_DownloadStarted(object sender,
DownloadStartedEventArgs e)
{
UpdateList("DownloadStarted for manifest: " + e.Manifest.ManifestId);
}

private void updater_DownloadProgress(object sender,
DownloadProgressEventArgs e)
{
UpdateList("DownloadProgress for manifest: "+ e.Manifest.ManifestId +
"- Files: "+e.FilesTransferred+"/"+e.FilesTotal+
" - Bytes: "+e.BytesTransferred+"/"+e.BytesTotal);
}

private void updater_DownloadCompleted(object sender,
ManifestEventArgs e)
{
UpdateList("DownloadCompleted for manifest: " +
e.Manifest.ManifestId);
manifestDownloaded++;
}

private void updater_DownloadError(object sender,
ManifestErrorEventArgs e)
{
UpdateList("DownloadError for manifest: " +
e.Manifest.ManifestId +"n"+e.Exception.Message);
}

private void updater_ActivationInitializing(object sender,
ManifestEventArgs e)
{
UpdateList("ActivationInitializing for manifest: " +
e.Manifest.ManifestId);
}

private void updater_ActivationStarted(object sender, ManifestEventArgs e)
{
UpdateList("ActivationStarted for manifest: " + e.Manifest.ManifestId);
}

private void updater_ActivationInitializationAborted(object sender,
ManifestEventArgs e)
{
UpdateList("ActivationInitializationAborted for manifest: " +
e.Manifest.ManifestId);
MessageBox.Show(this,
"The Application needs to restart for applying the updates," +
" please restart the application.",
"Auto Inproc Updates",MessageBoxButtons.OK,
MessageBoxIcon.Information);
}

private void updater_ActivationError(object sender,
ManifestErrorEventArgs e)
{
UpdateList("ActivationError for manifest: " + e.Manifest.ManifestId +
"n"+e.Exception.Message);
}

private void updater_ActivationCompleted(object sender,
ActivationCompleteEventArgs e)
{
UpdateList("ActivationCompleted for manifest: " + e.Manifest.ManifestId);
}

private void UpdateList(string displayString)
{
eventList.Items.Add(displayString);
eventList.Update();
}

private void InitializeAutoUpdater ()
{
// Seperate thread is spun to keep polling for updates

ThreadStart checkUpdateThreadStart = new ThreadStart(CheckAndUpdate);
pollThread = new Thread(checkUpdateThreadStart);
pollThread.Start();
}
#endregion




License


This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors may to use can be found here

About the Author







Praveen Nayak












Occupation: Web Developer
Location: United Kingdom United Kingdom

+ Recent posts