0

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

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

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

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

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

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

我很欣赏对一种方法的粗略建议——我自己也提出了一些建议。但是,在给定句柄的情况下,如何检索和锁定 C++ 类实例,而其他线程可能正在尝试销毁该对象,这些细节是不平凡的,所以我真的很想回答“我有完成以下操作,它可以正常工作。” 或者,甚至更好,“这个库实现了你所追求的明智和安全”。

4

5 回答 5

3

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

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

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

我对 C++11 或增强智能指针功能以及如何使用这些功能拦截或覆盖引用计数机制并不十分熟悉。但是,例如Alexandrescou 的 loki智能指针注意事项可能会让您针对如何处理引用计数以及哪些接口可以访问或不可以访问它来实施适当的策略。

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

于 2013-02-24T15:28:16.667 回答
2

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

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

在 API 中,您可以全面了解句柄的内容,它不必是 C 结构——它可以有唯一的 ptrs 或其他任何东西。您将不得不手动删除句柄,但这是不可避免的。

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

于 2013-02-24T15:28:59.030 回答
2

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

到目前为止还好

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

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

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

当然,一个

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

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

只需销毁库中的对象,

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

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

当然听起来不错。

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

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

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

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

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

所以你的图书馆需要:

  • 分配和释放对象的接口,被外部视为句柄
  • 在给定句柄的情况下执行操作的接口。

编辑:更多信息

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

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

您还没有包含有关如何确定是否要创建新对象或查找现有对象以及两个不同线程如何“查找”同一个对象的任何信息。但是,假设您有一个带有足够参数的 GetObject() 来唯一标识每个对象并从映射中返回句柄(如果对象存在)。

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

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

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

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

编辑:更多信息

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

该地图将包含复制所有其他人的“主”shared_ptr。但是,这是灵活且解耦的,因为一旦 Proxy 从地图中获取了 shared_ptr,它就不必担心“将其归还”。映射中的 shared_ptr 可以删除(即对象不再“存在”到工厂),但仍然可以存在具有 shared_ptr 的代理类,因此只要有东西使用它,实际对象仍然存在。

于 2013-02-24T15:37:02.137 回答
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++,它可以使用 COM 智能指针(如 ATL 的 CComPtr)来自动化内存管理。所以C++代码可以变成:

// Instantiation:
CComPtr<ISomeObject> pSomeObject;
HRESULT hr = pSomeObject.CoCreateInstance(...);

// Use:
hr = pSomeObject->SomeMethod(...);

// Cleanup is done automatically at the end of the current scope
于 2013-02-24T15:46:45.800 回答
0

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

请注意,同步不在这里,因为我不知道您使用的是什么操作系统...

智能指针.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

智能指针.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;
}
于 2013-02-24T18:57:04.580 回答