0

C++ でライブラリを作成していますが、C API が必要であり、これもスレッド セーフである必要があります。API が行う必要があることの 1 つは、ライブラリ内で作成されたオブジェクトのハンドル (参照またはポインターを含む構造体など) をやり取りすることです。これらのオブジェクトはある時点で破棄する必要があるため、まだ存在していたそのようなオブジェクトへのハンドルは無効になります。

編集: 各ハンドルが単一のクライアント スレッド内でのみ使用されると想定することはできません。特に、同じリソースに同時にアクセスする 2 つのクライアント スレッドがあり、一方がそれを破棄しようとしていて、もう一方がそれを変更しようとしている場合を処理したいと考えています。

これに対処するための 2 つのパラダイムがあります。1 つは、boost::shared_ptr や std::shared_ptr などのスマート ポインター パラダイムで、オブジェクトへの参照がなくなった場合にのみオブジェクトが破棄されるようにします。ただし、私が理解しているように、そのようなポインターは C で実装することはできません (コンストラクターとデストラクタをサポートしていないため)。そのため、このアプローチは私の場合は機能しません。ユーザーが取得したハンドルのすべてのインスタンスに対して「リリース」関数を呼び出すことに依存したくありません。

もう 1 つのパラダイムは、ライブラリ内のオブジェクトを単純に破棄し、そのオブジェクトへのハンドルを入力として渡す後続の関数呼び出しがエラー コードを返すようにすることです。私の質問は、特にマルチスレッド アプリケーションで、このアプローチを実装するために利用できる手法またはライブラリは何ですか?

編集:もちろん、ライブラリは内部メモリ自体の割り当て解除のすべての割り当てを処理する必要があります。ライブラリ内でスマート ポインタを使用することもできます。ただし、API を介して渡されることはありません。

編集: ハンドルの詳細はクライアント側で使用される場合があります。クライアントには 2 つのスレッドがあり、そのうちの 1 つがオブジェクトを作成します。そのスレッドがハンドルを 2 番目のスレッドに渡すか、2 番目のスレッドが「find_object」関数を使用してライブラリからハンドルを取得する可能性があります。次に、2 番目のスレッドが継続的にオブジェクトを更新する可能性がありますが、その実行中に最初のスレッドがオブジェクトを破棄し、オブジェクトへのすべてのハンドルが無効になる可能性があります。

アプローチの大まかな提案に感謝します-私もこれらのいくつかを思いつきました。ただし、他のスレッドがそのオブジェクトを破棄しようとしているときに、ハンドルを指定して C++ クラスのインスタンスを取得してロックする方法などの詳細は重要なので、「以下を実行すると、必ず動作します。」または、さらに良いことに、「このライブラリは、あなたが求めているものを賢明かつ安全に実装しています」。

4

5 に答える 5

3

内部的にスマート ポインターのマップのキー値として保持される IMHO ハンドル (単純な整数など) は、実行可能な解決策になる可能性があります。

ただし、ハンドルがマルチスレッド環境で他のクライアントによって使用されている限り、特定のクライアントによって解放されたハンドルがマップ エントリを破棄しないことを保証するメカニズムが必要です。

更新:
少なくとも@ user1610015の回答はそれほど悪い考えではありません(COM対応環境で作業している場合)。いずれにしても、内部で管理されているクラス インスタンスの参照カウントを追跡する必要があります。

私は C++11 やブーストのスマート ポインター機能の経験があまりなく、これらを使用して参照カウント メカニズムをインターセプトまたはオーバーライドする方法について詳しく知りません。しかし、たとえばAlexandrescou の lokiライブラリスマート ポインターの考慮事項により、参照カウントを処理する方法と、どのインターフェイスがこれにアクセスできるかどうかに関する適切なポリシーを実装できる場合があります。

Alexandrescou のSmart Pointerを使用すると、C-API と C++ の内部実装を介したアクセスを同時にサポートし、スレッドセーフな参照カウント メカニズムを提供できるはずです。

于 2013-02-24T15:28:16.667 に答える
2

