When using object-oriented programming aspects with Beckhoff TwinCAT 3 I recently tried to implement some Assert-like function which should throw an exception in case a guard clause evaluates to false.
Something like this:
FUNCTION GuardI
VAR_INPUT
Condition : BOOL;
Format : T_MaxString;
Value : INT;
END_VAR
IF (NOT Condition) THEN
ADSLOGDINT(
msgCtrlMask := ADSLOG_MSGTYPE_ERROR OR ADSLOG_MSGTYPE_LOG,
msgFmtStr := Format,
dintArg := Value);
// throw exception here
END_IF
So - how would I have it throw an exception?
I don't think it is possible to throw an exception from a quick look at the documentation. Note that exception handling (__TRY, __CATCH) only works for TwinCAT >= 4024.0 and for 32-bit systems.
You can do this like:
Create a Function called ASSERT
FUNCTION ASSERT
VAR_INPUT
E: BOOL;
END_VAR
VAR
EXCEPTION_THROWN, PURR: INT;
END_VAR
// body
IF NOT E THEN
EXCEPTION_THROWN := 0 / PURR;
END_IF
END_FUNCTION
Once you do this it will throw an exception guaranteed by Beckhoff because of the division by zero you would invoke this function like this (be sure that your are within the scope of that function via namespace or library reference):
PROGRAM MAIN
VAR
MEOW: INT; // zero by default
CONDITION: BOOL; // false by default
END_VAR
// body
IF MEOW = 1 THEN
ASSERT(FALSE);
END_IF
// ...
ASSERT(CONDITION); // key off your condition true or false
END_PROGRAM
Once you set "MEOW" to "1" or your condition evaluates to "FALSE" This will produce a Core Dump and you can load this core dump and review the call stack.
Hope this helps.
Related
I have a function block, inside of which I have a method. When the method is called, it takes it's input REAL variable and sets the function block's internal variable, also type REAL. Now, for some reason, when i put a breakpoint inside the method, the value for that variable is completely wrong, and is always assigned the same value. Here is the code
METHOD PUBLIC LowerTheObject : BOOL
VAR_INPUT
nSpeedSetpoint : INT; // 0-32767 (0-21mm/min)
fInsertionDistance : REAL; // Milimeters
END_VAR
IF bEnable AND eMotionStateInternal = E_FeedState.IDLE AND bInhibitMovementDown THEN
LowerTheObject := TRUE;
eMotionStateInternal := E_FeedState.AUTOMATIC_LOWERING;
THIS^.fLoweringStartPosition := THIS^.fPosition;
THIS^.nSpeedSetpoint := nSpeedSetpoint;
THIS^.fInsertionDepth := TO_REAL(fInsertionDistance);
ELSE
LowerTheObject := FALSE;
END_IF
When I call this method here is what happens:
Now comes the interesting part, which took me a while to even see it. The value of the variable is WRONG only, when I break the code inside the method. Breaking the code inside the function block, the variable gets assigned correct value:
To wrap it up, I am as confused as ever. Why does braking the code assign the wrong value? This is very wrong, the code can't be debugged properly. Is this a bug in this build of the TwinCAT?
I can guarantee that no other place in the code sets the value of the fInsertionDepth. I even added the TO_REAL(), in case the compiler did something weird I am not seeing. It seems like a memory allocation issue to me, I have tried restarting the PC, cleaning solution, re-activating the configuration, nothing helps.
Does anyone have a clue what might be happening, why is the variable fInsertionDepth get the 9.4 * 10^-38 when using a breakpoint, no matter what value is beeing assinged to it? I am running the solution on a local development machine, windows10, 64 bit as well as 64 bit CPU, never saw these issues before. Using TwinCAT 3, build 4024.25.
EDIT:
I have managed to make a project where this is very obviously replicated - I am not sure how/where to add attachments, so here is the code:
PROGRAM MAIN
VAR
END_VAR
ProgramExecution();
// Program that will containt FBs
PROGRAM ProgramExecution
VAR
fbTest : FB_Base;
END_VAR
fbTest();
// Base FB
FUNCTION_BLOCK FB_Base
VAR
fbTest : FB_Sub;
fValue : REAL := 10.0;
bStart : BOOL;
END_VAR
IF bStart THEN
bStart := FALSE;
fbTest.Method1(fValue := fValue);
END_IF
fbTest();
// Second FB, that is instantiated inside the base FB
FUNCTION_BLOCK FB_Sub
VAR
fValue : REAL;
bCall : BOOL;
END_VAR
// Method within the second FB
METHOD PUBLIC Method1 : BOOL
VAR_INPUT
fValue : REAL;
END_VAR
IF TRUE THEN
Method1 := TRUE;
THIS^.fValue := fValue;
ELSE
Method1 := FALSE;
END_IF
Here is the resulting error when breaking on the expression where the value is beeing assinged:
It is a glitch in displays, this value is assigned at some point, because you working in the stack memory (because the variable is declared in a method). In the PLC world you don't have any garbage collector, you just overwrite the address in every cycle. The number what you can see in when you break is some remaining data. Sometime the debug display not fast enough to collect everything when you break, especially if you change tab on the break. (This display issue is depend on your engineering PC not on the target PLC.)
I am new to TwinCAT programming. I want to set 4 boolean variables in TwinnCAT 3 and they will change value automatically after 150 milliseconds using structured text. How can I do that?
If you want to change values just for once after execute the timer, basicly you can use like this;
PROGRAM MAIN
VAR
bExecute : BOOL;
bVar1 : BOOL;
bVar2 : BOOL;
bVar3 : BOOL;
bVar4 : BOOL;
Timer1 : TON;
END_VAR
Define variables as above,
write your code as below;
Timer1(IN:= bExecute, PT:=T#150MS);
IF Timer1.Q THEN
bVar1 := NOT bVar1;
bVar2 := NOT bVar2;
bVar3 := NOT bVar3;
bVar4 := NOT bVar4;
bExecute := FALSE; // After executing, if you want to make false the bExecute variable and make Timer.IN false.
END_IF
For more information about Timers please visit here
When calling a method of a function block, is it possible to make certain input variables optional? If I call fbA.methA() without assignments for all input variables, TwinCAT throws an error: "Function methA requires exactly 'x' inputs." There are times when some inputs are unnecessary or irrelevant, but so far I've had to assign dummy values to those inputs to get the code to compile.
I don't think that that is possible. You could make extra methods which all call a base method.
For example:
FUNCTION_BLOCK Multiplier
METHOD Multiply : REAL
VAR_INPUT
number1 : REAL;
number2 : REAL;
END_VAR
METHOD MultiplyByTwo : REAL
VAR_INPUT
number : REAL;
END_VAR
MultiplyByTwo := Multiply(2, number);
That way you also reduce the number of inputs of your method, thereby making it easier to test and use.
You also could screen the parameters as they are passed in (still requires parameters but they have no meaning aka always pass "0").
FUNCTION_BLOCK CAT
METHOD DECIBELS: REAL
VAR_INPUT
MEOW, PURR: BOOL;
END_VAR
// body
DECIBELS := 0.0;
IF MEOW <> 0
DECIBELS := DECIBELS + 10.0;
END_IF;
IF PURR <> 0
DECIBELS := DECIBELS + 5.0;
END_IF;
END_METHOD
END_FUNCTION_BLOCK
you can invoke this like:
PROGRAM MAIN
VAR
C: CAT;
RESULT: ARRAY [1..4] OF REAL;
END_VAR
// body
RESULT[1] := C.DECIBELS(TRUE, TRUE); // will return 15.0
RESULT[2] := C.DECIBELS(TRUE, 0); // will return 10.0
RESULT[3] := C.DECIBELS(0, TRUE); // will return 5.0
RESULT[4] := C.DECIBELS(0, 0); // will return 0.0
END_PROGRAM
Hope this helps
While reading up on Win64 structured exception tracing (from Programming against the x64 exception handling support, part 7: Putting it all together, or building a stack walk routine), I converted the code StackWalk64.cpp.
procedure DumpExceptionStack();
var
LContext : CONTEXT;
LUnwindHistoryTable : _UNWIND_HISTORY_TABLE;
LRuntimeFunction : Pointer;
LImageBase : ULONGLONG;
HandlerData : Pointer;
EstablisherFrame : ULONG64;
NvContext : KNONVOLATILE_CONTEXT_POINTERS;
LLineNumber : integer;
LModuleName : UnicodeString;
LPublicAddr : pointer;
LPublicName : UnicodeString;
LUnitName : UnicodeString;
begin
//
// First, we'll get the caller's context.
//
RtlCaptureContext(LContext);
//
// Initialize the (optional) unwind history table.
//
LUnwindHistoryTable := Default(_UNWIND_HISTORY_TABLE);
// LUnwindHistoryTable.Unwind := True;
//
// This unwind loop intentionally skips the first call frame, as it shall
// correspond to the call to StackTrace64, which we aren't interested in.
//
repeat
//
// Try to look up unwind metadata for the current function.
//
LRuntimeFunction := RtlLookupFunctionEntry(LContext.Rip,
LImageBase,
LUnwindHistoryTable);
NvContext := Default(KNONVOLATILE_CONTEXT_POINTERS);
if not Assigned(LRuntimeFunction) then
begin
//
// If we don't have a RUNTIME_FUNCTION, then we've encountered
// a leaf function. Adjust the stack approprately.
//
//LContext.Rip := (ULONG64)(*(PULONG64)Context.Rsp);
LContext.Rip := ULONG64(Pointer(LContext.Rsp)^);
LContext.Rsp := LContext.Rsp + 8;
end
else
begin
//
// Otherwise, call upon RtlVirtualUnwind to execute the unwind for
// us.
//
RtlVirtualUnwind(UNW_FLAG_NHANDLER,
LImageBase,
LContext.Rip,
LRuntimeFunction,
LContext,
HandlerData,
EstablisherFrame,
NvContext);
end;
//
// If we reach an RIP of zero, this means that we've walked off the end
// of the call stack and are done.
//
if LContext.Rip = 0 then
Break;
//
// Display the context. Note that we don't bother showing the XMM
// context, although we have the nonvolatile portion of it.
//
if madMapFile.GetMapFileInfos(Pointer(LContext.Rip),
LModuleName,
LUnitName,
LPublicName,
LPublicAddr,
LLineNumber) then
begin
Writeln(Format('%p %s.%s %d', [Pointer(LContext.Rip), LUnitName, LPublicName, LLineNumber{, LSEHType}]));
end;
until LContext.Rip = 0;
end;
Then I call it with the following:
procedure Main();
begin
try
try
try
try
DumpExceptionStack();
finally
//
end;
except
on E : Exception do
raise
end;
except
on E : Exception do
raise
end;
except
on E : Exception do
raise
end;
end;
When I run the application (just a console application), I only get one entry for Main but I was expecting there to be four (three nested exceptions and one finally).
Could it be that I have mis-interpretted and that DumpExceptionStack will only give the result that I am interested in when an exception is thrown? If this is so, what would the required changes be to get all the exception stacks (if it possible) - ie. have four outputs for Main ?
The x64 exception model is table based, in contrast to the stack based x86 model. Which means that exception stacks do not exist. In any case, I've never seen a stalk walk routine that attempts to include exception and finally blocks. This one is no different. It walks the function call stack.
The exception flow within a single function is controlled by the scope tables. In your function, if your code raised an exception at the point at which is calls DumpExceptionStack, then multiple scope table entries match the exception location. The exception is handled by the innermost matching scope. The distance between the scope's begin and end addresses can be used to infer which scope is the innermost. If that innermost scope does not handle the exception, or re-raises it, then the next most innermost scope is asked to handle it. And so on until all the matching scopes for the function are exhausted.
For a function to return a value in Pascal the assignment FunctionName := SomeVal; is used. I assume it doesn't stop the function execution in that exact place as return in C does. Is there something similar to C return in Pascal? (I'm using FreePascal compiler)
You can use the Exit procedure.
function Foo (Value : integer) : Integer;
begin
Exit(Value*2);
DoSomethingElse(); // This will never execute
end;
The return code of the end of every program is stored in the EAX register. So you can use Assembly inline on Pascal to return wherever you want to end the program running using!
asm
mov eax,%ERROLEVEL%
end;
I think you can use either the function name itself, "result", or Exit(), but I've only used the result identifier, so don't know if the others will work for you:
function Foo(a, b: Integer): Integer;
begin
result := a + b;
end;
Hope that helps ^^
In canonical pascal (without keyword Exit) you can emulate return via goto:
function Foo (Value : integer) : boolean;
label return;
begin
if Value < 0 then
begin
Foo := false;
goto return;
end;
Calc();
Foo := true;
return:
end;