LCM
.NET Tutorial

An example use case in C#.NET

Introduction

This tutorial will guide you through the basics of using LCM .NET port. As the .NET port is basically a transcription of the original Java library, it tries to be functionally equivalent while maintaining C#.NET naming conventions and other platform specifics. All sample code is written in C# (as well as the port itself), but the principles are applicable to any of the languages supported by the .NET Framework.

The tutorial doesn't cover the very basics of the LCM (message transmision principles, message definition format etc.) - please see the rest of the documentation before further reading.

C#.NET-specific message files

To demonstrate basic functionality, this tutorial will use the same message format and application logic as the Java Tutorial to accent similarities and differences between Java and .NET ports. Let's have the following type specification, saved to a file named temperature_t.lcm:

1 package exlcm;
2 
3 struct example_t
4 {
5  int64_t timestamp;
6  double position[3];
7  double orientation[4];
8  int32_t num_ranges;
9  int16_t ranges[num_ranges];
10  string name;
11  boolean enabled;
12 }

In order to obtain C#.NET-specific handler class, we need to call lcm-gen with the –csharp flag:

1 lcm-gen --csharp example_t.lcm

Besides, the lcm-gen utility accepts the following .NET-specific options:

OptionDefault valueDescription
–csharp-pathC#.NET file destination directory
–csharp-mkdir1Make C#.NET source directories automatically
–csharp-strip-dirs0Do not generate folders for default and root namespace - unlike Java sources, the .NET source files' directory structure does not have to be analogic to their namespace structure. It is often advantageous to omit the top-most directories common to all generated files to simplify the directory layout.
–csharp-decl: LCM.LCM.LCMEncodableString added to class declarations - similar to Java option –jdecl
–csharp-root-nspRoot C#.NET namespace (wrapper) added before LCM package name - this comes handy when you want to place generated .NET bindings into a specific part of your .NET namespace hierarchy and do not want to affect other languages by embedding the wrapper namespace directly into the source .lcm files
–csharp-default-nspLCMTypesDefault NET namespace if the LCM type has no package defined

As with all tutorials, we will publish and subscribe to the "EXAMPLE" channel.

Initializing LCM

There are at least two ways how to use the .NET port of LCM:

Main classes of the library are put in the LCM.LCM namespace (while helper code is in LCM.Util). This results in quite funny fully qualified name of the master class - LCM.LCM.LCM (its constructor is even funnier - LCM.LCM.LCM.LCM() :-) ). It's logical to use the 'using LCM.LCM' statement to shorten calls to the library, but the naming scheme (chosen to agree with the Java variant) makes it a little difficult - you cannot use bare 'LCM' as class name - the compiler considers it to be the namespace. Instead, you need to write LCM.LCM to denote the main class.

Generated message handlers are placed in the LCMTypes namespace by default (you can change this by specifying lcm-gen option –csharp-default-nsp).

LCM itself has a mechanism to maintain single instance of the main class - static property LCM.Singleton:

1 LCM.LCM myLCM = LCM.LCM.Singleton;

You can also instantiate the class and take care of the object's singularity by yourself:

1 LCM.LCM myLCM = new LCM.LCM();

In situations where the default connection URL (fetched by LCM from the environment variable LCM_DEFAULT_URL or defined by the constant "udpm://239.255.76.67:7667" when the former is empty) is not suitable, the constructor can accept variable number of parameters specifying individual connection strings.

For detailed information on the LCM .NET API please see the .NET API reference.

Publishing a message

