2

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();
 *
 */
Community
  • 1
  • 1
Skippy VonDrake
  • 806
  • 8
  • 21
  • The correct syntax for taking the address of a member function is `&SerialPortInterface::set_result`, so the change you've made should be correct. Are you saying it still doesn't work? The second error message implies that your real code says `F::` somewhere, but the posted code doesn't. – Mike Seymour Feb 11 '15 at 17:09
  • And the placeholder is for a function argument provided by the library when it calls the completion handler. You'll get it as the second parameter of your handler function. – Mike Seymour Feb 11 '15 at 17:10
  • After making the change and recompiling the first error went away to be replaced by the "F::" error. The source of this error is boost's header "bind.hpp". – Skippy VonDrake Feb 11 '15 at 17:14
  • Note that `async_read` expects a [handler that accepts 2 args](http://www.boost.org/doc/libs/1_57_0/doc/html/boost_asio/reference/ReadHandler.html). So add `std::size_t` arg to `read_result`, and `_2` to the binder. – Igor R. Feb 11 '15 at 17:56
  • Igor, apologies if I've twisted your code into something non-functional. But do you mean add the arg to "set_result" and not "read_result"? – Skippy VonDrake Feb 11 '15 at 18:12

2 Answers2

0

You're using C++/cli

C++/CLI is not C++

Specifically: SerialPortInterface is a CLR ValueType. boost::bind doesn't work for those. You could create a static (native) helper function that gets bound with an extra parameter of gcroot<SerialPortInterface> to replace the this reference.

See e.g.

Pro tip: Hermetically separate your native and managed code even in mixed-mode assemblies. It will help you retain your sanity.

Community
  • 1
  • 1
sehe
  • 374,641
  • 47
  • 450
  • 633
0

Thanks for all the good advice. I wound up starting over and used asio code from [http://www.ridgesolutions.ie/index.php/2012/12/13/boost-c-read-from-serial-port-with-timeout-example/][1]

It's still pretty rough but working. Posting it on the small chance that someone else may be looking to do the same.

// ReadTimeOut_CLR_exe.cpp : main project file.

#include "stdafx.h"

// #pragma unmanaged
// push managed state on to stack and set unmanaged state
#pragma managed(push, off)

#include <boost/asio/serial_port.hpp> 
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

// #pragma managed
#pragma managed(pop)

using namespace System;
using namespace System::Runtime::InteropServices;

// #pragma unmanaged
// push managed state on to stack and set unmanaged state
#pragma managed(push, off)

class BlockingReader
{

public: 
    BlockingReader(const char *name, size_t timeout);
    bool read_char(char* val);

private:    
    boost::asio::io_service io;
    boost::asio::serial_port port;
    size_t timeout; 
    boost::asio::deadline_timer timer;
    bool read_error;
    char c;

    void time_out(const boost::system::error_code& error);
    void read_complete(const boost::system::error_code& error,
                    size_t bytes_transferred);

};

BlockingReader::BlockingReader(const char *name, size_t timeout) :
    io(), port(io, name), timeout(timeout),
                                timer(io),
                                read_error(true)
{
    port.set_option(boost::asio::serial_port_base::baud_rate(115200));
}

// Reads a character or times out
bool BlockingReader::read_char(char* val) 
{       
    *val = c = '\0';

    // After a timeout & cancel it seems we need
    // to do a reset for subsequent reads to work.
    port.get_io_service().reset();

    // Asynchronously read 1 character.
    boost::asio::async_read(port, boost::asio::buffer(&c, 1), 
            boost::bind(&BlockingReader::read_complete, 
                    this, 
                    boost::asio::placeholders::error, 
                    boost::asio::placeholders::bytes_transferred)); 

    // Setup a deadline time to implement our timeout.
    timer.expires_from_now(boost::posix_time::milliseconds(timeout));
    timer.async_wait(boost::bind(&BlockingReader::time_out,
                            this, boost::asio::placeholders::error));

    // This will block until a character is read
    // or until the it is cancelled.
    port.get_io_service().run();

    if (!read_error)
    {
        *val = c;
    }

    return !read_error;
}

// Called when the timer's deadline expires.
void BlockingReader::time_out(const boost::system::error_code& error) {

    // Was the timeout was cancelled?
    if (error) {
        // yes
        return;
    }

    // no, we have timed out, so kill the read operation
    // The read callback will be called with an error
    port.cancel();
}

// Called when an async read completes or has been cancelled
void BlockingReader::read_complete(const boost::system::error_code& error,
                    size_t bytes_transferred) {     

    read_error = (error || bytes_transferred == 0);

    // Read has finished, so cancel the timer.
    timer.cancel();
}

// #pragma managed
#pragma managed(pop)

public ref struct MyManagedClass {
    BlockingReader *nativeClassPtr;


public:
    MyManagedClass(System::String ^portName, int timeoutMillis)
    {

        // Marshal the managed string to unmanaged memory. 
        char* stringPointer = (char*) Marshal::StringToHGlobalAnsi(portName).ToPointer();

        nativeClassPtr = new BlockingReader(stringPointer, timeoutMillis);

        // Always free the unmanaged string.
        Marshal::FreeHGlobal(IntPtr(stringPointer));
    }

    ~MyManagedClass() { this->!MyManagedClass(); }
    !MyManagedClass() { delete nativeClassPtr; }

    bool readSerialPortByte(System::Byte^ bytePtrHandle)
    {
        pin_ptr<Byte> dontMove = &*bytePtrHandle; 
        unsigned char* ptrByte = dontMove;
        char* ptrByteCast = reinterpret_cast<char*>(ptrByte);
        return this->nativeClassPtr->read_char(ptrByteCast);
    }
};

int main(array<System::String ^> ^args)
{
    Console::WriteLine(L"Testing COM Port 22");
    MyManagedClass myClass("COM22", 500);
    Byte^ oneChar = (Byte)1;

    for (int i=0;i<10;i++)
    {       
        if (myClass.readSerialPortByte(oneChar))
        {
            std::cout << "got byte" << *oneChar << std::endl;
        }
        else
        {
            std::cout << "timed out" << std::endl;
        }
    }
    return 0;
}
Skippy VonDrake
  • 806
  • 8
  • 21