用于光盘层次结构的C API设计 - 最佳实践

时间:2009-04-20 23:56:04

标签: c api

我正在编写我的第一个主要的C API,我想把事情弄清楚。该库为内部结构分配和释放内存 - 使用typedef从客户端隐藏它。我提供访问的数据的粗略结构是:

盘状>程序 - >轨道

与光盘相关的内容包括要读取的文件描述符,文件大小,程序数量以及某些光盘范围的属性。

与程序相关的东西就像(程序)索引到光盘,物理偏移到文件,轨道数和名称。

与曲目相关联的是(跟踪)索引到程序,名称和一堆偏移到文件中。

每个结构都有一个指向父结构的指针。

我有几个问题,但我会尽量保持简短:

  • 轨道/程序是否应该知道它在父结构中的哪个索引?
  • 这种结构层次似乎相当复杂,但另一种方法是将程序和/或跟踪索引传递给各种函数。哪个更好?
  • 更一般的评论?

这是一个类似文件系统的结构(但它是只读的),我希望它在未来与多线程兼容,并且它需要是可移植的。我在这里特别谈论C - 没有C ++。

2 个答案:

答案 0 :(得分:3)

重度编辑介绍 我最初批评问题描述令人困惑。我的问题源于问题中使用“文件”的概念。问题意味着光盘,程序和曲目都存储在一个“文件”中。我认为提问者正在构建自己的文件系统,这会使“一切文件中的所有内容”结构变得奇怪,但我现在已经决定他可能不会这样做,在这种情况下它不那么奇怪。因此,基于他使用现有(可能是标准的)文件系统的假设,我将继续提供真正的答案,并且他的整个数据结构存储在该文件系统中的一个文件中。如果我假设错了,毫无疑问我会得到纠正。

我将首先为这种情况提供一条一般性建议;首先从API用户的角度看问题。然后设计您的API,以便他将编写的代码很容易流动,而无需处理您域中正确的详细信息。

处理API设计的一种方法是首先编写一些用户代码并定义API,以便该代码易于编写。作为奖励,在您实际实施API之后,您将获得一些测试代码以试用它。

转到更具体的建议;

以下是系统中三种数据类型的目录。如果您愿意,我们可以将它们视为抽象数据类型或“对象”,并定义一个typdef结构(DISC,PROGRAM,TRACK说)来表示每个结构。

disc = a collection of programs stored in a file
+-----------+
|file       |
+-----------+
|program    |
+-----------+
|program    |
+-----------+
|...        |
+-----------+
|program    |
+-----------+

program = a collection of tracks
+-----------+
|ptr->disc  |
+-----------+
|name       |
+-----------+
|file offset|
+-----------+
|track      |
+-----------+
|track      |
+-----------+
|...        |
+-----------+
|track      |
+-----------+

track = a collection of audio samples
+------------------+
|ptr->program      |
+------------------+
|name              |
+------------------+
|file offset+length|
+------------------+
|file offset+length|
+------------------+
|...               |
+------------------+
|file offset+length|
+------------------+

我建议您让您的用户从结构中挑选数据。你不能真正隐藏C中结构的内部结构(没有通过投射等跳过箍),但你可以提供一系列功能,让你的用户做他们需要做的事情而不访问抽象类型的内容他们自己。例如,我们的函数族可能看起来像这样;

// DISC functions
DISC      *dopen(  const char *disc_name );
void       dstats( int *ptr_nbr_programs, FILE **ptr_file );
void       dclose( DISC *disc );

// PROGRAM functions
PROGRAM   *popen_name( DISC *disc, const char *program_name );
PROGRAM   *popen_idx ( DISC *disc, int program_idx );
void       pstats( int *ptr_nbr_tracks );
void       pclose( PROGRAM *program );

// TRACK functions
TRACK     *topen_name( PROGRAM *program, const char *track_name );
TRACK     *topen_idx ( PROGRAM *program, int track_idx );
int        tread( unsigned char *buf, int nbytes_to_read );
void       tseek( unsigned long offset );
void       tclose( TRACK *track );

这应该都是合理的自我解释 - 它是以现有的标准C FILE范例为蓝本的。

首先,您的用户使用dopen()获取DISC的ptr。假设这有效(如果没有则返回NULL),他可以使用dstats()获取任何全局DISC信息。更重要的是,他可以使用popen()系列函数获得DISC中的PROGAM的ptr。

使用ptr到PROGRAM,他可以进一步向下钻取并使用topen()系列函数获得单个TRACK。

非常重要的一点是,您不要让用户自己迭代音频片段以从TRACK获取数据。用户提供了一个缓冲区来读取样本,并根据需要通过片段进行迭代以填充该缓冲区。提供了tseek()函数以便随机访问。

我没有尝试弄清楚每个参数的细节以及如何处理错误等等。我只是提出了一个需要改进的概念。

请注意,整个过程都使用“三明治”范例。每种类型的介绍性“开放式”和“结束式”三明治操作。

答案 1 :(得分:0)

  
      
  • 轨道/程序是否应该知道它在父结构中的哪个索引?
  •   

副手我不认为有任何理由反对这一点。没有例子就很难说......

  
      
  • 这种结构等级看起来相当复杂,但另一种选择是   传递程序和/或跟踪索引   各种功能。这是   优选?
  •   

你能举出各自的例子吗?即使是一些函数声明也可能有助于清除它。

  
      
  • 更一般的评论?
  •   

如果它真的是一个只读结构,那么将来你不需要过多关注多线程。