将句柄传递给C API边界的C ++类

时间:2013-02-24 15:19:44

标签: c++ c api smart-pointers

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

编辑:我们不能假设每个句柄仅在单个客户端线程中使用。特别是我想处理两个客户端线程同时访问同一个资源的情况,一个尝试销毁它而另一个尝试修改它

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

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

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

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

我很欣赏一种方法的粗略建议 - 我自己也提出了其中的一些建议。但是,如果一个C ++类实例被检索和锁定,给定一个句柄,而其他线程可能试图破坏该对象,这些细节非常重要,所以我真的回答“我有做了以下工作,它确实有效。“或者,更好的是,“这个图书馆实现了你理智和安全的目标”。

5 个答案:

答案 0 :(得分:3)

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

虽然您需要一种机制来保证特定客户端释放的句柄不会破坏映射条目,只要句柄仍然被多线程环境中的其他客户端使用。

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

我对C ++ 11不太熟悉或者提升智能指针功能,以及如何拦截或覆盖引用计数机制。但是例如Alexandrescou's lokismart pointer注意事项可以让您实现一个适当的策略,说明如何处理引用计数以及哪些接口可以访问它。

Alexandrescou的Smart Pointer应该可以提供一种引用计数机制,支持通过C-API和C ++内部实现同时访问和线程安全。

答案 1 :(得分:2)

当代码使用句柄时,它几乎总是负责调用dispose句柄函数。然后在处理过的手柄上操作是非法的。

句柄通常是指向没有正文的转发decl struct的指针,或者是指向struct的指针,其中有一个或两个可保留的字段(可能有助于在C端进行调试)。它们的创建和销毁发生在API内部。

在API中,您可以全面查看句柄的内容,它不必是C结构 - 它可以具有唯一的ptrs或其他任何内容。您必须手动删除句柄,但这是不可取的。

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

答案 2 :(得分:1)

  

API需要做的一件事就是来回传递句柄

目前为止

  

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

为什么呢? A&#34;手柄&#34;只是一种识别物体的方法。并不一定意味着必须保留引用或指针。

  

一个是智能指针范例,例如boost :: shared_ptr或std :: shared_ptr,它确保只有在没有对它的引用时才销毁对象。

当然,一个

map<int, boost::shared_ptr<my_object>> 
如果你想将它用于你的内存释放机制,

可能在这里工作正常。

  

简单地销毁库中的对象,

这可以与智能指针一起存在,它不是一个或另一个。

  

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

确实听起来不错。

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

我会返回简单的整数&#34;句柄&#34;来自库_GetNewObject()方法。

您的图书馆需要内部对象的句柄地图。库外的任何人都不应该看到C接口中的对象。

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

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

所以你的图书馆需要:

  • 用于分配和释放对象的接口,由外部视为句柄
  • 给出句柄的东西的接口。

编辑:更多信息

在库中我会使用Factory模式来创建新对象。工厂应在对象分配后分发shared_ptr。这样,库中的其他任何东西都只使用shared_ptr,清理将是相当自动的(即工厂不必存储创建的内容列表以便记住清理,并且没有人必须明确调用delete )。使用句柄将shared_ptr存储在地图中。您可能需要某种静态计数器以及GetNextHandle()函数来获取下一个可用句柄并处理环绕(取决于在运行程序的生命周期内创建和销毁的对象数)。

接下来,将您的共享指针放入代理服务器。代理应该非常轻量级,并且每个实际对象可以有许多代理对象。每个代理都将拥有一个私有的shared_ptr以及您选择使用的任何线程/互斥对象(您还没有给出任何有关此信息的信息,因此很难更具体)。创建代理时,它应该获取互斥锁,并在销毁时释放(即RAII释放锁定)。

您还没有包含有关如何确定是要创建新对象还是查找现有对象的任何信息,以及两个不同的线程如何找到&#34;&#34;同一个对象。但是,假设您有一个GetObject(),其中包含足够的参数来唯一标识每个对象,并且如果对象存在,则从地图返回句柄。

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

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

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

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

编辑:更多信息

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

地图将包含&#34; master&#34; shared_ptr,其他所有其他都是从中复制的。然而,这是灵活的并且是分离的,因为一旦代理从地图获得shared_ptr,它就不必担心&#34;将其返回&#34;。可以删除映射中的shared_ptr(即对象不再存在&#34;到工厂),但仍然可以存在具有shared_ptr的Proxy类,因此实际对象仍然存在正在使用它。

答案 3 :(得分:1)

另一种方法是公开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

答案 4 :(得分:0)

也许我手上有太多时间......但我已经考虑了几次并决定继续实施它。 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;
}