[C++] Connexion à une BDD SQL Serveur

K-lo
[C++] Connexion à une BDD SQL Serveur

Tels que dans votre magazine d'octobre 2006 "Ecrire un Client C/C++ pour PostgreSQL", j'aurai aimé savoir s'il était possible de faire de même pour SQL Serveur (2000) puisque je n'ai aucune connaissance avec PostGreSQL ?

Ayant des bases de connexion à une BDD via MySQL/PHP
je m'attenderais à avoir :

*Pour se connecter :
descConnexion = connexion(arg1, arg2, ....);

*Pour effecdtuer des requêtes :
sql_fetch_object(arg1, arg2 ...);
ou CheckResult(arg1, ...); comme dans votre exemple.

Mais ça semble trop simple :P

De plus s'il était possible d'exécuter une procedure stockée...

K-lo

Voilà j'ai trouvé 2 solutions (une via OLEDB et une via ODBC) pour répondre à ma question mais je tacherais de qq chose sortir un truc propre un peu plus tard.

Je viens pour une autre question :

J'ai dans ma base (via SQL Serveur 2000) un VARCHAR(50);
Quelle concordence/équivalance de type puis-je me servir en langage C++ ?
Sous MSDN on me parle de String mais c'est pas le type de la bibliothèque STL mais du framework utilisé par Visual Studio (que je ne veux pas utiliser).
Es-ce que le type std::string est envisageable (via ou non la méthode c_srt()) ?

Si vous aviez un tableau pour les principaux types ça serait bien ;)

fredericmazue

Quote:

Je n'ai aucune connaissance avec PostGreSQL ?

C'est un tort ;)

Quote:

Voilà j'ai trouvé 2 solutions (une via OLEDB et une via ODBC)

ODBC est une solution en effet.
Que veux tu dire par "via OLEDB" ? par un fournisseur de données .Net ? Si oui, c'est incontestablement la voie royale par les temps qui courent, puisque Microsoft ne jure que par sa plate-forme .Net. Mais bon on a encore le droit de vouloir faire autrement je pense :)
Pour répondre à ta première question, est-ce possible en C ou C++ comme avec PostgreSQL , je dirais que probablement oui. Il existe sans doute une API. Mais en toute honnêteté je ne la connais pas du tout. J'essaie d'éviter SQL Server que je n'aime pas. Je n'ai jamais eu à la programmez en C ou C++. En fouillant la MSDN en ligne tu vas certainement trouver un lien sur cette API.

Quote:

VARCHAR(50);
Quelle concordence/équivalance de type puis-je me servir en langage C+

Stricto sensu: aucun, car la chaîne de caractères à taille limitée n'est ni un type de C ni un type de C++. Il n'y a donc pas "d'équivalent". Mais bien entendu un tableau de caractères C, un char* ou un std::string::c_str() ça va aller dans la mesure ou la taille du tableau ou de la chaîne est correctement vérifié comme le veut le bon sens.

fredericmazue

Je viens de trouver ça dans la MSDN

http://msdn2.microsoft.com/en-us/library/ms131687.aspx

C'est peut être bien ce que tu appellais OLE DB.
Dans la MSDN il parlent de clients natifs avec ça ou ODBC.

Hem ... Bon courage avec OLE DB. c'est du COM et c'est assez inCOMmode... :twisted: Si ODBC est suffisant pour tes besoins, tu le trouveras sans doute plus facile.

K-lo

fredericmazue wrote:

C'est un tort ;)

Dsl, mais je ne suis pas contre le fait d'apprendre via PostGreSQL pour ma propre culture (mais on vera plus tard ;))

Quote:
En fouillant la MSDN en ligne tu vas certainement trouver un lien sur cette API.

En fait pas vraiment du moins pas comme je l'aurais souhaité, il nous font passer par l'ODBC qui aurait un temps de réponse plutôt long ou par OLE DB ("Api COM qui permet un accès unifié à toutes sortes de sources de données") qui est largement conseillé par MS mais qui n'est pas aussi sympathique que ce que je l'espérais mais bon...

