Différences d’utilisation entre _variant_t, COleVariant, CComVariant et VARIANT et utilisation des variations de SAFEARRAY

J’étudie plusieurs types de projets Visual Studio 2015 C ++ utilisant ADO pour accéder à une firebase database SQL Server. L’exemple simple effectue une sélection sur une table, lit les lignes, met à jour chaque ligne et met à jour la table.

La version MFC fonctionne bien. La version de la console Windows est l’endroit où je rencontre un problème pour mettre à jour les lignes du jeu d’enregistrements. La méthode update() du jeu d’enregistrements génère une exception COM avec le texte d’erreur suivant:

 L"Item cannot be found in the collection corresponding to the requested name or ordinal." 

avec un HRESULT de 0x800a0cc1 .

Dans les deux cas, j’utilise un object de jeu d’enregistrements ADO standard défini comme;

 _RecordsetPtr m_pRecordSet; // recordset object 

Dans la version MFC, la fonction permettant de mettre à jour la ligne actuelle du jeu d’enregistrements est la suivante:

 HRESULT CDBrecordset::UpdateRow(const COleVariant vPutFields, COleVariant vValues) { m_hr = 0; if (IsOpened()) { try { m_hr = m_pRecordSet->Update(vPutFields, vValues); } catch (_com_error &e) { _bstr_t bstrSource(e.Description()); TCHAR *description; description = bstrSource; TRACE2(" _com_error CDBrecordset::UpdateRow %s %s\n", e.ErrorMessage(), description); m_hr = e.Error(); } } if (FAILED(m_hr)) TRACE3(" %S(%d): CDBrecordset::UpdateRow() m_hr = 0x%x\n", __FILE__, __LINE__, m_hr); return m_hr; } 

Cette fonction est appelée à l’aide de deux objects COleSafeArray composés dans une classe d’assistance afin de faciliter la spécification des noms de colonne et des valeurs à mettre à jour.

 // Create my connection ssortingng and specify the target database in it. // Then connect to SQL Server and get access to the database for the following actions. CSsortingng ConnectionSsortingng; ConnectionSsortingng.Format(pConnectionSsortingngTemp, csDatabaseName); CDBconnector x; x.Open(_bstr_t(ConnectionSsortingng)); // Create a recordset object so that we can pull the table data that we want. CDBrecordset y(x); // ....... open and reading of record set deleted. MyPluOleVariant thing(2); thing.PushNameValue (SQLVAR_TOTAL, prRec.lTotal); thing.PushNameValue (SQLVAR_COUNTER, prRec.lCounter); hr = y.UpdateRow(thing.saFields, thing.saValues); 

Étant donné que la version de la console Windows n’utilise pas MFC, je rencontre des problèmes de définition qui semblent dus à la classe CComSafeArray classe COM d’ATL, CComSafeArray comme modèle.

Dans le source MFC, COleSafeArray est une classe dérivée de tagVARIANT qui est une union constituant la structure de données d’un VARIANT . Cependant, dans ATL COM, CComSafeArray est un modèle que j’utilise en tant que CComSafeArray ce qui semble raisonnable.

Cependant, lorsque j’essaie d’utiliser une variable définie avec ce modèle, une classe CDBsafeArray dérivée de CComSafeArray , l’erreur de compilation suivante se produit au point où j’appelle m_pRecordSet->Update() :

 no suitable user-defined conversion from "const CDBsafeArray" to "const _variant_t" exists 

_variant_t semble être une classe wrapper pour VARIANT et il ne semble pas exister de chemin de conversion entre CComSafeArray et _variant_t mais il existe un chemin de conversion entre COleSafeArray et _variant_t .

Ce que j’ai essayé est de spécifier le membre m_psa de la classe qui est un SAFEARRAY de type VARIANT et cela comstack cependant, je vois l’exception COM ci-dessus lors du test de l’application. En regardant dans l’object avec le débogueur, l’object spécifiant les champs à mettre à jour semble être correct.

Il semble donc que je mélange des classes incompatibles. Que serait une classe wrapper SAFEARRAY qui fonctionnerait avec _variant_t ?

Le type VARIANT est utilisé pour créer une variable pouvant contenir une valeur de nombreux types différents. Une telle valeur peut se voir atsortingbuer une valeur entière en un point et une valeur de chaîne en un autre. ADO utilise VARIANT avec un certain nombre de méthodes différentes pour que les valeurs lues dans une firebase database ou écrites dans une firebase database puissent être fournies à l’appelant via une interface standard plutôt que d’essayer de créer de nombreuses interfaces différentes, spécifiques au type de données.

