Key Practical Tips for Effective CAPL Development in CANoe 12
Local variables in CAPL behave like C static variables by default. For example:
on key 'b'
{
incrementCounter();
}
void incrementCounter()
{
byte tempVal = 0;
tempVal++;
writeLineEx(-3, 1, "Current tempVal: %d", tempVal);
}
Hitting 'b' seven times prints values 1 through 7, not 1 repeatedly. To reset a variable per function call, split declaration and initialization:
on key 'b'
{
incrementCounter();
}
void incrementCounter()
{
byte tempVal;
tempVal = 0;
tempVal++;
writeLineEx(-3, 1, "Current tempVal: %d", tempVal);
}
Now each press outputs 1.
When comparing unsigned integers with their highest bit set in CAPL (for CANoe 12 SP5 environments), unexpected results may occur. Test code:
on key 'm'
{
checkSignBehavior();
}
void checkSignBehavior()
{
byte u8 = 0x80;
byte u8_greater = u8 > 0;
byte u8_leq = u8 <= 0;
writeLineEx(-3, 1, "u8: %d, >0: %d, <=0: %d", u8, u8_greater, u8_leq);
word u16 = 0x8000;
byte u16_greater = u16 > 0;
byte u16_leq = u16 <= 0;
writeLineEx(-3, 1, "u16: %d, >0: %d, <=0: %d", u16, u16_greater, u16_leq);
dword u32 = 0x80000000;
byte u32_greater = u32 > 0;
byte u32_leq = u32 <= 0;
writeLineEx(-3, 1, "u32: %d, >0: %d, <=0: %d", u32, u32_greater, u32_leq);
}
The final 32-bit comparison shows 0x80000000 is incorrectly treated as non-positive. Work around this by using qword for such values.
CAPL does not support const arrays in .can node files, with immediate red squiggles in the editor. In .cin library files, the syntax check passes but compilation fails with an error.
The substr_cpy function extracts a substring from src and overwrites dest:
void substr_cpy(char dest[], char src[], long srcStart, long len, long maxDest);
maxDest specifies dest’s capacity, and extraction succeeds only if maxDest ≥ len + 1 to account for the null terminator. elcount() remains unaffected by maxDest, as it always returns the declared array size. Example usage:
on key 'p'
{
char source[80] = "prefix_456_suffix";
char target[80] = "default_val";
long numericResult;
substr_cpy(target, source, 7, 3, 3);
writeLineEx(-3, 1, "Case 1: target = %s, size = %d", target, elcount(target));
substr_cpy(target, source, 7, 3, 4);
writeLineEx(-3, 1, "Case 2: target = %s, size = %d", target, elcount(target));
substr_cpy(target, source, 7, 2, elcount(target));
writeLineEx(-3, 1, "Case 3: target = %s, size = %d", target, elcount(target));
substr_cpy(target, source, 7, 3, elcount(target));
writeLineEx(-3, 1, "Case 4: target = %s, size = %d", target, elcount(target));
numericResult = atol(target);
writeLineEx(-3, 1, "Parsed number: %d", numericResult);
}
Output:
Case 1: target = default_val, size = 80
Case 2: target = 456, size = 80
Case 3: target = 45, size = 80
Case 4: target = 456, size = 80
Parsed number: 456
The substr_cpy_off variant preserves the first destOffset characters of dest before inserting the extracted substring:
on key 'q'
{
char source[80] = "prefix_456_suffix";
char target[80] = "custom_789_extra";
substr_cpy_off(target, 7, source, 7, 3, elcount(target));
writeLineEx(-3, 1, "Modified target: %s", target);
}
Output:
Modified target: custom_456
The '@' shorthand works only for integer and float system variables. Use sysGetVariableString and sysSetVariableString for string system variables:
testcase TC_StrVarTest()
{
char buffer[120];
sysGetVariableString(sysvar::StrTestSys, buffer, elCount(buffer));
writeLineEx(-3, 1, "Initial string: %s", buffer);
sysSetVariableString(sysvar::StrTestSys, "Updated after initial read");
sysGetVariableString(sysvar::StrTestSys, buffer, elCount(buffer));
writeLineEx(-3, 1, "Modified string: %s", buffer);
}
Structs can be iintialized at declaration, including nested structs:
variables
{
struct SimpleStruct {
dword id;
char label[25];
};
struct CompositeStruct {
struct SimpleStruct first;
struct SimpleStruct second;
};
struct SimpleStruct simpleArr[2] = {
{0xC12300, "FirstSimpleItem"},
{0xC12301, "SecondSimpleItem"}
};
struct CompositeStruct compositeVal = {
{0xD12300, "FirstCompositeItem"},
{0xD12301, "SecondCompositeItem"}
};
}
on key 'r'
{
writeLineEx(-3, 1, "simpleArr[0]: 0x%X, %s", simpleArr[0].id, simpleArr[0].label);
}
on key 's'
{
writeLineEx(-3, 1, "compositeVal.first: 0x%X, %s", compositeVal.first.id, compositeVal.first.label);
}
When accessing files on a remote RT system via CANoe, configure allowed paths under File → Options → Extensions → User Files to enable CAPL file operations, such as reading flash binaries.
In complex CAPL projects, memcpy may leave residual characters in destination buffers. Explicitly termniate or reformat the buffer afterward:
void ProcessReceivedBytes(byte inputData[], dword byteCount)
{
char parsedStr[256];
memcpy(parsedStr, inputData, byteCount);
parsedStr[byteCount] = '\0';
writeLineEx(-3, 1, "Cleaned string: %s, length: %d", parsedStr, byteCount);
}
Avoid including the same .cin library with global variables in multiple .can files within a single XML TestModule. This can cause duplicate global variable instances where updates in one file do not propagate to others, leading to silent test failures. Merge .can files or split them across separate XML TestModules instead.
Using nested testfunction calls in an XML TestModule’s block to invoke testReportFileName may cause test report generation failures. Resolve this by: 1) Changing the outer function’s type to void, 2) Calling the configuration directly inside testcases, or 3) Removing unnecessary nesting.
When using LINtp_DataReq from LINtp.dll, initialize the node as a master with LINtp_InitAsMaster first. Define empty implementations of LINtp_ErrorInd, LINtp_DataCon, and LINtp_DataInd to suppress unnecessary error messages in the Write window.