1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324
|
.NET Tutorial {#tut_dotnet}
====
\brief An example use case in C#.NET
# Introduction {#tut_dotnet_intro}
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.
\section Generating C#.NET-specific message files
To demonstrate basic functionality, this tutorial will use the same message
format and application logic as the \ref tut_java to accent similarities and
differences between Java and .NET ports. Let's have the following type
specification, saved to a file named \p temperature_t.lcm:
\code
package exlcm;
struct example_t
{
int64_t timestamp;
double position[3];
double orientation[4];
int32_t num_ranges;
int16_t ranges[num_ranges];
string name;
boolean enabled;
}
\endcode
In order to obtain C#.NET-specific handler class, we need to call \p lcm-gen with
the \p --csharp flag:
\code
lcm-gen --csharp example_t.lcm
\endcode
Besides, the \p lcm-gen utility accepts the following .NET-specific options:
<table>
<tr><th>Option</th><th>Default value</th><th>Description</th></tr>
<tr><td>--csharp-path</td><td></td><td>C#.NET file destination
directory</td></tr>
<tr><td>--csharp-mkdir</td><td>1</td><td>Make C#.NET source
directories automatically</td></tr>
<tr><td>--csharp-strip-dirs</td><td>0</td><td>Do 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.</td></tr>
<tr><td>--csharp-decl</td><td>: LCM.LCM.LCMEncodable</td><td>String
added to class declarations - similar to Java option \p --jdecl</td></tr>
<tr><td>--csharp-root-nsp</td><td></td><td>Root 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 \p .lcm files</td></tr>
<tr><td>--csharp-default-nsp</td><td>LCMTypes</td><td>Default NET
namespace if the LCM type has no package defined</td></tr>
</table>
As with all tutorials, we will publish and subscribe to the "EXAMPLE" channel.
# Initializing LCM {#tut_dotnet_initialize}
There are at least two ways how to use the .NET port of LCM:
\li if you don't want to modify the library and just use it, the simplest way
is to build the library and copy resulting lcm.dll to your application; you
then need to add a reference to it (Project -> Add Reference... -> Browse) and
you are ready to start communicating!
\li if you plan to do some changes to library source code, the recommended way
is to add library Visual Studio project to your solution and reference it
(Project -> Add Reference... -> Projects)
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 \p '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 \p LCMTypes namespace by default
(you can change this by specifying lcm-gen option \p --csharp-default-nsp).
LCM itself has a mechanism to maintain single instance of the main class -
static property LCM.Singleton:
\code
LCM.LCM myLCM = LCM.LCM.Singleton;
\endcode
You can also instantiate the class and take care of the object's singularity
by yourself:
\code
LCM.LCM myLCM = new LCM.LCM();
\endcode
In situations where the default connection URL (fetched by LCM from the
environment variable \p LCM_DEFAULT_URL or defined by the constant
\p "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](lcm-dotnet/index.html).
# Publishing a message {#tut_dotnet_publish}
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:
\code
exlcm.example_t msg = new exlcm.example_t();
TimeSpan span = DateTime.Now - new DateTime(1970, 1, 1);
msg.timestamp = span.Ticks * 100;
msg.position = new double[] { 1, 2, 3 };
msg.orientation = new double[] { 1, 0, 0, 0 };
msg.ranges = new short[] { 0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 };
msg.num_ranges = msg.ranges.Length;
msg.name = "example string";
msg.enabled = true;
myLCM.Publish("EXAMPLE", msg);
\endcode
The data are simply assigned to appropriate fields inside the message container.
Passing the message object to the \p Publish method of the LCM object places
it to specified channel of the communication bus (channel "EXAMPLE" here).
# Subscribing to messages {#tut_dotnet_subscribe}
In order to receive messages, you have two options:
\li write a class implementing the LCMSubscriber interface (just one
handler-method MessageReceived) to assynchronously handle incoming
messages
\li use standard class MessageAggregator (that internally implements
LCMSubscriber interface) for synchronous blocking or not blocking
message delivery
This tutorial exploits the former option - the class \p SimpleSubscriber is defined
as internal inside the demo application class:
\code
internal class SimpleSubscriber : LCM.LCMSubscriber
{
public void MessageReceived(LCM.LCM lcm, string channel, LCM.LCMDataInputStream dins)
{
Console.WriteLine("RECV: " + channel);
if (channel == "EXAMPLE")
{
exlcm.example_t msg = new exlcm.example_t(dins);
Console.WriteLine("Received message of the type example_t:");
Console.WriteLine(" timestamp = {0:D}", msg.timestamp);
Console.WriteLine(" position = ({0:N}, {1:N}, {2:N})",
msg.position[0], msg.position[1], msg.position[2]);
Console.WriteLine(" orientation = ({0:N}, {1:N}, {2:N}, {3:N})",
msg.orientation[0], msg.orientation[1], msg.orientation[2],
msg.orientation[3]);
Console.Write(" ranges = [ ");
for (int i = 0; i < msg.num_ranges; i++)
{
Console.Write(" {0:D}", msg.ranges[i]);
if (i < msg.num_ranges-1)
Console.Write(", ");
}
Console.WriteLine(" ]");
Console.WriteLine(" name = '" + msg.name + "'");
Console.WriteLine(" enabled = '" + msg.enabled + "'");
}
}
}
\endcode
The class instance is then passed to LCM method \p 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 \p Subscribe that takes the channel name pattern as an argument is to
be used.
\code
myLCM.SubscribeAll(new SimpleSubscriber());
\endcode
# Putting it all together {#tut_dotnet_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
\p examples/csharp/ to find Visual Studio solution ready to be built.
The complete example transmitter application:
\code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LCM;
namespace LCM.Examples
{
/// <summary>
/// Demo transmitter, see LCM .NET tutorial for more information
/// </summary>
class ExampleTransmit
{
public static void Main(string[] args)
{
try
{
LCM.LCM myLCM = LCM.LCM.Singleton;
exlcm.example_t msg = new exlcm.example_t();
TimeSpan span = DateTime.Now - new DateTime(1970, 1, 1);
msg.timestamp = span.Ticks * 100;
msg.position = new double[] { 1, 2, 3 };
msg.orientation = new double[] { 1, 0, 0, 0 };
msg.ranges = new short[] { 0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 };
msg.num_ranges = msg.ranges.Length;
msg.name = "example string";
msg.enabled = true;
myLCM.Publish("EXAMPLE", msg);
}
catch (Exception ex)
{
Console.Error.WriteLine("Ex: " + ex);
}
}
}
}
\endcode
The complete example receiver application:
\code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LCM;
namespace LCM.Examples
{
/// <summary>
/// Demo listener, demonstrating interoperability with other implementations
/// Just run this listener and use any of the example_t message senders
/// </summary>
class ExampleDisplay
{
public static void Main(string[] args)
{
LCM.LCM myLCM;
try
{
myLCM = new LCM.LCM();
myLCM.SubscribeAll(new SimpleSubscriber());
while (true)
System.Threading.Thread.Sleep(1000);
}
catch (Exception ex)
{
Console.Error.WriteLine("Ex: " + ex);
Environment.Exit(1);
}
}
internal class SimpleSubscriber : LCM.LCMSubscriber
{
public void MessageReceived(LCM.LCM lcm, string channel, LCM.LCMDataInputStream dins)
{
Console.WriteLine("RECV: " + channel);
if (channel == "EXAMPLE")
{
exlcm.example_t msg = new exlcm.example_t(dins);
Console.WriteLine("Received message of the type example_t:");
Console.WriteLine(" timestamp = {0:D}", msg.timestamp);
Console.WriteLine(" position = ({0:N}, {1:N}, {2:N})",
msg.position[0], msg.position[1], msg.position[2]);
Console.WriteLine(" orientation = ({0:N}, {1:N}, {2:N}, {3:N})",
msg.orientation[0], msg.orientation[1], msg.orientation[2],
msg.orientation[3]);
Console.Write(" ranges = [ ");
for (int i = 0; i < msg.num_ranges; i++)
{
Console.Write(" {0:D}", msg.ranges[i]);
if (i < msg.num_ranges-1)
Console.Write(", ");
}
Console.WriteLine(" ]");
Console.WriteLine(" name = '" + msg.name + "'");
Console.WriteLine(" enabled = '" + msg.enabled + "'");
}
}
}
}
}
\endcode
# Conclusion {#tut_dotnet_conclusion}
The tutorial has provided a basic working demonstration of the LCM library
.NET port. For further information, please see the LCM documentation.
|