The AVlabs CAN Backend¶
There's no OS-level CAN abstraction on Windows the way SocketCAN provides
on Linux - every vendor ships its own proprietary DLL with its own API
shape. CANtrip's answer is a single vendor-neutral interface,
IAvlabsCanBackend (common/AVlabsCanBackend.h). Every piece of code
that needs to talk to CAN hardware, can2pcap.exe, MessageSender, talks
to this interface and never to a vendor SDK directly.
class IAvlabsCanBackend {
public:
virtual bool initialize(uint64_t channelId, const CanBitrateConfig& config,
bool requestOwnership, std::string* error) = 0;
virtual void uninitialize(uint64_t channelId) = 0;
virtual bool readFrame(uint64_t channelId, CanFrame* frame, std::string* error) = 0;
virtual bool writeFrame(uint64_t channelId, const CanFrame& frame, std::string* error) = 0;
// ...
};
- Each backend dynamically loads its vendor's DLL at runtime
(
LoadLibrary/GetProcAddress), so CANtrip builds and runs fine with only some (or none) of the vendor SDKs actually installed - a missing DLL just means that vendor's channels don't show up, not a build or launch failure. readFrame()is documented and used as non-blocking: it returns immediately whether or not a frame arrived.can2pcap.exe's capture loop relies on this (see Data Flow).initialize()'srequestOwnershipparameter is what listen-only mode threads through to a backend - what it actually does is vendor-specific, see Send Message Internals.
Reference implementations¶
PeakBackend(common/PeakBackend.cpp) wraps PEAK-System'sPCANBasic.dll. Classic CAN and CAN FD.VectorBackend(common/VectorBackend.cpp) wraps Vector Informatik'svxlapi64.dll(XL Driver Library), verified against a VN1640A and a VN7640. Classic CAN and CAN FD, with bit timing computed bycommon/CanBitTiming.h/.cpp.CanBackendRegistry.cppis the single place that lists every backend CANtrip knows about (probeAvailableBackends()).
Adding support for another vendor (Kvaser's CANlib, ETAS's BOA, etc.) means
implementing IAvlabsCanBackend once against that vendor's real SDK header,
following PeakBackend as the reference shape, and adding one line to the
registry - see
CONTRIBUTING.md.
The DLC code vs byte length trap¶
CanFrame::dlc stores the raw DLC code straight from the vendor
driver/CAN spec terminology (0-15), not a byte count. For classic frames
(dlc ≤ 8) the code and the byte length happen to be numerically identical,
which is exactly what makes this trap easy to miss - it only diverges for
CAN FD frames, where codes 9-15 map non-linearly to 12/16/20/24/32/48/64
bytes (ISO 11898-1):
Everywhere in the app layer that isn't this raw CanFrame struct -
DecodedCanFrame::dlc, TransmitMessage::dlc - stores a real byte length
(0-64) instead, matching what a human actually means by "how many bytes."
The conversion between the two happens at exactly the boundaries where a
CanFrame gets constructed or consumed: can2pcap.cpp's
fdDlcCodeToLength() (serializing into the SocketCAN wire format) and
AscLogWriter.cpp/MessageSender.cpp's dlcCodeFromByteLength() (going
the other direction, into a real transmit).
This is not a hypothetical foot-gun. Missing this conversion in
MessageSender::transmitOne() sent a raw byte count (e.g. 64) straight
into a real Vector adapter's DLC code field, and caused real bus errors
followed by a crash on real hardware, confirmed and fixed during Send
Message's development. If you're writing new code that touches a
CanFrame at a boundary with app-level code, check which convention
you're actually holding before assuming they're interchangeable.