FAQ
.NET クラスで IPersistStream を実装する方法

ナレッジ番号:5530 | 登録日:2023/07/27 | 更新日:2024/11/21

【概要】

カスタム オブジェクトを記述するとき、多くのケースでシリアライズをサポートする必要があります。例えば、カスタム シンボルやエレメントはマップ ドキュメントに保存され、マップ ドキュメントが開かれる際にロードされる必要があります。この機能は、IPersistVariant インタフェースを実装することによって利用可能となります。

シリアライズを通じたクローン作成をサポートするために、オブジェクトを一時的に ObjectSteam に保存し、その後、クラスの新しいインスタンスを作成し、一時的なObjectSteam からそのプロパティをロードすることによってオブジェクトを複製します。ObjectStream を内部的に使用する ObjectCopy クラスを使用してください。このクラスはクローン オブジェクト、またはクローニーが IPersistStream をサポートすることを要求します。

IPersistStream は、単純なシリアル ストリームを使用するオブジェクトをストレージのために保存したり、ロードしたりするためのメソッドを提供する Microsoft のインタフェースです。.NET を使用するこれらのメソッドによって提供される構造化されたストリームを使用するためには、オブジェクトはバイト配列に変換される必要があります。
 
.NET では、オブジェクトをバイト配列に変換するいくつかの方法があります。しかしながら、それぞれのメソッドはオブジェクト タイプに特有のものです。マネージ クラスのメンバのみを持つオブジェクトにおいて、すべてのメンバがシリアライズをサポートするとの仮定のもと、BinaryFormatter と連動して MemoryStream を使用します。実際には、オブジェクトはマネージ型とアンマネージ型(例: ArcObjects コンポーネント)の両方を含む、異なるタイプのクラス メンバを持ちます。このことが、IPersistStream.Save() や IPersistStream.Load() メソッドの実装を複雑にしています。カスタム オブジェクトの一部としてこのコードを記述することは厄介であり、コードの読み取りや管理も困難となる場合があります。

解決策として、Save() と Load () を使用して静的クラスを記述し、オブジェクト内で IPersistStream を実装する際に呼び出しをデリゲートすることです。

【手順】

PersistStream ヘルパー クラスの実装

ヘルパー クラスの背景にあるアイデアとは、ヘルパー クラスに渡される各オブジェクトは最終的にシリアライズが可能になるということです。このことは、すべてのマネージ オブジェクトがシリアライズをサポートし、且つ、アンマネージ オブジェクトが、マネージ型のシリアライズが可能なタイプに変換されなければならないということを意味しています。ArcObjects の場合、解決方法は、XMLSerialize を使用して ArcObjects のアンマネージ クラス メンバを xml ストリームに記述し、そのストリームをシリアライズできるシンプルな文字列に変換することです。

以下のコードは「永続化の実装」のサンプル コードです。下記の【関連情報】にあるリンクもご参照ください。

ウィンドウ ハンドル(hWnd)、デバイス コンテキスト(hDC)、ファイル ハンドル、グラフィカル デバイス インタフェース(GDI) など、いくつかのメンバは直接複製されません。

