A Modern Crestron Programming Strategy

A large majority of end-users and businesses understand the pace at which technology advances in the modern world. As a Crestron Service Provider, we often have to be more pragmatic regarding the efficacy of programming strategies underlying our business processes. I would argue, that the primary goal should be to maximize value. This ensures that the decisions being made are optimized to reduce senseless changes that add little or no value to the business or your customers. Beneficial decisions will naturally follow with such a mindset.

SIMPL# Pro is one such technology that can add unparalleled value to your control system solutions. To many Crestron programmers with their adolescent knowledge of the Microsoft C# language, it seems unfamiliar and peculiar. This stems from the fact that many programmers were never required to learn C# programming in the past. Consequently, some programmers dismiss the idea entirely; a mistake that can lead to a myriad of lost opportunities. As a zealous C# programmer having an award from Microsoft on the topic, I believe I have enough credibility to suggest a case for both sides. Let’s discuss the disadvantages first, following a list of advantages.

Note: “SIMPL#” is a Crestron term for their programming solution that utilizes the Microsoft C# programming language. SIMPL# Pro allows Crestron programmers to write control system programs entirely in C#.

Disadvantages:

  1. SIMPL# Pro is more difficult to learn, in which the learning curve of both SIMPL Windows and SIMPL+ pales by comparison.
  2. SIMPL# Pro library documentation is still a work in process. (This makes many things a journey of trial and error, which can be added complexity for someone who is trying to learn the C# language at the same time. The libraries themselves also do not come without their quirks.)

Advantages:

  1. SIMPL# Libraries can be used to extend the functionality of a program written in SIMPL Windows, but not without limitations. (i.e. Types have to be “translated” to SIMPL+ compatible ones which can reduce the versatility of the underlying functionality, the SIMPL+ cross compiler contains bugs, etc.) This leads to an implied benefit in having everything encapsulated within the SIMPL# Pro environment, without having to adapt the information to comply with the limited boundaries of SIMPL+.
  2. You can create hardware at runtime dynamically. (In comparison to SIMPL Windows programs, where you have to hardcode the combined hardware definitions before compilation, regardless of whether you’re remapping devices or not.)
  3. The controller definition is entirely dynamic. What I mean, is that you can write a single program that is designed to support a DMPS of any type, or even have a program that can support a CP4 and a PRO4 simultaneously.
  4. C# source code allows you to utilize source control the way it was intended to be used. (SIMPL Windows files that define the symbols and signals within a program aren’t managed by the programmer directly, making diffs completely useless.)
  5. The C# language allows you to represent data without restricting you to a maximum of 16-bit integers. You can use primitive types that hold 64-bit values, and even use classes that can support much larger such as BigInteger. It also gives you the ability to use IEEE 754 floating point datatypes such as float and double, as well as the decimal type when you need greater precision.
  6. Being able to use IDE’s like Visual Studio or JetBrains Rider gives you the ability to be vastly more efficient with your workflow. These tools are used by millions of developers around the world, and are fine tuned to accommodate many different use cases and features.
  7. The ability to use a fully fledged CI/CD pipeline to automate build and deployment processes for agile software development is invaluable. We now have far more capabilities in the context of improving testability as well, with unit tests that can also be automated. (Note: This is made even easier with the 4-series platform, which relies on the Mono runtime.)
  8. Reusability is often an overlooked benefit, in the shadow of change that many old school Crestron programmers have a distaste for. The gates are now nearly wide open for programmers to use virtually any open-source .NET library they want, as long as they comply with any applicable license agreements. Abstract C# code when written properly, is also inherently more reusable than code written in SIMPL Windows due to the nature of true polymorphism.
  9. You can also create extremely useful console commands for a multitude of purposes. Unlike with user program commands in SIMPL Windows, inside of a SIMPL# Pro application, you have access to any part of the program memory in terms of “state.” In SIMPL Windows, that state is isolated between each symbol, unless you’ve created signal ties between symbols, or you’ve created a SIMPL# library that allows you to share memory between instances of an object that only those symbols use.

