Reading properties from TwinCAT function blocks using c# - c#

We are using a C# application to read variables from a Beckhoff PLC through TwinCAT ADS v.3. If we attempt to utilize the same code to read properties the code fails with an exception.
FUNCTION_BLOCK FB_Sample
VAR
SomeVariable : INT;
END_VAR
PROPERTY SomeProp : INT // declared in a separate file
// Code used to read variable (symbol)
var handle = client.CreateVariableHandle("sampleProgram.Source.SomeVariable");
var result = client.ReadAny(handle, typeof(int));
client.DeleteVariableHandle(handle);
// Adapted code used to read property (not a symbol)
var handle = client.CreateVariableHandle("sampleProgram.Source.SomeProp"); // This fails
var result = client.ReadAny(handle, typeof(int));
client.DeleteVariableHandle(handle);
When trying to create a variable handle using the above code, we receive TwinCAT.Ads.AdsErrorException: 'Ads-Error 0x710 : Symbol could not be found.'.
Since we knew that METHOD must be marked with {attribute 'TcRpcEnable'} so it can be called with this code:
client.InvokeRpcMethod("{symbolPath}", "{methodName}", {parameters} });
We attempted to use that attribute {attribute 'TcRpcEnable'} on the property as well. Using TcAdsClient.CreateSymbolLoader and looping over all available symbols we discovered that the getter/setter of the property were then marked as rpc-methods.
Console.WriteLine($"Name: {rpcMethod.Name}; Parameters.Count: {rpcMethod.Parameters.Count}; ReturnType: {rpcMethod.ReturnType};");
RpcMethods: 2
Name: __setSomeProp; Parameters.Count: 1; ReturnType: ;
Name: __getSomeProp; Parameters.Count: 0; ReturnType: INT;
But try as we might, we cannot invoke the rpc method:
var propertyResult = client.InvokeRpcMethod("sampleProgram.Source", "__getSomeProp", Array.Empty<object>());
// Throws: TwinCAT.Ads.AdsErrorException: 'Ads-Error 0x710 : Symbol could not be found.'
var propertyResult = client.InvokeRpcMethod("sampleProgram.Source", "__get{SomeProp}", Array.Empty<object>());
// Throws: TwinCAT.Ads.RpcMethodNotSupportedException: 'The RPC method '__get{SomeProp}' is not supported on symbol 'sampleProgram.Source!'
var propertyResult = client.InvokeRpcMethod("sampleProgram.Source", "get{SomeProp}", Array.Empty<object>());
// Throws: TwinCAT.Ads.RpcMethodNotSupportedException: 'The RPC method 'get{SomeProp}' is not supported on symbol 'sampleProgram.Source!'
var propertyResult = client.InvokeRpcMethod("sampleProgram.Source.SomeProp", "get", Array.Empty<object>());
// Throws: System.ArgumentNullException: 'Value cannot be null.
// Parameter name: symbol'
Any suggestion on how we can read/write variables defined as properties on function blocks?

When you define a new property you automatically create a get and a set for that property.
You normally use properties to read or write variables that are in the VAR section of the function block.
All variables that are in the VAR section are private thus the need of properties to access those VARs from outside the Function Block.
Properties in theory should not do any complex calculations or run any logic unlike Methods.
The point I want to make is that you do not need and should not call properties via ADS.
You have access to all private VARs via ADS anyway so there is no need to call properties through ADS in the first place.
#Edit
I'm still of the opinion that properties should not contain any logic and therefor there is no need to call them via ADS.
Nevertheless there are always exceptions.
Be aware that according to the Beckhoff documentation, only simple data types and pointers will work, not structures.
Moreover "Function monitoring is not possible in the compact runtime system".
Here my working example after experimenting with the {attribute 'monitoring' := 'call'} attribute
In Twincat:
{attribute 'monitoring' := 'call'}
PROPERTY RemoteCall : INT
GET:
RemoteCall := buffer;
SET:
buffer := buffer + RemoteCall;
In C#
class Program
{
static TcAdsClient tcClient;
static void Main(string[] args)
{
tcClient = new TcAdsClient();
tcClient.Connect(851);
AdsStream dataStream = new AdsStream(2);
int iHandle = tcClient.CreateVariableHandle("MAIN.fbTest.RemoteCall");
tcClient.Read(iHandle, dataStream);
Console.WriteLine("Remote Var before property call: " + BitConverter.ToInt16(dataStream.ToArray(), 0));
tcClient.WriteAny(iHandle,Convert.ToInt16(2));
tcClient.Read(iHandle, dataStream);
Console.WriteLine("Remote Var after property call: " + BitConverter.ToInt16(dataStream.ToArray(), 0));
Console.WriteLine();
Console.ReadLine();
}
}