以下のコードは、IStream インタフェースがポインタの使用を必要とし、C# でのみ提供されるため、アンセーフ  コードを使用します。

 

  1. PersistStreamHelp.Save() メソッドを実装します。

    ArcObjects の型を文字列に変換します。XMLWriter を使用して、ArcObject を XMLStream にシリアライズします。これは、XMLStream にオブジェクトを書き出すXMLSerializer によって行われます。オブジェクトに与えられる名前のほか、"ArcObject" としてオブジェクトを認識する名前空間にも注目してください。

    if (Marshal.IsComObject(data))
    
    {
    //*** XmlWriter を作成します。 ***
    IXMLWriter xmlWriter = new XMLWriterClass();

    //*** XmlStream を作成します。 ***
    IXMLStream xmlStream = new XMLStreamClass();

    //*** ストリームにオブジェクトを書き出します。 ***
    xmlWriter.WriteTo(xmlStream as IStream);

    //*** オブジェクトをシリアライズします。 ***
    IXMLSerializer xmlSerializer = new XMLSerializerClass();
    xmlSerializer.WriteObject(xmlWriter, null, null, "arcobject", "http://www.esri.com/schemas/ArcGIS/9.2", data);



  2. XMLStream を文字列に変換します。 

    string str = xmlStream.SaveToString();
    data = (object)str;
    if (null == data)
    return;


  3. マネージ型の MemoryStream にオブジェクトを書き出します。このセクションはすべての型に共通なので、すべての入力オブジェクトはシリアライズをサポートするという状況であるはずです。

    
    
    //オブジェクトがシリアライズ可能であることを確認します。
    
    if (!data.GetType().IsSerializable)
    throw new Exception("Object is not serializable.");

    // 文字列をバイト配列に変換します。
    MemoryStream memoryStream = new MemoryStream();
    BinaryFormatter binaryFormatter = new BinaryFormatter();
    binaryFormatter.Serialize(memoryStream, data);

  4. MemoryStream をバイト配列に変換し、MemoryStream を閉じます。 

    byte[] bytes = memoryStream.ToArray();
    
    memoryStream.Close();

  5. オブジェクトのバイト配列長を取得し、その情報をバイト配列として格納します。 

    これが大変重要であるのは、.NET で構造化されたストリームからオブジェクトを読み込む際に、オブジェクトのサイズはバイト単位で指定されなくてはならないためです。バイト配列長は Integer 型として与えられるため、この情報をバイト配列に変換すると、常に 4 バイトの配列になります。構造化されたストリームからオブジェクトを読み込む際、まずオブジェクト長をバイトで指定して 4 バイト読み込み、その後、ストリームから実際のオブジェクトが読み込まれます。 

    // バイト長を取得します。
    byte[] arrLen = BitConverter.GetBytes(bytes.Length
    );

  6. オブジェクト長と、そのオブジェクトのバイト配列を、構造化されたストリームに書き出します。 

    // メモリ ポインタをInt32にします。
    
    int cb;
    int* pcb = &cb;

    // バイト長を書き出します。
    stream.Write(arrLen, arrLen.Length, new IntPtr(pcb));
    // バイト配列を書き出します。 stream.Write(bytes, bytes.Length, new IntPtr(pcb));


  7. オブジェクトのバイト配列がストリームに正常に書き込まれたことを確認します。IStream.Write() メソッドを呼び出す際に、返されるポインタは、書き込まれたバイトの番号を格納します。ストリームに書き込まれたバイト数とオブジェクトのバイト数が一致していることを確認します。 

    if (bytes.Length != cb)
       throw new Exception("Error writing object to stream");


  8. PersistStreamHelp.Load() メソッドを実装します。  

    構造化されたストリームからオブジェクトを読み込むことは、書き出しと反対の手法です。まず、オブジェクト長を読み込み、次にオブジェクトのバイト配列を読み込みます。オブジェクトをデシリアライズするためには BinaryFormatter を使用し、ArcObjects の場合には XMLSerializer を使用してオブジェクトを元に戻します。

    オブジェクトのバイト配列のサイズを取得します。この情報は、4 バイト配列として構造化されたストリームに書き出されます。 

    // ポインタを Int32 にします。
    int cb;
    int* pcb = &cb; &cb;

    // オブジェクトのバイト配列のサイズを取得します。
    byte[] arrLen = new Byte[4]; &cb;

    stream.Read(arrLen, arrLen.Length, new IntPtr(pcb));
    cb = BitConverter.ToInt32(arrLen, 0);


  9. 前ステップで読み込まれたサイズで、新規のバイト配列を割り当てます。 

    // オブジェクトのバイト配列を読み込みます。
    byte[] bytes = new byte[cb];
    stream.Read(bytes, cb, new IntPtr(pcb));


  10. バイトの読み込み数がオブジェクトのサイズと一致することを確認します。 

    if (bytes.Length != cb)
       throw new Exception("Error reading object from stream");


  11. BinaryFomatter と連動して、MemoryStream を使用してオブジェクトをデシリアライズします。 

    / / バイト配列をデシリアライズします。
    object data = null;
    MemoryStream memoryStream = new MemoryStream(bytes);
    BinaryFormatter binaryFormatter = new BinaryFormatter();
    object objectDeserialize = binaryFormatter.Deserialize(memoryStream);
    if (objectDeserialize != null)
    {
       data = objectDeserialize;
    }
    memoryStream.Close()
    ;
     

  12. オブジェクトが文字列の場合、それが ArcObjects の xml 文字列であるかどうか確認します。この場合、XMLSerializer を使用して、ArcObject のインスタンスを取得します。

    //ArcObjects をデシリアライズします。
    if (data is string)
    {
       string str = (string)data;
       if (str.IndexOf("http://www.esri.com/schemas/ArcGIS/9.2") != -1)
       {
          IXMLStream readerStream = new XMLStreamClass();
          readerStream.LoadFromString(str);
          IXMLReader xmlReader = new XMLReaderClass();
          xmlReader.ReadFrom((IStream)readerStream);
          IXMLSerializer xmlReadSerializer = new XMLSerializerClass();
          object retObj = xmlReadSerializer.ReadObject(xmlReader, null, null);
          if (null != retObj)
             data = retObj;
       }
    }
     

  13. オブジェクトを返します。  

    return data;  

  14. オブジェクト内で PersistStream ヘルパー クラスを使用します。

    例えば、オブジェクトが異なるタイプのメンバを持っており、IPersistStream をサポートしていなけれはなりません。これらのメンバは文字列型、ダブル型、整数型といった単純なタイプです。その他のマネージ型タイプとしては、DataTables、ArrayLists のほか、ArcObjects メンバのジオメトリ(例:IPoint)や空間参照(ISpatialReference)があります。 

    //クラス メンバ
    private int m_version = 1;
    private ISpatialReference m_spatialRef = null;
    private IPoint m_point = null;
    private string m_name = string.Empty;
    private ArrayList m_arr = null;
    private Guid m_ID;

    シリアライズするマネージ クラスの各メンバについて、それぞれがシリアライズをサポートすることをまず確認します。MSDN を開き、オブジェクトが 'SerialisableAttribute' のその定義に含まれていることを確認します。以下は、Guid メンバの例です。 

    32035_01 

    'SerialisableAttribute' は、オブジェクトがシリアライズ可能であることを確認するため、安全に使用することができます。ArcObjects では、オブジェクトは、ウィンドウ ハンドル、IScreenDisplay などのデバイス コンテキストといったシリアライズ不可のプロパティを参照しないことを確認します。

    IPersistStream.Save() と IPersist.Load() メソッドの例外として、IPersistStream .GetClassID() がクラスの有効なGuid を返すことを確認します。 

    
    public void GetClassID(out Guid pClassID)
    
    {
    pClassID = new Guid(ClonableObjClass.GUID);
    }

  15. IPersistStream.Save() と IPersistStream.Load() メソッドを実装します。 

    両方のメソッドで、入力の ESRI.ArcGIS.IStream を System.Runtime.InteropServices.Comtypes.IStream にキャストし、次にヘルパー メソッドを使用してクラス メンバを保存、またはロードします。 

    public void Save(IStream pStm, int fClearDirty)
    {
       //ESRI.ArcGIS.IStream をキャストします。
       System.Runtime.InteropServices.ComTypes.IStream stream = (System.Runtime.InteropServices.ComTypes.IStream)pStm;

       //異なるオブジェクトをストリームに保存します。 
       PeristStream.PeristStreamHelper.Save(stream, m_version);
       PeristStream.PeristStreamHelper.Save(stream, m_ID.ToByteArray());
       PeristStream.PeristStreamHelper.Save(stream, m_name);
       PeristStream.PeristStreamHelper.Save(stream, m_spatialRef);

       //Guid を保存します。

       PeristStream.PeristStreamHelper.Save(stream, m_ID);

       //ポイントをストリームに保存します。 
       if (null == m_point)
          m_point = new PointClass();
       PeristStream.PeristStreamHelper.Save(stream, m_point);

       if (null == m_arr)
         m_arr = new ArrayList();
       PeristStream.PeristStreamHelper.Save(stream, m_arr);
    }


    public void Load(IStream pStm)
    {
      //ESRI.ArcGIS.IStream をキャストします。 
      System.Runtime.InteropServices.ComTypes.IStream stream = (System.Runtime.InteropServices.ComTypes.IStream)pStm;

      //ストリームからの情報をロードします。 
      object obj = null;
      obj = PeristStream.PeristStreamHelper.Load(stream);
      m_version = Convert.ToInt32(obj);

      obj = PeristStream.PeristStreamHelper.Load(stream);
      byte[] arr = (byte[])obj;
      m_ID = new Guid(arr);

      obj = PeristStream.PeristStreamHelper.Load(stream);
      m_name = Convert.ToString(obj);

      obj = PeristStream.PeristStreamHelper.Load(stream);
      m_spatialRef = obj as ISpatialReference;

      obj = PeristStream.PeristStreamHelper.Load(stream);
      m_ID = (Guid)obj;

      obj = PeristStream.PeristStreamHelper.Load(stream);
      m_point = obj as IPoint;

      obj = PeristStream.PeristStreamHelper.Load(stream);
      m_arr = obj as ArrayList;
    }
     
     

【関連情報】

メタデータ

機能

種類

製品