[转]VC++连接/操作MySQL数据库

这是一篇非常基础的文章。想必每个刚开始学写程序的人,都很有可能会遇到这样的问题:如何用Visual C++编写一个可以操作(读写数据)MySQL的程序?
由于一直在Linux下工作,我很久没写Windows程序了,但是最近急于写一个操作MySQL的Windows程序来用,于是又重新拿起了VC++,写了一个MFC应用程序,临阵磨枪,不亮也光嘛。
在写这个程序的过程中,遇到的有些问题觉得挺扯蛋的,于是干脆就把整个过程给记下来了,整理成这篇文章,知识共享。
开发环境:Visual Studio 2005,Win 7 32位

文章来源:http://www.codelast.com/
【1】在开发VC++程序时,怎样访问MySQL
方法很多,我只就我了解过的来说一说。
①ADO
为了开发方便,你可以通过ADO来连接MySQL,这也是很多教程提到的方法,写起程序来确实简单,但它有一个问题:你需要先安装MySQL ODBC Driver,并在OS里配置一个数据源,否则程序写好了也是不能连接MySQL的。因此,你在一台计算机上配置好了之后,把你写的程序拷贝到另一台计算机上,又要在OS里重新安装MySQL ODBC Driver并配置数据源,这得有多蛋疼啊?!我觉得这是减轻了编码的工作,却增加了部署的工作,因此,不考虑这种方法,放弃ADO。

不过,这里还是要提一下用ADO访问MySQL的代码有多么简单:
在你的VC++项目中初始化ADO(例如在stdafx.h中):

1
#import "msado15.dll" no_namespace rename("EOF","adoEOF")

其中,msado15.dll是在Windows系统盘里的一个文件,你可以搜索到。可以把它拷贝到工程项目下,就无需写完整路径了。
然后,在操作数据库之前(一般是在项目的App类中),还要初始化一次:

1
2
3
4
if (!AfxOleInit()) {
    AfxMessageBox("初始化失败!");
    return FALSE;
}

无需其他特殊的设置,接着就可以开始写访问MySQL的程序了,关于读写MySQL的示例代码,这里就不附上了。
没错,就这么简单,你无需在项目中链接到任何MySQL的lib,也无需去找MySQL相关的头文件。ADO确实很适合想要在代码编写上省事的人。
文章来源:http://www.codelast.com/
而下面所说的几种方法都是在开发阶段麻烦,但在部署阶段简单(拷贝相关文件到目标机器上即可用,无需配置数据源)。所以,你可以根据实际情况做出取舍。
②使用MySQL C API
MySQL C API是什么?官方说明如下:

The C API provides low-level access to the MySQL client/server protocol and enables C programs to access database contents.

你可以通过它来访问MySQL。我最终用的就是这种方法。
③使用MySQL C++ Connector
MySQL C++ Connector是什么?官方说明如下:

MySQL Connector/C++ is a MySQL database connector for C++. It lets you develop C++ applications that connect to the MySQL Server.

那么,它与MySQL的C API有什么区别呢?为什么要使用它?官方说明如下:

MySQL Connector/C++ offers the following benefits for C++ users compared to the MySQL C API (MySQL client library):
Convenience of pure C++; no C function calls required
Supports JDBC 4.0, an industry standard API
Supports the object-oriented programming paradigm
Reduces development time
Licensed under the GPL with the FLOSS License Exception
Available under a commercial license upon request
可见其优点多多。但有一点要注意:如果使用MySQL C++ Connector,在开发阶段比MySQL C API要做更多的工作(下载、配置各种依赖库,等等),如果你想稍微简单一点,就使用MySQL C API;如果你不嫌麻烦,就使用MySQL C++ Connector,它的优点正如上面所说,这里就不复述了。
MySQL C++ Connector的下载链接在这里。你可以根据你的平台下载相应的版本,例如“Windows (x86, 32-bit), ZIP Archive”,解压出来会看到 include 和 lib 这两个目录,里面的文件都是我们需要的。

你可以参考MySQL官方的这篇文章。它比较详细地描述了如何创建一个VC++工程并使用MySQL C++ Connector的方法。
文章来源:http://www.codelast.com/
这里不得不提醒你的是,前面已经说了,比起使用MySQL C API,用MySQL C++ Connector来开发程序要做更多的工作,体现在:MySQL C++ Connector依赖于MySQL库、Boost,因此你还要先有MySQL的开发包、Boost库。Boost是个麻烦的东西,因为它很大,编译出lib很耗时间,很耗CPU资源,如果你和我一样,用的是一台老爷机,那么你会感觉到编译出Boost lib的过程异常痛苦。另外,在撮合了MySQL C++ Connector、Boost、MySQL的一个项目中,如果你的项目参数设置不对,会出现各种奇怪的问题,下面我列举一二:
㈠LINK : warning LNK4098: 默认库“MSVCRT”与其他库的使用冲突;请使用 /NODEFAULTLIB:library
解决办法是修改VC++项目属性:
项目→编辑项目属性→链接器→输入→“忽略特定库”里填上“MSVCRT.lib”(不含引号)。

