AccessViolationException с графикой с двойной буферизацией

как уже видно из заголовка, я сталкиваюсь с AccessViolationException, когда пытаюсь нарисовать элемент управления в .NET, используя объект BufferedGraphics. Это происходит через некоторое время, раньше или позже. Оценивая адрес объекта, я заметил, что он продолжал увеличиваться по мере выполнения программы. Я уже предпринял несколько мер по сохранению памяти, которые, казалось, немного помогли, но в конечном итоге не решили проблему. Кажется, проблема возникает независимо от того, избавляюсь ли я от графического объекта до окончания функции.

    private void checkMemory()
    {
        long mem = GC.GetTotalMemory(false);
        //Console.WriteLine("Allocated memory: " + mem.ToString());
        if (mem > criticalMemorySize) 
        {
            Console.WriteLine("GC started");
            GC.Collect(1, GCCollectionMode.Forced, true);
        }
    }

    BufferedGraphics graphics;

    protected override void OnPaint(PaintEventArgs e)
    {
        lock (e.Graphics)
        {
            base.OnPaint(e);
            checkMemory();
            if (e.ClipRectangle.Width * e.ClipRectangle.Height == 0)
                return;
            if (graphics == null || graphics.Graphics == null || graphics.Graphics.ClipBounds != e.ClipRectangle)
              graphics = _bufferedGraphicsContext.Allocate(e.Graphics, e.ClipRectangle);

            PaintBackground(graphics.Graphics);
            PaintHeader(graphics.Graphics);
            PaintBody(graphics.Graphics);
            DrawGrid(graphics.Graphics);
            //...

            graphics.Render(e.Graphics);
            graphics.Graphics.Dispose();
            graphics.Dispose();
        }
    }

Исключение возникает в этой функции при вызове g.DrawString:

    private void PaintBody(Graphics g)
    {
        Font seriffont = Design.CreateSerifFont(fontSize);
        Point mousePos = PointToClient(MousePosition);
        int hfeed = headHeight + headLineWidth - 2; //Dunno 
        for (int y = 0; y < LineCount; y++)
        {
            int vfeed = 0;
            for (int x = 0; x < ColumnCount; x++)
            {
                String s = "";
                Rectangle area = new Rectangle(vfeed, hfeed, colwidth, cellHeight);
                switch (_tableData[y][x])
                {
                    case ExBool.DontCare:
                        s = "*";
                        break;
                    case ExBool.False:
                        s = "0";
                        break;
                    case ExBool.True:
                        s = "1";
                        break;
                    default:
                        s = " ";
                        break;
                }
                Color backColor = cellBackColor;
                if (_activeHeaderColumn == x)
                {
                    backColor = cellActiveColColor;
                }
                if (area.Contains(mousePos))
                {
                    backColor = cellHoverColor;
                }
                if (area.Contains(mousePos) &&
                    ((MouseButtons & MouseButtons.Left) == MouseButtons.Left))
                {
                    backColor = cellActiveColor;
                }
                g.FillRectangle(new SolidBrush(backColor), area);
                g.DrawString(s, seriffont, Brushes.Black, area,
                    new StringFormat() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center });
                if (x != InputValues.Count() - 1)
                {
                    vfeed += lineWidth + colwidth;
                }
                else
                {
                    vfeed += typeSepLineWidth + colwidth;
                }
            }
            hfeed += cellHeight + lineWidth;
        }
    }

И это исключение, которое я получаю:

 System.AccessViolationException was unhandled
  HResult = -2147467261
  Message=Es wurde versucht, im geschützten Speicher zu lesen oder zu schreiben.Dies ist häufig ein Hinweis darauf, dass anderer Speicher beschädigt ist.
  Source= System.Drawing
  StackTrace:
       bei System.Drawing.SafeNativeMethods.Gdip.GdipDrawString(HandleRef graphics, String textString, Int32 length, HandleRef font, GPRECTF& layoutRect, HandleRef stringFormat, HandleRef brush)
       bei System.Drawing.Graphics.DrawString(String s, Font font, Brush brush, RectangleF layoutRectangle, StringFormat format)
       bei KarnaughVeitch.Controls.TruthTableControl.PaintBody(Graphics g)
       bei KarnaughVeitch.Controls.TruthTableControl.OnPaint(PaintEventArgs e)
       bei KarnaughVeitch.Controls.TruthTableControl.TruthTableControl_MouseMove(Object sender, MouseEventArgs e)
       bei System.Windows.Forms.Control.OnMouseMove(MouseEventArgs e)
       bei System.Windows.Forms.Control.WmMouseMove(Message& m)
       bei System.Windows.Forms.Control.WndProc(Message& m)
       bei System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
       bei System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
       bei System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
       bei System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
       bei System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
       bei System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
       bei System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
       bei System.Windows.Forms.Application.Run(Form mainForm)
       bei KarnaughVeitch.Program.Main()
       bei System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
       bei System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       bei Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       bei System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       bei System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       bei System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       bei System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       bei System.Threading.ThreadHelper.ThreadStart()
  InnerException: 