voici un shéma type pour l'acces a une source de donnée via OLE DB.
1) Création d'un objet DataSource où l'on renseigne les paramêtres de connexion à la source de donnée
2)Création d'un objet Session (création d'une session sur la source de donnée défini par l'objet DataSource)
3)Création d'un objet Command (on définie les paramètres, les propriétés... pour exécuter les commandes)
4)Création d'un objet Rowset (pour le partage des données)

Quote:

Stricto sensu: aucun, car la chaîne de caractères à taille limitée n'est ni un type de C ni un type de C++. Il n'y a donc pas "d'équivalent". Mais bien entendu un tableau de caractères C, un char* ou un std::string::c_str() ça va aller dans la mesure ou la taille du tableau où de la chaîne est correctement vérifiée comme le veut le bon sens.

Très bien et merci!
fredericmazue

Mais attention, les chaînes de caractères en COM ça va encore être particulier et à priori en Unicode. Il y a des API Windows pour faire les convertions Unicode/Ascii et réciproquement, mais ça n'allège pas le code :(

K-lo

Le problème de l'ODBC c'est qu'il n'allège pas le temps de réponse d'un programme ;)
Bref je vais persister.
Là ou je suis bloqué c'est pour l'exécution d'une procédure stockée et comme il faut faire une liaison entre tous les paramêtres de la procédure et les variable c++, c'est un peu le calvaire surtout que la fonction qui permet de vérifier une erreur d'exécution est FAILED (celle que je connais...) et elle retourne 0 (GetLastError()) quand il y a une erreur.
donc je sais pas trop si c'est un problème de type, de constante ou d'oublis (recul toujours le recul) d'information, à passer au système ?

fredericmazue

Quote:

Le problème de l'ODBC c'est qu'il n'allège pas le temps de réponse d'un programme

Sans doute mais ça allège le temps de codage ;)
Es tu sûr que ODBC n'est pas suffisant en terme de temps de réponse ?

Quote:

faire une liaison entre tous les paramêtres de la procédure et les variable c++, c'est un peu le calvaire

Oui....

Quote:

elle retourne 0 (GetLastError())

GetLastError te retourne 0 ? Quand quel contexte, je ne comprends pas pas bien.

Quote:

donc je sais pas trop si c'est un problème de type, de constante ou d'oublis

Je regarderais les problèmes de types en premier. Je ne suis plus si tu fais de l'OLE DB ou de l'ODBC, mais attention COM ==> UNICODE.
K-lo

OLE DB

VARCHAR d'apres SQL Serveur n'est pas de l'unicode
et le problème avec l'unicode est lors de la création de l'objet DataSource lorsque l'on passe les paramêtres de connexion.
Mais il y a bien des API pour les conversion

Non je ne peux pas utiliser ODBC :( (car pour l'exécution de grosse procédure stockée ça posera problème)

fredericmazue

Quote:

VARCHAR d'apres SQL Serveur n'est pas de l'unicode

J'entends bien. Ni dans SQL Server ni autre SGDBR (par défault sauf si la base de données en encodée en unicode ce qui est encore autre chose) à ma connaissance. Ce n'est pas ce que je voulais dire.
Ce que je veux dire c'est que quand tu passes une chaîne à une API COM quelconque il est fort probable que l'API s'attende à recevoir de l'unicode.
Si l'API injecte ta chaîne dans le VARCHAR elle fera la conversion. Mais elle n'en attendra pas moins de l'unicode elle même. Regarde bien la doc des APIs que tu utilises pour éviter des déboires et mauvaises surprises.

K-lo

Oki merci pour le conseil

Je me suis rendu compte que je n'avais pas répondu à la question du FAILED

FAILED (HRESULT hr);

et si il me retourne vrai j'affiche une erreur en grace a GetLastError
je crois avoir lu qu'elle retournais 0 si ça échouait (0 sur un FAILED 2 négations s'annule ça devient un positif :lol:)

Donc voilà dans quel cadre j'utilise GetLastError

fredericmazue

Quote:

négations s'annule ça devient un positif

Quel optimisme pour de la programmation Windows/COM ... ;)

