2009-06-06 6 views
7

큰 문자열 const를 가진 클래스를 생성해야하는 상황이 있습니다. 내 제어 밖에있는 코드는 생성 된 CodeDom 트리를 C# 소스로 내보내고 나중에 더 큰 어셈블리의 일부로 컴파일합니다.csc.exe에서 스택 오버플로 (CS1647)가 발생하는 C# CodeDom의 해결 방법은 무엇입니까?

fatal error CS1647: An expression is too long or complex to compile near 'int'

MSDN :

불행하게도,이 문자열의 길이가 Win2K8의 64에서 335,440 문자 (Win2K3 86에서 926240), 치명적인 오류와 C# 컴파일러 종료를 초과된다 경우 상황으로 실행했습니다 CS1647은 "컴파일러에서의 스택 오버플로"라고합니다. 더 자세히 살펴보면 CodeDom이 80 자의 문자열 const를 "멋지게"감싸는 것을 확인했습니다. 이로 인해 컴파일러는 x64 NetFx의 C# 컴파일러의 스택 깊이 인 4193 개의 문자열 청크를 연결합니다. CSC.exe는이 표현식을 내부적으로 재귀 적으로 평가하여 내 단일 문자열을 "rehydrate"해야합니다. "외부 시스템에서 C# 소스를 중간 단계로 사용한다는 사실을 제어 할 수 없으며이 코드를 사용하여 코드 생성기가 문자열을 내보내는 방식을 변경할 수 있습니까?"내 초기 질문은 "입니다. (문자열의 런타임 연결이 아니라) 상수가되도록합니다.

또는 는 어떻게이 표현은 문자의 특정 숫자 후, 난 여전히 상수를 만들 수 있어요하도록 공식화 할 수 있지만 덩어리 큰 여러 로 구성되어있다?

전체 생식가 여기에 있습니다 :

// this string breaks CSC: 335440 is Win2K8 x64 max, 926240 is Win2K3 x86 max 
string HugeString = new String('X', 926300); 

CodeDomProvider provider = CodeDomProvider.CreateProvider("C#"); 
CodeCompileUnit code = new CodeCompileUnit(); 

// namespace Foo {} 
CodeNamespace ns = new CodeNamespace("Foo"); 
code.Namespaces.Add(ns); 

// public class Bar {} 
CodeTypeDeclaration type = new CodeTypeDeclaration(); 
type.IsClass = true; 
type.Name = "Bar"; 
type.Attributes = MemberAttributes.Public; 
ns.Types.Add(type); 

// public const string HugeString = "XXXX..."; 

CodeMemberField field = new CodeMemberField(); 
field.Name = "HugeString"; 
field.Type = new CodeTypeReference(typeof(String)); 
field.Attributes = MemberAttributes.Public|MemberAttributes.Const; 
field.InitExpression = new CodePrimitiveExpression(HugeString); 
type.Members.Add(field); 

// generate class file 
using (TextWriter writer = File.CreateText("FooBar.cs")) 
{ 
    provider.GenerateCodeFromCompileUnit(code, writer, new CodeGeneratorOptions()); 
} 

// compile class file 
CompilerResults results = provider.CompileAssemblyFromFile(new CompilerParameters(), "FooBar.cs"); 

// output reults 
foreach (string msg in results.Output) 
{ 
    Console.WriteLine(msg); 
} 

// output errors 
foreach (CompilerError error in results.Errors) 
{ 
    Console.WriteLine(error); 
} 
+0

이 아래 실행, 2.0로 나타나는에는 Csc.exe 버전의 .NET 3.5을 목표에도 불구하고 : 검색의 전체 하루를 보낸 후, 나는 철 속도 디자이너 포럼에서 수정이 링크를 발견했다. – mckamey

답변

4

CodeSnippetExpression 및 수동 인용 문자열을, 내가 좋아했을 소스를 방출 할 수 있었다 사용 :

editbin /stack:100000,1000 csc.exe <options> 

는 다음의 사용의 예입니다 Microsoft에서 보았습니다 .CSharp.CSharpCodeGenerator. 이와

field.InitExpression = new CodePrimitiveExpression(HugeString); 

:

그래서 교체, 위의이 선 질문에 대답하는

field.InitExpression = new CodeSnippetExpression(QuoteSnippetStringCStyle(HugeString)); 

을 마지막으로 포장하지 Microsoft.CSharp.CSharpCodeGenerator.QuoteSnippetStringCStyle 방법을 인용 민간 문자열을 수정 80 자 이후 :

