2009-06-06 14 views
7

Mam sytuację, w której muszę wygenerować klasę z dużą ciąg const. Kod spoza mojej kontroli powoduje, że moje wygenerowane drzewo CodeDom jest emitowane do źródła C#, a następnie kompilowane jako część większego złożenia.Obejście problemu dla C# CodeDom powodującego przepełnienie stosu (CS1647) w csc.exe?

Niestety, mam biegać w sytuacji, w której, jeśli długość tego ciągu przekracza 335440 znaków w Win2K8 x64 (926240 w Win2K3 x86), wyjść kompilator C# z fatalnym błędzie:

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

MSDN mówi CS1647 "przepełnienie stosu w kompilatorze" (gra słów nie jest przeznaczona!). Przyjrzyjmy się bliżej, stwierdziłem, że CodeDom "ładnie" opakowuje ciąg znaków na 80 znaków. To powoduje, że kompilator łączy się z 4193 fragmentami łańcuchów, co najwyraźniej jest głębokością stosu kompilatora C# w x64 NetFx. CSC.exe musi wewnętrznie rekurencyjnie ocenić to wyrażenie, aby "nawodnić" mój pojedynczy ciąg.

Moje pierwsze pytanie brzmi następująco: "czy ktoś wie o obejściu, aby zmienić sposób generowania kodu przez generator kodu?" Nie mogę kontrolować faktu, że system zewnętrzny używa źródła C# jako pośredniego i chcę tego być ciągłym (zamiast ciągłym łączeniem ciągów).

Alternatywnie jak mogę sformułować wyrażenie to tak, że po określonej liczbie znaków, nadal jestem w stanie stworzyć stały, ale składa się z wielu dużej kawałki?

Pełna repro jest tutaj:

// 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

Wersja csc.exe ten przebiega pod wydaje się być 2,0, pomimo kierowania .NET 3.5. – mckamey

Odpowiedz

4

Stosując CodeSnippetExpression i cytowane ręcznie łańcuch, i był w stanie emitować źródła, które będę bicykle widziałem z Microsoft.CSharp.CSharpCodeGenerator.

więc odpowiedzieć na pytanie powyżej, zastąpić ten wiersz:

field.InitExpression = new CodePrimitiveExpression(HugeString); 

z tym:

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

I wreszcie zmodyfikować prywatny ciąg cytowanie metodę Microsoft.CSharp.CSharpCodeGenerator.QuoteSnippetStringCStyle aby nie zawijać po 80 znakach:

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

Dziękuję Jonowi Skeetowi za dyskusję, która skłoniła to rozwiązanie do myślenia. Także dzięki Robertowi Harveyowi za myślenie nieszablonowe. – mckamey

+0

Kolejne ograniczenie csc.exe, które należy wziąć pod uwagę przy wyborze, aby nie owijać stałych łańcuchowych: "error CS1034: Przekroczono limit kompilatora: Linia nie może przekroczyć 16777214 znaków" Najwyraźniej potrzebne jest hybryda: wrap z naprawdę długimi rozmiarami porcji. – mckamey

+0

Ta odpowiedź pozwala na * wiele * rzędów wielkości dłuższych łańcuchów znaków (czytaj: setki milionów znaków). Testy wytrzymałościowe wykazały, że limity pamięci maszyny stają się nowym rozmiarem granicznym. – mckamey

2

Więc mam rację, mówiąc, że masz plik # źródłowy C z czymś takim:

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

i ci następnie spróbować skompilować to?

Jeśli tak, spróbuję edytować plik tekstowy (oczywiście w kodzie) przed kompilacją. To powinno być stosunkowo łatwe do wykonania, ponieważ prawdopodobnie będą podążać za sztywno zdefiniowanym wzorem (w porównaniu z generowanym przez ludzi kodem źródłowym). Przekonwertuj go, aby miał pojedynczą masywną linię dla każdej stałej. Daj mi znać, jeśli chcesz wypróbować przykładowy kod.

Nawiasem mówiąc, twoje repro się powiedzie bez żadnych błędów na moim pudełku - której wersji frameworka używasz? (Moje pudełko ma wersję beta 4.0, która może mieć wpływ na rzeczy.)

EDYCJA: A co powiesz na zmianę tej wartości, tak aby nie była stała łańcuchową? trzeba by złamać go samemu, i emitują go jako public static readonly pola tak:

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

najważniejsze, string.Empty jest polem public static readonly, nie stałą. Oznacza to, że kompilator C# po prostu wyśle ​​wywołanie do string.Concat, które może być w porządku. Zdarza się to tylko raz w czasie wykonywania - wolniej niż w czasie kompilacji, ale może być łatwiejszym rozwiązaniem niż cokolwiek innego.

