ANSI C Based OPC UA Client/Server/PubSub SDK  1.9.3.467
ANSI C Based OPC UA Client/Server/PubSub SDK Documentation

Overview

This manual is an API reference and also contains some general information on how to use the SDK. Please read the Introduction first to understand the principles of this SDK before you start implementing.

ansi_c_sdk_architecture.png
SDK Architecture Overview

This manual is divided into the following sections:

Introduction

A typical OPC UA Application is composed of three software layers. The following figure shows the three layers that can be implemented in a C/C++, .NET, or JAVA environment.

softwarelayers.png
Software Layers

The OPC UA Stack implements the serialization, security, and transport of messages exchanged between different UA Applications. The stack does not contain any application layer functionality. The OPC Foundation is providing different implementations of the stack. The Ansi C version has a platform layer that contains the platform specific code separated from the platform independent functionality.

A SDK simplifies the UA stack APIs, implements common UA functionality needed in most or all UA applications, provides base functionality and helper functions, implements the security handling and provides samples for common use cases.

The Application provides or consumes information via OPC UA. This layer contains the application specific logic and a mapping to OPC UA using the OPC SDKs.

The ANSI C OPC UA Server SDK provides an ANSI C library used to develop OPC UA Servers providing a standard interface to vendor specific systems. The OPC UA Server is normally used to describe the available information from a vendor system and to provide access to the data for external systems in a standardized way.

The ANSI C OPC UA SDK is a basic OPC UA SDK designed for embedded devices providing the basic infrastructure to create an OPC UA Server.

It supports the following OPC UA services:

  • FindServer, GetEndpoints
  • CreateSession, ActivateSession, CloseSession
  • Browse, Translate, RegisterNodes, UnregisterNodes
  • Read, HistoryRead
  • Write, HistoryUpdate
  • Call
  • CreateSubscription, ModifySubscription, DeleteSusbcription, TransferSubscriptions
  • CreateMonitoredItems, ModifyMonitoredItems, DeleteMonitoredItems
  • Publish, Republish

Coding Conventions

The SDK follows an object oriented design even though it’s developed in ANSI C. It uses a special coding convention to offer functionality like C++ classes. The following samples explain the principle of this coding convention.

The C++ way:

// Definition
class Foo
{
public:
Foo();
~Foo();
void doSomething(int x, int y);
private:
int m_data;
};
// Usage
Foo *pObject = new Foo();
pObject->doSomething(5, 3);
delete pObject;

The ANSI C equivalent:

// Definition
struct _Foo
{
int data;
};
typedef struct _Foo Foo;
Foo* Foo_Create();
void Foo_Initialize(Foo *pThis);
void Foo_DoSomething(Foo *pThis, int x, int y);
void Foo_Clear(Foo *pThis);
void Foo_Delete(Foo *pThis);
// Usage
Foo *pObject;
pObject = Foo_Create();
Foo_DoSomething(pObject, 5, 3);
Foo_Delete(pObject);

ANSI C Class Comparison:

ANSI CC++ equivalentDescription
<ClassName>_Create();new <ClassName>;Allocates a new object on the heap and calls the constructor.
<ClassName>_Initialize();<ClassName>::<ClassName>;The constructor initializes the object.
<ClassName>_Clear();<ClassName>::~<ClassName>;The destructor cleans up the object.
<ClassName>_Delete();delete …Calls the destructor and frees the memory for this object on the heap.

Error handling inside most of the SDK’s functions is done using certain macros provided by the UaStack. After the declaration of variables at the beginning of a function, a status variable uStatus is created and initialized with OpcUa_Good using the macro OpcUa_InitializeStatus. This also enables automatic tracing of method calls when using UaStack’s preprocessor macro OPCUA_TRACE_ERROR_MACROS:

OpcUa_StatusCode UaServer_DoSomething(OpcUa_Void *a_pParam1)
{
OpcUa_UInt32 i;
OpcUa_InitializeStatus(OpcUa_Module_Server, "UaServer_DoSomething");

After this, the UaStack’s error handling macros from opcua_errorhandling.h might be used:

OpcUa_GotoErrorIfArgumentNull(a_pParam1);
...

At the end of the normal function flow, the value of uStatus is returned using OpcUa_ReturnStatusCode. All OpcUa_GotoError... macros jump to the OpcUa_BeginErrorHandling line if their condition is true; between this line and OpcUa_FinishErrorHandling the error handling routine is implemented:

OpcUa_ReturnStatusCode;
OpcUa_BeginErrorHandling;
if (a_pParam1 == OpcUa_Null)
{
OpcUa_Trace(OPCUA_TRACE_LEVEL_ERROR, "UaServer_DoSomething: a_pParam1 was NULL");
}
OpcUa_FinishErrorHandling;
}

ANSI C Polymorphism

This coding convention is straight forward and very simple, because it doesn’t use any advanced object oriented features like inheritance, polymorphism, or access specifiers (private, protected, …).

The only exception to this rule are the files uaserver_basenode.h and uaserver_basenode.c. These are generated files using a specially developed C++ precompiler that outputs ANSI C code. It generates ANSI C structures like described above, but additionally supports inheritance and polymorphism. This precompiler generates structures where inherited variables are copied from the base “class” and adds VTables to support polymorphism. There is no magic in this code, but we are using a compiler to generate it, because manually writing this would be very error-prone. E.g. you can call OpcUa_BaseNode_GetType(pObject), which is a “virtual” function, and the right GetType implementation is called through the function pointer in the VTable.

Polymorph Sample:

typedef OpcUa_BaseNode* OpcUa_BaseNodePtr;
OpcUa_BaseNodePtr NodeArray[10];
int i;
NodeArray[0] = OpcUa_Folder_Create();
NodeArray[1] = OpcUa_DataVariable_Create();
...
for (int i=0; i<10; i++)
{
NodeTypes type = OpcUa_BaseNode_GetType(NodeArray[i]); // call polymorph GetType function
...
OpcUa_BaseNode_Delete(NodeArray[i]); // call polymorph Delete function
}

Provider Architecture

The SDK follows a modular design where the generic code is separated from the application specific code. The Server implements generic functionality like implementing the UA Stack service table, subscription handling, monitored item management and offers the base class framework for creating an address space. The Server also creates the main UA Nodes “Root”, “Objects”, “Types” and “Views” that we call the “root address space”.

The so-called Providers implement application specific nodes using the server infrastructure and connect the server to their underlying IO data. One important Provider that comes with the SDK is the Server Provider. It implements the Server object that is defined by the OPC Foundation and is available in every OPC UA Server.

provider_architecture_640x365.png
Provider Architecture

Provider Interface

The Provider Interface is the interface used by the Server to call a Provider. This interface is defined in UaServer_pProviderInterface and must be implemented by every provider. All functions in the SDK declared with UASERVER_API can be used by the provider and the application.

Interface Functions and Dll Boundaries

The modular concept of the UA Server SDK allows to link the Data Providers statically into a single executable or dynamically using a dynamic library (e.g. *.dll on Windows, or *.so on linux/unix like systems).

The interface methods that cross these boundaries are implemented using the macro UASERVER_API just to simplify coding and to hide OS specific differences.

For example, the method definition and implementation of

UASERVER_API(OpcUa_BaseNode*) OpcUa_BaseNode_Create();

will be expanded by the pre-processor for Windows Dlls to

__declspec ( dllexport ) OpcUa_BaseNode* __stdcall OpcUa_BaseNode_Create();

and on Linux or for static linking simply to

OpcUa_BaseNode* OpcUa_BaseNode_Create();

All dynamic memory is being allocated through OpcUa_Alloc and is freed using OpcUa_Free, which are macros from the OPC UA Stack that call the memory allocation function of the Stack.

Attention: If you decide to link the providers dynamically, you also have to link against the OPC UA Stack dynamically to guarantee that there is only one OPC UA Stack with one CRT loaded. This way, allocating and freeing memory across dll boundaries works without any problems.
If you link the providers statically, you can also link the OPC UA Stack statically.