5

SWIG が他の構造体をメンバーとして含む C 構造体の参照カウントを処理する方法に関連する興味深い発見に出会いました。

構造体のサブメンバーから他の Python オブジェクト ( list/dicts ) にデータを格納している状況で、Python SWIG オブジェクトを使い終わる前にガベージ コレクションを取得していることに気付きました。かなり掘り下げた後、インタープリターが「Swigオブジェクト」であると示していても、SWIG-ed構造メンバーには独自の独立した参照カウントがないように見えることがわかりました。したがって、構造サブ要素からリストにデータを追加したとき、Python はこのデータへの参照を追加したことを知りませんでした。

デモ用に簡単なケースを作成しました。次の 3 つの構造を SWIG しました。

SWIG-ed C 構造体:

typedef struct
{
    unsigned long source;      
    unsigned long destination; 
} message_header;

typedef struct
{
    unsigned long data[120];    
} message_large_body;


typedef struct
{
    message_header       header;
    message_large_body   body;
} large_message;

次に、動作を純粋に SWIG で処理されたソリューションと比較するために、ある程度同等の Python クラスを作成しました。

ある程度同等の Python クラス

class pyLargeMessage(object):
    def __init__(self):
        self.header = bar.message_header()
        self.body = bar.message_large_body()

次に、インタープリターで次のテストを実行しました。

Python インタープリターの結果

>>> y = pyLargeMessage()
>>> y
<__main__.pyLargeMessage object at 0x06C5E6B0>
>>> y.header
<Swig Object of type 'message_header *' at 0x06C5E700>
>>> sys.getrefcount(y.header)
3
>>> z = [y.header]
>>> sys.getrefcount(y.header)
3
>>> z += [y.header]
>>> sys.getrefcount(y.header)
4
>>>
>>> y = bar.large_message()
>>> y
<Swig Object of type 'large_message *' at 0x06C668E0>
>>> y.header
<Swig Object of type 'message_header *' at 0x06C66B60>
>>> sys.getrefcount(y.header)
1
>>> z = [y.header]
>>> sys.getrefcount(y.header)
1
>>> z += [y.header]
>>> sys.getrefcount(y.header)
1
>>>

Python の実装は期待どおりに動作しましたが、純粋な SWIG の実装はそうではありませんでした。誰かがここで何が起こっているのか説明できますか?

SWIG ドキュメントのさまざまなセクションを何度も読みましたが、これを直接説明していると思われるものは見つかりません。物事がどのように機能するかについてさらに多くのことを学びましたが、上記の現象の明確な説明/回避策が見つかりません.

しばらく考えた後、構造体とクラス、プロキシ クラス、および構造体データ メンバーのセクションを何度も読み直し、生成されたラッパー コードを調べても、参照カウントが正常に処理されない理由がわかりません。 .

生成された C コードはSWIG_NewPointerObjを呼び出し、最終的には (ほとんどの場合) を呼び出しPyObject_New、(Python のドキュメントにあるように) 新しい参照を返す必要があります。

ヘッダーメンバーの get-er 用に生成された SWIG コード

SWIGINTERN PyObject *_wrap_large_message_header_get(PyObject *self, PyObject *args) {
  PyObject *resultobj = 0;
  large_message *arg1 = (large_message *) 0 ;
  void *argp1 = 0 ;
  int res1 = 0 ;
  message_header *result = 0 ;

  if (args && PyTuple_Check(args) && PyTuple_GET_SIZE(args) > 0) SWIG_fail;
  res1 = SWIG_ConvertPtr(self, &argp1,SWIGTYPE_p_large_message, 0 |  0 );
  if (!SWIG_IsOK(res1)) {
    SWIG_exception_fail(SWIG_ArgError(res1), "in method '" "large_message_header_get" "', argument " "1"" of type '" "large_message *""'"); 
  }
  arg1 = (large_message *)(argp1);
  result = (message_header *)& ((arg1)->header);
  resultobj = SWIG_NewPointerObj(SWIG_as_voidptr(result), SWIGTYPE_p_message_header, 0 |  0 );
  return resultobj;
fail:
  return NULL;
}
4

1 に答える 1

2

指摘されているように、getter for によって返されるオブジェクトは、header基本body的には軽量のプロキシ オブジェクトであり、header/body内のメモリへのポインタを保持しますstruct。そのメモリは所有しておらず (作成方法に応じて、オブジェクト自体または C ライブラリによって「所有」されていmessageます)、コピーではありません。

それがコピーであっても、あなたの呼び出しsys.getrefcountは常に 1 を返します - ゲッターへの各呼び出しは新しいコピーを返します。

Python の観点から、ダングリング ポインターがないようにしたい場合は、次の 2 つの方法で修正できます。

  1. ゲッターは、それが指すメモリを所有するheader/のコピーのプロキシを返します。body
  2. ゲッターは、messageそれ自体への参照を保持するプロキシを返します。そのため、messageが解放されたとしても、プロキシ オブジェクトがその一部を参照している間は、refcount が 0 になることはありません。

#2 を SWIG で実行する例をまとめました。ヘッダー ファイルは変更されませんが、インターフェイスは次のようになります。

%module test

%{
#include "test.h"
%}

%typemap(out) message_header * header %{
  // This expands to resultobj = SWIG_NewPointerObj(...) exactly as before:
  $result = SWIG_NewPointerObj(SWIG_as_voidptr($1), $1_descriptor, 0);
  // This sets a reference to the parent object inside the child
  PyObject_SetAttrString($result, "_parent", obj0);
%}

%include "test.h"

これは、次のように言うのと同じです:

z = y.header
z._parent = y

パイソンで。

これで、実行できるようになりました。

y = test.large_message()
print(sys.getrefcount(y))
print(y.header)
z = [y.header]
print(sys.getrefcount(y))
z += [y.header]
print(sys.getrefcount(y))

y予想どおり、作成されるすべてのサブオブジェクト プロキシで増加する参照カウントを示しています。したがって、それらが参照するメモリを時期尚早に解放することはできません (少なくとも SWIG によっては)。

それをより一般的にして、次を使用して複数のタイプ/メンバーに適用できます%apply

%module test

%{
#include "test.h"
%}

%typemap(out) SWIGTYPE * SUBOBJECT %{
  $result = SWIG_NewPointerObj(SWIG_as_voidptr($1), $1_descriptor, 0);
  PyObject_SetAttrString($result, "_parent", obj0);
  assert(obj0);
  // hello world
%}

%apply SWIGTYPE * SUBOBJECT { message_header * header };
%apply SWIGTYPE * SUBOBJECT { message_large_body * body };

%include "test.h"
于 2012-12-23T12:46:54.980 に答える