Если вы хотите создать уникальные дескрипторы, вы можете сделать это с помощью malloc()
и struct
:
typedef intptr_t HANDLE_TYPE;
HANDLE_TYPE init_buffer_traverse(double * src, size_t src_len);
int copy_buffer(HANDLE_TYPE h_traverse, double * dest, size_t dest_len);
void close_handle_buffer_traverse(HANDLE_TYPE h);
typedef struct
{
double * source;
size_t source_length;
size_t position;
} TRAVERSAL;
#define INVALID_HANDLE 0
/*
* Returns a new traversal handle, or 0 (INVALID_HANDLE) on failure.
*
* Allocates memory to contain the traversal state.
* Resets traversal state to beginning of source buffer.
*/
HANDLE_TYPE init_buffer_traverse(double *src, size_t src_len)
{
TRAVERSAL * trav = malloc(sizeof(TRAVERSAL));
if (NULL == trav)
return INVALID_HANDLE;
trav->source = src;
trav->source_len = src_len;
trav->position = 0;
return (HANDLE_TYPE)trav;
}
/*
* Returns the system resources (memory) associated with the traversal handle.
*/
void close_handle_buffer_traverse(HANDLE_TYPE h)
{
TRAVERSAL * trav = NULL;
if (INVALID_HANDLE != h)
free((TRAVERSAL *)h);
}
int copy_buffer(HANDLE_TYPE h,
float* dest, int dest_length)
{
TRAVERSAL * trav = NULL;
if (INVALID_HANDLE == h)
return -1;
trav = (TRAVERSAL *)h;
int copy_length = trav->source_length - trav->position;
if (dest_length < copy_length)
copy_length = dest_length;
for (int i = 0; i*emphasized text* < copy_length; i++)
dest[i] = trav->source[trav->position + i];
// remember where to continue next time the copy_buffer() is called
trav->position += copy_length;
return copy_length;
}
Этот стиль - то, что некоторые C-кодеры использовали до появления C ++. Стиль включает в себя структуру данных, которая содержит все элементы данных нашего «класса». Большинство API для класса принимает в качестве первого аргумента указатель на одну из этих структур. Этот указатель похож на указатель this
. В нашем примере этот параметр был назван trav
.
Исключением для API будут те методы, которые выделяют тип дескриптора; они похожи на конструкторы и имеют тип дескриптора в качестве возвращаемого значения. В нашем случае с именем init_buffer_traverse
можно было бы назвать construct_traversal_handle
.
Существует много других методов, кроме этого, для реализации значения «непрозрачный дескриптор». Фактически, некоторые кодеры манипулируют битами (например, через XOR), чтобы скрыть истинную природу дескрипторов. (Эта неясность не обеспечивает безопасность там, где это необходимо.)
В данном примере я не уверен (не смотрел на sndlib), имеет ли смысл указатель и длина целевого буфера удерживаться в структуре дескриптора или нет. Если это так, это сделало бы его дескриптором «буфера копирования», а не дескриптором «обхода», и вы захотите изменить всю терминологию из этого ответа.
Эти дескрипторы действительны только в течение времени жизни текущего процесса, поэтому они не подходят для дескрипторов, которые должны пережить перезапуски сервера дескрипторов. Для этого используйте базу данных ISAM и идентификатор столбца в качестве дескриптора. Подход с использованием базы данных намного медленнее, чем подход с указателем в памяти, но для постоянных дескрипторов вы все равно не можете использовать значения в памяти.
С другой стороны, похоже, что вы реализуете библиотеку, которая будет работать в течение одного времени жизни процесса. В этом случае ответ, который я написал, должен быть пригоден для использования после изменения ваших требований.
Добавление
Вы попросили пояснить сходство с C ++, о котором я упоминал выше. Чтобы быть конкретным, некоторый эквивалент (к приведенному выше коду C) кода C ++ может быть:
class TRAVERSAL
{
double * source;
size_t source_length;
size_t position;
public TRAVERSAL(double *src, size_t src_len)
{
source = src;
source_length = src_len;
position = 0;
}
public int copy_buffer(double * dest, size_t dest_len)
{
int copy_length = source_length - position;
if (dest_length < copy_length)
copy_length = dest_length;
for (int i = 0; i < copy_length; i++)
dest[i] = source[position + i];
// remember where to continue next time the copy_buffer() is called
position += copy_length;
return copy_length;
}
}
Есть некоторые очевидные различия. Версия C ++ немного менее многословна. Отчасти это иллюзорно; эквивалент close_handle_buffer_traverse
теперь равен delete
объекту C ++. Конечно, delete
не является частью реализации класса TRAVERSAL
, delete
поставляется с языком.
В версии C ++ нет "непрозрачного" дескриптора.
Версия C является более явной и, возможно, делает более очевидным, какие операции выполняются аппаратными средствами в ответ на выполнение программы.
Версия C более пригодна для использования приведения к HANDLE_TYPE
для создания «непрозрачного идентификатора», а не типа указателя. Версия C ++ может быть «обернута» в API, который выполняет то же самое при добавлении другого слоя. В текущем примере пользователи этого класса сохранят копию TRAVERSAL *
, которая не совсем «непрозрачна».
В функции copy_buffer()
версия C ++ не должна упоминать указатель trav
, поскольку вместо этого она неявно разыменовывает указатель this
, предоставленный компилятором.
sizeof(TRAVERSAL)
должно быть одинаковым для обоих примеров C и C ++ - без vtable, а также при условии, что идентификация типа времени выполнения для C ++ отключена, класс C ++ содержит только ту же структуру памяти, что и C struct в нашем первом примере.
Реже использовать стиль "непрозрачный идентификатор" в C ++, потому что в C ++ снижено наказание за "прозрачность". Членами данных class TRAVERSAL
являются private
, поэтому TRAVERSAL *
нельзя случайно использовать для разрыва нашего контракта API с пользователем API.
Обратите внимание, что как непрозрачный идентификатор, так и указатель класса уязвимы для злоупотреблений со стороны злонамеренного пользователя API - либо непрозрачный идентификатор, либо указатель класса может быть приведен непосредственно к, например, double **
, что позволяет держателю идентификатора изменить элемент source
напрямую через память. Конечно, вы уже должны доверять вызывающему API, потому что в этом случае вызывающий код API находится в том же адресном пространстве. В примере сетевого файлового сервера могут возникнуть проблемы с безопасностью, если «непрозрачный идентификатор», основанный на адресе памяти, будет открыт снаружи.
Я бы обычно не делал отступление в доверие к пользователю API, но я хочу уточнить, что ключевое слово C ++ private
не имеет «правоприменительных полномочий», оно только определяет соглашение между программистами, которое компилятор также соблюдает, если только иное сказано человеком.
Наконец, указатель класса C ++ может быть преобразован в непрозрачный идентификатор следующим образом:
typedef intptr_t HANDLE_TYPE;
HANDLE_TYPE init_buffer_traverse(double *src, size_t src_len)
{
return (HANDLE_TYPE)(new TRAVERSAL(src, src_len));
}
int copy_buffer(HANDLE_TYPE h_traverse, double * dest, size_t dest_len)
{
return ((TRAVERSAL *)h_traverse)->copy_buffer(dest, dest_len);
}
void close_handle_buffer_traverse(HANDLE_TYPE h)
{
delete ((TRAVERSAL *)h);
}
И теперь наша краткость "эквивалентного" C ++ может быть подвергнута дальнейшему сомнению.
То, что я написал о старом стиле программирования на C, которое относится к C ++, не означало, что C ++ лучше подходит для этой задачи. Я имею в виду только то, что инкапсуляция данных и скрытие деталей реализации могут быть выполнены в C через стиль, который почти изоморфен стилю C ++. Это может быть полезно знать, если вы начинаете программировать на C, но, к сожалению, сначала выучили C ++.
PS
Я только что заметил, что наша реализация на сегодняшний день использовала:
dest[i] = (float)source[position + i];
при копировании байтов. Поскольку оба dest
и source
равны double *
(то есть они оба указывают на значения double
), здесь нет необходимости в приведении. Кроме того, приведение от double
к float
может потерять цифры точности в представлении с плавающей точкой. Так что это лучше удалить и переформулировать как:
dest[i] = source[position + i];