通过C API边界向C++类传递句柄

Passing handle to C++ classes across a C API boundary

本文关键字:句柄 C++ API 边界 通过      更新时间:2023-10-16

我正在用C++编写一个库,但希望它有一个C API,它也应该是线程安全的。API需要做的一件事是来回传递在库中创建的对象的句柄(例如,包含引用或指针的结构)。这些对象需要在某个时刻被销毁,因此对仍然存在的此类对象的任何句柄都将无效。

EDIT:我们不能假设每个句柄只在一个客户端线程中使用。特别是,我想处理这样一种情况,即有两个客户端线程同时访问同一资源,一个试图销毁它,而另一个试图修改它

处理这一问题有两种模式。一种是智能指针范式,例如boost::shared_ptr或std::shared_ptr,它确保只有在没有更多引用的情况下才销毁对象。然而,据我所知,这样的指针不可能在C中实现(因为它不支持构造函数和析构函数),所以这种方法在我的情况下不起作用。我不想依赖用户为他们获得的句柄的每个实例调用"release"函数,因为他们不可避免地不会这样做。

另一种范式是简单地销毁库中的对象,并让任何后续的函数调用将句柄作为输入传递给该对象,只需返回错误代码。我的问题是,有什么技术或库可以实现这种方法,特别是在多线程应用程序中?

编辑:库当然应该处理内部内存本身的所有分配和释放。智能指针也可以在库中使用;但是它们可能不会通过API。

编辑:关于句柄的更多细节可以在客户端使用:客户端可能有两个线程,其中一个线程创建一个对象。该线程可能会将句柄传递给第二个线程,或者第二个螺纹可能会使用"find_object"函数从库中获取句柄。第二个线程可能会不断更新对象,但在进行更新时,第一个线程可能破坏对象,使对象的所有句柄无效。

我很欣赏这种方法的粗略建议——我自己也提出了一些建议。然而,当其他线程可能试图破坏该对象时,C++类实例如何被检索和锁定(给定句柄)等细节是非常重要的,所以我真的很想知道"我已经完成了以下操作,它毫无故障地工作了",或者更好的是,"这个库明智而安全地实现了你想要的东西"。

IMHO句柄(例如,简单整数)在内部作为键值保存在智能指针映射中可能是一个可行的解决方案。

尽管您需要有一种机制来保证由特定客户端释放的句柄不会破坏映射条目,但只要该句柄仍由其他客户端在多线程环境中使用即可。

更新:
至少@user1610015的答案不是一个坏主意(如果您使用的是启用COM的环境)。在任何情况下,您都需要跟踪内部托管类实例的一些引用计数。

我对C++11或增强智能指针功能以及如何使用这些功能拦截或覆盖引用计数机制没有太多经验。但例如,Alexandresou的loki库智能指针考虑因素可能会让您实现一个适当的策略,说明如何处理引用计数,以及哪些接口可以访问或不访问。

Alexandresou的智能指针应该可以提供一种引用计数机制,该机制支持同时通过C-API和C++内部实现进行访问,并且是线程安全的。

API需要做的一件事是来回传递处理

到目前为止还可以

(例如,包含引用或指针的结构)

为什么?"句柄"只是识别对象的一种方式。并不一定意味着它必须保持引用或指针。

一种是智能指针范式,例如boost::shared_ptr或std::shared-ptr,它确保只有在没有更多引用时对象才会被销毁。

当然,

map<int, boost::shared_ptr<my_object>> 

如果您想将它用于内存释放机制,那么这里可能工作得很好。

只需销毁库中的对象,

这可以与智能指针一起存在,而不是其中之一。

的任何后续函数调用都会将句柄作为输入传递给该对象,只会返回错误代码。

听起来确实不错。

如果您的库负责分配内存,那么它应该负责释放内存。

我会从库_GetNewObject()方法返回简单的整数"句柄"。

您的库需要内部对象的句柄映射。库外的任何人都不应该从C接口看到这些对象。

所有库方法都应该将句柄作为它们的第一个参数。

对于多线程的东西,两个线程需要访问同一个对象吗?如果是这样,您将需要放入某种锁定,这种锁定在C API函数被输入时发生,并在它离开之前释放。如果你想让库外的代码知道这个锁定(你可能不知道),你必须做出决定,调用库函数的C函数可能只想得到返回值,而不担心锁定/解锁。

所以你的图书馆需要:

  • 一个用于分配和释放对象的接口,被外部视为句柄
  • 一个给定句柄的接口

编辑:更多信息