コードがハンドルを使用する場合、ほとんどの場合、破棄ハンドル関数を呼び出します。そして、破棄されたハンドルでの操作は違法です。

ハンドルは通常、struct本体のない forward decl へのポインター、またはstruct未完成の 1 つまたは 2 つの preable フィールド (おそらく C 側でのデバッグを支援するため) へのポインターのいずれかです。それらの作成と破棄は API 内で行われます。

API では、ハンドルの内容を完全に把握できます。これは C 構造体である必要はありません。一意の ptr などを使用できます。手動でハンドルを削除する必要がありますが、それは避けられません。

以下で説明するように、別の可能なハンドルは でguid、API 内に guid からデータへのマップがあります。これは遅いですが、GUID のスペースは事実上無限であるため、erasedハンドルの使用を検出してエラーを返すことができます。ハンドルを返さないとリソース リークが発生することに注意してください。

于 2013-02-24T15:28:59.030 に答える
2

API が行う必要があることの 1 つは、ハンドルを前後に渡すことです。

わかりました

(例: 参照またはポインターを含む構造体)

なんで?「ハンドル」は、オブジェクトを識別するための単なる方法です。必ずしも参照またはポインターを保持する必要があるとは限りません。

1 つは、boost::shared_ptr や std::shared_ptr などのスマート ポインター パラダイムで、オブジェクトへの参照がなくなった場合にのみオブジェクトが破棄されるようにします。

確かに、

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

メモリ解放メカニズムに使用する場合は、ここで問題なく機能する可能性があります。

ライブラリ内のオブジェクトを破棄するだけで、

これは、スマート ポインターで存在する可能性がありますが、いずれかではありません。

入力としてそのオブジェクトへのハンドルを渡す後続の関数呼び出しは、単にエラー コードを返します。

確かにいいですね。

ライブラリがメモリの割り当てを担当する場合は、メモリの割り当て解除を担当する必要があります。

ライブラリの _GetNewObject() メソッドから単純な整数の「ハンドル」を返します。

ライブラリには、内部オブジェクトへのハンドルのマップが必要です。ライブラリの外にいる人は、C インターフェースからのオブジェクトを見るべきではありません。

すべてのライブラリ メソッドは、最初のパラメーターとしてハンドルを取る必要があります。

マルチスレッドの場合、2 つのスレッドが同じオブジェクトにアクセスする必要がありますか? その場合は、C API 関数に入ったときに発生し、終了する前に解放される、ある種のロックを組み込む必要があります。ライブラリ外のコードにこのロックを認識させたい場合は、決定を下す必要があります (おそらく認識しません)。ライブラリ関数を呼び出す C 関数は、おそらく戻り値を取得するだけで、施錠開錠。

したがって、ライブラリには次のものが必要です。

  • 外部からはハンドルと見なされる、オブジェクトの割り当てと割り当て解除を行うインターフェース
  • ハンドルを指定して何かを行うためのインターフェース。

編集: 詳細情報

ライブラリ内では、Factory パターンを使用して新しいオブジェクトを作成します。Factory は、オブジェクトの割り当て後に shared_ptr を配布する必要があります。このように、ライブラリ内の他のすべては単に shared_ptr を使用し、クリーンアップはかなり自動化されます (つまり、ファクトリはクリーンアップを覚えておくために作成されたもののリストを格納する必要がなく、明示的に delete を呼び出す必要はありません)。ハンドルを使用してマップに shared_ptr を格納します。次に利用可能なハンドルを取得し、ラップアラウンドを処理するために、GetNextHandle() 関数と一緒にある種の静的カウンターが必要になるでしょう (実行中のプログラムの存続期間内に作成および破棄されるオブジェクトの数によって異なります)。

次に、共有ポインタをプロキシに配置します。プロキシは非常に軽量である必要があり、実際のオブジェクトごとに多くの Proxy オブジェクトを持つことができます。各プロキシは、プライベート shared_ptr と、使用することを選択したスレッド/ミューテックス オブジェクトを保持します (これに関する情報を提供していないため、これ以上特定するのは困難です)。プロキシが作成されると、ミューテックスを取得し、破壊時に解放する必要があります (つまり、ロックを解放するための RAII)。

