System designTroubleshooting

Camera SDK access violation on close: fix teardown order

Why a machine-vision SDK crashes with 0xC0000005 only on camera close, the teardown order that fixes the access violation, and a soak test that proves it.

LumaSpec Vision10 min read Share on LinkedIn

On this page 10 sections
  1. 01Why does the crash only appear when the camera closes?
  2. 02What actually owns the image buffer?
  3. 03Why doesn't the try block catch the exception?
  4. 04What is the correct teardown order for a camera SDK?
  5. 05Which teardown mistakes cause the crash, in what order?
  6. 06How do you find the exact call that faults?
  7. 07What test proves the fix before you ship?
  8. 08Does the camera or controller hardware change the fix?
  9. 09When is the shutdown crash not your teardown order?
  10. 10Questions

A camera that grabs correctly all shift can still take the process down at shutdown. You call the SDK's stop, close and terminate calls, and Windows kills the process with 0xC0000005 — an access violation — inside the SDK's own runtime library. None of your catch blocks runs. The grab code was right; the teardown was not. This page is for C++ developers integrating GigE and USB3 cameras on a vision controller. You will get the object-ownership rule behind the crash, a six-step teardown order, the four ordering mistakes ranked in the order to check them, and a 1,000-cycle soak test that proves the fix before you ship.

Why does the crash only appear when the camera closes?

The grab loop never faults. Every buffer it touches stays alive while the stream runs. The crash appears at shutdown because 0xC0000005 — a Windows access violation — is raised inside the SDK's runtime library when teardown order lets code touch memory that has already been freed.

While the stream runs, the transport layer keeps every buffer it handed out valid, so a lifetime mistake sleeps. Teardown wakes it. Two objects typically outlive their owner at shutdown: a grab result, or an image wrapped around its buffer, still held after the device closes; and the camera object itself, destroyed after the library terminate call has already run. Both faults fire after your last log line. That is why the log stops mid-list and the debugger, not your catch block, is the only witness.

A hang at exit is the milder version of the same disease. The camera SDK hang-on-exit guide works the stream side of the same discipline.

What actually owns the image buffer?

The SDK's transport layer owns the pixel memory and your grab-result smart pointer only borrows it. A 2.5 MB mono 8-bit frame from a 2.5 MP sensor sits in a driver-owned buffer that is recycled when the result is released, so anything you keep must be a copy.

The grab-result smart pointer — declared CGrabResultPtr ptrGrabResult; in the failing pattern — is a reference counter, not an owner. Release it and the transport layer recycles the pixels. Keep it and it keeps a header pointing into recycled memory. cv::Mat makes this dangerous when you wrap the SDK buffer without copying: the Mat stores the raw pointer, so a Mat built directly on the converted buffer dangles the moment the result goes away.

SDK buffer = pixels × 1 B = 2,500,000 × 1 B = 2.5 MB (8-bit mono, 2.5 MP) cv::Mat copy = pixels × 3 B = 2,500,000 × 3 B = 7.5 MB (BGR8 packed)

The fix costs one call: build the Mat on the converted buffer and call .clone(), which copies 7.5 MB into your own allocation, so nothing you keep points into the SDK. The failing pattern already cloned — that is why grabbing survived — and the crash came from the lifetime mistakes below. The copy-out rule holds outside C++ too: copying Mat data out of the SDK buffer shows the saving side, and frame memory lifetime in managed code applies the identical rule to C# wrappers.

Why doesn't the try block catch the exception?

Your catch blocks stay silent because an access violation is a hardware exception raised by the processor, not a C++ exception. Under the compiler's default exception model it never unwinds into catch, and it usually fires inside a destructor after the last line your log printed.

The operating system delivers it as a structured exception. Compilers route that into catch(...) only when asynchronous exception handling is switched on at build time. Under the default model the fault terminates the process. Do not add a catch(...) as the fix: by the time it runs, the evidence you need — the call stack — is on screen in the debugger, not in your log.

The signature is not unique to vision code: PLC programming software produces the same fatal 0xC0000005 access violation. Read the code as a host-side memory fault, never as a camera fault.

What is the correct teardown order for a camera SDK?