Ca veut simplement dire que GetLastError a pas su te donner l'info.
Mais tu avais bien compris je pense :)

K-lo

Désolé pour la remarque ;) la fatigue me gagne souvnet en ce moment :P
En fait ce que j'avais pas compris c'est ce que me retournais hr (de type HRESULT), soit -2147217887, qui est passé en argument de FAILED()
Jusqu'à ce que je repère l'expression DB_E_ERRORSOCCURED (en regardant le comportement de la variable hr)
Bref que dit MS à ce propos ?
Et bien plusieurs choses et la cause s'appliquant à mon problème semble être celle là :

Quote:
Les service OLEDB ne sont pas chargés si ICommandText::Execute est appelé sans demander d'abord l'interface ICommand. (ICommandText::Execute me permet d'exécuter ma procédure stockée
Quote:
Quel optimisme pour de la programmation Windows/COM
=>si je suis arrivé jusquau lancement de la procédure, jvé pas lacher le morceau maintenant :wink: )

En fait j'ai repris du code venant de SQL serveur et "ça semble" (tout est relatif) donc voilà je sais pas si ce type d'erreur peux toujours être du aux types ? ou à une erreur d'inatention (et pourant jsuis attentionné sur ce type de connexion)
:oops:

fredericmazue

Quote:

c'est ce que me retournais hr (de type HRESULT)

Comme tout appel à une API COM.
Quote:

c'est ce que me retournais hr (de type HRESULT)
DB_E_ERRORSOCCURED

Le message DB_E_ERRORSOCCURED est tout à fait clair: Ca peut être absolument n'importe quoi :lol:
Une erreur de type, une colonne qui n'existe pas (une coquille dans un nom de colonne dans la requête par exemple) etc.

Hem... bon courage.
Décidément j'aime bien PostgreSQL moi ;)

K-lo

Je vous tiens informé de mon avancer dans mon exécution des procédures stockées en C++ via OLEDB et SQL Serveur.

Donc j'ai réussit à exécuter (après nombreux tests) ma procédure stockée mais là où ça pêche c'est que mes valeurs varchar (type de la colonne SQL) sont = à 0 (le fait était qu'au depart je refusais que ces colonnes est cette valeur d'ou l'ECHEC lors de l'éxecution de la procédure)
donc j'ai pensé a ce que tu me disait, fredericmazue (:o me suis mm pas rendu compte depuis quand je te tutoie ) => Attention UNICODE.
Voui et bien voilà je n'arrive pas a trouver le bon type à utiliser :

std::string en indiquant DBTYPE_STR pour le type de paramètre que reçoit la procédure => EXE de la procédure mais colonne = à 0.

std::string en indiquant DBTYPE_WSTR => EXE de la procédure mais colonne = à 0.

LPWSTR en indiquant DBTYPE_WSTR => ECHEC de l'EXE de la procédure.

LPSTR en indiquant DBTYPE_STR => ECHEC de l'EXE de la procédure

Bref je sais pas quelle type utiliser (en fait jsuis habituer aux types de base :( )

fredericmazue

Quote:

me suis mm pas rendu compte depuis quand je te tutoie )

C'est un forum, on se tutoie :)
Quote:

donc j'ai pensé a ce que tu me disait, fredericmazue
=> Attention UNICODE.

Je t'avais prévenu ;)
Quote:

std::string en indiquant DBTYPE_STR pour le type de paramètre que reçoit la procédure => EXE de la procédure mais colonne = à 0.

Normal tu envoies de l'ASCII
Quote:

std::string en indiquant DBTYPE_WSTR => EXE de la procédure mais colonne = à 0.

Ca me parait également normal. Tu fais croire que c'est de l'unicode mais ça n'en est pas :)
Quote:
LPWSTR en indiquant DBTYPE_WSTR => ECHEC de l'EXE de la procédure.

Ca ça pourrait marcher, à condition que la chaîne pointée soit effectivement de l'unicode. Elle ne va pas être convertie par magie.
Quote:
LPSTR en indiquant DBTYPE_STR => ECHEC de l'EXE de la procédure

