none
CodeDom implicitly creating resource retrieval statements; causes unloadable code RRS feed

  • Question


  • I’m using a standard C# WinForms Designer to let the user create a Dialog box.  When the user wants to show the dialog box, I compile it into an Assembly file (a DLL) using my own subclass of CodeDomDesignerLoader, which I call FormEditorCodeDomDesignerLoader.

    The code that creates the CodeCompileUnit in the FormEditorCodeDomDesignerLoader looks like this:

            protected override CodeCompileUnit Parse()
            {
                DesignSurface ds = new DesignSurface();
                ds.BeginLoad(typeof (Form));
                IDesignerHost idh = (IDesignerHost)ds.GetService(typeof(IDesignerHost));

               return CreateCCU (idh);
            }

            private CodeCompileUnit CreateCCU (IDesignerHost idh)
            {
                idh.RootComponent.Site.Name = "Form1";

                cg = new FormEditorCodeGen();
                CodeCompileUnit ccu = cg.GetCodeCompileUnit(idh);

                AssemblyName[] names = Assembly.GetExecutingAssembly().GetReferencedAssemblies();
                for (int i = 0; i < names.Length; i++)
                {
                    Assembly assembly = Assembly.Load(names[i]);
                    ccu.ReferencedAssemblies.Add(assembly.Location);
                }

                codeCompileUnit = ccu;
                return ccu;
            }

           internal class FormEditorCodeGen
           {
             private CodeCompileUnit codeCompileUnit;
             private CodeNamespace ns;
             private CodeTypeDeclaration myDesignerClass = new CodeTypeDeclaration();
             private CodeMemberMethod initializeComponent = new CodeMemberMethod();
             private IDesignerHost host;
             private IComponent root;

             /// <summary>
             /// This function generates the default CodeCompileUnit template
            /// </summary>
             public CodeCompileUnit GetCodeCompileUnit(IDesignerHost host)
            {
                this.host = host;
                IDesignerHost idh = (IDesignerHost)this.host.GetService(typeof(IDesignerHost));
                root = idh.RootComponent;
                Hashtable nametable = new Hashtable(idh.Container.Components.Count);

                ns = new CodeNamespace("YetiFormEditor");
                myDesignerClass = new CodeTypeDeclaration();
                initializeComponent = new CodeMemberMethod();

                CodeCompileUnit code = new CodeCompileUnit();

                // Imports
                ns.Imports.Add(new CodeNamespaceImport("System"));
                ns.Imports.Add(new CodeNamespaceImport("System.ComponentModel"));
                ns.Imports.Add(new CodeNamespaceImport("System.Windows.Forms"));
                ns.Imports.Add(new CodeNamespaceImport("AGI.FormEditor"));
                code.Namespaces.Add(ns);
                myDesignerClass = new CodeTypeDeclaration(root.Site.Name);
                myDesignerClass.BaseTypes.Add(typeof(Form).FullName);

                IDesignerSerializationManager manager =
                                  host.GetService(typeof(IDesignerSerializationManager)) as IDesignerSerializationManager;

                ns.Types.Add(myDesignerClass);

                // Constructor
                CodeConstructor con = new CodeConstructor();

                con.Attributes = MemberAttributes.Public;
                con.Statements.Add(new CodeMethodInvokeExpression(
               new CodeMethodReferenceExpression(new CodeThisReferenceExpression(), "InitializeComponent")));
                myDesignerClass.Members.Add(con);

                // InitializeComponent
                initializeComponent.Name = "InitializeComponent";
                initializeComponent.Attributes = MemberAttributes.Private;
                initializeComponent.ReturnType = new CodeTypeReference(typeof(void));
                myDesignerClass.Members.Add(initializeComponent);
                codeCompileUnit = code;
                return codeCompileUnit;
     }
           }// class

     


    The code that takes the CodeCompileUnit and turns it into an Assembly (DLL) file looks like this:

            public bool Build (string dllFileName)
            {
                   Flush();

                   // We need to collect the parameters that our compiler will use.
                   CompilerParameters cp = new CompilerParameters();
                   AssemblyName[] assemblyNames = Assembly.GetEntryAssembly().GetReferencedAssemblies();

                   foreach (AssemblyName an in assemblyNames)
                   {
                          Assembly assembly = Assembly.Load(an);
                          cp.ReferencedAssemblies.Add(assembly.Location);
                   }

                   cp.GenerateExecutable = false;  // only set to true for code that contains an entry point
                   cp.OutputAssembly = dllFileName;

                  // Remember our main class is not Form, but Form1!
                 string mainClass = "YetiFormEditor.Form1";
                 cp.MainClass = mainClass;

                 CSharpCodeProvider cc = new CSharpCodeProvider();
                 CompilerResults cr = cc.CompileAssemblyFromDom(cp, codeCompileUnit);

                  return !cr.Errors.HasErrors;
            }


    Here’s my problem:

    Whenever any of the controls within the Dialog box being designed has a string Property that’s longer than 200 characters, the code in the generated DLL contains an implicit call to retrieve this string from the DLL Assembly’s resources – and the DLL Assembly doesn’t HAVE any string resources!  This causes an exception when attempting to call dllAssembly.CreateInstance (“YetiFormEditor.Form1”);.  If the string Property is 200 characters or shorter, this exception does not occur and the dialog box gets created successfully.

    I looked at the C# code in the CodeCompileUnit (via CSharpCodeProvider.GenerateCodeFromCompileUnit() ).  If the string Property is 200 characters or less I’ll see this:

    this.comboBox1.MyProperty = "012345678901234567890123456789012345678901234567890123456789012345678901234567890" +
    "12345678901234567890123456789012345678901234567890123456789012345678901234567890" +
    "123456789012345678901234567890123456789";

    … but if the string Property is more than 200 characters I’ll see this:

    this.comboBox1.MyProperty = resources.GetString("comboBox1.MyProperty");


    I cannot believe no one else has run into this issue, considering how commonplace Properties longer than 200 characters must be (e.g. Image properties, the contents of a multi-line text edit, etc.).  So:

    Is there a way to either make the CodeCompileUnit (or the CompileAssemblyFromDom() call) explicitly include the string resources this generated code depends on, or a way to make the CodeCompileUnit/CompileAssemblyFromDom() generate code for long strings that doesn’t require resources?


    Thanks!

    • Moved by Larcolais Gong Monday, May 23, 2011 5:26 AM (From:Visual C# General)
    Thursday, May 19, 2011 9:12 PM

All replies

  • I'll help you moving your thread into CLR forum. You will get more helpful suggestions there.

    Best Regards,


    Larcolais Gong[MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Monday, May 23, 2011 5:25 AM
  • Instead of directly assigning value to string property of a control (e.g. label's Text property), VS automatically put long literal string to Form1.resx file. For example, you can give a long text to a label control of Form1, and then open the Form1.Designer.cs, and you will find:

     

    this.label1.Text = resources.GetString("label1.Text");

     

    The actual string is stored in the Form1.resx file which is built as "Embedded Resource";

     

    In this case, you may want to use CompilerParameters.EmbeddedResources property to specify resource files for the generated assembly.


    Eric Yang [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Tuesday, May 24, 2011 2:14 AM
  • The issue here is that this Form1.resx file you mention does not appear to exist – or at least, I haven’t a clue as to how to find it.

     

    As you can see from the code I previously provided, I take an IDesignerHost – the one provided automatically by the Standard WinForms dialog Designer classes, which I have not edited and presumably cannot edit – then I feed its IDesignerHost.RootComponent.Site.Name to a CodeTypeDeclaration constructor, then I feed that to a CodeNamespace’s Types collection, then I feed that CodeNamespace to the Namespaces collection of a CodeCompileUnit, then finally I call CompileAssemblyFromDom() on that CodeCompileUnit.

     

    At no time along this chain of events do I specify a .resx file.  Yet the “resource.GetString(“controlName.Text”)” call you mention is being generated in the code output from CompileAssemblyFromDom() anyway.  (I’ve confirmed this by calling GenerateCodeFromCompileUnit() on the CSharpCodeProvider and looking at the code that comes out.)

     

     

    Do the standard Designer classes automatically produce a .resx file internally if you feed them to a CodeCompileUnit in this manner?  If so, is it possible for me to get access to this .resx file data, perhaps via one of the Designer classes I have access to, or (even better) is there a way to tell the Designer to turn off this feature so that a long text label does not generate a string resource entry?

     

    Wednesday, May 25, 2011 8:03 PM
  • What I have found is that winform controls use  ControlCodeDomSerializer as their CodeDomSerializer, however, I didn't find any related logic.

     

    We're working on this case now, will let you know as soon as there is any update.


    Eric Yang [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Thursday, May 26, 2011 6:46 AM
  • Is there any movement on this?  Ir ealize this is not a trivial question, but this is kind of a blocking issue for us.  Any status update?
    Monday, June 6, 2011 8:03 PM
  • Hi,

    If this is critical for you, consider support options for more in depth level or review. A repro of this and evalutation would take some time.

    Your question falls into the paid support category which requires a more in-depth level of support.  Please visit the below link to see the various paid support options that are available to better meet your needs.

    http://support.microsoft.com/default.aspx?id=fh;en-us;offerprophone

    Thanks!


    bill boyce
    Tuesday, June 7, 2011 1:43 PM
    Moderator
  • You need to implement IResourceService and add it to design services.

    When you call GenerateCodeFromCompileUnit the code generator will also call IResourceService methods (the GetResourceWriter method).

    In this method you need to write the file that you will add to EmbeddedResources collection.

    Monday, April 14, 2014 11:11 AM