C# Frequently Asked Questions for C++ programmers

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:

clip_image002[5]

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:\\temp\\test.txt”

versus:

string fileName = @”c:\temp\test.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/

Posted in C#. No Comments »

Visual Studio 6.0에서 2005이상으로 넘어가야 하는 이유..

정말 아직 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 프로그램과 호환이 안 되는 경우는 없을 것으로 보입니다.

clip_image001페이지 맨 위로

관리 세계로 이동

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 입니다.

clip_image002
그림 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

clip_image003
그림 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 구문을 사용한 버전을 제공 합니다. 즐거운 프로그래밍 하세요~!

Posted in C#. No Comments »

관리되는 DirectX 9.0 튜토리얼

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

Tutorial 1: 디바이스의 작성


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

패스

소스의 위치: (SDK 루트)\Samples\C#\Direct3D\Tutorials\Tutorial1

순서

애플리케이션 윈도우 작성

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 루트)\Samples\C#\Direct3D\Tutorials\Tutorial2

순서

이 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 루트)\Samples\C#\Direct3D\Tutorials\Tutorial3

순서

주 : 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 루트)\Samples\C#\Direct3D\Tutorials\Tutorial4

순서

’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(0×202020);

    .

    .

    .

    .

Tutorial 5 : 텍스쳐 맵의 사용법


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

위치

소스의 위치 : (SDK 루트)\Samples\C#\Direct3D\Tutorials\Tutorial5

순서

‘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 루트)\Samples\C#\Direct3D\Tutorials\Tutorial6

순서

”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 Bin\debug or Bin\release.

    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 로 오세요^^

Posted in C#. No Comments »

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

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

Written by ProgC

2007-05-31

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