Steema Issues Database

Note: This database is for bugs and wishes only. For technical support help, if you are a customer please visit our online forums;
otherwise you can use StackOverflow.
Before using this bug-tracker we recommend a look at this document, Steema Bug Fixing Policy.



Bug 718 - Deserialization fail on user customized classes which derive from TeeChart classes
Summary: Deserialization fail on user customized classes which derive from TeeChart cl...
Status: VERIFIED INVALID
Alias: None
Product: .NET TeeChart
Classification: Unclassified
Component: Importing (show other bugs)
Version: unspecified
Hardware: PC Windows
: --- blocker
Target Milestone: ---
Assignee: Steema Issue Manager
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2014-04-11 05:09 EDT by cqb
Modified: 2014-04-17 06:02 EDT (History)
1 user (show)

See Also:
Chart Series: ---
Delphi / C++ Builder RAD IDE Version:


Attachments
Deserialization bug in TeeChart (36.25 KB, application/x-zip-compressed)
2014-04-11 05:09 EDT, cqb
Details

Note You need to log in before you can comment on or make changes to this bug.
Description cqb 2014-04-11 05:09:47 EDT
Created attachment 170 [details]
Deserialization bug in TeeChart

I experienced an exception about deserialization of user customized class, which is derived from TeeChart class.

The Serialization/Deserialization in TeeChart will fail if all following conditions are satisfied:
  1. A user customized class which derives from TeeChart, and the class is not defined in TeeChart.dll
  2. One or more calling dlls call the class in step 1.
  3. A program will fail to deserialize the class in step 1, if it indirectly calling it by calling one or more intermediate dlls in step 2.

I have also developed a demo program to describe the bug in detail

Reason:
  "private Type FindType(string typeName) in Steema.TeeChart.Import.Imports" can't load the assembly where the user customized class is defined
  I think this bug will affect all TeeChart products and is very serious

See also:
  there are also other users who have experienced this bug several years ago but no solution, please refer to the link:
  http://www.teechart.net/support/viewtopic.php?f=4&t=12416

the exception stacktrace looks like:
System.Reflection.TargetInvocationException was unhandled
  Message=Exception has been thrown by the target of an invocation.
  Source=mscorlib
  StackTrace:
       at System.RuntimeMethodHandle._SerializationInvoke(IRuntimeMethodInfo method, Object target, SignatureStruct& declaringTypeSig, SerializationInfo info, StreamingContext context)
       at System.RuntimeMethodHandle.SerializationInvoke(IRuntimeMethodInfo method, Object target, SignatureStruct declaringTypeSig, SerializationInfo info, StreamingContext context)
       at System.Reflection.RuntimeConstructorInfo.SerializationInvoke(Object target, SerializationInfo info, StreamingContext context)
       at System.Runtime.Serialization.ObjectManager.CompleteISerializableObject(Object obj, SerializationInfo info, StreamingContext context)
       at System.Runtime.Serialization.ObjectManager.FixupSpecialObject(ObjectHolder holder)
       at System.Runtime.Serialization.ObjectManager.DoFixups()
       at System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
       at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
       at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream)
       at Steema.TeeChart.Import.TemplateImport.Load(Stream stream)
       at AppFail.FormFail.button2_Click(Object sender, EventArgs e) in D:\Desktop\DeserialDemo\AppFail\FormFail.cs:line 38
       at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
       at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
       at System.Windows.Forms.Control.WndProc(Message& m)
       at System.Windows.Forms.ButtonBase.WndProc(Message& m)
       at System.Windows.Forms.Button.WndProc(Message& m)
       at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
       at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
       at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
       at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
       at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
       at AppFail.Program.Main() in D:\Desktop\DeserialDemo\AppFail\Program.cs:line 18
       at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException: System.NullReferenceException
       Message=Object reference not set to an instance of an object.
       Source=TeeChart
       StackTrace:
            at Steema.TeeChart.Import.Imports.DeserializeFrom(SerializationInfo info, StreamingContext context)
            at Steema.TeeChart.Chart..ctor(SerializationInfo info, StreamingContext context)
       InnerException: 


Environment:
  Windows 7,
  VS 2010,
  .Net Framework 4.0
  Steema TeeChart for .NET 2012 4.1.2012.09280
  Steema TeeChart for .NET 2013 4.1.2013.11080
Comment 1 christopher ireland 2014-04-11 06:19:47 EDT
The problem here is that when the TeeChart serialization code searches for a type, it does search for the type in all libraries referenced by the ExecutingAssembly, as well as in all libraries referenced by the EntryAssembly, but what it doesn't do is recursively search through all assemblies referenced by those assemblies, which is in fact your case. The code we're running looks like this:

    private Type FindType(string typeName)
    {
      Type result = InternalFindType(typeName, GetType().Assembly);

      if (result == null)
        result = InternalFindType(typeName, Assembly.GetExecutingAssembly());

      if (result == null)
        result = InternalFindType(typeName, Assembly.GetEntryAssembly());

      if (result == null)
      {
        foreach (AssemblyName a in Assembly.GetExecutingAssembly().GetReferencedAssemblies())
        {
          result = InternalFindType(typeName, Assembly.Load(a));
          if (result != null)
          {
            break;
          }
        }
      }

      if (result == null)
      {
        foreach (AssemblyName a in Assembly.GetEntryAssembly().GetReferencedAssemblies())
        {
          result = InternalFindType(typeName, Assembly.Load(a));
          if (result != null)
          {
            break;
          }
        }
      }

      return result;
    }

