I'm wrapping calls to Boost.Asio via Visual Studio C++ to access a serial port and deliver the data to a C# WPF app. At the end of this post is the code I'm using to build the DLL wrapper.
I compiled the Boost libraries [1.57.0] with VS targeting x64 and I'm using Windows 7.
.\b2 link=shared threading=multi address-model=64 stage
The code for the blocking read works, so I tried to improve it with a timeout/async read ability.
For direction I used postings from: asio::read with timeout
The errors stem from a call to async_read with bind results as one of the parameters. Originally, the errors started with:
Error: 'SerialPortInterface::set_result': function call missing argument list; use '&SerialPortInterface::set_result' to create a pointer to member.
So I made the change suggested by the above error message and now the first error is:
Error: 'F': must be a class or namespace when followed by '::'
located in boost's 'bind.hpp'. And that is followed by 75 error messages from the boost sources.
That is my first problem. Getting this to compile.
My second problem relates to my ignorance of boost and bind. Considering this statement:
boost::asio::async_read(serialPort, oneByteBuffer, boost::bind(&SerialPortInterface::set_result, &read_result, _1));
Can someone walk me through how the handler 'set_result' is called?
I've read http://www.boost.org/doc/libs/1_42_0/libs/bind/bind.html#Purpose and understand how this statement works:
bind(g, _1, 9, _1)(x); // equates to g(x, 9, x)
So the handler is called with the read_result reference for the 1st parameter but the 2nd parameter uses the placeholder to be filled in with... I'm lost here.
Cpp Code:
#include "stdafx.h"
#include "ImprovedAsioDLL.h"
#include <msclr/marshal.h>
using namespace msclr::interop;
using namespace ImprovedAsioDLL;
/***** Boost.Asio Native class *****************/
SerialPortInterface::SerialPortInterface(const char* portName): io(),
serialPort(io,portName), deadlineTimer(io)
{}
void SerialPortInterface::setOptions(int baud)
{
serialPort.set_option(boost::asio::serial_port_base::baud_rate(baud)); //115200
serialPort.set_option(boost::asio::serial_port_base::flow_control( boost::asio::serial_port_base::flow_control::none ));
serialPort.set_option(boost::asio::serial_port_base::parity ( boost::asio::serial_port_base::parity::none ));
serialPort.set_option(boost::asio::serial_port_base::stop_bits ( boost::asio::serial_port_base::stop_bits::one ));
serialPort.set_option(boost::asio::serial_port_base::character_size ( 8 ));
}
// Igor's version
void SerialPortInterface::set_result(boost::optional<boost::system::error_code>* a,
boost::system::error_code b)
{
a->reset(b);
}
unsigned char SerialPortInterface::readByte()
{
boost::optional<boost::system::error_code> timer_result;
deadlineTimer.expires_from_now(boost::posix_time::seconds(1));
// Igor's version
deadlineTimer.async_wait(boost::bind(&(SerialPortInterface::set_result),
&timer_result, _1));
boost::optional<boost::system::error_code> read_result;
// Igor's version
//boost::asio::async_read(serialPort, oneByteBuffer,
// boost::bind(set_result, &read_result, _1));
// Igor's version with compile error suggestion for 'set_result'
boost::asio::async_read(serialPort, oneByteBuffer,
boost::bind(&SerialPortInterface::set_result, &read_result, _1));
io.reset();
while (io.run_one())
{
if (read_result)
{
deadlineTimer.cancel();
// return oneByteBuffer[0] from here ?
}
else if (timer_result)
serialPort.cancel();
}
if (*read_result)
throw boost::system::system_error(*read_result);
return 'c'; // TODO - Placeholder - fix later
}
void SerialPortInterface::cancelAsyncOps()
{
serialPort.cancel();
}
/***** C++/CLI managed class *****************/
AsioInterface::AsioInterface(String ^portname)
{
try
{
marshal_context ^ context = gcnew marshal_context();
const char* str = context->marshal_as<const char*>(portname);
nativeClassPtr = new SerialPortInterface(str);
delete context;
}
catch(...)
{
throw; // rethrow
}
}
void AsioInterface::setOptions(int baud)
{
nativeClassPtr->setOptions(baud);
}
unsigned char AsioInterface::readByte()
{
// if (nativeClassPtr != nullptr) ??
return nativeClassPtr->readByte();
}
// dtor
AsioInterface::~AsioInterface()
{
this->!AsioInterface();
}
// finalizer
AsioInterface::!AsioInterface()
{
if (nativeClassPtr != nullptr)
{
nativeClassPtr->serialPort.close();
delete nativeClassPtr;
}
}
And its header:
#pragma once
using namespace System;
#define BOOST_ALL_DYN_LINK
//#include <boost/cstdint.hpp> // added to try to fix boost compile errors
#include <boost/asio.hpp> // include boost
#include <boost/asio/deadline_timer.hpp>
#include <boost/optional/optional.hpp>
#include <boost/system/error_code.hpp>
#include <boost/bind.hpp>
/***** Boost.Asio Native class *****************/
public struct SerialPortInterface
{
boost::asio::io_service io;
boost::asio::serial_port serialPort;
boost::asio::deadline_timer deadlineTimer;
unsigned char oneByteBuffer[1];
SerialPortInterface(const char* portName);
unsigned char readByte();
void instantiatePort(const char* portName);
void setOptions(int baud);
void cancelAsyncOps();
void set_result(boost::optional<boost::system::error_code>* a, boost::system::error_code b);
void SerialPortInterface::handle_read ( const boost::system::error_code& ec);
};
/***** C++/CLI managed class *****************/
namespace ImprovedAsioDLL {
public ref class AsioInterface
{
SerialPortInterface *nativeClassPtr;
int i;
public:
AsioInterface(String ^portname);
void setOptions(int baud);
unsigned char readByte();
private:
~AsioInterface();
!AsioInterface();
};
}
/*
* C# code snippets using this DLL
*
* string portName = "COM22";
* ImprovedAsioDLL.AsioInterface serialPort = new ImprovedAsioDLL.AsioInterface(portName);
* this.serialPort.setOptions(115200);
* byte chr = this.serialPort.readByte();
*
*/