private static string QuoteSnippetStringCStyle(string value) 
{ 
    // CS1647: An expression is too long or complex to compile near '...' 
    // happens if number of line wraps is too many (335440 is max for x64, 926240 is max for x86) 

    // CS1034: Compiler limit exceeded: Line cannot exceed 16777214 characters 
    // theoretically every character could be escaped unicode (6 chars), plus quotes, etc. 

    const int LineWrapWidth = (16777214/6) - 4; 
    StringBuilder b = new StringBuilder(value.Length+5); 

    b.Append("\r\n\""); 
    for (int i=0; i<value.Length; i++) 
    { 
     switch (value[i]) 
     { 
      case '\u2028': 
      case '\u2029': 
      { 
       int ch = (int)value[i]; 
       b.Append(@"\u"); 
       b.Append(ch.ToString("X4", CultureInfo.InvariantCulture)); 
       break; 
      } 
      case '\\': 
      { 
       b.Append(@"\\"); 
       break; 
      } 
      case '\'': 
      { 
       b.Append(@"\'"); 
       break; 
      } 
      case '\t': 
      { 
       b.Append(@"\t"); 
       break; 
      } 
      case '\n': 
      { 
       b.Append(@"\n"); 
       break; 
      } 
      case '\r': 
      { 
       b.Append(@"\r"); 
       break; 
      } 
      case '"': 
      { 
       b.Append("\\\""); 
       break; 
      } 
      case '\0': 
      { 
       b.Append(@"\0"); 
       break; 
      } 
      default: 
      { 
       b.Append(value[i]); 
       break; 
      } 
     } 

     if ((i > 0) && ((i % LineWrapWidth) == 0)) 
     { 
      if ((Char.IsHighSurrogate(value[i]) && (i < (value.Length - 1))) && Char.IsLowSurrogate(value[i + 1])) 
      { 
       b.Append(value[++i]); 
      } 
      b.Append("\"+\r\n"); 
      b.Append('"'); 
     } 
    } 
    b.Append("\""); 
    return b.ToString(); 
} 
+0

Jon Skeet에게 감사의 말을 전합니다. 또한 상자 밖에서 생각 해준 Robert Harvey에게 감사드립니다. – mckamey

+0

문자열 상수를 래핑하지 않기로 선택할 때 고려해야 할 또 다른 csc.exe 제한은 "오류 CS1034 : 컴파일러 제한을 초과했습니다. 줄은 16777214자를 초과 할 수 없습니다."분명히 필요한 것은 하이브리드입니다. 실제로 긴 청크 크기로 줄 바꿈합니다. – mckamey

+0

이 대답은 크기가 더 긴 문자열 길이의 * 많은 * 주문을 허용합니다 (읽음 : 수백 억 개의 문자). 스트레스 테스트를 통해 기계 메모리 한계가 새 경계 크기가된다는 사실이 입증되었습니다. – mckamey

2

그래서 바로 당신 같은 것을 사용하여 C# 소스 파일을 가지고 말하는 I 오전 : 당신

public const HugeString = "xxxxxxxxxxxx...." + 
    "yyyyy....." + 
    "zzzzz....."; 

다음은 컴파일하려고 그것?

그렇다면 컴파일하기 전에 텍스트 파일을 편집하려고합니다. 아마 상대적으로 직설적이어서 인간이 생성 한 소스 코드와 비교하여 엄격하게 정의 된 패턴을 따르기 때문입니다. 각 상수에 대해 하나의 거대한 선을 갖도록 변환하십시오. 일부 샘플 코드에서이 기능을 사용하기를 원한다면 알려주십시오.

그건 그렇고, 당신의 repro는 내 상자에 오류없이 성공합니다 - 어떤 버전의 프레임 워크를 사용하고 있습니까? (내 상자에 영향을 미칠 수있는 4.0의 베타 버전이 있습니다.)

EDIT : 문자열 상수가 아닌 것으로 변경하는 것은 어떻습니까? 당신은 스스로를 파괴해야하고,이 같은 공공 정적 읽기 전용 필드로 방출 것 :

public static readonly HugeString = "xxxxxxxxxxxxxxxx" + string.Empty + 
    "yyyyyyyyyyyyyyyyyyy" + string.Empty + 
    "zzzzzzzzzzzzzzzzzzz"; 

결정적으로는, string.Emptypublic static readonly 필드, 하지 상수. 즉, C# 컴파일러가 string.Concat에 대한 호출을 내보내는 것만으로도 괜찮습니다. 그것은 실행 시간에 한 번만 발생합니다 - 컴파일 시간에 실행하는 것보다 느리지 만 다른 어떤 것보다 쉬운 해결 방법 일 수 있습니다.

+0

런타임은 .NET 3.5이지만 실제로 코드를 컴파일 할 때 2.0 csc.exe 이상을 실행하는지 잘 모르겠습니다. 더 많은 상황에서 실패 할 수 있도록 문자열 크기를 repro에서 부 팅했습니다. 여전히 성공한다면, 4.0은 스택 깊이를 증가 시키거나 의심되는 것보다 더 많은 기계 의존적입니다. 예 파일을 편집 할 수 있지만 불행히도 내 코드는 CodeDom 트리를 반환하기 위해 호출되고 있습니다. 외부 코드는 중간 파일을 내보내거나 컴파일 할시기와시기를 결정합니다. – mckamey

