RectangularLineBase DrawOutline() method throw OutOfMemory exception

Apr 22, 2014 at 8:59 AM
Hi,
I saw in the NShape source code that the DrawOutline() method of the RectangularLineBase class may throw an OutOfMemory exception if we use a custom cap line.
Despite the workaround that consists of drawing a line a little bit longer, I still have this OutOfMemory expection when I use a custom cap line.

Did you find anything about this problem that could help me ?
Coordinator
Apr 22, 2014 at 11:04 AM
Edited Apr 22, 2014 at 11:10 AM
No, I'm sorry. I have no further information about this problem. We tried to find out the source of the problem but we failed because most of the System.Drawing assembly consists of wrappers around unmanaged code (the GDI+ Flat API).
The only thing I found in the documentation was the following remark in the documentation of the "Status Enumeration" (the return value of the flat API functions):
Remarks
If you construct a GDI+ object and then immediately call the GetLastStatus method of that object, you can determine whether the constructor succeeded or failed. In such cases, GetLastStatus might return OutOfMemory even though there was plenty of memory available to create the object. Several GDI+ constructors set the status to OutOfMemory when they fail regardless of the reason for failure.
Can you provide me with some code of what you are doing?
Apr 22, 2014 at 12:48 PM
Do you want me to send you a zip of a project or just to post samples on the forum ?
Coordinator
Apr 22, 2014 at 1:54 PM
If you have a zipped project where I can reproduce (debug) the problem, this option would be my favourite.
But if you do not have such a project or the context of the problem is simple, you can also post code here.
Apr 23, 2014 at 11:00 AM
Well, the context is quite simple:
I just created a Shape inheriting from RectangularLine and set its capstyles in the InitializeToDefault() method.
public class BasicConnectorShape : RectangularLine
{
        protected internal BasicConnectorShape(ShapeType shapeType, Template template)
            : base(shapeType, template)
        {
        }

        protected internal BasicConnectorShape(ShapeType shapeType, IStyleSet styleSet)
            : base(shapeType, styleSet)
        {
        }

        internal static Shape CreateInstance(ShapeType shapeType, Template template)
        {
            return new BasicConnectorShape(shapeType, template);
        }

        protected override void InitializeToDefault(IStyleSet styleSet)
        {
            base.InitializeToDefault(styleSet);
            this.EndCapStyleInternal = styleSet.CapStyles.Special1;
            this.StartCapStyleInternal = styleSet.CapStyles.Special1;
        }
}
Coordinator
Apr 23, 2014 at 11:47 AM
Edited Apr 23, 2014 at 11:48 AM
Sorry, I cannot reproduce the problem.
This is what I've done:
  • Pasted your code into the GeneralShapes library.
  • Added a register call for the shape type to the NShapeLibraryInitializer.Initialize method:
    registrar.RegisterShapeType(new ShapeType("BasicConnector", 
        namespaceName, namespaceName,
        delegate(ShapeType shapeType, Template t) { 
            return (Shape)new BasicConnectorShape(shapeType, t);
        },
        BasicConnectorShape.GetPropertyDefinitions));
  • Started the NShapeDesigner (through the Visual Studio Debugger) that loads the modified library on startup
  • Created a new BasicConnectorShape using the toolbox
    -> No OutOfMemoryException.
Can you reproduce the issue when performing the steps above?
Apr 23, 2014 at 12:20 PM
You are right, I didn't explain my problem clearly enough.
The exception happens when I try to connect 2 shapes together through the BasicConnectorShape shape.
Only creating this shape on the display is ok but linking 2 shapes throw the OutOfMemory exception.
Coordinator
Apr 23, 2014 at 2:29 PM
Edited Apr 23, 2014 at 2:30 PM
Seems to be hard to reproduce:
I restored the scenario above, started the NShapeDesigner and when creating the BasicConnectorShape, the OutOfMemoryException occured. Unfortunately, this was the only time this issue showed up. At the moment, it runs without errors - connected or not.

Some more questions:
  • Which shapes did you connect (shape types of the planar shapes)?
  • Did you create Point-to-Point or Point-to-Shape connections?
  • What are your compiler settings? x86 or x64? Debug or Release?
  • Do you reference the installed Release assemblies (from "Program Files (x86)"), the installed Debug assemblies or the source code projects?
Apr 23, 2014 at 2:45 PM
  • I'm connecting custom shapes inheriting from the RectangleBase class.
  • I create mainly Point-to-Point connections.
  • My compiler settings are x86 - Debug.
  • I reference the source code project librairies as I slightly modified the source code to fix some issues.
Coordinator
Apr 24, 2014 at 8:33 AM
Edited Apr 24, 2014 at 8:33 AM
After a lot of testing, it figured out how to reproduce the isssue:
  • Start NShapeDesigner (with the modified GeneralShape library)
  • Select the "BasicConnectorShape" from the toolbox and start 'drawing' the line
  • Move the mouse (with the unfinished line sticking to the mouse cursor) very close around the start point of the line until the error occurs
I think the source of the problem is the same as the one with the one-segment-line but I wasn't able to figure out the exact circumstances.
As soon as I know more, I will post my results here.

Unitl then, you can try the following workaround (which is not really correct, I know):
/// <summary>Overriden method. Check base class for documentation.</summary>
public override void DrawOutline(Graphics graphics, Pen pen) {
    if (graphics == null) throw new ArgumentNullException("graphics");
    if (pen == null) throw new ArgumentNullException("pen");
    // Workaround for a very strange problem:
    // (...)
    try {
        // Draw line
        graphics.DrawLines(pen, shapePoints);
    } catch (OutOfMemoryException) {
        // Draw line a little bit longer in order to avoid the issue described above
        const float delta = 0.001f;
        PointF p1 = shapePoints[0];
        PointF p2 = shapePoints[1];
        if (p1.X == p2.X) p2.Y += delta;
        else p2.X += delta;
        graphics.DrawLine(pen, p1, p2);
    }
    base.DrawOutline(graphics, pen);
}
Marked as answer by roux1max on 4/24/2014 at 2:26 AM
Apr 24, 2014 at 9:26 AM
Thank you, it's good to see that you're really active on the forum.
Coordinator
Apr 28, 2014 at 8:20 AM
Edited Apr 28, 2014 at 3:06 PM
Ok, here comes more information on this issue:
The source of the problem is a behaviour of GDI+ which tries to adjust the orientation of the line cap so it intersects with the line even if vertices of the line are inside the cap. In case that the intersection point between the line and the cap is not defined, an OutOfMemoryException is thrown.

After knowing the source of the problem, I was able to create a better workaround that also covers the issue reported above (and others) and integrate it in all base classes of linear shapes.
Unfortunately, the workaround consists of many lines of code distributed over a couple of files, so I cannot post it here.
The fix will be available in the next release NShape 2.2.0 which should be released soon.
Marked as answer by roux1max on 4/28/2014 at 1:54 AM