OPCFoundation / UA-.NETStandard

OPC Unified Architecture .NET Standard
Other
1.95k stars 945 forks source link

InvalidCastException when casting MethodState to generated MethodState subclass #520

Closed romatthe closed 5 years ago

romatthe commented 6 years ago

Hi all,

We've been playing around with this library to see what it takes to get a basic PoC going. Right now we're trying out the code generation abilities of the SDK, but I've come up with a couple of issues.

Our generated model contains a predefined node PhysicalGroupSet, which has a method attached to it called AddElement. Both PhysicalGroupSet and its method appear at server startup.

I've been trying to attach a method implementation to AddElement. At first, doing so seemed simple enough.

PredefinedNodes.TryGetValue(new NodeId(I4Model.MethodIds.PhysicalGroupSet_AddElement.Identifier , m_typeNamespaceIndex),
                    out var state);

var method = (MethodState)state;
method.OnCallMethod = Methods.AddElement;

Where AddElement simply refers to:

public static ServiceResult AddElement(ISystemContext context,
            MethodState method,
            IList<object> inputArguments,
            IList<object> outputArguments)
 {
      Console.WriteLine("Calling AddElement");
      return ServiceResult.Good;
 }

This works! Unfortunately, this involves dealing with a List<object> as input args and output, which is a bit cumbersome and generates a lot of type checking noise to a method implementation. However, as part of the code generation process, a class AddElementMethodState gets generated.

/// <summary>
/// Stores an instance of the AddElementMethodType Method.
/// </summary>
/// <exclude />
[System.CodeDom.Compiler.GeneratedCodeAttribute("Opc.Ua.ModelCompiler", "1.0.0.0")]
public partial class AddElementMethodState : MethodState

A first glance, I thought this allowed us to attach methods with the proper type signature. Indeed, I can now write the following code:

PredefinedNodes.TryGetValue(new NodeId(I4Model.MethodIds.PhysicalGroupSet_AddElement.Identifier , m_typeNamespaceIndex),
                    out var state);

var method = (AddElementMethodState)state;
method.OnCall = Methods.AddPhysicalHierarchyElement;

Where AddPhysicalHierarchyElement has a more sensible type signature that matches the definition in our model.

public static ServiceResult AddPhysicalHierarchyElement(
            ISystemContext context, 
            MethodState method, 
            NodeId node, 
            string name, 
            string id, 
            PhysicalHierarchyType type)
{
      Console.WriteLine("Calling AddPhysicalHierarchyElemet!");
      return ServiceResult.Good;
}

However, performing the cast to AddElementMethodState at runtime throws am InvalidCastException, which surprised me. Indeed, the runtime type of state is MethodState and not AddElementMethodState.

This confuses me. What exactly then is the point of the generated AddElementMethodState class? Is it possible to wire up a method with a more specific signature?

Thanks!

(I apologise if my question is a little unclear, I'm new to both this SDK and OPC UA)

AlinMoldovean commented 6 years ago

Hello @romatthe ,

You can have a look at the example from DiagnosticsNodeManager.AddBehaviourToPredefinedNode() where it uses the OnCall handler.

protected override NodeState AddBehaviourToPredefinedNode(ISystemContext context, NodeState predefinedNode)
        {
            BaseObjectState passiveNode = predefinedNode as BaseObjectState;

            if (passiveNode == null)
            {
                MethodState passiveMethod = predefinedNode as MethodState;

                if (passiveMethod == null)
                {
                    return predefinedNode;
                }

                if (passiveMethod.NodeId == MethodIds.ConditionType_ConditionRefresh)
                {
                    ConditionRefreshMethodState activeNode = new ConditionRefreshMethodState(passiveMethod.Parent);
                    activeNode.Create(context, passiveMethod);

                    // replace the node in the parent.
                    if (passiveMethod.Parent != null)
                    {
                        passiveMethod.Parent.ReplaceChild(context, activeNode);
                    }

                    activeNode.OnCall = OnConditionRefresh;

                    return activeNode;
                }

                return predefinedNode;
            }