Обходной путь для C # CodeDom, вызывающий переполнение стека (CS1647) в csc.exe? - PullRequest
7 голосов
/ 06 июня 2009

У меня есть ситуация, когда мне нужно сгенерировать класс с большой строкой const. Код за пределами моего контроля приводит к тому, что мое сгенерированное дерево CodeDom отправляется в исходный код C #, а затем компилируется как часть более крупной сборки.

К сожалению, я столкнулся с ситуацией, когда если длина этой строки превышает 335440 символов в Win2K8 x64 (926240 в Win2K3 x86), компилятор C # завершается с фатальной ошибкой:

фатальная ошибка CS1647: выражение слишком длинное или сложное для компиляции вблизи 'int'

MSDN говорит, что CS1647 - это «переполнение стека в компиляторе» (не каламбур!). Присмотревшись повнимательнее, я определил, что CodeDom «красиво» оборачивает мою строку const в 80 символов. Это заставляет компилятор объединять более 4193 фрагментов строки, что, по-видимому, является глубиной стека компилятора C # в x64 NetFx. CSC.exe должен внутренне рекурсивно вычислить это выражение, чтобы «перегидрировать» мою единственную строку.

Мой первоначальный вопрос таков: « кто-нибудь знает обходной путь для изменения способа генерации кода генератором строк? » Я не могу контролировать тот факт, что внешняя система использует источник 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);
}

Ответы [ 5 ]

4 голосов
/ 07 июня 2009

Используя CodeSnippetExpression и строку в кавычках вручную, я смог выдать источник, который мне хотелось бы видеть из 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();
}
2 голосов
/ 26 ноября 2009

Обратите внимание: если вы объявите строку как const, она будет скопирована в каждой сборке, которая использует эту строку в своем коде.

Возможно, вам лучше использовать статическое чтение.

Другим способом было бы объявить свойство только для чтения, которое возвращает строку.

2 голосов
/ 06 июня 2009

Так что я прав, говоря, что у вас есть исходный файл C # с чем-то вроде:

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

а вы потом пытаетесь его скомпилировать?

Если это так, я бы попытался отредактировать текстовый файл (конечно, в коде) перед компиляцией. Это должно быть относительно просто сделать, так как они предположительно будут следовать строго определенному шаблону (по сравнению с исходным кодом, созданным человеком). Преобразуйте это, чтобы иметь одну массивную линию для каждой постоянной. Дайте мне знать, если вам нужен пример кода, чтобы попробовать это.

Кстати, ваше репродукция без ошибок на моем компьютере - какую версию фреймворка вы используете? (Моя коробка имеет бета-версию 4.0, что может повлиять на ситуацию.)

РЕДАКТИРОВАТЬ: Как насчет изменения его, чтобы не быть строковой константой? Вам нужно было бы разбить его самостоятельно и выдать его как общедоступное статическое поле только для чтения, например:

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

Важно, что string.Empty - это поле public static readonly, не константа. Это означает, что компилятор C # просто выдаст вызов string.Concat, что вполне может быть хорошо. Конечно, это произойдет только один раз во время выполнения - медленнее, чем во время компиляции, но это может быть более простой обходной путь, чем все остальное.

0 голосов
/ 23 апреля 2011

Убедитесь, что в пулах приложений в IIS включены 32-разрядные приложения. Это все, что мне понадобилось, чтобы решить эту проблему, пытаясь скомпилировать 32-битное приложение в 64-битной Win7. Как ни странно (или нет), Microsoft не могла предоставить этот ответ. После целого дня поиска я нашел эту ссылку на исправление на форуме Iron Speed ​​Designer:

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

0 голосов
/ 06 июня 2009

Я не знаю, как изменить поведение генератора кода, но вы можете изменить размер стека, который использует компилятор, с параметром / stack , равным EditBin.EXE .

Пример:

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

Ниже приведен пример его использования:

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