2016-09-03 1 views
1

J'étudie plusieurs types de projets Visual Studio 2015 C++ qui utilisent ADO pour accéder à une base de données SQL Server. L'exemple simple effectue une sélection sur une table, lit dans les lignes, met à jour chaque ligne et met à jour la table.Différences d'utilisation entre _variant_t, COleVariant, CComVariant et VARIANT et en utilisant les variations SAFEARRAY

La version MFC fonctionne correctement. La version de la console Windows est l'endroit où j'ai un problème de mise à jour des lignes dans le jeu d'enregistrements. La méthode update() du jeu d'enregistrements est de lancer une exception COM avec le texte d'erreur:

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 objet jeu d'enregistrements ADO standard défini comme;

_RecordsetPtr  m_pRecordSet; // recordset object 

Dans la version MFC, la fonction de mettre à jour la ligne en cours dans le recordset est:

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 objets COleSafeArray composés dans une classe d'aide pour le rendre plus facile à spécifiez les noms et les valeurs des colonnes à mettre à jour.

// Create my connection string and specify the target database in it. 
// Then connect to SQL Server and get access to the database for the following actions. 
CString ConnectionString; 
ConnectionString.Format(pConnectionStringTemp, csDatabaseName); 

CDBconnector x; 
x.Open(_bstr_t(ConnectionString)); 

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

Parce que la version console de Windows n'utilise pas MFC, je suis en cours d'exécution dans des problèmes de définition qui semblent être dues à ATL classe COM CComSafeArray étant un modèle.

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

Cependant, lorsque je tente d'utiliser une variable définie avec ce modèle, une classe CDBsafeArray dérivée de CComSafeArray<VARIANT>, je reçois l'erreur de compilation suivante 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 classe wrapper pour VARIANT et il ne semble pas y avoir un chemin de conversion entre CComSafeArray<VARIANT> 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 compile cependant je vois l'exception COM ci-dessus en testant l'application. En regardant dans l'objet avec le débogueur, l'objet spécifiant les champs à mettre à jour semble être correct.

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

+0

Quelque chose comme cela devrait fonctionner: '_variant_t var; var.parray = myarray.Detach(); var.vt = VT_ARRAY | VT_VARIANT; '. Où 'myarray' est un' CComSafeArray '. Il ne semble pas qu'il y ait un wrapper safearray qui interagisse naturellement avec '_variant_t', donc vous devez descendre au niveau brut de' VARIANT'. –

+0

@IgorTandetnik, merci pour l'aide. Je vais regarder ce chemin et voir où cela mène. Je me demandais si j'aurais besoin de faire quelque chose comme ça. Tout en travaillant avec l'exemple MFC à un moment donné, j'avais ma propre wrapper pour 'VARIANT' jusqu'à ce que je compris que MFC a des classes qui étaient beaucoup plus agréable et je l'espère plus que l'épreuve des balles mon travail. –

+0

@IgorTandetnik, ce que je cherche en fait la classe la '' CDBsafeArray' dérivent de tagVARIANT' puis en utilisant le '' CComSafeArray pour créer un membre dans la classe 'CDBsafeArray',' m_SafeArray'. Ensuite, je modifie la partie 'tagVARIANT' de la classe' CDBsafeArray' pour ressembler à un tableau de VARIANT avec 'vt = VT_ARRAY | VT_VARIANT, 'et' parray = m_SafeArray.m_psa, 'qui est à l'origine l'objet' CDBsafeArray' pour ressembler à un SAFEARRAY de Variant. Cela fonctionne bien avec mon exemple limité mais je soupçonne qu'il y aurait des problèmes avec quelque chose de plus compliqué que ce que je fais. –

Répondre

2

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

Microsoft spécifie le type VARIANT qui est représenté en tant que C/C++ struct qui contient un certain nombre de champs. Les deux parties principales de ce struct sont un champ qui contient 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 qui contient des données de gestion de tableau, des données sur le tableau telles que le nombre d'éléments qu'il contient, ses dimensions et les limites supérieures et inférieures (les données de limites vous permettent d'avoir des plages d'index arbitraires).

