15、Windows 管理规范 (WMI) 是可伸缩的系统管理结构,它采用一个统一的、基于标准的、可扩展的面向对象接口。WMI 为您提供与系统管理信息和基础 WMI API 交互的标准方法。WMI 主要由系统管理应用程序开发人员和管理员用来访问和操作系统管理信息。
WMI 可用于生成组织和管理系统信息的工具,使管理员或系统管理人员能够更密切地监视系统活动。例如,可以使用 WMI 开发一个应用程序,用于在 Web 服务器崩溃时呼叫管理员。
将 WMI 与 .NET 框架一起使用
WMI 提供了大量的规范以便为许多高端应用程序(例如,Microsoft Exchange、Microsoft SQL Server 和 Microsoft Internet 信息服务 (IIS))实现几乎任何管理任务。管理员可以执行下列任务:
• 监视应用程序的运行状况。
• 检测瓶颈或故障。
• 管理和配置应用程序。
• 查询应用程序数据(使用对象关系的遍历和查询)。
• 执行无缝的本地或远程管理操作。
WMI 结构由以下三层组成:
• 客户端
使用 WMI 执行操作(例如,读取管理详细信息、配置系统和预订事件)的软件组件。
• 对象管理器
提供程序与客户端之间的中间装置,它提供一些关键服务,如标准事件发布和预订、事件筛选、查询引擎等。
• 提供程序
软件组件,它们捕获实时数据并将其返回到客户端应用程序,处理来自客户端的方法调用并将客户端链接到所管理的基础结构。
通过定义完善的架构向客户端和应用程序无缝地提供了数据和事件以及配置系统的能力。在 .NET 框架中,System.Management 命名空间提供了用于遍历 WMI 架构的公共类。
除了 .NET 框架,还需要在计算机上安装 WMI 才能使用该命名空间中的管理功能。如果使用的是 Windows Millennium Edition、Windows 2000 或 Windows XP,那么已经安装了 WMI。否则,将需要从 MSDN 下载 WMI。
用 System.Management 访问管理信息
System.Management 命名空间是 .NET 框架中的 WMI 命名空间。此命名空间包括下列支持 WMI 操作的第一级类对象:
• ManagementObject 或 ManagementClass:分别为单个管理对象或类。
• ManagementObjectSearcher:用于根据指定的查询或枚举检索 ManagementObject 或 ManagementClass 对象的集合。
• ManagementEventWatcher:用于预订来自 WMI 的事件通知。
• ManagementQuery:用作所有查询类的基础。
System.Management 类的使用编码范例对 .NET 框架环境很适合,并且 WMI 在任何适当的时候均使用标准基框架。例如,WMI 广泛利用 .NET 集合类并使用推荐的编码模式,如 .NET 异步操作的“委托”模式。因此,使用 .NET 框架的开发人员可以使用他们的当前技能访问有关计算机或应用程序的管理信息。
请参见
使用 WMI 管理应用程序 | 检索管理对象的集合 | 查询管理信息 | 预订和使用管理事件 | 执行管理对象的方法 | 远程处理和连接选项 | 使用强类型对象
获取CPU序列号代码
string cpuInfo = "";//cpu序列号
ManagementClass cimobject = new ManagementClass("Win32_Processor");
ManagementObjectCollection moc = cimobject.GetInstances();
foreach(ManagementObject mo in moc)
{
cpuInfo = mo.Properties["ProcessorId"].value.ToString();
Console.WriteLine(cpuInfo);
Console.ReadLine();
}
获取网卡硬件地址
using System.Management;
...
ManagementClass mc = new ManagementClass("Win32_NetworkAdapterConfiguration");
ManagementObjectCollection moc = mc.GetInstances();
foreach(ManagementObject mo in moc)
{
if((bool)mo["IPEnabled"] == true)
Console.WriteLine("MAC address\t{0}", mo["MacAddress"].ToString());
mo.Dispose();
}
}
获取硬盘ID
String HDid;
ManagementClass cimobject = new ManagementClass("Win32_DiskDrive");
ManagementObjectCollection moc = cimobject.GetInstances();
foreach(ManagementObject mo in moc)
{
HDid = (string)mo.Properties["Model"].value;
MessageBox.Show(HDid );
}
16、在.NET中轻松获取系统信息(1) -WMI篇
Montaque
申明:
1、个人的一点心得,仅供参考
2、转载时候,请保留原本。概述:
不知道大家有没有这种体会?有时候为了获取系统一点点信息,比如考虑一下操作系统的版本号,或者当前屏幕的分辨率。其实说到底就是读操作系统某个方面的一个属性值而已,然后就看到我们的程序中密密麻麻的Win32 API申明,调用,代码的可读性和维护性不言而喻。到了.NET,微软提供了更为丰富的类,有很多以前要调用API的方法可以在.NET中轻而易举的调用实现。今天简单介绍一个在.NET中如何通过与WMI(Windows 管理规范)的通讯,从而得到获取信息的目的。
主要思路:
举一个获取操作系统共享目录和获取主板号的例子,介绍如何利用System.Managment下面的类获取系统相关的信息:正文:
WMI(Windows管理规范:Windows Management Instrumentation)是Microsoft基于Web的企业管理(WBEM)的实现,同时也是一种基于标准的系统管理接口。WMI最早出现在Microsoft Windows 2000系统上,但它同样可以安装在Windows NT 4和Windows 9x计算机上。WMI是一种轻松获取系统信息的强大工具。
在.NET中,有一个System.Management名空间(系统默认没有引用,我们可以手动添加引用),通过下面的Class的操作,可以查询系统软硬件的信息,先看一个简单的例子:Imports System.Management
Dim searcher As New ManagementObjectSearcher("SELECT * FROM Win32_share")
Dim share As ManagementObject
For Each share In searcher.Get()
MessageBox.Show(share.GetText(TextFormat.Mof))
Next share
运行的结果是列出了所有系统当前共享的目录、以及描述等等。分析一下上面的代码,可以看到一下几点:
1、似乎是在进行数据库操作,有点像SQL语句。其实就是SQL操作,这种语句被成WQL(WMI Query Language),实际上是标准SQL的一个子集加上了WMI的扩展.
2、WQL是个只读的查询语言,我们只能查询响应的数据,不能用UPDATE,INSERT等更新操作
3、代码很简单、通俗易懂
4、我们采用了一种MOF(托管对象格式)的显示。例子二:获取当前主板的信息
上面的例子是一个软件方面的信息,下面看一个获取硬件信息的例子,获取主板的序列号以及制造商:
Dim searcher As New ManagementObjectSearcher("SELECT * FROM Win32_BaseBoard")
Dim share As ManagementObject
For Each share In searcher.Get()
Debug.WriteLine("主板制造商:" & share("Manufacturer"))
Debug.WriteLine("型号:" & share("Product"))
Debug.WriteLine("序列号:" & share("SerialNumber"))
Next share
总结以及补充:
WMI类也是分层次的,具体可以参考msdn中的WMI;转向.NET平台开发的时候,最好能多看一些关于.NET新特性的介绍,这样可以大幅度的提升代码的开发效率以及运行效率。
解决方案 »
- vc2008菜单图标问题
- 请问为何A程序 用 WM_copydata 发送资料给B程序 ,而B却没有回应 WM_copydata 呢?
- 用create创建非模态对话框的时候使用this出错
- 关于GDI+使用内存越来越大的问题
- 如何在主窗口中显示对话框(有高分送给大家,恢复就有分)
- 跟dll 调用约定有关的问题
- 怎样将一个ListControl 里面将一个文件夹下的所有Bmp 图片显示在里面?
- 朋友们cs结构下,Server如何实时(几秒内也允许)检测多个client的在线情况?
- 急!:如何使对话框不显示在任务拦上???
- *****关于对新函数的调用##已经附加原形******
- 我想请教一个很简单的数据库的问题?
- VC++中的数据类型
2001-02-05· 胡朝晖·yesky--------------------------------------------------------------------------------7 1 2 3 下一页8
我们知道,在VB下进行基于ADO的编程相对比较简单,只要我们通过reference加载了适当的类型库以后,我们就可以正常的调用ADO对象,但是可能很多开发人员对于VC下的基于ADO,OLE DB的数据库开发就没有很多经验了。所以我们有必要先讨论一下VC下基于ADO开发的几种模式。 VC下关于ADO的操作分析 一般来说,用VC++有三种方法可以实现对ADO的操作: 1. 通过#import方法 2. 通过利用MFC OLE的ClassWizard 3. 通过Windows API中COM相关的函数在所有这三种方法种,#import是最方便的方法,它允许你产生一个类似VB的类结构。使你的开发变的很方便。 #import方法 在#import中,你需要提供所包含的类型库的路径和名称,它能够自动产生一个对GUIDs的定义,同时对自动生成对ADO对象的封装。同时,能够列举它在类型库中所能找到的类型,对任何你所引用的类型库,VC++会在编译的时候自动生成两个文件: 一个头文件(.tlh),它包含了列举的类型和对类型库中对象的定义 一个实现文件(.tli)对类型库对象模型中的方法产生封装。比如,你在stdafx.h文件中增加对msado15.dd的import以后,VC++会产生msado15.tlh和msado15.tli两个文件。 #import也能够使用一个新的类,_com_ptr_t,也被称为智能指针。智能指针能够自动执行QuyerInterface,AddRef和Release函数。对一个COM对象模型使用#import产生代码和VBA很类似。 下面的代码演示了如何使用#import在你的应用中实现对ADO的操作: #import "c:\program files\common files\system\ado\msado15.dll" \ no_namespace rename ( "EOF", "adoEOF" )对EOF进行该名是必要的,因为典型的VC++应用都已经定义了EOF作为常数-1。 下面分析用#import定义和初始化ADO对象 通常来说,操作一个自动化对象需要两个步骤:定义和初始化一个用来操作COM对象的变量。通过#import你可以在一行代码完成这个工作,通过使用智能指针(_com_ptr_t)的构造函数传递一个有效的CLSID或者是PROGID。开发人员也可以通过_com_ptr_t::CreateInstance()方法来定义对象的一个示例。具体代码如下所示: _ConnectionPtr Conn1( __uuidof( Connection ) );也可以采用下面的代码实现同样的功能 _ConnectionPtr Conn1 = NULL; file://定义对象 HRESULT hr = S_OK; hr = Conn1.CreateInstance( __uuidof( Connection ) ); file://创建实例推荐采用第二种方法,因为如果用第一种方法的话不能返回一个失败的HRESULT,所以也就不能判断ADO连接对象是成功还是失败。注意这里的__uuidof( Connection)中的Connection是在.tlh文件中定义的。通过把它传递给方法CreateInstance,实际上就是创建了一个有效的ADOConnection对象。 需要注意到的是#import中有一个属性为no_namespace,这是告诉编译器该类不在一个单独的名字空间中,使用no_namespace意味着你不需要在初始化变量的时候引用名字空间。当然如果在你的应用中需要倒入多个类型库的话,最后不要使用no_namespace,以免引起名字冲突。 下面是简单的基于ADO应用的代码,采用了#import方法: #include #import rename("EOF", "adoEOF") void main() { HRESULT hr = S_OK; file://因为没有在#import中指定no_namespace,所以必须采用ADODB::这样的形式 file://来作为变量类型 ADODB::_RecordsetPtr Rs1 = NULL; file://通过ODBC建立ADO连接 _bstr_t Connect( "DSN=AdoDemo;UID=sa;PWD=;" ); _bstr_t Source ( "SELECT * FROM Authors" ); CoInitialize(); file://初始化Rs1对象 hr = Rs1.CreateInstance( __uuidof( ADODB::Recordset ) ); file://对hr的返回正确性判断省略 Rs1->Open( Source, Connect, ADODB::adOpenForwardOnly, ADODB::adLockReadOnly, -1 ); file://这里可以对记录集Rs1进行操作 Rs1->Close(); Rs1 = NULL; ::MessageBox( NULL, "Success!", "", MB_OK ); CoUninitialize(); }
2001-02-05· 胡朝晖·yesky--------------------------------------------------------------------------------7上一页 1 2 3 下一页8
用MFC OLE创建ADO应用 MFC OLE类似于#import,能够对一个类型库产生一个封装(wrapper),但是不象#import,MFC OLE不能够从类型库中产生枚举类型,但是它能够更干净的实现ADO。MFC类CString和COleVariant隐藏了BSTRS和Variants的细节。需要注意的是,有MFC OLE产生的类封装都是继承了类ColeDispatchDriver,由ADO产生的失败的HRESULTS被封装在类ColeDispatchException中。 首先我们需要说明一下用MFC OLE ClassWizard创建ADO应用的几个不可缺少的步骤: 从Tools菜单中,选择Options,然后选择Directories tab,在Show Directories中,选择Library Files,然后在directories增加路径C:\program files\common files\system\ado,这样做的目的是设置包含ADO类型库的路径。 从View菜单中,选择ClassWizard,点击Add Class按纽并选择From A Type Library...,然后在Type Library dialog box对话框中,从C:\program files\common files\system\ado选择文件msado15.dll,在Confirm Classes对话框中,选择所有列出的类并按OK按纽,退出ClassWizard。实际上,ClassWizard为你生成了两个文件msado15.h和msado15.cpp. 下面的代码是实现ADO应用的自己编写的代码: AfxOleInit(); file://初始化COM对象 ... _Recordset Rs1; file://定义数据集对象 COleException e;
COleVariant Connect( "DSN=AdoDemo;UID=sa;PWD=;" ); COleVariant Source ( "SELECT * FROM Authors" ); file://创建数据集对象 Rs1.CreateDispatch( "ADODB.Recordset.2.0", &e ); Rs1.Open( (VARIANT) Source, (VARIANT) Connect, 0, 1, -1 ); file://这里可以对结果集Rs1进行处理 Rs1.Close(); Rs1.ReleaseDispatch(); AfxMessageBox("Success!"); #import和MFC OLE都围绕着一个给定的自动化对象产生了一个封装类,它们分别继承自_com_ptr_t和ColeDispatchDriver。但是事实上,你可以通过使用Windows API函数直接初始化ADO对象,下面讨论直接用Win32 API函数来操作COM对象。
2001-02-05· 胡朝晖·yesky--------------------------------------------------------------------------------7上一页 1 2 3 8
用COM API创建ADO工程 为了直接使用ADO和COM对象,需要添加两个头文件adoid.h和adoint.h,这两个头文件定义了CLSIDs,接口定义和你操作ADO类型库所需要的枚举类型。同时你也需要增加头文件INITGUID.H。 为了能够编译用COM API创建的ADO工程文件,你需要安装OLE DB SDK或者是MSDASDK工具。下面是简单的示例代码: #include #include #include "adoid.h" // ADO的GUID's #include "adoint.h" // ADO的类、枚举等等 void main() { HRESULT hr = S_OK; ADORecordset* Rs1 = NULL; // ADORecordset 是在adoint.h中定义的 VARIANT Source; VARIANT Connect; VariantInit( &Source ); VariantInit( &Connect ); Source.vt = VT_BSTR; Source.bstrVal = ::SysAllocString( L"SELECT * FROM Authors"); Connect.vt = VT_BSTR; Connect.bstrVal = ::SysAllocString( L"DSN=AdoDemo;UID=sa;PWD=;" ); hr = CoCreateInstance( CLSID_CADORecordset, NULL, CLSCTX_INPROC_SERVER, IID_IADORecordset, (LPVOID *) &Rs1 ); if( SUCCEEDED( hr ) ) hr = Rs1->Open( Source, Connect, adOpenForwardOnly, adLockReadOnly, -1 ); file://这里你可以对记录集Rs1进行处理 if( SUCCEEDED( hr ) ) hr = Rs1->Close(); if( SUCCEEDED( hr ) ) { Rs1->Release(); Rs1 = NULL; } if( SUCCEEDED( hr ) ) ::MessageBox( NULL, "Success!", "", MB_OK ); }
一、ADO简介
ADO(ActiveX Data Object)是Microsoft数据库应用程序开发的新接口,是建立在OLE DB之上的高层数据库访问技术,请不必为此担心,即使你对OLE DB,COM不了解也能轻松对付ADO,因为它非常简单易用,甚至比你以往所接触的ODBC API、DAO、RDO都要容易使用,并不失灵活性。本文将详细地介绍在VC下如何使用ADO来进行数据库应用程序开发,并给出示例代码。
本文示例代码 二、基本流程
万事开头难,任何一种新技术对于初学者来说最重要的还是“入门”,掌握其要点。让我们来看看ADO数据库开发的基本流程吧!
(1)初始化COM库,引入ADO库定义文件
(2)用Connection对象连接数据库
(3)利用建立好的连接,通过Connection、Command对象执行SQL命令,或利用Recordset对象取得结果记录集进行查询、处理。
(4)使用完毕后关闭连接释放对象。准备工作:
为了大家都能测试本文提供的例子,我们采用Access数据库,您也可以直接在我们提供的示例代码中找到这个test.mdb。
下面我们将详细介绍上述步骤并给出相关代码。
【1】COM库的初始化
我们可以使用AfxOleInit()来初始化COM库,这项工作通常在CWinApp::InitInstance()的重载函数中完成,请看如下代码:
BOOL CADOTest1App::InitInstance()
{
AfxOleInit();
...... 【2】用#import指令引入ADO类型库
我们在stdafx.h中加入如下语句:(stdafx.h这个文件哪里可以找到?你可以在FileView中的Header Files里找到)
#import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF")
这一语句有何作用呢?其最终作用同我们熟悉的#include类似,编译的时候系统会为我们生成msado15.tlh,ado15.tli两个C++头文件来定义ADO库。几点说明:
(1) 您的环境中msado15.dll不一定在这个目录下,请按实际情况修改
(2) 在编译的时候肯能会出现如下警告,对此微软在MSDN中作了说明,并建议我们不要理会这个警告。
msado15.tlh(405) : warning C4146: unary minus operator applied to unsigned type, result still unsigned 【3】创建Connection对象并连接数据库
首先我们需要添加一个指向Connection对象的指针:
_ConnectionPtr m_pConnection;
下面的代码演示了如何创建Connection对象实例及如何连接数据库并进行异常捕捉。
BOOL CADOTest1Dlg::OnInitDialog()
{
CDialog::OnInitDialog();
HRESULT hr;
try
{
hr = m_pConnection.CreateInstance("ADODB.Connection");///创建Connection对象
if(SUCCEEDED(hr))
{
hr = m_pConnection->Open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb","","",adModeUnknown);///连接数据库
///上面一句中连接字串中的Provider是针对ACCESS2000环境的,对于ACCESS97,需要改为:Provider=Microsoft.Jet.OLEDB.3.51; }
}
catch(_com_error e)///捕捉异常
{
CString errormessage;
errormessage.Format("连接数据库失败!\r\n错误信息:%s",e.ErrorMessage());
AfxMessageBox(errormessage);///显示错误信息
} 在这段代码中我们是通过Connection对象的Open方法来进行连接数据库的,下面是该方法的原型
HRESULT Connection15::Open ( _bstr_t ConnectionString, _bstr_t UserID, _bstr_t Password, long Options )
ConnectionString为连接字串,UserID是用户名, Password是登陆密码,Options是连接选项,用于指定Connection对象对数据的更新许可权,
Options可以是如下几个常量:
adModeUnknown:缺省。当前的许可权未设置
adModeRead:只读
adModeWrite:只写
adModeReadWrite:可以读写
adModeShareDenyRead:阻止其它Connection对象以读权限打开连接
adModeShareDenyWrite:阻止其它Connection对象以写权限打开连接
adModeShareExclusive:阻止其它Connection对象打开连接
adModeShareDenyNone:允许其它程序或对象以任何权限建立连接我们给出一些常用的连接方式供大家参考:
(1)通过JET数据库引擎对ACCESS2000数据库的连接m_pConnection->Open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\test.mdb","","",adModeUnknown);(2)通过DSN数据源对任何支持ODBC的数据库进行连接:
m_pConnection->Open("Data Source=adotest;UID=sa;PWD=;","","",adModeUnknown);(3)不通过DSN对SQL SERVER数据库进行连接: m_pConnection->Open("driver={SQL Server};Server=127.0.0.1;DATABASE=vckbase;UID=sa;PWD=139","","",adModeUnknown);其中Server是SQL服务器的名称,DATABASE是库的名称Connection对象除Open方法外还有许多方法,我们先介绍Connection对象中两个有用的属性ConnectionTimeOut与State
ConnectionTimeOut用来设置连接的超时时间,需要在Open之前调用,例如: m_pConnection->ConnectionTimeout = 5;///设置超时时间为5秒
m_pConnection->Open("Data Source=adotest;","","",adModeUnknown);
State属性指明当前Connection对象的状态,0表示关闭,1表示已经打开,我们可以通过读取这个属性来作相应的处理,例如:
if(m_pConnection->State)
m_pConnection->Close(); ///如果已经打开了连接则关闭它
【4】执行SQL命令并取得结果记录集
为了取得结果记录集,我们定义一个指向Recordset对象的指针:_RecordsetPtr m_pRecordset;
并为其创建Recordset对象的实例: m_pRecordset.CreateInstance("ADODB.Recordset");
SQL命令的执行可以采用多种形式,下面我们一进行阐述。(1)利用Connection对象的Execute方法执行SQL命令
Execute方法的原型如下所示:
_RecordsetPtr Connection15::Execute ( _bstr_t CommandText, VARIANT * RecordsAffected, long Options ) 其中CommandText是命令字串,通常是SQL命令。参数RecordsAffected是操作完成后所影响的行数, 参数Options表示CommandText中内容的类型,Options可以取如下值之一:
adCmdText:表明CommandText是文本命令
adCmdTable:表明CommandText是一个表名
adCmdProc:表明CommandText是一个存储过程
adCmdUnknown:未知
Execute执行完后返回一个指向记录集的指针,下面我们给出具体代码并作说明。 _variant_t RecordsAffected;
///执行SQL命令:CREATE TABLE创建表格users,users包含四个字段:整形ID,字符串username,整形old,日期型birthday
m_pConnection->Execute("CREATE TABLE users(ID INTEGER,username TEXT,old INTEGER,birthday DATETIME)",&RecordsAffected,adCmdText);
///往表格里面添加记录
m_pConnection->Execute("INSERT INTO users(ID,username,old,birthday) valueS (1, ''''''''Washington'''''''',25,''''''''1970/1/1'''''''')",&RecordsAffected,adCmdText);
///将所有记录old字段的值加一
m_pConnection->Execute("UPDATE users SET old = old+1",&RecordsAffected,adCmdText);
///执行SQL统计命令得到包含记录条数的记录集
m_pRecordset = m_pConnection->Execute("SELECT COUNT(*) FROM users",&RecordsAffected,adCmdText);
_variant_t vIndex = (long)0;
_variant_t vCount = m_pRecordset->GetCollect(vIndex);///取得第一个字段的值放入vCount变量
m_pRecordset->Close();///关闭记录集
CString message;
message.Format("共有%d条记录",vCount.lVal);
AfxMessageBox(message);///显示当前记录条数
(2)利用Command对象来执行SQL命令
_CommandPtr m_pCommand;
m_pCommand.CreateInstance("ADODB.Command");
_variant_t vNULL;
vNULL.vt = VT_ERROR;
vNULL.scode = DISP_E_PARAMNOTFOUND;///定义为无参数
m_pCommand->ActiveConnection = m_pConnection;///非常关键的一句,将建立的连接赋值给它
m_pCommand->CommandText = "SELECT * FROM users";///命令字串
m_pRecordset = m_pCommand->Execute(&vNULL,&vNULL,adCmdText);///执行命令,取得记录集在这段代码中我们只是用Command对象来执行了SELECT查询语句,Command对象在进行存储过程的调用中能真正体现它的作用。下次我们将详细介绍。
(3)直接用Recordset对象进行查询取得记录集
例如 m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch *)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);Open方法的原型是这样的:
HRESULT Recordset15::Open ( const _variant_t & Source, const _variant_t & ActiveConnection, enum CursorTypeEnum CursorType, enum LockTypeEnum LockType, long Options )
其中:
①Source是数据查询字符串
②ActiveConnection是已经建立好的连接(我们需要用Connection对象指针来构造一个_variant_t对象)
③CursorType光标类型,它可以是以下值之一,请看这个枚举结构:
enum CursorTypeEnum
{
adOpenUnspecified = -1,///不作特别指定
adOpenForwardOnly = 0,///前滚静态光标。这种光标只能向前浏览记录集,比如用MoveNext向前滚动,这种方式可以提高浏览速度。但诸如BookMark,RecordCount,AbsolutePosition,AbsolutePage都不能使用
adOpenKeyset = 1,///采用这种光标的记录集看不到其它用户的新增、删除操作,但对于更新原有记录的操作对你是可见的。
adOpenDynamic = 2,///动态光标。所有数据库的操作都会立即在各用户记录集上反应出来。
adOpenStatic = 3///静态光标。它为你的记录集产生一个静态备份,但其它用户的新增、删除、更新操作对你的记录集来说是不可见的。
};
④LockType锁定类型,它可以是以下值之一,请看如下枚举结构:
enum LockTypeEnum
{
adLockUnspecified = -1,///未指定
adLockReadOnly = 1,///只读记录集
adLockPessimistic = 2,悲观锁定方式。数据在更新时锁定其它所有动作,这是最安全的锁定机制
adLockOptimistic = 3,乐观锁定方式。只有在你调用Update方法时才锁定记录。在此之前仍然可以做数据的更新、插入、删除等动作
adLockBatchOptimistic = 4,乐观分批更新。编辑时记录不会锁定,更改、插入及删除是在批处理模式下完成。
};
⑤Options请参考本文中对Connection对象的Execute方法的介绍
【5】记录集的遍历、更新
根据我们刚才通过执行SQL命令建立好的users表,它包含四个字段:ID,username,old,birthday
以下的代码实现:打开记录集,遍历所有记录,删除第一条记录,添加三条记录,移动光标到第二条记录,更改其年龄,保存到数据库。
_variant_t vUsername,vBirthday,vID,vOld;
_RecordsetPtr m_pRecordset;
m_pRecordset.CreateInstance("ADODB.Recordset");
m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch*)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
while(!m_pRecordset->adoEOF)///这里为什么是adoEOF而不是EOF呢?还记得rename("EOF","adoEOF")这一句吗?
{
vID = m_pRecordset->GetCollect(_variant_t((long)0));///取得第1列的值,从0开始计数,你也可以直接给出列的名称,如下一行
vUsername = m_pRecordset->GetCollect("username");///取得username字段的值
vOld = m_pRecordset->GetCollect("old");
vBirthday = m_pRecordset->GetCollect("birthday");
///在DEBUG方式下的OUTPUT窗口输出记录集中的记录
if(vID.vt != VT_NULL && vUsername.vt != VT_NULL && vOld.vt != VT_NULL && vBirthday.vt != VT_NULL)
TRACE("id:%d,姓名:%s,年龄:%d,生日:%s\r\n",vID.lVal,(LPCTSTR)(_bstr_t)vUsername,vOld.lVal,(LPCTSTR)(_bstr_t)vBirthday);
m_pRecordset->MoveNext();///移到下一条记录
}
m_pRecordset->MoveFirst();///移到首条记录
m_pRecordset->Delete(adAffectCurrent);///删除当前记录
///添加三条新记录并赋值
for(int i=0;i<3;i++)
{
m_pRecordset->AddNew();///添加新记录
m_pRecordset->PutCollect("ID",_variant_t((long)(i+10)));
m_pRecordset->PutCollect("username",_variant_t("叶利钦"));
m_pRecordset->PutCollect("old",_variant_t((long)71));
m_pRecordset->PutCollect("birthday",_variant_t("1930-3-15"));
}
m_pRecordset->Move(1,_variant_t((long)adBookFirst));///从第一条记录往下移动一条记录,即移动到第二条记录处
m_pRecordset->PutCollect(_variant_t("old"),_variant_t((long)45));///修改其年龄
m_pRecordset->Update();///保存到库中【6】关闭记录集与连接
记录集或连接都可以用Close方法来关闭
m_pRecordset->Close();///关闭记录集
m_pConnection->Close();///关闭连接至此,我想您已经熟悉了ADO操作数据库的大致流程,也许您已经胸有成竹,也许您还有点胡涂,不要紧!建议你尝试写几个例子,这样会更好地熟悉ADO,最后我给大家写了一个小例子,例子中读出所有记录放到列表控件中、并可以添加、删除、修改记录。
点这里下载示例代码后记:限于篇幅ADO中的许多内容还没有介绍,下次我们将详细介绍Recordset对象的属性、方法并解决几个关键的技术:绑定方式处理记录集数据、存储过程的调用、事务处理、图象在数据库中的保存与读取、与表格控件的配合使用等
作者:Adrian Roman 翻译:白鹤 我花了好长时间来注意这件事情,因为我不能在其他任何文件中找到关于
DBGrid 在无绑定模式下的VC++编程内容(只有VB的)。我想把我的心得与别人
分享会是一个好主意。 首先,尽管连随机文档都说它能接受任何类型的变量作为书签,但是好象并不
是那么回事儿。DaoRecordset把SAFEARRAY作为书签(GetBook返回包含
VT_ARRAY | VT_UI1的变量),但它不能与DBGrid一起工作(DBGrid把变量转变
为VT_BSTR)。 它可能会与VT_I4工作的很好,但据说字符串是最好的,所以我
用他们。当然,我把书签转换成字符串再转换回来。 SchedQ是一个通过查询得
来的动态集(CDaoRecordset),如果关于他你有任何bug或是好想法,请跟我分
享您的发现。 啊,对,我几乎忘了!极其重要!如果你从OCX的类库中继承了
RowBuffer类,请修改构造函数,原构造函数得到LPDISPACH作为参数去调用基类
的构造函数,这时第二个参数要设为FALSE(原来隐性地设置为TRUE)。如果不
这样做,当RowBuffer对象被消除时整个程序会崩溃。 补充技巧:记着使用
BeforeColUpdate 和KeyPress事件去验证一个域的输入及相应地过滤键盘的输入。 请不要给我来e-mails,问我关于如何在绑定模式下使用他们的问题。在加入
DBGrid之前加上一个Microsoft Remote Data Control 的对话框,然后花点时
间调整一下它的属性,你就会弄明白如何使用它。 致那些想在运行时改变列
的人:由DBGrid类生成的GetColumns()成员函数是一个真正的GetColumn函数
(返回一个指向单个列的LPDISPATCH---这样你就可以用这个LPDISPATCH来构造
单个列对象,而不是列集的对象)。如果你想获得指向列集的LPDISPATCH,
把GetColumns()该名为GetColumn,然后象下面这样重新写一个GetColumns():
LPDISPATCH CMsDgridCtrl::GetColumns()
{
LPDISPATCH result;
InvokeHelper(0x8,DISPATCH_PROPERTYGET,VT_DISPATCH,(void*)&result,NULL,NULL);
return result;
}
这样你就可以用返回的LPDISPATCH来构造列集对象。 那么现在,往网格中输
入数据、删除纪录、当网格中写操作发生时从网格中获取数据的代码如下所示:
void CMyView::OnUnboundReadDataDbgridMy(LPDISPATCH RowBuf, VARIANT FAR* StartLocation, BOOL ReadPriorRows) {
// TODO: Add your control notification handler code here
RowBuffer buf(RowBuf);
VARIANT varbok;
varbok.vt=VT_ARRAY | VT_UI1;
long Row,RowsFetched;
VARIANT var;
RowsFetched=0;
if(StartLocation->vt==VT_NULL){
if(ReadPriorRows){
try{
SchedQ->MoveLast();
}catch(CDaoException* e){
e->Delete();
}
}else{
try{
SchedQ->MoveFirst();
}catch(CDaoException* e){
e->Delete();
}
}
}else{
//根据StartLocation书签和ReadPriorRows参数找到开始读数据的位置try{
VectorFromBstr(StartLocation->bstrVal,&varbok.parray);
SchedQ->SetBook(COleVariant(varbok));
if(ReadPriorRows)SchedQ->MovePrev();
else SchedQ->MoveNext();
}catch(CDaoException* e){
e->Delete();
}
}
//把我们的数据集数组转换为DBGrid 用来显示数据的RowBuf对象,
for(Row = 0; Row<buf.GetRowCount() ;Row++){
if(SchedQ->IsEOF() || SchedQ->IsBOF())break;
var.vt=VT_I4;
var.lVal=SchedQ->m_WorkCodeID;
if(buf.GetColumnCount()>0)buf.SetValue(Row, 0,var);
COleVariant olvi(SchedQ->m_TimeIn);
var=*((LPVARIANT)olvi);
if(buf.GetColumnCount()>1)buf.SetValue(Row, 1,var);
COleVariant olvo(SchedQ->m_TimeOut);
var=*((LPVARIANT)olvo);
if(buf.GetColumnCount()>2)buf.SetValue(Row, 2,var);
// Set book using CurRow which is also our
// array index
var.vt=VT_BSTR;
BstrFromVector(((LPVARIANT)SchedQ->GetBook())->parray,&var.bstrVal);
buf.SetBook(Row,var);
RowsFetched++;
if(ReadPriorRows)SchedQ->MovePrev();
else SchedQ->MoveNext();
}
buf.SetRowCount(RowsFetched);
}
void CMyView::OnUnboundWriteDataDbgridMy(LPDISPATCH RowBuf, VARIANT FAR* WriteLocation)
{
// TODO: Add your control notification handler code here
RowBuffer buf(RowBuf);
VARIANT varbok;
varbok.vt=VT_ARRAY | VT_UI1;
COleVariant var;
CString str;
try{
VectorFromBstr(WriteLocation->bstrVal,&varbok.parray);
SchedQ->SetBook(COleVariant(varbok));
}catch(CDaoException* e){
e->Delete();
buf.SetRowCount(0);
return;
}
//**********************************************************************
//Here was a portion of code that validated the record
//************************************************//只有被修改过的列更新,否则,值被设置为NULL
try{
SchedQ->Edit();
}
catch(CDaoException* e){
e->Delete();
buf.SetRowCount(0);
return;
}
if(buf.GetValue(0L, 0).vt!=VT_NULL){
var=buf.GetValue(0, 0);
str=CString(var.bstrVal);
SchedQ->m_WorkCodeID=atol(str);
}
if(buf.GetValue(0L, 1).vt!=VT_NULL){
var=buf.GetValue(0, 1);
str=CString(var.bstrVal);
SchedQ->m_TimeIn.ParseDateTime(str);
}
if(buf.GetValue(0L, 2).vt!=VT_NULL){
var=buf.GetValue(0, 2);
str=CString(var.bstrVal);
SchedQ->m_TimeOut.ParseDateTime(str);
}
try{
SchedQ->Update();
}catch(CDaoException *e){
SchedQ->CancelUpdate();
e->Delete();
buf.SetRowCount(0);
}
}
void CMyView::OnUnboundAddDataDbgridMy(LPDISPATCH RowBuf, VARIANT FAR* NewRowBook)
{
// TODO: Add your control notification handler code here
RowBuffer buf(RowBuf);
COleVariant var;
CString str;
NewRowBook->vt=VT_NULL;
//**********************************************************************
//Here was a portion of code that validated the record
//**********************************************************************
try{
SchedQ->AddNew();
SchedQ->Update();
SchedQ->SetBook(SchedQ->GetLastModifiedBook());
SchedQ->Edit();
}catch(CDaoException* e){
e->Delete();
buf.SetRowCount(0);
return;
}
SchedQ->m_EmployeeID=m_pSet->m_EmployeeID;
try{
if(buf.GetValue(0L, 0).vt!=VT_NULL){
var=buf.GetValue(0, 0);
}else{
var.vt=VT_I4;
var.lVal=0;
Column col(m_SchedGrid.GetColumns(var));
var=col.GetDefaultValue();
}
str=(char*)_bstr_t(var.bstrVal);
SchedQ->m_WorkCodeID=atol(str);
if(buf.GetValue(0L, 1).vt!=VT_NULL){
var=buf.GetValue(0, 1);
}else{
var.vt=VT_I4;
var.lVal=1;
Column col(m_SchedGrid.GetColumns(var));
var=col.GetDefaultValue();
}
str=(char*)_bstr_t(var.bstrVal);
SchedQ->m_TimeIn.ParseDateTime(str);
if(buf.GetValue(0L, 2).vt!=VT_NULL){
var=buf.GetValue(0, 2);
}else{
var.vt=VT_I4;
var.lVal=2;
Column col(m_SchedGrid.GetColumns(var));
var=col.GetDefaultValue();
}
str=(char*)_bstr_t(var.bstrVal);
SchedQ->m_TimeOut.ParseDateTime(str);
var.vt=VT_I4;
SchedQ->Update();
}catch(CDaoException *e){
e->Delete();
SchedQ->CancelUpdate();
SchedQ->Delete();
buf.SetRowCount(0);
}
NewRowBook->vt=VT_BSTR;
BstrFromVector(((LPVARIANT)SchedQ->GetBook())->parray,&(NewRowBook->bstrVal));
}
void CMyView::OnUnboundDeleteRowDbgridMy(VARIANT FAR* Book)
{
// TODO: Add your control notification handler code here
VARIANT varbok;
varbok.vt=VT_ARRAY | VT_UI1;
VectorFromBstr(Book->bstrVal,&varbok.parray);
try{
SchedQ->SetBook(COleVariant(varbok));
SchedQ->Delete();
}catch(CDaoException* e){
Book->vt=VT_NULL;
e->Delete();
}
}
在VC++中建立自定义数据库类
作者:李健萍
摘要
---- 本文较为详细的介绍了一个使用ODBC对数据库进行操作的
CdataBaseOperate类的建立,并给出了几个主要函数的具体实现,以及该类
在实际的应用程序中的使用。 ---- 众所周知VC++的MFC类库为编程者编制好了对数据库操作的类,编程者可
以使用向导建立一个与数据库联结并对数据库进行操作的应用程序,不需要编
制任何代码,这无疑为编程人员提供了一个捷径。但是,使用向导时只有选用基
于单文档或多文档的项目才能选择数据源,与指定的的数据库相连,对用向导生成
的基于对话框的应用程序不提供数据库的支持。即使是基于单文档或多文档的
应用程序,当需要一些特殊的操作,例如,打开一个表,要求返回满足一定条件
的记录集时,MFC并没有提供完全符合要求的现成函数。如果,能利用MFC所提
供的数据库操作,再加上自己设计的函数,也就是说,设计一个对数据库操作
的类,在程序中手工加入这个类,那么就可以在基于对话框的应用程序中实现对
数据库的操作,而且,也可以针对自己应用程序的具体需要来设计类的函数,为
特定功能的实现提供了很大的方便。 ---- 在一个涉及数据库操作的应用程序中,常用到的MFC类有CdaoDatabase类、
CdaoTableDef类、CdaoRecordset类和 CdaoQueryDef类,当对数据库进行操作
时,需要先打开数据库,然后打开数据库中的表,再得到查询集和记录集。在自
己定义的类中综合这四个类的操作,设计一个打开表得到查询集和记录集的函
数,以后,在应用程序中使用该类时只需包含该类的头文件,所设计的函数就
可以直接调用了。 ---- 建立数据库类的过程可分为如下四步: ---- 一、定义一个无基类的CdataBaseOperate类 ---- 1、在Workspace窗口选择ClassView选项卡,在树型类结构图的根部单击
鼠标右键,选择New Class…,系统将弹出建立新类的对话框; ---- 2、在Class type中选择Generic Class; ---- 3、在Name中填写要建的新类的名称,要以大写字母C开头,系统会自动建立
新类的头文件和实现文件,文件的名称为类名去掉第一个大写字母C,如果想改变
文件的名称,可以单击change按钮. ---- 4、在填写好各项后,按OK按钮确定,一个无基类的新类建立成功,但,他
还是一个空类,下一步,就要给类添加内容. ---- 二、在自定义的类中加入有关的定义 ---- 1、在本应用程序中,使用ODBC与SQL SERVER的数据库相连,因而,在类的
实现文件构造函数前加入如下的定义: #define SQL_DATABASE _T("ODBC;
DSN=sql-database;UID=sa;PWD=pass;") DSN=sql-database表示建立的ODBC联
接的名称是sql-database,如果选用其他数据库,只需在此改变与所需数据库建
立的联接,或是重新配置sql-database 使之联接新的数据库。UID=sa;PWD=pass
表示登录数据库的用户名是sa,密码是pass,如果密码是空则表示为PWD=""。 ---- 2、在该类中综合使用到了MFC类库提供的有关数据库的几个类
CdaoDatabase类、CdaoTableDef类、CdaoRecordset类和CdaoQueryDef类,
而这四个类的定义和实现都包括在头文件afxdao.h中,因此,在新定义的类
的头文件中一定要加上语句: #include < afxdao.h >;---- 3、对要用到的四个类各声明一个对象如下:
CDaoDatabase* loc_pDataBase;
CDaoTableDef* loc_pTable;
CDaoRecordset loc_pRecordset;
CDaoQueryDef* loc_pQueryDef;---- 其中CdaoDatabase类、CdaoTableDef类和CdaoQueryDef类定义了对象指针,
在使用时要先new,最后要delete。以CdaoDatabase类为例,在
CdataBaseOperate类的构造函数中初始化对象指针 loc_pDataBase=new CDaoDatabase;在析构函数中要释放该指针delete loc_pDataBase;
---- 三、在自定义的类中加入所需的函数和变量 ---- 手工加入函数包括两项工作,首先在头文件中加入函数的声明,然后,在
实现文件中加入函数的具体实现,声明与实现一定要统一; ---- 使用向导加入函数和变量: ---- 1、在Workspace窗口选择ClassView选项卡; ---- 2、在树型类结构图的要添加函数和变量的类上单击鼠标右键,如果加入成
员函数则单击Add Member Function,加入虚函数单击Add Virtual Function,
加入成员变量单击 Add Member Variable; ---- 3、出现对话框后,填写成员函数或变量的名称、类型,系统会自动添加函
数的声明与实现; ---- 4、添加函数的具体操作,可以通过编辑代码进一步填写; ---- 这些操作将会在Workspace窗口的ClassView选项中立即体现出来,并且,
单击ClassView中的相应函数就可进入该函数的实现部分,进行进一步编写代码,
如果做不到这一点,说明添加成员函数的操作有误。 ---- 下面以本应用程序为例,给出具体的表结构和几个主要函数的实现,读者
可以根据自己的实际情况设计函数。 ---- 本应用程序中的一个典型表的结构是: 序号 正题内容 难度系数 分值 答案 备注
整型 字符型 长整型 双精度 字符型 字符型---- 打开数据库的函数实现如下:
if (!loc_pDataBase->IsOpen())
loc_pDataBase->Open( NULL, FALSE,
FALSE, SQL_DATABASE);---- 该函数中用到了CdaoDatabase类的两个函数IsOpen()和Open(NULL, FALSE,
FALSE, SQL_DATABASE),因为已经声明了该类的指针对象loc_pDataBase,所以
可以直接调用CdaoDatabase类的函数。其中,Open()函数中的最后一个参数
SQL_DATABASE在前面已经介绍过,通过他打开相关的数据库。
---- 由于程序中打开表后,不仅要返回所有的记录集,还用到返回满足一定条
件的记录集,因此打开表的函数除了带入表名外还有一个参数难度系数,
lNDXS=0时,选择表中全部数据, lNDXS=1~n时,表示选择难度系数=1~n的记录。 bool CDataBaseOperate::OpenTable
(Cstring strTableName,long lNDXS)
{
Cstring strFieldNumber;
loc_pTable=new CdaoTableDef
(loc_pDataBase);
if (!loc_pTable->IsOpen())
loc_pTable->Open(strTableName);
//打开指定的表名
strFieldNumber.Format("%d",loc_pTable- >
GetFieldCount()); //得到字段数
Cstring Sqlstr,Sqlstr1,Sqlstr2;
loc_pQueryDef=new CDaoQueryDef(loc_pDataBase);
//得到查询集和记录集
if (lNDXS==0)
{
Sqlstr=_T("SELECT * FROM "+strTableName);
}
else
{ Sqlstr1="SELECT * FROM "+strTableName ;
Sqlstr2.Format("WHERE 难度系数= %d",lNDXS);
Sqlstr=_T(Sqlstr1+Sqlstr2);
}
loc_pQueryDef->Create(NULL,Sqlstr);
loc_pRecordset.Open(loc_pQueryDef);
m_nRecordNumber=0;
while(!loc_pRecordset.IsEOF())
{
m_nRecordNumber++;
loc_pRecordset.MoveNext( );
}
return TRUE;
}
---- 为了维护数据库的安全,表用过后应该关闭,关闭表的同时,要释放在打
开表的操作时初始化的对象指针,例如:delete loc_pQueryDef。同样要注意,
在构造函数中初始化的对象指针,在析构函数中一定要释放。对象指针的初始化
和释放是成对出现的。
loc_pDataBase=new CDaoDatabase;
//在构造函数中初始化对象指针。
Delete loc_pDataBase;
//在析构函数中释放该对象指针。---- 四、CdataBaseOperate类的应用
---- 1、使用VC++的向导生成一个应用程序,可以根据需要选择基于对话框或
是基于单、多文档,选择单文档或多文档时不要选择数据库支持。 ---- 2、在应用程序的主头文件中加入#include "DataBaseOperate.h",并且还
要声明一个CdataBaseOperate类的对象,public: CDataBaseOperate m_CDataBaseOperate;
---- 3、有了指向CdataBaseOperate类的对象后,刚刚在CdataBaseOperate类中
编制的函数都可以通过
"m_CdataBaseOperate .函数名"来调用。 小结: ---- 本文是VC++6.0下的CdataBaseOperate类建立与应用的一个初步探讨,
CdataBaseOperate类所实现的功能是很强大的。除了介绍的CdataBaseOperate类
的几个基本而又常用的函数之外,CdataBaseOperate还有很多用于其他方面的功
能函数,在此不一一介绍。CdataBaseOperate类的函数,实现了数据库内容的显
示、修改、添加、删除等功能,基本上满足了数据库操作人员的需要。除了这
些,编程人员还可以根据程序的需要定义自己的特有的函数。
在我们写的程序当中,总有一些配置信息需要保存下来,以便完成程序的功能,最简单的办法就是将这些信息写入INI文件中,程序初始化时再读入.具体应用如下: 一.将信息写入.INI文件中. 1.所用的WINAPI函数原型为: BOOL WritePrivateProfileString(
LPCTSTR lpAppName,
LPCTSTR lpKeyName,
LPCTSTR lpString,
LPCTSTR lpFileName
); 其中各参数的意义: LPCTSTR lpAppName 是INI文件中的一个字段名. LPCTSTR lpKeyName 是lpAppName下的一个键名,通俗讲就是变量名. LPCTSTR lpString 是键值,也就是变量的值,不过必须为LPCTSTR型或CString型的. LPCTSTR lpFileName 是完整的INI文件名. 2.具体使用方法:设现有一名学生,需把他的姓名和年龄写入 c:\stud\student.ini 文件中. CString strName,strTemp;
int nAge;
strName="张三";
nAge=12;
::WritePrivateProfileString("StudentInfo","Name",strName,"c:\\stud\\student.ini"); 此时c:\stud\student.ini文件中的内容如下: [StudentInfo]
Name=张三 3.要将学生的年龄保存下来,只需将整型的值变为字符型即可:strTemp.Format("%d",nAge);
::WritePrivateProfileString("StudentInfo","Age",strTemp,"c:\\stud\\student.ini");
二.将信息从INI文件中读入程序中的变量. 1.所用的WINAPI函数原型为:DWORD GetPrivateProfileString(
LPCTSTR lpAppName,
LPCTSTR lpKeyName,
LPCTSTR lpDefault,
LPTSTR lpReturnedString,
DWORD nSize,
LPCTSTR lpFileName
); 其中各参数的意义: 前二个参数与 WritePrivateProfileString中的意义一样. lpDefault : 如果INI文件中没有前两个参数指定的字段名或键名,则将此值赋给变量. lpReturnedString : 接收INI文件中的值的CString对象,即目的缓存器. nSize : 目的缓存器的大小. lpFileName : 是完整的INI文件名. 2.具体使用方法:现要将上一步中写入的学生的信息读入程序中.CString strStudName;
int nStudAge;
GetPrivateProfileString("StudentInfo","Name","默认姓名",strStudName.GetBuffer(MAX_PATH),MAX_PATH,"c:\\stud\\student.ini"); 执行后 strStudName 的值为:"张三",若前两个参数有误,其值为:"默认姓名". 3.读入整型值要用另一个WINAPI函数: UINT GetPrivateProfileInt(
LPCTSTR lpAppName,
LPCTSTR lpKeyName,
INT nDefault,
LPCTSTR lpFileName
); 这里的参数意义与上相同.使用方法如下:nStudAge=GetPrivateProfileInt("StudentInfo","Age",10,"c:\\stud\\student.ini");
三.循环写入多个值,设现有一程序,要将最近使用的几个文件名保存下来,具体程序如下: 1.写入:CString strTemp,strTempA;
int i;
int nCount=6;
file://共有6个文件名需要保存
for(i=0;i {strTemp.Format("%d",i);
strTempA=文件名;
file://文件名可以从数组,列表框等处取得.
::WritePrivateProfileString("UseFileName","FileName"+strTemp,strTempA,
"c:\\usefile\\usefile.ini");
}
strTemp.Format("%d",nCount);
::WritePrivateProfileString("FileCount","Count",strTemp,"c:\\usefile\\usefile.ini");
file://将文件总数写入,以便读出. 2.读出:nCount=::GetPrivateProfileInt("FileCount","Count",0,"c:\\usefile\\usefile.ini");
for(i=0;i {strTemp.Format("%d",i);
strTemp="FileName"+strTemp;
::GetPrivateProfileString("CurrentIni",strTemp,"default.fil", strTempA.GetBuffer(MAX_PATH),MAX_PATH,"c:\\usefile\\usefile.ini");file://使用strTempA中的内容.} 补充四点: 1.INI文件的路径必须完整,文件名前面的各级目录必须存在,否则写入不成功,该函数返回 FALSE 值. 2.文件名的路径中必须为 \\ ,因为在VC++中, \\ 才表示一个 \ . 3.也可将INI文件放在程序所在目录,此时 lpFileName 参数为: ".\\student.ini".
在下列函数声明中,为什么要同时使用*和&符号?以及什么场合使用这种声明方式? void func1( MYCLASS *&pBuildingElement ); 论坛中经常有人问到这样的问题。本文试图通过一些实际的指针使用经验来解释这个问题。
仔细看一下这种声明方式,确实有点让人迷惑。在某种意义上,"*"和"&"是意思相对的两个东西,把它们放在一起有什么意义呢?。为了理解指针的这种做法,我们先复习一下C/C++编程中无所不在的指针概念。我们都知道MYCLASS*的意思:指向某个对象的指针,此对象的类型为MYCLASS。 Void func1(MYCLASS *pMyClass); // 例如: MYCLASS* p = new MYCLASS;
func1(p);
上面这段代码的这种处理方法想必谁都用过,创建一个MYCLASS对象,然后将它传入func1函数。现在假设此函数要修改pMyClass: void func1(MYCLASS *pMyClass)
{
DoSomething(pMyClass);
pMyClass = // 其它对象的指针
}
第二条语句在函数过程中只修改了pMyClass的值。并没有修改调用者的变量p的值。如果p指向某个位于地址0x008a00的对象,当func1返回时,它仍然指向这个特定的对象。(除非func1有bug将堆弄乱了,完全有这种可能。) 现在假设你想要在func1中修改p的值。这是你的权利。调用者传入一个指针,然后函数给这个指针赋值。以往一般都是传双指针,即指针的指针,例如,CMyClass**。
MYCLASS* p = NULL;
func1(&p);void func1(MYCLASS** pMyClass);
{
*pMyClass = new MYCLASS;
……
}
调用func1之后,p指向新的对象。在COM编程中,你到处都会碰到这样的用法--例如在查询对象接口的QueryInterface函数中:
interface ISomeInterface {
HRESULT QueryInterface(IID &iid, void** ppvObj);
……
};
LPSOMEINTERFACE p=NULL;
pOb->QueryInterface(IID_SOMEINTERFACE, &p);
此处,p是SOMEINTERFACE类型的指针,所以&p便是指针的指针,在QueryInterface返回的时候,如果调用成功,则变量p包含一个指向新的接口的指针。 如果你理解指针的指针,那么你肯定就理解指针引用,因为它们完全是一回事。如果你象下面这样声明函数:
void func1(MYCLASS *&pMyClass);
{
pMyClass = new MYCLASS;
……
}
其实,它和前面所讲得指针的指针例子是一码事,只是语法有所不同。传递的时候不用传p的地址&p,而是直接传p本身: MYCLASS* p = NULL;
func1(p); 在调用之后,p指向一个新的对象。一般来讲,引用的原理或多或少就象一个指针,从语法上看它就是一个普通变量。所以只要你碰到*&,就应该想到**。也就是说这个函数修改或可能修改调用者的指针,而调用者象普通变量一样传递这个指针,不使用地址操作符&。 至于说什么场合要使用这种方法,我会说,极少。MFC在其集合类中用到了它--例如,CObList,它是一个Cobjects指针列表。Class CObList : public Cobject {
……// 获取/修改指定位置的元素
Cobject*& GetAt(POSITION position);
Cobject* GetAt(POSITION position) const;
};
这里有两个GetAt函数,功能都是获取给定位置的元素。区别何在呢? 区别在于一个让你修改列表中的对象,另一个则不行。所以如果你写成下面这样: Cobject* pObj = mylist.GetAt(pos); 则pObj是列表中某个对象的指针,如果接着改变pObj的值: pObj = pSomeOtherObj; 这并改变不了在位置pos处的对象地址,而仅仅是改变了变量pObj。但是,如果你写成下面这样: Cobject*& rpObj = mylist.GetAt(pos); 现在,rpObj是引用一个列表中的对象的指针,所以当改变rpObj时,也会改变列表中位置pos处的对象地址--换句话说,替代了这个对象。这就是为什么CObList会有两个GetAt函数的缘故。一个可以修改指针的值,另一个则不能。注意我在此说的是指针,不是对象本身。这两个函数都可以修改对象,但只有*&版本可以替代对象。 在C/C++中引用是很重要的,同时也是高效的处理手段。所以要想成为C/C++高手,对引用的概念没有透彻的理解和熟练的应用是不行的。