Кто-нибудь знает, как это решить? Заранее спасибо!

Редактировать:

Переменная Context создается глобально следующим образом:

    BufferedGraphicsContext _bufferedGraphicsContext = new BufferedGraphicsContext()

Это было рекомендовано в документации для случая, когда вы будете часто использовать BufferedGraphics. Функции являются частью пользовательского элемента управления, который позволяет пользователю вводить входные и выходные данные бинарных функций. Поскольку я не знал о подобном элементе управления, я просто написал его сам. Вышеупомянутая функция вызывается каждый раз, когда элемент управления необходимо перекрасить, например. если пользователь двигает мышкой, нажимает на что-то и так далее, для всех этих действий требуется визуальная обратная связь. Как правило, он работает как хотелось бы, за исключением исключения, которое возникает после использования элемента управления в течение некоторого времени...

Редактировать 2:

Поскольку исключение возникает почти исключительно для DrawString функций, возможно ли, что NullReference (согласно HResult, если я не ошибаюсь) не из-за самого процесса рисования, а из-за одного из аргументов или чего-то еще?


person XChalo    schedule 09.06.2016    source источник
comment
Обратите внимание: объект Graphics не содержит графику; это инструмент, позволяющий рисовать на связанном растровом изображении, включая поверхность элемента управления. Не кэшируйте его «буфером». Для настоящей двойной буферизации вам необходимо «буферизировать» растровое изображение. Или используйте элемент управления DoubleBuffered, например PictureBox. - Не уверен, что делает _bufferedGraphicsContext.Allocate, но если это в конечном итоге приведет к удалению e.Graphics, вы получите исключение.   -  person TaW    schedule 09.06.2016
comment
Мне кажется, что вы думаете о C++ и пытаетесь написать C#. Вам не нужно управлять графическим объектом. Вам даже не нужно самостоятельно обрабатывать двойную буферизацию (хотя вы можете оправдать это в особых случаях). А то, что форсировать сборку мусора — плохая идея. Если вы думаете, что вам нужно это сделать, значит, у вас серьезный недостаток дизайна.   -  person DonBoitnott    schedule 09.06.2016
comment
Спасибо за внимание.   -  person XChalo    schedule 10.06.2016
comment
@TAW Функция Allocate, если я не ошибаюсь, позволяет мне создать объект BufferedGraphics, к которому я могу применить функции рисования и визуализировать его позже в реальной графике, когда я закончу. Мне нужно это, чтобы предотвратить мерцание, которое работает довольно хорошо, пока не произойдет исключение. Я полагаю, что он использует растровое изображение внутри. Если я правильно помню документацию, я использую класс BufferedGraphics так, как это должно быть, но все же я не могу объяснить себе, почему существует AccessViolation...   -  person XChalo    schedule 10.06.2016
comment
@DonBoitnott Я удалил бит GC, так как он не сильно изменился ... Я полагаю, вы правы, что я просто неправильно себе это представляю, но я не смог найти другого объяснения. Ручная двойная буферизация кажется мне необходимой, потому что автоматическая не предотвращает массовое мерцание, но таким образом она выглядит достаточно красиво и плавно некоторое время, прежде чем вылетит. Это происходит быстрее при большем контроле и/или большем количестве итераций.   -  person XChalo    schedule 10.06.2016
comment
Ручная двойная буферизация мне кажется необходимой, потому что автоматическая не предотвращает массовое мерцание, а так выглядит вполне красиво и плавно Хм. first: Спасибо, что спустя какое-то время снова указали мне на этот класс.. Не могли бы вы немного рассказать о том, что вы делаете? А как вы использовали обычную двойную буферизацию? - Также: можете ли вы показать, как вы создаете переменную bufferedGraphicsContext?   -  person TaW    schedule 10.06.2016
comment
Конечно, я обновлю свой вопрос!   -  person XChalo    schedule 10.06.2016


Ответы (1)


Это может быть не совсем ваш случай (и я думаю, что для вас это уже не актуально спустя два с половиной года), но все же.

В вашем коде есть следующая строка:

Font seriffont = Design.CreateSerifFont(fontSize);

Похоже, это ваш собственный код, так как я не нашел упоминаний о нем в гугле. Итак, вот реализация этого метода, которая может привести к наблюдаемой вами ошибке:

static class Design {
    static Font CreateSerifFont() {
        var fonts = new PrivateFontCollection();
        fonts.AddFontFile(@"c:\some_font.ttf");
        return new Font(fonts.Families[0], 12);        
    }
}

Я бы сказал, что это плохой дизайн PrivateFontCollection - его можно удалить, пока Font все еще используются, и это вызовет AccessViolationException. Приведенный выше код можно исправить, переместив fonts из локальной области видимости в поле класса, например:

static class Design {
    private static readonly PrivateFontCollection Fonts = new PrivateFontCollection();
    public  static readonly Font                  SerifFont;

    static Design() {
        Fonts.AddFontFile(@"c:\some_font.ttf");
        SerifFont = new Font(Fonts.Families[0], 12);
    }
}
person Grief    schedule 06.01.2019