Tout ASCII, aucune chance.

Bon je te suggère la solution simple avant de faire compliqué. Pourquoi ne par utiliser des std::wstring ?
(attention le standard C++ ne prévoit pas de converstion de std::wstring vers std::string)

K-lo

fredericmazue wrote:

Je t'avais prévenu ;)
Quote:

Certes et j'en ai tenu compte mais ne connaissant pas vraiment les implication du a l'Unicode j'essayais de montrer les solutions employé

En plus d'un problème de type j'ai fais une erreur d'inatention (j'ai oublier de rappeler que je n'étais plus en INT mais en VARCHAR à ma procédure d'ou le 0 au lieu de caractère vide ce qui m'arrive lorsque j'utilise wstring

enfin je sais pas si je l'utilise correctement :

wstring my_wstr.assig(L"TEST");

En fait je vais faire plus de recherche au sujet de l'UNICODE et ce que ça implique, et je vous tiens au courant!

fredericmazue

Quote:

wstring my_wstr.assig(L"TEST");

C'est correct.
Mais quand à savoir si c'est ce qu'il te faut, au regard de ce que tu viens d'écrire, je ne suis pas sûr.
Poste un petit bout du code concerné, qu'on se fasse une idée

K-lo

DBBINDING acDBBinding[1];

	struct s_param{
	
		std::wstring col1;
	}
		
	s_param p1;
	p1.col1.assign(L"test1");

        //init DBBINDING 
	acDBBinding[0].obLength = 0;
	acDBBinding[0].obStatus = 0;
	acDBBinding[0].pObject = NULL;
	acDBBinding[0].pTypeInfo = NULL;
	acDBBinding[0].pBindExt = NULL;
	acDBBinding[0].dwPart = DBPART_VALUE;
	acDBBinding[0].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
	acDBBinding[0].dwFlags = 0;
	acDBBinding[0].bScale = 0;

	
	acDBBinding[0].iOrdinal = 1;
	acDBBinding[0].obValue = offsetof(p1, col1);
	acDBBinding[0].eParamIO = DBPARAMIO_INPUT;
	acDBBinding[0].cbMaxLen= 50;
	acDBBinding[0].wType = DBTYPE_WSTR;
	acDBBinding[0].bPrecision = 0;

	params.pData = &p1;
	params.cParamSets = 1;
	params.hAccessor = hAccessor;

	if( FAILED( hr = pICommandText->Execute( NULL, 
		IID_IRowset, 
		&params, 
		&cNumRows, 
		(IUnknown **)&pIRowset ) ) 
		){
			cerr<<"Echec lors de l'execution de la procedure "<<endl;
			return 1;
	}

	cout <<"Execution de la procedure stockee, terminee."<<endl;


	

Je vous ai bien évidemment épargné les nombreuses déclarations qui ne sont pas utiles ici.

K-lo

si j'utilise un CHAR * ou un PSTR a la place du wstring, il va me stocké une drole de valeur comme si il me rentrait le nom de l'adresse et pas la valeur...

fredericmazue

Quote:

Je vous ai bien évidemment épargné les nombreuses déclarations qui ne sont pas utiles ici.

Ah bon ?

Bon quoi qu'il en soit je vois au moins une chose qui ne va pas.

Quote:
   struct s_param{
   
      std::wstring col1;
   }
      
   s_param p1;
   p1.col1.assign(L"test1"); 

Quel est l'intérêt de faire ça, sinon de t'enduire d'erreur ? D'alleurs l'erreur ne se fait pas attendre à ce qu'il me semble:

Quote:
 params.pData = &p1; 

puis

Quote:
if( FAILED( hr = pICommandText->Execute( NULL,
      IID_IRowset,
      &params,
      &cNumRows,
      (IUnknown **)&pIRowset ) )
      ){
         cerr<<"Echec lors de l'execution de la procedure "<<endl;
         return 1;
   } 