From a business perspective, it may be difficult to transition to SIMPL# Pro initially. Although, I suspect many will be forced to make the leap within the next few years, with the slow but obvious transition towards more open technology. With the advent of HTML5 and JavaScript inside the Crestron ecosystem, there may even come a point when SIMPL Windows and SIMPL+ are no longer viable solutions. Even if they are still remain available options in the future, I sure wouldn’t want to be the one bringing a knife to a gunfight!

To learn more about SIMPL#, we have developed a popular SIMPL# Fundamentals course that allows people to get their foot in the door. The course has proven to be a valuable resource for Crestron Dealers and CSP’s, regardless of whether their programming staff has beginner or intermediate knowledge on the subject. This course can be found here: https://www.tbdenterprises.ca/product/simplsharp-fundamentals-course. In the next couple months, we also hope to be unveiling a brand new course that focuses exclusively on SIMPL# Pro. Our goal is to leverage our extensive knowledge of the C# language to fill in the gaps for those who still find this technology confusing in the context of Crestron control systems.

 

Try our Advanced Matrix Module For Free!

We are happy to announce that our advanced matrix module is now free to try! Experience the power and flexibility of an extremely dynamic matrix routing module suite for 30 minutes without limitations.

Over the years, our module has been tested against some of the largest systems, making easy work of complicated, yet common tasks involving A/V switchers. This module can be used for small huddle space rooms with a 4×1 switcher, up to systems that have one or more DM-MD128x128 switchers that may or may not be interconnected. This module impressively makes easy work of any sized system.

Reduce development time & costs

This module does most of the heavy lifting for you behind the scenes. Work on building a configuration file, connecting signals, and you’re done!

fast & reliable

Experience the performance and power of a module written in SIMPL#. The SIMPL+ only acts as a translation layer for SIMPL Windows. This means there’s less of a bottleneck in terms of speed and reliability.

adaptability

Customer wants to add a source or a destination? Hardware wiring needs to change? No worries! Change the config file, add new virtual source routing logic and you’re done!

Automatic Routing

We’ve recently introduced a new addition to the module as of v3.0.0. By harnessing the functionality of connected graphing algorithms, we are now able to provide “automatic routing”. Define the rules by applying “weights” to output nodes within the configuration file, and let the algorithms do the rest of the work!

No more fiddling around with analogs and switching things manually. Simply specify a source and a destination routing key, and we’ll find a destination for any of your sources (as long as there is a path defined from source to definition of course).

Advanced Matrix Module v3.0.0 - Trial

FREE DOWNLOAD

Send download link to:

Overview
In the following sections, we will explore different techniques to expand and enhance the functionality of your SIMPL Windows programs by harnessing the capabilities of SIMPL#. SIMPL# Pro is not covered in this edition, although a lot of the core principles and code examples can still be translated over to SIMPL# Pro.

Prerequisites
It is assumed that you have the following (as a minimum set of requirements):

❖ Microsoft Visual Studio 2008 Pro (SP1)
❖ Crestron SIMPL# Plugin
❖ SIMPL Windows and SIMPL+ language proficiency

Note: A basic understanding of the C# language (syntax) and .NET compact framework is recommended, although not required. It should be noted that this paper is not an alternative to learning how to program in C#.

Type Alias Helpers

You can use the using directive outside (or within) namespace scope to make the type aliases in your
SIMPL# library project source files. A set of those using directives for SIMPL+ type aliasing has been defined below:

using STRING = System.String; // string = STRING
using SSTRING = Crestron.SimplSharp.SimplSharpString; // SimplSharpString = STRING
using INTEGER = System.UInt16; // ushort = INTEGER (unsigned)
using SIGNED_INTEGER = System.Int16; // short = SIGNED_INTEGER
using SIGNED_LONG_INTEGER = System.Int32; // int = SIGNED_LONG_INTEGER
using LONG_INTEGER = System.UInt32; // uint = LONG_INTEGER (unsigned)

Now you can use these as normal identifiers in your code (which are synonymous with their underlying type):

private INTEGER Initialize()
{
    // TODO: ...
}

