Have you ever use the reflection to examine your code produced after a successful compilation of your PI System application using the PISDK? For those of you that are not familiar with the reflection principle, it is the process by which a computer program can observe (do type introspection) and modify its own structure and behavior at runtime. I did recently for one of my project where I had to write the same project but using C# instead of Visual Basic. Something caught my eyes when reviewed the code. Some of the declarations I made with the PISDK objects were replaced with PISDKClass instead as shown below.
Excerpt of my project written in VB.NET
Excerpt of the code rendered via reflection
Why the compiler replaced the instantiation of the _MyPISDK object with the PISDKClass instead of the PISDK one? Several examples of codes in these forums show connectivity with the PI Server using the PISDKClass class instead of the PISDK one as described in the PISDK help file. I dug the subject a bit and here what I found.
If you look carefully at the PI SDK object model you can see the PISDK object is declared as a COM class, meaning this object is creatable by using its constructor; if you compare the COM Interop library OSIsoft.PISDK.dll, the same object is declared as an interface. Let refresh our mind on the basic definition of a class and an interface enumerated below.
A class is a construct that is used as a blueprint to create instances of itself (referred most of the time as instance objects). A class defines constituent members which enable these class instances to have state and behavior.
An interface contains only the signatures of methods, delegates or events. The implementation of the methods is done in the class that implements the interface. An interface is not creatable by definition.
Declare a New Object from an Interface?
Per the documentation we should be able to create a new PISDK top-level object with the New keyword either in the COM or the .NET world. How will it be possible within the .NET world as the New keyword cannot be used with an interface?
The answer lies into how a COM Interop library is formed. A class is COM's language-independent way of defining a class in the object-oriented sense. A class can be a group of similar objects or a class is simply a representation of a type of object; it should be thought of as a blueprint that describes the object. A coclass supplies concrete implementation(s) of one or more interfaces (a default interface and sometimes secondary interfaces). In COM, such concrete implementations can be written in any programming language that supports COM component development, e.g. Delphi, C++, Visual Basic, etc.
When this COM coclass is converted into the .NET world i.e. a .NET interop, a public interface with the same name as the COM coclass is created; it is known as a coclass interface and has no members itself. This .NET interface derives from the converted COM coclass’ default interface. Both created interfaces are marked with the same IID. To complete the conversion of this COM coclass, another piece is created to bridge the COM and .NET worlds. This piece is a Runtime Callable Wrapper (RCW) class that will be named with the COM class name and the "Class" suffix. The RCW class calls the native CoCreateInstance COM function for creating the COM object that it wraps. The RCW class will convert each call to the COM calling convention.
Creating a new object derived from a coclass interface will result into creating the desired COM object via the RCW class. The coming example will shed some light on how this done. The RunTime Callable Wrapper is explained in the following section.
Runtime Callable Wrapper
COM differs from the .NET Framework object model in several important ways:
· Clients of COM objects must manage the lifetime of those objects; the Common Language Runtime (CLR) manages the lifetime of objects in its environment.
· Clients of COM objects discover whether a service is available by requesting an interface that provides that service and getting back an interface pointer, or not. Clients of .NET objects can obtain a description of an object's functionality using reflection (analysis of the libraries/assemblies themselves).
· .NET objects reside in memory and are managed by the .NET Framework execution environment. The execution environment can move objects around in memory for performance reasons, in which case it will update all references to the objects it moved. Unmanaged clients, having obtained a pointer to an object, rely on the object to remain at the same location. These clients have no mechanism for dealing with an object whose location is not fixed.
To overcome these differences, the runtime provides wrapper classes to make both managed and unmanaged clients think they are calling objects within their respective environment. Whenever your managed client calls a method on a COM object, the runtime creates a runtime callable wrapper (RCW). RCWs abstract the differences between managed and unmanaged reference mechanisms, among other things. The runtime also creates a COM callable wrapper (CCW) to reverse the process, enabling a COM client to seamlessly call a method on a .NET object. As the following illustration shows, the perspective of the calling code determines which wrapper class the runtime creates.
Creating a top-level PISDK object like presented below is correct from the .NET world because the PISDK interface is in fact a coclass interface defaulting to the RCW class called PISDKClass.
The .NET Visual Basic or .NET C# linker or interpreter will change your code to respect the official syntax to create a variable to the PISDK interface and instantiate it with the PISDKClass class:
You can then interchangeably use these:
Mainly the difference resides on the members exposed by both implementations, the PISDK coclass interface inherits only from the IPISDK interface and the PISDKClass class inherits from the IPISDK, PISDK, IPIAppIdentity and the IPIGlobalRestorer. Creating an object instance from the PISDKClass will give you natively access to members of all implemented interfaces without having to cast the object into another interface. This is a faster and more straight-forward way to use the PISDK top-level objects, as shown in the following examples.
All COM coclasses from the PI SDK can be instanciated using the RCW classes the same way. The RCW classes in the COM Interop library are adding the "Class" suffix at the end such as for PIValueClass and PIValuesClass for handling the PIValue and PIValues class definitions from the COM world.
I hope this shed some light on the topic. Either you choose the standard PISDK and let the compiler replaces it or you use directly the PISDKClass definition in your project, it will perform the same.