如果您想创建独特的句柄,您可以使用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);
无效 close_handle_buffer_traverse(HANDLE_TYPE h);
类型定义结构
{
双 * 来源;
size_t 源长度;
size_t 位置;
} 遍历;
#define INVALID_HANDLE 0
/*
* 返回一个新的遍历句柄,失败时返回 0 (INVALID_HANDLE)。
*
* 分配内存以包含遍历状态。
* 将遍历状态重置为源缓冲区的开头。
*/
HANDLE_TYPE init_buffer_traverse(double *src, size_t src_len)
{
遍历 * trav = malloc(sizeof(TRAVERSAL));
如果(NULL == trav)
返回无效句柄;
trav->源 = src;
trav->source_len = src_len;
旅游->位置= 0;
返回(HANDLE_TYPE)trav;
}
/*
* 返回与遍历句柄关联的系统资源(内存)。
*/
无效close_handle_buffer_traverse(HANDLE_TYPE h)
{
遍历 * trav = NULL;
if (INVALID_HANDLE != h)
免费((遍历*)h);
}
int copy_buffer(HANDLE_TYPE h,
浮动* dest, int dest_length)
{
遍历 * trav = NULL;
if (INVALID_HANDLE == h)
返回-1;
trav = (TRAVERSAL *)h;
int copy_length = trav->source_length - trav->位置;
如果(目标长度 < 复制长度)
复制长度 = 目标长度;
for (int i = 0; i*强调文本* < copy_length; i++)
dest[i] = trav->source[trav->position + i];
// 记住下次调用 copy_buffer() 时从哪里继续
trav->位置+=复制长度;
返回复制长度;
}
这种风格是一些 C 编码器在 C++ 出现之前使用的。该样式涉及一个数据结构,其中包含我们“类”的所有数据元素。该类的大多数 API 都将其作为第一个参数,即指向其中一个结构的指针。这个指针类似于this
指针。在我们的示例中,此参数被命名为trav
。
API 的例外是那些分配句柄类型的方法;这些类似于构造函数,并将句柄类型作为返回值。在我们的例子中,namedinit_buffer_traverse
还不如被称为construct_traversal_handle
。
除此方法外,还有许多其他方法可以实现“不透明句柄”值。事实上,一些编码人员会操纵这些位(例如,通过 XOR)以掩盖句柄的真实性质。(这种默默无闻并不能在需要的地方提供安全性。)
在给出的示例中,我不确定(没有查看 sndlib)是否将目标缓冲区指针和长度保存在句柄结构中是否最有意义。如果是这样,那将使其成为“复制缓冲区”句柄而不是“遍历”句柄,并且您希望更改此答案中的所有术语。
这些句柄仅在当前进程的生命周期内有效,因此它们不适用于必须在句柄服务器重新启动后仍然存在的句柄。为此,使用 ISAM 数据库和列 ID 作为句柄。数据库方法比内存/指针方法慢得多,但是对于持久句柄,无论如何您都不能使用内存值。
另一方面,听起来您正在实现一个将在单个进程生命周期内运行的库。在这种情况下,根据您的要求修改后,我写的答案应该是可用的。
附录
您要求澄清我上面提到的与 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
来创建“不透明 ID”而不是指针类型。C++ 版本可以“包装”在一个 API 中,该 API 在添加另一层的同时完成了同样的事情。在当前示例中,此类的用户将维护 a 的副本TRAVERSAL *
,这不是很“不透明”。
在 functioncopy_buffer()
中,C++ 版本不需要提及trav
指针,因为它隐式地取消引用编译器提供的this
指针。
sizeof(TRAVERSAL)
C 和 C++ 示例应该是相同的——没有 vtable,还假设 C++ 的运行时类型标识已关闭,C++ 类仅包含与我们第一个示例中的 C 结构相同的内存布局。
在 C++ 中使用“不透明 ID”样式不太常见,因为在 C++ 中对“透明度”的惩罚降低了。class TRAVERSAL
are的数据成员,private
因此TRAVERSAL *
不能意外地用于破坏我们与 API 用户的 API 合同。
请注意,不透明 ID 和类指针都容易受到恶意 API 用户的滥用——不透明 ID 或类指针都可以直接转换为,例如,double **
允许 ID 的持有者source
直接通过记忆。当然,您必须已经信任 API 调用者,因为在这种情况下,API 调用代码在同一个地址空间中。在网络文件服务器的示例中,如果基于内存地址的“不透明 ID”暴露给外部,则可能存在安全隐患。
我通常不会离题到 API 用户的可信度,但我想澄清 C++ 关键字private
没有“强制执行权”,它只指定程序员之间的协议,编译器也尊重该协议,除非人类另有说明。
最后,可以将 C++ 类指针转换为不透明 ID,如下所示:
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++,这可能会很好地了解您。
附言
我刚刚注意到我们迄今为止的实现使用了:
dest[i] = (float)source[position + i];
复制字节时。因为两者dest
和source
都是double *
(即它们都指向double
值),所以这里不需要强制转换。此外,从double
to 转换float
可能会丢失浮点表示中的精度数字。因此,最好将其删除并重述为:
dest[i] = source[position + i];