(Note: In the example above, the INTEGER return value is interchangeable with ushort. The compiler does not identify INTEGER as a separate type, and both INTEGER and ushort can be used in the same manner for this reason.)

I typically avoid using these for any of my classes that don’t need to interface with SIMPL+. The purpose of these aliases is to make SIMPL+ API’s create more similarities in comparing your C# source code.

Compiler Directives & Build Configurations

Using the build configurations provided by default can be beneficial even if you don’t create your own. You’ll notice two that are available by default; Debug and Release. Both of these configurations also have a predefined set of defined compiler constants that you can use to include or omit sections of code in the compiled output. There are other benefits to understanding default build configurations. The Release build for example, enables compiler optimization whereas the Debug configuration does not.

Let’s talk about the compiler for a bit though. Have you ever considered the fact that there is a slight performance impact in having diagnostic code in your compiled dll’s (i.e. console output messages, logging mechanisms, etc)? Even if you enable/disable your debug messages and logging functionality at runtime, there is conditional logic which determines whether to call your debug functions that still need to be evaluated by the CPU. This can be mitigated by telling your compiler to omit this code entirely for a certain build configuration like the Release configuration using conditional directives.

public INTEGER Load(STRING configFilepath)
{
#if DEBUG
    CrestronConsole.PrintLine("Loading configuration file");
#endif
    try
    {
        if (!File.Exists(configFilepath))
        {
#if DEBUG
            CrestronConsole.PrintLine("File does not exist: {0}", configFilepath);
#endif
            return 0;
        }
        return 1;
    }
    catch (ConfigurationLoadException ex)
    {
#if DEBUG
        CrestronConsole.PrintLine("Failed to load configuration: {0}\n\tReason: {1}", ex.Filepath, ex.Reason);
#endif
    }
    return 0;
}

This may be undesirable if you are writing a module that you need to support though, since you might
still want to allow the end-user to turn on debug messages at runtime so that you can help them troubleshoot. This feature may prove useful to selectively omit more verbose debug calls however that aren’t meant to be shown in your production build, and to limit your debug output and/or logging to the end-user level debug output.

The above code looks somewhat ugly too, and if we ended up writing our entire library like this, readability would probably suffer. You could create a function that internally calls CrestronConsole.PrintLine() and have a single pair of #if DEBUG / #endif around that call within the function, although the function call which does nothing is still compiled into the assembly; there is still a better alternative. There is the ConditionalAttribute class, which is already provided by the .NET framework. Using custom attributes can be very useful in C#, and this one allows us to have regular function calls in code, but omit the call itself if the specified directive is not defined.

public const string DEBUG_FLAG_DIRECTIVE = "DEBUG";
[Conditional(DEBUG_FLAG_DIRECTIVE)]
public static void WriteLine(string message, params object[] args)
{
    CrestronConsole.PrintLine(message, args);
}

If we put this into a class named Debug, an example call would look something like this:

Debug.WriteLine("Configuration load exception: {0}", ex.Message);

No need for any of that conditional directive bloat either which increases the line count and negatively impacts readability! In this case this function call is only compiled if we’re running the assembly which was compiled under the Debug configuration, and not the Release configuration. More information can be found here: https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.conditionalattribute

Note: This is modern documentation, but the concept is still the same for us.

Cleaning Up the Public API

If you’re writing a library with more than a few classes, and you want to hide certain classes from the public API, you can mark things as internal. This accessibility modifier ensures that usage is still permitted from within the same assembly (or those external assemblies defined in the InternalsVisibleToAttribute definitions in the main assembly). Classes marked as internal hides them from the generated SIMPL+ API.

Passing Arrays to SIMPL+
You cannot return arrays directly back to SIMPL+ because there are no return types in SIMPL+ such as INTEGER_ARRAY_FUNCTION. You can return them via callbacks though. One somewhat “universal” solution involves utilizing inheritance and generics. You can even extend such functionality by expanding upon and deriving from the following class:

