常见问题总结3

yintongshun 2003-12-02 11:56:25
加精
C++指针使用方法解惑


在下列函数声明中,为什么要同时使用*和&符号?以及什么场合使用这种声明方式?

  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++高手,对引用的概念没有透彻的理解和熟练的应用是不行的。


...全文
203 10 打赏 收藏 转发到动态 举报
写回复
用AI写文章
10 条回复
切换为时间正序
请发表友善的回复…
发表回复
yintongshun 2003-12-02
  • 打赏
  • 举报
回复

运用VC或Java对Office进行编程操作 etre(原作)
用VC对Office进行操作的介绍已经不少了,但是从来没有把word,excel,powerPoint进进全面的介绍的。
由于工作的需要,我需要对在自己的软件中对word,excel,powerPoint进行操作。所以把自己的体会写出来和大家分享,希望对大家有所帮助。当然还有很多不当之处,希望大家指出。
用例子来说明吧,首先创建一个MFC AppWizard(EXE)工程,然后通过在VIEW菜单中,选ClassWizard,选Automation选项卡,选Add Class,选择From a TypeLibrary, 选中Microsoft Office 2000 类型库:Excel9.olb,MSPPT9.OLB,MSWORD9.OLB(在Microsoft Office\Office目录下) 会将类型库中的所有类添加到你的工程中。
然后写一个类来操作Office吧!
ObtGuiGcomOfficePrinter .h
#if !defined(AFX_OBTGUIGCOMOFFICEPRINTER_H__03A0C2D8_DFC8_4B51_8ADB_994B86BACB82__INCLUDED_)
#define AFX_OBTGUIGCOMOFFICEPRINTER_H__03A0C2D8_DFC8_4B51_8ADB_994B86BACB82__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#include "comdef.h"
#include "ObtGuiGcomMsWord9.h"
#include "ObtGuiGcomMsPpt9.h"
#include "ObtGuiGcomExcel9.h"

class AFX_EXT_CLASS ObtGuiGcomOfficePrinter
{
public:
ObtGuiGcomOfficePrinter();
virtual ~ObtGuiGcomOfficePrinter();

//Operator
public:
BOOL WordPrinterToJcf(LPCTSTR lpszFileName,LPCTSTR lpszActivePrinter);
BOOL ExcelPrinterToJcf(LPCTSTR lpszFileName,LPCTSTR lpszActivePrinter);
BOOL PowerPointPrinterToJcf(LPCTSTR lpszFileName,LPCTSTR lpszActivePrinter);

};

#include "stdafx.h"
#include "ObtGuiGcomOfficePrinter.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

ObtGuiGcomOfficePrinter::ObtGuiGcomOfficePrinter()
{

}


ObtGuiGcomOfficePrinter::~ObtGuiGcomOfficePrinter()
{

}

BOOL ObtGuiGcomOfficePrinter::PowerPointPrinterToJcf(LPCTSTR lpszFileName,LPCTSTR lpszActivePrinter)
{
_PptApplication m_powerpointApp;
Presentations m_powerpointPres;
_Presentation m_powerpointPre;

m_powerpointPres.ReleaseDispatch();
m_powerpointPre.ReleaseDispatch();

if(!m_powerpointApp.CreateDispatch("PowerPoint.Application"))
{
AfxMessageBox("创建PowerPoint服务失败!");
return FALSE;
}

m_powerpointApp.m_bAutoRelease=true;
m_powerpointApp.SetVisible(TRUE);//对于PowerPoint必须设置为TRUE

m_powerpointPres.AttachDispatch(m_powerpointApp.GetPresentations());
m_powerpointPres.Open(lpszFileName,-1,-1,-1);
m_powerpointPre.AttachDispatch(m_powerpointApp.GetActivePresentation(),TRUE);

m_powerpointPre.PrintOut(-1,-1,"",long(1),-1);

m_powerpointApp.Quit();

m_powerpointPre.ReleaseDispatch();
m_powerpointPres.ReleaseDispatch();
m_powerpointApp.ReleaseDispatch();

return TRUE;
}
BOOL ObtGuiGcomOfficePrinter::ExcelPrinterToJcf(LPCTSTR lpszFileName,LPCTSTR lpszActivePrinter)
{
_ExcelApplication m_excelApp;//定义Excel提供的应用程序对象
Workbooks m_excelBooks;
_Workbook m_excelBook;

m_excelBooks.ReleaseDispatch();
m_excelBook.ReleaseDispatch();
m_excelApp.m_bAutoRelease=true;

//创建Excel 2000服务器(启动Excel)
if (!m_excelApp.CreateDispatch("Excel.Application"))
{
AfxMessageBox("创建Excel服务失败!");
return FALSE;
}

m_excelApp.SetVisible(FALSE); //设置为隐藏
//利用模板文件建立新文档
m_excelBooks.AttachDispatch(m_excelApp.GetWorkbooks(),true);
m_excelBook.AttachDispatch(m_excelBooks.Add(_variant_t(lpszFileName)));

//m_excelApp.SetActivePrinter(lpszActivePrinter); //设置当前打印机
COleVariant covTrue((short)TRUE), covFalse((short)FALSE), covOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR);//定义打印机并打印
m_excelBook.PrintOut(covOptional,covOptional,COleVariant(long(1)),covFalse,covOptional,covOptional,covOptional,covOptional);

m_excelApp.Quit();//退出

m_excelBook.ReleaseDispatch();
m_excelBooks.ReleaseDispatch();
m_excelApp.ReleaseDispatch();

return TRUE;
}

BOOL ObtGuiGcomOfficePrinter::WordPrinterToJcf(LPCTSTR lpszFileName,LPCTSTR lpszActivePrinter)
{

_WordApplication m_wordApp;//定义Word提供的应用程序对象
Documents m_wordDocs;//定义Word提供的文档对象
_Document m_wordDoc; //当前的的文档对象

m_wordDocs.ReleaseDispatch();
m_wordDoc.ReleaseDispatch();
m_wordApp.m_bAutoRelease=true;

if(!m_wordApp.CreateDispatch("Word.Application")) //创建Word应用服务
{
AfxMessageBox("创建Word应用服务失败!");
return FALSE;
}

m_wordApp.SetVisible(FALSE); //设置为隐藏
//下面是打开文件定义VARIANT变量;
COleVariant varFilePath(lpszFileName);
COleVariant varstrNull("");
COleVariant varZero((short)0);
COleVariant varTrue(short(1),VT_BOOL);
COleVariant varFalse(short(0),VT_BOOL);

m_wordDocs.AttachDispatch(m_wordApp.GetDocuments());//将Documents类对象m_Docs和Idispatch接口关联起来;
m_wordDocs.Open(varFilePath,varFalse,varFalse,varFalse,varstrNull,varstrNull,varFalse,varstrNull,varstrNull,varTrue,varTrue,varTrue);
m_wordDoc.AttachDispatch(m_wordApp.GetActiveDocument()); //得到当前激活的Document对象

m_wordApp.SetActivePrinter(lpszActivePrinter); //设置当前打印机
COleVariant covTrue((short)TRUE), covFalse((short)FALSE), covOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR); //定义打印属性
m_wordDoc.PrintOut(covFalse,
covOptional,
covOptional,
covOptional,
covOptional,
covOptional,
covOptional,
COleVariant((long)1),
covOptional,
covOptional,
covOptional,
covOptional,
covOptional,
covOptional,
covOptional,
covOptional,
covOptional,
covOptional);

m_wordApp.Quit(covOptional,covOptional,covOptional);//退出

m_wordDoc.ReleaseDispatch(); //断开关联;
m_wordDocs.ReleaseDispatch();
m_wordApp.ReleaseDispatch();
return TRUE;
}
以上是用Office打开相应的文档进行打印的操作,如果你需要进行相应的其他操作,你可以用Office里面的宏进行录制然后转化为相应的代码。





yintongshun 2003-12-02
  • 打赏
  • 举报
回复
获取远程网卡MAC地址。 prettywolf(收藏) 阅读:550


首先在头文件定义中加入#include "nb30.h"
#pragma comment(lib,"netapi32.lib")
typedef struct _ASTAT_
{
ADAPTER_STATUS adapt;
NAME_BUFFER NameBuff[30];
} ASTAT, * PASTAT;


如何用VC++开发读取网卡MAC地址的程序

2003-4-6 8:07:11 LYCOS 温卫红 阅读次数: 4693
在实际的应用系统中,我们往往会需要在程序运行时获取当前机器的网卡的MAC地址,以便作为某种标识之用,如控制程序的合法性等。下文就如何用Microsoft Visual C++ 6.0开发这样的程序演示如何实现其要点。

---- 这里采用的方法是通过Windows 9x/NT/Win2000中内置的NetApi32.DLL的功能来实现的,首先通过发送NCBENUM命令获取网卡的数目和每个网卡的内部编号,然后对每个网卡标号发送NCBASTAT命令获取其MAC地址。注意:这里的网卡是指捆绑了NetBeui协议的通信协议栈,可以在网卡的属性处查看到。

