System designTroubleshooting

DLL not loading on a vision controller: match the target OS

A DLL that runs on your desktop can never load on a controller with another OS or CPU. The checks that find the mismatch and the rebuild that fixes it.

LumaSpec Vision12 min read Share on LinkedIn

On this page 10 sections
  1. 01What the error is actually telling you
  2. 02Why it loads on the PC and not on the controller
  3. 03The likely causes, in order
  4. 04Read the header: the one-minute test
  5. 05Worked example: a 64-bit Windows DLL failing on an ARM Linux controller
  6. 06The fix: rebuild for the target and carry the whole chain
  7. 07When you cannot rebuild: match the controller to the code
  8. 08Prevention: make the target part of the build
  9. 09The decision, case by case
  10. 10Questions

Your inspection code calls a shared library. On the office PC it works: the function returns and the result comes back. Deploy the same project to the vision controller on the machine and the call dies with a failed-to-load error before your first line of algorithm code runs. The file is on the target, the path looks right, and the same binary behaved on the desk ten minutes earlier.

This guide is for anyone moving C or C++ libraries, or a camera SDK built on them, from a desktop build to an embedded controller. It gives you the loader's actual rules, the causes in the order they usually stack up, a header test that settles the question in a minute, and the fix for each case — including the cases where no copy of the file can ever load.

What the error is actually telling you

The message names your library, but it comes from the loader on the target, not from your code. It fires before any of your functions run: the loader tried to open the file, walk its import table and map it into the process, and one step failed. The named library is what you asked for; the broken step can be the file format, the machine type, a dependent file or the search path.

Re-copying the same file rarely changes anything — same file, same loader, same failure. The useful move is to let the binary describe itself, which it does in its first bytes.

Desktop cousins of this failure have known signatures: a Python wrapper that reports a DLL load failure usually means the interpreter's bitness, and a GenTL producer that never appears in the list usually means a Windows search path. The controller version is the same machinery with a longer walk.

Why it loads on the PC and not on the controller

A shared library is compiled machine code, and machine code only means something to one pairing of operating system and processor. Three conditions must all hold between the file and the process that loads it:

  • Format matches the OS. Windows libraries are PE files; Linux systems load ELF shared objects; a real-time target loads what its own build system produces. A PE file handed to an ELF loader is not an old version — it is unreadable.
  • Machine type matches the processor. An x86-64 build holds x86-64 instructions. An ARM controller refuses the file by machine type before a single instruction executes.
  • Bitness matches the process. A 32-bit process cannot load a 64-bit library, and the reverse. Bitness belongs to the process, not the computer — same file can load in one host process and fail in another.
What you built What runs on the controller Will the file ever load? The fix
Windows DLL, x64 Windows, x64 process Yes Search path, dependencies, clean transfer
Windows DLL, x64 32-bit process on Windows No Rebuild the library 32-bit, or run a 64-bit process
Windows DLL Linux or real-time OS No Rebuild with the controller vendor's cross-toolchain
x64 build ARM controller No Cross-compile for the target machine

Read the table as a gate. The top row is an hour of path and dependency work; the bottom two rows are rebuild decisions, and no transfer setting changes them. The gate is not unique to vision code either — an HMI that reports a target-does-not-match-model error fails the same check one layer up the machine.

The likely causes, in order

Work down the list and stop at the first cause that is true. The next section gives each one a one-minute test.

  1. A different operating system on the target. The most common cause when the controller is an embedded or real-time box rather than a Windows PC. No file copy survives it; only a rebuild does.
  2. A different CPU architecture. Desktop builds are x86 or x86-64; many compact controllers run ARM processors. Compare the processor the controller runs against the machine line inside your binary.
  3. Wrong bitness for the loading process. The runtime process on the controller can be 32-bit even on 64-bit hardware, and a 64-bit library will not enter it.
  4. A missing dependency. The loader must resolve every file in your library's import table, and the error still names your library. A build that links on one machine and fails on another over dependency mismatches is the same failure class one step earlier.
  5. A search path that never includes your folder. The directory you copied into is where you put the file, not the list of directories the loader searches at runtime. Place the library where the search order looks, or call it by absolute path.
  6. A damaged or misnamed transfer. ASCII-mode file transfer rewrites bytes inside binaries; Linux file systems treat MyLib.dll and mylib.dll as different files; and a noexec mount option on the target's partition blocks any mapped library regardless of name.

Read the header: the one-minute test

The file states what it is. Both major formats carry their identity in the first bytes, and both toolchains ship a reader, so this test needs no deployment at all.