/// <summary>
/// Generic array wrapper class for SIMPL+ compatibility.
/// </summary>
/// <remarks>Inheritors of this type for typed array wrappers can be
/// passed back to SIMPL+ using callbacks but cannot be returned from
/// functions back to SIMPL+ for some reason.</remarks>
/// <typeparam name="T">Array element type</typeparam>
public abstract class CrestronArray<T>
{
    protected readonly T[] ObjArray;
    public T[] Elements { get { return ObjArray; } }
    public int Length { get { return ObjArray.Length; } }
    protected CrestronArray() { }
    protected CrestronArray(T[] array) { ObjArray = array; }
    public virtual int IndexOf(T item)
    {
        for (int i = 0; i < ObjArray.Length; i++)
        {
            if (ObjArray[i].Equals(item))
                return i;
        }
        return -1;
    }
}

This class is marked as abstract since it is not meant to be instantiated directly. It exposes an accessor for the Elements, as well as the Length of the encapsulated array. There is also a virtual function, IndexOf(), which is a useful function to have for all derived classes as a searching operation, but you might choose to add others depending on your requirements. Now for a derived class example:

/// <inheritdoc />
/// <summary>
/// String array for SIMPL+ compatibility.
/// </summary>
public sealed class StringArray : CrestronArray<string>
{
    [Obsolete("Provided only for S+ compatibility", false)]
    public StringArray() { }
    public StringArray(string[] array) : base(array) { }
}

Here we are implementing a string array wrapper class, and inheriting from the abstract generic class from above. For the parameterless constructor, we’ve marked it with the ObsoleteAttribute to help prevent unwanted usage from SIMPL# since we want to always have the object’s internal array properly initialized by only calling the constructor that passes an array of the specified type. The parameterless constructor is only necessary to keep the type compatible with SIMPL+. Generics are not directly supported in SIMPL+, which is the reason why we can’t just pass CrestronArray<string> directly back into the SIMPL+ layer. This solution works because SIMPL+ doesn’t care if the object inherits from a generic type. Usage in SIMPL+ is relatively straightforward:

a) Retrieve an element at index i

element = obj.Elements[i];

b) Get the array length

SIGNED_LONG_INTEGER n;
n = obj.Length;

c) Get the index of a particular element

SIGNED_LONG_INTEGER n;
n = obj.IndexOf("Name");

User-Interactive Callbacks
Say we had a module that needed to respond to a callback fired from SIMPL#. A typical example would be to acknowledge or decline a host key received during an SSH connection to a server. We could start the connection process from SIMPL+, but there’s no way to tell when we receive the host key other than an event in the SIMPL# SSH library that we can use to create a callback. To our benefit, SIMPL+ delegate callbacks can also be bi-directional. This would allow us to accept or decline the host key in response to the callback fired from SIMPL# (to proceed with, or abort the connection).

private bool OnValidateNewHostKey(byte[] key)
{
    var target = ValidateNewHostKey;
    if (target != null)
    {
        bool autoAcceptKey = target.Invoke(key.ToHexString()) != 0;
        if (!autoAcceptKey)
        {
            _hostKeyAcknowledgedEvent.Reset();
            if (!_hostKeyAcknowledgedEvent.Wait(10000))
                return false;
            return AcceptNewKey != 0;
        }
        return true;
    }
    return false;
}

In the code above, we’re using the callback to determine whether to auto-accept the key or wait for a manual trigger to check a flag that gets set to determine whether to accept or decline the new key. ValidateNewHostKey is the target callback that invokes the SIMPL+ callback, and this method is assigned to the event handler of the wrapper class I made for my SSH client which gets invoked by the event invocator in the code below (OnAcceptNewHostKey()).