Microsoft spécifie le type VARIANT qui est représenté sous la forme d’une struct C / C ++ contenant un certain nombre de champs. Les deux parties principales de cette struct sont un champ contenant une valeur représentant le type de la valeur actuelle stockée dans le VARIANT et une union des différents types de valeur pris en charge par un VARIANT .

En plus de VARIANT un autre type utile est SAFEARRAY . Un SAFEARRAY est un tableau contenant des données de gestion, des informations sur le tableau, telles que le nombre d’éléments qu’il contient, ses dimensions et les limites supérieure et inférieure (les données sur les limites vous permettent d’avoir des plages d’index arbitraires).

Le code source C / C ++ d’un VARIANT ressemble à ce qui suit (tous les membres de la struct et de l’ union du composant semblent être anonymes, par exemple __VARIANT_NAME_2 est __VARIANT_NAME_2 pour être vide):

 typedef struct tagVARIANT VARIANT; struct tagVARIANT { union { struct __tagVARIANT { VARTYPE vt; WORD wReserved1; WORD wReserved2; WORD wReserved3; union { LONGLONG llVal; LONG lVal; BYTE bVal; SHORT iVal; // ... lots of other fields in the union } __VARIANT_NAME_2; DECIMAL decVal; } __VARIANT_NAME_1; } ; 

COM utilise le type VARIANT dans les interfaces d’object COM pour fournir la possibilité de transmettre des données à travers l’interface et d’effectuer tout type de transformation de données nécessaire (marshaling).

Le type VARIANT prend en charge une grande variété de types de données, dont SAFEARAY . Vous pouvez donc utiliser un VARIANT pour passer un SAFEARRAY sur une interface. Plutôt que d’avoir une interface SAFEARRAY explicite, vous pouvez spécifier une interface VARIANT qui reconnaîtra et traitera un VARIANT contenant un SAFEARRAY .

Plusieurs fonctions sont fournies pour gérer le type VARIANT :

 VariantInit() VariantClear() VariantCopy() 

Et il existe plusieurs fonctions fournies pour gérer le type SAFEARRAY parmi lesquelles:

 SafeArrayCreate() SafeArrayCreateEx() SafeArrayCopyData(); 

Microsoft a fourni plusieurs frameworks différents au fil des ans et l’un des objectives de ces frameworks et bibliothèques était la possibilité de travailler facilement avec des objects COM.

Nous allons examiner trois versions différentes des classes VARIANT pour C ++ dans les domaines suivants: (1) MFC, (2) ATL et (3) ce que Microsoft appelle le C ++ natif.

MFC est un framework complexe développé au début de la vie de C ++ pour fournir une bibliothèque très complète aux programmeurs Windows C ++.

ATL est un cadre simplifié développé pour aider les utilisateurs à créer des composants logiciels basés sur COM.

_variant_t semble être un wrapper de classe C ++ standard pour VARIANT .

La classe ADO _RecordsetPtr a la méthode Update() qui accepte un object _variant_t qui ressemble à:

 inline HRESULT Recordset15::Update ( const _variant_t & Fields, const _variant_t & Values ) { HRESULT _hr = raw_Update(Fields, Values); if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this)); return _hr; } 

MFC fournit un ensemble de classes permettant de travailler avec des objects COM, les classes du type VARIANT étant COleVariant et COleSafeArray . Si nous examinons la déclaration de ces deux classes, nous constatons ce qui suit:

 class COleVariant : public tagVARIANT { // Constructors public: COleVariant(); COleVariant(const VARIANT& varSrc); // .. the rest of the class declaration }; class COleSafeArray : public tagVARIANT { //Constructors public: COleSafeArray(); COleSafeArray(const SAFEARRAY& saSrc, VARTYPE vtSrc); // .. the rest of the class declaration }; 

Si nous examinons les versions ATL de ces classes, nous trouvons CComVariant et CComSafeArray mais CComSafeArray est un modèle C ++. Lorsque vous déclarez une variable avec CComSafeArray vous spécifiez le type des valeurs devant figurer dans la structure SAFEARRAY sous-jacente. Les déclarations ressemblent à:

 class CComVariant : public tagVARIANT { // Constructors public: CComVariant() throw() { // Make sure that variant data are initialized to 0 memset(this, 0, sizeof(tagVARIANT)); ::VariantInit(this); } // .. other CComVariant class stuff }; // wrapper for SAFEARRAY. T is type stored (eg BSTR, VARIANT, etc.) template ::type> class CComSafeArray { public: // Constructors CComSafeArray() throw() : m_psa(NULL) { } // create SAFEARRAY where number of elements = ulCount explicit CComSafeArray( _In_ ULONG ulCount, _In_ LONG lLBound = 0) : m_psa(NULL) { // .... other CComSafeArray class declaration/definition }; 

La classe _variant_t est déclarée comme suit:

 class _variant_t : public ::tagVARIANT { public: // Constructors // _variant_t() throw(); _variant_t(const VARIANT& varSrc) ; _variant_t(const VARIANT* pSrc) ; // .. other _variant_t class declarations/definition }; 

Nous constatons donc une petite différence entre la manière dont les trois frameworks différents (MFC, ATL et C ++ natif) font VARIANT et SAFEARRAY .

Tous les trois ont une classe pour représenter un VARIANT qui est dérivé de la struct tagVARIANT qui permet à tous les trois d’être utilisés de manière interchangeable sur plusieurs interfaces. La différence réside dans la manière dont chacun gère un SAFEARRAY . L’ COleSafeArray MFC fournit COleSafeArray qui dérive de struct tagVARIANT et enveloppe la bibliothèque SAFEARRAY . La structure ATL fournit CComSafeArray qui ne dérive pas de struct tagVARIANT mais utilise plutôt la composition plutôt que l’inheritance.

La classe _variant_t possède un ensemble de constructeurs acceptant un VARIANT ou un pointeur sur un VARIANT , ainsi que des méthodes d’opérateur pour l’affectation et la conversion acceptant un VARIANT ou un pointeur sur un VARIANT .

Ces méthodes _variant_t pour VARIANT fonctionnent avec la classe ATL CComVariant et avec les COleVariant MFC COleVariant et COleSafeArray car elles sont toutes dérivées de la struct tagVARIANT qui est VARIANT . Cependant, la classe de modèle ATL CComSafeArray ne fonctionne pas bien avec _variant_t car elle n’hérite pas de struct tagVARIANT .

Pour C ++, cela signifie qu’une fonction prenant un argument de _variant_t peut être utilisée avec ATL CComVariant ou avec MFC COleVariant et COleSafeArray mais ne peut pas être utilisée avec ATL CComSafeArray . Cela générera une erreur de compilation telle que:

 no suitable user-defined conversion from "const ATL::CComSafeArray" to "const _variant_t" exists 

Voir Conversions de types définis par l’utilisateur (C ++) dans la documentation de Microsoft Developer Network pour une explication.

La CComSafeArray plus simple pour un object CComSafeArray semble être de définir une classe CComSafeArray de CComSafeArray , puis de fournir une méthode fournissant un object VARIANT qui englobe l’object SAFEARRAY de CComSafeArray dans un VARIANT .

 struct CDBsafeArray: public CComSafeArray { int m_size; HRESULT m_hr; CDBsafeArray(int nSize = 0) : m_size(nSize), m_hr(0) { // if a size of number of elements greater than zero specified then // create the SafeArray which will start out empty. if (nSize > 0) m_hr = this->Create(nSize); } HRESULT CreateOneDim(int nSize) { // remember the size specified and create the SAFEARRAY m_size = nSize; m_hr = this->Create(nSize); return m_hr; } // create a VARIANT representation of the SAFEARRAY for those // functions which require a VARIANT rather than a CComSafeArray. // this is to provide a copy in a different format and is not a transfer // of ownership. VARIANT CreateVariant() const { VARIANT m_variant = { 0 }; // the function VariantInit() zeros out so just do it. m_variant.vt = VT_ARRAY | VT_VARIANT; // indicate we are a SAFEARRAY containing VARIANTs m_variant.parray = this->m_psa; // provide the address of the SAFEARRAY data structure. return m_variant; // return the created VARIANT containing a SAFEARRAY. } }; 

Cette classe serait ensuite utilisée pour contenir les noms de champ et les valeurs de ces champs _RecordsetPtr méthode ADO _RecordsetPtr de Update() s’appellerait ainsi:

 m_hr = m_pRecordSet->Update(saFields.CreateVariant(), saValues.CreateVariant());