Boum!
Tu passes une référence d'un bidule params à une API. Et le params.pData contient une référence sur une chaîne C++ standard, c'est à dire un objet.
Il me semble que params.pData ça doit être un pointeur en dur sur des données, en l'occurence le tampon qui contient les caractères.

Je verrais plutôt:

params.pData = p1.c_str();

En plus là si j'en crois ton bout de code, si ça se trouve une string et non une wstring est ce qu'il te faut. Parce que tu ne passe pas directement une chaîne à une API COM là.

La déclaration de params j'aurais été curieux de la voir d'ailleurs.

K-lo

Dsl autant pour moi
DBPARAMS params;
(en fait j'ai repris 1 exemple de MS et ça jlé pas modifié)
:oops:

Sinon j'ai réglé mon erreur en faisant comme ceci :

typedef struct  tagParamADef{

    char nomValeur[100];
    char valeur[100];

}PARAMADEF;

PARAMADEF p1;

strcpy(p1.nomValeur, "nomVal1"); /*strcpy(p1.nomValeur, varNomVal1.c_str()); dans l'intérêt ou on dépasse pas la mémoire alloué*/
strcpy(p1.valeur, "valeur1"); /*strcpy(p1.valeur, varVal.c_str());*/


for (int i=0 ; i<=1; i++){
     acDBBinding[i].obLength = 0;
     acDBBinding[i].obStatus = 0;
     acDBBinding[i].pObject = NULL;
     acDBBinding[i].pTypeInfo = NULL;
     acDBBinding[i].pBindExt = NULL;
     acDBBinding[i].dwPart = DBPART_VALUE;
     acDBBinding[i].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
     acDBBinding[i].dwFlags = 0;
     acDBBinding[i].bScale = 0;
} 

   
acDBBinding[0].iOrdinal = 1;
acDBBinding[0].obValue = offsetof(PARAMADEF, nomValeur);
acDBBinding[0].eParamIO = DBPARAMIO_INPUT;
acDBBinding[0].cbMaxLen= 100;
acDBBinding[0].wType = DBTYPE_STR;
acDBBinding[0].bPrecision = 0; 

acDBBinding[1].iOrdinal = 2;
acDBBinding[1].obValue = offsetof(PARAMADEF, valeur);
acDBBinding[1].eParamIO = DBPARAMIO_INPUT;
acDBBinding[1].cbMaxLen= 100;
acDBBinding[1].wType = DBTYPE_STR;
acDBBinding[1].bPrecision = 0; 


params.pData = &p1;
params.cParamSets = 1;
params.hAccessor = hAccessor;
cNumRows=0;

if( FAILED( hr = pICommandText->Execute( NULL,
      IID_IRowset,
      &params,
      &cNumRows,
      (IUnknown **)&pIRowset ) )
      ){
         cerr<<"Echec lors de l'execution de la procedure "<<endl;
         return 1;
} 

Enfin c'est relatif de dire que j'ai réglé mon erreur car même si ça s'inscrit bien dans ma base (et que j'ai fait moins de faute à la recopie sur le topic) ça semble pas très beau ?

fredericmazue

Quote:
typedef struct  tagParamADef{

    char nomValeur[100];
    char valeur[100];

}PARAMADEF;

PARAMADEF p1; 

C'est une déclaration de style C ça :) (mais peu importe)

Quote:
Enfin c'est relatif de dire que j'ai réglé mon erreur car même si ça s'inscrit bien dans ma base (et que j'ai fait moins de faute à la recopie sur le topic) ça semble pas très beau ?

C'est pas beau, c'est le moins qu'on puisse dire. Mais à qui la faute ? Le DBPARAMS de Microsoft étant ce qu'il est, il n'y a pas beaucoup mieux à faire. Sauf à encapsuler ça (DBPARAMS) dans une classe bien sentie et en définissant un opérateur & et des choses de ce genre. Mais faut en avoir l'usage pour se lancer là-dedans. Toute la question est de savoir si le jeu en vaut la chandelle.

K-lo

Je sais pas encore si ça en vaut le coup
mais en tout cas merci

fredericmazue

C'était avec plaisir. :)