– If the SIMPL+ callback returns TRUE (as a way to indicate that autoAcceptKey has been enabled on the SIMPL+ module, then we return true to accept the new key immediately.
– If the SIMPL+ callback returns FALSE (indicating that we aren’t automatically accepting new keys), then we wait to be signaled on the calling thread and check the AcceptNewKey property to see whether this new key should be accepted or not. The timeout on the Wait() method is there to allow the key to automatically be declined after a certain amount of time if the user hasn’t accepted or declined the key manually so we can free this thread from just sitting there doing nothing. (In this case we assume that the new host key cannot be trusted. If there is no user interaction, we default to declining the received host key.)

The signal to the waiting thread comes from the _hostKeyAcknowledgedEvent being Set() within the mutator on a property which is accessible from SIMPL+:

private INTEGER _acceptNewKey;
public INTEGER AcceptNewKey
{
    get { return _acceptNewKey; }
    set
    {
        _acceptNewKey = value;
        _hostKeyAcknowledgedEvent.Set();
    }
}

The following code is invoked from the SSH.NET library when we receive the host key from the server:

if (OnAcceptNewHostKey(e.FingerPrint))
{
    Debug.WriteInfo("Writing SSH key to file: '{0}'", filepath);
    using(var fs = new FileStream(filepath, FileMode.Create, FileAccess.Write))
    {
        fs.Write(string.Format("Fingerprint: {0}\nKey: {1}", fingerprint, key),
                 Encoding.UTF8);
    }
    e.CanTrust = true;
}
else
{
    e.CanTrust = false;
}

The corresponding SIMPL+ for the unique callback looks like this:

CALLBACK INTEGER_FUNCTION ValidateNewHostKeyHandler(STRING key)
{
    manual_host_key_validate_fb = FALSE;
    if (!auto_accept_new_keys)
    {
        // user must take action to validate received host key otherwise
        // the key will be automatically rejected after a timeout period.
        fingerprint$ = key;
        manual_host_key_validate_fb = TRUE;
        return (FALSE);
    }
    return (TRUE); // auto accept new host key
}

And the two SIMPL+ events that invoke to signal the thread are as follows:

PUSH accept_new_key
{
    ssh.AcceptNewKey = TRUE;
    fingerprint$ = "";
    manual_host_key_validate_fb = FALSE;
}
PUSH decline_new_key
{
    ssh.AcceptNewKey = FALSE;
    fingerprint$ = "";
    manual_host_key_validate_fb = FALSE;
}

manual_host_key_validate_fb is there as feedback to show a popup or something on the UI which allows the user to accept_new_key or decline_new_key respectively. In essence, it acts like our callback to SIMPL Windows, so the output signal can trigger conditional logic that tells the program that a new key needs to be either accepted or declined.

Inter-Symbol Communication (not Inter-Process Communication)
The most basic way is to use a static instance for talking between modules in SIMPL, without having to actually pass data back through SIMPL+. You can store all your information directly in the static instance, or implement a more advanced model like a publisher/subscriber implementation. In my case, I have a static publisher object that I can call upon to send notifications out to a defined set of subscribers, or globally, and this makes for a very dynamic but powerful solution over just a simple static class that stores all the information directly (although it definitely is a lot more work to set up so I won’t cover that here).

public class MyModule
{
    private static readonly Configuration _configuration = new Configuration("...");
    // ...
}

In the code above, we have a static readonly field that is implied to hold some kind of configuration. For every SIMPL+ module using this class, the important thing to remember here is that there is only a single instance, of which is shared across all other SIMPL+ modules. What you do with this is now up to your imagination. You can also create static events that will fire for every instance of your SIMPL+ module, it doesn’t necessarily need to be a static object to share information.

Note: SIMPL+ module events run from a threadpool, the other thing you have to keep in mind here is thread synchronization (which I’ll get to in the next example). Because this instance is shared, a lot more threads will be requesting access to this Configuration class, but threading is a problem we have to deal with regardless because of the way SIMPL+ was designed.

Thread Synchronization
For those unfamiliar with this concept, it is any mechanism that limits the number of threads that can access a shared resource at any given time. Unlike in SIMPL+ where you used a variable to restrict execution, SIMPL# has a lot more to offer.

Variable assignments and conditional checks are not really an acceptable way to deal with thread-safety in most contexts (including C#). In addition to having constructs that can block a thread for a timeout period, or indefinitely, there are also classes that allow you to implement thread synchronization in the following ways:

  1. Across program slots, or only within the same program.
  2. Events that get manually reset or reset automatically, which allow you to implement threads that must wait to be signaled. (This is very useful for initialization processes.)
  3. Semaphore’s which grant a specific number of threads access to a shared resource or pool of resources. (Note: This must be implemented or ported manually, or you can use one of the ports that others have provided to the programming community.)

The flexibility here is vastly greater than what is provided in SIMPL+, however, that doesn’t come without its set of responsibilities! You still must properly implement thread-safety, or you might find yourself spinning up threads in your modules that just sit there because you either forgot to release a lock. (You may have even created a deadlock!)

Note: It is important to avoid the lock statement as a means of thread synchronization. There is a known bug on the WinCE platform for 3-series regarding thread contention which may lead to a deadlock scenario.

Locks can also be expensive in some cases. If you are writing code that is performance critical, atomic instructions may be available to help you maintain the necessary performance, but keep your code thread-safe. One class that you can use to achieve this is the Interlocked class, which provides static methods to perform such atomic instructions to increment/decrement, exchange (conditionally and unconditionally), and more. (Some of the .NET BCL classes provide thread-safety entirely by using the Interlocked class for performance reasons, but this doesn’t mean get carried away with micro-optimization; only use it where it is necessary.) Some wrapper classes are also by default thread-safe for certain things, or fully thread-safe, so thread concurrency is already dealt with for you. In scope-based scenarios with things like CCricitalSection for example, you would call to Enter() to acquire the critical section lock, and Leave() to relinquish that lock which would allow another thread to obtain it. You should get into the habit of calling these functions within the try and finally branches of a try statement respectively. The reason for this is simply because even in the event of an exception that happens after the lock has been acquired, the finally block will ensure that the lock is released by executing Leave().

try
{
    criticalSection.Enter();
    // do work here
}
finally
{
    // this is called even if the work being done
    // after the Enter() call throws an exception.
    criticalSection.Leave();
}

IDisposable and the GC
You may have heard people talk about disposing objects via Dispose() explicitly or implicitly the using statement. The IDisposable interface is one that should be properly implemented to avoid problems down the road. For a managed code like .NET, we have what is called the garbage collector. The rules governing garbage collection can be a fairly lengthy topic so I will omit that discussion, perhaps until a very rainy day.

For simplicity, any object that implement the IDisposable interface will need to be disposed of accordingly in SIMPL#. If you create a wrapper that contains IDisposable members in your class, then you should also make your class implement IDisposable and create the proper dispose pattern to free your managed resource.

(There are other rules when you’re creating something designed to be inherited from, but again, I want to keep things simple for now.)

If P/Invoke was not hardcoded to be sandboxed for us, you would also have to do this for any unmanaged resources, but since it doesn’t apply to us (for 3-series) we’ll only be talking about managed resources. The idea is to ensure that you call Dispose() on any object that implements the IDisposable interface when you no longer need it so that unused resources can be freed up cleanly. Just like with thread synchronization objects, there is also a statement that is used to ensure Dispose() is called within a scope-based scenario, and that is the using statement.

using (var fs = File.OpenRead("file.txt"))
{
    // TODO: ...
}

There’s nothing special about this statement, it’s what we call syntactic sugar; specifically for a try-finally setup. The following is equivalent to what was written above:

{
    var fs = File.OpenRead("file.txt");
    try
    {
        // TODO: ...
    }
    finally
    {
        if (fs != null)
        ((IDisposable)fs).Dispose();
    }
}

The surrounding braces are there to define additional scope because just like in the using statement example above. The FileStream object is not accessible from outside of the statement’s scope. Failure to properly dispose of an object, especially if you’re creating many objects on the heap, will force the GC to do a clean sweep at some point to clean up all the garbage that you didn’t clean up properly. This is an expensive task, and negatively impacts performance across the entire program. This is also the reason why some developers implement object pools, especially in game development; sacrificing framerate is a sin.

Extension Methods
Extension methods can be useful. The syntax for calling an extension method is slightly different to a standard method call where you would just call the method and enclose all of the parameters within the parenthesis of the call. Extension methods are called as an extension of the first (this) parameter, hence the name “extension method.” Let’s show an example to demonstrate what I mean by that.

The following is a method I’ve used in some of my own libraries to ensure that I don’t pass a null string to a callback method for SIMPL+:

public static class Extensions
{
    public static SimplSharpString ToSimplSharpString(this string input)
    {
        return input ?? string.Empty;
    }
}

This method returns string.Empty if input is null, otherwise it returns the value of input if input is not null. Why do this? Because the regular implicit conversion operator for string to SimplSharpString will throw an exception if the string is null since it tries to calculate the length of the string. You can’t call upon the Length property if the string is null. The way you invoke an extension method is via the dot notation on the first parameter, negating the need to pass that variable in as a “normal” argument. See the following:

string data = "testing";
data.ToSimplSharpString();

Assembly Information & Reflection
I figured these topics kind of go hand in hand, especially with the example that I’ve prepared for this section, so I’ll talk with respect to both in this part. There is one particular file we’ll take a look at in this section as well, and that is the AssemblyInfo.cs file. If you take a look at your solution explorer, expand the project you’re working on, then open the “Properties” folder, the AssemblyInfo.cs file should be in there.

By default, that file should contain something like the following:

using System.Reflection;
[assembly: AssemblyTitle("MyProject")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("MyProject")]
[assembly: AssemblyCopyright("Copyright © 2019")]
[assembly: AssemblyVersion("1.0.0.*")]

These are the pieces of information that get compiled into your assembly after a compile in the metadata storage streams of the file.

(Note: The wildcard asterisk in the AssemblyVersion line allows for the assembly version to be incrementally changed automatically with every new build output.)

Did you notice the using directive for System.Reflection in the top line? Well we’re going to see how to extract this information at runtime using reflection, because assembly metadata and reflection are closely related. Reflection is feature provided by .NET to find out information about managed items, as well as the ability to dynamically create, and invoke types. It is useful, but it can also be relatively expensive to use in your program, so use it sparingly! The following is an example that will retrieve the assembly version as seen above (with the current numeric value of the wildcard) at runtime:

var asmName = Assembly.GetExecutingAssembly().GetName();
CrestronConsole.PrintLine(asmName.Version.ToString());

There are other properties aside from Version that can be retrieved from the asmName variable as well.

There’s no Company or Product property here though so the way to access those is to get the custom attributes for the assembly, and find the appropriate one:

Assembly asm = Assembly.GetExecutingAssembly();
object[] attributes = asm.GetCustomAttributes(typeof(AssemblyCompanyAttribute), false);
if (attributes.Length > 0)
    CrestronConsole.PrintLine(((AssemblyCompanyAttribute)attributes[0]).Company);

A few valid reasons why you might want this kind of information, would be licensing, logging, or perhaps even some kind of watermarking. Some obfuscators like Dotfuscator will do this.

Conclusion
The road to becoming proficient in SIMPL# is going to take some time. There are lots of resources available for .NET Compact Framework 3.5 and older C# 3.0 language version. As C# becomes increasingly more popular and the SIMPL# libraries mature, eventually .NET Compact Framework, Visual Studio 2008, older C# language and CLR versions will no longer be a limitation. At that time, the gap between modern C# tutorials and the relevant learning material for SIMPL# will be a lot smaller; making it easier to understand and use. Currently, there is no shortcut to learning how the Crestron libraries work though, and some of the wrapper classes have been modified in a way that MIcrosoft documentation incorrect for the equivalent SIMPL# classes provided by the SIMPL# libraries.

If you are aiming to use modern IDE’s for refactoring SIMPL# projects, or even building them, it is going to take some custom-built solution(s) to do this, and some modification to the modern environment in most cases, but it is possible. I personally use JetBrains Rider, and I know others have been able to use VS 2017. Building the project(s) still requires VS 2008 at this point because of the plugin, however, this can be automated if you know how to deal with COM, and can write your own software. That is a topic not within the scope of this paper however.

Resources

  1. Online SIMPL# Documentation: https://help.crestron.com/simpl_sharp/ (Note: you should also have a help file located within your filesystem.)
  2. Downloads for the SIMPL# plugin: https://crestron.com
  3. Microsoft Visual Studio 2008 Pro (SP1): https://microsoft.com (Note: You will need to obtain a license through a subscription or by some other means.)