让我们假设我们正在编写一个实现某些API的库。 lib创建了一些资源(连接/内部数据结构/无论如何)并返回一个句柄,然后用它来进行进一步的调用。
表示句柄的推荐方法是什么?
它应该是指向分配的某些内部数据结构的原始指针 在创建会话时?
如果是这种情况,我如何在API函数的情况下保护lib
在释放数据句柄的close
方法之后调用
指着?
如果handle是抽象数(比如“handle” - >“obj”map中的键),是否会影响性能?
还有哪些方法可行?我应该选择哪一个?
如果有人参与过API,你能分享一下这个主题的经验吗?
这个问题过于宽泛和抽象,但目前我正在从事非常类似的工作,只是不知道如何解决这个问题。
我一直在查看sqlite3
来源,他们的句柄只是一个动态分配结构的指针,他们在打开连接并关闭它时设置了一些幻数,并检查句柄的有效性在于检查这些幻数。它足够安全吗?
答案 0 :(得分:4)
我设计了“close”函数来将不透明指针设置为NULL
。
interface.h:
typedef struct handle_internals * Handle;
int handle_open(/* out */ Handle * phandle, ..);
int handle_close(/* in out */ Handle * phandle)
int handle_func1(Handle handle, ..);
...
implementation.c:
...
#include "interface.h"
...
struct handle_internals
{
...
}
int handle_open(Handle * phandle, ..)
{
if (NULL == phandle)
{
errno = EINVAL;
return -1;
}
*phandle = malloc(sizeof ** phandle);
...
return 0;
}
int handle_close(Handle * phandle)
{
if (NULL == phandle)
{
errno = EINVAL;
return -1;
}
free(*phandle);
*phandle = NULL;
return 0;
}
int handle_func1(Handle handle, ..)
{
if (NULL == handle)
{
errno = EINVAL;
return -1;
}
...
return 0;
}
答案 1 :(得分:3)
问题"它足够安全吗?" 取决于您的应用程序。
如果它是在您的笔记本电脑上运行的数独求解器,则可能只需要很少或不需要处理验证。
如果它是自动驾驶汽车的控制器,那么我希望你能够非常自信你不会在垃圾上工作。
对于大多数用途,使用不透明指针都很好。
答案 2 :(得分:3)
句柄可以像分配的整数值一样简单,以维护进程和对象或内核之间的关联。或者它可以是指向非常大的表的指针,由内核(或调用应用程序或API)用于跟踪(和提供)进程状态,对象状态,内存使用等。
并非所有API都需要处理,但有许多API都需要处理。也许我要做的第一个观察是Windows编程中已经存在多少种类型的句柄,然后询问他们具有哪些属性或质量,以及为什么在这些场景中它们是必需的。以下是一些用于Windows编程的常见句柄类型的简短列表:
1) HWND - 处理窗口。用于跟踪显示屏上的物体
2) HINSTANCE - 允许OS(kernal)与应用程序实例通信。
3) HMENU - 用于显示或控制应用程序中的菜单。
4) HANDLE - WINAPI通用句柄。例如,用于关联许多对象,例如控制线程的句柄。
所有这些共同的工作是在内核,应用程序对象和进程之间提供逻辑链接,并在所有这些之间传递相关的上下文信息。
通常,尽管句柄通常被定义为无符号32位值,但它们永远不应被视为整数。它们是一个唯一值,如果编辑或在任何可能改变其值的操作中使用,它就不再可信,因为创建了对象的链接。程序员应该存储句柄,但永远不要修改它。
那么,您的API的 具体需求 是什么?
它应该是创建会话时分配的某些内部数据结构的原始指针吗? 你说 lib创建了一些资源。如果您需要跟踪或控制这些资源,那么对象的句柄(指向struct的指针或用于支持多个进程的struct数组)将非常有用。
如果在关闭方法之后调用API函数,如何保护lib?
设计您的API:
1)您的close方法应该将句柄对象设置为0或NULL(取决于您使用的对象类型)
2)API中的所有公共函数都应该要求句柄的非零/非NULL值
3)在API中提供私有函数,以验证作为任何API函数的句柄传递的值是否已分配。 (检查非空,或非0。值是否传递给活动句柄?)在所有公共API函数的顶部调用此函数。
如果handle是一个抽象数字(比如“handle” - >“obj”map中的键),它会不会影响性能?
我假设你说的是通过函数参数传递句柄信息的效率...
关于函数参数和性能,将小值作为函数参数传递仅仅是开展业务的成本,但对于大型对象,您可以通过将指针传递给该对象而不是传递对象本身来提高吞吐量性能。
答案 3 :(得分:1)
当缺乏更好的想法时,我建议使用指向实现代码已知的结构的不透明指针。例如:
typedef struct SomeInternal* Handle;
避免多余的SDK源更改以及使调试更容易确保此内部命名正确并且不太可能与其他库冲突的方式至关重要。前缀命名约定在此处非常有用,您可以在整个SDK中全面应用。
现在关于抽象成本,我过去常常在我的职业生涯中担心这一点,我最大的建议是永远忘记它(现在)。这是因为间接函数调用的成本几乎总是被估计为远高于实际值。考虑经典的qsort
与std::sort
示例,为类型T生成的C ++ std::sort
通常优于qsort
2-3次,因为缺少间接调用比较器。由于运行时抽象,qsort
中的这种低效率在这里不可避免地存在,但是考虑到它通常每个元素多次调用比较器函数(通常每个元素平均有30个间接调用,因此对于各种元素有100万个元素)我已经测试过的实现)。在这样的几个选择区域之外遇到这类场景是很少见的,在这些场景中你有大量的间接函数调用乘以每个函数在内部做的很少的事实。
现在忘记它的另一个更大的理由是因为在分析会话强烈要求它时,你总是可以将select内部结构公开为优化,而不会影响ABI或处理级联源破坏。你不能这么容易地反过来,所以在不透明的一面犯错是比较安全的。
如果是这种情况,如果在释放数据句柄指向的close方法之后调用API函数,我该如何保护lib?
如果这种安全性是一个大问题(我通常认为它不应该是一个主要的安全问题,否则C可能是错误的工具,因为指针误用的可能性总是存在在语言中换取原始力量),通常你可以在调用类似close
或destroy
的内容中设置前向声明的不透明结构中的某些字段,就像在结构表明它被摧毁了。然后,如果客户端尝试使用已经破坏的资源以及接受不透明指针作为该资源句柄的函数,则可能引发错误。