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());