Sérialisation des types Mixte (C++/CLI)

 

On m’a souvent posé la question de la sérialisation des types mixte C++/CLI.

En effet la capacité d’embarquer des pointeurs natif dans des ref class pose un problème quand la class est marquée [Serializable].

La solution pour maitriser comment et quelle donnée va être sérialisé est d’implémenter l’interface ISerializable.

Cette interface implique d’avoir un constructeur par défaut, un constructeur surchargé prenant en paramètre (SerializationInfo ^si, StreamingContext sc) et surcharger la méthode virtuelle : virtual void GetObjectData(SerializationInfo ^si, StreamingContext sc).

 

Dans cette dernière méthode et le constructeur surchargé, on doit « manuellement » préciser les champs a sérialiser via :

si->AddValue("m_nCount",m_nCount);

et accéder aux données via : m_nCount = si->GetInt32("m_nCount");

 

Prenons un exemple :

 

[Serializable]

public ref class MyClass2 : public ISerializable

{

public :

     int m_nCount;

     int m_nData;

     Native* pn;

     virtual void GetObjectData(SerializationInfo ^si, StreamingContext sc)

    {

        si->AddValue("m_nCount",m_nCount);

        si->AddValue("m_nData",m_nData);

          si->AddValue("pnx",pn->x);

          si->AddValue("pny",pn->y);

          String^ s = gcnew String(pn->chaine);

          si->AddValue("chaine",s);

    }

     MyClass2()

    {

        m_nCount = -1;

        m_nData = -1;

     pn=new Native(8,2);

    }

    

protected:

    MyClass2(SerializationInfo ^si, StreamingContext sc)

    {

          if (pn==nullptr)

                pn=new Native(8,2);

        int i =si->GetInt32("m_nCount");

          m_nCount = i;

        m_nData = si->GetInt32("m_nData");

          pn->x = si->GetInt32("pnx");

          pn->y = si->GetInt32("pny");

          String ^tt= si->GetString("chaine");

          StringUtilities::StringConvertor scc(tt);

          strcpy_s(pn->chaine,scc.NativeCharPtr);

    }

     ~MyClass2()

     {

          delete pn;

     }

     !MyClass2()

     {

          delete pn;

     }

};

 

Vous noterez que la ref class embarque un pointeur sur une classe native (code mixte)que j’alloue sur le tas natif au moment de la construction et détruit au moment de la destruction et finalisation de la ref class.

Pour faciliter le transtypage des chaines natives vers les System ::String (et en attendant la marshall library prévue pour Orcas) l’excellent code source de Nishant Sivakumar (blog.voidnish.com) publié sur https://www.codeproject.com/managedcpp/StringConvertor.asp Celui ci auteur d’un ouvrage important sur le C++/CLI, propose une classe de StringConertor (tout est dans nom !)..y’a plus qu’à l’utiliser !

Pour corser l’affaire j’au utilisé un buffer de char dans la classe native, le stringconvertor gère pour moi les copies entre tas natif et tas managé :

 

property char* NativeCharPtr

          {

                char* get()

                {

                     IntPtr ptr = Marshal::StringToHGlobalAnsi(m_String);

                     if(ptr != IntPtr::Zero)

                     {

                     m_vec_hglobal->push_back(ptr);

                          return reinterpret_cast<char*>(static_cast<void*>(ptr));

                     }

                     else

                          return nullptr;

                }

          }

 

Reste à utiliser cette classe avec un serialisateur : (ici en binaire, mais on pourrait utiliser le soapformater…ou le xmlformater de WCF)

 

FileStream ^fs = gcnew FileStream("data2.txt" ,

                     FileMode::Create, FileAccess::ReadWrite);

     BinaryFormatter ^bf = gcnew BinaryFormatter();

     // SoapFormatter ^bf = gcnew SoapFormatter();

     bf->Serialize(fs,mc);

     fs->Close();

 

La dé sérialisation s’opère de manière symétrique :

     MyClass2 ^data;

     FileStream fs ("data2.txt" , FileMode::Open, FileAccess::ReadWrite);

     BinaryFormatter bf;

     data = (MyClass2^) bf.Deserialize(%fs);

     Console::WriteLine("data->Count is {0}",

            data->m_nCount.ToString());

     Console::WriteLine("data->Data is {0}",

            data->m_nData.ToString());

     Console::WriteLine("data->pn->x is {0}",

            data->pn->x.ToString());

     Console::WriteLine("data->pn->y is {0}",

            data->pn->y.ToString());

     StringUtilities::StringConvertor sc(data->pn->chaine);

     Console::WriteLine("data->pn->chaine is {0}",

            sc.ToString());

     fs.Close();

 

En synthèse il est facile de rendre une ref class de type mixte sérialisable et via l’implémentation de ISerializable de maitriser ce qui doit être persisté.

 

A bientôt,

Eric