DarthAffe / ScreenCapture.NET

Fast and easy to use screen-capturing
GNU Lesser General Public License v2.1
68 stars 12 forks source link

System.ObjectDisposedException: Cannot access a disposed object #33

Closed sgf closed 4 months ago

sgf commented 4 months ago

System.ObjectDisposedException: Cannot access a disposed object. Object name: 'ScreenCapture.NET.DX9ScreenCapture'. at ScreenCapture.NET.AbstractScreenCapture`1.CaptureScreen() at WinFormsApp1.Form1.timer1_Tick(Object sender, EventArgs e) in E:\MySrc\tmvur\WinFormsApp1\WinFormsApp1\Form1.cs:line 73

timer1.Intval=100;//100ms

There will be no errors in the first few times, but after about 8 ticks, errors will appear.

    private IScreenCapture screenCapture;
    private ICaptureZone captureZone;
    private ConcurrentQueue<Bitmap> bmpBuff = new();

    private void Form1_Load(object sender, EventArgs e)
    {
        IScreenCaptureService screenCaptureService = new DX9ScreenCaptureService();
        IEnumerable<GraphicsCard> graphicsCards = screenCaptureService.GetGraphicsCards();
        IEnumerable<Display> displays = screenCaptureService.GetDisplays(graphicsCards.First());
        screenCapture = screenCaptureService.GetScreenCapture(displays.First());
        captureZone = screenCapture.RegisterCaptureZone(0, 0, screenCapture.Display.Width, screenCapture.Display.Height);
    }

    private void timer1_Tick(object sender, EventArgs e)
    {
        try
        {
            if (!screenCapture.CaptureScreen()) //Error Here System.ObjectDisposedException: Cannot access a disposed object
                return;
            using (captureZone.Lock())
            {
                var image = captureZone.Image;
                Debug.WriteLine($"{image.ColorFormat.ToString()}");

                var bmp = image.ToBitmap();
                bmpBuff.Enqueue(bmp);
                this.Invalidate();
                return;
            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.ToString());
        }

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);
        if (!bmpBuff.TryDequeue(out var bmp)) return;

        var g = e.Graphics;
        g.DrawImageUnscaled(bmp, new Point(500, 300));
    }
DarthAffe commented 4 months ago

Your screenCaptureService is only a local and will be collected by the garbage collector at some point after the 'Form1_Load' has finished, which causes it to be disposed. You have to keep it alive (for example by storing it the same way as the screenCapture).

sgf commented 4 months ago

I thought that generally speaking, unmanaged objects should need to be released manually. Moreover, GC should scan the reference and it should not be released. However, thanks for supporting the problem. I'll go ahead and give it a try.

DarthAffe commented 4 months ago

Moreover, GC should scan the reference and it should not be released.

yes, and that is the problem in your case - you don't keep a reference to it. the only one you have is the local that gets released once the method exits.

sgf commented 4 months ago

yes, and that is the problem in your case - you don't keep a reference to it. the only one you have is the local that gets released once the method exits.

I originally thought the code below would form an indirect reference.

        IScreenCaptureService screenCaptureService = new DX9ScreenCaptureService();
        IEnumerable<GraphicsCard> graphicsCards = screenCaptureService.GetGraphicsCards();
        IEnumerable<Display> displays = screenCaptureService.GetDisplays(graphicsCards.First());
        screenCapture = screenCaptureService.GetScreenCapture(displays.First());

In fact, there should be indirect references internally, otherwise no error will be reported, which means that the screenshot process depends on screenCaptureService. However, this reference relationship was not scanned by the GC, so screenCaptureService was released.

This is indeed the problem, thank you for your guidance.

DarthAffe commented 4 months ago

In fact, there should be indirect references internally, otherwise no error will be reported, which means that the screenshot process depends on screenCaptureService. However, this reference relationship was not scanned by the GC, so screenCaptureService was released.

no, that's not true here. It's quite the opposite. The ScreenCaptureService manages all the screen captures and therefore keeps references to them (not the other way around). When the ScreenCaptureService is disposed (which also happens when it's collected) it actively cleans up all the ScreenCaptures it manages (since it can't be sure that you're still able to do so once it's gone, you might not have a reference to the ScreenCapture itself).

sgf commented 4 months ago

I mean backreferences are also references. Logically speaking, GC should not distinguish between forward and reverse.

Of course, I'm not very good at writing low-coupling code. Therefore, I probably encounter this situation very rarely.😂