On a Windows PC:

dumpbin /headers mylib.dll → machine (x64), machine (x86) or ARM64
dumpbin /dependents mylib.dll → every library the file needs

The machine line is the architecture of the build. The dependents list is exactly what the target must satisfy.

On a Linux target, over its console or an SSH session:

file /usr/local/lib/libmine.so
ldd /usr/local/lib/libmine.so

file prints one line such as ELF 64-bit LSB shared object, x86-64 — or ARM aarch64, which is the architecture mismatch written out for you. ldd walks the dependency chain on the target itself: every line ending in 'not found' names a file to deploy or rebuild.

Without tools, read the magic bytes in any hex editor. A PE file starts 4D 5A (the letters MZ); an ELF file starts 7F 45 4C 46. First bytes wrong for the target's loader — stop copying and rebuild.

Windows loader failures carry numbers, and the number narrows the cause: 126 means a module in the chain was not found, 193 means the file is not a valid executable format for that process (the classic bitness mismatch), and 1114 means the library's own initialization code failed. The Windows system error code list decodes any other number you hit.

Worked example: a 64-bit Windows DLL failing on an ARM Linux controller

Inputs. The build machine is 64-bit Windows on x86-64 — the report says the library works well on the host computer, a desktop PC. The target is an embedded vision controller running Linux on an ARM processor — the failed deployment in the case behind this guide, and the standard case for compact controllers of this class. The file is one 64-bit Windows DLL whose import list shows KERNEL32.dll, VCRUNTIME140.dll and MSVCP140.dll, a typical set for a C++ toolchain; read your own list with dumpbin /dependents. The transfer put that single file into the folder the project references.

The rule. A library loads only when format, machine and process bitness all match. Count the matches.

The arithmetic. Format: PE on the build side, ELF loader on the target — mismatch. Machine: x86-64 instructions for an ARM processor — mismatch. Bitness: a 64-bit file heading for a 64-bit process — match, and irrelevant while the first two fail. That is 1 of 3 conditions met. Files from the PC build that can ever load on the target: 0, because the library and both runtime imports are all PE x86-64 binaries. Files to produce for the target: 1, your library rebuilt as an ARM Linux shared object; its C and C++ runtime imports come from the target's own libc and libstdc++, or travel with the rebuild.

The result and decision. One rebuild, not one more copy. Compile the same C/C++ source with the controller vendor's cross-toolchain into a position-independent ARM shared object, deploy it, then run ldd on the target until no line ends in 'not found'. The threshold between the two paths: if the header test shows the same format and machine on both sides, spend the hour on paths, dependencies and transfer; the moment format or machine differs, stop transferring files, because every further copy is guaranteed to fail.

The fix: rebuild for the target and carry the whole chain

  • Build with the controller vendor's cross-toolchain. Their compiler and system root produce binaries the target OS accepts; that is the one supported route onto that class of hardware.
  • Carry every dependency at the same target build. Your rebuilt library needs any helper libraries beside it, all produced with the same toolchain; mixed builds are how a Docker pull drags in mismatched SDK versions and plain deployments go wrong alike.
  • Let the OS provide the standard runtimes. libc and libstdc++ already sit on the target; build against the versions present there instead of copying desktop copies.
  • Check on the target, then smoke-test. ldd with no 'not found' lines, followed by one real function call with a known input and a known output.
  • Use the vendor's deployment mechanism where one exists. It installs libraries where the loader searches, instead of an ad-hoc copy into a folder the search order skips.

Two habits shrink the work next time. Keep the algorithm core in portable C/C++ so one source produces both the desktop and the target variant — the approach behind one GenICam codebase across languages and environments. And when the library you are calling is the camera SDK itself, its host-side call has rules of its own: callbacks, buffers and the snap call deserve a read before you re-port the code.

When you cannot rebuild: match the controller to the code

A third-party library with no source available changes the order of decisions:

  1. Ask its maker for a build for your controller's OS and machine. SDK vendors produce per-target builds on request, and that one request can replace a month of porting.
  2. Keep the processing on a machine that matches the build. The controller runs acquisition, lighting and the checks it can do; the matched machine runs the third-party library and sends coordinates or pass-fail results back over the network. Which job belongs on which side of the cable is exactly what planning the inference deployment across camera and host sorts out.
  3. Replace the library with one that builds for the target, if the maker cannot help.
  4. Never run the Windows library under an emulator on the production controller. It is unsupported, slow, and it fails in ways the line cannot debug mid-shift.

If the code base decides the hardware instead, pick the controller family your build already targets:

