2009-06-06 28 views
7

Tengo una situación en la que necesito generar una clase con una const de cadena grande. El código fuera de mi control hace que mi árbol CodeDom generado se emita a la fuente C# y luego se compile como parte de un ensamblaje más grande.Solución temporal para C# CodeDom que causa el desbordamiento de pila (CS1647) en csc.exe?

Por desgracia, me he encontrado con una situación en la que si la longitud de esta cadena es superior a 335440 caracteres en Win2K8 x64 (926240 en Win2K3 x 86), las salidas compilador de C# con un error fatal:

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

MSDN dice que CS1647 es "un desbordamiento de pila en el compilador" (¡sin juego de palabras!). Mirando más de cerca, he determinado que el CodeDom "muy bien" envuelve mi const de cadena a 80 caracteres. Esto hace que el compilador concatene más de 4193 fragmentos de cadena que aparentemente es la profundidad de la pila del compilador de C# en x64 NetFx. CSC.exe debe evaluar internamente recursivamente esta expresión para "rehidratar" mi cadena única.

Mi pregunta inicial es la siguiente: "¿alguien sabe de un trabajo en torno a cambiar la forma en que el generador de código emite cadenas?" No puedo controlar el hecho de que el sistema externo utiliza fuente C# como un producto intermedio y quiero esta ser una constante (en lugar de una concatenación de cadenas en tiempo de ejecución).

Alternativamente, cómo puedo formular esta expresión tal que después de un cierto número de caracteres, lo sigo siendo capaz de crear una constante, sino que está compuesto de múltiples grandes trozos?

repro completo está aquí:

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

La versión csc.exe este se ejecuta bajo parece ser 2.0, a pesar de la orientación .NET 3.5. – mckamey

Respuesta

4

El uso de un CodeSnippetExpression y una cadena entre comillas manualmente, yo era capaz de emitir la fuente que yo hubiera gustado visto desde Microsoft.CSharp.CSharpCodeGenerator.

Así que para responder a la pregunta anterior, reemplace esta línea:

field.InitExpression = new CodePrimitiveExpression(HugeString); 

con esto:

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

Y finalmente modificar la cadena privada citando método Microsoft.CSharp.CSharpCodeGenerator.QuoteSnippetStringCStyle a lo envuelva después de 80 caracteres:

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

Gracias a Jon Skeet por la discusión que nos hizo pensar en esta solución. También gracias a Robert Harvey por pensar fuera de la caja. – mckamey

+0

Otra limitación de csc.exe a tener en cuenta al elegir no ajustar las constantes de cadena: "error CS1034: límite del compilador excedido: la línea no puede superar los 16777214 caracteres" Aparentemente, lo que se necesita es un híbrido: ajustar con tamaños de fragmentos realmente largos. – mckamey

+0

Esta respuesta permite * muchos * órdenes de magnitud más largas longitudes de cadena (léase: cientos de millones de caracteres). Las pruebas de tensión han demostrado que los límites de la memoria de la máquina se convierten en el nuevo tamaño límite. – mckamey

2

Así que estoy en lo cierto al decir que tenga el archivo fuente de C# con algo como:

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

y continuación intenta compilar ¿eso?

Si es así, trataría de editar el archivo de texto (en código, por supuesto) antes de compilar. Eso debería ser relativamente sencillo de hacer, ya que presumiblemente seguirán un patrón rígidamente definido (en comparación con el código fuente generado por el ser humano). Convierta para tener una sola línea masiva para cada constante. Avíseme si desea algún código de muestra para probar esto.

Por cierto, su repro tiene éxito sin errores en mi caja, ¿qué versión de la estructura está utilizando? (Mi caja tiene la versión beta de 4.0 en, que puede afectar las cosas.)

EDITAR: ¿Qué hay de cambiarlo para que no sea una cadena constante? Que había necesidad de romperlo a ti mismo, y emitirlo como un campo de sólo lectura estática pública como esto:

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

Fundamentalmente, string.Empty es un campo public static readonly, no una constante. Eso significa que el compilador de C# simplemente emitirá una llamada al string.Concat, que bien puede estar bien. Solo ocurrirá una vez en el tiempo de ejecución, por supuesto, más lento que hacerlo en tiempo de compilación, pero puede ser una solución más fácil que cualquier otra cosa.

