Tactics for Debugging Custom Components with CodeDom Serialized State

M

Mark Olbert

I have a custom component which has a complex internal state that is serialized using CodeDom serialization. It is part of a library
used in a moderately complex project. The component has a custom design-time interface displayed within VS2003. All of the library
assemblies are loaded into the global assembly cache on my development machine.

This is all under VS2003 and .NET 1.1.

Unfortunately, for reasons I have not been able to fathom, the project is very "touchy". Changes to any one of the library
assemblies (there are 17) tends to break the deserialization process. I suspect this is due to known problems with VS2003's
synchronization of changed global cache assemblies.

The resulting error messages are not at all helpful (e.g., Method not found, object cannot be converted to target type). What's
worse, I can't seem to "break" on where the problem is occuring, and single-stepping through the component's initialization process
doesn't run into any problems.

The only clue I've been able to find is that commenting out the following line in InitializeComponent() makes the "object cannot be
converted to target type" error message go away):

this.AccessControl = new OlbertMcHughLLC.DBFramework.Security.AccessControl("acl", "idnum", "name", "acl_detail", "acl_id", "name",
"can_read", "can_write", "can_add", "can_delete");

What's weird about this is that the object being created >>is<<, in fact, of the correct Type...which is why I think the problem is
with VS2003's support for syncing assemblies.

I'd appreciate hearing any tactics/strategies which others have found helpful debugging these kinds of problems.

- Mark

p.s. and please, MS support staff, don't ask me to send the entire project to someone -- it's megabytes of source code, and it'd
take me a week to explain how it ought to work.
 
L

Luke Zhang [MSFT]

Hello,

Thank you for using MSDN newsgroup. Regarding the issue, we are finding
proper resource to assist you and will update you as soon as possible.

Luke
 
J

Jeffrey Tan[MSFT]

Hi Mark,

Thanks for your post.

Based on my understanding, your project uses a lot of different
technologies, which causes some strange design-time errors.

Currently, in your post description, there is too much information unclear
to us, so it is not easy for us to provide an end *solution*. For example,
I am not sure if you used the CodeDOM serialization correct, and if CodeDOM
serialization is the correct solution for serializing your objects. Maybe
binary or xml serialization is more suitable for your complex objects. I
will try to provide some debugging information to you. (This is what I will
do, if I am in your position)

I assume you see these error messages in the VS.net2003 designer surface.
Actually, VS.net2003 designer will catch all the deserialization exceptions
and display them on the designer surface. However, this information is not
enough for troubleshooting out the root cause. It is better that we can get
the call stack of the exception, and then we can find out which statement
(in our code) in the stack caused this exception. To do this, we should
first setup the symbol correctly, and then use another instance of
VS.net2003 (debugger) to attach our problematic VS.net2003 instance
(debuggee). Now, we should also enable the first chance exception handling
in the "Exceptions" dialog in debugger, so the debugger will catch the
exception before the debuggee. When the exception throws out, in the
debugger call stack window, we can get the list of exception call stack.
(You have to setup the symbol correctly)

For more information, please refer to the article below:
"Debugging Design-Time Controls"
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwinforms/
html/designtimedebugging.asp

Hope this helps

Best regards,
Jeffrey Tan
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "as is" with no warranties and confers no rights.
 
M

Mark Olbert

Jeffrey,

Thanks for your message. I've interleaved my replies.
I am not sure if you used the CodeDOM serialization correct, and if CodeDOM serialization is the correct solution for serializing your objects.

I need the state of the component to be serialized into its source code, and for reasons that would be too complicated to go into
here, the InstanceDescriptor approach won't work (put simply, the internal state of the component is too involved to fit that
model).

It's important to keep in mind that the CodeDOM implementation I am using was working fine. The only change I made was to a support
assembly used by the project (which actually has nothing to do with the code serialization). So there's some kind of unintended
consequence, or parallel bug, occuring.
I assume you see these error messages in the VS.net2003 designer surface.

Not quite. They appear in the Task List window below the design surface as "Build Errors". I'm familiar with the kind of exceptions
you're talking about (e.g., failure to secure a valid license). This problem is a little different -- the custom design surface
appears, and some of the state information is correctly displayed, but much of the state information doesn't appear. For example,
this line is involved with the failure to convert Type (I say this because commenting out the line in the component's
InitializeComponent() method makes that particular build error go away):