This gives you an easy workaround - instead of your AppFail project referencing CallingLib, it can reference MyLib meaning FormFail would look like this:

    private void Form1_Load(object sender, EventArgs e)
    {
      //CallingClass cc = new CallingClass();
      //cc.AddLines(tChart1);

      AddLines(tChart1);
    }

    public void AddLines(TChart tChart)
    {
      Double[] xValues = new Double[] { 1, 2, 3, 4, 5, 6, 7 };
      Double[] yValues = new Double[] { 2, 5, -3, 7, 0, 5, 2 };

      MyLine myLine = new MyLine();
      myLine.Add(xValues, yValues);
      tChart.Series.Add(myLine);
    }

This means that your MyLine type is found and serialization occurs successfully. Of course the issue here is putting a limit on the amount of recursive searching done by our FindType method - we could change it to search the assemblies referenced by those referenced, but then why not search for the assemblies referenced by those etc.?
Comment 2 christopher ireland 2014-04-11 06:23:29 EDT
In fact, I see that what I suggest is exactly what your AppOK project does.
Comment 3 christopher ireland 2014-04-15 09:16:35 EDT
This particular case has now been fixed, with recursion set to two levels, e.g.

		private Type FindType(string typeName)
		{
			Type result = InternalFindType(typeName, GetType().Assembly);

			if (result == null)
				result = InternalFindType(typeName, Assembly.GetExecutingAssembly());

			if (result == null)
				result = InternalFindType(typeName, Assembly.GetEntryAssembly());

			return result;
		}

    private Type InternalFindType(string typeName, Assembly a)
    {
      Type[] types = a.GetExportedTypes();
      foreach (Type t in types)
      {
        if (t.FullName == typeName)
          return t;
      }

      foreach (Type t in GetReferencedTypes(a))
      {
        if (t.FullName == typeName)
          return t;
      }

      return null;
    }

    //CDI only two levels of recursion here, as more quickly leads to StackOverflow due to possibility 
    //of circular references e.g. System.Configuration->System->System.Configuration
    private List<Type> GetReferencedTypes(Assembly a)
    {
      List<Type> types = new List<Type>();
      foreach (AssemblyName name in a.GetReferencedAssemblies())
      {
        Assembly aa = Assembly.Load(name);
        types.AddRange(aa.GetExportedTypes());

        foreach (AssemblyName aname in aa.GetReferencedAssemblies())
        {
          Assembly aaa = Assembly.Load(aname);
          types.AddRange(aaa.GetExportedTypes());
        }
      }
      return types;
    }
Comment 4 cqb 2014-04-17 01:53:44 EDT
Two levels can fix the issue in the demo project only, which can't fulfill the requirements in practice. In large projects, the calling levels may be greater than two.

I have a suggest: let users participate in the searching by override or event, because the users must know how to find the type they have defined. see below:
1. override. Make FindType(string typeName) protected method, thus the user can override the search
  protected virtual Type FindType(string typeName)
  {
    ...
  }

2. expose an event.
  public delegate Type UserFindTypeDelegate(string typeName)
  public event UserFindTypeDelegate UserFindType;
  
  private Type FindType(string typeName)
        {
	        if(UserFindTypeDelegate != null)
			{
			    Type result = UserFindTypeDelegate(typeName);
				if(result != null) return result;
			}
			
            Type result = InternalFindType(typeName, GetType().Assembly);

            if (result == null)
                result = InternalFindType(typeName,
Assembly.GetExecutingAssembly());

            if (result == null)
                result = InternalFindType(typeName,
Assembly.GetEntryAssembly());

            return result;
        }

thus, the problem is fixed and the risk of stack flow issue caused by search level is avoided, and no much code change is needed, what do you think of this idea?
Comment 5 christopher ireland 2014-04-17 06:02:22 EDT
I liked the event idea, unfortunately I don't think it's going to work. The problem is that during template import both the TChart and Chart object instances are overwritten by the call to BinaryFormatter.Deserialize(), meaning that the delegate reference of the event is lost.

Overriding the FindType method is not going to be feasible either, due to the structure of the TeeChart API, in the sense that doing so would require a great deal of 'boilerplate' code to get it working. 

I'm not sure quite what the answer is here. By far and away the easiest workaround is to move necessary references up the reference chain so they are visible to TeeChart deserialization, although I understand this might not always be neither possible nor desirable.