On 2015-06-26 01:33, Claudiu Olteanu wrote:
I attached some patches which can be used to make the *libdivecomputer *
project to accept callbacks to custom functions used for serial
communication.

As I mentioned in my weekly report, I am not sure if I made the best
decisions on the implementation.

I didn't have time yet to go through all the bluetooth related emails and do a detailed review of your work so far. Instead, I'll describe the architectural changes that I had already planned for integrating my bluetooth prototype. Conceptually there is really no difference with the changes that are needed for your custom I/O backend.

There are three main steps:

1. Abstract serial interface

The very first step will be to change the serial communication layer from a concrete implementation into a abstract interface. This will be very similar to how the device and parser layer work. So if you wonder how something should be done, just replace "serial" with "device" or "parser" and look how it's done there.

We'll need three files here:

 * include/libdivecomputer/serial.h
 * src/serial-private.h
 * src/serial.c

The first one is the public interface. This is basically the existing src/serial.h file, but everything gets an extra "dc_" prefix. There is no need for the dc_serial_open() function, because each backend will have to implement its own, and may need completely different parameters. (If you're familiar with C++, a virtual constructor is not possible.)

(Note that the serial api will need some additional rework too in order to become public, but let's ignore that for now.)

In the serial-private.h file, you define the base structure that is shared by all implementations:

struct dc_serial_t {
    const dc_serial_vtable_t *vtable;
    dc_context_t *context;
};

and the virtual function table containing one function pointer for each public method:

struct dc_serial_vtable_t {
    int (*close) (dc_serial_t *serial);
    ...
};

In the serial.c file, you implement each function by calling the corresponding function in the vtable.


Next, we need to modify the existing serial code, and turn it into an implementation of our new interface. To do this, you add the new dc_serial_t struct as the first member of the existing struct:

struct serial_t {
    dc_serial_t base;
    ...
};

And setup a vtable:

static const dc_serial_vtable_t serial_vtable = {
    serial_close, /* close */
    ...
};

Of course you'll need a bit more glue code here and there, but I think you get the idea. If not, just ask.


Last but not least, you replace all serial_xxx calls with dc_serial_xxx calls in the entire codebase.

At this point there should be no functional changes (e.g. everything is still working as before), but we now have the necessary infrastructure for implementing a custom serial I/O layer.

2. Custom serial backend

This will be very similar to the native backend. The public part should contain a structure with the callback functions:

typedef struct dc_custom_serial_operations_t {
    int (*close) (void *userdata);
    ...
} dc_custom_serial_operations_t;

Notice how I'm passing a void pointer here! The internals of the custom backend are not exposed to the application! Instead we pass a userdata cookie that needs to be supplied by the application in the custom open function:

int
dc_custom_serial_open (dc_serial_t **out, dc_context_t *context, const dc_serial_operations_t *ops, void *userdata);

And in the private implementation part:

struct dc_custom_serial_t {
    dc_serial_t base;
    const dc_custom_serial_operations_t *ops;
    void *userdata;
};

static const dc_serial_vtable_t custom_serial_vtable = {
    custom_serial_close, /* close */
    ...
};

static int
custom_serial_close(dc_serial_t *serial)
{
    return serial->ops->close(serial->userdata);
}

At this point, the application can already create a custom I/O backend, but libdivecomputer won't be able to use it yet, because it's still hardcoded to use the native backend.

3. Modify the dc_device_open() function

The last remaining step is to replace the last "const char *name" parameter of the dc_device_open() function with a "dc_serial_t *serial" parameter. Of course this will also need similar changes to all dive computer backends.

(To take into account irda and usb communication, we may actually need a void* parameter here, so we can also pass a dc_irda_t parameter in the future. Or we need to introduce a more generic I/O abstraction layer. For example something like a dc_stream_t object, which can abstract both serial and irda communication.)

This is a relative small change, but it does affect the application side. So we'll have to decide how we're going to deal with this: modify the existing function and break backwards compatibility, or add another dc_device_open2() function and keep the old one around for a while?

(Note that after v0.5 backwards compatibility will get broken anyway, because I want to cleanup some older mistakes too.)

Jef
_______________________________________________
subsurface mailing list
subsurface@subsurface-divelog.org
http://lists.subsurface-divelog.org/cgi-bin/mailman/listinfo/subsurface

Reply via email to