According to Stefan Hennecken on his Blog the property has to be decorated with a pragma to enable this:
{attribute ‘monitoring’ := ‘call’}
PROPERTY PUBLIC nProp : BYTE
Then it can be read/written with this code sample:
using (AdsClient client = new AdsClient())
{
byte valuePlc;
client.Connect(AmsNetId.Local, 851);
valuePlc = (byte)client.ReadValue(“MAIN.fbFoo.nProp”, typeof(byte));
client.WriteValue(“MAIN.fbFoo.nProp”, ++valuePlc);
}

Related

The term 'Get-WBSummary' is not recognized as a name of a cmdlet [duplicate]

I have below command and it returns me null object . When I run the command separately in PowerShell window I get the right result. Below is my PowerShell method which is calling the command and the also the PowerShell command which I have defined. I am basically looking to return a string value. Please let me know what wrong am I doing?
C# method:
public string RunScript( string contentScript, Dictionary<string, EntityProperty> parameters)
{
List<string> parameterList = new List<string>();
foreach( var item in parameters )
{
parameterList.Add( item.Value.ToString() );
}
using( PowerShell ps = PowerShell.Create() )
{
ps.AddScript( contentScript );
// in ContentScript I get "Get-RowAndPartitionKey" on debugging
ps.AddParameters( parameterList );//I get list of strings
IAsyncResult async = ps.BeginInvoke();
StringBuilder stringBuilder = new StringBuilder();
foreach( PSObject result in ps.EndInvoke( async ) )
// here i get result empty in ps.EndInvoke(async)
{
stringBuilder.AppendLine( result.ToString() );
}
return stringBuilder.ToString();
}
}
}
My Powershell GetRowAndPartitionKey cmdlet definition, which the code above is trying to call:
public abstract class GetRowAndPartitionKey : PSCmdlet
{
[Parameter]
public List<string> Properties { get; set; } = new List<string>();
}
[Cmdlet( VerbsCommon.Get, "RowAndPartitionKey" )]
public class GetRowAndPartitionKeyCmd : GetRowAndPartitionKey
{
protected override void ProcessRecord()
{
string rowKey = string.Join( "_", Properties );
string pKey = string.Empty;
WriteObject( new
{
RowKey = rowKey,
PartitionKey = pKey
} );
}
}
}
When using the PowerShell SDK, if you want to pass parameters to a single command with .AddParameter() / .AddParameters() / AddArgument(), use .AddCommand(), not .AddScript()
.AddScript() is for passing arbitrary pieces of PowerShell code that is executed as a script block to which the parameters added with .AddParameters() are passed.
That is, your invocation is equivalent to & { Get-RowAndPartitionKey } <your-parameters>, and as you can see, your Get-RowAndPartitionKey command therefore doesn't receive the parameter values.
See this answer or more information.
Note: As a prerequisite for calling your custom Get-RowAndPartitionKey cmdlet, you may have to explicitly import the module (DLL) that contains it, which you can do:
either: with a separate, synchronous Import-Module call executed beforehand (for simplicity, I'm using .AddArgument() here, with passes an argument positionally, which binds to the -Name parameter (which also accepts paths)):
ps.AddCommand("Import-Module").AddArgument(#"<your-module-path-here>").Invoke();
or: as part of a single (in this case asynchronous) invocation - note the required .AddStatement() call to separate the two commands:
IAsyncResult async =
ps.AddCommand("Import-Module").AddArgument(#"<your-module-path-here>")
.AddStatement()
.AddCommand("GetRowAndPartitionKey").AddParameter("Properties", parameterList)
.BeginInvoke();
"<your-module-path-here>" refers to the full file-system path of the module that contains the Get-RowAndPartitionKey cmdlet; depending on how that module is implemented, it is either a path to the module's directory, its .psd1 module manifest, or to its .dll, if it is a stand-alone assembly.
Alternative import method, using the PowerShell SDK's dedicated .ImportPSModule() method:
This method obviates the need for an in-session Import-Module call, but requires extra setup:
Create a default session state.
Call .ImportPSModule() on it to import the module.
Pass this session state to PowerShell.Create()
var iss = InitialSessionState.CreateDefault();
iss.ImportPSModule(new string[] { #"<your-module-path-here>" });
var ps = PowerShell.Create(iss);
// Now the PowerShell commands submitted to the `ps` instance
// will see the module's exported commands.
Caveat: A PowerShell instance reflects its initial session state in .Runspace.InitialSessionState, but as a conceptually read-only property; the tricky part is that it is technically still modifiable, so that mistaken attempts to modify it are quietly ignored rather than resulting in exceptions.
To troubleshoot these calls:
Check ps.HadErrors after .Invoke() / .EndInvoke() to see if the PowerShell commands reported any (non-terminating) errors.
Enumerate ps.Streams.Errors to inspect the specific errors that occurred.
See this answer to a follow-up question for self-contained sample code that demonstrates these techniques.

Having issues understanding class constructors in F#

I have the following code:
module ExchangeSocket =
type Socket(url, id, key) =
let Communicator = new TestCommunicator(url)
let Client = new WebsocketClient(Communicator)
do
Communicator.Name <- "test"
Communicator.ReconnectTimeoutMs <- int (TimeSpan.FromSeconds(30.).TotalMilliseconds)
if we look at the last two lines, the C# usage is like that:
Communicator = new WebsocketCommunicator(wsUrl) { Name = "tst", ReconnectTimeoutMs = (int) TimeSpan.FromSeconds(30).TotalMilliseconds };
Now I read that to make a class constructor, I have to use the 'new' keyword; so I make the fields members and do a 'new' section:
member this.communicator : TestCommunicator
member this.client : WebsocketClient
new() =
this.communicator <- new TestCommunicator(url)
this.client <- new WebsocketClient(this.communicator)
but this doesn't work (line 15 is the top line in this example)
Socket.fs(15, 64): [FS0010] Incomplete structured construct at or before this point in member definition. Expected 'with', '=' or other token.
my questions are:
How to make this work?
What does 'new' bring that 'do' doesn't?
Now I read that to make a class constructor, I have to use the 'new' keyword
To make additional class constructors you should use the new keyword. The primary constructor is sufficient for you here.
In member this.communicator : TestCommunicator, while you have specified the type of the property communicator, you have not specified how to get (or set) this property. It is missing = ... or with get/set as the error message says.
new() =
this.communicator <- new TestCommunicator(url)
this.client <- new WebsocketClient(this.communicator)
When you fix the previous error, you will get another error here because 1. new() should return a Socket, and 2. it references this and url, which are not in scope.
Probably what you want is:
type Socket(url, id, key) =
let communicator =
new TestCommunicator(url,
Name = "test",
ReconnectTimeoutMs = int (TimeSpan.FromSeconds(30.).TotalMilliseconds))
let client = new WebsocketClient(communicator)
member _.Communicator = communicator
member _.Client = client
While this answer does what you asked for, better would be to read the spec for classes ( See https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/classes and https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/members/) and some examples of usage. Your code suggests you were guessing which is not the right approach.

Calling function from ComImport class doesn't fail as expected

I'm trying to verify that the class I'm trying to use via COM works as expected. Unfortunately it seems to succeed on a call which should fail:
enum X509CertificateEnrollmentContext
{
ContextUser = 0x1,
ContextMachine = 0x2,
ContextAdministratorForceMachine = 0x3
}
[ComImport(), Guid("884e2045-217d-11da-b2a4-000e7bbb2b09")]
class Cenroll { }
[Guid("728ab35d-217d-11da-b2a4-000e7bbb2b09")]
interface IX509CertificateRequestCmc2
{
void InitializeFromTemplate(
[In] X509CertificateEnrollmentContext Context,
[In] IX509EnrollmentPolicyServer pPolicyServer,
[In] IX509CertificateTemplate pTemplate);
}
static void Main(string[] args)
{
var cr = new Cenroll();
var cmc2 = (IX509CertificateRequestCmc2)cr;
cmc2.InitializeFromTemplate(X509CertificateEnrollmentContext.ContextUser, null, null);
}
Casting from Cenroll to the interface works, which indicates that the guids are ok. (and it fails casting to other guids, so it's not random success)
But when I call InitializeFromTemplate, with both parameters set to null, it succeeds. The documentation says that the result should be an E_POINTER error:
Return code - Description
E_POINTER - The pPolicyServer and pTemplate parameters cannot be NULL.
So why don't I see an exception?
The problem is that you are redeclaring the interface, and the new definition is different from the original.
Guids are OK, but underneath, QueryInterface implementation checks GUID, and returns the pointer to the implementation - this is the interface vtable and method addresses are calculated relative to this address (when a call to the method is compiled, offset of the method is added to this address to get the actuall address).
In your implementation, InitializeFromTemplate is the first method and generated client code calls the method at the beginning of vtable.
However, in the original interface, there are 56 other methods before InitializeFromTemplate because there is an inheritance chain:
IX509CertificateRequest (25 methods)
|
+-> IX509CertificateRequestPkcs7 (8 methods)
|
+-> IX509CertificateRequestCmc (23 methods)
|
+-> IX509CertificateRequestCmc2
Function addresses in the certenroll.dll adhere to this layout, so when you call InitializeFromTemplate as declared in your interface, you are calling the first method in chain which is actually IX509CertificateRequest::Initialize.
As an experiment, if you add 56 dummy methods before InitializeFromTemplate in your IX509CertificateRequestCmc2 you will correctly receive an exception:
[Guid("728ab35d-217d-11da-b2a4-000e7bbb2b09")]
interface IX509CertificateRequestCmc
{
void fn1();
void fn2();
...
void fn56();
void InitializeFromTemplate(...);
}
The call will throw: CertEnroll::CX509CertificateRequestCmc::InitializeFromTemplate: Invalid pointer 0x80004003 (-2147467261)
Of course, the solution is not to add the dummy methods :) You should use the generated interop types instead of providing your own. As you are referencing the certenroll assembly, I don't understand why don't you simply use those generated interop classes. Here's the full example which behaves as expected:
using CERTENROLLLib;
namespace comcerttest
{
class Program
{
static void Main(string[] args)
{
// If you are embedding the interop types, note that you must
// remove the `Class` suffix from generated type name in order
// to instantiate it. See link at the bottom for explanation:
var cr = new CX509CertificateRequestCmc();
var cmc2 = (IX509CertificateRequestCmc2)cr;
cmc2.InitializeFromTemplate(X509CertificateEnrollmentContext.ContextUser, null, null);
}
}
}
The issue with using class vs interface type is explained here:
Using embedded interop types

Coldfusion Accessing a .net objects parent methods

For some reason Coldfusion is having an issue with accessing the parent methods of one of the objects it creates.
consider this code:
<cfscript>
variables.sHTML = '<html><head><title></title></head><body><p>Hello <strong>World</strong></p></body></html>';
try{
variables.sAltChunkID = "altChunk1";
variables.sExportDirectory = application.sSecureExportPath&'int'&'\word\';
variables.sDLLPath = 'C:\Program Files (x86)\Open XML SDK\V2.0\lib\DocumentFormat.OpenXml.dll';
variables.sFileName = "testI.docx";
variables.sFileToWrite = variables.sExportDirectory&'#variables.sFileName#';
variables.enumWordProcessingDocumentType = createObject("dotnet","DocumentFormat.OpenXml.WordprocessingDocumentType","#variables.sDLLPath#").init().Document;
variables.oDocument = createObject("dotnet","DocumentFormat.OpenXml.Packaging.WordprocessingDocument","#variables.sDLLPath#").Create(variables.sFileToWrite,variables.enumWordProcessingDocumentType);
variables.oMainDocument = variables.oDocument.AddMainDocumentPart();
variables.oEncoding = createObject("dotnet","System.Text.UTF8Encoding").init();
//variables.oMemoryStream = createObject("dotnet","System.IO.MemoryStream").init(variables.oEncoding.GetBytes(variables.sHTML));
variables.enumAltChunk = createObject("dotnet","DocumentFormat.OpenXml.Packaging.AlternativeFormatImportPartType","#variables.sDLLPath#").html;
variables.oFormatImportPart = variables.oMainDocument.AddAlternativeFormatImportPart(variables.enumAltChunk,variables.sAltChunkID);
writeDump(variables.oFormatImportPart);
variables.oFormatImportPart.FeedData(createObject("dotnet","System.IO.MemoryStream").init(variables.oEncoding.GetBytes(variables.sHTML)));
} catch(Any e) {
writeDump(e);
}
</cfscript>
variables.oFormatImportPart has a parent method of FeedData(System.IO.Stream), however when I get to that line, Coldfusion hits me with an exception of:
Either there are no methods with the specified method name and argument types or the FeedData method is overloaded with argument types that ColdFusion cannot decipher reliably. ColdFusion found 0 methods that match the provided arguments. If this is a Java object and you verified that the method exists, use the javacast function to reduce ambiguity.
But as you can see from my Dump, FeedData does indeed exist as a method:
FeedData is overloaded and expecting a Stream object. You are currently sending it an ambiguous object as it has not be cast to the proper type after your createObject call.

Moling DataContext with MS Moles?

How can I mole the DataContext that I'm using in a class to write messages to a table. I'd like to assert that the table LINQ is writing to has the expected count of messages. Here's what i have so far.
var context = new MJustTestingDataContext();
MyMessagewriter writer = new MyMessageWriter(context);
var messageList = new List<MIncmoingMessage>();
MTable<MIncomingMessage> messageTable = new MTable<MIncomingMessage>();
messageTable.Bind(messagesLinqList.AsQueryable());
If I use this code with xUnit in my class under test I'll get this exception
Microsoft.Moles.Framework.Moles.MoleNotImplementedException: DataContext.Dispose() was not moled.
What am I missing here and how to implement DataContext.Dispose() on the mole? I'm using moles standalone without Pex.
When you create a new Mole the default behavior for its methods and properties is to throw a MoleNotImplementedException whenever they are called.
To implement the mole you can do context.Dispose = () => {}; which means that nothing happens when the Dispose method gets called on the moled instance.
I reread the question and you probably are having a problem since Dispose is defined in a base class. To mole base method you need to do the following:
var context = new MJustTestingDataContext();
var baseContext = new MDataContext(context);
baseContext.Dispose = () => {};
You'll need to implement every property/method that gets called by the code under test or you can set the default behavior for the mole instance globally using the method BehaveAsDefaultValue. This way every method in the mole will do nothing and return the default value for it's return type if one exists instead of throwing a MoleNotImplementedException. However if you require this behavior it's better to use a stub than a mole.
I'm having trouble understanding what your test is doing. I had to do something similar yesterday, so I'll share my experience. First, it's important to understand that you don't need to use all the MoleTypes to test your code -- you just need to use Moles to redirect certain parts of your code to lambda expressions. Given a method that does this:
get a list of users to modify from the database
modify every user in the set
send the new set back to the database
I'd like to redirect 1 and 3 to not use the database. For instance, I can redirect the call to SubmitChanges (3) via this code:
bool hitSubmitChanges = false;
int changeCount = 0;
IList<object> updates = null;
// more code here...
// redirect DataContext.SubmitChanges() to a lambda to catch updates
MDataContext.AllInstances.SubmitChanges = (c) =>
{
changeCount = c.GetChangeSet().Updates.Count;
updates = c.GetChangeSet().Updates;
hitSubmitChanges = true;
};
That (and the call to get the users) would be the only Moletypes I'd use in the test. The rest of it would be normal. Then I can use assertions to check the values of changeCount, updates and hitSubmitChanges.

Categories