Le C/C code source de un VARIANT ressemble à quelque chose comme ce qui suit (tous les composants struct et union membres semblent être anonymes, par exemple __VARIANT_NAME_2 est #defined ê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 VARIANT tapez dans les interfaces d'objet COM pour fournir la possibilité de transmettre des données à travers l'interface et de faire 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 l'un est SAFEARAY. Vous pouvez donc utiliser un VARIANT pour passer un SAFEARRAY sur une interface. Plutôt que d'avoir une interface explicite SAFEARRAY, vous pouvez spécifier à la place une interface VARIANT qui reconnaîtra et traitera un VARIANT qui contient un SAFEARRAY.

Il existe plusieurs fonctions qui permettent de gérer le type VARIANT dont certains sont:

VariantInit() 
VariantClear() 
VariantCopy() 

Et il y a plusieurs fonctions qui permettent de gérer le type SAFEARRAY dont certains sont:

SafeArrayCreate() 
SafeArrayCreateEx() 
SafeArrayCopyData(); 

Microsoft a fourni plusieurs cadres différents au cours des années et l'un des objectifs de ces cadres et bibliothèques a été la capacité de travailler facilement avec des objets COM.

Nous examinerons trois versions différentes de classes VARIANT pour C++ dans les éléments suivants: (1) MFC, (2) ATL et (3) ce que Microsoft appelle C++ natif. MFC est un framework complexe développé au début de la vie C++ pour fournir une bibliothèque très complète pour les programmeurs Windows C++. ATL est un framework plus simple développé pour aider les gens à créer des composants logiciels basés sur COM. Le _variant_t semble être un wrapper de classe C++ standard pour VARIANT.

La classe ADO _RecordsetPtr a la méthode Update() qui accepte un objet _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 pour travailler avec des objets COM avec les classes pour le type VARIANT étant COleVariant et COleSafeArray . Si nous regardons la déclaration de ces deux classes, nous voyons les suivantes:

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 regardons les versions ATL de ces classes ce que nous trouvons est CComVariant et CComSafeArray cependant CComSafeArray est un modèle de C++. Lorsque vous déclarez une variable avec CComSafeArray, vous spécifiez le type des valeurs à contenir 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 (e.g. BSTR, VARIANT, etc.) 
template <typename T, VARTYPE _vartype = _ATL_AutomationType<T>::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 
}; 

donc ce que nous voyons est une petite différence entre la façon dont les trois cadres différents (MFC, ATL et C++ natif) faites VARIANT et SAFEARRAY.

Tous les trois ont une classe pour représenter un VARIANT qui est dérivé du struct tagVARIANT qui permet aux trois d'être utilisés interchangeables entre les interfaces. La différence est la façon dont chacun gère un SAFEARRAY. Le cadre MFC fournit COleSafeArray qui dérive de struct tagVARIANT et enveloppe la bibliothèque SAFEARRAY. Le framework ATL fournit CComSafeArray qui ne dérive pas de struct tagVARIANT mais utilise plutôt la composition plutôt que l'héritage.

La classe _variant_t a un ensemble de constructeurs qui acceptent un VARIANT ou un pointeur vers une VARIANT ainsi que des méthodes de l'opérateur d'affectation et de conversion qui acceptent un VARIANT ou pointeur vers un VARIANT.

Ces _variant_t méthodes pour VARIANT travail avec l'ATL CComVariant classe et avec le MFC COleVariant et COleSafeArray les classes parce que ceux-ci sont tous dérivés de struct tagVARIANT qui est VARIANT. Toutefois, la classe de modèle ATL CComSafeArray ne fonctionne pas correctement avec _variant_t car elle n'hérite pas de struct tagVARIANT.

Pour C++, cela signifie qu'une fonction qui prend un argument de _variant_t peut être utilisé avec l'ATL CComVariant ou avec le MFC COleVariant et COleSafeArray mais ne peut pas être utilisé avec un ATL CComSafeArray. Cela génère une erreur de compilation telles que:

no suitable user-defined conversion from "const ATL::CComSafeArray<VARIANT, (VARTYPE)12U>" to "const _variant_t" exists 

Voir User-Defined Type Conversions (C++) dans la documentation Microsoft Developer Network pour une explication.

Le travail le plus simple autour d'un CComSafeArray semble définir une classe qui dérive de CComSafeArray et ensuite fournir une méthode qui fournira un objet VARIANT qui enveloppe l'objet SAFEARRAY de la CComSafeArray à l'intérieur d'un VARIANT.

struct CDBsafeArray: public CComSafeArray<VARIANT> 
{ 
    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<VARIANT>. 
    // 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 alors utilisée pour contenir les noms de champs et les valeurs de ces champs et la méthode ADO _RecordsetPtr de Update() serait appelé comme:

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

Un problème avec la classe 'CDBsafeArray' héritant de' 'CComSafeArray est que le' CComSafeArray' destructor est pas virtuel. –

+0

lecture sur [étend une classe de base avec destructor non-virtuelle dangereux?] (Http://stackoverflow.com/questions/2597116/is-extending-a-base-class-with-non-virtual-destructor-dangerous) en utilisant [les fonctions libres plutôt que l'extension triviale d'une classe sans un destructeur 'virtual'] (https://punchlet.wordpress.com/2009/12/29/letter-the-fifth/). Cela semblerait être une meilleure approche alors permettez-moi de changer ma réponse. –