---- 请运行VC++,打开一个新的工程,选择创建一个Win32 Console程序,然后按下文输入代码,并请参见其中的注释:

#include "stdafx.h"

#include < windows.h >
#include < wincon.h >
#include < stdlib.h >
#include < stdio.h >
#include < time.h >

---- // 因为是通过NetAPI来获取网卡信息,所以需要包含其题头文件nb30.h #include < nb30.h >
typedef struct _ASTAT_
{
ADAPTER_STATUS adapt;
NAME_BUFFER NameBuff [30];
}ASTAT, * PASTAT;

ASTAT Adapter;

---- // 定义一个存放返回网卡信息的变量
---- // 输入参数:lana_num为网卡编号,一般地,从0开始,但在Windows 2000中并不一定是连续分配的

void getmac_one (int lana_num)
{
NCB ncb;
UCHAR uRetCode;

memset( &ncb, 0, sizeof(ncb) );
ncb.ncb_command = NCBRESET;
ncb.ncb_lana_num = lana_num;
// 指定网卡号

---- // 首先对选定的网卡发送一个NCBRESET命令,以便进行初始化
uRetCode = Netbios( &ncb );
printf( "The NCBRESET return code is:
0x%x \n", uRetCode );

memset( &ncb, 0, sizeof(ncb) );
ncb.ncb_command = NCBASTAT;
ncb.ncb_lana_num = lana_num; // 指定网卡号

strcpy( (char *)ncb.ncb_callname,
"* " );
ncb.ncb_buffer = (unsigned char *) &Adapter;

---- // 指定返回的信息存放的变量
ncb.ncb_length = sizeof(Adapter);

---- // 接着,可以发送NCBASTAT命令以获取网卡的信息
uRetCode = Netbios( &ncb );
printf( "The NCBASTAT
return code is: 0x%x \n", uRetCode );
if ( uRetCode == 0 )
{

---- // 把网卡MAC地址格式化成常用的16进制形式,如0010-A4E4-5802
printf( "The Ethernet Number[%d]
is: %02X%02X-%02X%02X-%02X%02X\n",
lana_num,
Adapter.adapt.adapter_address[0],
Adapter.adapt.adapter_address[1],
Adapter.adapt.adapter_address[2],
Adapter.adapt.adapter_address[3],
Adapter.adapt.adapter_address[4],
Adapter.adapt.adapter_address[5] );
}
}

int main(int argc, char* argv[])
{
NCB ncb;
UCHAR uRetCode;
LANA_ENUM lana_enum;

memset( &ncb, 0, sizeof(ncb) );
ncb.ncb_command = NCBENUM;

ncb.ncb_buffer = (unsigned char *) &lana_enum;
ncb.ncb_length = sizeof(lana_enum);

---- // 向网卡发送NCBENUM命令,以获取当前机器的网卡信息,如有多少个网卡、每张网卡的编号等
uRetCode = Netbios( &ncb );
printf( "The NCBENUM return
code is:
0x%x \n", uRetCode );
if ( uRetCode == 0 )
{
printf( "Ethernet Count is : %d\n\n", lana_enum.length);

---- // 对每一张网卡,以其网卡编号为输入编号,获取其MAC地址
for ( int i=0; i< lana_enum.length; ++i)
getmac_one( lana_enum.lana[i]);
}
return 0;
}

---- 此时,按F7编译、直至通过,按F5运行即可。
---- 这段代码可以直接嵌入相关的应用系统之中,或封装成.DLL或COM控件,以便可以在Visual Basic、Visual Foxpro、Power Builder或Delphi等其他程序中调用





网友对该文章的评论
网友: goat623(gxx623@sina.com) 发表于: 2003-10-16 9:18:07

#include <stdio.h>
#include <stdlib.h>
#include <Winsock2.h>
#include <Winsock.h>
#include <iphlpapi.h>
#include <windows.h>
#include <iostream.h>


#pragma comment ( lib, "ws2_32.lib" )
#pragma comment ( lib, "Iphlpapi.lib" )

void main( int argc, char ** argv )
{

typedef struct _MIB_IPNETROW
{ DWORD dwIndex;
DWORD dwPhysAddrLen;
BYTE bPhysAddr[MAXLEN_PHYSADDR];
DWORD dwAddr;
DWORD dwType;
} MIB_IPNETROW, *PMIB_IPNETROW;

typedef struct _ASTAT_
{
ADAPTER_STATUS adapt;
NAME_BUFFER NameBuff [30];
}ASTAT, * PASTAT;

int iRet;
int numberOfHost;
struct in_addr sa;
unsigned char macAddress[6];
HOSTENT* remoteHostent;
IPAddr nRemoteAddr;
ULONG macAddLen = 6;
WSADATA wsaData;
ASTAT Adapter;
MIB_IPNETROW dwIndex;
MIB_IPNETROW dwaddr;

typedef DWORD(CALLBACK * PFLUSHIPNETTABLE)(DWORD);

HINSTANCE hInst;
hInst=LoadLibrary("iphlpapi.dll");

//处理命令行参数
if ( argc == 2)
{
numberOfHost = atoi( argv[2] );
}
if ( ( argc >3 ) || ( argc < 2 ) )
{
printf( "RmtHost v0.2 - Get remote HostName /MacAddress\n" );
printf( "Usage :\n\tRmtHost.exe [RemoteIP] \n\n" );
printf( "Example:\n\tRmtHost.exe 192.168.200.254\n" );
}

//初始化SOCKET
iRet = WSAStartup(MAKEWORD(2,1), &wsaData);
if ( iRet != 0 )
{
printf( "WSAStartup Error:%d\n", GetLastError() );
exit( 0 );
}
nRemoteAddr = inet_addr( "192.168.200.160" );
// dwaddr = inet_addr( "192.168.200.160" );
remoteHostent= (struct hostent*)malloc( sizeof(struct hostent ));
//获取远程机器名
sa.s_addr = nRemoteAddr;
printf( "\nIpAddress : %s\n", inet_ntoa( sa ) );
remoteHostent = gethostbyaddr( (char*)&nRemoteAddr,4, AF_INET );
if ( remoteHostent )
{
printf( "HostName : %s\n",remoteHostent->h_name );
}
else
{
printf( "SendARP Error:%d\n", GetLastError());

}
//发送ARP查询包获得远程MAC地址

iRet=SendARP(nRemoteAddr, (unsigned long)NULL,(PULONG)&macAddress, &macAddLen);
if ( iRet == NO_ERROR )
{

printf("MacAddress is: %02X-%02X-%02X-%02X-%02X-%02X", macAddress[0], macAddress[1], macAddress[2], macAddress[3], macAddress[4], macAddress[5]);
printf("\n");

}


必须安装PLATFORM SDK


就可以这样调用来获取远程网卡MAC地址了:
CString GetMacAddress(CString sNetBiosName)
{
ASTAT Adapter;

NCB ncb;
UCHAR uRetCode;

memset(&ncb, 0, sizeof(ncb));
ncb.ncb_command = NCBRESET;
ncb.ncb_lana_num = 0;

uRetCode = Netbios(&ncb);

memset(&ncb, 0, sizeof(ncb));
ncb.ncb_command = NCBASTAT;
ncb.ncb_lana_num = 0;

sNetBiosName.MakeUpper();

FillMemory(ncb.ncb_callname, NCBNAMSZ - 1, 0x20);

strcpy((char *)ncb.ncb_callname, (LPCTSTR) sNetBiosName);

ncb.ncb_callname[sNetBiosName.GetLength()] = 0x20;
ncb.ncb_callname[NCBNAMSZ] = 0x0;

ncb.ncb_buffer = (unsigned char *) &Adapter;
ncb.ncb_length = sizeof(Adapter);

uRetCode = Netbios(&ncb);

CString sMacAddress;

if (uRetCode == 0)
{
sMacAddress.Format(_T("%02x%02x%02x%02x%02x%02x"),
Adapter.adapt.adapter_address[0],
Adapter.adapt.adapter_address[1],
Adapter.adapt.adapter_address[2],
Adapter.adapt.adapter_address[3],
Adapter.adapt.adapter_address[4],
Adapter.adapt.adapter_address[5]);
}
return sMacAddress;
}



yintongshun 2003-12-02
  • 打赏
  • 举报
回复
//-----------------------------------------------------------------
// 取得所有网卡信息
//-----------------------------------------------------------------
BOOL GetAdapterInfo()
{
// 这里的代码适合WINDOWS2000,对于NT需要读取HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkCards
HKEY hKey, hSubKey, hNdiIntKey;

if(RegOpenKeyEx(HKEY_LOCAL_MACHINE,
"System\\CurrentControlSet\\Control\\Class\\{4d36e972-e325-11ce-bfc1-08002be10318}",
0,
KEY_READ,
&hKey) != ERROR_SUCCESS)
return FALSE;

DWORD dwIndex = 0;
DWORD dwBufSize = 256;
DWORD dwDataType;
char szSubKey[256];
unsigned char szData[256];

while(RegEnumKeyEx(hKey, dwIndex++, szSubKey, &dwBufSize, NULL, NULL, NULL, NULL) == ERROR_SUCCESS)
{
//AfxMessageBox(szSubKey);
if(RegOpenKeyEx(hKey, szSubKey, 0, KEY_READ, &hSubKey) == ERROR_SUCCESS)
{
if(RegOpenKeyEx(hSubKey, "Ndi\\Interfaces", 0, KEY_READ, &hNdiIntKey) == ERROR_SUCCESS)
{
dwBufSize = 256;
if(RegQueryValueEx(hNdiIntKey, "LowerRange", 0, &dwDataType, szData, &dwBufSize) == ERROR_SUCCESS)
{
if(strcmp((char*)szData, "ethernet") == 0) // 判断是不是以太网卡
{
dwBufSize = 256;
if(RegQueryValueEx(hSubKey, "DriverDesc", 0, &dwDataType, szData, &dwBufSize) == ERROR_SUCCESS)
{
ADAPTER_INFO *pAI = new ADAPTER_INFO;
pAI->strDriverDesc = (LPCTSTR)szData;
//AfxMessageBox((LPCTSTR)szData);
dwBufSize = 256;
if(RegQueryValueEx(hSubKey, "NetCfgInstanceID", 0, &dwDataType, szData, &dwBufSize) == ERROR_SUCCESS)
{
pAI->strName = (LPCTSTR)szData;
//AfxMessageBox((LPCTSTR)szData);
RegGetIP(pAI, (LPCTSTR)szData);
//AfxMessageBox((LPCTSTR)szData);
}
AdapterInfoVector.push_back(pAI); // 加入到容器中
}
}
}
RegCloseKey(hNdiIntKey);
}
RegCloseKey(hSubKey);
}

dwBufSize = 256;
} /* end of while */

RegCloseKey(hKey);

/*
// 可以使用GetAdaptersInfo来取得网卡信息,但其显示的名称不是很具体

ULONG ulAdapterInfoSize = sizeof(IP_ADAPTER_INFO);
IP_ADAPTER_INFO *pAdapterInfoBkp, *pAdapterInfo = (IP_ADAPTER_INFO*)new char[ulAdapterInfoSize];
if( GetAdaptersInfo(pAdapterInfo, &ulAdapterInfoSize) == ERROR_BUFFER_OVERFLOW ) // 缓冲区不够大
{
delete pAdapterInfo;
pAdapterInfo = (IP_ADAPTER_INFO*)new char[ulAdapterInfoSize];
pAdapterInfoBkp = pAdapterInfo;
}
if( GetAdaptersInfo(pAdapterInfo, &ulAdapterInfoSize) == ERROR_SUCCESS )
{
do {
if (pAdapterInfo->Type == MIB_IF_TYPE_ETHERNET)
{
ADAPTER_INFO *pAI = new ADAPTER_INFO;
pAI->strDriverDesc = pAdapterInfo->Description;
pAI->strName = pAdapterInfo->AdapterName;
RegGetIP(pAI, (LPCTSTR)pAdapterInfo->AdapterName); // 因为IP_ADAPTER_INFO中未包含掩码信息,所以干脆直接读注册表
AdapterInfoVector.push_back(pAI);
}
pAdapterInfo = pAdapterInfo->Next;
} while(pAdapterInfo);
}
delete pAdapterInfoBkp;
*/
return TRUE;
}

//-----------------------------------------------------------------
// 得到注册表中的IP信息
// nIndex暂时未处理
//-----------------------------------------------------------------

BOOL RegGetIP(ADAPTER_INFO *pAI, LPCTSTR lpszAdapterName, int nIndex/* =0 */)
{
ASSERT(pAI);

HKEY hKey;
string strKeyName = "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces\\";
strKeyName += lpszAdapterName;
if(RegOpenKeyEx(HKEY_LOCAL_MACHINE,
strKeyName.c_str(),
0,
KEY_READ,
&hKey) != ERROR_SUCCESS)
return FALSE;

unsigned char szData[256];
DWORD dwDataType, dwBufSize;

dwBufSize = 256;
if(RegQueryValueEx(hKey, "IPAddress", 0, &dwDataType, szData, &dwBufSize) == ERROR_SUCCESS)
pAI->strIP = (LPCTSTR)szData;

dwBufSize = 256;
if(RegQueryValueEx(hKey, "SubnetMask", 0, &dwDataType, szData, &dwBufSize) == ERROR_SUCCESS)
pAI->strNetMask = (LPCTSTR)szData;

dwBufSize = 256;
if(RegQueryValueEx(hKey, "DefaultGateway", 0, &dwDataType, szData, &dwBufSize) == ERROR_SUCCESS)
pAI->strNetGate = (LPCTSTR)szData;

RegCloseKey(hKey);
return TRUE;
}

//-----------------------------------------------------------------
// 设置注册表中的IP信息
//-----------------------------------------------------------------

BOOL RegSetIP(LPCTSTR lpszAdapterName, int nIndex, LPCTSTR pIPAddress, LPCTSTR pNetMask, LPCTSTR pNetGate)
{
HKEY hKey;
string strKeyName = "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces\\";
strKeyName += lpszAdapterName;
if(RegOpenKeyEx(HKEY_LOCAL_MACHINE,
strKeyName.c_str(),
0,
KEY_WRITE,
&hKey) != ERROR_SUCCESS)
return FALSE;

char mszIPAddress[100];
char mszNetMask[100];
char mszNetGate[100];

strncpy(mszIPAddress, pIPAddress, 98);
strncpy(mszNetMask, pNetMask, 98);
strncpy(mszNetGate, pNetGate, 98);

int nIP, nMask, nGate;

nIP = strlen(mszIPAddress);
nMask = strlen(mszNetMask);
nGate = strlen(mszNetGate);

*(mszIPAddress + nIP + 1) = 0x00;
nIP += 2;

*(mszNetMask + nMask + 1) = 0x00;
nMask += 2;

*(mszNetGate + nGate + 1) = 0x00;
nGate += 2;

RegSetValueEx(hKey, "IPAddress", 0, REG_MULTI_SZ, (unsigned char*)mszIPAddress, nIP);
RegSetValueEx(hKey, "SubnetMask", 0, REG_MULTI_SZ, (unsigned char*)mszNetMask, nMask);
RegSetValueEx(hKey, "DefaultGateway", 0, REG_MULTI_SZ, (unsigned char*)mszNetGate, nGate);

RegCloseKey(hKey);

return TRUE;
}

//-----------------------------------------------------------------
// 通知IP地址的改变
//-----------------------------------------------------------------

BOOL NotifyIPChange(LPCTSTR lpszAdapterName, int nIndex, LPCTSTR pIPAddress, LPCTSTR pNetMask)
{
BOOL bResult = FALSE;
HINSTANCE hDhcpDll;
DHCPNOTIFYPROC pDhcpNotifyProc;
WCHAR wcAdapterName[256];

MultiByteToWideChar(CP_ACP, 0, lpszAdapterName, -1, wcAdapterName,256);

if((hDhcpDll = LoadLibrary("dhcpcsvc")) == NULL)
return FALSE;

if((pDhcpNotifyProc = (DHCPNOTIFYPROC)GetProcAddress(hDhcpDll, "DhcpNotifyConfigChange")) != NULL)
if((pDhcpNotifyProc)(NULL, wcAdapterName, TRUE, nIndex, inet_addr(pIPAddress), inet_addr(pNetMask), 0) == ERROR_SUCCESS)
bResult = TRUE;

FreeLibrary(hDhcpDll);
return bResult;
}

//-----------------------------------------------------------------
// 设置IP地址
// 如果只绑定一个IP,nIndex = 0,暂时未处理一个网卡绑定多个地址
//-----------------------------------------------------------------

BOOL SetIP(LPCTSTR lpszAdapterName, int nIndex, LPCTSTR pIPAddress, LPCTSTR pNetMask, LPCTSTR pNetGate)
{
if(!RegSetIP(lpszAdapterName, nIndex, pIPAddress, pNetMask, pNetGate))
return FALSE;

if(!NotifyIPChange(lpszAdapterName, nIndex, pIPAddress, pNetMask))
return FALSE;

return TRUE;
}

yintongshun 2003-12-02
  • 打赏
  • 举报
回复
http://www.vckbase.com/document/viewdoc.asp?id=836
VC控件 TreeCtrl 与 ListCtrl 演示


作者:兰州大学现物系 王景生

下载源代码


这个例子类似于 Windows 的资源管理器,程序运行界面如图一所示:


图一

主要用到的类有:
CListCtrl,CTreeCtrl,CImageList,CFileFind 和函数SHGetFileInfo()

简述步骤如下:
1、增加 TreeCtrl 的 TVS_HASBUTTONS,TVS_HASLINES、TVS_LINESATROOT Style,代码如下:

DWORD dwStyle = GetWindowLong(m_tree.m_hWnd,GWL_STYLE);
dwStyle |= TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT;
SetWindowLong(m_tree.m_hWnd,GWL_STYLE,dwStyle);
2、为TreeCtrl添加Root项:
m_hRoot = m_tree.InsertItem("我的电脑");
InsertItem()的函数原形为
HTREEITEM InsertItem( LPCTSTR lpszItem, HTREEITEM hParent = TVI_ROOT,
HTREEITEM hInsertAfter = TVI_LAST );

3、获取本地逻辑驱动器,并添加: void CTreeViewDlg::GetLogicalDrives(HTREEITEM hParent)
{
size_t szAllDriveStrings = GetLogicalDriveStrings(0,NULL);
char *pDriveStrings = new char[szAllDriveStrings + sizeof(_T(""))];
GetLogicalDriveStrings(szAllDriveStrings,pDriveStrings);
size_t szDriveString = strlen(pDriveStrings);
while(szDriveString > 0)
{
m_tree.InsertItem(pDriveStrings,hParent);
pDriveStrings += szDriveString + 1;
szDriveString = strlen(pDriveStrings);
}
}

4、添加TVN_EXPANDED消息处理函数,当一项展开时,为其子项添加下一级目录: void CTreeViewDlg::OnItemexpandedTree(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
// TODO: Add your control notification handler code here
TVITEM item = pNMTreeView->itemNew;
if(item.hItem == m_hRoot)
return;
HTREEITEM hChild = m_tree.GetChildItem(item.hItem);
while(hChild)
{
AddSubDir(hChild);
hChild = m_tree.GetNextItem(hChild,TVGN_NEXT);
}
*pResult = 0;
}
AddSubDir函数功能添加子项,具体代码见示例。

5、添加TVN_SELCHANGED消息处理函数,在这个函数里,用GetFullPath()取得选中项的绝 路径(GetFullPath()具体代码看示例),在ListCtrl中添加文件而非文件夹的图标: void CTreeViewDlg::OnSelchangedTree(NMHDR* pNMHDR, LRESULT* pResult)
{
m_list.DeleteAllItems();
NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
TVITEM item = pNMTreeView->itemNew;
if(item.hItem == m_hRoot)
return;
CString str = GetFullPath(item.hItem);
if(str.Right(1) != "\\")
str += "\\";
str += "*.*";
CFileFind file;
BOOL bContinue = file.FindFile(str);
while(bContinue)
{
bContinue = file.FindNextFile();
if(!file.IsDirectory() && !file.IsDots())
{
SHFILEINFO info;
CString temp = str;
int index = temp.Find("*.*");
temp.Delete(index,3);
SHGetFileInfo(temp + file.GetFileName(),
0,
&info,sizeof(&info),
SHGFI_DISPLAYNAME | SHGFI_ICON);
int i = m_ImageList.Add(info.hIcon);
m_list.InsertItem(i,info.szDisplayName,i);
}
}
*pResult = 0;
}
这只是一个简单的例子,你可以在 ListCtrl 中添加鼠标双击消息的处理函数,用 Process 打开该选中的文件; 该示例在VC6,xp下编译通过。


yintongshun 2003-12-02
  • 打赏
  • 举报
回复
引用是一种没有指针语法的指针.与指针一样,引用提供对对象的间接访问.
--《c++ primer》p29
虽然引用也可以被用作一种指针,但是象对指针一样用一个对象的地址初始化一个引用却是错误的。例如:
int i=0;
int &refi=i; //ok,refi指向一个i的引用

//int &refi=&i; 错误,不能用指针初始化引用


一旦引用已定义,它就不能再指向其他的对象(这也是为什么必须要被初始化的原因)。

引用所有的操作实际上都被应用在它所指的对象身上,包括取地址操作。例如:
refi+=2;
等同于: i+=2;

int *j=&refi;
等同于: int *j=&i;

--《c++ primer》p87


yintongshun 2003-12-02
  • 打赏
  • 举报
回复
指针和引用[转贴]
发信站: 华师陶园站 (Tue Jun 10 14:19:15 2003), 转信

主题:没人发贴?偶发一个问题
偶实在是搞不懂指针和引用有什么不同——
void func(int* p)和void func(int &i)对传进的参数有什么不一样?当然传出时赋值
语句不一样,但是就结果来说是一样的吧?BTW 偶除了C/C++的语法书上外似乎少见人
用过引用传参数。

----------------------------------------------------------------------------



主题:指针更灵活,引用更安全
1.引用比指针更安全,比如:如果调用一个某类型空悬指针(空悬指针:此指针的值不是空,但
是它指向的内容已经被delete了)的成员方法,可能导致系统崩溃;但是引用永远不能是空的
,就不存在这个问题.
2. 指针比引用更灵活
2.1 你可以让一个函数的最后一个参数的类型是指针,缺省值是NULL;在这个函数里面检查
此指针的值,如果是空则做1,如果不是空则做2(会用到这个指针).而如果用引用,则无法实
现.
2.2 对指针可以有++, --这样的操作;而引用永远只能指向一个地方.而且只能在初始化的
时候指定,以后就不能重新指向别的对象了.
----------------------------------------------------------------------------


主题:不能一概而论
安全,小强指的是由于程序员的使用的问题而使指针不安全,难道就说指针不安全吗?我
认为不是这样的,按引用主要用于传递一些结构或者对象之类的大的东西,速度比较快,
按引用并不比指针安全多少,因为在过程里,也有很大可能改变传递的值,指针也是一样
,最主要就是避免野指针的问题,引用我个人觉得比指针好用,呵呵
----------------------------------------------------------------------------


主题:小心小心再小心
C就是比较麻烦就是很多时候CORE DUMP了都不知道在哪里出的错。野指针的问题呢,如果
你在使用完(FREE或DELETE)P之后记住赋个NULL,然后尽量不要再用这个P;做传入参数
的话,函数里第一句话就要判断P是否是NULL再做判断吧;传出参数的话,函数里第二句就
先给它赋个默认值咯。
C里面最怕人家的程序CORE DUMP了,而且要你改,还没有文档,我FT!
----------------------------------------------------------------------------


主题:什么是安全?什么是不安全?
李铁,我不同意你的看法。
什么是安全?什么是不安全?容易让程序员出错的东西就是不安全的,不容易出错的东西
就是相对安全的。即使不用指针,不用引用;也可能出现一个变量是除数,这个变量的值
是零,结果一除,程序倒掉了。你能说“普通自动变量不比指针安全多少”或者“普通自
动变量不比引用安全多少”吗?
编程的不是机器,是人;是人就会犯错误,没有不犯错误的人,除非他不是人。那我们就
要首先认识到容易犯错误的地方,不安全的因素有哪些。再采用一定的体制(或者叫行动
)来减少这些失误,比如代码交叉审核等。
----------------------------------------------------------------------------


主题:回复
人犯错误难道是对的?虽然人会犯错误,但是C不会,它只会按照程序员编写的那样执行,
而不在乎对错,我觉得程序员由于对于指针和引用的东西没有完全掌握的话,实在不能称
为真正合格的程序员,但是由于自身犯的错误,怎么能说是C的问题呢?
其实,如果对指针和引用的底层的东西不熟,只要知道什么时候用指针好,什么时候用引
用好就可以了,不能说因为本身对C的了解的局限,而说什么安全什么不安全,其实有很多
程序员对指针和引用一知半解,并不奇怪,可以找书看看
----------------------------------------------------------------------------


主题:我的一点看法
让我们回到引用和指针参数的区别上来吧。对于杨强的说法,我是大部分同意的,只是第
一点的举例我个人认为不是很恰当,它们的区别应该不在失效指针上,而是说对于指针参
数,函数内部第一件要做的事应该是判空,对非空指针才可以继续操作。此外,它们还有
一个区别,指针参数暴露了函数内部的实现,不够直观,尤其是在重载操作符的时候,举
例来说:
值传递
CTest operator+(CTest param1, CTest param2);
用例:a+b,a+b+c
指针传递
CTest operator+(CTest *param1, CTest *param2);
用例:&a+&b,&(&a+&b)+&c
引用传递
CTest operator+(CTest *param1, CTest *param2);
用例:a+b,a+b+c
指针和引用作为C/C++里面的语法元素,自有它最擅长的地方,只要你用的顺手,也不必拘
泥。
----------------------------------------------------------------------------


主题:第一,对空悬指针判空是没用的.第二,指针参数没有暴露函数内部实现.
第一,对空悬指针判空是没用的.
空悬指针是那些new过之后,又delete了的指针,这个指针的值不是NULL,除非手动赋值成NU
LL.你不能假设所有的程序员在对所有的指针delete之后都给指针赋值成NULL了.应该这么
做的道理我们都懂,但是知易行难,总会有人有疏漏的时候.尽管可能这种疏漏只有很小的几
率.因此通过判断指针是否为空来判断是否是失效指针,这种做法不能完全避免使用无效指
针的可能.
第二,指针参数没有暴露函数内部实现.
过程化编程,以致现在的面向对象编程的核心思想就是隐藏实现细节.函数这种实体本身就
是用来隐藏内部实现的,不管函数用了什么样的参数,除非它返回一个函数内的局部静态变
量的指针(或引用)或者它使用了一个全局变量,你都不能说一个函数暴露了内部实现细节.
而且可以把基类指针作为函数的形式参数,在传实参的时候使用一个派生类的指针;这种做
法恰恰是隐藏函数实现细节,达到系统灵活性和可维护性需求的一个非常常用的方法.
第三,丘兄有句话我不能认同.
指针和引用作为C/C++里面的语法元素,自有它最擅长的地方,只要你用的顺手,也不必拘
泥。
确实有一些地方使用指针和引用都差不多.但是当我们面临选择的时候,使用指针还是引用
绝对不能是靠是否顺手来确定.因为有的时候就是其中一个比另一个好.

yintongshun 2003-12-02
  • 打赏
  • 举报
回复

需要引起注意的是IDINFO第57-58 WORD (CHS可寻址的扇区数),因为不满足32位对齐的要求,不可定义为一个ULONG字段。Lynn McGuire的程序里正是由于定义为一个ULONG字段,导致该结构不可用。

以下是核心代码:

// 打开设备
// filename: 设备的“文件名”
HANDLE OpenDevice(LPCTSTR filename)
{
HANDLE hDevice;

// 打开设备
hDevice= ::CreateFile(filename, // 文件名
GENERIC_READ | GENERIC_WRITE, // 读写方式
FILE_SHARE_READ | FILE_SHARE_WRITE, // 共享方式
NULL, // 默认的安全描述符
OPEN_EXISTING, // 创建方式
0, // 不需设置文件属性
NULL); // 不需参照模板文件

return hDevice;
}

// 向驱动发“IDENTIFY DEVICE”命令,获得设备信息
// hDevice: 设备句柄
// pIdInfo: 设备信息结构指针
BOOL IdentifyDevice(HANDLE hDevice, PIDINFO pIdInfo)
{
PSENDCMDINPARAMS pSCIP; // 输入数据结构指针
PSENDCMDOUTPARAMS pSCOP; // 输出数据结构指针
DWORD dwOutBytes; // IOCTL输出数据长度
BOOL bResult; // IOCTL返回值

// 申请输入/输出数据结构空间
pSCIP = (PSENDCMDINPARAMS)::GlobalAlloc(LMEM_ZEROINIT, sizeof(SENDCMDINPARAMS)-1);
pSCOP = (PSENDCMDOUTPARAMS)::GlobalAlloc(LMEM_ZEROINIT, sizeof(SENDCMDOUTPARAMS)+sizeof(IDINFO)-1);

// 指定ATA/ATAPI命令的寄存器值
// pSCIP->irDriveRegs.bFeaturesReg = 0;
// pSCIP->irDriveRegs.bSectorCountReg = 0;
// pSCIP->irDriveRegs.bSectorNumberReg = 0;
// pSCIP->irDriveRegs.bCylLowReg = 0;
// pSCIP->irDriveRegs.bCylHighReg = 0;
// pSCIP->irDriveRegs.bDriveHeadReg = 0;
pSCIP->irDriveRegs.bCommandReg = IDE_ATA_IDENTIFY;

// 指定输入/输出数据缓冲区大小
pSCIP->cBufferSize = 0;
pSCOP->cBufferSize = sizeof(IDINFO);

// IDENTIFY DEVICE
bResult = ::DeviceIoControl(hDevice, // 设备句柄
DFP_RECEIVE_DRIVE_DATA, // 指定IOCTL
pSCIP, sizeof(SENDCMDINPARAMS) - 1, // 输入数据缓冲区
pSCOP, sizeof(SENDCMDOUTPARAMS) + sizeof(IDINFO) - 1, // 输出数据缓冲区
&dwOutBytes, // 输出数据长度
(LPOVERLAPPED)NULL); // 用同步I/O

// 复制设备参数结构
::memcpy(pIdInfo, pSCOP->bBuffer, sizeof(IDINFO));

// 释放输入/输出数据空间
::GlobalFree(pSCOP);
::GlobalFree(pSCIP);

return bResult;
}

// 向SCSI MINI-PORT驱动发“IDENTIFY DEVICE”命令,获得设备信息
// hDevice: 设备句柄
// pIdInfo: 设备信息结构指针
BOOL IdentifyDeviceAsScsi(HANDLE hDevice, int nDrive, PIDINFO pIdInfo)
{
PSENDCMDINPARAMS pSCIP; // 输入数据结构指针
PSENDCMDOUTPARAMS pSCOP; // 输出数据结构指针
PSRB_IO_CONTROL pSRBIO; // SCSI输入输出数据结构指针
DWORD dwOutBytes; // IOCTL输出数据长度
BOOL bResult; // IOCTL返回值

// 申请输入/输出数据结构空间
pSRBIO = (PSRB_IO_CONTROL)::GlobalAlloc(LMEM_ZEROINIT, sizeof(SRB_IO_CONTROL)+sizeof(SENDCMDOUTPARAMS)+sizeof(IDINFO)-1);
pSCIP = (PSENDCMDINPARAMS)((char *)pSRBIO+sizeof(SRB_IO_CONTROL));
pSCOP = (PSENDCMDOUTPARAMS)((char *)pSRBIO+sizeof(SRB_IO_CONTROL));

// 填充输入/输出数据
pSRBIO->HeaderLength = sizeof(SRB_IO_CONTROL);
pSRBIO->Timeout = 10000;
pSRBIO->Length = sizeof(SENDCMDOUTPARAMS)+sizeof(IDINFO)-1;
pSRBIO->ControlCode = IOCTL_SCSI_MINIPORT_IDENTIFY;
::strncpy ((char *)pSRBIO->Signature, "SCSIDISK", 8);

// 指定ATA/ATAPI命令的寄存器值
// pSCIP->irDriveRegs.bFeaturesReg = 0;
// pSCIP->irDriveRegs.bSectorCountReg = 0;
// pSCIP->irDriveRegs.bSectorNumberReg = 0;
// pSCIP->irDriveRegs.bCylLowReg = 0;
// pSCIP->irDriveRegs.bCylHighReg = 0;
// pSCIP->irDriveRegs.bDriveHeadReg = 0;
pSCIP->irDriveRegs.bCommandReg = IDE_ATA_IDENTIFY;
pSCIP->bDriveNumber = nDrive;

// IDENTIFY DEVICE
bResult = ::DeviceIoControl(hDevice, // 设备句柄
IOCTL_SCSI_MINIPORT, // 指定IOCTL
pSRBIO, sizeof(SRB_IO_CONTROL) +sizeof(SENDCMDINPARAMS) - 1, // 输入数据缓冲区
pSRBIO, sizeof(SRB_IO_CONTROL) +sizeof(SENDCMDOUTPARAMS) + sizeof(IDINFO) - 1, // 输出数据缓冲区
&dwOutBytes, // 输出数据长度
(LPOVERLAPPED)NULL); // 用同步I/O

// 复制设备参数结构
::memcpy(pIdInfo, pSCOP->bBuffer, sizeof(IDINFO));

// 释放输入/输出数据空间
::GlobalFree(pSRBIO);

return bResult;
}

// 将串中的字符两两颠倒
// 原因是ATA/ATAPI中的WORD,与Windows采用的字节顺序相反
// 驱动程序中已经将收到的数据全部反过来,我们来个负负得正
void AdjustString(char* str, int len)
{
char ch;
int i;

// 两两颠倒
for(i=0;i<len;i+=2)
{
ch = str[i];
str[i] = str[i+1];
str[i+1] = ch;
}

// 若是右对齐的,调整为左对齐 (去掉左边的空格)
i=0;
while(i<len && str[i]==' ') i++;

::memmove(str, &str[i], len-i);

// 去掉右边的空格
i = len - 1;
while(i>=0 && str[i]==' ')
{
str[i] = '\0';
i--;
}
}

// 读取IDE硬盘的设备信息,必须有足够权限
// nDrive: 驱动器号(0=第一个硬盘,1=0=第二个硬盘,......)
// pIdInfo: 设备信息结构指针
BOOL GetPhysicalDriveInfoInNT(int nDrive, PIDINFO pIdInfo)
{
HANDLE hDevice; // 设备句柄
BOOL bResult; // 返回结果
char szFileName[20]; // 文件名

::sprintf(szFileName,"\\\\.\\PhysicalDrive%d", nDrive);

hDevice = ::OpenDevice(szFileName);

if(hDevice == INVALID_HANDLE_VALUE)
{
return FALSE;
}

// IDENTIFY DEVICE
bResult = ::IdentifyDevice(hDevice, pIdInfo);

if(bResult)
{
// 调整字符串
::AdjustString(pIdInfo->sSerialNumber, 20);
::AdjustString(pIdInfo->sModelNumber, 40);
::AdjustString(pIdInfo->sFirmwareRev, 8);
}

::CloseHandle (hDevice);

return bResult;
}

// 用SCSI驱动读取IDE硬盘的设备信息,不受权限制约
// nDrive: 驱动器号(0=Primary Master, 1=Promary Slave, 2=Secondary master, 3=Secondary slave)
// pIdInfo: 设备信息结构指针
BOOL GetIdeDriveAsScsiInfoInNT(int nDrive, PIDINFO pIdInfo)
{
HANDLE hDevice; // 设备句柄
BOOL bResult; // 返回结果
char szFileName[20]; // 文件名

::sprintf(szFileName,"\\\\.\\Scsi%d:", nDrive/2);

hDevice = ::OpenDevice(szFileName);

if(hDevice == INVALID_HANDLE_VALUE)
{
return FALSE;
}

// IDENTIFY DEVICE
bResult = ::IdentifyDeviceAsScsi(hDevice, nDrive%2, pIdInfo);

// 检查是不是空串
if(pIdInfo->sModelNumber[0]=='\0')
{
bResult = FALSE;
}

if(bResult)
{
// 调整字符串
::AdjustString(pIdInfo->sSerialNumber, 20);
::AdjustString(pIdInfo->sModelNumber, 40);
::AdjustString(pIdInfo->sFirmwareRev, 8);
}

return bResult;
}


Q 我注意到ATA/ATAPI里,以及DiskID32里,有一个“IDENTIFY PACKET DEVICE”指令,与“IDENTIFY DEVICE”有什么区别?

A IDENTIFY DEVICE专门用于固定硬盘,而IDENTIFY PACKET DEVICE用于可移动存储设备如CDROM、CF、MO、ZIP、TAPE等。因为驱动程序的原因,实际上用本例的方法,不管是IDENTIFY DEVICE也好,IDENTIFY PACKET DEVICE也好,获取可移动存储设备的详细信息,一般是做不到的。而且除了IDE硬盘,对SCSI、USB等接口的硬盘也不起作用。除非厂商提供的驱动支持这样的功能。

Q ATA/ATAPI有很多指令,如READ SECTORS, WRITE SECTORS, SECURITY, SLEEP, STANDBY等,利用上述方法,是否可进行相应操作?

A 应该没问题。但切记,要慎重慎重再慎重!


Q 关于权限问题,请解释一下好吗?

A 在NT/2000/XP下,administrator可以管理设备,上述两种访问驱动的方法都行。但在user身份下,或者登录到域后,用户无法访问PhysicalDrive驱动的核心层,但SCSI MINI-PORT驱动却可以。目前是可以,不知道Windows以后的版本是否支持。因为这肯定是一个安全隐患。
另外,我们着重讨论NT/2000/XP中DeviceIoControl的应用,如果需要在98/ME中得到包括硬盘序列号在内的更加详细的信息,请参考DiskID32。

yintongshun 2003-12-02
  • 打赏
  • 举报
回复
获取硬盘的详细信息
Q 用IOCTL_DISK_GET_DRIVE_GEOMETRY或IOCTL_STORAGE_GET_MEDIA_TYPES_EX只能得到很少的磁盘参数,我想获得包括硬盘序列号在内的更加详细的信息,有什么办法呀?

A 确实,用你所说的I/O控制码,只能得到最基本的磁盘参数。获取磁盘出厂信息的I/O控制码,微软在VC/MFC环境中没有开放,在DDK中可以发现一些线索。早先,Lynn McGuire写了一个很出名的获取IDE硬盘详细信息的程序DiskID32,下面的例子是在其基础上经过增删和改进而成的。
本例中,我们要用到ATA/APAPI的IDENTIFY DEVICE指令。ATA/APAPI是国际组织T13起草和发布的IDE/EIDE/UDMA硬盘及其它可移动存储设备与主机接口的标准,至今已经到了ATA/APAPI-7版本。该接口标准规定了ATA/ATAPI设备的输入输出寄存器和指令集。欲了解更详细的ATA/ATAPI技术资料,可访问T13的站点。
用到的常量及数据结构有以下一些:

// IOCTL控制码
// #define DFP_SEND_DRIVE_COMMAND 0x0007c084
#define DFP_SEND_DRIVE_COMMAND CTL_CODE(IOCTL_DISK_BASE, 0x0021, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
// #define DFP_RECEIVE_DRIVE_DATA 0x0007c088
#define DFP_RECEIVE_DRIVE_DATA CTL_CODE(IOCTL_DISK_BASE, 0x0022, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)

#define FILE_DEVICE_SCSI 0x0000001b
#define IOCTL_SCSI_MINIPORT_IDENTIFY ((FILE_DEVICE_SCSI << 16) + 0x0501)
#define IOCTL_SCSI_MINIPORT 0x0004D008 // see NTDDSCSI.H for definition

// ATA/ATAPI指令
#define IDE_ATA_IDENTIFY 0xEC // ATA的ID指令(IDENTIFY DEVICE)

// IDE命令寄存器
typedef struct _IDEREGS
{
BYTE bFeaturesReg; // 特征寄存器(用于SMART命令)
BYTE bSectorCountReg; // 扇区数目寄存器
BYTE bSectorNumberReg; // 开始扇区寄存器
BYTE bCylLowReg; // 开始柱面低字节寄存器
BYTE bCylHighReg; // 开始柱面高字节寄存器
BYTE bDriveHeadReg; // 驱动器/磁头寄存器
BYTE bCommandReg; // 指令寄存器
BYTE bReserved; // 保留
} IDEREGS, *PIDEREGS, *LPIDEREGS;

// 从驱动程序返回的状态
typedef struct _DRIVERSTATUS
{
BYTE bDriverError; // 错误码
BYTE bIDEStatus; // IDE状态寄存器
BYTE bReserved[2]; // 保留
DWORD dwReserved[2]; // 保留
} DRIVERSTATUS, *PDRIVERSTATUS, *LPDRIVERSTATUS;

// IDE设备IOCTL输入数据结构
typedef struct _SENDCMDINPARAMS
{
DWORD cBufferSize; // 缓冲区字节数
IDEREGS irDriveRegs; // IDE寄存器组
BYTE bDriveNumber; // 驱动器号
BYTE bReserved[3]; // 保留
DWORD dwReserved[4]; // 保留
BYTE bBuffer[1]; // 输入缓冲区(此处象征性地包含1字节)
} SENDCMDINPARAMS, *PSENDCMDINPARAMS, *LPSENDCMDINPARAMS;

// IDE设备IOCTL输出数据结构
typedef struct _SENDCMDOUTPARAMS
{
DWORD cBufferSize; // 缓冲区字节数
DRIVERSTATUS DriverStatus; // 驱动程序返回状态
BYTE bBuffer[1]; // 输入缓冲区(此处象征性地包含1字节)
} SENDCMDOUTPARAMS, *PSENDCMDOUTPARAMS, *LPSENDCMDOUTPARAMS;

// IDE的ID命令返回的数据
// 共512字节(256个WORD),这里仅定义了一些感兴趣的项(基本上依据ATA/ATAPI-4)
typedef struct _IDINFO
{
USHORT wGenConfig; // WORD 0: 基本信息字
USHORT wNumCyls; // WORD 1: 柱面数
USHORT wReserved2; // WORD 2: 保留
USHORT wNumHeads; // WORD 3: 磁头数
USHORT wReserved4; // WORD 4: 保留
USHORT wReserved5; // WORD 5: 保留
USHORT wNumSectorsPerTrack; // WORD 6: 每磁道扇区数
USHORT wVendorUnique[3]; // WORD 7-9: 厂家设定值
CHAR sSerialNumber[20]; // WORD 10-19:序列号
USHORT wBufferType; // WORD 20: 缓冲类型
USHORT wBufferSize; // WORD 21: 缓冲大小
USHORT wECCSize; // WORD 22: ECC校验大小
CHAR sFirmwareRev[8]; // WORD 23-26: 固件版本
CHAR sModelNumber[40]; // WORD 27-46: 内部型号
USHORT wMoreVendorUnique; // WORD 47: 厂家设定值
USHORT wReserved48; // WORD 48: 保留
struct {
USHORT reserved1:8;
USHORT DMA:1; // 1=支持DMA
USHORT LBA:1; // 1=支持LBA
USHORT DisIORDY:1; // 1=可不使用IORDY
USHORT IORDY:1; // 1=支持IORDY
USHORT SoftReset:1; // 1=需要ATA软启动
USHORT Overlap:1; // 1=支持重叠操作
USHORT Queue:1; // 1=支持命令队列
USHORT InlDMA:1; // 1=支持交叉存取DMA
} wCapabilities; // WORD 49: 一般能力
USHORT wReserved1; // WORD 50: 保留
USHORT wPIOTiming; // WORD 51: PIO时序
USHORT wDMATiming; // WORD 52: DMA时序
struct {
USHORT CHSNumber:1; // 1=WORD 54-58有效
USHORT CycleNumber:1; // 1=WORD 64-70有效
USHORT UnltraDMA:1; // 1=WORD 88有效
USHORT reserved:13;
} wFieldValidity; // WORD 53: 后续字段有效性标志
USHORT wNumCurCyls; // WORD 54: CHS可寻址的柱面数
USHORT wNumCurHeads; // WORD 55: CHS可寻址的磁头数
USHORT wNumCurSectorsPerTrack; // WORD 56: CHS可寻址每磁道扇区数
USHORT wCurSectorsLow; // WORD 57: CHS可寻址的扇区数低位字
USHORT wCurSectorsHigh; // WORD 58: CHS可寻址的扇区数高位字
struct {
USHORT CurNumber:8; // 当前一次性可读写扇区数
USHORT Multi:1; // 1=已选择多扇区读写
USHORT reserved1:7;
} wMultSectorStuff; // WORD 59: 多扇区读写设定
ULONG dwTotalSectors; // WORD 60-61: LBA可寻址的扇区数
USHORT wSingleWordDMA; // WORD 62: 单字节DMA支持能力
struct {
USHORT Mode0:1; // 1=支持模式0 (4.17Mb/s)
USHORT Mode1:1; // 1=支持模式1 (13.3Mb/s)
USHORT Mode2:1; // 1=支持模式2 (16.7Mb/s)
USHORT Reserved1:5;
USHORT Mode0Sel:1; // 1=已选择模式0
USHORT Mode1Sel:1; // 1=已选择模式1
USHORT Mode2Sel:1; // 1=已选择模式2
USHORT Reserved2:5;
} wMultiWordDMA; // WORD 63: 多字节DMA支持能力
struct {
USHORT AdvPOIModes:8; // 支持高级POI模式数
USHORT reserved:8;
} wPIOCapacity; // WORD 64: 高级PIO支持能力
USHORT wMinMultiWordDMACycle; // WORD 65: 多字节DMA传输周期的最小值
USHORT wRecMultiWordDMACycle; // WORD 66: 多字节DMA传输周期的建议值
USHORT wMinPIONoFlowCycle; // WORD 67: 无流控制时PIO传输周期的最小值
USHORT wMinPOIFlowCycle; // WORD 68: 有流控制时PIO传输周期的最小值
USHORT wReserved69[11]; // WORD 69-79: 保留
struct {
USHORT Reserved1:1;
USHORT ATA1:1; // 1=支持ATA-1
USHORT ATA2:1; // 1=支持ATA-2
USHORT ATA3:1; // 1=支持ATA-3
USHORT ATA4:1; // 1=支持ATA/ATAPI-4
USHORT ATA5:1; // 1=支持ATA/ATAPI-5
USHORT ATA6:1; // 1=支持ATA/ATAPI-6
USHORT ATA7:1; // 1=支持ATA/ATAPI-7
USHORT ATA8:1; // 1=支持ATA/ATAPI-8
USHORT ATA9:1; // 1=支持ATA/ATAPI-9
USHORT ATA10:1; // 1=支持ATA/ATAPI-10
USHORT ATA11:1; // 1=支持ATA/ATAPI-11
USHORT ATA12:1; // 1=支持ATA/ATAPI-12
USHORT ATA13:1; // 1=支持ATA/ATAPI-13
USHORT ATA14:1; // 1=支持ATA/ATAPI-14
USHORT Reserved2:1;
} wMajorVersion; // WORD 80: 主版本
USHORT wMinorVersion; // WORD 81: 副版本
USHORT wReserved82[6]; // WORD 82-87: 保留
struct {
USHORT Mode0:1; // 1=支持模式0 (16.7Mb/s)
USHORT Mode1:1; // 1=支持模式1 (25Mb/s)
USHORT Mode2:1; // 1=支持模式2 (33Mb/s)
USHORT Mode3:1; // 1=支持模式3 (44Mb/s)
USHORT Mode4:1; // 1=支持模式4 (66Mb/s)
USHORT Mode5:1; // 1=支持模式5 (100Mb/s)
USHORT Mode6:1; // 1=支持模式6 (133Mb/s)
USHORT Mode7:1; // 1=支持模式7 (166Mb/s) ???
USHORT Mode0Sel:1; // 1=已选择模式0
USHORT Mode1Sel:1; // 1=已选择模式1
USHORT Mode2Sel:1; // 1=已选择模式2
USHORT Mode3Sel:1; // 1=已选择模式3
USHORT Mode4Sel:1; // 1=已选择模式4
USHORT Mode5Sel:1; // 1=已选择模式5
USHORT Mode6Sel:1; // 1=已选择模式6
USHORT Mode7Sel:1; // 1=已选择模式7
} wUltraDMA; // WORD 88: Ultra DMA支持能力
USHORT wReserved89[167]; // WORD 89-255
} IDINFO, *PIDINFO;

// SCSI驱动所需的输入输出共用的结构
typedef struct _SRB_IO_CONTROL
{
ULONG HeaderLength; // 头长度
UCHAR Signature[8]; // 特征名称
ULONG Timeout; // 超时时间
ULONG ControlCode; // 控制码
ULONG ReturnCode; // 返回码
ULONG Length; // 缓冲区长度
} SRB_IO_CONTROL, *PSRB_IO_CONTROL;

yintongshun 2003-12-02
  • 打赏
  • 举报
回复
如何将一个文件从一个目录复制到另一个目录中

你可以调用API函数CopyFile,如:
Copy("C:\\MyFile.txt", "D:\\MyDir\MyFile.txt", FALSE);
你也可以使用SHFileOperation来实现更复杂的操作。


如何在编程的过程中随时结束应用程序(常规)

这虽是个很简单的问题,但在编程时却常常要遇到.

1)需要向窗口发送 WM_CLOSE/WM_QUIT消息,
调用 CWnd::OnClose成员函数并允许对用户提示是否保存修改过的数据.
AfxGetMainWnd()->SendMessage(WM_CLOSE); //别忘了先得到当前窗口的指针

2)使用函数: void PostQuitMessage( int nExitCode // exit code );

3)使用标准函数:void exit( int status ); //尽量不要在MFC中使用




如何在InstallShield中使用自己的DLL

  作为一个开发人员,当我们为用户开发好应用系统时,就要包装分发自己的程序给最终用户。一般情况下,相应的开发系统中也提供了相应的分发工具,例如Visual Basic6.0中的 [Package & Deployment 向导] ,但是有的开发系统提供的都是英文版的,为了适应中国用户,我们需要选择一款易学易用,界面友好的工具软件,而 InstallShield 就是最好的工具之一。在此我选用的是 InstallShield 5.1 远东专业版本,此版本可以制作出支持多种语言界面的安装程序。

  InstallShield 的功能相当强大,能够对系统和安装需要提供丰富的函数支持,有足够灵活的界面控制支持,而且其脚本语言特点与Visual C++的代码极其相似,非常适合使用Visual C++的开发者使用。

  但是,在某些情况下,InstallShield不能提供能完成某种特殊功能的相应函数,这时需要我们自己开发出能够完成此功能的函数,然后放在InstallShield中调用,达到与其完美的结合,DLL是最佳的选择。

  本文以一个实例介绍了如何生成自己的DLL,以及如何在 InstallShield中调用。

一、制作DLL:

  此动态库函数主要是显示出传给自己的字符串,并做了改动返回(如果要在InstallShield中使用改动过的字符串,在InstallShield中原型声明时,用POINTER对应LPSTR),并且根据传入的数据返回不同的返回值,用户只要根据此原理实现自己的函数即可:

  1、在Visual C++中的 New 中选择 MFC AppWizard(DLL) 工程类型,输入工程名Setup00,点OK,直接 Finish 即可。然后在ClassView中,用鼠标右击Csetup00App,选择Add Member Function…,在Function Type中输入int,在Function Declaration中输入Test(LPSTR lpszStr, int iVal),Access类型选择Public,然后输入下面函数体。 Setup00.DLL的Test函数例码:

int CSetup00App::Test(LPSTR lpszStr, int iVal)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
AfxMessageBox(lpszStr);
//如果想改变字符串,用下面方法
strcpy(lpszStr,_T("动态库已经变了字符串"));
if (iVal == 1)
return 1;
else
return 2;
}
  2、在FileView中,从Source Files中双击 Setup00.def,在代码; Explicit exports can go here的下一行输入: Test @1
  3、选择成Release版本,从 Build 菜单上选择 Build Setup00.dll即可。

二、在 InstallShield 中使用实例:

  1、在 Setup Files Pane 中的 Language IndependentOperating System Independent下用 Insert Files 命令添入 Setup00.dll 。这样,你的动态库就可以在制作安装盘时被压入 _user1.cab 中,

  如果你没有把DLL放至_user1.cab中,你可以把DLL和你的应用程序放到一块,然后从目标系统中调用。但是,如果你已经做了,你必须指明你要使用的DLL的位置以使安装程序能够找到。而且你必须确信在调用DLL之前它已经被拷贝到预定目录之下。

  2、在脚本程序的程序段中,添加下面代码:

// 声明setp00.dll中的Test函数原型:
prototype INT Setup00.Test( STRING,INT);
  //如果想要在DLL中改变字符串值,需要将STRING声明成按指针传递,如下:
//prototype INT Setup00.Test( BYREF STRING,INT);

STRING szDLL, svString;
INT nValue;
POINTER psvString;
BOOL bDone;
NUMBER nResult;

program
szDLL = DLL_FILE;
/*--------------------------------------*
* Load SETUP00.DLL into memory.
*--------------------------------------*/
nResult = UseDLL (szDLL);
if (nResult = 0) then
MessageBox ("动态库调入内存成功!", INFORMATION);
else
MessageBox ("动态库调入内存失败!", INFORMATION);
abort;
endif;
AskText("请输入示例字符串:", "这是示例字符串", svString);
nValue = StrLength(svString);
nResult = Test(svString,nValue);

// 显示调用Setup00.dll中Test函数后的字符串
SprintfBox(INFORMATION, "调用动态库", "
调用后的字符串为:%s ", svString);
if (UnUseDLL (szDLL) < 0) then
MessageBox("卸载动态库失败,仍在内存中!", SEVERE);
else
MessageBox("从内存中卸载动态库成功!",
INFORMATION);
endif;
三、请使用下面参数类型对照表,在原型声明和外部函数之间进行数据类型匹配。


  在安装程序调用动态库函数时需要遵循三个规则:

  1、动态库函数的名字最长为33个字符;InstallShield函数名最长为80个字符。

  2、InstallShield在调用DLL时,不能接受组合参数(就是说一个大于由个字节的参数),然而参数可以是一个指向组合结构的指针。

  3、在16位平台上应调用16位DLL;而在32位平台上应调用32位DLL。

  

yintongshun 2003-12-02
  • 打赏
  • 举报
回复
窗口最大化、最小化及关闭的消息是什么


最大化、最小化将发送WM_SYSCOMMAND消息。要处理该消息,可以这么做:
1、在Form的头文件中添加:
void __fastcall RestrictMinimizeMaximize(TMessage &Msg);

BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_SYSCOMMAND, TMessage, RestrictMinimizeMaximize)
END_MESSAGE_MAP(TForm)
2、在Form的单元文件中添加:

void __fastcall TForm1::RestrictMinimizeMaximize(TMessage& Msg)
{
if (Msg.WParam == SC_MINIMIZE)
{
//catches minimize...
}
else if (Msg.WParam == SC_MAXIMIZE)
{
//catches maximize...
}
TForm::Dispatch(&Msg);
// or "else TForm::Dispatch(&Msg)" to trap
}
关闭窗口的消息为WM_CLOSE,C++Builder提供了OnClose事件


得到文件的大小

发布者: soarlove ——>查看soarlove在VCCode发布的所有文章
发布日期:2002.08.20
在BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)中加一下代码:

程序执行自动隐藏

cs.style =WS_POPUP;//使主窗口不可见
cs.dwExStyle |=WS_EX_TOOLWINDOW; 不显示任务按钮
return CFrameWnd::PreCreateWindow(cs);


int GetmpgFileSize(CString filename)
{
CFile cf;
int t1;
if( cf.Open( filename, CFile::modeCreate | CFile::modeWrite,NULL ) )
{
t1=cf.GetFileLength();
}
cf.Close();
return t1;

Visual C++实现文件间批量转换功能

一。前言

  本人在开发惠普色谱工作站增强软件的过程中,实现了把指定目录下的色谱数据文件全部转换成纯文本文件的功能。下面,通过用一个具体的例子来说明此功能的实现方法。

  全部代码用Visual C++6.0在Windows95/98/2000下编译通过。

  二、实例

  首先用MFC AppWizard生成一个SDI风格的应用程序test,生成过程中全部使用缺省设置。

  其次,利用资源编辑器,在主菜单“文件”下增加一个菜单项“转换”,属性为:

   ID:ID_CONVERT

   Caption: 转换

   Prompt: 在不同格式文件之间进行转换\n转换文件

  然后用“CTRL-W”热键激活MFC ClassWizard,为CmainFrame类增加响应ID_CONVERT消息的命令函数OnConvert()。加入转换功能的代码如下所示:

   void CMainFrame::OnConvert()
    {
     LPMALLOC pMalloc;//利用shell扩展功能
     BROWSEINFO bi;
     if (SUCCEEDED(SHGetMalloc(&pMalloc)))//为生成目录选择对话框分配自由内存
      {
       ZeroMemory(&bi,sizeof(bi));//清零分配的空间
       char pszDirName[MAX_PATH];//存放选择的目录名
       LPITEMIDLIST pidl;
       bi.hwndOwner = GetSafeHwnd();
       bi.pidlRoot = NULL;
       bi.pszDisplayName = pszDirName;
       bi.lpszTitle = _T("选择要批量转换文件所在的目录");
       bi.ulFlags = BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
       bi.lpfn = NULL;
       bi.lParam = 0;
       if ((pidl = ::SHBrowseForFolder(&bi)) != NULL)//调用选择目录对话框
        {
         if (::SHGetPathFromIDList(pidl, pszDirName))//获得所选择的目录
          {
           file://设置选择的目录为当前目录,以便查找
            SetCurrentDirectory(pszDirName);
            file://定义一个查找
            CFileFind findch1;
            CString strconv;
            CString strsour;
          if(findch1.FindFile("*.CH1"))//在当前目录进行查找
           {
            CFile SourceFile;
            CStdioFile TargetFile;
            BOOL bfindresult;
            do
            {
             file://查找下一个符合条件的文件
              bfindresult= findch1.FindNextFile();
              file://获得查找到的文件名
              strsour=findch1.GetFilePath();
              strconv=strsour;
              file://把文件名转换为小写
              strconv.MakeLower();
              file://把*.ch1类型的文件转换为*.txt
              strconv.Replace(".ch1",".txt");
              file://打开*.ch1类型的文件作为源文件
              SourceFile.Open(strsour,CFile::modeRead);
              file://打开*.txt类型的文件作为目标文件
              TargetFile.Open(strconv,CFile::modeCreate|CFile::modeWrite);

              file://此处调用*.ch1类型的文件的解码函数
              file://此处调用转换成文本文件的函数
              file://文件使用完毕,要关闭
              SourceFile.Close();
              TargetFile.Close();
             }while(bfindresult);
             MessageBox("转换完毕!","转换完毕!",MB_OK);
            }
          else
           {
            MessageBox("没找到CH1文件","没找到",MB_OK);
           }
          findch1.Close();//关闭这个搜索
         }
        pMalloc->Free(pidl);//释放使用完的资源
        }
       pMalloc->Release();//释放使用完的资源
      }
     }

  编译并运行程序,选择“文件”菜单下的“转换”命令, 选择一个目录就完成了对此目录下所有具有.ch1扩展名的文件的转换工作。

  三、结论

  本程序利用了Windows95/98/2000下的shell扩展功能,实现了对一个用户指定目录的拾取,再利用MFC的CfileFind类,来检索出所有想要转换的源文件,最后通过编码转换把源文件内容输出到指定类型的目标文件。这个方法也适合于要大量在不同格式的文件之间进行转换的工作,如图形文件、声音文件等的转换。希望有兴趣的朋友和我交流讨论。
 


16,551

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC相关问题讨论
社区管理员
  • 基础类社区
  • AIGC Browser
  • encoderlee
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

        VC/MFC社区版块或许是CSDN最“古老”的版块了,记忆之中,与CSDN的年龄几乎差不多。随着时间的推移,MFC技术渐渐的偏离了开发主流,若干年之后的今天,当我们面对着微软的这个经典之笔,内心充满着敬意,那些曾经的记忆,可以说代表着二十年前曾经的辉煌……
        向经典致敬,或许是老一代程序员内心里面难以释怀的感受。互联网大行其道的今天,我们期待着MFC技术能够恢复其曾经的辉煌,或许这个期待会永远成为一种“梦想”,或许一切皆有可能……
        我们希望这个版块可以很好的适配Web时代,期待更好的互联网技术能够使得MFC技术框架得以重现活力,……

试试用AI创作助手写篇文章吧