Destroy what borrowed before what owns, on the one thread that created the session: stop the capture loop, stop grabbing, release every grab result, close the device, let the camera object be destroyed, and only then terminate the library — six steps, always in that order.

  1. Join the capture thread. The stop button sets a flag; the thread that owns the camera makes every SDK call. Two threads inside the SDK at once is mistake 3 below.
  2. Stop grabbing. Repeated starts without an intervening stop raise a catchable busy error, not a crash; the single-frame busy fix shows why the stop belongs inside every cycle.
  3. Release every grab result. Let the smart pointers go out of scope; keep no copies in containers that outlive the device.
  4. Close the device. No result is alive at this point, so nothing references transport-layer memory.
  5. Destroy the camera object. Scope it inside an inner block so its destructor runs here, not after the next step.
  6. Terminate the library — on the same thread that initialized it, from normal program flow, never from a static destructor.
// one owner thread; library initialized once per process
bool getCurrentFrame(cv::Mat& image) {
    {
        // declare the camera object and the grab-result smart pointer
        // (a local such as CGrabResultPtr ptrGrabResult;) inside this scope
        // open, configure gain/exposure/trigger, grab one frame
        // image = cv::Mat(h, w, CV_8UC3, convertedBuffer).clone(); // own copy
        // close the device before the scope ends
    } // camera object and grab result are destroyed here
    // terminate the library last, on this same thread
    return true;
}

In this sketch image is the caller's cv::Mat&, and h, w and convertedBuffer are placeholder names for the frame height, width and the SDK's converted 8-bit BGR buffer.

Which teardown mistakes cause the crash, in what order?

Check four mistakes in this order: the camera object outliving the library terminate, a grab result or wrapped image alive at close, two threads inside the SDK at once, and termination from a static destructor. The first two are the most common and take a minute each to test.

Check Mistake What you see Test and fix
1 Camera object alive when the library terminates Crash at scope exit, after your logs, inside the SDK runtime Put the camera object in an inner scope that closes before terminate
2 Grab result or Mat-wrapped buffer alive at close Crash on the close or release line Log between calls; release results and clone pixels before close
3 Two threads inside the SDK (UI stop button vs capture thread) Intermittent crash, worse under load One owner thread; join it before any teardown call
4 Terminate from a static or global destructor Crash after main returns; no frame of yours on the stack Move terminate into normal flow after all joins

Mistake 1 matches the failing pattern best: the capture function calls the terminate call while its local camera object is still in scope, so the destructor runs later against dead library state. One inner scope removes it, and the crash moves to nothing.

How do you find the exact call that faults?

Run the program under the debugger and let the first-chance access violation break. The call stack names the faulting SDK module and shows whether your teardown function, a destructor or the run-time's static-unload code sits on top — one stack decides which of the four fixes you need.

Put a log line between every teardown call — stop, release, close, terminate — and the last printed line brackets the fault to a single call. The faulting address itself is noise: address-space layout randomization moves it between runs. A crash reading location 0x000001A1E96D9CC8 tells you nothing beyond freed or unmapped memory. The faulting module name and the stack are the evidence.

Keep grab-timeout values honest while you hunt. A camera configured for a 300 ms exposure on a hardware trigger sits out the full 5,000 ms grab timeout when no trigger arrives. That produces a catchable timeout and a log entry, never the access violation, so do not confuse a five-second wait with the crash.

What test proves the fix before you ship?

Loop the full cycle — open, configure, grab one frame, stop grabbing, close, terminate — 1,000 times in one process. At roughly 0.5 s per cycle the soak runs about 8 min 20 s, and the fixed teardown passes with exit code 0 and zero first-chance access violations.

cycle ≈ 0.2 s open + configure + 0.3 s exposure + teardown ≈ 0.5 s soak = 1,000 cycles × 0.5 s = 500 s ≈ 8 min 20 s

The 0.3 s exposure is what the failing pattern configured; a longer exposure stretches the cycle time with it, and 200 cycles is enough to expose a lifetime fault. Pass criteria: process exit code 0, zero first-chance access violations in the debugger output, the camera enumerated on every cycle, three consecutive clean runs. If 1,000 cycles pass clean but the production cell still crashes, the remaining suspect is concurrency: re-run the soak while a second thread hammers the stop flag mid-grab, and fix mistake 3 before touching anything else.

Does the camera or controller hardware change the fix?

No: the teardown rules come from the SDK's object lifetimes, not from the sensor. A GigE camera such as the MV-CU020-90GC and a USB3 model follow the identical stop, close, destroy, terminate sequence on any host.

