Публичный дизайн API с параметром out типа структуры, содержащим char * - PullRequest
0 голосов
/ 29 апреля 2019

Я разрабатываю независимый от платформы API для уведомления о событиях и в настоящее время имею следующий интерфейс:

enum ns_event_type{
    deleted,
    moved
};

struct ns_event_meta{
    enum ns_event_type type;
    size_t internal_buffer_size;
    void *internal_buffer; /* Memory to store either deleted_path or moved */
    union {
        const char *deleted_path;
        struct {
             const char *moved_from;
             const char *moved_to;
        } moved;
    } event_data;
};

typedef struct ns_event_queue ns_event_queue;

int ns_take_event(ns_event_queue *queue, struct ns_event_meta *meta_out);

Способ, которым я разработал struct ns_event_meta, состоит в том, чтобы содержать буфер сырой памяти.Буфер используется в качестве контейнера для const char *readable_path или (const char *moved_from и const char *moved_to).

Так что в случае, если размера void *interval_buffer недостаточно для хранения пути (путей) для принимаемого события, функция ns_take_event возвращает -1, и ожидается, что вызывающая сторона будет расти void *internal_buffer;

Проблема, которую я вижу в этом проекте, заключается в том, что клиент библиотеки может получить доступ к необработанному буферу void *internal_buffer, который на самом деле не предназначен для доступа клиента.В отличие от этого, мета-событие предназначено для изучения с помощью элементов enum ns_event_type type; и event_data.

Есть ли другой способ решения такой проблемы проектирования?

1 Ответ

2 голосов
/ 29 апреля 2019

Мне кажется, что у вас есть два противоречивых требования.

  1. Вы хотите, чтобы пользователь выделил память для internal_buffer (и, я полагаю, также обновите internal_buffer_size).

  2. Вы хотите скрыть internal_buffer от пользователя.

Вы не можете сделать оба! Если пользователь собирается обрабатывать выделение памяти, вы не можете «спрятать» буфер. Так что либо вы должны признать, что пользователь знает о буфере, либо вы должны выделить память в вашем коде.

Хорошо известным способом скрытия внутренних данных является использование непрозрачного типа данных. Основная идея заключается в том, что вы собираете личные данные членов в структуре, например, struct private. Пользователь получает только указатель на структуру, но не получает информацию о содержимом структуры. Вам нужно будет предоставить все функции, необходимые для работы со скрытыми / личными данными.

Это может выглядеть примерно так:

ns_event.h (для пользователей)

enum ns_event_type{
    deleted,
    moved
};

struct ns_private;  // Opaque data type. The user knows that this type exists
                    // but have no idea what it contains.

struct ns_event_meta{
    enum ns_event_type type;
    union {
        const char *deleted_path;
        struct {
             const char *moved_from;
             const char *moved_to;
        } moved;
    } event_data;
    struct ns_private *buffer;  // The user only gets a pointer to the
                                // "private" data but can't access the
                                // members as the user doesn't know
                                // what's inside
};

// Public functions
int ns_init_buffer(struct ns_event_meta *buffer, size_t size);    
int ns_resize_buffer(struct ns_event_meta *buffer, size_t size);    
size_t ns_get_buffer_size(struct ns_event_meta *buffer);    
void ns_free_buffer(struct ns_event_meta *buffer);    
...
...

ns_event.c (ваш код)

struct ns_private {
    size_t internal_buffer_size;
    void *internal_buffer;
}

int ns_init_buffer(struct ns_event_meta *buffer, size_t size)
{
    assert(buffer != NULL);
    buffer->internal_buffer = malloc(size);
    if (buffer->internal_buffer == NULL)
    {
        buffer->internal_buffer_size = 0;
        return 1;
    }
    buffer->internal_buffer_size = size;
    return 0;
}

...
...

Вы также можете использовать дескриптор вместо указателя на непрозрачный тип данных. Как

struct ns_event_meta{
    enum ns_event_type type;
    union {
        const char *deleted_path;
        struct {
             const char *moved_from;
             const char *moved_to;
        } moved;
    } event_data;
    int handle;    // A handle to hide the location of "private" data
                   // Translation will be needed in the c-file
};

handle будет просто целым числом, выбранным вашим кодом при вызове ns_init_buffer. Внутри вашего кода у вас будет таблица, которая переводит между номерами дескрипторов и указателями на личные данные. Таким образом, пользователь никогда не узнает, где хранятся личные данные, и даже не узнает имя структуры, содержащей личные данные.

примечание: хранение ручки внутри структуры - дело вкуса. Вы можете удалить его из структуры и позволить init-функции возвращать дескриптор. Затем пользователь должен сохранить дескриптор в другой переменной и передать его в качестве дополнительного аргумента функции.

...