In order to use LCM types, you can either build an assembly containing generated classes (needed when using LCM from other .NET language then C#), or include the classes directly to application project. Utilization of the generated classes is then fairly straightforward:

1 exlcm.example_t msg = new exlcm.example_t();
2 TimeSpan span = DateTime.Now - new DateTime(1970, 1, 1);
3 msg.timestamp = span.Ticks * 100;
4 msg.position = new double[] { 1, 2, 3 };
5 msg.orientation = new double[] { 1, 0, 0, 0 };
6 msg.ranges = new short[] { 0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 };
7 msg.num_ranges = msg.ranges.Length;
8 msg.name = "example string";
9 msg.enabled = true;
10 
11 myLCM.Publish("EXAMPLE", msg);

The data are simply assigned to appropriate fields inside the message container. Passing the message object to the Publish method of the LCM object places it to specified channel of the communication bus (channel "EXAMPLE" here).

Subscribing to messages

In order to receive messages, you have two options:

This tutorial exploits the former option - the class SimpleSubscriber is defined as internal inside the demo application class:

1 internal class SimpleSubscriber : LCM.LCMSubscriber
2 {
3  public void MessageReceived(LCM.LCM lcm, string channel, LCM.LCMDataInputStream dins)
4  {
5  Console.WriteLine("RECV: " + channel);
6 
7  if (channel == "EXAMPLE")
8  {
9  exlcm.example_t msg = new exlcm.example_t(dins);
10 
11  Console.WriteLine("Received message of the type example_t:");
12  Console.WriteLine(" timestamp = {0:D}", msg.timestamp);
13  Console.WriteLine(" position = ({0:N}, {1:N}, {2:N})",
14  msg.position[0], msg.position[1], msg.position[2]);
15  Console.WriteLine(" orientation = ({0:N}, {1:N}, {2:N}, {3:N})",
16  msg.orientation[0], msg.orientation[1], msg.orientation[2],
17  msg.orientation[3]);
18  Console.Write(" ranges = [ ");
19  for (int i = 0; i < msg.num_ranges; i++)
20  {
21  Console.Write(" {0:D}", msg.ranges[i]);
22  if (i < msg.num_ranges-1)
23  Console.Write(", ");
24  }
25  Console.WriteLine(" ]");
26  Console.WriteLine(" name = '" + msg.name + "'");
27  Console.WriteLine(" enabled = '" + msg.enabled + "'");
28  }
29  }
30 }

The class instance is then passed to LCM method SubscribeAll that passes all received messages to our subscriber class. When selective subscription is needed (i.e. in almost all real-world cases as we usually don't to listen to all channels), method Subscribe that takes the channel name pattern as an argument is to be used.

1 myLCM.SubscribeAll(new SimpleSubscriber());

Putting it all together

Distribution of the LCM library includes a directory of examples. One of them is a couple of programs implementing all described features. Please go to examples/csharp/ to find Visual Studio solution ready to be built.

The complete example transmitter application:

1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using LCM;
6 
7 namespace LCM.Examples
8 {
9  /// <summary>
10  /// Demo transmitter, see LCM .NET tutorial for more information
11  /// </summary>
12  class ExampleTransmit
13  {
14  public static void Main(string[] args)
15  {
16  try
17  {
18  LCM.LCM myLCM = LCM.LCM.Singleton;
19 
20  exlcm.example_t msg = new exlcm.example_t();
21  TimeSpan span = DateTime.Now - new DateTime(1970, 1, 1);
22  msg.timestamp = span.Ticks * 100;
23  msg.position = new double[] { 1, 2, 3 };
24  msg.orientation = new double[] { 1, 0, 0, 0 };
25  msg.ranges = new short[] { 0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 };
26  msg.num_ranges = msg.ranges.Length;
27  msg.name = "example string";
28  msg.enabled = true;
29 
30  myLCM.Publish("EXAMPLE", msg);
31  }
32  catch (Exception ex)
33  {
34  Console.Error.WriteLine("Ex: " + ex);
35  }
36  }
37  }
38 }

The complete example receiver application:

1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using LCM;
6 
7 namespace LCM.Examples
8 {
9  /// <summary>
10  /// Demo listener, demonstrating interoperability with other implementations
11  /// Just run this listener and use any of the example_t message senders
12  /// </summary>
13  class ExampleDisplay
14  {
15  public static void Main(string[] args)
16  {
17  LCM.LCM myLCM;
18 
19  try
20  {
21  myLCM = new LCM.LCM();
22 
23  myLCM.SubscribeAll(new SimpleSubscriber());
24 
25  while (true)
26  System.Threading.Thread.Sleep(1000);
27  }
28  catch (Exception ex)
29  {
30  Console.Error.WriteLine("Ex: " + ex);
31  Environment.Exit(1);
32  }
33  }
34 
35  internal class SimpleSubscriber : LCM.LCMSubscriber
36  {
37  public void MessageReceived(LCM.LCM lcm, string channel, LCM.LCMDataInputStream dins)
38  {
39  Console.WriteLine("RECV: " + channel);
40 
41  if (channel == "EXAMPLE")
42  {
43  exlcm.example_t msg = new exlcm.example_t(dins);
44 
45  Console.WriteLine("Received message of the type example_t:");
46  Console.WriteLine(" timestamp = {0:D}", msg.timestamp);
47  Console.WriteLine(" position = ({0:N}, {1:N}, {2:N})",
48  msg.position[0], msg.position[1], msg.position[2]);
49  Console.WriteLine(" orientation = ({0:N}, {1:N}, {2:N}, {3:N})",
50  msg.orientation[0], msg.orientation[1], msg.orientation[2],
51  msg.orientation[3]);
52  Console.Write(" ranges = [ ");
53  for (int i = 0; i < msg.num_ranges; i++)
54  {
55  Console.Write(" {0:D}", msg.ranges[i]);
56  if (i < msg.num_ranges-1)
57  Console.Write(", ");
58  }
59  Console.WriteLine(" ]");
60  Console.WriteLine(" name = '" + msg.name + "'");
61  Console.WriteLine(" enabled = '" + msg.enabled + "'");
62  }
63  }
64  }
65  }
66 }

Conclusion

The tutorial has provided a basic working demonstration of the LCM library .NET port. For further information, please see the LCM documentation.