在库中,我会使用Factory模式来创建新对象。工厂应该在对象分配之后分发一个shared_ptr。通过这种方式,库中的所有其他内容都只使用shared_ptr,并且清理将是相当自动的(即,工厂不必存储要记住清理的创建内容的列表,也不必显式调用delete)。使用句柄将shared_ptr存储在映射中。您可能需要某种静态计数器和GetNextHandle()函数来获得下一个可用句柄并处理环绕(取决于在运行程序的生命周期内创建和销毁的对象数量)。

接下来,将共享指针放入代理中。代理应该非常轻,并且每个实际对象可以有许多代理对象。每个代理都将保存一个私有的shared_ptr和您选择使用的任何线程/互斥对象(您还没有提供任何关于这方面的信息,所以很难再具体说明)。当创建代理时,它应该获取互斥,并在销毁时释放(即用于释放锁的RAII)。

关于如何确定是要创建新对象还是要查找现有对象,以及两个不同的线程如何"查找"同一对象,您还没有提供任何信息。但是,假设您有一个GetObject(),它具有足够的参数来唯一标识每个对象,并在对象存在的情况下从映射返回句柄。

在这种情况下,每个可见的extern C库函数都将接受一个对象句柄和:

为给定句柄创建一个新的代理。在Proxy构造函数中,Proxy会在映射中查找句柄,如果句柄不存在,请要求Factory创建一个句柄(或者返回一个错误,您可以在此处选择)。然后代理将获取锁。然后,您的函数将从Proxy获取指针并使用它。当函数退出时,Proxy将超出范围,释放锁,并递减引用计数器。

如果两个函数在不同的线程中运行,只要其中一个函数中存在代理,对象就会仍然存在。另一个函数可以要求库删除对象,从而从映射中删除引用。一旦具有活动Proxy对象的所有其他函数完成,最终的shared_ptr将超出范围,对象将被删除。

您可以用模板来完成大部分工作,或者编写具体的类。

编辑:更多信息

代理将是一个小类。它将有一个shared_ptr,并有一个锁。您可以在客户端调用的外部C函数的范围内实例化一个Proxy(注意,这实际上是一个具有所有优点的C++函数,例如能够使用C++类)。代理很小,应该放在堆栈上(不要新建和删除它,这比它的价值更麻烦,只需创建一个作用域变量,让C++为您完成工作)。代理将使用RAII模式来获取shared_ptr的副本(这将增加shared_ptr的引用计数),并在构造时获取锁。当代理超出范围时,它所拥有的shared_ptr将被销毁,从而减少引用计数。代理析构函数应该释放锁。顺便说一句,您可能需要考虑阻塞以及希望线程互斥体如何工作。我对您的特定互斥实现还不太了解,无法提出任何建议。

该映射将包含"master"shared_ptr,所有其他映射都是从该"master"shared_ptr复制的。然而,这是灵活和解耦的,因为一旦代理从映射中获得shared_ptr,它就不必担心"将其归还"。映射中的shared_ptr可以被删除(即对象对工厂不再"存在"),但仍然可以有具有shared_ptr的代理类,因此只要有东西在使用它,实际对象就会仍然存在。

当代码使用句柄时,它几乎总是负责调用dispose句柄函数。然后,对已处理的句柄进行操作是非法的。

句柄通常是指向不带正文的前向declstruct的指针,或者指向带有一两个不完整的可预置字段的struct的指针(可能是为了帮助C端调试)。它们的创建和销毁发生在API内部。

在API中,您可以看到句柄内容的完整视图,它不需要是C结构——它可以具有唯一的ptr或其他任何东西。您将不得不手动删除句柄,但这是不可取的。

如下所述,另一个可能的句柄是guid,API内部具有从guid到数据的映射。这很慢,但guid的空间实际上是无限的,因此您可以检测到erased句柄的使用并返回错误。请注意,未能返回句柄会泄漏资源,但这会以modsst运行时为代价消除悬空指针segfault。

另一种方法是公开COM API。这具有面向对象的优点,不像C API。但它仍然可以从C中使用。它看起来像这样:

C++:

// Instantiation:
ISomeObject* pObject;
HRESULT hr = CoCreateInstance(..., IID_PPV_ARGS(&pObject));
// Use:
hr = pObject->SomeMethod(...);
// Cleanup:
pObject->Release();

C:

// Instantiation:
ISomeObject* pObject;
HRESULT hr = CoCreateInstance(..., IID_PPV_ARGS(&pObject));
// Use:
hr = (*pObject->lpVtbl->SomeMethod)(pObject, ...);
// Cleanup:
(*pObject->lpVtbl->Release)(pObject);

此外,如果客户端是C++,它可以使用像ATL的CComPtr这样的COM智能指针来自动化内存管理。所以C++代码可以变成:

// Instantiation:
CComPtr<ISomeObject> pSomeObject;
HRESULT hr = pSomeObject.CoCreateInstance(...);
// Use:
hr = pSomeObject->SomeMethod(...);
// Cleanup is done automatically at the end of the current scope

也许我手头的时间太多了。。。但我已经考虑过几次了,决定继续实现它。C++是可调用的,没有外部库。完全重新发明了轮子,只是为了好玩(如果你能在周日称编码为好玩的话。)

注意,同步不在这里,因为我不知道你在什么操作系统上…

SmartPointers.h:

#ifndef SMARTPOINTER_H
#define SMARTPOINTER_H
#ifdef __cplusplus
extern "C" {
#endif
#ifndef __cplusplus
#define bool int
#define true (1 == 1)
#define false (1 == 0)
#endif
// Forward declarations  
struct tSmartPtr;
typedef struct tSmartPtr SmartPtr;
struct tSmartPtrRef;
typedef struct tSmartPtrRef SmartPtrRef;
// Type used to describe the object referenced.
typedef void * RefObjectPtr;
// Type used to describe the object that owns a reference.
typedef void * OwnerPtr;
// "Virtual" destructor, called when all references are freed.
typedef void (*ObjectDestructorFunctionPtr)(RefObjectPtr pObjectToDestruct);
// Create a smart pointer to the object pObjectToReference, and pass a destructor that knows how to delete the object.
SmartPtr *SmartPtrCreate( RefObjectPtr pObjectToReference, ObjectDestructorFunctionPtr Destructor );
// Make a new reference to the object, pass in a pointer to the object that will own the reference.  Returns a new object reference.
SmartPtrRef *SmartPtrMakeRef( SmartPtr *pSmartPtr, OwnerPtr pReferenceOwner );
// Remove a reference to an object, pass in a pointer to the object that owns the reference.  If the last reference is removed, the object destructor is called.
bool SmartPtrRemoveRef( SmartPtr *pSmartPtr, OwnerPtr pReferenceOwner );
// Remove a reference via a pointer to the smart reference itself.
// Calls the destructor if all references are removed.
// Does SmartPtrRemoveRef() using internal pointers, so call either this or SmartPtrRemoveRef(), not both.
bool SmartPtrRefRemoveRef( SmartPtrRef *pRef );
// Get the pointer to the object that the SmartPointer points to.
void *SmartPtrRefGetObjectPtr(  SmartPtrRef *pRef );
#ifdef __cplusplus
}
#endif
#endif // #ifndef SMARTPOINTER_H

SmartPointers.c:

#include "SmartPointers.h"
#include <string.h>
#include <stdlib.h>
#include <assert.h>
typedef struct tLinkedListNode {
struct tLinkedListNode *pNext;
} LinkedListNode;
typedef struct tLinkedList {
LinkedListNode dummyNode;
} LinkedList;
struct tSmartPtrRef {
LinkedListNode    listNode;
OwnerPtr          pReferenceOwner;
RefObjectPtr      pObjectReferenced;
struct tSmartPtr *pSmartPtr;
};
struct tSmartPtr {
OwnerPtr                    pObjectRef;
ObjectDestructorFunctionPtr ObjectDestructorFnPtr;
LinkedList refList;
};
// Initialize singly linked list
static void LinkedListInit( LinkedList *pList )
{
pList->dummyNode.pNext = &pList->dummyNode;
}
// Add a node to the list
static void LinkedListAddNode( LinkedList *pList, LinkedListNode *pNode )
{
pNode->pNext = pList->dummyNode.pNext;
pList->dummyNode.pNext = pNode;
}
// Remove a node from the list
static bool LinkedListRemoveNode( LinkedList *pList, LinkedListNode *pNode )
{
bool removed = false;
LinkedListNode *pPrev = &pList->dummyNode;
while (pPrev->pNext != &pList->dummyNode) {
if  (pPrev->pNext == pNode) {
pPrev->pNext = pNode->pNext;
removed = true;
break;
}
pPrev = pPrev->pNext;
}
return removed;
}
// Return true if list is empty.
static bool LinkedListIsEmpty( LinkedList *pList )
{
return (pList->dummyNode.pNext == &pList->dummyNode);
}
// Find a reference by pReferenceOwner
static SmartPtrRef * SmartPtrFindRef( SmartPtr *pSmartPtr, OwnerPtr pReferenceOwner )
{
SmartPtrRef *pFoundNode = NULL;
LinkedList * const pList = &pSmartPtr->refList;
LinkedListNode *pIter = pList->dummyNode.pNext;
while ((pIter != &pList->dummyNode) && (NULL == pFoundNode)) {
SmartPtrRef *pCmpNode = (SmartPtrRef *)pIter;
if  (pCmpNode->pReferenceOwner == pReferenceOwner) {
pFoundNode = pCmpNode;
}
pIter = pIter->pNext;
}
return pFoundNode;
}
// Commented in header.
SmartPtrRef *SmartPtrMakeRef( SmartPtr *pSmartPtr, OwnerPtr pReferenceOwner )
{
// TODO: Synchronization here!
SmartPtrRef *pRef = (SmartPtrRef *)malloc(sizeof(SmartPtrRef) );
LinkedListAddNode( &pSmartPtr->refList, &pRef->listNode );
pRef->pReferenceOwner = pReferenceOwner;
pRef->pObjectReferenced = pSmartPtr->pObjectRef;
pRef->pSmartPtr = pSmartPtr;
return pRef;
}
// Commented in header.
bool SmartPtrRemoveRef( SmartPtr *pSmartPtr, OwnerPtr pReferenceOwner )
{
// TODO: Synchronization here!
SmartPtrRef *pRef = SmartPtrFindRef( pSmartPtr, pReferenceOwner ); 
if (NULL != pRef) {
assert( LinkedListRemoveNode( &pSmartPtr->refList, &pRef->listNode ) );
pRef->pReferenceOwner = NULL;
pRef->pObjectReferenced = NULL;
free( pRef );
if (LinkedListIsEmpty( &pSmartPtr->refList ) ) {
pSmartPtr->ObjectDestructorFnPtr( pSmartPtr->pObjectRef );
}
}
return (NULL != pRef);
}
// Commented in header.
bool SmartPtrRefRemoveRef( SmartPtrRef *pRef )
{
return SmartPtrRemoveRef( pRef->pSmartPtr, pRef->pReferenceOwner );
}
// Commented in header.
void *SmartPtrRefGetObjectPtr(  SmartPtrRef *pRef )
{
return pRef->pObjectReferenced;
}
// Commented in header.
SmartPtr *SmartPtrCreate( void *pObjectToReference, ObjectDestructorFunctionPtr Destructor )
{
SmartPtr *pThis = (SmartPtr *)malloc( sizeof( SmartPtr ) );
memset( pThis, 0, sizeof( SmartPtr ) );
LinkedListInit( &pThis->refList );
pThis->ObjectDestructorFnPtr = Destructor;
pThis->pObjectRef = pObjectToReference;
return pThis;
}

和一个测试程序(main.cpp)

// SmartPtrs.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "SmartPointers.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>

typedef struct tMyRefObj {
int       refs;
SmartPtr *pPointerToMe;
bool      deleted;
} MyRefObj;
static bool objDestructed = false;
static MyRefObj *MyObjectGetReference( MyRefObj *pThis, void *pObjectReferencing )
{
// TODO: Synchronization here...
pThis->refs++;
SmartPtrRef * const pRef = SmartPtrMakeRef( pThis->pPointerToMe, pObjectReferencing );
return (MyRefObj *)SmartPtrRefGetObjectPtr( pRef );
}
static void MyObjectRemoveReference( MyRefObj *pThis, void *pObjectReferencing )
{
// TODO: Synchronization here...
pThis->refs--;
assert( SmartPtrRemoveRef( pThis->pPointerToMe, pObjectReferencing ) );
}
static void MyObjectDestructorFunction(void *pObjectToDestruct)
{
MyRefObj *pThis = (MyRefObj *)pObjectToDestruct;
assert( pThis->refs == 0 );
free( pThis );
objDestructed = true;
}
static MyRefObj *MyObjectConstructor( void )
{
MyRefObj *pMyRefObj =new MyRefObj;
memset( pMyRefObj, 0, sizeof( MyRefObj ) );
pMyRefObj->pPointerToMe = SmartPtrCreate( pMyRefObj, MyObjectDestructorFunction );
return pMyRefObj;
}
#define ARRSIZE 125
int main(int argc, char* argv[])
{
int i;
// Array of references
MyRefObj *refArray[ARRSIZE];
// Create an object to take references of.
MyRefObj *pNewObj = MyObjectConstructor();
// Create a bunch of references.
for (i = 0; i < ARRSIZE; i++) {
refArray[i] = MyObjectGetReference( pNewObj, &refArray[i] );
}
assert( pNewObj->refs == ARRSIZE );
for (i = 0; i < ARRSIZE; i++) {
MyObjectRemoveReference( pNewObj, &refArray[i] );
refArray[i] = NULL;
}
assert(objDestructed);
return 0;
}