The pattern above — a GenICam-based C++ SDK, one capture thread, hardware-triggered grabs — is the default build around area scan cameras hosted on a dedicated vision controller. When you size or rebuild the host while you fix the code, count ports first: one camera per NIC or USB3 port, plus built-in light channels that save a separate lighting interface.

For a one-frame-per-trigger loop, a compact GigE camera keeps the transport simple:

Area scan camerasHikrobot MV-CU020-90GC 2.5 MP Area Scan Camera, GigE, GMAX4002, ColorMV-CU020-90GC
Sensor format
1/1.7"
Lens mount
C-mount
Interface
GigE
Resolution
Up to 3 MP
$309.00View partBuild a system with it

On the host side, this controller class carries 5+ LAN ports and 4 built-in light channels — enough for a multi-camera cell plus strobes from one box:

Vision controllersHikrobot MV-VC3301H-128G66 I3-8100, 8GB+128G SSD, No PoE, PCIe expansion supported, Incl. VM6200Pro licenseMV-VC3301H-128G66
LAN ports
5+ LAN ports
USB 3 ports
3-4 USB 3 ports
Light outputs
4 built-in channels
Memory
8 GB
$2,419.00View partBuild a system with it

The camera finder filters by interface, shutter and resolution, and the camera configurator walks the whole stack. More integration-side fixes sit on the systems topic page.

When is the shutdown crash not your teardown order?

If the soak passes 1,000 cycles clean and production still crashes, move suspicion to the SDK build itself: a mismatched driver family, an old runtime DLL shipped in your own folder or a stale USB host-controller driver can all fault at teardown; update from the vendor's distribution pages first.

Three tells move suspicion off your code: the crash survives the fixed order and a clean soak; the faulting module is a driver or host-controller DLL, not the SDK runtime; or the same program crashes on one PC and runs clean on another with identical code. Then update the SDK runtime and transport driver from the vendor's own distribution channels, and make sure the DLL your program loads is the one the installer put down — a stale copy in the executable folder shadows the installed one. A driver family mismatch produces the same confusion one layer up, and stop acquisition before you close a USB camera covers the stop-before-close rule that keeps reopen cycles clean.

The decision, per case: crash at scope exit after your logs — inner scope around the camera object. Crash on close or release — clone the pixels and kill the results first. Intermittent under load — one owner thread, joined before teardown. Crash after main returns — terminate in normal flow. Clean soak but field crashes — environment: runtime, driver, host controller. If you are specifying hardware for the rebuild, the camera configurator sizes camera, lens and lighting together, and expert support comes with every order through the contact page.

Questions

Does calling stop twice, or stopping when not grabbing, cause an access violation?

No. Stopping an idle stream either does nothing or raises a catchable SDK error, which your log records. The fatal 0xC0000005 faults come from object lifetime — a result, an image or the camera object referencing memory that close or terminate has already freed — not from redundant calls. Fix lifetime first; redundant stop calls are harmless in comparison.

Why does the crash address change every time the program runs?

Windows loads modules at randomized addresses (ASLR) and heap addresses shift between runs, so the location an access violation reports is meaningless. Read the two stable things instead: the module that raised the fault and the call stack at the moment of the fault. Those name the object whose lifetime is wrong; the address never will.

Can the SDK be terminated inside a class destructor?

Only if you can prove three things when that destructor runs: every grab result is already released, every camera object is already destroyed, and no other thread is inside the SDK. A global object's destructor cannot prove any of them, because static destruction order across files is unspecified. Call terminate from normal shutdown flow after joining your threads.

Could the camera, cable or frame grabber be causing the shutdown crash?

Almost never. A failing link shows up as catchable grab timeouts and error descriptions during acquisition, not as an access violation in the SDK runtime at teardown. Host-side memory lifetime produces exactly one signature: 0xC0000005 in the SDK or driver module after close or terminate. Test the teardown order before you swap any hardware.

Is this teardown order still needed when the camera is opened and closed many times per shift?

Yes — and the order matters more, because each cycle multiplies a latent lifetime fault. Run the whole sequence every cycle: stop grabbing, release results, close the device, destroy the camera object; terminate the library only when the process is done grabbing for good. A 1,000-cycle soak reproduces a full shift of open-close cycling in about eight minutes.