C#의 __arglist는 MSIL로 번역되면서 Call Instruction에 varargs라는 추가 파라미터를 지정하는 방식으로 사용됩니다. gmcs 컴파일러에서는 Microsoft .NET Stack과의 호환성을 위하여 __arglist 키워드에 대한 정확한 처리를 수행하는 것 처럼 보입니다. 그러나 런타임에서 Microsoft .NET Framework의 경우 이것을 정확히 관리하고 있지만 Mono의 경우 가변 인수를 IL에서 사용할 수 없습니다. 이것은 __arglist가 표준 사양이 아니기 때문에 그렇습니다. 다음은 Mono에서 가변 인수 메서드를 호출할 때 발생하는 예외입니다.
[CODE] [root@localhost ~]# mono test.exe
Unhandled Exception: System.InvalidProgramException: Invalid IL code in Program: Main (): IL_0007: call 0x0a000001
[root@localhost ~]
[/CODE]
그러나 가변 인수를 사용하지 않고 필요한 만큼 인수를 직접 선언하는 방법은 유효합니다. 다음과 같이 직접 필요한 인수를 지정하여 플랫폼 호출 선언을 지정하여 사용하는 경우 정상적으로 실행됩니다.
[CODE] [DllImport("libc", CharSet = CharSet.Ansi, ExactSpelling=true)] public static extern int printf(string format, int testIntenger);
[/CODE]
xPlatform은 이 부분에 대한 문제 개선을 위하여 동적 플랫폼 호출 선언 관리 클래스를 별도로 구현할 예정에 있으며 이 클래스를 통하여 Mono의 __arglist 비호환성 문제를 비롯하여 타 언어에서도 가변 인수 메서드의 사용을 가능하게 할 것입니다.
우연히 printf 계열의 함수들에 대한 플랫폼 호출 방법에 대한 자료를 찾는 도중 재미있는 글을 발견하였습니다. C#의 공식 사양에 포함되지 않은 Microsoft C# 컴파일러의 특수한 기능들 (Mono나 DotGNU의 C# 컴파일러에는 존재하지 않거나 의도하지 않은 예외가 발생할 수도 있습니다.)을 알게 되었습니다.
주의: 비공식 키워드를 사용하는 것에 대한 책임은 프로그래머에게 달려있습니다. 다른 언어들과의 호환성 문제와 부딪히지 않으려면 이러한 비공식 키워드의 사용에 주의해야만 합니다.
TypedReference에 대한 비공식 키워드
TypedReference 형식은 관리되는 객체에 대한 명확한 주소 정보 및 핸들 정보를 가리키는 특수한 객체입니다. TypedReference 형식의 객체를 만들기 위한 비공식 키워드가 C#에 존재합니다.
__makeref(변수) : 괄호 안에 TypedReference 형식으로 조사하기를 원하는 객체를 지정하면 되는데, 여기에는 값 형식의 변수가 올 수도 있습니다. 예를 들면 다음과 같습니다.
int x = 0; TypedReference xt = __makeref(x);
__reftype(TypedReference 객체) : 괄호 안에 TypedReference 객체를 지정하면 TypedReference 객체가 원래 가리키고 있던 객체의 형식 정보를 반환합니다. 예를 들어, 위의 예에서 System.Int32에 대한 TypedReference를 만들었는데 이 키워드를 사용함으로서 System.Int32에 대한 Type 객체가 반환됩니다.
int x = 0; TypedReference xt = __makeref(x); Type xt2 = __reftype(xt);
__refvalue(TypedReference 객체, 가져오기 원하는 형식) : 괄호 안에 TypedReference 객체를 첫 인수로 지정하고, TypedReference가 가리키고 있는 실제 객체에 대한 형식명을 두 번째 인수로 지정합니다. 이 키워드를 이용하여 TypedReference 객체로부터 곧바로 원래의 객체를 가져올 수 있습니다.
int x = 0; TypedReference xt = __makeref(x); Type xt2 = __reftype(xt); int y = __refvalue(xt, int);
printf, sprintf, fprintf와 같이 C 언어에서만 사용하는 가변 인수를 C#에서도 도입하기
va_args, va_start, va_end와 같은 매크로 함수를 혹시 기억하십니까? printf 같이 인수를 가변적으로 조절할 수 있는 함수를 디자인하기 위하여 사용했던 함수들입니다. 이러한 함수들을 흉내내기 위하여 C#에서는 params 키워드를 제공하였고 그 결과 가변 인수처럼 동작하면서도 결과적으로 1차원 배열을 다룰 수 있는 향상된 가변 인수 처리법을 제공할 수 있게 되었습니다. 하지만 이렇게 변경된 가변 인수 처리법은 종전의 C 언어 스타일과는 크게 다르기 때문에 C 언어를 위하여 디자인된 printf 계의 함수들은 사용할 수 없는 것 처럼 이야기되어왔습니다. 하지만 지금 소개하는 __arglist 키워드를 이용함으로서 이런 한계를 극복할 수 있습니다.
__arglist라는 키워드를 주목합니다. 매개 변수 형식 선언이 아님에도 불구하고 사용할 수 있다는 것이 매우 특이한데, 바로 이것이 C 언어 스타일의 가변 인수여야 함을 컴파일러에게 알려주는 힌트가 됩니다. 하지만 이렇게 선언하면 뒷쪽에 가변 인수를 제공하기 위하여 __arglist() 키워드를 사용하여야 하지만, __arglist라는 키워드를 지원하지 못하는 다른 프로그래밍 언어에서는 사용할 수 없습니다.
이제 위의 _cprintf 함수를 부르는 예제를 보기로 합니다.
int result = _cprintf("%d", __arglist(30));
그렇습니다. __arglist라는 가상의 매크로에 필요한 만큼 인수를 던져주면 됩니다. 그러면, 여기서 궁금한 점이 하나 더 생기는데 __arglist를 params 키워드 대신해서 사용할 수도 있을까에 대한 의문입니다. 그 답은 "Yes"입니다. 다음의 코드를 보겠습니다.
protected void Page_Load(Object sender, EventArgs e) { int x=85; string y = "a stringy thingy"; double d=19.45; WriteToPage(__arglist(x,y,d)); }
public void WriteToPage(__arglist) { ArgIterator ai = new ArgIterator(__arglist); while(ai.GetRemainingCount() >0) { TypedReference tr = ai.GetNextArg(); Response.Write(TypedReference.ToObject(tr)+"<BR>"); } }
int, string, double 형의 변수를 선언하고 __arglist(x,y,d)로 이들 인수를 던집니다. 그러면 WriteToPage 메서드에서는 이렇게 받아들인 인수들을 조회하기 위하여 ArgIterator 객체를 사용하여 하나씩 조회합니다. ArgIterator 객체로 조회한 매개 변수들은 TypedReference로 포장된 객체들이므로 실제 객체를 위의 코드처럼 TypedReference.ToObject 메서드로 가져오거나 __refvalue 키워드를 이용하여 가져와서 사용하는 것입니다.
당신의 의견을 작성해 주세요.