Or why there is no SerialPort in C++
Developing code in C++ for robotics, I often faced the problem of communicating via serial port with a robot, a sensor or any other device. C and C++ are languages supposedly very close to hardware. Furthermore, they are the most common and oldest mainstream programming languages out there. So communicating over a serial port in a portable way should be straightforward.
<!--more-->
Probably as straightforward as it is in python. To send a few bytes, with just a bare python installation, all you have to do (in any OS) is:
$ pip install pyserial
this downloads and sets up a dozen files, just a few KBs. Then, write your code:
import serial
ser = serial.Serial(0)
ser.write("hello")
ser.close()
And that’s it!
Lets do it now in C++. Suppose you want to write a similar application just to send some bytes over the serial port. The first difficulty is that seems there’s nothing as “standard” or widely accepted, so my first attempt was to use
MRPT. MRPT is a robotics library plenty of great drivers, algorithms, tools, interfaces… an amazing and very popular tool kit for robotics. I could use the whole MRPT, but lets say I am not willing to introduce a dependency to it, it is very large (60Mb of source code, >5k source files) and contains tons of stuff I do not need. I want to get just the SerialPort functionality. Going down to the code, you can find a
CSerialPort_win.cpp
and a
CSerialPort_lin.cpp
files, both implementing the same CSerialPort class, declared in
CSerialPort.h
header file. In the implementation file you can find:
void CSerialPort::open( )
{
MRPT_START
…
if ( INVALID_HANDLE_VALUE == (
hCOM = CreateFileA( m_serialName.c_str(),
And in the header:
class HWDRIVERS_IMPEXP CSerialPort : public CStream
{
friend class PosixSignalDispatcherImpl;
public:
That's it, the implementation relies on a macro MRPT_START (it allows profiling and exception handling), which seems could be easily removed, but the header inherits from CStream, a base class for streams (network, files, output, etc), which in turn depends on a serialization framework (CSerializable, CObject).
While the overall software design of the library is great, it is very difficult to isolate what, in my opinion, should be a very independent and low coupled component. Please, do not misunderstand my opinion, MRPT is really great. The problem is simply that the library was not designed with that goal in mind.
My guess was that something more generic, not tied to robotics, solution already existed. So I googled for it a little and it seemed that the most accepted solution (at least in stackoverflow) was using
boost::asio
. It seemed reasonable to me that depending on 500 Mb of source code is probably overkill for sending some bytes, so I also tried to extract the serial port functionality. But I found some high levels of abstractions over it, for example, in the file
basic_serial_port.hpp
:
template <typename SerialPortService = serial_port_service>
class basic_serial_port
: public basic_io_object<SerialPortService>,
public serial_port_base
{
public:
You have to dig deeply into
win_iocp_serial_port_service.ipp
to actually find the code that opens the port in Win:
boost::system::error_code win_iocp_serial_port_service::open(
win_iocp_serial_port_service::implementation_type& impl,
const std::string& device, boost::system::error_code& ec)
{
if (is_open(impl))
{
ec = boost::asio::error::already_open;
return ec;
}
::HANDLE handle = ::CreateFileA(name.c_str()
Surprisingly, the functionality is spread between different files, for example, the latter contains code for opening, closing and setting parameters, but the code for sending and receiving bytes is located in another file.
Basically the conclusion is that you cannot use the serial port separately, you are forced to use it with Asio. We’re facing the same problem again: boost is really amazing and asio is also brilliant. And surely their serial port implementations (both MRPT and boost::asio) are more configurable and powerful than the python one is. I personally wish someday I could write code half as good as the one in any of those projects. But I see a pattern here, one, that doesn't allow to have a small and simple SerialPort functionality to just synchronously send and receive some bytes over the serial port. So I dare to state what IMHO could be considered as a design principle:
Let me use your functionality without using your design
Of course, this principle is nothing new. It is very related to many well known principles and patterns. It is very related to the trending functional programming approach, and could be considered as a consequence of many patterns as single responsibility, low coupling, separation of concerns, high cohesion, etc. But I have never seen it stated as above, and I think it could be another perspective to be taken into account. By "without using your design" I mean architectural design, obviously every single line of code has some design in it.
How do I think the SerialPort example should be addressed? The main problem I see with it, is the failure to identify SerialPort as a first level building block, and thus it deserves its own “package”, namespace, library… you named it. With that in mind, it could be very easy to build it in those libraries. For example, in the MRPT case, you can have both the SerialPort and a streams and serialization framework independent from each other. Then, bind both things very easily with templates (very simplified for clarity, just an idea):
class SerialPort{
public:
void write(char c);
};
class MyBaseStream{
virtual void write(std::string str)=0;
friend MyBaseStream& operator <<(MyBaseStream& mystream, std::string str){
mystream.write(str);
return mystream;
}
};
template <typename T>
class Stream: public MyBaseStream{
public:
Stream(T& _port): port(_port){}
virtual void write(std::string str){
for(auto c: str)
port.write(c);
}
private:
T port;
};
void write2stream(MyBaseStream& stream, std::string str){
stream<<str;
}
using SerialPortStream = Stream<SerialPort>;
int main(){
SerialPort serial;
SerialPortStream serial_stream(serial);
serial_stream<<"Hello World\n";
write2stream(serial_stream, "Good Bye\n");
}
In this way, both the SerialPort and the Stream classes become two independent first class citizens in our project. It becomes very easy to test (and mock), understand, maintain and extend them, and it is also simpler to grow and scale the whole project using them. I know I am not saying something very new with this software design proposal, for example it is similar to Alexandrescu’s policy based design, though the final goal could be different.
Failures to follow this principle are more common in C/C++
I have seen this pattern a few times in C and C++ projects, but very rarely in other languages (at least those that I have used more as java and python) and I believe there is a reason, not directly related to software design for it: the lack of a widely used dependency manager. And no, OS package managers, installers and so, are not enough to solve this problem. Even if the authors of these libraries decide to decouple the functionality of the SerialPort basic wrapper in their design, there is little gain in it, users should still manually extract those files and integrate them in their projects, which doesn't sound as reasonable engineering and will produce, for sure, maintenance problems and lack of updates. It is really unlikely that the authors will decide to create a separate project/library for the SerialPort, it is more effort not only to do it in the short term, but also to maintain and work with it in the mid and long terms. So developers just roll out their designs, and fill the functionality in it as required. I’ve done it so many times too.
On the contrary, if a dependency manager existed, it is very likely that a simple, independent and robust SerialPort implementation would emerge and be widely adopted, and people creating asynchronous communication frameworks or robotics applications would use it and have less code to write.