this.AccessControl = new OlbertMcHughLLC.DBFramework.Security.AccessControl("acl", "idnum", "name", "acl_detail", "acl_id", "name",
"can_read", "can_write", "can_add", "can_delete");

What confuses me about this supposed Type conversion error is that the AccessControl property is >>defined as<<
OlbertMcHughLLC.DBFramework.Security.AccessControl (I tried making that definition explicit -- i.e., by not relying on the using
statement, just in case I had a name collision -- but that didn't solve the problem). How an instance of a Type cannot have the same
Type is confusing. But I've run into that problem before, when an assembly is loaded both dynamically and statically. The Framework
considers the two assemblies to be different, even though they're not. Is there a tool I can use to see if assemblies are being
loaded both dynamically and statically? I don't know why that would suddenly crop up in a working build, but at this point I'll try
anything.

This is an example of a statement in InitializeComponent() which causes the method not found error:

this.TableList.Add(new TableInfo("appeal", "Appeals", "records information on which Donot Groups were solicited in which
fund-raising appeals", "", "appeal_desc", "appeal_desc", "OlbertMcHughLLC.SolDon.SCEFRowCalculator,SolDonBrowser",
"CalculateAppeal", new DataField[] {
new LookupField("giver_id", "giver", "Donor Group ID", "", false, false, false, false, true),
new LookupField("campaign_id", "campaign", "Campaign ID", "", false, false, false, false, true),
new LookupField("year_id", "year", "Year ID", "", false, false, false, false, true),
new LookupField("user_id", "user", "User ID Number", "", false, false, false, true, true),
new DataField("appeal_date", typeof(System.DateTime), "Date Appeal Made", "", false, false, false, false, false, false,
true),
new TextField("notes", "Notes", "", false, false, false, false, false, true, 1073741823, true),
new CalculatedField("appeal_desc", typeof(string), "appeal_desc", "", false, false, false)}));

I don't expect this to make a lot of sense to you out of context, but the key point is this: the TableInfo object being added to the
TableList collection is of the same Type as the TableList collection is defined to contain (it's a strongly-typed collection). I'm
guessing that the reason the method can't be found is because, again, the Type of the TableInfo object being created is subtly
different, so far as the Framework is concerned, from the Type the collection is designed to contain. Again, this leads me back to
that static/dynamic assembly loading silliness.
When the exception throws out, in the debugger call stack window, we can get the list of exception call stack.
(You have to setup the symbol correctly)

I am familiar with that technique of using one instance of VS2003 to debug another. In fact, I cannot imagine how one could develop
things like custom control designers without it :).

Good point about dumping the exception call stack. I should've included that in the original post. Here is the dump for the failure
to convert Type:

mscorlib.dll!System.Reflection.RuntimeMethodInfo.InternalInvoke(System.Object obj =
{OlbertMcHughLLC.DBFramework.OfflineQuery.OfflineSqlDataPackage}, System.Reflection.BindingFlags invokeAttr = Default,
System.Reflection.Binder binder = <undefined value>, System.Object[] parameters = {Length=1}, System.Globalization.CultureInfo
culture = <undefined value>, bool verifyAccess = true) + 0x232 bytes
mscorlib.dll!System.Reflection.RuntimeMethodInfo.Invoke(System.Object obj =
{OlbertMcHughLLC.DBFramework.OfflineQuery.OfflineSqlDataPackage}, System.Reflection.BindingFlags invokeAttr = Default,
System.Reflection.Binder binder = <undefined value>, System.Object[] parameters = {Length=1}, System.Globalization.CultureInfo
culture = <undefined value>) + 0x23 bytes
mscorlib.dll!System.Reflection.MethodBase.Invoke(System.Object obj =
{OlbertMcHughLLC.DBFramework.OfflineQuery.OfflineSqlDataPackage}, System.Object[] parameters = {Length=1}) + 0x1d bytes
mscorlib.dll!System.Reflection.MethodInfo.Invoke(System.Object obj =
{OlbertMcHughLLC.DBFramework.OfflineQuery.OfflineSqlDataPackage}, System.Object[] parameters = {Length=1}) + 0x15 bytes
system.dll!System.ComponentModel.ReflectPropertyDescriptor.SetValue(System.Object component =
{OlbertMcHughLLC.DBFramework.OfflineQuery.OfflineSqlDataPackage}, System.Object value =
{OlbertMcHughLLC.DBFramework.Security.AccessControl}) + 0x189 bytes
system.design.dll!System.ComponentModel.Design.InheritedPropertyDescriptor.SetValue(System.Object component =
{OlbertMcHughLLC.DBFramework.OfflineQuery.OfflineSqlDataPackage}, System.Object value =
{OlbertMcHughLLC.DBFramework.Security.AccessControl}) + 0x1a bytes

system.design.dll!System.ComponentModel.Design.Serialization.CodeDomSerializer.DeserializeAssignStatement(System.ComponentModel.Design.Serialization.IDesignerSerializationManager
manager = {Microsoft.VisualStudio.Designer.Serialization.VSDesignerLoader.VsCodeDomLoader}, System.CodeDom.CodeAssignStatement
statement = {System.CodeDom.CodeAssignStatement}) + 0x797 bytes

system.design.dll!System.ComponentModel.Design.Serialization.CodeDomSerializer.DeserializeStatement(System.ComponentModel.Design.Serialization.IDesignerSerializationManager
manager = {Microsoft.VisualStudio.Designer.Serialization.VSDesignerLoader.VsCodeDomLoader}, System.CodeDom.CodeStatement statement =
{System.CodeDom.CodeAssignStatement}) + 0x74 bytes

system.design.dll!System.ComponentModel.Design.Serialization.RootCodeDomSerializer.Deserialize(System.ComponentModel.Design.Serialization.IDesignerSerializationManager
manager = {Microsoft.VisualStudio.Designer.Serialization.VSDesignerLoader.VsCodeDomLoader}, System.Object codeObject =
{System.CodeDom.CodeTypeDeclaration}) + 0x12e5 bytes
microsoft.visualstudio.dll!Microsoft.VisualStudio.Designer.Serialization.CodeDomLoader.Load() + 0x235 bytes
microsoft.visualstudio.dll!Microsoft.VisualStudio.Designer.Serialization.BaseDesignerLoader.Load() + 0x98 bytes
microsoft.visualstudio.dll!Microsoft.VisualStudio.Designer.Serialization.BaseDesignerLoader.OnTextBufferLoaded(System.Object
sender = {Microsoft.VisualStudio.Designer.Shell.ShellTextBuffer}, System.EventArgs e = {...}) + 0x42 bytes

microsoft.visualstudio.dll!Microsoft.VisualStudio.Designer.Shell.ShellTextBuffer.Microsoft.VisualStudio.Interop.IVsTextBufferDataEvents.OnLoadCompleted(bool
fReload = false) + 0x78 bytes

Here is the exception trace for the failure to find a method:

mscorlib.dll!System.DefaultBinder.BindToMethod(System.Reflection.BindingFlags bindingAttr = 276,
System.Reflection.MethodBase[] match = {Length=1}, System.Object[] args = {Length=1}, System.Reflection.ParameterModifier[]
modifiers = <undefined value>, System.Globalization.CultureInfo cultureInfo = <undefined value>, string[] names = <undefined value>,
System.Object state = <undefined value>) + 0xee2 bytes

system.design.dll!System.ComponentModel.Design.Serialization.CodeDomSerializer.DeserializeExpression(System.ComponentModel.Design.Serialization.IDesignerSerializationManager
manager = {Microsoft.VisualStudio.Designer.Serialization.VSDesignerLoader.VsCodeDomLoader}, string name = null,
System.CodeDom.CodeExpression expression = {System.CodeDom.CodeMethodInvokeExpression}) + 0x1bb3 bytes

system.design.dll!System.ComponentModel.Design.Serialization.CodeDomSerializer.DeserializeStatement(System.ComponentModel.Design.Serialization.IDesignerSerializationManager
manager = {Microsoft.VisualStudio.Designer.Serialization.VSDesignerLoader.VsCodeDomLoader}, System.CodeDom.CodeStatement statement =
{System.CodeDom.CodeExpressionStatement}) + 0x12f bytes

system.design.dll!System.ComponentModel.Design.Serialization.RootCodeDomSerializer.Deserialize(System.ComponentModel.Design.Serialization.IDesignerSerializationManager
manager = {Microsoft.VisualStudio.Designer.Serialization.VSDesignerLoader.VsCodeDomLoader}, System.Object codeObject =
{System.CodeDom.CodeTypeDeclaration}) + 0x12e5 bytes
microsoft.visualstudio.dll!Microsoft.VisualStudio.Designer.Serialization.CodeDomLoader.Load() + 0x235 bytes
microsoft.visualstudio.dll!Microsoft.VisualStudio.Designer.Serialization.BaseDesignerLoader.Load() + 0x98 bytes
microsoft.visualstudio.dll!Microsoft.VisualStudio.Designer.Serialization.BaseDesignerLoader.OnTextBufferLoaded(System.Object
sender = {Microsoft.VisualStudio.Designer.Shell.ShellTextBuffer}, System.EventArgs e = {...}) + 0x42 bytes

microsoft.visualstudio.dll!Microsoft.VisualStudio.Designer.Shell.ShellTextBuffer.Microsoft.VisualStudio.Interop.IVsTextBufferDataEvents.OnLoadCompleted(bool
fReload = false) + 0x78 bytes

For more information, please refer to the article below:
"Debugging Design-Time Controls"
http://msdn.microsoft.com/library/d...en-us/dnwinforms/html/designtimedebugging.asp

I'll take a look at the article.

- Mark
 
J

Jeffrey Tan[MSFT]

Hi Mark,

Thanks for your feedback.

Can you show me how many issues you meet in your project? Why do you paste
2 call stacks? Based on my understanding from this reply, I think the key
point is that the compile error from the code statement below:
this.AccessControl = new ....

Without reproduce out this issue, I am not sure why we can not assign the
type to the property of the same type. If you are sure it is caused by
dynamically and statically load issue. Can you provide a little sample
demonstration project for this specific issue? Then we can focus on this
specific issue, because researching on an issue without reproduce out lies
too much with guess.

Thanks for your understanding.

Best regards,
Jeffrey Tan
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "as is" with no warranties and confers no rights.
 
M

Mark Olbert

Jeffrey,

I've finally freed up some time to dig into this problem again. I'm putting together a simple example to send to you (the problem is
the simplest example involves six assemblies, a license file, and a SqlServer database with four tables...but I'm working on it).

In the course of putting that example/sample together, I think I may have found another clue. As I mentioned earlier, my suspicion
is that I'm running into a dynamic versus static assembly load problem, where two identical assemblies are being deemed to be
different, hence Types which should be related to each other are deemed not to be. Let me explain.

There are two classes of concern here, SqlDataPackage and OfflineSqlDataPackage. Each is defined in its own assembly:

Assembly MetaData.dll:

namespace OlbertMcHughLLC.DBFramework.MetaData
{
/// <summary>
/// a class defining all of the metadata associated with a database used in a
/// DBFramework-based application
/// <para>For more information on configuring the metadata for a DBFramework appplication, see the
/// <see href="package/overview.html">
/// SqlDataPackage configuration overview</see>.
/// </para>
/// </summary>
[
Designer(typeof(SqlDataPackageDocumentDesigner), typeof(IRootDesigner)),
DesignerSerializer(typeof(SqlDataPackageCodeSerializer), typeof(CodeDomSerializer)),
LicenseProvider(typeof(DBFLicenseProvider)),
]
public class SqlDataPackage : System.ComponentModel.Component, ISupportInitialize
{
// rest omitted

Assembly Offline.dll:

using OlbertMcHughLLC.DBFramework.MetaData;
using OlbertMcHughLLC.DBFramework.Support;

namespace OlbertMcHughLLC.DBFramework.OfflineQuery
{
/// <summary>
/// a class extending the SqlDataPackage class to include support for offline (ad hoc)
/// queries.
/// </summary>
[
Designer(typeof(OfflineSqlDataPackageDocumentDesigner), typeof(IRootDesigner)),
]
public class OfflineSqlDataPackage : SqlDataPackage
{
// rest omitted

As you can see the two classes are related to each other: OfflineSqlDataPackage is derived directly from SqlDataPackage.

Both SqlDataPackage and OfflineSqlDataPackage have custom designers associated with them so that they can be configured at
design-time (their internal state is pretty complex; SqlDataPackage essentially represents an entire SqlServer database, all its
tables, queries, relations, etc., as well as a bunch of optional metadata about those database objects, like friendly field names).

When a valid design-time configuration is serialized to source code a CodeDomSerializer class gets invoked. This class, as you know,
handles the grunt work of writing the source code representation of an SqlDataPackage object to disk.

One of the first steps involved in that serialization is a cast of the object being serialized to SqlDataPackage. This would be
expected to fail if it was given some arbitrary object, but the system is set up so that the only objects that ever get serialized
are SqlDataPackages or OfflineSqlDataPackages, which are related to each other.

Yet the Type cast fails! Here's the code snippet:

internal class SqlDataPackageCodeSerializer : CodeDomSerializer
{
public override object Serialize( IDesignerSerializationManager manager, object value )
{
// associate the component with the serializer
CodeDomSerializer baseClassSerializer = (CodeDomSerializer) manager.GetSerializer(typeof(SqlDataPackage).BaseType,
typeof(CodeDomSerializer));

SqlDataPackage thePkg = null;
try
{
// this next line fails!
thePkg = (SqlDataPackage) value;
}
catch( Exception eAll )
{
int i = 9;
i++;
}
// rest omitted

I've walked through this with the debugger, and value -- the object to be cast as an SqlDataPackage -- is, as expected, an
OfflineSqlDataPackage object. It's assembly-qualified Type name is exactly what I expected...yet it won't cast.

I look forward to hearing your suggestions/solutions to how to cast a child object back to its parent. That should always work...yet
it doesn't here.

- Mark
 
M

Mark Olbert

Jeffrey,

Some further info...

Taking out the explicit reference in the project that uses the library to MetaData.dll lets the design-time designer for the
OfflineSqlDataPackage object function (there's still some problems, but I'm optimistic). It also lets that cast succeed.

Of course, taking out the explicit reference prevents the project itself from compiling, so it's not a solution, just (maybe) a
clue.

One thing that suddenly occurred to me is that, some time ago, I switched all the library assemblies from being unsigned and not
stored in the global cache to being signed and stored in the global cache (besides being a "best practice" -- at least so far as the
strong naming is concerned -- this also seemed to solve some version dependency problems I had).

I thought I'd tested the designer component after doing that, but maybe I didn't.

Is it possible that this is all due to some weird interaction between strong-named assemblies? I'm asking -- rather than just
reverting -- because it's a fair amount of work to undo the change.

What do you think?

- Mark
 
M

Mark Olbert

Jeffrey,

I've resolved the problem by changing things so the library assemblies are not stored in the global assembly cache. Apparently,
doing that was what was causing the "child Type not descended from parent" type cast problem.

I don't understand why that should be the case. Why do you think that was happening?

- Mark
 
J

Jeffrey Tan[MSFT]

Hi Mark ,

In your length reply, there are too much context information unclear to me,
and I was confused by these information. It is hard for me to provide
useful information in this scenario.

Regarding your last reply, it seems that problem lies with stronge named
assembly which resides in GAC. Can you provide some detailed sample
reproduce steps on our side for better understanding?

Best regards,
Jeffrey Tan
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "as is" with no warranties and confers no rights.
 
M

Mark Olbert

Jeffrey,

Try this:

1) Create a component with a custom designer in a separate assembly which serializes itself to source code. Put some other dummy
code in the assembly which is unrelated to the designer, but can be called from an application.
2) Strong name it and put it in the GAC.
3) Create a Windows app which uses the assembly from (1). Add a reference to the assembly. Add something that references the dummy
code from the assembly.
4) Add an instance of the component with the custom designer you created in (1) to the app project.
5) Try to design the component.

If I'm right this should cause a problem.

As to going into this in more detail, frankly, I don't really have the time or interest to debug .NET. That's what Microsoft's
supposd to do :). But I would be willing to meet with someone from MS to demonstrate the problem to them if you'd like.

- Mark
 
J

Jeffrey Tan[MSFT]

Hi Mark ,

Thanks for your feedback.

I have tried these steps, however, I did not see any problem after doing #5
step.

Best regards,
Jeffrey Tan
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "as is" with no warranties and confers no rights.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top