新しいオブジェクトを作成するか既存のオブジェクトを検索するかを決定する方法、および 2 つの異なるスレッドが同じオブジェクトを「検索」する方法に関する情報は含まれていません。ただし、オブジェクトが存在する場合は、各オブジェクトを一意に識別し、マップからハンドルを返すのに十分なパラメーターを持つ GetObject() があると仮定します。

この場合、可視の extern C ライブラリ関数のそれぞれがオブジェクト ハンドルを受け入れ、次のようになります。

指定されたハンドルの新しいプロキシを作成します。Proxy コンストラクターでは、Proxy はマップを調べてハンドルを見つけます。ハンドルが存在しない場合は、Factory にハンドルを作成するように依頼します (または、ここで選択したエラーを返します)。次に、プロキシがロックを取得します。次に、関数はプロキシからポインターを取得して使用します。関数が終了すると、プロキシはスコープ外になり、ロックを解放し、参照カウンターをデクリメントします。

2 つの関数が異なるスレッドで実行されている場合、関数の 1 つにプロキシが存在する限り、オブジェクトは引き続き存在します。他の関数は、マップから参照を削除するオブジェクトを削除するようにライブラリに要求できます。アクティブなプロキシ オブジェクトを持つ他のすべての関数が終了すると、最終的な shared_ptr が範囲外になり、オブジェクトが削除されます。

このほとんどは、テンプレートを使用して一般的に行うか、具象クラスを作成することができます。

編集: 詳細情報

プロキシは小さなクラスになります。これには shared_ptr があり、ロックがあります。クライアントによって呼び出される extern C 関数のスコープ内で Proxy をインスタンス化します (注: これは実際には、C++ クラスを使用できるなどのすべての利点を持つ C++ 関数です)。プロキシは小さく、スタックに配置する必要があります (これを新規作成して削除しないでください。その価値よりも問題が多くなります。スコープ付き変数を作成し、C++ に作業を任せてください)。プロキシは RAII パターンを使用して shared_ptr のコピーを取得し (shared_ptr の参照カウントをインクリメントします)、構築時にロックを取得します。Proxy がスコープ外になると、それが持っている shared_ptr が破棄されるため、参照カウントが減少します。Proxy デストラクタはロックを解放する必要があります。ところで、ブロッキングと、スレッドミューテックスをどのように機能させたいかについて考えたいと思うかもしれません。

マップには、他のすべてのコピー元の「マスター」shared_ptr が含まれます。ただし、プロキシがマップから shared_ptr を取得すると、「それを返す」ことを心配する必要がないため、これは柔軟で分離されています。マップ内の shared_ptr は削除できます (つまり、オブジェクトは Factory に「存在しません」) が、shared_ptr を持つ Proxy クラスがまだ存在する可能性があるため、実際のオブジェクトは、何かがそれを使用している限り存在し続けます。

于 2013-02-24T15:37:02.137 に答える
1

もう 1 つの方法は、COM API を公開することです。これには、C API とは異なり、オブジェクト指向であるという利点があります。ただし、C からも使用できます。次のようになります。

C++:

// Instantiation:
ISomeObject* pObject;
HRESULT hr = CoCreateInstance(..., IID_PPV_ARGS(&pObject));

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

// Cleanup:
pObject->Release();

子:

// 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
于 2013-02-24T15:46:45.800 に答える
0

たぶん、私はあまりにも多くの時間を手にしています...しかし、私はこれについて数回考え、先に進んで実装することにしました. C++ 呼び出し可能、外部ライブラリなし。楽しみのためだけに、車輪を完全に再発明しました(日曜日の楽しみでコーディングを呼び出すことができれば)。

同期はここにはありません。あなたが使用しているOSがわからないためです...

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;
}
于 2013-02-24T18:57:04.580 に答える