+0

El tiempo de ejecución es .NET 3.5 pero no estoy seguro de si ejecuta el 2.0 csc.exe o más reciente cuando realmente compila el código. Golpeé el tamaño de la cuerda en la reproducción para que fallara en más circunstancias. Si todavía tiene éxito, 4.0 incrementó la profundidad de la pila o depende más de la máquina de un valor que el que sospechaba. Sí, la edición del archivo lo haría, pero desafortunadamente mi código está siendo llamado solo para devolver el árbol CodeDom. El código externo determina dónde y cuándo se emiten/compilan los archivos intermedios. – mckamey

+0

Ah. De acuerdo, editando con una idea extraña. –

+0

Te votaré pero aparentemente no participo lo suficiente en SO. Interesante. CodeDom no está lleno de C#, por lo que no puedo emitir de forma automática, pero quitar el concat le permite compilar. Ahora necesito ver si esto solo empuja el desbordamiento hacia el tiempo de ejecución. – mckamey

0

No tengo idea de cómo cambiar el comportamiento del generador de código, pero puede cambiar el tamaño de la pila que usa el compilador con la opción /stack de EditBin.EXE.

Ejemplo:

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

siguiente es un ejemplo de su uso:

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

Sugerencia interesante. Lamentablemente, cuando me llaman no tengo acceso directamente al csc.exe. Idealmente, me gustaría nunca tener que volver a preguntar si la cadena era demasiado larga. Este trabajo alternativo me exigiría seguir golpeando el tamaño de la pila a medida que crecía la cuerda. – mckamey

2

Tenga en cuenta que si declara la string como const, será copiado en cada ensamblado que use esta cadena en su código.

Puede ser mejor con solo lectura estática.

Otra forma sería declarar una propiedad de solo lectura que devuelve la cadena.

+0

Esto es interesante. No he oído hablar de esto. ¿Qué constituye "usar esta cadena"? ¿Quiere decir cuando otra asamblea hace referencia al miembro constante de la clase generada? ¿No podría ver la constante copiada en el otro ensamblaje con Reflector? Lo que realmente estoy haciendo en mi código es satisfacer una interfaz que se está implementando al devolver esta constante en un getter de propiedades. Estoy bastante seguro de que el compilador no podría saber que siempre iba a devolver la constante para poder dar la vuelta e insertarla en el conjunto de referencia. ¿Dónde puedo encontrar más información? – mckamey

+0

si llama a Console.WriteLine (MyClass.HugeString) y mira dentro del reflector, entonces solo verá Console.WriteLine ("blah blah blubb .."), la referencia ya no está. const son constantes de tiempo de compilación similares (pero diferentes) para definir en C++. Con mucha tranquilidad este no es el caso. google para "const vs readonly" para encontrar más información o leer las especificaciones del lenguaje C#. – codymanix

+0

Gracias por el aviso y la aclaración. Puedo ver cómo el plegado constante del compilador no podría cruzar los límites de ensamblaje para que tenga sentido. Creo que estaré bien en este caso ya que el código de ejemplo aquí se simplifica de lo que estoy haciendo en realidad. En realidad estoy construyendo un literal de cadena para devolverlo desde una propiedad con solo un getter: 'property.GetStatements.Add (new CodeMethodReturnStatement (new CodeSnippetExpression (QuoteSnippetStringCStyle (str))));' – mckamey

-1

Asegúrese de que los grupos de aplicaciones en IIS tengan habilitadas las aplicaciones de 32 bits. Eso fue todo lo que necesité para resolver este problema al intentar compilar una aplicación de 32 bits en Win7 de 64 bits. Curiosamente (o no), Microsoft no pudo proporcionar esta respuesta. Después de un día de búsqueda, me encontré este enlace a la corrección en un foro Diseñador Iron Speed:

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

+1

-1, fallo para ver cómo esta respuesta es relevante para el problema. ¿Por qué el compilador C# se preocupa por los grupos de aplicaciones de IIS? – stakx

Cuestiones relacionadas