The MV-VC3301H-128G66 pairs an Intel Core i3 with 8 GB of memory and 5+ LAN ports, and includes a VM6200Pro license. The MV-VC2140-128G40-1T fits compact cells with an Intel N-series N97, 8 GB of memory, a 128 GB SSD plus a 1 TB HDD and 3-4 LAN ports. The MV-VC5525-256G70-NN steps up to a 12th-generation Intel Core i5 with 32 GB of memory, a 256 GB SSD, PoE camera ports and 8 built-in light-output channels.

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
$2,419.00View part
Vision controllersHikrobot MV-VC2140-128G40-1T VC2100 Vision Controller, N97, 8GB+128G SSD+ 1THDDMV-VC2140-128G40-1T
LAN ports
3-4 LAN ports
USB 3 ports
2 USB 3 ports
$1,679.00View part
Vision controllersHikrobot MV-VC5525-256G70-NN VC5000 Vision Controller, 12th-I5, 32GB+256G SSD, Supported PCIEMV-VC5525-256G70-NN
LAN ports
5+ LAN ports
USB 3 ports
3-4 USB 3 ports
$4,479.00 · quotedView and quote

All three run x86-64 Intel processors, the same machine type as a desktop Windows or Linux build, so the remaining question is the OS layer, and the controller's spec sheet answers it in one read.

Prevention: make the target part of the build

Record the target triplet — operating system, machine, bitness — next to the toolchain version in the project notes, and make every build state which target it produces for.

Build the desktop and target variants from the same source at the same time. A target build that compiles every week cannot rot for three months and surprise you at commissioning; a folder marked 'final' can.

Keep a deployment manifest: every file that goes on the controller, with its byte count and a hash. Compare after each transfer. This catches ASCII-mode corruption and half-copied files in seconds, with no loader involved.

Test on the real controller at project start, not the week before commissioning. The target-discovery checklist for embedded vision projects puts the OS, machine and interface questions before any code is written.

The decision, case by case

Same OS and same machine on both sides: the file can load, so work the search path, the dependency list and a binary-mode transfer. Expect the fix inside an hour.

Different OS or different machine: no copy can ever load. Rebuild your library with the controller vendor's cross-toolchain and carry its dependencies at the same target build — or, without source, get a per-target build from the library's maker, or keep that step on a machine that matches the build.

Hardware still open: pick the controller family your code base can reach before you write the deployment. The vision controllers category filters by processor, memory and camera ports. For the full build — camera, lens, light and the controller that runs the code — the system configurator walks the steps in order, and specific deployment questions go through the contact page. More loader, path and deployment fixes sit on the systems topic page.

Questions

Can a Windows DLL ever run on a Linux-based vision controller?

No, not as a copied file. Windows libraries use the PE format and the PE loader; a Linux controller loads ELF shared objects with its own loader, so the file is never even recognized as code. The supported paths are rebuilding the same source with the controller vendor's cross-toolchain, getting a per-target build from the library's maker, or keeping that processing on a Windows machine and exchanging results over the network.

How do I check whether a DLL is 32-bit or 64-bit before deploying?

On a Windows PC, run dumpbin /headers and read the machine line: x64 for 64-bit, x86 for 32-bit, ARM64 for an ARM build. Without that tool, open the file in a hex editor and read the PE machine field: 0x8664 marks an x64 build, 0x014C a 32-bit x86 build. On Linux, the file command prints format and architecture in one line. Match the result to the bitness of the process that will load the library, not to the size of the file.

Is it safe to copy the C++ runtime DLLs from my PC onto the controller?

Only when the controller runs the same Windows family, the runtime versions match what the build expects, and the location is one the controller vendor documents for system libraries. Copying system files between machines is how version mismatches start, and the failure appears later as error 126 on a file that is clearly present. Linking your own builds against a static runtime removes those files from the dependency list entirely, which is the cleanest option for a deployed target.

Why does the error name my DLL when a different file is missing?

The loader resolves your library's import table as part of loading it, so when a child library is missing the whole operation aborts under the name you asked for. Produce the dependency list on the PC with dumpbin /dependents, then run ldd on the target: every line ending in 'not found' is a file to deploy or rebuild for that target.

Does FTP damage DLLs, and how do I confirm a clean transfer?

It can. A transfer client set to ASCII mode translates line-end sequences inside the binary, so the loaded file differs from the one that worked on the PC even though the name looks right. Transfer in binary mode, then compare the byte count on the target against the source and check a hash on both sides; a matching hash settles integrity beyond argument.