解决csc.exe中导致堆栈溢出(CS1647)的C#CodeDom问题?

解决csc.exe中导致堆栈溢出(CS1647)的C#CodeDom问题?,c#,compiler-construction,codedom,csc,C#,Compiler Construction,Codedom,Csc,在这种情况下,我需要生成一个具有大字符串常量的类。我无法控制的代码导致生成的CodeDom树被发送到C#源代码,然后作为更大程序集的一部分进行编译 不幸的是,我遇到了这样一种情况:如果此字符串的长度在Win2K8 x64中超过335440个字符(在Win2K3 x86中为926240个字符),C#编译器将退出并出现致命错误: MSDN说CS1647是“编译器中的堆栈溢出”(没有双关语!)。仔细观察,我确定CodeDom“很好地”将我的字符串常量包装为80个字符。这会导致编译器连接超过4193个

在这种情况下,我需要生成一个具有大字符串常量的类。我无法控制的代码导致生成的CodeDom树被发送到C#源代码,然后作为更大程序集的一部分进行编译

不幸的是,我遇到了这样一种情况:如果此字符串的长度在Win2K8 x64中超过335440个字符(在Win2K3 x86中为926240个字符),C#编译器将退出并出现致命错误:

MSDN说CS1647是“编译器中的堆栈溢出”(没有双关语!)。仔细观察,我确定CodeDom“很好地”将我的字符串常量包装为80个字符。这会导致编译器连接超过4193个字符串块,这显然是x64 NetFx中C#编译器的堆栈深度。CSC.exe必须在内部递归计算此表达式,以“重新水化”我的单个字符串

我最初的问题是:“有人知道改变代码生成器如何发出字符串的变通方法吗?”我无法控制外部系统使用C#source作为中介的事实,我希望这是一个常量(而不是字符串的运行时串联)

或者,我如何表达这个表达式,使得经过一定数量的字符后,我仍然能够创建一个常量,但它是由多个大块组成的?

全文如下:

// 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);
}

那么,我说的对吗,你得到了C#源文件,其中包含如下内容:

public const HugeString = "xxxxxxxxxxxx...." +
    "yyyyy....." +
    "zzzzz.....";
然后你试着编译它

如果是这样,我会在编译之前尝试编辑文本文件(当然是代码)。这应该相对简单,因为他们可能会遵循严格定义的模式(与人工生成的源代码相比)。将其转换为每个常数有一条大质量线。如果您想要一些示例代码来尝试这个,请告诉我

顺便说一下,你的复制成功了,我的盒子上没有任何错误-你使用的是哪个版本的框架?(我的盒子的beta版本为4.0,这可能会影响一些东西。)

编辑:将其更改为非字符串常量如何?您需要自己将其分解,并将其作为公共静态只读字段发出,如下所示:

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

重要的是,
string.Empty
是一个
公共静态只读
字段,而不是常量。这意味着C#编译器只会发出一个对
string.Concat
的调用,这可能没问题。当然,这只会在执行时发生一次-比在编译时慢,但这可能是一种比其他任何方法都简单的解决方法。

我不知道如何更改代码生成器的行为,但您可以使用选项更改编译器使用的堆栈大小

例如:

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

使用CodeSnippet表达式和手动引用的字符串,我能够发出我希望从Microsoft.CSharp.CSharpCodeGenerator中看到的源代码

因此,要回答上述问题,请替换此行:

field.InitExpression = new CodePrimitiveExpression(HugeString);
为此:

field.InitExpression = new CodeSnippetExpression(QuoteSnippetStringCStyle(HugeString));
最后修改私有字符串quoting 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();
}
私有静态字符串QuoteSnippetStringCStyle(字符串值)
{
//CS1647:表达式太长或太复杂,无法在“…”附近编译
//如果换行次数过多(对于x64,335440为最大值,对于x86,926240为最大值),则会发生这种情况
//CS1034:超出编译器限制:行不能超过16777214个字符
//理论上,每个字符都可以用unicode(6个字符)加引号等进行转义。
常量int LineWrapWidth=(16777214/6)-4;
StringBuilder b=新的StringBuilder(值.Length+5);
b、 追加(“\r\n\”);

对于(int i=0;i请注意,如果将字符串声明为const,则它将在每个在其代码中使用此字符串的程序集中复制

使用静态只读可能会更好


另一种方法是声明一个返回字符串的只读属性。

确保IIS中的应用程序池已启用32位应用程序。这就是我试图在Win7 64位中编译32位应用程序时解决此问题的全部方法。奇怪的是(或不是),Microsoft无法提供此答案。经过一整天的搜索,我在Iron Speed Designer论坛上找到了此修复程序的链接:


运行时为.NET 3.5,但我不确定它在实际编译代码时是否执行2.0 csc.exe或更高版本。我在repu中调整了字符串大小,以便在更多情况下失败。如果仍然成功,则4.0增加了堆栈深度,或者它比我想象的更依赖于机器。是的,编辑e文件可以做到这一点,但不幸的是,调用我的代码只是为了返回CodeDom树。外部代码决定中间文件的发出/编译位置和时间。我会投票支持你,但显然我参与得不够。有趣的是,CodeDom不是完整的C#,所以我实际上无法发出readonly,但取消了concates允许它编译。现在我需要看看这是否只是将溢出推到运行时。不过,你的建议给了我一个想法:当我说我不能发出readonly时,我认为我可以从CodeDom发出一个C#source的直接字符串,以发出“readonly”“。如果我能做到这一点,那么我可以回到一个简单的常量,但只是作为一行C#发出。尝试一下……缺少“readonly”似乎很奇怪。然而,这篇博客文章似乎证实了这一点:如果使用逐字字符串文字(即,在开始处放置@),那么转义字符串应该不会太难-那么你唯一要做的就是用“替换”"。有趣的建议。不幸的是,在我被调用的地方,我没有直接访问csc.exe的权限。理想情况下,我不想再质疑字符串是否太长。这项工作需要我随着字符串的增长不断调整堆栈大小。尽管目标是.NET 3.5,但运行该命令的csc.exe版本似乎是2.0.多亏了J
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();
}