㈡msvcprt.lib(MSVCP80.dll) : error LNK2005: “public: __thiscall std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const &)” (??0?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAE@ABV01@@Z) 已经在 XXX.obj 中定义
解决办法是修改VC++项目属性:
项目→编辑项目属性→C/C++→代码生成→运行时库→修改为“多线程调试 DLL (/MDd)”或“多线程 DLL (/MD)”
同时,因为此项修改,可能会导致另一个编译错误:
fatal error C1189: #error :  Building MFC application with /MD[d] (CRT dll version) requires MFC shared dll version. Please #define _AFXDLL or do not use /MD[d]
如果看到这个错误提示,那么就要再次修改项目属性:
项目→编辑项目属性→配置属性→常规→MFC的使用→修改为“在共享 DLL 中使用 MFC”
然后就解决了此问题。
文章来源:http://www.codelast.com/
这可能只是各种编译问题中的一小部分,除非你运气好,没遇到这些问题,或者你愿意花很多时间来搞定它,否则你一定不想在时间紧张的时候看到这些烦人的错误。

【2】头文件,lib的目录结构
综上所述,我使用的是MySQL C API,那么我需要拿到相关的头文件和lib文件。我是先下载到 mysql-installer-community-5.6.11.0.msi 这个MySQL的安装包,然后(无需安装)用Universal Extractor把它解压出来,得到 mysql-5.6.11-win32.msi 文件,再次使用Universal Extractor解压,最终会拿到include和lib两个目录,就是我们所需要的。然后就把它们都放到项目目录下,我是像下面这样组织目录结构的:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
├─include
│  └─mysql
│      └─server
│          │  big_endian.h
│          │  (后面省略)
│          └─mysql
│              │  client_authentication.h
│              │  (后面省略)
│              └─psi
│                      mysql_file.h
│                      (后面省略)
│                     
├─lib
│  └─mysql
│      └─server
│          │  libmysql.dll
│          │  libmysql.lib
│          │  mysqlclient.lib
│          │ 
│          ├─debug
│          │      mysqlclient.lib
│          │     
│          └─plugin
│                  adt_null.dll
│                  auth.dll
│                  auth_test_plugin.dll
│                  libdaemon_example.dll
│                  mypluglib.dll
│                  qa_auth_client.dll
│                  qa_auth_interface.dll
│                  qa_auth_server.dll
│                  semisync_master.dll
│                  semisync_slave.dll
│                  validate_password.dll

其中,include下的是头文件,lib下的是库文件,它们下面的boost、connector、server分别是Boost、MySQL C++ Connector、MySQL的文件。
有了这样的目录结构,VC++项目怎么设置?
文章来源:http://www.codelast.com/
【3】Visual C++项目设置
打开项目属性设置对话框,依次设置如下(无论是对Debug,还是Release,都这样设置):
①“配置属性”→“常规”→“MFC的使用”→“在静态库中使用MFC”(这一项不一定非要这样设置)。
②“配置属性”→“C/C++”→“常规”→“附加包含目录”,把上面提到的include目录添加进去。对应到上面所说的层级结构,它应该是 XXX\include\mysql\server 这样的目录。如下图所示(后面的步骤也类似,不再图示):

VC++ project properties
VC++ add include directory

文章来源:http://www.codelast.com/
③“配置属性”→“链接器”→“常规”→“附加库目录”,把上面提到的lib目录添加进去。对应到上面所说的层级结构,它应该是 XXX\lib\mysql\server 这样的目录。
④“配置属性”→“链接器”→“输入”→“附加依赖项”,填入“libmysql.lib”(不包含引号)。
文章来源:http://www.codelast.com/
【4】程序编写
我添加了一个类DataAccess,所有的数据库操作都通过这个类完成。这个类大概是长这个样子的(由于是截取下来的代码片段,所以如果有错误导致无法编译,还请见谅):
头文件:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
 * DataAccess.h
 *
 * @author Darran Zhang @ codelast.com
 */
#pragma once
#include "stdafx.h"
#include <string>
#include <winsock.h>
#include "mysql.h"
class CDataAccess
{
public:
  CDataAccess(void);
  ~CDataAccess(void);
private:
  MYSQL* m_pSQL; 
  MYSQL_RES* m_pSQLResultSet;
protected:
  BOOL m_bIsConnect;    // 数据库是否已连接
public:
  BOOL ConnectDB(const std::string &strHost, const int nPort, const std::string &strDBName, const std::string &strUserName, const std::string &strPassword);
  BOOL ExecuteQuery(std::string &strSQL);
};

其中,如果没有包含 winsock.h 这个文件的话,编译时会报错:

mysql_com.h(XXX) : error C2146: syntax error : missing ‘;’ before identifier ‘fd’

m_pSQL 和 m_pSQLResultSet 这两个指针的类型是在MySQL C API中定义的。
ConnectDB()函数用于连接MySQL,ExecuteQuery()函数用于执行一句SQL。
文章来源:http://www.codelast.com/
实现文件:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/**
 * DataAccess.cpp
 *
 * @author Darran Zhang @ codelast.com
 */
#include "DataAccess.h"
using namespace std;
CDataAccess::CDataAccess(void)
{
  m_pSQL = NULL;
  m_pSQLResultSet = NULL;
  m_bIsConnect = FALSE;
}
CDataAccess::~CDataAccess(void)
{
  if(m_pSQL) {
    mysql_close(m_pSQL);
  }
  m_pSQL = NULL;
  m_pSQLResultSet = NULL;
}
BOOL CDataAccess::ConnectDB(const string &strHost, const int nPort, const string &strDBName, const string &strUserName, const string &strPassword)
{
  if (m_bIsConnect) {
    MessageBox(AfxGetMainWnd()->m_hWnd, _T("数据库已连接"), _T("提示"), MB_ICONWARNING | MB_OK);
    return TRUE;
  }
  mysql_library_init(0, NULL, NULL);  // 初始化MySQL C API库
  if( (m_pSQL = mysql_init(NULL)) == NULL) {
    MessageBox(AfxGetMainWnd()->m_hWnd, _T("初始化MySQL连接失败"), _T("错误"), MB_ICONERROR | MB_OK);
    return FALSE;
  }
  mysql_options(m_pSQL, MYSQL_SET_CHARSET_NAME, "gb2312");
  if (NULL == mysql_real_connect(m_pSQL, strHost.c_str(), strUserName.c_str(), strPassword.c_str(), strDBName.c_str(), nPort, NULL, 0)) {
      MessageBox(AfxGetMainWnd()->m_hWnd, _T("连接MySQL失败"), _T("错误"), MB_ICONERROR | MB_OK);
      return FALSE;
  }
  m_bIsConnect = TRUE;
  return m_bIsConnect;  // 返回连接是否成功的标志
}
BOOL CDataAccess::ExecuteQuery(string &strSQL)
{
  if (!m_bIsConnect) {
    MessageBox(AfxGetMainWnd()->m_hWnd, _T("尚未连接MySQL"), _T("错误"), MB_ICONERROR | MB_OK);
    return FALSE;
  }
  size_t nReturnValue = mysql_real_query(m_pSQL, strSQL.c_str(), strSQL.length());
  return (0 == nReturnValue) ? TRUE : FALSE;
}

这个类的使用也很简单,构造一个DataAccess类对象来调用其方法即可。
注意:有一句代码 mysql_options(m_pSQL, MYSQL_SET_CHARSET_NAME, “gb2312”) 的作用是设置连接的字符集,我记得我测试的时候,使用gbk字符集的话,向数据库中插入的中文是乱码,所以在这里我将它设置为gb2312,经测试就没有问题了。
文章来源:http://www.codelast.com/
上面的代码片段甚至于没有演示如何取出一个SQL查询得到的记录集,因此在这里补充一下。
假设你有一张名为“student”的MySQL表,有两列:ID(主键),name(字符串)。现在要用程序取出该表里的所有记录,可以像下面那样做:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
string strSQL = "SELECT * FROM student";
if(mysql_query(m_pSQL, strSQL.c_str())) {
  MessageBox(AfxGetMainWnd()->m_hWnd, _T("查询数据库失败"), _T("错误"), MB_ICONERROR | MB_OK);
  return FALSE;
}
m_pSQLResultSet = mysql_store_result(m_pSQL);
MYSQL_ROW pSQLRow = NULL;
while (pSQLRow = mysql_fetch_row(m_pSQLResultSet)) {   // 循环读出每一行
  for (int i = 0; i <= 1; i++) { // 循环读出每一列
    string strFieldValue = pSQLRow[i];
    if (0 == i) { // 第1列
      long nId = atol(strFieldValue.c_str());
    } else if (1 == i) {  // 第2列
      string name = strFieldValue;
    }
  }
}
mysql_free_result(m_pSQLResultSet);   // 释放资源
return TRUE;

文章来源:http://www.codelast.com/
上面的代码还不完整,只是演示了怎么读取SQL查询的结果集。但是有了上面的一系列铺垫,你应该可以写出自己的程序来用了。

【5】后记
工作就是这样,谁能想到自己整天在Linux下工作,却会在一连串相似的任务到来之后,自己被逼着要写一个Windows程序来减轻自己的工作量呢?归根结底还是懂得太少,谁让我完全不懂Linux的GUI程序开发呢?要不然也不会重拾多年前的VC++…

发表评论

电子邮件地址不会被公开。