一 关于初始化ADODB
需要在InitInstance()中首先调用AfxOleInit()对OLE进行初始化,之后要导入
c:\program files\common files\system\ado\msado15.dll,经过以上几步操作,接下来可以通过调用_ConnectionPtr和_RecordsetPtr来建立连接对象与记录集对象。为了简化编程,我封装了一个很简单的类库CDB,实现了对_ConnectionPtr的简单调用。
// DB.h: interface for the CDB class.
//
//////////////////////////////////////////////////////////////////////
#if !defined(__DB_H__)
#define __DB_H__
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#import "c:\program files\common files\system\ado\msado15.dll" no_namespace \
rename("EOF", "adoEOF") \
rename("LockTypeEnum", "adoLockTypeEnum") \
rename("DataTypeEnum", "adoDataTypeEnum")
#include "icrsint.h"
inline void TESTHR(HRESULT x) { if FAILED(x) _com_issue_error(x); }
inline BOOL IS_VT_NULL(unsigned short x) { return (x == VT_NULL ? TRUE : FALSE); }
class CDB
{
public:
CDB();
virtual ~CDB();
_RecordsetPtr Exec(CString& strSql);
void Close();
private:
_ConnectionPtr m_Conn;
};
class CSecUtil
{
public:
static CString StrReplace(LPTSTR lpszString, LPTSTR lpszKey, LPTSTR lpszReplace);
static BOOL StrCheck(LPCTSTR);
};
#endif // !defined(__DB_H__)
// DB.cpp: implementation of the CDB class.
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "DB.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
//////////////////////////////////////////////////////////////////////
// CDB Construction/Destruction
//////////////////////////////////////////////////////////////////////
CDB::CDB()
{
try
{
m_Conn.CreateInstance(__uuidof(Connection));
// _bstr_t connstr = "driver={SQL Server};Server=127.0.0.1;DATABASE=;UID=;PWD="; // 针对mssql
_bstr_t connstr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=;user id=admin;jet oledb:database password="; // 针对ACCESS
}
catch(_com_error& e)
{
AfxMessageBox(e.Description());
}
}
CDB::~CDB()
{
if(m_Conn->State)
{
m_Conn->Close();
}
}
_RecordsetPtr CDB::Exec(CString& strSql)
{
_RecordsetPtr rs;
try
{
rs = m_Conn->Execute((_bstr_t)strSql, NULL, adCmdText);
}
catch(_com_error& e)
{
AfxMessageBox(e.Description());
}
return rs;
}
void CDB::Close()
{
if(m_Conn->State)
{
m_Conn->Close();
}
}
使用时先按需要填充connstr,之后可以使用
CDB db;
建立一个对象,调用db.Exec来执行sql语句,其内部通过调用连接对象的Execute()方法来实现,Exec()方法调用后返回一个_RecordsetPtr的对象。调用db.close()关闭数据库。
二 类型转换
成功执行一次SELECT操作后从数据库中取出的记录保存在由_RecordsetPtr所定义的记录集对象中,通过调用GetCollect方法,可以将记录取出,如:
CDB db;
_RecordsetPtr rs;
CString strSql;
strSql = "SELECT id, user FROM logon";
rs = db.Exec(strSql);
if(rs->EOF)
...
else
rs->GetCollect("id");
然而调用GetCollect()返回的类型为_variant_t(相关介绍见MSDN),需要将其转化成控件所支持得类型才能显示,当然这些数据的原始类型是由数据库字段的定义类型,可以通过判断结构中的vt这个变量来取出当前的数据类型。这个变量返回一个VARTYPE类型的值(见表1)。
以下列出的是针对ACCESS的字段类型的转换,MSSQL与ACCESS的字段类型相类似,大部分可以按照下面的方法对类型进行转换。
数字:rs->GetCollect("id").intVal;
BYTE:rs->GetCollect("id").bVal;
文本/备注/时间:(char*)(_bstr_t)(rs->GetCollect("user")
这里要注意,对于数字类型,需要判断有无符号的情况。下面是VARIANT结构对于数据类型的定义。注释后面的为VARTYPE枚举类型。对于特定类型的转换可以通过查表1来进行操作,对于VARTYPE类型的定型可以以VARENUM为关键字在MSDN中搜索(我使用的是MSDN 2003 April)。
LONGLONG llval; // VT_I8.
LONG lVal; // VT_I4.
BYTE bVal; // VT_UI1.
SHORT iVal; // VT_I2.
FLOAT fltVal; // VT_R4.
DOUBLE dblVal; // VT_R8.
VARIANT_BOOL boolVal; // VT_BOOL.
_VARIANT_BOOL bool;
SCODE scode; // VT_ERROR.
CY cyVal; // VT_CY.
DATE date; // VT_DATE.
BSTR bstrVal; // VT_BSTR.
IUnknown * punkVal; // VT_UNKNOWN.
IDispatch * pdispVal; // VT_DISPATCH.
SAFEARRAY * parray; // VT_ARRAY|*.
BYTE * pbVal; // VT_BYREF|VT_UI1.
SHORT * piVal; // VT_BYREF|VT_I2.
LONG * plVal; // VT_BYREF|VT_I4.
LONGLONG * pllVal; // VT_BYREF|VT_I8.
FLOAT * pfltVal; // VT_BYREF|VT_R4.
DOUBLE * pdblVal; // VT_BYREF|VT_R8.
VARIANT_BOOL * pboolVal; // VT_BYREF|VT_BOOL.
_VARIANT_BOOL * pbool;
SCODE * pscode; // VT_BYREF|VT_ERROR.
CY * pcyVal; // VT_BYREF|VT_CY.
DATE * pdate; // VT_BYREF|VT_DATE.
BSTR * pbstrVal; // VT_BYREF|VT_BSTR.
IUnknown ** ppunkVal; // VT_BYREF|VT_UNKNOWN.
IDispatch ** ppdispVal; // VT_BYREF|VT_DISPATCH.
SAFEARRAY ** pparray; // VT_ARRAY|*.
VARIANT * pvarVal; // VT_BYREF|VT_VARIANT.
PVOID * byref; // Generic ByRef.
CHAR cVal; // VT_I1.
USHORT uiVal; // VT_UI2.
ULONG ulVal; // VT_UI4.
ULONGLONG ullVal; // VT_UI8.
INT intVal; // VT_INT.
UINT uintVal; // VT_UINT.
DECIMAL * pdecVal // VT_BYREF|VT_DECIMAL.
CHAR * pcVal; // VT_BYREF|VT_I1.
USHORT * puiVal; // VT_BYREF|VT_UI2.
ULONG * pulVal; // VT_BYREF|VT_UI4.
ULONGLONG * pullVal; // VT_BYREF|VT_UI8.
INT * pintVal; // VT_BYREF|VT_INT.
UINT * puintVal; // VT_BYREF|VT_UINT.
(表1)
这里还有一个重要的值VT_NULL,它表示数据库中相应字段的数据为空。一些控件上无法处理VT_NULL的情况,例如:ListCtrl。如果使用ListCtrl对数据进行显示,可以考虑用下面的方法进行判断:
if(rs->GetCollect("user").vt == VT_NULL)
// record is null
else
// record is non-null
三 安全问题
现在流行SQL INJECTION,因此对于字符串的过滤是一个很重要的问题。
如果是用VB来做这样的程序,可以很容易的实现,VB中的字符串操作是很容易的,还可以使用VBSCRIPT中的正则表达式(VC中也可以使用VBSCRIPT中的正则表达式,然而方法我忘记了,记得Codeproject中有相关的文章。或者使用现成的库如BOOST,greta)。
如果不会正则表达式,也可以用下面的方法:
//
// 检查指定字符串中是否包含非法字符
//
BOOL StrCheck(LPCTSTR lpszString)
{
CString strKey = "\'\\\"%#|=+-)(*&^$@!`~:;<>,.?/";
int len = strlen(lpszString);
for(int i = 0; i < len; i++)
{
for(int j = 0; j < strKey.GetLength(); j++)
{
if(lpszString[i] == strKey.GetAt(j)) return FALSE;
}
}
return TRUE;
}
如果程序的输入需要用到"'"或"""这两个符号的话,还应该进行相应的转换。
//
// 对于ACCESS和MSSQL
//
strUser.Replace("\'", "\'\'");
strUser.Replace("\"", "\"\"");
//
// 对于MySQL
//
strUser.Replace("\'", "\\\'");
strUser.Replace("\"", "\\\"");
对于使用搜索的话,还需要过滤掉"_"和"#",经过上面几步才能保证SQL INJECTION发生的几率减少。
对于多用户的系统,应该使用MD5的方法将登录的密码进行加密。
尽量不要使用ACCESS作为开发使用的数据库,其安全性实在是太差,即便是加了密码,也可以轻松在本地破解出来。推荐使用MySQL或者是MSSQL。
本文地址:http://com.8s8s.com/it/it270.htm