+0

Środowisko wykonawcze to .NET 3.5, ale nie jestem pewien, czy uruchamia program 2.0 csc.exe lub nowszy, gdy faktycznie kompiluje kod. Zderzyłem się z rozmiarem sznurka w replice, aby w kolejnych okolicznościach nie zadziałało. Jeśli nadal się uda, to albo 4.0 zwiększa głębię stosu, albo jest bardziej zależne od maszyny niż wartość, którą podejrzewałam. Tak, edytowanie pliku byłoby możliwe, ale niestety mój kod jest wywoływany tylko w celu zwrócenia drzewa CodeDom. Kod zewnętrzny określa, gdzie i kiedy pliki pośrednie są emitowane/kompilowane. – mckamey

+0

Ah. Ok, edytuję z dziwnym pomysłem. –

+0

Chciałbym zagłosować, ale najwyraźniej nie uczestniczę w nim wystarczająco dużo. Interesujące. CodeDom nie jest pełnym C#, więc nie mogę faktycznie emitować tylko do odczytu, ale usunięcie konkatutu pozwala na jego kompilację. Teraz muszę sprawdzić, czy to po prostu przesyła przepełnienie do środowiska wykonawczego. – mckamey

0

Nie mam pojęcia, jak zmienić zachowanie generatora kodu, ale można zmienić rozmiar stosu, który kompilator używa z opcją /stack z EditBin.EXE.

przykład:

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

Poniżej przedstawiono przykład użycia:

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

Interesująca sugestia. Niestety tam, gdzie jestem nazywany, nie mam bezpośredniego dostępu do csc.exe. Najlepiej byłoby, gdyby nigdy więcej nie musiałem pytać, czy łańcuch jest zbyt długi. Ta praca wymagałaby ode mnie podważania wielkości stosu w miarę wzrostu sznurka. – mckamey

2

Pamiętaj, że jeśli zadeklarujesz string jako const, będzie skopiowany w każdym zestawie, który używa tego ciągu w swoim kodzie.

Możesz być lepiej ze statycznym tylko do odczytu.

Innym sposobem byłoby zadeklarowanie właściwości tylko do odczytu, która zwraca ciąg znaków.

+0

To jest interesujące. Nie słyszałem o tym. Co stanowi "używanie tego ciągu"? Czy masz na myśli, gdy inny zestaw odwołuje się do stałego elementu wygenerowanej klasy? Czy nie byłbym w stanie zobaczyć skopiowanej stałej w drugim zespole z reflektorem? To, co faktycznie robię w moim kodzie, jest satysfakcjonujący interfejs, który jest realizowany przez zwrócenie tej stałej w getwerze właściwości. Jestem prawie pewien, że kompilator nie będzie w stanie wiedzieć, że zawsze będzie zwracać stałą, aby móc odwrócić i osadzić go w zestawie odniesienia. Gdzie mogę znaleźć więcej informacji? – mckamey

+0

jeśli zadzwonisz do Console.WriteLine (MyClass.HugeString) i zajrzysz do reflektora, to zobaczysz tylko Console.WriteLine ("bla bla bla .."), odniesienie zniknęło. const są stałe kompilacji podobne (ale różne) do zdefiniowania w C++. Z całą pewnością tak nie jest. google dla "const vs readonly", aby znaleźć więcej informacji lub przeczytać specyfikacje języka C#. – codymanix

+0

Dzięki za podpowiedzi i wyjaśnienia. Widzę, że stałe składanie kompilatora nie byłoby w stanie przekraczać granic zespołu, więc ma to sens. Myślę, że będę w porządku w tym przypadku, ponieważ kod przykładowy tutaj jest uproszczony z tego, co faktycznie robię. W rzeczywistości buduję literał łańcuchowy, aby zwrócić go z właściwości z tylko getter: 'property.GetStatements.Add (new CodeMethodReturnStatement (nowa funkcja CodeSnippetExpression (QuoteSnippetStringCStyle (str)));' – mckamey

-1

Upewnij się, że pule aplikacji w usługach IIS mają włączone aplikacje 32-bitowe. To wszystko zajęło mi wyleczenie tego problemu, próbując skompilować 32-bitową aplikację na 64-bitowym Win7. Dziwnie (lub nie) firma Microsoft nie mogła dostarczyć tej odpowiedzi. Po całym dniu poszukiwań, znalazłem ten link do montażu na forum Iron Speed ​​Designer:

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

+1

-1, nie działa aby zobaczyć, jak ta odpowiedź jest istotna dla problemu. Dlaczego kompilator C# powinien dbać o pule aplikacji IIS? – stakx

Powiązane problemy