+0

아. 좋아, 이상한 생각으로 편집해라. –

+0

나는 당신을 투표 하겠지만 분명히 나는 ​​그렇게 충분하지가 않다. 흥미 롭습니다. CodeDom은 C#이 아니기 때문에 실제로 readonly를 낼 수는 없지만 concat을 없애면 컴파일 할 수 있습니다. 이제는 이것이 단순히 오버플로를 런타임으로 푸시하는지 확인해야합니다. – mckamey

0

코드 생성기의 동작을 변경하는 방법을 알지 못하지만 /stack 옵션이 EditBin.EXE 인 경우 컴파일러에서 사용하는 스택 크기를 변경할 수 있습니다.

예 :

class App 
{ 
    private static long _Depth = 0; 

    // recursive function to blow stack 
    private static void GoDeep() 
    { 
     if ((++_Depth % 10000) == 0) System.Console.WriteLine("Depth is " + 
      _Depth.ToString()); 
     GoDeep(); 
    return; 
    } 

    public static void Main() { 
     try 
     { 
      GoDeep(); 
     } 
     finally 
     { 
     } 

     return; 
    } 
} 




editbin /stack:100000,1000 q.exe 
Depth is 10000 
Depth is 20000 

Unhandled Exception: StackOverflowException. 

editbin /stack:1000000,1000 q.exe 
Depth is 10000 
Depth is 20000 
Depth is 30000 
Depth is 40000 
Depth is 50000 
Depth is 60000 
Depth is 70000 
Depth is 80000 

Unhandled Exception: StackOverflowException. 
+0

흥미로운 제안. 불행히도 내가 불려지는 곳에서는 csc.exe에 직접 액세스 할 수 없습니다. 이상적으로는 문자열이 너무 길면 다시 질문하지 않아도됩니다. 이 해결 방법은 문자열이 커질 때 스택 크기를 계속 유지해야합니다. – mckamey

2

문자열을 const로 사용하는 경우 이 코드에서이 문자열을 사용하는 각 어셈블리에 복사됩니다.

정적 읽기 전용으로 더 나을 수도 있습니다.

또 다른 방법은 문자열을 반환하는 읽기 전용 속성을 선언하는 것입니다.

+0

이것은 흥미 롭습니다. 나는 이것에 대해 들어 보지 못했다. "이 문자열 사용"은 무엇을 구성합니까? 다른 어셈블리가 생성 된 클래스의 상수 멤버를 참조 할 때를 의미합니까? 리플렉터가있는 다른 어셈블리에서 복사 된 상수를 볼 수 없습니까? 내 코드에서 실제로 무엇을하는지는이 속성을 getter에서 반환하여 구현되는 인터페이스를 만족시키는 것입니다. 필자는 컴파일러가 항상 상수를 반환하고 참조 어셈블리에 포함 할 수 있음을 알 수 없다고 확신합니다. 더 많은 정보는 어디에서 찾을 수 있습니까? – mckamey

+0

Console.WriteLine (MyClass.HugeString)을 호출하고 반사체를 보면 Console.WriteLine ("blah blah blubb ..") 만 표시됩니다. 참조가 사라졌습니다. const는 C++에서 정의하는 것과 비슷한 (그러나 다른) 컴파일 타임 상수입니다. reaadonly로 이것은 사실이 아닙니다. 자세한 내용을 확인하거나 C# 언어 사양을 읽으려면 "const vs readonly"에 대해 google을 사용하십시오. – codymanix

+0

고개를 숙고 해명 해 주셔서 감사합니다. 컴파일러의 상수 폴딩이 어 셈 블리 경계를 넘을 수 없는지 알 수 있습니다. 여기 예제 코드는 실제로 수행 한 작업에서 단순화되었으므로이 경우에는 괜찮을 것이라고 생각합니다. 실제로 문자열 리터럴을 getter 만 사용하여 속성에서 반환하려고합니다. property.GetStatements.Add (새 CodeMethodReturnStatement (새 CodeSnippetExpression (QuoteSnippetStringCStyle (str)))) ' – mckamey

-1

IIS의 응용 프로그램 풀에 32 비트 응용 프로그램이 설정되어 있는지 확인하십시오. Win7 64 비트에서 32 비트 응용 프로그램을 컴파일하려고하면이 문제가 해결됩니다. 이상하게도 Microsoft는이 대답을 제공하지 못했습니다.

http://darrell.mozingo.net/2009/01/17/running-iis-7-in-32-bit-mode/

+1

-1, 실패했습니다. 이 답변이 문제와 어떤 관련이 있는지 확인하십시오. 왜 C# 컴파일러는 IIS 응용 프로그램 풀에 신경을 써야합니까? – stakx