You built a DLL around a machine vision library, called it from a host program, and it ran — until you stopped the program. From then on every call fails, and only closing and reopening the whole development environment brings the DLL back. The same source built from an older toolchain runs clean, which makes the new library look guilty. It is not. This is a state-lifetime failure with one mechanism and one fix. This guide ranks the causes by probability with a test for each, shows the code shape that survives run, stop and run again, and lists the checks to run before you ship the DLL.
The mechanism: DLL state is per process, your session is not
The decision first: rebuild the DLL so the camera session is created at the start of every host program run and destroyed at its end, through exported functions the host calls. Never assume the DLL stays loaded, and never trust a pointer that outlived the run that created it.
Three Win32 facts drive this. Global variables in a DLL belong to the process that loaded it — each process gets its own copy of the DLL's data. The DLL's one-time initialization runs once per process load, not once per program run. And a development environment is one process: your program runs inside it, so an abort-style stop ends the program while the process, and the loaded DLL, live on. The initialization does not run again, and the camera handle cached on the first run still sits in its global, pointing at a session whose owning code no longer exists.
Reloading the environment fixes it because a new process loads a fresh copy of the DLL and the one-time initialization runs again. Your cleanup never ran either: an abort-style stop in the IDE, like an unconditional end statement in the host language, terminates the program without executing the exit path where you closed the camera.
The four causes, ranked, with a test for each
1. The session lives in a global, initialized once
Highest probability, and it matches the reported evidence: the same logic built from a different toolchain behaves differently, which points at where the session is created, not at the algorithms.
Test: export a status function that returns an initialization counter. Run the program, note the value, stop, run again. A counter stuck at its first value while the calls fail confirms the second run is using the first run's dead session.
2. Acquisition was still running when the program stopped
The camera kept streaming into buffers nobody reads, so the next open finds a busy device or returns stale frames.
Test: after a failed second run, leave the environment open and power-cycle the camera alone. If the next run works, the device was left in acquisition.
3. Cleanup in the detach path runs at the wrong time
Process-exit code can run your teardown after the camera runtime has already torn itself down, and some toolchains never run static cleanup on unload at all. The next load inherits a half-closed device.
Test: delete the detach cleanup, move it into an exported close function, and run the sequence again. If the failure mode changes, teardown order was the killer — the same trap inside one process is covered in camera SDK access violation on close: fix the teardown order.
4. The host frees the DLL between calls
A host that loads and unloads the DLL around every call defeats one-time initialization entirely; static and thread-local variables can be skipped or invalidated between loads.
Test: log each load and unload, or hold the DLL loaded for the whole program run. If the failures track the load churn, the calls must become stateless.
| Cause | Likelihood | Quick test | Fix |
|---|---|---|---|
| Session in a global, initialized once | Highest | Init counter stuck at 1 across a stop and restart | Exported open and close per run |
| Acquisition left running | High | Power-cycling only the camera clears it | Stop and drain in the close path |
| Detach-time cleanup, wrong order | Medium | Moving cleanup into an exported close changes the failure | No camera SDK calls in the DLL entry point |
| DLL unloaded between calls | Low | Load and unload log shows churn | Stateless calls, or hold the DLL loaded |
The fix: one session per run, owned by the host
- Export three functions: open, grab (or inspect), and close. The host calls open at program start and close on every exit path — the normal stop, the error branches, the window-closing path.
- Hold all state in one structure the host owns as an opaque handle. No file-scope pointers to camera objects.
- If a global is unavoidable, guard every exported function with a session-valid check and reopen when the state is stale. Reopening is the correct behavior; refusing is not.
- Close in a fixed order: stop acquisition, drain the frame queue, release the buffers, close the device, free the structure.
- Keep the DLL entry point nearly empty. It runs under the loader lock, so opening a camera or loading another library there can deadlock or fail.
- Make open idempotent: if a session exists, close it first, then open fresh.
The function names below are placeholders for your SDK's own calls:
typedef struct {
void *camera; /* SDK camera handle, NULL when closed */
int acquiring; /* 1 while the stream runs */
} Session; /* one per host program run */
static Session *g_run = NULL; /* the only global, re-checked every call */
int VisionOpen(void)
{
if (g_run != NULL) VisionClose(); /* idempotent open */
g_run = calloc(1, sizeof(Session));
if (g_run == NULL) return -1;
g_run->camera = open_first_camera(); /* your SDK's open call */
if (g_run->camera == NULL) {
free(g_run);
g_run = NULL;
return -1;
}
return 0;
}
The proof is a host-side loop that stops the program the way an operator does:
for (cycle = 1; cycle <= 10; cycle++) {
VisionOpen();
frame = VisionGrab(); /* must succeed every cycle */
VisionClose(); /* then stop the program */
}
Each cycle must return a frame whose timestamp is later than the previous cycle's last frame.
Worked example: stop mid-stream on a 12 MP USB3 camera
Inputs: a host program with a stop button, a DLL that cached its session under the old design, and the camera — the LS-CMR-0166, which prints a 4000 × 3000 resolution at 30 fps over USB3. The queue depth is three frames, a value you set when you configure the acquisition.
Rule in words: the close path must outlast every frame still in flight — one frame period per queued frame, doubled for margin.
The arithmetic:
- Frame period = 1 / 30 fps = 0.033 s ≈ 33 ms.
- Payload per frame = 4000 × 3000 pixels × 1 byte = 12,000,000 bytes ≈ 12 MB in 8-bit mono.
- Stream rate while you are closing = 12 MB × 30 fps = 360 MB/s.
- Drain budget = 3 frames × 33 ms ≈ 100 ms; set the close timeout to 200 ms, a 2× margin.
Decision: run the ten-cycle loop with stops mid-stream, not between frames. Ten clean cycles — ship it. A timeout at 200 ms means the queue was not drained and the close order is wrong, back to step 4 of the fix. An instant failure means a stale handle is still in the code, cause 1. The companion failure — buffers that stay busy because acquisition never stopped — is covered in stop camera acquisition with busy buffers.
The host side: route the stop button through the close call
The DLL cannot save a host that never calls close. Three host rules:
- Wire the real stop paths — the button, the window close, the error branches — to the exported close before the program ends.
- Replace unconditional end statements with a normal exit that runs the close call.
- Match the loading pattern to the DLL design. If the host loads and frees the DLL per call, every call must be stateless; if it holds the DLL for the run, the open and close pair owns the session. Graphical environments have their own rules — how a LabVIEW DLL's assembly loading depends on the host process covers that side, and calling a camera SDK DLL from LabVIEW covers callbacks and buffers at runtime.
Hardware that survives ten restarts an hour
Once the DLL reopens cleanly on every run, the parts around it must re-arm too. Three that fit that loop: a GigE camera such as the MV-CU020-90GC, a dual-port PoE NIC such as the MV-GE2002P in the host, and a vision controller such as the MV-VC3301H-128G66 to run the whole station.
- Sensor format
- 1/1.7"
- Lens mount
- C-mount
- Interface
- GigE
- Ports
- 2 ports
- LAN ports
- 5+ LAN ports
- USB 3 ports
- 3-4 USB 3 ports
For a GigE camera, the NIC re-establishes the link after each stop: the MV-GE2002P is a dual-port PoE card, the LS-FGB-0004 a 4-port PCIe x4 PoE card.
| Specification | MV-GE2002P | LS-FGB-0004 |
|---|---|---|
| Price | $249.00 | $359.00 |
Values as printed in each product's data sheet; open a part for its drawing and full specifications.
Pick the camera from the area scan cameras finder, the NIC from frame grabbers, and the host from vision controllers.
When the DLL is clean and it still fails
If the DLL is stateless and the second run still fails, the stale state is on the transport or the device:
- The camera answers ping but never appears: ping works, zero cameras: the GigE transport layer is not loaded.
- The frame grabber will not come back after the restart: frame grabber initialization failure: fix firmware and runtime.
- Discovery succeeds but open fails: GenTL camera open failure after successful discovery.
- On Linux, the dead run still holds the device: release a camera after a crash on Linux with the GigE restart fix.
Before adding a second camera, read multi-camera acquisition session architecture — the same per-run session rule, applied per camera.
Checks before you ship
- Ten run-stop-run cycles in one environment session, each returning a frame with a later timestamp. This is the regression test for the whole fix.
- Stop mid-stream on at least two cycles. The worst case is the one that ships.
- Unplug the camera mid-run: open must report the failure rather than hang, and the next cycle must recover.
- Close order confirmed in code review: stop acquisition, drain queue, release buffers, close device.
- The DLL entry point contains no camera SDK calls.
- No exported function dereferences a pointer from a previous run without the validity check.
Building it into a system
If the DLL fix lands alongside new hardware, start at the camera step of the configurator and walk camera, lens, light and controller in order.
The configurator matches a camera, lens, light, light controller and cables to each other, step by step, and lists only parts that fit what you already picked.
For an integration that stays stuck after these tests, contact the applications team — free expert support comes with every order.
Questions
Why does reloading the development environment make the DLL work again?
Because it starts a new process. A DLL's data belongs to the process that loaded it, and its one-time initialization runs once per process load. The environment keeps the DLL loaded while your program runs inside it, so stopping the program leaves the old, dead session in place; closing the whole environment unloads the DLL, and the next start loads a fresh copy whose initialization runs again. That is the fingerprint of cause 1, and it disappears once open and close are exported per run.
Should the camera be opened when the DLL loads?
No. The DLL entry point runs under the loader lock when the process maps the DLL, and opening a camera there can pull in further libraries, touch threads or deadlock. Initialize in an exported open function the host calls at program start, and tear down in an exported close on every exit path. The entry point stays limited to what the runtime itself needs.
Does the vision library version matter for this failure?
Not for this symptom. A build that works and a build that fails from the same logic differ in where the session is created, not in the algorithms. Run the initialization-counter test from cause 1: a counter stuck at 1 while the calls fail convicts the cached session. Upgrade the library only after that test passes clean and the failure persists — then the transport checks above are the next stop.
How do I test the fix without touching the camera power?
Script the ten-cycle loop from the fix section: open, grab once, close, stop the program exactly as the operator does, start again. Assert two things per cycle — the grab returns a frame, and that frame's timestamp is later than the previous cycle's last frame. Stop mid-stream on at least two cycles. If all ten cycles pass inside one environment session, the DLL no longer carries state across runs.
The DLL is stateless and the second run still fails. What next?
The stale state has moved to the transport or the device. Work outward: does the camera answer ping, does the frame grabber initialize after the restart, does discovery succeed while open fails, and is the device still locked by the dead run on a Linux host. Each of those has its own signature and fix; the last section of this guide links the article for each one.


