用WinInet开发Internet客户端应用指南

实现步骤

大家知道,每个Internet客户端
程序都伴随有一定的目的行为,如读文件、写文件、删除文件等等。客户端的程序要实现这些行为的先决条件是建立Internet连接。然后再根据不同的目的进行具体的操作。为了方便起见,下面这这些张表格针对不同的应用行为列出了所需要的具体操作。其中列出了一般的Internet URL (FTP、或者 HTTP)客户端行为要实现某个目标所必须使用的方法。这张表格的内容来自MSDN。我对部分我认为重要的地方做了补充。

(表一)一个典型的Internet客户端
程序的处理流程

目的 方法 结果
开始一个Internet session 创建 CInternetSession 对象 初始化WinInet,并连接服务器
读取或设置 InternetQuery 选项 (如超时或重试次数) 调用 CInternetSession::SetOption 不成功返回FALSE
建立回调函数监视session状态 调用CInternetSession::EnableStatusCallback
建立回调函数
CInternetSession::OnStatusCallback,重写OnStatusCallback,创建自己的回调例程
Internet服务器Intranet服务器或本地文件 调用 CInternetSession::OpenURL 解析并打开到指定服务器的连接,返回CStdioFile(如果你传递的OpenURL是本地文件名)或CInternetFile对象,通过存取这个对象,获得服务器文件的数据
文件 调用 CInternetFile::Read 用你提供的Buffer读指定的字节数
异常处理 在 CInternetException 类中处理 处理所有普通的 Internet 异常类型
结束 Internet session 处理 CInternetSession对象 自动清除打开的句柄的连接


(表二)典型的 FTP 客户端程序实现的一般步骤

目的 方法 结果
开始一个FTP会话,建立一个FTP连接 创建一个CInternetSession对象,调用CInternetSession::GetFtpConnection 初始化WinInet
并联接服务器
连接到一个FTP Server 用CInternetSession::GetFtpConnection 返回一个CFtpConnection对象
CD到 FTP 服务器的一个新目录 用CFtpConnection::SetCurrentDirectory CD到FTP服务器的一个
新目录
Find 第一个FTP目录中的文件 创建一个CFtpFileFind对象,调用CFtpFileFind::FindFile,OpenURL函数返回一个只读资源对象;调用CFtpFileFind::FindFile Find第一个文件,如果文件每找到返回FALSE
枚举所有可获得的资源,Find下一个FTP目录中的文件 Find下一个资源,调用CFtpFileFind::FindNextFile直到返回FALSE。 Find下一个文件
如果文件没找到返回FALSE
打开FindFile或FindNextFile找到的文件(用于读写) 调用CFtpConnection::OpenFile,参数为FindFile或FindNextFile返回的文件名 ,创建并打开一个CInternetFile对象 打开FindFile或FindNextFile找到的文件(用于读写),返回一个CInternetFile对象
读写文件 以读方式打开FTP文件,用CInternetFile::Read 使用你指定的缓冲读
指定的字节数
写FTP文件 以写方式打开FTP文件,调用CInternetFile::Write,重写CInternetSession::OnStatusCallback 使用你指定的缓冲写
指定的字节数
改变客户端在服务器上的目录 调用CFtpConnection::SetCurrentDirectory 进入新的目录
获取客户端在服务器上的当前目录 调用CFtpConnection::GetCurrentDirectory 获取目录信息
异常处理 用CInternetException类  处理所有普通的Internet异常类型
结束FTP session 处理CInternetSession对象 自动清除打开的句柄的连接


(表三)显示了一个典型的删除文件的FTP客户端应用要实现的一般步骤:

目的 方法 结果
开始一个FTP session 创建一个CInternetSession对象 初始化WinInet
并联接服务器
连接到一个FTP Server 用CInternetSession::GetFtpConnection 返回一个CFtpConnection对象
检查FTP目录是否正确 用CFtpConnection::GetCurrentDirectory或CFtpConnection::GetCurrentDirectoryAsURL 返回目录名字
服务器目录或返回目录的URL
CD(改变目录)到 FTP 服务器的一个新目录 用CFtpConnection::SetCurrentDirectory CD到FTP服务器的一个
新目录
Find 第一个FTP目录中的文件 用CFtpFileFind::FindFile Find第一个文件,如果文件每找到返回FALSE
Find 下一个FTP目录中的文件 用CFtpFileFind::FindNextFile Find下一个文件
如果文件没找到返回FALSE
删除FindFile或FindNextFile找到的文件  用CFtpConnection::Remove用FindFile或FindNextFile返回的文件 删除FindFile或FindNextFile
找到的文件    
异常处理 用CInternetException类  处理所有普通的Internet异常类型
结束FTP session 处理CInternetSession对象 自动清除打开的句柄的连接


(表四)显示了实现一个典型的 HTTP 客户端应用程序的一般步骤:

目的 方法 结果
开始HTTP会话,建立HTTP连接 创建 CInternetSession对象,调用CInternetSession::GetHttpConnection
创建CHttpConnection对象
初始化WinInet并联接服务器,返回一个CHttpConnection对象
创建一个 HTTP 请求 调用CHttpConnection::OpenRequest
创建一个CHttpFile对象;
返回一个CHttpFile对象
发送一个HTTP 请求 用CHttpFile::AddRequestHeaders 并且用CHttpFile::SendRequest Find一个文件
如果文件没找到返回FALSE
文件 调用CInternetFile::Read 使用你提供的缓冲读指定的字节
获取HTTP请求信息 调用CHttpFile::QueryInfo 服务器获取HTTP请求头信息
异常处理 利用CInternetException类 处理所有普通的Internet异常类型
结束 HTTP 会话 处理CInternetSession对象 自动清除打开的句柄的连接

由于时间关系,我没有写本文的例子代码。不过MSDN里有两个简单的例子可以参考,一个是FTPTREE,另一个是TEAR。此外,也可以用“WinInet”作为关键字在google里搜一下也能找到一些使用MFC WinInet的技术信息。(完)

9月3日

VC Post HTTP

vc中如何用post方法提交表单 在vc中如何用post方法提交表单!

我这里有一段程序,用来在一个对话框里显示出一次http request的原始信息,不过使用Inet API做的,希望能有帮助。

void CHTTPRequestDlg::OnButtonRequest()
{
UpdateData(TRUE);
HINTERNET hInternet = InternetOpen("Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 5.0)", INTERNET_OPEN_TYPE_DIRECT,
NULL, NULL, NULL);
HINTERNET hSession = InternetConnect(hInternet, m_strHost,
m_nPort, "username", "password",
INTERNET_SERVICE_HTTP, 0, 0);
char* szAccept[] = {"*/*", NULL};
CString strVerb;
m_comboVerb.GetWindowText(strVerb);
HINTERNET hRequest = HttpOpenRequest(hSession, strVerb, m_strObject, NULL, NULL, (LPCSTR*)szAccept, 0, 0);
struct
{
char* Language;
char* Encoding;
char* ContentType;
}Headers = {"Accept-Language: zh-cn\r\n",
"Accept-Encoding: gzip, deflate\r\n",
"Content-Type: application/x-www-form-urlencoded\r\n"};

if(m_bLanguage)
HttpAddRequestHeaders(hRequest, Headers.Language, -1, HTTP_ADDREQ_FLAG_ADD|HTTP_ADDREQ_FLAG_REPLACE);
if(m_bEncoding)
HttpAddRequestHeaders(hRequest, Headers.Encoding, -1, HTTP_ADDREQ_FLAG_ADD|HTTP_ADDREQ_FLAG_REPLACE);
if(m_bContentType)
HttpAddRequestHeaders(hRequest, Headers.ContentType, -1, HTTP_ADDREQ_FLAG_ADD|HTTP_ADDREQ_FLAG_REPLACE);
LPCSTR lpAddHeader = NULL, lpContent = NULL;
if(m_strHeaders.GetLength())
{
if(m_strHeaders.Right(2) != "\r\n")
m_strHeaders += "\r\n";
lpAddHeader = (LPCSTR)m_strHeaders;
}
if(m_strContent.GetLength() && (strVerb == "POST" || strVerb == "PUT"))
lpContent = (LPCSTR)m_strContent;
HttpSendRequest(hRequest, lpAddHeader, -1, (LPVOID)lpContent, m_strContent.GetLength());

m_editContentGot.SetSel(0, -1);
m_editContentGot.ReplaceSel("");


LPSTR lpszData; // buffer for the data
DWORD dwSize; // size of the data available
DWORD dwDownloaded; // size of the downloaded data

// Set the cursor to an hourglass.
SetCursor(LoadCursor(NULL,IDC_WAIT));

// This loop handles reading the data.
while(1)
{
// The call to InternetQueryDataAvailable determines the amount of
// data available to download.
if (!InternetQueryDataAvailable(hRequest,&dwSize,0,0))
{
SetCursor(LoadCursor(NULL,IDC_ARROW));
break;
}
else
{
// Allocates a buffer of the size returned by InternetQueryDataAvailable
lpszData = new char[dwSize+1];

// Reads the data from the HINTERNET handle.
if(!InternetReadFile(hRequest,(LPVOID)lpszData,dwSize,&dwDownloaded))
{
delete[] lpszData;
break;
}
else
{
// Adds a null terminator to the end of the data buffer
lpszData[dwDownloaded]='\0';

int nLen = m_editContentGot.GetWindowTextLength();
m_editContentGot.SetSel(nLen-1, nLen-1);
m_editContentGot.ReplaceSel(lpszData);

// Delete the two buffers
delete[] lpszData;

// Check the size of the remaining data. If it is zero, break.
if (dwDownloaded == 0)
break;
}
}
}

// Close the HINTERNET handle
InternetCloseHandle(hRequest);
InternetCloseHandle(hSession);
InternetCloseHandle(hInternet);

// Set the cursor back to an arrow
SetCursor(LoadCursor(NULL,IDC_ARROW));



使用MFC示例如下:
首先设置m_strRequest请求字符串 eg."name=aaa&pass=bbb";
m_strServerName 服务器名称或者IP eg."www.yahoo.com"
m_strObjectName 请求文件位置 eg. "pub/aaa.asp"
请求的结果存放在m_strHtml中
func(){
CInternetSession m_InetSession("session");
CHttpConnection* pServer = NULL;
CHttpFile* pFile = NULL;
try{
INTERNET_PORT nPort;
nPort=80;
pServer = m_InetSession.GetHttpConnection(m_strServerName, nPort);
pFile = pServer->OpenRequest(CHttpConnection::HTTP_VERB_POST,
m_strObjectName);
char szHeaders[100];
strcpy(szHeaders,"Accept: text*/*\r\nContent-Type: application/x-www-form-urlencoded");
pFile->AddRequestHeaders(szHeaders);


pFile->SendRequestEx(m_strRequest.GetLength());
pFile->WriteString(m_strRequest); //重要-->m_Request 中有"name=aaa&name2=BBB&..."
pFile->EndRequest();
DWORD dwRet;
pFile->QueryInfoStatusCode(dwRet);
CString str;

m_Mutex.Lock();
m_strHtml="";
char szBuff[1024];
if (dwRet == HTTP_STATUS_OK){
UINT nRead;
while ((nRead = pFile->Read(szBuff,1023))>0)
{
m_strHtml+=CString(szBuff,nRead);
}
}
m_Mutex.Unlock();

delete pFile;
delete pServer;
}
catch (CInternetException* e){
CString s;
s.Format("Internet Exception\r\nm_dwError%u,m_dwContextError%u",e->m_dwError,e->m_dwContext);
AfxMessageBox(s);
//catch errors from WinInet
}
}

DOM应用---遍历网页中的元素

DOM应用---遍历网页中的元素

作者:杨老师

下载源代码

一、摘要
  在我们编写的程序中,如果想要实现对浏览器打开的网页进行监视、模拟操纵、动态提取用户输入、动态修改......等功能,那么请你抽出宝贵的时间,继续往下阅读。本文介绍的知识和示例程序都是围绕如何遍历 HTML 中的表单(form)并枚举出表单域的属性为目标的,对于网页中的其它元素,比如图象、连接、脚本等等,应用同样的方法都可以轻松实现。

二、网页的文档层次结构
  IE 浏览器,采用 DOM(文档对象模型)来管理网页的数据。它通过一个容器(IWebBrowser2/IHTMLWindow2)来装载网页文档(IHTMLDocument2),而一个文档,又可以由 0 或多个贞(frame)组成,管理这些贞的接口叫“框架集合(IHTMLFramesCollection2)”,而每个贞的容器又是 IHTMLWindow2,和IWebBrowser2一样,它也装载着各自的文档(IHTMLDocument2)。因此,我们的第一个任务,就是想方设法能够得到IHTMLDocument2的接口。因为文档可能包含贞,而贞又包含着子文档,子文档可能再包含贞......,如此要得到所有的文档,这里有一个递归遍历的处理过程。
  得到文档(IHTMLDocument2)后,下一步任务就是要设法取得表单了(IHTMLFormElement)。因为在一个文档中可以包含 0 或多个表单(form),而管理这些表单的又是一个表单集合(IHTMLElementCollection),所以必须先得到集合,然后再枚举出所有的表单条目了。
  得到表单(IHTMLFormElement)后,接下来的事情就简单了,逐个提取表单中的元素(也叫表单域 IHTMLInputElement)就可以读写这些域的属性了。
  说了半天,我估计初次接触的朋友一定没有听懂:( 呵呵,还是用图的方式表示一下吧,这样比较清晰一些。
 

三、程序实现

<1> 取得 IHTMLDocument2 的接口指针。根据IE浏览器的运行方式,有多种不同的方式可以获取文档指针。
  <1.1> 如果你在程序中使用MFC的 CHtmlView 视来浏览网页。
        取得文档的方法最简单,调用 CHtmlView::GetHtmlDocument() 函数。
  <1.2> 如果你的程序中使用了“Web 浏览器” 的ActiveX 控件。
        取得文档的方法也比较简单,调用 CWebBrowser2::GetDocument() 函数。
  <1.3> 如果你的程序是用 ATL 写的 ActiveX 控件。
        那么需要调用 IOleClientSite::GetContainer 得到 IOleContainer 接口,然后就可以通过 QueryInterface() 查询得到 IHTMLDocument2 的接口。主要代码如下:

CComPtr < IOleContainer > spContainer;
m_spClientSite->GetContainer( &spContainer );
CComQIPtr < IHTMLDocument2 > spDoc = spContainer;
if ( spDoc )
{
// 已经得到了 IHTMLDocument2 的接口指针
}
  <1.4> 如果你的程序是用 MFC 写的 ActiveX 控件。
        那么需要调用 COleControl::GetClientSite() 得到 IOleContainer 接口,然后的操作和<1.3>是一致的了。
  <1.5> IE 浏览器作为独立的进程正在运行。
        每个运行的浏览器(IE 和 资源浏览器)都会在 ShellWindows 中进行登记,因此我们要通过 IShellWindows 取得实例(示例程序中使用的就是这个方法)。主要代码如下:
#include < atlbase.h >
#include < mshtml.h >

void FindFromShell()
{
CComPtr< IShellWindows > spShellWin;
HRESULT hr = spShellWin.CoCreateInstance( CLSID_ShellWindows );
if ( FAILED( hr ) ) return;

long nCount=0;
spShellWin->get_Count(&nCount); // 取得浏览器实例个数

for(long i=0; i<nCount; i++)
{
CComPtr< IDispatch ><nCount; i++)
{
CComPtr< IDispatch ><nCount; i++)
{
CComPtr< IDispatch > spDisp;
hr=spShellWin->Item(CComVariant( i ), &spDisp );
if ( FAILED( hr ) ) continue;

CComQIPtr< IWebBrowser2 > spBrowser = spDisp;
if ( !spBrowser ) continue;

spDisp.Release();
hr = spBrowser->get_Document( &spDisp );
if ( FAILED ( hr ) ) continue;

CComQIPtr< IHTMLDocument2 > spDoc = spDisp;
if ( !spDoc ) continue;

// 程序运行到此,已经找到了 IHTMLDocument2 的接口指针
}
}

  <1.6> IE 浏览器控件被一个进程包装在一个子窗口中。那么你首先要得到那个进程的顶层窗口句柄(使用 FindWindow() 函数,或其它任何可行的方法),然后枚举所有子窗口,通过判断窗口类名是否是“Internet Explorer_Server”,从而得到浏览器的窗口句柄,再向窗口发消息取得文档的接口指针。主要代码如下:

#include < atlbase.h >
#include < mshtml.h >
#include < oleacc.h >
#pragma comment ( lib, "oleacc" )

BOOL CALLBACK EnumChildProc(HWND hwnd,LPARAM lParam)
{
TCHAR szClassName[100];

::GetClassName( hwnd, &szClassName, sizeof(szClassName) );
if ( _tcscmp( szClassName, _T("Internet Explorer_Server") ) == 0 )
{
*(HWND*)lParam = hwnd;
return FALSE; // 找到第一个 IE 控件的子窗口就停止
}
else return TRUE; // 继续枚举子窗口
};

void FindFromHwnd(HWND hWnd)
{
HWND hWndChild=NULL;
::EnumChildWindows( hWnd, EnumChildProc, (LPARAM)&hWndChild );
if(NULL == hWndChild) return;

UINT nMsg = ::RegisterWindowMessage( _T("WM_HTML_GETOBJECT") );
LRESULT lRes;
::SendMessageTimeout( hWndChild, nMsg, 0L, 0L, SMTO_ABORTIFHUNG, 1000, (DWORD*) &lRes );

CComPtr < IHTMLDocument2 > spDoc;
HRESULT hr = ::ObjectFromLresult ( lRes, IID_IHTMLDocument2, 0 , (LPVOID *) &spDoc );
if ( FAILED ( hr ) ) return;

// 程序运行到此,已经找到了 IHTMLDocument2 的接口指针
}
<2> 得到了 IHTMLDocument2 接口指针后,如果网页是单贞的,那么转第<4>步骤。如果是多贞(有子框架)则还需要遍历所有的子框架。这些子框架(IHTMLWindow2),被保存在集合中(IHTMLFramesCollection2),取得集合指针的方法比较简单,取属性 IHTMLDocument2::get_frames()。
<3> 首先取得子框架的总数目 IHTMLFramesCollection::get_length(),接着就可以循环调用 IHTMLFramesCollection::item()函数一个一个地取得子框架 IHTMLWindow2 指针,然后转第<1>步。
<4> 一个文档中可能拥有多个表单,因此还是同样的道理,先要取得表单的集合(IHTMLElementCollection,其实这个不光是表单的集合,其他元素的集合,比如图片集合也是用它)。这个操作也很简单,取得属性 IHTMLDocument2::get_forms()。
<5> 属性 IHTMLElementCollection::get_length() 得到表单总数目,就可以循环取得每一个表单指针了 IHTMLElementCollection::item()。
<6> 在第<5>步中的item()函数,得到的是一个IDispatch的指针,你通过QueryInterface()查询,就可以得到 某类型输入的指针,代码如下:
// 假设 spDisp 是由IHTMLElementCollection::item() 得到的 IDispatch 指针
CComQIPtr < IHTMLInputTextElement > spInputText(spDisp);
CComQIPtr < IHTMLInputButtonElement > spInputButton(spDisp);
CComQIPtr < IHTMLInputHiddenElement > spInputHidden(spDisp);
......
if ( spInputText )
{
//如果是文本输入表单域
}
else if ( spInputButton )
{
//如果是按纽输入表单域
}
else if ( spInputHiddent )
{
//如果是隐藏输入表单域
}
else if ........ //其它输入类型
  上面的方法,由于使用具体类型的接口指针,因此程序的效率比较高。但是通过 QueryInterface 接口查询,然后再进行条件判断显然是比较烦琐的,所以这个方法适合于特定的已知网页设计内容的程序。在示例程序中,我则是直接使用 IDispatch 接口进行操作的,这个方式执行起来稍微慢一些,但程序比较简单。主要代码和说明如下:
#include < atlbase.h >
CComModule _Module; // 由于需要使用 CComDispatchDriver 的 IDispatch 包装类ATL智能指针,所以这个是必须的
#include < atlcom.h >
......
long nElemCount=0; //表单域的总数目
spFormElement->get_length( &nElemCount );

for(long j=0; j< nElemCount; j++)
{
CComDispatchDriver spInputElement; // IDispatch 的智能指针
spFormElement->item( CComVariant( j ), CComVariant(), &spInputElement );

CComVariant vName,vVal,vType; // 域名称,域值,域类型
spInputElement.GetPropertyByName( L"name", &vName );
spInputElement.GetPropertyByName( L"value",&vVal );
spInputElement.GetPropertyByName( L"type", &vType );
// 使用 IDispatch 的智能指针的好处就是:象上面这样读取、设置属性很简单
// 另外调用 Invoke 函数也异常方便,Invoke0(),Invoke1(),Invoke2()....
......
}
四、结束语
  示例程序在 VC6 下编译执行通过。运行方法:随便启动几个 IE 浏览网页,最好是有表单输入的网页。然后执行示例的 EXE 程序即可。到这里,就到这里了......祝大家学习快乐 ^-^

CString Usage

 VC++中的CString操作指南

 
 主题:c++ | 作者:auguester | 标签: | 浏览(437) | 评论(0) | 01-12 15:00

VC++中的CString操作指南
原文出处:codeproject:CString Management


通过阅读本文你可以学习如何有效地使用 CString。

  CString 是一种很有用的数据类型。它们很大程度上简化了MFC中的许多操作,使得MFC在做字符串操作的时候方便了很多。不管怎样,使用CString有很多特殊的技巧,特别是对于纯C背景下走出来的程序员来说有点难以学习。这篇文章就来讨论这些技巧。
  使用CString可以让你对字符串的操作更加直截了当。这篇文章不是CString的完全手册,但囊括了大部分常见基本问题。

这篇文章包括以下内容:

CString 对象的连接

格式化字符串(包括 int 型转化为 CString )
CString 型转化成 int 型
CString 型和 char* 类型的相互转化

char* 转化成 CString
CString 转化成 char* 之一:使用LPCTSTR强制转化
CString 转化成 char* 之二:使用CString对象的GetBuffer方法
CString 转化成 char* 之三: 和控件的接口
CString 型转化成 BSTR 型;
BSTR 型转化成 CString 型;
VARIANT 型转化成 CString 型;
载入字符串表资源;
CString 和临时对象;
CString 的效率;
总结
下面我分别讨论。

1、CString 对象的连接

  能体现出 CString 类型方便性特点的一个方面就字符串的连接,使用 CString 类型,你能很方便地连接两个字符串,正如下面的例子:

CString gray("Gray");
CString cat("Cat");
CString graycat = gray + cat;
要比用下面的方法好得多:

char gray[] = "Gray";
char cat[] = "Cat";
char * graycat = malloc(strlen(gray) + strlen(cat) + 1);
strcpy(graycat, gray);
strcat(graycat, cat);
2、格式化字符串

  与其用 sprintf() 函数或 wsprintf() 函数来格式化一个字符串,还不如用 CString 对象的Format()方法:

CString s;
s.Format(_T("The total is %d"), total);
  用这种方法的好处是你不用担心用来存放格式化后数据的缓冲区是否足够大,这些工作由CString类替你完成。
  格式化是一种把其它不是字符串类型的数据转化为CString类型的最常用技巧,比如,把一个整数转化成CString类型,可用如下方法:

CString s;
s.Format(_T("%d"), total);
  我总是对我的字符串使用_T()宏,这是为了让我的代码至少有Unicode的意识,当然,关于Unicode的话题不在这篇文章的讨论范围。_T()宏在8位字符环境下是如下定义的:

#define _T(x) x // 非Unicode版本(non-Unicode version)
而在Unicode环境下是如下定义的:

#define _T(x) L##x // Unicode版本(Unicode version)
所以在Unicode环境下,它的效果就相当于:

s.Format(L"%d", total);
  如果你认为你的程序可能在Unicode的环境下运行,那么开始在意用 Unicode 编码。比如说,不要用 sizeof() 操作符来获得字符串的长度,因为在Unicode环境下就会有2倍的误差。我们可以用一些方法来隐藏Unicode的一些细节,比如在我需要获得字符长度的时候,我会用一个叫做DIM的宏,这个宏是在我的dim.h文件中定义的,我会在我写的所有程序中都包含这个文件:

#define DIM(x) ( sizeof((x)) / sizeof((x)[0]) )
  这个宏不仅可以用来解决Unicode的字符串长度的问题,也可以用在编译时定义的表格上,它可以获得表格的项数,如下:

class Whatever { ... };
Whatever data[] = {
{ ... },
...
{ ... },
};
for(int i = 0; i < DIM(data); i++) // 扫描表格寻找匹配项。

  这里要提醒你的就是一定要注意那些在参数中需要真实字节数的API函数调用,如果你传递字符个数给它,它将不能正常工作。如下:
TCHAR data[20];
lstrcpyn(data, longstring, sizeof(data) - 1); // WRONG!
lstrcpyn(data, longstring, DIM(data) - 1); // RIGHT
WriteFile(f, data, DIM(data), &bytesWritten, NULL); // WRONG!
WriteFile(f, data, sizeof(data), &bytesWritten, NULL); // RIGHT
造成以上原因是因为lstrcpyn需要一个字符个数作为参数,但是WriteFile却需要字节数作为参数。
同样需要注意的是有时候需要写出数据的所有内容。如果你仅仅只想写出数据的真实长度,你可能会认为你应该这样做:

WriteFile(f, data, lstrlen(data), &bytesWritten, NULL); // WRONG
但是在Unicode环境下,它不会正常工作。正确的做法应该是这样:

WriteFile(f, data, lstrlen(data) * sizeof(TCHAR), &bytesWritten, NULL); // RIGHT
  因为WriteFile需要的是一个以字节为单位的长度。(可能有些人会想“在非Unicode的环境下运行这行代码,就意味着总是在做一个多余的乘 1操作,这样不会降低程序的效率吗?”这种想法是多余的,你必须要了解编译器实际上做了什么,没有哪一个C或C++编译器会把这种无聊的乘1操作留在代码中。在Unicode环境下运行的时候,你也不必担心那个乘2操作会降低程序的效率,记住,这只是一个左移一位的操作而已,编译器也很乐意为你做这种替换。)
  使用_T宏并不是意味着你已经创建了一个Unicode的程序,你只是创建了一个有Unicode意识的程序而已。如果你在默认的8-bit模式下编译你的程序的话,得到的将是一个普通的8-bit的应用程序(这里的8-bit指的只是8位的字符编码,并不是指8位的计算机系统);当你在 Unicode环境下编译你的程序时,你才会得到一个Unicode的程序。记住,CString 在 Unicode 环境下,里面包含的可都是16位的字符哦。

3、CString 型转化成 int 型

  把 CString 类型的数据转化成整数类型最简单的方法就是使用标准的字符串到整数转换例程。
  虽然通常你怀疑使用_atoi()函数是一个好的选择,它也很少会是一个正确的选择。如果你准备使用 Unicode 字符,你应该用_ttoi(),它在 ANSI 编码系统中被编译成_atoi(),而在 Unicode 编码系统中编译成_wtoi()。你也可以考虑使用_tcstoul()或者_tcstol(),它们都能把字符串转化成任意进制的长整数(如二进制、八进制、十进制或十六进制),不同点在于前者转化后的数据是无符号的(unsigned),而后者相反。看下面的例子:

CString hex = _T("FAB");
CString decimal = _T("4011");
ASSERT(_tcstoul(hex, 0, 16) == _ttoi(decimal));
4、CString 型和 char* 类型的相互转化

  这是初学者使用 CString 时最常见的问题。有了 C++ 的帮助,很多问题你不需要深入的去考虑它,直接拿来用就行了,但是如果你不能深入了解它的运行机制,又会有很多问题让你迷惑,特别是有些看起来没有问题的代码,却偏偏不能正常工作。
比如,你会奇怪为什么不能写向下面这样的代码呢:

CString graycat = "Gray" + "Cat";
或者这样:

CString graycat("Gray" + "Cat");
  事实上,编译器将抱怨上面的这些尝试。为什么呢?因为针对CString 和 LPCTSTR数据类型的各种各样的组合,“ +” 运算符被定义成一个重载操作符。而不是两个 LPCTSTR 数据类型,它是底层数据类型。你不能对基本数据(如 int、char 或者 char*)类型重载 C++ 的运算符。你可以象下面这样做:

CString graycat = CString("Gray") + CString("Cat");
或者这样:

CString graycat = CString("Gray") + "Cat";
研究一番就会发现:“ +”总是使用在至少有一个 CString 对象和一个 LPCSTR 的场合。

注意,编写有 Unicode 意识的代码总是一件好事,比如:

CString graycat = CString(_T("Gray")) + _T("Cat");
这将使得你的代码可以直接移植。

char* 转化为 CString

  现在你有一个 char* 类型的数据,或者说一个字符串。怎么样创建 CString 对象呢?这里有一些例子:

char * p = "This is a test";
或者象下面这样更具有 Unicode 意识:

TCHAR * p = _T("This is a test")


LPTSTR p = _T("This is a test");
你可以使用下面任意一种写法:

CString s = "This is a test"; // 8-bit only
CString s = _T("This is a test"); // Unicode-aware
CString s("This is a test"); // 8-bit only
CString s(_T("This is a test")); // Unicode-aware
CString s = p;
CString s(p);
  用这些方法可以轻松将常量字符串或指针转换成 CString。需要注意的是,字符的赋值总是被拷贝到 CString 对象中去的,所以你可以象下面这样操作:

TCHAR * p = _T("Gray");
CString s(p);
p = _T("Cat");
s += p;
结果字符串肯定是“GrayCat”。

CString 类还有几个其它的构造函数,但是这里我们不考虑它,如果你有兴趣可以自己查看相关文档。

事实上,CString 类的构造函数比我展示的要复杂,比如:

CString s = "This is a test";
  这是很草率的编码,但是实际上它在 Unicode 环境下能编译通过。它在运行时调用构造函数的 MultiByteToWideChar 操作将 8 位字符串转换成 16 位字符串。不管怎样,如果 char * 指针是网络上传输的 8 位数据,这种转换是很有用的。

CString 转化成 char* 之一:强制类型转换为 LPCTSTR;

  这是一种略微硬性的转换,有关“正确”的做法,人们在认识上还存在许多混乱,正确的使用方法有很多,但错误的使用方法可能与正确的使用方法一样多。
  我们首先要了解 CString 是一种很特殊的 C++ 对象,它里面包含了三个值:一个指向某个数据缓冲区的指针、一个是该缓冲中有效的字符记数以及一个缓冲区长度。有效字符数的大小可以是从0到该缓冲最大长度值减1之间的任何数(因为字符串结尾有一个NULL字符)。字符记数和缓冲区长度被巧妙隐藏。
  除非你做一些特殊的操作,否则你不可能知道给CString对象分配的缓冲区的长度。这样,即使你获得了该0缓冲的地址,你也无法更改其中的内容,不能截短字符串,也绝对没有办法加长它的内容,否则第一时间就会看到溢出。
  LPCTSTR 操作符(或者更明确地说就是 TCHAR * 操作符)在 CString 类中被重载了,该操作符的定义是返回缓冲区的地址,因此,如果你需要一个指向 CString 的 字符串指针的话,可以这样做:


CString s("GrayCat");
LPCTSTR p = s;
  它可以正确地运行。这是由C语言的强制类型转化规则实现的。当需要强制类型转化时,C++规测容许这种选择。比如,你可以将(浮点数)定义为将某个复数(有一对浮点数)进行强制类型转换后只返回该复数的第一个浮点数(也就是其实部)。可以象下面这样:

Complex c(1.2f, 4.8f);
float realpart = c;
如果(float)操作符定义正确的话,那么实部的的值应该是1.2。
  这种强制转化适合所有这种情况,例如,任何带有 LPCTSTR 类型参数的函数都会强制执行这种转换。于是,你可能有这样一个函数(也许在某个你买来的DLL中):

BOOL DoSomethingCool(LPCTSTR s);
你象下面这样调用它:

CString file("c:\\myfiles\\coolstuff")
BOOL result = DoSomethingCool(file);
  它能正确运行。因为 DoSomethingCool 函数已经说明了需要一个 LPCTSTR 类型的参数,因此 LPCTSTR 被应用于该参数,在 MFC 中就是返回的串地址。

如果你要格式化字符串怎么办呢?

CString graycat("GrayCat");
CString s;
s.Format("Mew! I love %s", graycat);
  注意由于在可变参数列表中的值(在函数说明中是以“...”表示的)并没有隐含一个强制类型转换操作符。你会得到什么结果呢?
  一个令人惊讶的结果,我们得到的实际结果串是:

"Mew! I love GrayCat"。
  因为 MFC 的设计者们在设计 CString 数据类型时非常小心, CString 类型表达式求值后指向了字符串,所以这里看不到任何象 Format 或 sprintf 中的强制类型转换,你仍然可以得到正确的行为。描述 CString 的附加数据实际上在 CString 名义地址之后。
  有一件事情你是不能做的,那就是修改字符串。比如,你可能会尝试用“,”代替“.”(不要做这样的,如果你在乎国际化问题,你应该使用十进制转换的 National Language Support 特性,),下面是个简单的例子:

CString v("1.00"); // 货币金额,两位小数
LPCTSTR p = v;
p[lstrlen(p) - 3] = '','';
  这时编译器会报错,因为你赋值了一个常量串。如果你做如下尝试,编译器也会错:

strcat(p, "each");
  因为 strcat 的第一个参数应该是 LPTSTR 类型的数据,而你却给了一个 LPCTSTR。

  不要试图钻这个错误消息的牛角尖,这只会使你自己陷入麻烦!

  原因是缓冲有一个计数,它是不可存取的(它位于 CString 地址之下的一个隐藏区域),如果你改变这个串,缓冲中的字符计数不会反映所做的修改。此外,如果字符串长度恰好是该字符串物理限制的长度(梢后还会讲到这个问题),那么扩展该字符串将改写缓冲以外的任何数据,那是你无权进行写操作的内存(不对吗?),你会毁换坏不属于你的内存。这是应用程序真正的死亡处方。

CString转化成char* 之二:使用 CString 对象的 GetBuffer 方法;

  如果你需要修改 CString 中的内容,它有一个特殊的方法可以使用,那就是 GetBuffer,它的作用是返回一个可写的缓冲指针。 如果你只是打算修改字符或者截短字符串,你完全可以这样做:

CString s(_T("File.ext"));
LPTSTR p = s.GetBuffer();
LPTSTR dot = strchr(p, ''.''); // OK, should have used s.Find...
if(p != NULL)
*p = _T(''\0'');
s.ReleaseBuffer();
  这是 GetBuffer 的第一种用法,也是最简单的一种,不用给它传递参数,它使用默认值 0,意思是:“给我这个字符串的指针,我保证不加长它”。当你调用 ReleaseBuffer 时,字符串的实际长度会被重新计算,然后存入 CString 对象中。
  必须强调一点,在 GetBuffer 和 ReleaseBuffer 之间这个范围,一定不能使用你要操作的这个缓冲的 CString 对象的任何方法。因为 ReleaseBuffer 被调用之前,该 CString 对象的完整性得不到保障。研究以下代码:

CString s(...);

LPTSTR p = s.GetBuffer();

//... 这个指针 p 发生了很多事情

int n = s.GetLength(); // 很糟D!!!!! 有可能给出错误的答案!!!

s.TrimRight(); // 很糟!!!!! 不能保证能正常工作!!!!

s.ReleaseBuffer(); // 现在应该 OK

int m = s.GetLength(); // 这个结果可以保证是正确的。

s.TrimRight(); // 将正常工作。
  假设你想增加字符串的长度,你首先要知道这个字符串可能会有多长,好比是声明字符串数组的时候用:

char buffer[1024];
表示 1024 个字符空间足以让你做任何想做得事情。在 CString 中与之意义相等的表示法:

LPTSTR p = s.GetBuffer(1024);
  调用这个函数后,你不仅获得了字符串缓冲区的指针,而且同时还获得了长度至少为 1024 个字符的空间(注意,我说的是“字符”,而不是“字节”,因为 CString 是以隐含方式感知 Unicode 的)。
  同时,还应该注意的是,如果你有一个常量串指针,这个串本身的值被存储在只读内存中,如果试图存储它,即使你已经调用了 GetBuffer ,并获得一个只读内存的指针,存入操作会失败,并报告存取错误。我没有在 CString 上证明这一点,但我看到过大把的 C 程序员经常犯这个错误。
  C 程序员有一个通病是分配一个固定长度的缓冲,对它进行 sprintf 操作,然后将它赋值给一个 CString:

char buffer[256];
sprintf(buffer, "%......", args, ...); // ... 部分省略许多细节
CString s = buffer;
虽然更好的形式可以这么做:

CString s;
s.Format(_T("%...."), args, ...);
如果你的字符串长度万一超过 256 个字符的时候,不会破坏堆栈。

  另外一个常见的错误是:既然固定大小的内存不工作,那么就采用动态分配字节,这种做法弊端更大:

int len = lstrlen(parm1) + 13 lstrlen(parm2) + 10 + 100;

char * buffer = new char[len];

sprintf(buffer, "%s is equal to %s, valid data", parm1, parm2);

CString s = buffer;

......

delete [] buffer;
它可以能被简单地写成:

CString s;

s.Format(_T("%s is equal to %s, valid data"), parm1, parm2);
  需要注意 sprintf 例子都不是 Unicode 就绪的,尽管你可以使用 tsprintf 以及用 _T() 来包围格式化字符串,但是基本 思路仍然是在走弯路,这这样很容易出错。

CString to char * 之三:和控件的接口;

  我们经常需要把一个 CString 的值传递给一个控件,比如,CTreeCtrl。MFC为我们提供了很多便利来重载这个操作,但是在大多数情况下,你使用“原始”形式的更新,因此需要将墨某个串指针存储到 TVINSERTITEMSTRUCT 结构的 TVITEM 成员中。如下:

TVINSERTITEMSTRUCT tvi;
CString s;
// ... 为s赋一些值。
tvi.item.pszText = s; // Compiler yells at you here
// ... 填写tvi的其他域
HTREEITEM ti = c_MyTree.InsertItem(&tvi);
  为什么编译器会报错呢?明明看起来很完美的用法啊!但是事实上如果你看看 TVITEM 结构的定义你就会明白,在 TVITEM 结构中 pszText 成员的声明如下:

LPTSTR pszText;
int cchTextMax;
  因此,赋值不是赋给一个 LPCTSTR 类型的变量,而且编译器无法知道如何将赋值语句右边强制转换成 LPCTSTR。好吧,你说,那我就改成这样:

tvi.item.pszText = (LPCTSTR)s; //编译器依然会报错。
  编译器之所以依然报错是因为你试图把一个 LPCTSTR 类型的变量赋值给一个 LPTSTR 类型的变量,这种操作在C或C++中是被禁止的。你不能用这种方法来滥用常量指针与非常量指针概念,否则,会扰乱编译器的优化机制,使之不知如何优化你的程序。比如,如果你这么做:

const int i = ...;
//... do lots of stuff
... = a[i]; // usage 1
// ... lots more stuff
... = a[i]; // usage 2
  那么,编译器会以为既然 i 是 const ,所以 usage1和usage2的值是相同的,并且它甚至能事先计算好 usage1 处的 a[i] 的地址,然后保留着在后面的 usage2 处使用,而不是重新计算。如果你按如下方式写的话:

const int i = ...;
int * p = &i;
//... do lots of stuff
... = a[i]; // usage 1
// ... lots more stuff
(*p)++; // mess over compiler''s assumption
// ... and other stuff
... = a[i]; // usage 2
  编译器将认为 i 是常量,从而 a[i] 的位置也是常量,这样间接地破坏了先前的假设。因此,你的程序将会在 debug 编译模式(没有优化)和 release 编译模式(完全优化)中反映出不同的行为,这种情况可不好,所以当你试图把指向 i 的指针赋值给一个 可修改的引用时,会被编译器诊断为这是一种伪造。这就是为什么(LPCTSTR)强制类型转化不起作用的原因。
  为什么不把该成员声明成 LPCTSTR 类型呢?因为这个结构被用于读写控件。当你向控件写数据时,文本指针实际上被当成 LPCTSTR,而当你从控件读数据 时,你必须有一个可写的字符串。这个结构无法区分它是用来读还是用来写。

因此,你会常常在我的代码中看到如下的用法:

tvi.item.pszText = (LPTSTR)(LPCTSTR)s;
  它把 CString 强制类型转化成 LPCTSTR,也就是说先获得改字符串的地址,然后再强制类型转化成 LPTSTR,以便可以对之进行赋值操作。 注意这只有在使用 Set 或 Insert 之类的方法才有效!如果你试图获取数据,则不能这么做。
  如果你打算获取存储在控件中的数据,则方法稍有不同,例如,对某个 CTreeCtrl 使用 GetItem 方法,我想获取项目的文本。我知道这些 文本的长度不会超过 MY_LIMIT,因此我可以这样写:

TVITEM tvi;
// ... assorted initialization of other fields of tvi
tvi.pszText = s.GetBuffer(MY_LIMIT);
tvi.cchTextMax = MY_LIMIT;
c_MyTree.GetItem(&tvi);
s.ReleaseBuffer();
  可以看出来,其实上面的代码对所有类型的 Set 方法都适用,但是并不需要这么做,因为所有的类 Set 方法(包括 Insert方法)不会改变字符串的内容。但是当你需要写 CString 对象时,必须保证缓冲是可写的,这正是 GetBuffer 所做的事情。再次强调: 一旦做了一次 GetBuffer 调用,那么在调用 ReleaseBuffer 之前不要对这个 CString 对象做任何操作。

5、CString 型转化成 BSTR 型

  当我们使用 ActiveX 控件编程时,经常需要用到将某个值表示成 BSTR 类型。BSTR 是一种记数字符串,Intel平台上的宽字符串(Unicode),并且 可以包含嵌入的 NULL 字符。

你可以调用 CString 对象的 AllocSysString 方法将 CString 转化成 BSTR:

CString s;
s = ... ; // whatever
BSTR b = s.AllocSysString();
  现在指针 b 指向的就是一个新分配的 BSTR 对象,该对象是 CString 的一个拷贝,包含终结 NULL字符。现在你可以将它传递给任何需要 BSTR 的接口。通常,BSTR 由接收它的组件来释放,如果你需要自己释放 BSTR 的话,可以这么做:

::SysFreeString(b);
  对于如何表示传递给 ActiveX 控件的字符串,在微软内部曾一度争论不休,最后 Visual Basic 的人占了上风,BSTR(“Basic String”的首字母缩写)就是这场争论的结果。

6、BSTR 型转化成 CString 型

  由于 BSTR 是记数 Unicode 字符串,你可以用标准转换方法来创建 8 位的 CString。实际上,这是 CString 内建的功能。在 CString 中 有特殊的构造函数可以把 ANSI 转化成 Unicode,也可以把Unicode 转化成 ANSI。你同样可以从 VARIANT 类型的变量中获得 BSTR 类型的字符串,VARIANT 类型是 由各种 COM 和 Automation (自动化)调用返回的类型。

例如,在一个ANSI程序中:

BSTR b;
b = ...; // whatever
CString s(b == NULL ? L"" : b)
  对于单个的 BSTR 串来说,这种用法可以工作得很好,这是因为 CString 有一个特殊的构造函数以LPCWSTR(BSTR正是这种类型) 为参数,并将它转化成 ANSI 类型。专门检查是必须的,因为 BSTR 可能为空值,而 CString 的构造函数对于 NULL 值情况考虑的不是很周到,(感谢 Brian Ross 指出这一点!)。这种用法也只能处理包含 NUL 终结字符的单字符串;如果要转化含有多个 NULL 字符 串,你得额外做一些工作才行。在 CString 中内嵌的 NULL 字符通常表现不尽如人意,应该尽量避免。
  根据 C/C++ 规则,如果你有一个 LPWSTR,那么它别无选择,只能和 LPCWSTR 参数匹配。

在 Unicode 模式下,它的构造函数是:

CString::CString(LPCTSTR);
正如上面所表示的,在 ANSI 模式下,它有一个特殊的构造函数:

CString::CString(LPCWSTR);
  它会调用一个内部的函数将 Unicode 字符串转换成 ANSI 字符串。(在Unicode模式下,有一个专门的构造函数,该函数有一个参数是LPCSTR类型——一个8位 ANSI 字符串指针,该函数将它加宽为 Unicode 的字符串!)再次强调:一定要检查 BSTR 的值是否为 NULL。
  另外还有一个问题,正如上文提到的:BSTRs可以含有多个内嵌的NULL字符,但是 CString 的构造函数只能处理某个串中单个 NULL 字符。 也就是说,如果串中含有嵌入的 NUL字节,CString 将会计算出错误的串长度。你必须自己处理它。如果你看看 strcore.cpp 中的构造函数,你会发现 它们都调用了lstrlen,也就是计算字符串的长度。
  注意从 Unicode 到 ANSI 的转换使用带专门参数的 ::WideCharToMultiByte,如果你不想使用这种默认的转换方式,则必须编写自己的转化代码。
  如果你在 UNICODE 模式下编译代码,你可以简单地写成:


CString convert(BSTR b)
{
if(b == NULL)
return CString(_T(""));
CString s(b); // in UNICODE mode
return s;
}

  如果是 ANSI 模式,则需要更复杂的过程来转换。注意这个代码使用与 ::WideCharToMultiByte 相同的参数值。所以你 只能在想要改变这些参数进行转换时使用该技术。例如,指定不同的默认字符,不同的标志集等。
CString convert(BSTR b)
{
CString s;
if(b == NULL)
return s; // empty for NULL BSTR
#ifdef UNICODE
s = b;
#else
LPSTR p = s.GetBuffer(SysStringLen(b) + 1);
::WideCharToMultiByte(CP_ACP, // ANSI Code Page
0, // no flags
b, // source widechar string
-1, // assume NUL-terminated
p, // target buffer
SysStringLen(b)+1, // target buffer length
NULL, // use system default char
NULL); // don''t care if default used
s.ReleaseBuffer();
#endif
return s;
}

  我并不担心如果 BSTR 包含没有映射到 8 位字符集的 Unicode 字符时会发生什么,因为我指定了::WideCharToMultiByte 的最后两个参数为 NULL。这就是你可能需要改变的地方。

7、VARIANT 型转化成 CString 型

  事实上,我从来没有这么做过,因为我没有用 COM/OLE/ActiveX 编写过程序。但是我在microsoft.public.vc.mfc 新闻组上看到了 Robert Quirk 的一篇帖子谈到了这种转化,我觉得把他的文章包含在我的文章里是不太好的做法,所以在这里多做一些解释和演示。如果和他的文章有相孛的地方可能是我的疏忽。
  VARIANT 类型经常用来给 COM 对象传递参数,或者接收从 COM 对象返回的值。你也能自己编写返回 VARIANT 类型的方法,函数返回什么类型依赖可能(并且常常)方法的输入参数(比如,在自动化操作中,依赖与你调用哪个方法。IDispatch::Invoke 可能返回(通过其一个参数)一个 包含有BYTE、WORD、float、double、date、BSTR 等等 VARIANT 类型的结果,(详见 MSDN 上的 VARIANT 结构的定义)。在下面的例子中,假设类型是一个BSTR的变体,也就是说在串中的值是通过 bsrtVal 来引用,其优点是在 ANSI 应用中,有一个构造函数会把 LPCWCHAR 引用的值转换为一个 CString(见 BSTR-to-CString 部分)。在 Unicode 模式中,将成为标准的 CString 构造函数,参见对缺省::WideCharToMultiByte 转换的告诫,以及你觉得是否可以接受(大多数情况下,你会满意的)。
VARIANT vaData;
vaData = m_com.YourMethodHere();
ASSERT(vaData.vt == VT_BSTR);
CString strData(vaData.bstrVal);
你还可以根据 vt 域的不同来建立更通用的转换例程。为此你可能会考虑:


CString VariantToString(VARIANT * va)
{
CString s;
switch(va->vt)
{ /* vt */
case VT_BSTR:
return CString(vaData->bstrVal);
case VT_BSTR | VT_BYREF:
return CString(*vaData->pbstrVal);
case VT_I4:
s.Format(_T("%d"), va->lVal);
return s;
case VT_I4 | VT_BYREF:
s.Format(_T("%d"), *va->plVal);
case VT_R8:
s.Format(_T("%f"), va->dblVal);
return s;
... 剩下的类型转换由读者自己完成
default:
ASSERT(FALSE); // unknown VARIANT type (this ASSERT is optional)
return CString("");
} /* vt */
}

8、载入字符串表资源

  如果你想创建一个容易进行语言版本移植的应用程序,你就不能在你的源代码中直接包含本土语言字符串(下面这些例子我用的语言都是英语,因为我的本土语是英语),比如下面这种写法就很糟:
CString s = "There is an error";
  你应该把你所有特定语言的字符串单独摆放(调试信息、在发布版本中不出现的信息除外)。这意味着向下面这样写比较好:

s.Format(_T("%d - %s"), code, text);
  在你的程序中,文字字符串不是语言敏感的。不管怎样,你必须很小心,不要使用下面这样的串:

// fmt is "Error in %s file %s"
// readorwrite is "reading" or "writing"
s.Format(fmt, readorwrite, filename);
  这是我的切身体会。在我的第一个国际化的应用程序中我犯了这个错误,尽管我懂德语,知道在德语的语法中动词放在句子的最后面,我们的德国方面的发行人还是苦苦的抱怨他们不得不提取那些不可思议的德语错误提示信息然后重新格式化以让它们能正常工作。比较好的办法(也是我现在使用的办法)是使用两个字符串,一个用于读,一个用于写,在使用时加载合适的版本,使得它们对字符串参数是非敏感的。也就是说加载整个格式,而不是加载串“reading”, “writing”:

// fmt is "Error in reading file %s"
// "Error in writing file %s"
s.Format(fmt, filename);
  一定要注意,如果你有好几个地方需要替换,你一定要保证替换后句子的结构不会出现问题,比如在英语中,可以是主语-宾语,主语-谓语,动词-宾语的结构等等。
  在这里,我们并不讨论 FormatMessage,其实它比 sprintf/Format 还要有优势,但是不太容易和CString 结合使用。解决这种问题的办法就是我们按照参数出现在参数表中的位置给参数取名字,这样在你输出的时候就不会把他们的位置排错了。
  接下来我们讨论我们这些独立的字符串放在什么地方。我们可以把字符串的值放入资源文件中的一个称为 STRINGTABLE 的段中。过程如下:首先使用 Visual Studio 的资源编辑器创建一个字符串,然后给每一个字符串取一个ID,一般我们给它取名字都以 IDS_开头。所以如果你有一个信息,你可以创建一个字符串资源然后取名为 IDS_READING_FILE,另外一个就取名为 IDS_WRITING_FILE。它们以下面的形式出现在你的 .rc 文件中:

STRINGTABLE
IDS_READING_FILE "Reading file %s"
IDS_WRITING_FILE "Writing file %s"
END
注意:这些资源都以 Unicode 的格式保存,不管你是在什么环境下编译。他们在Win9x系统上也是以Unicode 的形式存在,虽然 Win9x 不能真正处理 Unicode。
然后你可以这样使用这些资源:
// 在使用资源串表之前,程序是这样写的:


CString fmt;
if(...)
fmt = "Reading file %s";
else
fmt = "Writing file %s";
...
// much later
CString s;
s.Format(fmt, filename);

// 使用资源串表之后,程序这样写:
CString fmt;
if(...)
fmt.LoadString(IDS_READING_FILE);
else
fmt.LoadString(DS_WRITING_FILE);
...
// much later
CString s;
s.Format(fmt, filename);

  现在,你的代码可以移植到任何语言中去。LoadString 方法需要一个字符串资源的 ID 作为参数,然后它从 STRINGTABLE 中取出它对应的字符串,赋值给 CString 对象。 CString 对象的构造函数还有一个更加聪明的特征可以简化 STRINGTABLE 的使用。这个用法在 CString::CString 的文档中没有指出,但是在构造函数的示例程序中使用了。(为什么这个特性没有成为正式文档的一部分,而是放在了一个例子中,我记不得了!)——【译者注:从这句话看,作者可能是 CString的设计者。其实前面还有一句类似的话。说他没有对使用GetBuffer(0)获得的指针指向的地址是否可读做有效性检查】。这个特征就是:如果你将一个字符串资源的ID强制类型转换为 LPCTSTR,将会隐含调用 LoadString。因此,下面两个构造字符串的例子具有相同的效果,而且其 ASSERT 在debug模式下不会被触发:
CString s;
s.LoadString(IDS_WHATEVER);
CString t( (LPCTSTR)IDS_WHATEVER );
ASSERT(s == t);//不会被触发,说明s和t是相同的。
  现在,你可能会想:这怎么可能工作呢?我们怎么能把 STRINGTABLE ID 转化成一个指针呢?很简单:所有的字符串 ID 都在1~65535这个范围内,也就是说,它所有的高位都是0,而我们在程序中所使用的指针是不可能小于65535的,因为程序的低 64K 内存永远也不可能存在的,如果你试图访问0x00000000到0x0000FFFF之间的内存,将会引发一个内存越界错误。所以说1~65535的值不可能是一个内存地址,所以我们可以用这些值来作为字符串资源的ID。
  我倾向于使用 MAKEINTRESOURCE 宏显式地做这种转换。我认为这样可以让代码更加易于阅读。这是个只适合在 MFC 中使用的标准宏。你要记住,大多数的方法即可以接受一个 UINT 型的参数,也可以接受一个 LPCTSTR 型的参数,这是依赖 C++ 的重载功能做到的。C++重载函数带来的弊端就是造成所有的强制类型转化都需要显示声明。同样,你也可以给很多种结构只传递一个资源名。

CString s;
s.LoadString(IDS_WHATEVER);
CString t( MAKEINTRESOURCE(IDS_WHATEVER));
ASSERT(s == t);
  告诉你吧:我不仅只是在这里鼓吹,事实上我也是这么做的。在我的代码中,你几乎不可能找到一个字符串,当然,那些只是偶然在调试中出现的或者和语言无关的字符串除外。

9、CString 和临时对象

  这是出现在 microsoft.public.vc.mfc 新闻组中的一个小问题,我简单的提一下,这个问题是有个程序员需要往注册表中写入一个字符串,他写道:
  我试着用 RegSetValueEx() 设置一个注册表键的值,但是它的结果总是令我困惑。当我用char[]声明一个变量时它能正常工作,但是当我用 CString 的时候,总是得到一些垃圾:"

VC++ type Cast ChapterA

VC中有关数据类型转换的整理

 

说明:本文纯粹是总结一下有关类型转换的贴子,

本人并未对所有方法都做测试,仅仅是为了给大家一个参考。

读者层次:初学

int i = 100;
long l = 2001;
float f=300.2;
double d=12345.119;
char username[]="程佩君";
char temp[200];
char *buf;
CString str;
_variant_t v1;
_bstr_t v2;

一、其它数据类型转换为字符串

  • 短整型(int)
    itoa(i,temp,10);///将i转换为字符串放入temp中,最后一个数字表示十进制
    itoa(i,temp,2); ///按二进制方式转换
  • 长整型(long)
    ltoa(l,temp,10);
  • 浮点数(float,double)
    用fcvt可以完成转换,这是MSDN中的例子:
    int decimal, sign;
    char *buffer;
    double source = 3.1415926535;
    buffer = _fcvt( source, 7, &decimal, &sign );
    运行结果:source: 3.1415926535 buffer: '31415927' decimal: 1 sign: 0
    decimal表示小数点的位置,sign表示符号:0为正数,1为负数
  • CString变量
    str = "2008北京奥运";
    buf = (LPSTR)(LPCTSTR)str;
  • BSTR变量
    BSTR bstrValue = ::SysAllocString(L"程序员");
    char * buf = _com_util::ConvertBSTRToString(bstrValue);
    SysFreeString(bstrValue);
    AfxMessageBox(buf);
    delete(buf);
  • CComBSTR变量
    CComBSTR bstrVar("test");
    char *buf = _com_util::ConvertBSTRToString(bstrVar.m_str);
    AfxMessageBox(buf);
    delete(buf);
  • _bstr_t变量
    _bstr_t类型是对BSTR的封装,因为已经重载了=操作符,所以很容易使用
    _bstr_t bstrVar("test");
    const char *buf = bstrVar;///不要修改buf中的内容
    AfxMessageBox(buf);

  • 通用方法(针对非COM数据类型)
    用sprintf完成转换
    char  buffer[200];
    char c = '1';
    int i = 35;
    long j = 1000;
    float f = 1.7320534f;
    sprintf( buffer, "%c",c);
    sprintf( buffer, "%d",i);
    sprintf( buffer, "%d",j);
    sprintf( buffer, "%f",f);

二、字符串转换为其它数据类型
strcpy(temp,"123");

  • 短整型(int)
    i = atoi(temp);
  • 长整型(long)
    l = atol(temp);
  • 浮点(double)
    d = atof(temp);
  • CString变量
    CString name = temp;
  • BSTR变量
    BSTR bstrValue = ::SysAllocString(L"程序员");
    ...///完成对bstrValue的使用
    SysFreeString(bstrValue);
  • CComBSTR变量
    CComBSTR类型变量可以直接赋值
    CComBSTR bstrVar1("test");
    CComBSTR bstrVar2(temp);
  • _bstr_t变量
    _bstr_t类型的变量可以直接赋值
    _bstr_t bstrVar1("test");
    _bstr_t bstrVar2(temp);

三、其它数据类型转换到CString
使用CString的成员函数Format来转换,例如:

  • 整数(int)
    str.Format("%d",i);
  • 浮点数(float)
    str.Format("%f",i);
  • 字符串指针(char *)等已经被CString构造函数支持的数据类型可以直接赋值
    str = username;
  • 对于Format所不支持的数据类型,可以通过上面所说的关于其它数据类型转化到char *的方法先转到char *,然后赋值给CString变量。

四、BSTR、_bstr_t与CComBSTR

  • CComBSTR 是ATL对BSTR的封装,_bstr_t是C++对BSTR的封装,BSTR是32位指针,但并不直接指向字串的缓冲区。
    char *转换到BSTR可以这样:
    BSTR b=_com_util::ConvertStringToBSTR("数据");///使用前需要加上comutil.h和comsupp.lib
    SysFreeString(bstrValue);
    反之可以使用
    char *p=_com_util::ConvertBSTRToString(b);
    delete p;
    具体可以参考一,二段落里的具体说明。

    CComBSTR与_bstr_t对大量的操作符进行了重载,可以直接进行=,!=,==等操作,所以使用非常方便。
    特别是_bstr_t,建议大家使用它。

五、VARIANT 、_variant_t 与 COleVariant

  • VARIANT的结构可以参考头文件VC98\Include\OAIDL.H中关于结构体tagVARIANT的定义。
    对于VARIANT变量的赋值:首先给vt成员赋值,指明数据类型,再对联合结构中相同数据类型的变量赋值,举个例子:
    VARIANT va;
    int a=2001;
    va.vt=VT_I4;///指明整型数据
    va.lVal=a; ///赋值

    对于不马上赋值的VARIANT,最好先用Void VariantInit(VARIANTARG FAR* pvarg);进行初始化,其本质是将vt设置为VT_EMPTY,下表我们列举vt与常用数据的对应关系:

    Byte bVal; // VT_UI1.
    Short iVal; // VT_I2.
    long lVal; // VT_I4.
    float fltVal; // VT_R4.
    double dblVal; // VT_R8.
    VARIANT_BOOL boolVal; // VT_BOOL.
    SCODE scode; // VT_ERROR.
    CY cyVal; // VT_CY.
    DATE date; // VT_DATE.
    BSTR bstrVal; // VT_BSTR.
    DECIMAL FAR* pdecVal // VT_BYREF|VT_DECIMAL.
    IUnknown FAR* punkVal; // VT_UNKNOWN.
    IDispatch FAR* pdispVal; // VT_DISPATCH.
    SAFEARRAY FAR* parray; // VT_ARRAY|*.
    Byte FAR* pbVal; // VT_BYREF|VT_UI1.
    short FAR* piVal; // VT_BYREF|VT_I2.
    long FAR* plVal; // VT_BYREF|VT_I4.
    float FAR* pfltVal; // VT_BYREF|VT_R4.
    double FAR* pdblVal; // VT_BYREF|VT_R8.
    VARIANT_BOOL FAR* pboolVal; // VT_BYREF|VT_BOOL.
    SCODE FAR* pscode; // VT_BYREF|VT_ERROR.
    CY FAR* pcyVal; // VT_BYREF|VT_CY.
    DATE FAR* pdate; // VT_BYREF|VT_DATE.
    BSTR FAR* pbstrVal; // VT_BYREF|VT_BSTR.
    IUnknown FAR* FAR* ppunkVal; // VT_BYREF|VT_UNKNOWN.
    IDispatch FAR* FAR* ppdispVal; // VT_BYREF|VT_DISPATCH.
    SAFEARRAY FAR* FAR* pparray; // VT_ARRAY|*.
    VARIANT FAR* pvarVal; // VT_BYREF|VT_VARIANT.
    void FAR* byref; // Generic ByRef.
    char cVal; // VT_I1.
    unsigned short uiVal; // VT_UI2.
    unsigned long ulVal; // VT_UI4.
    int intVal; // VT_INT.
    unsigned int uintVal; // VT_UINT.
    char FAR * pcVal; // VT_BYREF|VT_I1.
    unsigned short FAR * puiVal; // VT_BYREF|VT_UI2.
    unsigned long FAR * pulVal; // VT_BYREF|VT_UI4.
    int FAR * pintVal; // VT_BYREF|VT_INT.
    unsigned int FAR * puintVal; //VT_BYREF|VT_UINT.
  • _variant_t是VARIANT的封装类,其赋值可以使用强制类型转换,其构造函数会自动处理这些数据类型。
    使用时需加上#include <comdef.h>
    例如:
    long l=222;
    ing i=100;
    _variant_t lVal(l);
    lVal = (long)i;

  • COleVariant的使用与_variant_t的方法基本一样,请参考如下例子:
    COleVariant v3 = "字符串", v4 = (long)1999;
    CString str =(BSTR)v3.pbstrVal;
    long i = v4.lVal;

六、其它一些COM数据类型

  • 根据ProgID得到CLSID
    HRESULT CLSIDFromProgID( LPCOLESTR lpszProgID,LPCLSID pclsid);
    CLSID clsid;
    CLSIDFromProgID( L"MAPI.Folder",&clsid);
  • 根据CLSID得到ProgID
    WINOLEAPI ProgIDFromCLSID( REFCLSID clsid,LPOLESTR * lplpszProgID);
    例如我们已经定义了 CLSID_IApplication,下面的代码得到ProgID
    LPOLESTR pProgID = 0;
    ProgIDFromCLSID( CLSID_IApplication,&pProgID);
    ...///可以使用pProgID
    CoTaskMemFree(pProgID);//不要忘记释放

七、ANSI与Unicode
Unicode称为宽字符型字串,COM里使用的都是Unicode字符串。

  • 将ANSI转换到Unicode
    (1)通过L这个宏来实现,例如: CLSIDFromProgID( L"MAPI.Folder",&clsid);
    (2)通过MultiByteToWideChar函数实现转换,例如:
    char *szProgID = "MAPI.Folder";
    WCHAR szWideProgID[128];
    CLSID clsid;
    long lLen = MultiByteToWideChar(CP_ACP,0,szProgID,strlen(szProgID),szWideProgID,sizeof(szWideProgID));
    szWideProgID[lLen] = '\0';
    (3)通过A2W宏来实现,例如:
    USES_CONVERSION;
    CLSIDFromProgID( A2W(szProgID),&clsid);
  • 将Unicode转换到ANSI
    (1)使用WideCharToMultiByte,例如:
    // 假设已经有了一个Unicode 串 wszSomeString...
    char szANSIString [MAX_PATH];
    WideCharToMultiByte ( CP_ACP, WC_COMPOSITECHECK, wszSomeString, -1, szANSIString, sizeof(szANSIString), NULL, NULL );
    (2)使用W2A宏来实现,例如:
    USES_CONVERSION;
    pTemp=W2A(wszSomeString);

八、其它

  • 对消息的处理中我们经常需要将WPARAM或LPARAM等32位数据(DWORD)分解成两个16位数据(WORD),例如:
    LPARAM lParam;
    WORD loValue = LOWORD(lParam);///取低16位
    WORD hiValue = HIWORD(lParam);///取高16位

  • 对于16位的数据(WORD)我们可以用同样的方法分解成高低两个8位数据(BYTE),例如:
    WORD wValue;
    BYTE loValue = LOBYTE(wValue);///取低8位
    BYTE hiValue = HIBYTE(wValue);///取高8位

  • 两个16位数据(WORD)合成32位数据(DWORD,LRESULT,LPARAM,或WPARAM)
    LONG MAKELONG( WORD wLow, WORD wHigh );
    WPARAM MAKEWPARAM( WORD wLow, WORD wHigh );
    LPARAM MAKELPARAM( WORD wLow, WORD wHigh );
    LRESULT MAKELRESULT( WORD wLow, WORD wHigh );

  • 两个8位的数据(BYTE)合成16位的数据(WORD)
    WORD MAKEWORD( BYTE bLow, BYTE bHigh );

  • 从R(red),G(green),B(blue)三色得到COLORREF类型的颜色值
    COLORREF RGB( BYTE byRed,BYTE byGreen,BYTE byBlue );
    例如COLORREF bkcolor = RGB(0x22,0x98,0x34);

  • 从COLORREF类型的颜色值得到RGB三个颜色值
    BYTE Red = GetRValue(bkcolor); ///得到红颜色
    BYTE Green = GetGValue(bkcolor); ///得到绿颜色
    BYTE Blue = GetBValue(bkcolor); ///得到兰颜色

九、注意事项
假如需要使用到ConvertBSTRToString此类函数,需要加上头文件comutil.h,并在setting中加入comsupp.lib或者直接加上#pragma comment( lib, "comsupp.lib" )

后记:本文匆匆写成,错误之处在所难免,欢迎指正.

关于把BSTR类型数据转换成CString 类型数据时的问题?
当我在把BSTR类型数据转换成CString 或 “char* 类型”数据时,发现在BSTR类型字符串较短的情况下没问题,当较长时就会出现
内存读写错了。(在NT,2000下都测试是这样的。)
根据你所说:
1)字符串指针(char *)等已经被CString构造函数支持的数据类型 可以直接赋值 str = username;
2)当b 为BSTR类型时可以使用
char *p=_com_util::ConvertBSTRToString(b);
于是以下是对的:
CString cstr;
BSTR bstr;
....
cstr=com_util::ConvertBSTRToString(bstr);
...
可是当bstr非常大时(其实,较大时就会)就会出现内存读写错,不知何故。
此外我发现cstr=com_util::ConvertBSTRToString(bstr);
可以简化为 cstr=bstr; 但当bstr较大时同样出现这个问题。
请兄弟帮忙!急。谢谢!

如何转化((list*)fileip.bian)->liang

关于把CString转化成LPCTSTR的问题 作者:jakiesun 发表日期:2001-9-5 20:08:48
我记的我以前写过这样一段代码
void function()
{
CString str,str1,str2;
function((char*)(LPCTSTR)str1);
str=str1;
...//调试道此发现str2的值随着str的改变而改变,请问能解释一下为什么,如有回答,请通知
wangshaohong@sohu.com,tx先

}

添加lib支持 作者:磨刀霍霍 发表日期:2001-9-10 11:32:12
如果不添加会产生错误,在setting中加入comsupp.lib或者直接#pragma comment( lib, "comsupp.lib" )
微软认为缺省的设置call convention如果不设置成__cdecl也会出现同样的错误。


1。int 转成cstring ??

回复人: caigzhi(caigzhi) (2001-10-17 11:27:35) 得0分 
CString 的成员函数Format()

int a = 2131;
CString str;
str.Format("%d",a);

回复人: tenchi(C与C++之间) (2001-10-17 11:32:12) 得0分 
int i=2001;
char str[10];
_itoa(i,str,10);
CString szString=str; 
回复人: fiolin(幽深的水) (2001-10-17 11:45:40) 得0分 
他们两个的都可以!! 

回复人: sohucsdnvc(thanks) (2001-10-17 13:24:17) 得0分 
那如何把double转成cstring 
回复人: yihugang(小虎子) (2001-10-17 13:29:15) 得6分 
int i = 2131;
char *c=new char[20];
CString str;
sprintf(c,'%d',i);
str=*c;


回复人: Gu_c_h(Gu) (2001-10-17 14:07:17) 得0分 
用 _gcvt 下面是 msdn 的例子

Example

/* _GCVT.C: This program converts -3.1415e5
* to its string representation.
*/

#include <stdlib.h>
#include <stdio.h>

void main( void )
{
char buffer[50];
double source = -3.1415e5;
_gcvt( source, 7, buffer );
printf( "source: %f buffer: '%s'\n", source, buffer );
_gcvt( source, 7, buffer );
printf( "source: %e buffer: '%s'\n", source, buffer );
}


Output

source: -314150.000000 buffer: '-314150.'
source: -3.141500e+005 buffer: '-314150.'

回复人: Gu_c_h(Gu) (2001-10-17 14:49:56) 得6分 
int a = -3.1415e5;
CString str;
str.Format("%f",a); 
回复人: ruixp(锐剑) (2001-10-17 15:06:48) 得6分 
CString 的成员函数Format()
int a = 2131;
CString str;
str.Format("%d",a);

2。基类对象怎么能转换成派生类对象?
int CDaoListView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
lpCreateStruct->style |= LVS_REPORT |LVS_EDITLABELS;
if (CListView::OnCreate(lpCreateStruct) == -1)
return -1;

//////////// 创建ImageList;
CDaoViewApp * pApp = (CDaoViewApp *) AfxGetApp();

m_pImageList = new CImageList();
ASSERT(m_pImageList !=NULL);
m_pImageList->Create(16,16,TRUE,4,4);
m_pImageList->Add(pApp->LoadIcon(IDI_KEY));
m_pImageList->Add(pApp->LoadIcon(IDI_ICON4));
m_pImageList->Add(pApp->LoadIcon(IDI_ICON5));

CListCtrlEx& ctlList = (CListCtrlEx&) GetListCtrl();//我不懂的就这句,cListCtrlEx看下面的声明。
ctlList.SetImageList (m_pImageList, LVSIL_SMALL) ;
////////////
return 0;
}

class CListCtrlEx : public CListCtrl//类cListCtrlEx定义。
{.....
}
class CDaoListView : public CListView//cDaoListView定义!
{
...
}
注:我的问题是GetListCtrl()返回的是一个cListCtrl对象的引用,怎么能把它转换成一个它的派生类对象的引用?c++的类型转换怎么支持? 


回复贴子: 
回复人: lhj(努力加油) (2002-1-29 18:56:06) 得0分 
CListCtrlEx& ctlList = (CListCtrlEx&) GetListCtrl();
这是强制类型转换,&表示是一个引用,lctList的值在这次赋值后不能被修改。

回复人: wwwsq(wwwsq) (2002-1-29 19:09:22) 得0分 
建议你找本C++方面的书看看,VC虽然号称可视,实际上C++基础还是很重要的。


回复人: xcopy(xcopy) (2002-1-29 19:26:16) 得0分 
用dynamic_cast()可以安全的转换。 

3。如何在CString和double之间转换?要求转换之后能保留小数点,保留正负号??

cstring::format(%.xe) x为精度位 

回复人: pchaos(杂讲) (2002-1-28 11:21:46) 得0分 
CString str;
double db;
str = "123.456";
db = atof((LPCTSTR)str); 
回复人: hgw111(hg) (2002-1-28 11:52:57) 得0分 
CString -> double : atof
double ->CString :Format 
回复人: pchaos(杂讲) (2002-1-28 13:46:04) 得0分 
CString str;
double db;
str = "123.456";
db = atof((LPCTSTR)str); 
db = 777.999;
str.format("%e", db); 

4。字符型要转换成int??
atoi(str) 

5。_bstr_t 到 unsigned int??
_bstr_t str;
unsigned int Length=6;
Length=Length-str.length(); 

6。VARIANT类型转换问题?
我在使用MSCOMM中SetOutput()函数时
形参必须为VARIANT变量
如何将其它的数据类型转换为VARIANT类型?
如:Cstring->VARIANT、 *char->VARIANT
我对VARIANT的类型结构体不太熟,请讲详细些(最好有范例),谢谢!

回复贴子: 
回复人: vc_xiaoxin(小新) (2001-12-26 15:43:57) 得0分 
VARIANT本身是一个复杂的结构,别的数据怎么转呀?关注 
回复人: mpg_liu(星仁) (2001-12-27 18:33:50) 得10分 
定义一个VARIANT变量后,他应该是一个结构体变量,其中有一个成员是字符型的,给这个成员赋值 
回复人: LLnju(LLnju) (2001-12-27 18:36:10) 得0分 
实在不清楚嘛就用 _variant_t , COleVariant 这些东东嘛,很好用的啊 
回复人: softarts(CDMA2000) (2001-12-27 18:41:32) 得10分 
构造一个就行了。
VARIANT varXX;
CString strYY;
varXX.vt=VT_BSTR;
varXX.bstrVal = strYY.allocsysstring();
应该可以了。
回复人: softarts(CDMA2000) (2001-12-27 18:42:11) 得0分 
我也觉得COleVariant要好用一些,呵呵,我都用它。 
回复人: bobofu(有问题要问) (2001-12-27 19:32:18) 得10分 
CString str;
_variant_t var;
var = _variant_t(str); 

7。COleVarant 如何转换为 CString?
CString 如何转换为 char *
CString 如何转换为 char[xx] ??

CString 如何转换为 char * wsprintf或者=
CString 如何转换为 char[xx] strcpy() 
回复人: SecretGarden(天堂鸟) (2002-1-14 11:55:23) 得0分 
COleVarant封装了VAREANT类型。
VAREANT类型其实是个巨大地Union,里面自然有你
想要地unsigned char *类型。
CString地GetBuffer和Format可以实现你的后两个问题


8。v_variant_t类型转换成cstring
总提示我cstring未定义
程序如下
_variant_t vfirstname;//存储的是数据库中的数据
CString str;//提示出错
vfirstname=pRs->GetCollect (_variant_t("Phone_Num"));
vfirstname.ChangeType (VT_BSTR);
str=vfirstname.bstrVal;//提示出错 


回复贴子: 
回复人: hydnoahark(诺亚方舟) (2001-11-12 11:56:51) 得10分 
>>CString str;//提示出错
要求include <afx.h>并且设置Use run-time Library为Multithreaded 
回复人: zhengyun_ustc(^-^) (2001-11-12 12:04:39) 得15分 
CString未定义,说明你的工程没有引用MFC!!

要想使你的工程支持MFC,请按照以下步骤作:
1:在你的stdafx.h的头文件中加入:
#include "afxtempl.h"
这是一个囊括了MFC的集合定义的头文件,有了它,你的工程就识别Cstring类了。

2:在你的工程设置中,在General页中,选择“MFC”为“Using MFC in a shared DLL”

OK,现在再编译你的工程即可。 
回复人: zhengyun_ustc(^-^) (2001-11-12 12:06:56) 得5分 
_variant_t的bstrVal成员是BSTR类型。
它是一个指向一个OLECHART*的指针。 
回复人: vickowang(小苍) (2001-11-12 12:48:21) 得5分 
(char *)_bstr_t(vfirstname) 
回复人: smallfool(smallfool) (2001-11-12 13:52:54) 得4分 
或许你还需要一个从UNICODE字符到ANSI字符的转变函数 
回复人: sun_1112(萧) (2001-11-12 17:34:44) 得0分 
谢谢大家
给我这么大的支持!:) 
回复人: zhengyun_ustc(^-^) (2001-11-14 13:24:07) 得0分 
用vickowang(小苍)的意见可能会有问题,转换出的字符串应该是乱码。

因为(char *)转换需要一个const的字符串资源,才能强制转换。
所以应该:
_bstr_t bstrTemp = _bstr_t(vfirstname.bstrVal);
TCHAR szTemp[MAX_PATH];
szTemp = (char*)bstrTemp;

9。char * 转换为TCHAR类型??
直接转换,TCHAR相当于char了
char * s;
TCHAR * s1=(TCHAR *)s; 
回复人: dysxq() (2001-12-21 21:26:25) 得0分 
要看你的程序设置是ANSI还是UNICODE, 如果是ANSI,直接转,如果是UNICODE,TCHAR相当于WCHAR, 要用mbstowcsz转一下 
回复人: xiaoxiaohan(萧晓寒) (2001-12-21 23:52:17) 得0分 
Unicode :宽字节字符集
1. 如何取得一个既包含单字节字符又包含双字节字符的字符串的字符个数?
可以调用Microsoft Visual C++的运行期库包含函数_mbslen来操作多字节(既包括单字节也包括双字节)字符串。
调用strlen函数,无法真正了解字符串中究竟有多少字符,它只能告诉你到达结尾的0之前有多少个字节。
2. 如何对DBCS(双字节字符集)字符串进行操作?
函数 描述
PTSTR CharNext ( LPCTSTR ); 返回字符串中下一个字符的地址
PTSTR CharPrev ( LPCTSTR, LPCTSTR ); 返回字符串中上一个字符的地址
BOOL IsDBCSLeadByte( BYTE ); 如果该字节是DBCS字符的第一个字节,则返回非0值
3. 为什么要使用Unicode?
(1) 可以很容易地在不同语言之间进行数据交换。
(2) 使你能够分配支持所有语言的单个二进制.exe文件或DLL文件。
(3) 提高应用程序的运行效率。
Windows 2000是使用Unicode从头进行开发的,如果调用任何一个Windows函数并给它传递一个ANSI字符串,那么系统首先要将字符串转换成
Unicode,然后将Unicode字符串传递给操作系统。如果希望函数返回ANSI字符串,系统就会首先将Unicode字符串转换成ANSI字符串,然后将结
果返回给你的应用程序。进行这些字符串的转换需要占用系统的时间和内存。通过从头开始用Unicode来开发应用程序,就能够使你的应用程序
更加有效地运行。
Windows CE 本身就是使用Unicode的一种操作系统,完全不支持ANSI Windows函数
Windows 98 只支持ANSI,只能为ANSI开发应用程序。
Microsoft公司将COM从16位Windows转换成Win32时,公司决定需要字符串的所有COM接口方法都只能接受Unicode字符串。
4. 如何编写Unicode源代码?
Microsoft公司为Unicode设计了WindowsAPI,这样,可以尽量减少代码的影响。实际上,可以编写单个源代码文件,以便使用或者不使用
Unicode来对它进行编译。只需要定义两个宏(UNICODE和_UNICODE),就可以修改然后重新编译该源文件。
_UNICODE宏用于C运行期头文件,而UNICODE宏则用于Windows头文件。当编译源代码模块时,通常必须同时定义这两个宏。
5. Windows定义的Unicode数据类型有哪些?
数据类型 说明
WCHAR Unicode字符
PWSTR 指向Unicode字符串的指针
PCWSTR 指向一个恒定的Unicode字符串的指针
对应的ANSI数据类型为CHAR,LPSTR和LPCSTR。
ANSI/Unicode通用数据类型为TCHAR,PTSTR,LPCTSTR。
6. 如何对Unicode进行操作?
字符集 特性 实例
ANSI 操作函数以str开头 strcpy
Unicode 操作函数以wcs开头 wcscpy
MBCS 操作函数以_mbs开头 _mbscpy
ANSI/Unicode 操作函数以_tcs开头 _tcscpy(C运行期库)
ANSI/Unicode 操作函数以lstr开头 lstrcpy(Windows函数)
所有新的和未过时的函数在Windows2000中都同时拥有ANSI和Unicode两个版本。ANSI版本函数结尾以A表示;Unicode版本函数结尾以W表示。
Windows会如下定义:
#ifdef UNICODE
#define CreateWindowEx CreateWindowExW
#else
#define CreateWindowEx CreateWindowExA
#endif // !UNICODE
7. 如何表示Unicode字符串常量?
字符集 实例
ANSI “string”
Unicode L“string”
ANSI/Unicode T(“string”)或_TEXT(“string”)if( szError[0] == _TEXT(‘J’) ){ }
8. 为什么应当尽量使用操作系统函数?
这将有助于稍稍提高应用程序的运行性能,因为操作系统字符串函数常常被大型应用程序比如操作系统的外壳进程Explorer.exe所使用。由于
这些函数使用得很多,因此,在应用程序运行时,它们可能已经被装入RAM。
如:StrCat,StrChr,StrCmp和StrCpy等。
9. 如何编写符合ANSI和Unicode的应用程序?
(1) 将文本串视为字符数组,而不是chars数组或字节数组。
(2) 将通用数据类型(如TCHAR和PTSTR)用于文本字符和字符串。
(3) 将显式数据类型(如BYTE和PBYTE)用于字节、字节指针和数据缓存。
(4) 将TEXT宏用于原义字符和字符串。
(5) 执行全局性替换(例如用PTSTR替换PSTR)。
(6) 修改字符串运算问题。例如函数通常希望在字符中传递一个缓存的大小,而不是字节。这意味着不应该传递sizeof(szBuffer),而应该传
递(sizeof(szBuffer)/sizeof(TCHAR)。另外,如果需要为字符串分配一个内存块,并且拥有该字符串中的字符数目,那么请记住要按字节来
分配内存。这就是说,应该调用malloc(nCharacters *sizeof(TCHAR)),而不是调用malloc(nCharacters)。
10. 如何对字符串进行有选择的比较?
通过调用CompareString来实现。
标志 含义
NORM_IGNORECASE 忽略字母的大小写
NORM_IGNOREKANATYPE 不区分平假名与片假名字符
NORM_IGNORENONSPACE 忽略无间隔字符
NORM_IGNORESYMBOLS 忽略符号
NORM_IGNOREWIDTH 不区分单字节字符与作为双字节字符的同一个字符
SORT_STRINGSORT 将标点符号作为普通符号来处理
11. 如何判断一个文本文件是ANSI还是Unicode?
判断如果文本文件的开头两个字节是0xFF和0xFE,那么就是Unicode,否则是ANSI。
12. 如何判断一段字符串是ANSI还是Unicode?
用IsTextUnicode进行判断。IsTextUnicode使用一系列统计方法和定性方法,以便猜测缓存的内容。由于这不是一种确切的科学方法,因此 
IsTextUnicode有可能返回不正确的结果。
13. 如何在Unicode与ANSI之间转换字符串?
Windows函数MultiByteToWideChar用于将多字节字符串转换成宽字符串;函数WideCharToMultiByte将宽字符串转换成等价的多字节字符串。

回复人: xtky_limi(痛在心中笑在脸上) (2001-12-22 0:35:58) 得0分 
上面说的已经比较全了。 
回复人: xtky_limi(痛在心中笑在脸上) (2001-12-22 0:38:13) 得0分 
TEXT是宏
相当于L##

它可以根据编译环境确定为DBMS,还是UNICODE字符集

10。int类型转换为CString类型?
回复人: tjmxf(天涯) (2001-12-17 19:59:34) 得0分 
itoa() 
回复人: zf925(天下哪来那么多高手) (2001-12-17 20:00:30) 得22分 
char m[20];
str=str + itoa(i,m,10); 
回复人: yuezifeng(wybzd) (2001-12-17 20:00:50) 得22分 
CString str;
str.Format("%d",i);

回复人: kingfish(今飞) (2001-12-17 20:06:27) 得0分 
str.Format("%s%d",str,i); 
回复人: tanyajun(谈子) (2001-12-17 20:09:25) 得0分 
CString str="test";
int i=11;
CString str1;
str1.Format("%d",i);
str = str+str1;

回复人: guanjinke(纶巾客) (2001-12-17 20:10:42) 得0分 
int i=11;
CString str="test";
CString addition;
addition.Format("%d",i);
str+=addition;
就可以了。 

11。关于sprintf类型转换的问题
sprintf(buf,"select price from ls01 where p_date>='%'",t_date)
其中t_date是CTime类型,%后面应该是什么呢?%s是String类型,%c是char,那么CTime型对应的是什么呢? 
 
回复人: yakai(日落长河) (2001-12-17 17:45:47) 得0分 
sprintf(buf,"select price from ls01 where p_date>='%S'",(LPCSTR)t_date.Format( "%A, %B %d, %Y" ));
如果不行,就
char temp[50];
CString str=t_date.Format( "%A, %B %d, %Y" );
strcpy(temp,(LPCSTR)str);
sprintf(buf,"select price from ls01 where p_date>='%S'",temp);
CTime::Format返回CString 
回复人: loh(乐啸天涯) (2001-12-17 17:52:57) 得0分 
wait

don't know 
回复人: masterz() (2001-12-17 20:21:05) 得0分 
SQL语句中日期要写成字符串"yyyymmdd" 


12。类型转换 unsigned int <==>CString??
回复次数:8
发表时间:2001-12-17 10:25:23

unsigned int f;//unsigned int 0~4294967295
CString g;
f=2300000000;
g.Format("%d",f);
AfxMessageBox(g);
出错。 


回复人: ydogg(灰毛兔频频) (2001-12-17 10:31:29) 得0分 
unsigned int f;//unsigned int 0~4294967295
CString g;
f=2300000000;
g.Format("%d",f);
MessageBox(g);//使用AfxMessageBox,需要窗口的句炳参数

回复人: asdmusic8(asdmusic8) (2001-12-17 10:35:15) 得0分 
我 AfxMessageBox(g); 和MessageBox(g); 都不错。
错的是g.从 2300000000=》1994967296

回复人: asdmusic8(asdmusic8) (2001-12-17 10:36:10) 得0分 
是2300000000=》-1994967296 类型转换错。

回复人: ydogg(灰毛兔频频) (2001-12-17 10:37:54) 得6分 
g.Format("%u",f);

回复人: asdmusic8(asdmusic8) (2001-12-17 10:40:24) 得0分 
to dgsnmpoperate 那怎么从 CString ==>>unsigned int 
回复人: kingfish(今飞) (2001-12-17 10:42:10) 得6分 
既然是 unsigned int,
超过 0x7f000000 (2130706432) 当然不能用 %d (signed int)用%u 
回复人: kingfish(今飞) (2001-12-17 10:44:57) 得8分 
CString ==>>unsigned int 
char *p = (LPSTR)(LPCSTR) g;
f = atoi(p); 

13。static_cast、dynamic_cast 和直接类型转换(如 (void *)p )的区别?
发表时间:2001-12-14 9:31:13

先拷贝MSDN中的一小段话:
class B { ... };
class C : public B { ... };
class D : public C { ... };

void f(D* pd)
{
C* pc = dynamic_cast<C*>(pd); // ok: C is a direct base class
// pc points to C subobject of pd 

B* pb = dynamic_cast<B*>(pd); // ok: B is an indirect base class
// pb points to B subobject of pd 
...
}
我已经知道 static_cast 和 dynamic_cast 的作用,但MSDN中并没有提到这两个操作符与直接类型转换如
void f(D* pd)
{
C* pc = (C*)(pd);

B* pb = (B*)(pd); 
...
}
的不同啊。不知道那位知道的告诉一声,在此不胜感谢,50分奉上。

回复贴子:ysdesigned(清泉) (2001-12-14 10:03:07) 得0分 
static_cast、dynamic_cast 代 替 简 单 的 强 制 转 化, 从 而 消 除 多 继 承 带 来 的 歧 义。 使 用 这 两 个 运 算 符 号, 我 们可以 在 对 象 运 行 过 程 中 获 取 对 象 的 类 型 信 息
dynamic_cast 用于多态类型的转换
static_cast 用于非多态类型的转换

回复人: masterz() (2001-12-14 10:05:48) 得0分 
static_cast<...>compile时能发现不正确的指针类型转换
dynamic_cast<...>运行时如果发现是不正确的指针类型转换会返回NULL
(void*)强制转换,如果是不正确的指针类型转换,没有办法检查,不如上面2中安全 
回复人: meady() (2001-12-14 11:29:05) 得0分 
类型安全 
回复人: bluecrest(为什么我的VC还是那么的菜) (2001-12-14 11:45:34) 得0分 
com技术内幕介绍过
我刚看完就忘了 

14。byte数据类型转换成int型??
我用byte型读进一组数据想把他转成int型进行运算如何做呢?
如果再把int型转回byte又怎么实现呢? 

回复人: louifox(兰陵笑笑生) (2001-12-6 9:18:38) 得0分 
用下面这些宏:
WORD MAKEWORD(
BYTE bLow, 
BYTE bHigh 
);
BYTE LOBYTE(
WORD wValue 
);
BYTE HIBYTE(
WORD wValue 
);

回复人: chskim(大刀阔斧) (2001-12-6 9:21:04) 得0分 
int i;
BYTE b;
b=128;
i=(int)b;

回复人: nannet(似的) (2001-12-6 9:38:24) 得0分 
这个宏怎么用呀?有没有简单一点儿的,我现在能把BYTE 转成INT 型了,再转回去直接赋值可以吗? 
回复人: louifox(兰陵笑笑生) (2001-12-6 9:46:24) 得20分 
WORD wa;
BYTE ba=32,bb=64;
wa=MAKEWORD(ba,bb);
...
WORD wa=1234;
BYTE ba,bb;
ba=LOBYTE(wa);
bb=LOBYTE(wa);

回复人: nannet(似的) (2001-12-6 9:54:55) 得0分 
问题解决了,多谢各位 

15。类型转换的问题,unsigned int --> lptstr/lpctstr??
发表时间:2001-8-7 23:49:41
如果强制转换的话,会出现致命错误,有什么好的办法呢?
能列举一些其他的办法吗?
谢谢大虾! 

回复人: AlphaOne(总是第一个倒下) (2001-8-8 0:02:43) 得5分 
你为什么要强行转换呢?
如果你是要把int 的值作为 lptstr/lpctstr 的内容的话,
可以用CString:
unsigned int a = 100;
LPCTSTR lpText;
CString str;
str.Format("%d",a);
lpText = (LPCTSTR)str;

回复人: tryibest(编の魂) (2001-8-8 8:20:20) 得5分 
wsprintf(str,"%u",ui); 
回复人: zzh() (2001-8-8 9:04:39) 得5分 
这种情况不需要进行强制转换,直接使用wsprintf就可以了。 
回复人: GJA106(中文字符) (2001-8-8 10:10:51) 得5分 
unsigned int m_na=22;
LPTSTR lptstr;
wsprintf(lptstr,"%u",m_na);

16。关于COM类型转换问题??
我定义了两个变量,一个是void *piaRef=new unsigned char[1000];另一个是m_Temp=new CComVariant();我的问题是如何将piaRef中的值
COPY到m_Temp中。 

回复人: nichang() (2001-11-21 15:34:04) 得0分 
CComBSTR bsRef=piaRef;
m_Temp=bsRef.copy() 
回复人: VincentChin(瘟神) (2001-11-21 17:04:24) 得0分 
CComBSTR bsRef=piaRef;
//error C2440: 'initializing' : cannot convert from 'void *' to 'class ATL::CComBSTR'
m_Temp=bsRef.copy();
//error C2440: '=' : cannot convert from 'unsigned short *' to 'class ATL::CComVariant *' 
回复人: nichang() (2001-11-21 17:14:28) 得0分 
将void*改为unsigned char * 
回复人: VincentChin(瘟神) (2001-11-21 17:22:22) 得0分 
我用CComBSTR bsRef=(unsigned char*)piaRef,也不行吗? 
回复人: VincentChin(瘟神) (2001-11-21 17:28:06) 得0分 
会报错:
error C2440: 'type cast' : cannot convert from 'unsigned char *' to 'class ATL::CComBSTR' 
回复人: nichang() (2001-11-22 9:12:14) 得0分 
m_Temp=::SysAllocString((OLECHAR *)piaRef) 
回复人: VincentChin(瘟神) (2001-11-22 10:43:07) 得0分 
//error C2440: '=' : cannot convert from 'unsigned short *' to 'class ATL::CComVariant *' 
回复人: VincentChin(瘟神) (2001-11-22 11:22:35) 得0分 
m_Temp=new CComVariant(::SysAllocString(OLECHAR *)piaRef));没有出错,但是我的m_Temp是COM组件中的一个PROPERTY,我想返回的是
unsigned char类型(单字节),但经过上述转换后,就不再是单字节了呀!怎么办? 
回复人: jiangsheng(蒋晟) (2001-11-22 11:36:58) 得0分 
把这个属性的类型改成BSTR 
回复人: GrayWhite(灰白) (2001-11-22 12:01:09) 得0分 
m_Temp = new CComVariant((char*) piaRef);就可以了。VB就是用的BSTR,你要给谁用阿?VC不用VARIANT的。 
回复人: GrayWhite(灰白) (2001-11-22 12:18:18) 得19分 
哦,我明白了,你要各字节数组:
SAFEARRAY *psa = SafeArrayCreateVector(VT_UI1, 0, 1000);
if (!psa)
_com_issue_error(ERROR_NOT_ENOUGH_MEMORY);

HRESULT hr
for (long i = 0; i < 2; i ++)
{
if (FAILED (hr = SafeArrayPutElement(psa, &i, piaRef + i)))
_com_issue_error(hr);
}

_variant_t va; // include <comdef.h>
va.vt = VT_ARRAY | VT_UI1;
va.parray = psa;

m_Temp = new CComVariant(va); 
回复人: VincentChin(瘟神) (2001-11-22 14:21:08) 得0分 
SafeArrayPutElement(psa, &i, piaRef + i)
//error C2036: 'void *' : unknown size 
回复人: VincentChin(瘟神) (2001-11-22 14:46:05) 得0分 
To GrayWhite:为什么要for(long i=0;i<2;i++)? 
回复人: nichang() (2001-11-22 15:16:35) 得0分 
到底你想怎样转换嘛,是将数组内的值拷贝到CComVariant中存为字符串吗? 
回复人: VincentChin(瘟神) (2001-11-22 15:28:35) 得0分 
我是想把piaRef中的值照原样返回给其它程序使用。我正在做的是一个COM组件。谢谢各位! 
回复人: nichang() (2001-11-22 15:34:40) 得10分 
unsigned char *s=new unsigned char[1000];
strcpy((char*)s,"1234");//可以用你自己方法设置s中的值。
BSTR bstrS;
oleS=A2WBSTR((char*)s);//将char*转换成BSTR类型

CComVariant comVT;
comVT=oleS;//将BSTR转成CComVariant,这里一步也可,comVT=A2WBSTR((char*)s);

回复人: VincentChin(瘟神) (2001-11-22 16:54:07) 得0分 
谢谢你!
但我还有一个问题,就是如果在s中有'\0'之类的东西我该怎么返回呢?char *遇到'\0'会认为到头了。完整的设计是这样的,我定义一个void * 用来从一个外部设备获取数据,该数据应该是unsigned char,我想把这个返回的数据作为属性传出,让其它应用使用(如VB)。 
回复人: nichang() (2001-11-22 17:18:09) 得0分 
将'\0'转换成其它如'\1'就OK了, 
回复人: jiangsheng(蒋晟) (2001-11-22 18:07:16) 得0分 
用字符串数组 
回复人: VincentChin(瘟神) (2001-11-23 15:54:39) 得0分 
谢谢各位的回复!我的问题解决了!如下:
SAFEARRAY *psa = SafeArrayCreateVector(VT_UI1, 0, 1000);
if (!psa)
return S_FALSE;
HRESULT hr;
for (long i = 0; i < 1000; i ++)
if (FAILED (hr = SafeArrayPutElement(psa, &i, ((unsigned char*)piaRefTemplate) + i)))
return S_FALSE;
VARIANT va;
va.vt = VT_ARRAY | VT_UI1;
va.parray = psa;
CComVariant *m_Temp = new CComVariant();
m_Temp->Copy(&va);

17。类型转换 static_cast reinterprete_cast 的区别??
static_cast reinterprete_cast 的区别 

回复人: tar(GPS) (2001-11-21 10:06:41) 得0分 
static_cast会检查转换类型健的相关性
如果没有的画会有编译错误
reinterprete_cast就是硬转了 
回复人: tigerwoods(tao) (2001-11-21 12:28:19) 得0分 
是否可以这样理解:在多重继承中,static_cast可实现对象指针的移动,从而指向正确的父类对象部分,而reinterprete_cast不作偏移? 
回复人: liu_feng_fly(我恨死驱动程序了,哎,就是为了混口饭吃) (2001-11-21 12:35:14) 得0分 
在多重继承中可以用dynamic_cast啊 

18。那如何取得CString中的字符串??
回复人: nichang() (2001-11-5 17:06:00) 得0分 
=(LPCTSTR)CString变量 
回复人: snake1122(领悟) (2001-11-5 17:12:16) 得0分 
方法太多了:
GetAt,Left,Mid,Right等等,就看你怎么取了! 
回复人: dusb(保时捷) (2001-11-5 17:34:29) 得0分 
可是不管是GetAt,Left,Mid,Right返回类型都是CString,还是不能用,我是要取其中的字符串,奇怪的是,VC中没有string类型。(我要的字符串是给树型控件中的分支名称) 
回复人: Alps_lou(云飞扬) (2001-11-5 17:41:36) 得0分 
有string类型的啊,要包含<string> 
回复人: luxes() (2001-11-5 17:42:19) 得0分 
加上(LPCTSTR),相当于一个const char *了,还不能用? 
回复人: wt007(tt) (2001-11-5 17:48:33) 得0分 
GetBuffer 
回复人: espon99() (2001-11-5 17:54:06) 得20分 
(LPSTR)(LPCTSTR)

回复人: ineedyou(古寺僧) (2001-11-5 17:59:29) 得0分 
...m_str.GetBuffer(needlen)...;
....
m_str.ReleaseBuffer() 
回复人: dusb(保时捷) (2001-11-6 15:08:36) 得0分 
espon99大侠,果然是绝招,不过能否解释一下啊? 

19。如何从CString类型转换为Unicode string 类型?
回复人: ychener(贫血) (2001-10-20 10:28:48) 得0分 
CString本身就支持Unicode的。
只要你选择的是UniCode编译,生成的可执行程序就是支持UniCode的 

回复人: ychener(贫血) (2001-10-20 10:30:04) 得0分 
CString类是自适应的就像TCHAR一样,如果你定义了UniCode宏 就会以UniCode编译 

回复人: xjl1980_81(阿龙) (2001-10-20 10:35:16) 得0分 
不是呀,我有个函数中有一个参数需Unicode string 类型的,比如应该填L"abc",而且引号中的内容要有变化,现在我有一个 temp变量,是CString类型的,如何用呀? 
回复人: xt_jat(桑巴) (2001-10-20 10:39:37) 得0分 
_T()
_TEXT()
行不行? 
回复人: xjl1980_81(阿龙) (2001-10-20 10:43:18) 得0分 
不行 
回复人: Jeffery__Chen() (2001-10-20 11:04:53) 得0分 
强制转化:
CString temp;
WCHAR wTemp = (WCHAR)temp; 
回复人: xjl1980_81(阿龙) (2001-10-20 11:37:06) 得0分 
to:Jeffery__Chen() 
不对呀,出现不能转换的错误 
回复人: hongzhh(关儿) (2001-10-20 11:39:42) 得0分 
问题是这样的:
temp 是 CString类型变量,值为zhh
现在有一个API 
PCCERT_CONTEXT WINAPI CertFindCertificateInStore(
HCERTSTORE hCertStore, 
DWORD dwCertEncodingType, 
DWORD dwFindFlags, 
DWORD dwFindType, 
const void *pvFindPara, //此处用 L"zhh" 没问题 
//请问怎么转换 可以 用temp

PCCERT_CONTEXT pPrevCertContext 
);

在此谢谢大家,请帮忙看看

回复人: hongzhh(关儿) (2001-10-20 13:27:10) 得0分 
WCHAR wszDomain[256]; 
MultiByteToWideChar( CP_ACP, 0, temp,
strlen(temp)+1, wszUserName, 
sizeof(wszUserName)/sizeof(wszUserName[0]) );


wszUserName就是转换后的值

回复人: ychener(贫血) (2001-10-23 11:43:05) 得0分 
只要你用的是CString的函数就行的,如果你要用类似strcpy函数时,看看MSDN中一般情况下都有响应的函数对于Unicode的。只要换成_tcscpy等等。 
回复人: ychener(贫血) (2001-10-23 11:44:10) 得0分 
你有没有定义Unicode宏? 
20。请问在用ATL且不支持MFC的组件开发中,如何将从数据库中读到的DATE数据类型转换回为字符串?? 

复人: zhxuys(zhxuys) (2001-9-24 10:36:47) 得0分 
ATL把datetime类型的列映射为DBTIMESTAMP类型,可取出该类型的year、month、day等,然后将这些数据传递回客户端,在客户端用CTime来构造 
回复人: YUANXU(旭) (2001-9-24 11:18:14) 得0分 
to zhxuys:CTime是MFC类,在ATL 不支持MFC时不能用。DATE其实质是个double* 
回复人: zhxuys(zhxuys) (2001-9-24 11:57:01) 得0分 
你在客户端与服务器端只用ATL规定的数据类型或VARIANT类型,而在客户端,可以用MFC来重新构造想要的数据结构 

21。类型转换,CString to wchar_t ??

CString ss("aabb");
wchar_t* cc;
cc=ss.AllocSysString();

22。如何将CString类型转换为_bstr_t类型?
回复人: wei97081116(韦小宝) (2001-9-4 11:19:30) 得20分 
CString b;
_bstr_t a;
a=(_bstr_t)b; 

回复人: zhaozhen1212(赵振) (2001-9-18 1:30:18) 得0分 
_bstr_t a=b.AllocSysString();;

23。如何把一个CString类型转换成一个普通的字符串,如char*?

回复人: liu_feng_fly(我恨死驱动程序了,哎,就是为了混口饭吃) (2001-9-17 18:00:52) 得0分 
所以,直接用就可以,因为类里边有这样的转换函数 
回复人: ydogg(灰毛兔频频) (2001-9-17 18:01:21) 得0分 
CString show;

char *p = show.GetBuffer(show.GetLength()); 
回复人: jiangping_zhu(娜可露露之风之刃) (2001-9-17 18:02:05) 得0分 
(char*)(LPCTSTR)str 
回复人: bmouse(老鼠) (2001-9-18 0:10:56) 得0分 
同意楼上! 
回复人: bmouse(老鼠) (2001-9-18 0:13:22) 得0分 
你还可以通过GetBuff来直接操作CString的缓冲区,不过要记着释放缓冲区. 

24。CString 类型转换成 unsigned char类型吗??
回复人: LJN(*)风流倜傥无人及,玉树偏又临风立(*) (2001-9-17 12:46:01) 得0分 
可以用CString.GetBuffer函数 
回复人: xpmao() (2001-9-17 13:09:09) 得0分 
CString strWork;
MessageBox(0,(LPSTR)strWork,0,0);
或MessageBox(0,strWork.GetBuffer(0),0,0);

回复人: sandd(降龙掌) (2001-9-17 13:17:32) 得0分 
CString string;

(LPCTSTR)string; 
回复人: jeff_hunter(PandaLee) (2001-9-17 13:45:30) 得0分 
(unsigned char *)(LPCTSTR) 
回复人: fandh(好了) (2001-9-17 14:00:57) 得0分 
用(unsigned char *)(LPCTSTR)即可 
回复人: ygd(ygd) (2001-9-17 16:11:17) 得0分 
unsigned char *p;
CString str;
int length=str.GetLength();
for(int i=0;i<length;i++)
p[i]=str.GetAt(i); 
回复人: swordbroken(断剑书生) (2001-9-17 16:25:57) 得0分 
CString str;
unsigned char string[30];
strcpy(string,str); 

25。何将一个unsigned int 类型变量值赋给类型为unsigned short的变量,并保证数值不丢失(当然数值在一定范围内)?
回复人: maxsuy(魔法师兔子) (2001-8-14 16:37:30) 得0分 
直接=就OK了 
回复人: oppmm(ppmm) (2001-8-14 16:38:11) 得0分 
直接赋值 
回复人: milefo(弥勒佛) (2001-8-14 16:40:40) 得0分 
如果数值在一定范围内怎么回丢失呢?
unsigned short a;
unsigned int b;
a=( b & 0xffff);
你试试看吧!

26。CString ----char* 
定义了char* aa的变量,现在有一个CString的变量bb,怎样把bb的值赋给aa呢? 

回复人: emmai(WaTaXiWaWaTaXi) (2001-8-10 11:57:33) 得0分 
aa=bb.GetBuffer(); 
回复人: hswqs(??????????????????) (2001-8-10 11:59:01) 得0分 
aa= (LPSTR)(LPCTSTR)bb; 
回复人: ydogg(灰毛兔) (2001-8-10 12:27:23) 得0分 
1.aa=bb.GetBuffer(bb.GetLenth());//第一种方法
2.aa= (LPSTR)(LPCTSTR)bb; //第二种方法 
回复人: zhizhi() (2001-8-10 13:16:23) 得0分 
aa= (char *)(LPCTSTR)bb,hehe 

27。在一个COM的接口函数中有一个 BSTR* 类型的参数,需要把一个 char * 类型转换为 BSTR* 类型,不知道如何转换? 由于调用这个函数后需要把这个参数值再取出来所以只能用指针,另外在调用的时候应该用什么类型的数据传递参数呢?大虾帮忙。

BSTR bstr = SysAllocString(L"字符串");
这样转换,用的时候你用地址操作符&啊,要不指针还得new 

回复人: yongyue2000i(小吕) (2001-9-9 18:38:26) 得13分 
CString str = "abcd";
BSTR bstr = str.AllocSysString(); 
回复人: houjzs() (2001-9-9 19:14:44) 得13分 
BSTR b = SysAllocString(OLESTR("your string"));

28。要把一个double的数字输出到CEdit控件是否需要类型转换?
回复人: jiangsheng(蒋晟) (2001-8-24 14:46:17) 得0分 
void AFXAPI DDX_Text( CDataExchange* pDX, int nIDC, double& value ); 
回复人: xiezhsh(雪中行) (2001-8-24 14:56:22) 得0分 
假如你的CEdit相关的成员变量是Double型的,那根本用不着.(ClassWizard增加成员变量的对话框中,Variable Type选择Double可) 
回复人: xiezhsh(雪中行) (2001-8-24 14:58:16) 得0分 
假如你的CEdit相关的成员变量不是Double型的,是CString型,那就需要用ltoa()来转换成CString型, 
回复人: haven(大天) (2001-8-24 14:58:32) 得0分 
m_Edit.Fromat("%l",VarBouble);
updatedata(false); 
回复人: 12345678() (2001-8-24 14:59:54) 得0分 
CString m_Edit.Format("%lf", doubleVar); 
GetDlgItem(EditID)->SetWindowText(m_strEdit); 

29。该如何把 WINDOWPLACEMENT * 转换成 char **类型??

(char**)&pWP 

30。怎样把CString的类型转换成char*型的?
回复人: dcz(dcz) (2001-8-19 19:13:27) 得5分 
// str is CString var
char* temp = strdup(str);
...
free(temp); 
回复人: yu900(疾风之狼) (2001-8-19 19:57:25) 得0分 
getbuffer();即可! 
回复人: aileen_long(挑战2001) (2001-8-19 21:10:35) 得0分 
同意楼上的意见! 
回复人: czh912() (2001-8-19 21:27:08) 得0分 
char buf[20];
printf(buf,"%s",string);

回复人: casl(casl) (2001-8-19 22:59:44) 得5分 
CString s("abc");
char* temp=s.GetBuffer(10);
...
s.ReleaseBuffer(); 
回复人: cocia(高亚) (2001-8-19 23:04:23) 得0分 
char* temp=s.GetBuffer(10);
10是什么意思啊

回复人: kevin_dong(梦仙人) (2001-8-20 10:26:35) 得0分 
// str is CString var
char* temp = strdup(str);
// free
free(temp); 
我的这段代码在一个程序中能通过编译,但是在另外一个中总是出现cannot convert parameter 1 from 'class CString' to 'const char *'的错误。str和temp的类型都一样。这是为什么?

回复人: dcz(dcz) (2001-8-20 14:13:45) 得0分 
you may setting your compiler option to UNICODE, in this case, declare the var:

// str is CString var
_TCHAR* temp = _tcsdup(str);

// free
free(str);

31。SA,SB为两个结构类型??
SA* A;
SB* B;
(SB*)A->...(调用函数)
请问此时A的类型,是指向SA还是SB
此时编译器是生成一个临时指针吗?
另外,
B=(SB*)A;此时A又是什么类型???

回复贴子: 
回复人: ddeng(登登) (2001-8-9 17:13:58) 得0分 
A的类型始终是SA *
B的类型始终是SB *
当进行强制类型转换时使的是临时指针 
回复人: gold_water(风雨无阻) (2001-8-9 17:30:46) 得0分 
同意楼上的。 

32。char buff[100],char UserName[50][100],怎么将buff的值传给UserName,是其成为UserName数组中的某一项呢??

//0=<i<50
strcpy(UserName[i],buff); 
回复人: Ashura(阿修罗) (2001-7-26 10:08:20) 得0分 
呵呵,benbensan抢先一步。 
回复人: tuita(斗牛士) (2001-7-26 10:13:22) 得0分 
for (i=0;i<100;i++)
*(*(username+x)+i)=*(buffer+i)
其中0《X〈50
benbensan写的也对

回复人: kekeke(我是来向大家学习的) (2001-7-26 10:24:22) 得0分 
那反过来呢?把UserName中的某一项读出赋值给buff呢?怎么弄? 
回复人: benbensan(笨笨三) (2001-7-26 10:26:53) 得0分 
//0=<i<50
strcpy(UserName[i],buff); 
回复人: benbensan(笨笨三) (2001-7-26 10:28:15) 得0分 
对不起,能错了,不过建议你看一下C语言了的指针和数组
//0=<i<50
strcpy(buff,UserName[i]); 

回复人: jfzsl(剿匪总司令) (2001-7-26 10:32:57) 得0分 
好好看看老潭的书先!OK? 
回复人: kekeke(我是来向大家学习的) (2001-7-26 10:44:25) 得0分 
好。。。。! 

33。请问怎样把SYSTEMTIME类型转换成time_t类型?
SYSTEMTIME st;
GetLocalTime(&st);
CTime tm(st.wYear,st.wMonth,st.wDay,st.wHour,st.wMinute,st.wSecond);
time_t t = tm.GetTime();

34。unsigned char Exponent[10]; //e
unsigned long eBytes; //e的字节数
如何转换成DWord型! ??


用强制类型转换呀
(DWord)eBeytes;
(DWord)Exponent[i];//(0<=i<=10);

回复人: xjl1980_81(阿龙) (2001-7-26 16:47:29) 得0分 
我是说把e转换成DWORD型
也就是说把Exponent中的内容转换成DWORD型

回复人: cloudshadow1(云影) (2001-7-26 17:13:30) 得0分 
用强制类型转换就可以了,(DWORD的高24位自动加0)
DWORD Des[10]
for (int i=0;i<11;i++)
Des[i]=Exponent[i];
至于那个ULONG的也是用强制类型软换就可以了

35。请问怎样把time_t类型转换成SYSTEMTIME类型?
回复人: haven(大天) (2001-7-26 17:12:36) 得0分 
typedef struct _SYSTEMTIME
typedef long time_t
很明显不行嘛! 
回复人: facexy(FACE仔) (2001-7-26 17:17:38) 得0分 
哎呀,问错了,前后对象相反了;-(
忙昏了的结果…………

另外,TO 楼上的:
转换是可以的
struct tm *tblock;
SYSTEMTIME SystemTime;
memset(&SystemTime,0,sizeof(SYSTEMTIME));
tblock=localtime(&timer);
SystemTime.wYear=tblock->tm_year+1900;
SystemTime.wMonth=tblock->tm_mon+1;
SystemTime.wDay=tblock->tm_mday;
SystemTime.wHour=tblock->tm_hour;
SystemTime.wMinute=tblock->tm_min;
SystemTime.wSecond=tblock->tm_sec;
SystemTime.wDayOfWeek=tblock->tm_wday;
return &SystemTime; 
回复人: zjh73(大章鱼) (2001-7-26 20:28:28) 得0分 
有两种方法:
1、用CTime类
先用time_t类型构造一个CTime对象,再定义一个SYSTEMTIME结构,最后用CTime类的成员函数GetAsSystemTime将时间转换到SYSTEMTIME结构中
即可。
2、用gmtime函数
gmtime函数将time_t时间转换到tm结构中并返回一个tm指针,再将tm结构的相对应的项赋给SYSTEMTIME相对应的项即可,不过用这种方法要注
意这两种结构在天、星期等方面的记数方法有点区别,一个一般从0开始,一个一般从1开始,赋值时要注意校正,还有要注意的是SYSTEMTIME
结构中有一项是毫秒,而time_t是以秒记数的。 
回复人: zjh73(大章鱼) (2001-7-26 20:32:13) 得0分 
反过来也可以用Ctime类的方法
就是先用SYSTEMTIME结构构造一个CTime对象,在用CTime类中的成员函数GetTime返回一个对应的time_t即可。 36。我现在正在学习SDK编程,遇到的问题是:
我定义了一个静态长整形变量,
static long lScore=0;
我想把窗口的标题换成长整形数值,用SetWindowText函数来实现,
由于它的第二个参数要求数据类型为 unsigned short *,但用其来实现强制转换时
总是出现编译错误:
cannot convert parameter 2 from 'unsigned short *' to 'const char *'
后来改成来LPCTSTR 来实现强制转换,没有出现编译错误,但函数总是执行不成功,
请教各位高人,这倒底是怎么回事???

回复贴子: 
回复人: prog_st(st) (2001-8-4 21:20:07) 得0分 
/* ITOA.C: This program converts integers of various
* sizes to strings in various radixes.
*/

#include <stdlib.h>
#include <stdio.h>

void main( void )
{
char buffer[20];
int i = 3445;
long l = -344115L;
unsigned long ul = 1234567890UL;

_itoa( i, buffer, 10 );
printf( "String of integer %d (radix 10): %s\n", i, buffer );
_itoa( i, buffer, 16 );
printf( "String of integer %d (radix 16): 0x%s\n", i, buffer );
_itoa( i, buffer, 2 );
printf( "String of integer %d (radix 2): %s\n", i, buffer );

_ltoa( l, buffer, 16 );
printf( "String of long int %ld (radix 16): 0x%s\n", l, 
buffer );

_ultoa( ul, buffer, 16 );
printf( "String of unsigned long %lu (radix 16): 0x%s\n", ul,
buffer );
}


Output

String of integer 3445 (radix 10): 3445
String of integer 3445 (radix 16): 0xd75
String of integer 3445 (radix 2): 110101110101
String of long int -344115 (radix 16): 0xfffabfcd
String of unsigned long 1234567890 (radix 16): 0x499602d2


回复人: lwg7603(刑满释放人员) (2001-8-4 21:36:15) 得0分 
TCHAR str[255]={_T('\0')};
_stprintf(str,_T("%d"),lScore);
SetWindowText(hwnd,str);

37。我用socket发送的的buf中间需要的是 char *类型的数据,我想将一个 struct 直接转换成 char * 发过去。
我用
struct ABCD *abcd;
char *buf;
abcd = (ABCD *)calloc(1,sizeof(ABCD));
buf = (char *)calloc(1,sizeof(ABCD));
///
给abcd 中间赋值,其中有多个char[]的值和int 的值
///
memcpy(buf,abcd,sizeof(ABCD));
//strcpy(buf,(char *)abcd);也不可以
sock(host,buf,....);
//sock(host,(char *)buf,...);也不可以
问题就是在这里,这个buf中间的值总是不对,大家知道为什么否。

回复人: wolf721() (2001-7-30 18:18:34) 得5分 
你传的是个指针值,而不是数据 
回复人: kiko_lee(到处瞧瞧) (2001-7-30 18:50:49) 得0分 
但是用memcpy这个是将整个数据都复制过去 
回复人: lz_0618(lz_0618) (2001-7-30 19:26:44) 得5分 
你用的VC???改成ABCD *abcd;后编译一点问题也没有啊!
sock(host,buf,....);这不知是什么,自定义函数?

typedef struct _ABCD
{
int ID;
char Name[10];
}ABCD;

.......


ABCD *abcd;
char *buf;
abcd = (ABCD *)calloc(2,sizeof(ABCD));
buf = (char *)calloc(2,sizeof(ABCD));
///
//给abcd 中间赋值,其中有多个char[]的值和int 的值
abcd[0].ID =1;
abcd[1].ID =2;
///
memcpy(buf,abcd,2*sizeof(ABCD));
strcpy(buf,(char *)abcd);//也不可以

buf中的内容也正确!!

回复人: kiko_lee(到处瞧瞧) (2001-7-31 8:57:52) 得0分 
我按照楼上的兄弟说的,做了一下,但是仍然做不下来,我用
memcpy(buf,abcd,sizeof(ABCD));
中间的abcd,不知道是不是地址的问题。 
回复人: supersusheng(小苏) (2001-7-31 14:30:42) 得0分 
老大,你sizeof()得出的数值事多大,看看吧。 
回复人: ydogg(灰毛兔) (2001-7-31 14:41:52) 得0分 
只能传递流数据,结构是传递不过去的。 
回复人: IamNotMan(NorGirl) (2001-7-31 14:50:53) 得5分 
我常这么用
ABCD a ;
//给a的各个域赋值(一定不能含有指针项)
char* buff = new char[sizeof(ABCD)];
memcpy(buff,&a,sizeof(ABCD));
//或者 *(ABCD*)buff = a;
.................
如果buff里的数对,说明问题不在这儿吧

回复人: zb_china(最后一座水车zb_china新浪) (2001-7-31 15:16:24) 得0分 
看不明白 
回复人: eggplant(拉拉) (2001-7-31 15:42:48) 得0分 
最好使用memcpy(),因为struct中的值有可能包含零字节,所以strcpy()可能不对,如果传递struct,最好把struct的字节对齐改为以字节为单位。 
回复人: lvfengxun(lfx) (2001-7-31 16:06:57) 得5分 
直接将结构指针作为send的参数发就可以了,还用转换什么
有必要在这里讨论吗?
struct AA
{
int a;
char b[100];
};
struct AA aa;
aa.a=11;
strcpy(aa.b,"aaa");
send(hSocket,(char *)(&aa),sizeof(aa),0);
//OK 
回复人: mydewang(mydewang) (2001-7-31 16:33:21) 得0分 
其实这里是一个字节对齐的问题,
比如
struct AA
{
int a;
char b;
};
那么sizeof( struct AA )就不等于5了,而是8了,所以,将这个结构赋值给一个char *,里面会多出一些零...

需要解决这个问题,可以在Project->Setting->Link->Project Options里加上/Zp1

另外,可以参考一下MSDN里/Zp的编译选项..... 
回复人: lz_0618(lz_0618) (2001-7-31 19:43:54) 得0分 
根本不是什么字节对齐的问题,我上面的程序经过测试都好用啊,这位老兄用我的那段程序究竟是什么错误,能说清楚点吗???

我这边用socket发送结构是一点问题也没有啊,而且是在VC和Delphi编的程序间通讯,当然,这时应该注意字节对齐的问题了,在VC程序间,
只要不是故意将服务器和客户端的编译环境设的不一样,肯定是没有什么问题的,最多是多传几个Bit罢了。 
回复人: kiko_lee(到处瞧瞧) (2001-8-3 11:02:51) 得0分 
我发现将char * memcpy 到 char * 中间都有点问题,但是如果放到 char []中间就可以了,大家知道为什么否? 
回复人: ydogg(灰毛兔) (2001-8-3 11:40:35) 得0分 
memcpy不copy最后的'\0'... 
回复人: wenjunlin2000(微软克星) (2001-8-3 14:32:17) 得0分 
是你看错了
因为char*是以0 结尾的

回复人: mc_music(狂沙) (2001-8-3 15:07:21) 得0分 
请注意我的程序:
struct ABCD *abcd;
char *buf;
abcd = (ABCD *)calloc(1,sizeof(ABCD));
//初始化abcd
buf=abcd;//指针直接符值就可以了 
回复人: zhangnanonnet(WinSockZhang) (2001-8-3 16:21:03) 得0分 
你试试把类型变为BYTE 
回复人: kiko_lee(到处瞧瞧) (2001-8-7 9:21:08) 得0分 
不管了,给分,大家都有不少的建议呢。 


38。double dou=12.34;我如何可以得到char * ch="12.34";转换函数是什么?

回复人: wyzegg(蛋) (2001-7-24 21:26:04) 得50分 
double dou=12.34;
char * ch;
ch=malloc(100);
sprintf(ch,"%5.2f",dou); 
回复人: wyzegg(蛋) (2001-7-24 21:28:24) 得0分 
或者
#include <stdlib.h>
#include <stdio.h>

void main( void )
{
int decimal, sign;
char *buffer;
int precision = 10;
double source = 3.1415926535;

buffer = _ecvt( source, precision, &decimal, &sign );
printf( "source: %2.10f buffer: '%s' decimal: %d sign: %d\n",
source, buffer, decimal, sign );
}

但是第一种常用 
回复人: Matrix_w(学会一点点) (2001-7-24 21:32:43) 得30分 
int decimal, sign;
double dou =12.34;
char* ch;
ch = _ecvt(dou,4,&decimal,&sign);

回复人: imhua(华弟) (2001-7-24 21:35:02) 得20分 
double dou=12.34;
char *str;
gcvt(dou,5,str); //5是长度
MessageBox(str); 
回复人: Matrix_w(学会一点点) (2001-7-24 21:37:58) 得0分 
/* _GCVT.C: This program converts -3.1415e5
* to its string representation.
*/

#include <stdlib.h>
#include <stdio.h>

void main( void )
{
char buffer[50];
double source = -3.1415e5;
_gcvt( source, 7, buffer );
printf( "source: %f buffer: '%s'\n", source, buffer );
_gcvt( source, 7, buffer );
printf( "source: %e buffer: '%s'\n", source, buffer );
}


Output

source: -314150.000000 buffer: '-314150.'
source: -3.141500e+005 buffer: '-314150.'

39。 我在ADO中调用一个存储过程,存储过程有三个输入参数@useradd char(30),@username char(10),@userage char(3),现在要把char 
*addr,char *name,char *age分别赋值给他们。??
我做了如下定义:
_ParameterPtr para1;
_variant_t var1,var2,var3;
==============================================================
var1.vt=VT_BSTR;
var1.bstrval=addr;/////(编译错误)
==============================================================
para1=m_pCommand->CreateParameter(L"useradd",adBSTR,adParamInput,30,var1);
m_pCommand->Parameters->Append(para1);
编译

9月2日

Capture Network Data Package Introduction

整天在网上转,也看到许多不错的文章,但我发现大多文章要么只停留在理论上,要么就是太高深。对问题详细分析介绍的很少。今天,我就想以数据包分析程序为主题和大家讨论一下网络编程的的相关问题,我也是新手,有不到之处,还望大家不吝指正。
通过对数据包的分析,我们可以判断通信双方的操作系统、网络信息流量、经过的路由、数据包的大小,以及数据包的内容等等。对于喜欢网络安全的人来说,掌握这方面的知识是相当重要的。现在的网络通信中,大部分数据都没有加密,我们可以轻易地从数据包中提取账号、密码之类我们关心的数据.大家在看本文时如有困难,可先读一读计算机网络及C程序设计还有协议分析方面的书。下面我将分TCP/IP族协议结构、程序部分函数及数据结构说明、案例程序剖析三个部分与大家共同学习数据包分析程序的设计方法。

一、TCP/IP族协议结构
在说TCP/IP之前,先让我们来认识一下以太网,因为我们现在接触最多的就是以太网,并且研究数据包又是离不开以太网的帧的。在以太网中,数据是以被称为帧的数据结构本为单位进行交换的。以太网中常用的协议是CSMA/CD (carrier sense multiple access with collision detection)即载波监听多点接入/碰撞检测,在这里,我们关注的是帧的格式。常用的以太网帧的格式有两种标准,一种是DIX Ethernet V2标准,另一种是IEEE的802.3标准。现在最常用的MAC帧是V2格式,这也是我们所要研究的格式,至于802.3帧我们不再讨论。以太网V2帧的格式如下:
(插入8字节)目的地址(6字节)->源地址(6字节)->类型(2字节)->数据(46-1500)->FCS(4字节)
以太网的地址由48位的二进制来表示,也就是我们常说的MAC地址及硬件地址。在MAC帧前还有8字节的前同步码和帧的开始定界符,之后才是地址等报头信息。接收端和发送端的地址之后是2字节的类型字段,存放帧中传送数据的上层协议类型,RFC1700号文档规定了这些,如下:
ETHER TYPES(十六进制) PROTOCOlS
800 IP
806 ARP
8035 Revese ARP
809B Apple Talk
8137/8138 Novel
814c SNMP
帧的数据部分长度为46-1500字节,当小于46时,会在后面加入一个整数字节的填充字段。FCS(Frame Check Sequence)在以太网常用循环冗佘校检(CRC:cyclic redandancy check)。
IP协议为网络层协议,网络层的数据结构体被称为IP数据报。IP地址及域名这两个概念我们就不说了,下面我们来看一看IP数据报的结构:
成员名 字节数 说明
version 1/2 IP的版本,现在为IPV4
IHL(报送长度) 1/2 最常用为20,取5-15之前的值,最大60字节
Type Of Service 1 优先和可靠性服务要求的数值
Total Lenth 2 IP数据报的全长
Identification 2 识别IP数据报的编号
Flags 3/8 1位为0表示有碎块,2位为0表示是最后的碎块,为1表示接收中。
Fragment Offset 13/8 分片在原分组中的位置
TTL 1 数据报寿命,建议值为32秒
Protocol 1 上层协议
Headerchecksum 2 报头检验码
Source Address 4 发送端IP地址
Destination Address 4 接收端IP地址
Options And Padding 4 选项及填充位
其中协议字段的值对我们分析数据包是很重要的,下面列出来给大家看看:
值 协议 意义
1 ICMP Internet Control Message Protocol
6 TCP Tranfer Control Protocol
8 EGP Exterior Gateway Protocol
9 IGP Interior Gateway Protocol
17 UDP User Datagram Protocol
下面这些协议的值在后面的程序中我们可以见到,请大家留心记一下。接着我们介绍地址解析协议(ARP/RARP):
成员名 字节数 说明
Hardware address 2 硬件类型,以太网为1
Protocol address 2 上层协议类型,IP为800
Byte length of each hardware 1 查询物理地址的字节长度,以太网为6
Byte length of each protocol address 1 查询上层协议的字节长度,IPv4时为4
Opcode 2 1为ARP请求,2为响应;3为RARP请求,4为响应
Hardware address of sender of this packet 6 发送端硬件地址
protocol address of sender of this packet 4 发送端IP地址
Hardware address of target of this packet 6 查询对象硬件地址
Protocol address of target of this packet 4 查询对象IP地址
ARP/RARP 协议用来查询IP对应的硬件地址或反过来查询IP地址,这在我们分析数据包时也会见到。下面介绍ICMP协议。我们常用的PING命令就是用的这个协议,这个协议比较简单,由类型(1字节)、代码(1字节)、检验和(2字节)、还有四个字节的与类型相关的可变部分及数据构成。
数据包在运输层还有两个重要的协议,即TCP/UDP,TCP/UDP中使用端口的概念,以区别计算机上不同的程序。下面我们先来看看TCP数据报的首部构成:
成员名 字节数 说明
Source Port 2 发送端端口号
Destination Port 2 接收端端口号
Sequence NO 4 本报文段所发送的第一个字节的序号
ACk Number 4 期望收到的下一个报文段的序号
DAta Offset 1/2 首部的长度
Reserved 3/4 保留今后用
Contol Bits 3/4 控制位
Window 2 滑动窗口的大小
Checksum 2 检验和
Urgent Pointer 2 紧急指针
Options And Padding 4 可选,真充项
Tcp被使用在跨越路由器进行网络服务的网络应用程序中,如WWW、电子邮件、新闻、FTP等。UDP则是在IP的基础上加入了端口的概念,其结构很简单,只有八个字节首部如下:
源端口(2字节)->目的端口(2字节)->长度(2字节)->检验和(2字节)

二、程序部分函数及数据结构说明
在此部分我们将介绍后面程序中用到的部分函数及数据结构。在程序中我们使用了PCAP程序库,大家可以从
ftp: //ftp.ee.lbl.gov/libpcap.tar.z下载。我们主要在Redhat Linux下测试程序,这里简单介绍一下程序库的安装方法,其它环境请大家自行解决。我的目的是给大家编写数据包分析程序提供思路,至于实用程序的实现这里不做介绍,第三部分给出的程序也不具实用性,为了演示,程序中实现的功能较多而有些地方又不够详细,编写实用程序时请适当取舍并加入你所需要的功能实现部分。PCAP程序库的安装方法如下:
1、解压文件
2、进入文件目录执行./configure 及make
3、使用Make命令,设定手册和Include文件(要有Root权限),执行以下命令:
make install -man
make install -incl
4、如出现不存在Include及Include/net目录,则建立此目录并重新执行 make install -incl
5、检查/usr/include/netinet/目录是否存在Protocols.h文件,不存在则拷贝过去。至此程序库安装完毕。
下面介绍程序中出现的部分函数及数据结构:
1、PCAP_t *pd;
此型数据结构称为数据包捕捉描述符。
2、Pcap_Open_Live(argv[1],DEFAUT_SNALEN,1,1000,ebuf)
此函数对Pcap程序库进行初始化并返回指向Pcap_t型数据的指针,其参数列表如下:
char * 指定网络接口
int 取得数据的最大字节数
int 指定网络接口卡,一般用1
int 读出暂停时间
char * 错误消息用缓冲区
3、Pcap_loop(pd,-1,packet_proce,NUll)
此函数程序的核心,反复执行,利用Pcap取得数据包,返回的是读入数据包的个数,错误时返回-1,其参数列表如下:
Pcap_t * 指定取得数据包的数据包捕捉描述符
int 取得数据包的个数,-1为无限
返回指向函数的指针 指定数据包处理的函数
U_char * 指向赋给数据包处理函数字符串的指针
4、struct ether_header * eth
此结构体存储以太网报头信息,其成员如下:
ether_dhost[6] 接收端的MAC地址
ether_shost[6] 发送端的MAC地址
ether_type 上层协议的种类
5、fflush(stdout)
此函数完成的是强制输出,参数Stdout,强制进行标准输出。
6、noths(((struct ether_header *P)->ether_type))
此函数将短整型网络字节顺序转换成主机字节顺序。此类函数还有:
ntohl 长整型 功能同上
htons 短整型 将主机字节顺序转换成网络字节顺序
htons 长整型 同上
7、struct IP *iph
ip型结构体在IPh文件中定义,其成员和第一部分讲到的IP数据报结构对应,如下:
成员名 类型 说明
ip_hl 4位无符号整数 报头长度
ip_v 同上 版本,现为4
ip_tos 8位无符号整数 Type of service
ip_len 16位无符号整数 数据报长度
ip_id 同上 标识
ip_off 同上 数据块偏移和标志
ip_ttl 8位无符号整数 TTL值
ip_p 同上 上层协议
ip_sum 16位无符号整数 检验和
ip_src in_addr结构体 发送端IP
ip_dst 同上 接收端IP
8、struct ether_arp *arph
ether_arp型结构体成员如下:
成员名 类型 说明
ea_hdr arphdr型结构体 报头中地址以外的部分
arp_sha 8位无符号整数数组 发送端MAC地址
arp_spa 同上 发送端IP地址
arp_tha 同上 目标MAC地址
arp_tpa 同上 目标IP地址
9、struct icmphdr * icmp
icmphdr型结构体中包含共用体根据数据报类型的不同而表现不同性质,这里不再列出,只列能通用的三个成员
成员名 说明
type 类型字段
code 代码
checksum 检验和

三、案例程序剖析
//example.c
//使用方法:example〈网络接口名〉 > 〈输出文件名〉
//例如:example etho > temp.txe
//结束方法:ctrl+c
//程序开始,读入头文件
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netinet/in_systm.h>
#include<netinet/ip.h>
#include<netinet/if_ether.h>
#include<pcap.h> //pcap程序库
#include<netdb.h> //DNS检索使用
#define MAXSTRINGSIZE 256 //字符串长度
#define MAXSIZE 1024 //主机高速缓存中的最大记录条数
#fefine DEFAULT_SNAPLEN 68 /数据包数据的长度
typedef struct
{
unsigned long int ipaddr; //IP地址
char hostname[MAXSTRINGSIZE]; //主机名
}dnstable; //高速缓存数据结构
typedef struct
{
dnstable table[MAXSIZE];
int front;
int rear;
}sequeue;
sequeue *sq; //定义缓存队列
sq->rear=sq->front=0; //初始化队列
//输出MAC地址函数
void print_hwadd(u_char * hwadd)
{
for(int i=0,i<5;++i)
printf("%2x:",hwadd[i]);
printf("%2x",hwadd[i]);
}
//输出IP地址的函数
void print_ipadd(u_char *ipadd)
{
for(int i=0;i<3;++i)
printf("%d.",ipadd[i]);
printf("%d",ipadd[i]);
}
//查询端口函数
void getportname(int portno,char portna[],char* proto)
{
if(getservbyport(htons(portno),proto)!=NULL)
{
strcpy(portna,getservbyport(htons(portno),proto)->s_name);
}
else
sprintf(portna,"%d",portno);
}
//将IP转化为DNS名
void iptohost(unsigned long int ipad,char* hostn)
{
struct hostent * shostname;
int m,n,i;
m=sq->rear;
n=sq->front;
for(i=n%MAXSIZE;i=m%MAXSIZE;i=(++n)%MAXSIZE)
{
//检查IP是否第一次出现
if(sq->table[i].ipaddr==ipad)
{
strcpy(hostn,sq->table[i].hostname);
break;
}
}
if(i=m%MAXSIZE)
{//不存在则从域名服务器查询并把结果放入高速缓存
if((sq->rear+1)%MAXSIZE=sq->front) //判队满
sq->front=(sq->front+1)%MAXSIZE; //出队列
sq->table[i].ipaddr=ipad;
shostname=gethostbyaddr((char*)&ipad,sizeof(ipad),AF_INET);
if(shostname!=NULL)
strcpy(sq->table[i].hostname,shostname->h_name);
else
strcpy(sq->table[i].hostname,"");
sq->rear=(sq->rear+1)%MAXSIZE;
}
}
void print_hostname(u_char* ipadd)
{
unsigned long int ipad;
char hostn[MAXSTRINTSIZE];
ipad=*((unsigned long int *)ipadd);
iptohost(ipad,hostn)
if(strlen(hostn)>0)
printf("%s",hostn);
else
print_ipadd(ipadd);
}
//处理数据包的函数
void packet_proce(u_char* packets,const struct pcap_pkthdr * header,const u_char *pp)
{
struct ether_header * eth; //以太网帧报头指针
struct ether_arp * arth; //ARP报头
struct ip * iph; //IP报头
struct tcphdr * tcph;
struct udphdr * udph;
u_short srcport,dstport; //端口号
char protocol[MAXSTRINGSIZE]; //协议类型名
char srcp[MAXSTRINGSIZE],dstp[MAXSTRINGSIZE]; //端口名
unsigned int ptype; //协议类型变量
u_char * data; //数据包数据指针
u_char tcpudpdata[MAXSTRINGSIZE]; //数据包数据
int i;
eth=(struct ether_header *)pp;
ptype=ntohs(((struct ether_header *)pp)->ether_type);
if((ptype==ETHERTYPE_ARP)||(ptype==ETHERTYPE_RARP))
{
arph=(struct ether_arp *)(pp+sizeof(struct ether_header));
if(ptype==ETHERTYPE_ARP)
printf("arp ");
else
printf("rarp "); //输出协议类型
print_hwadd((u_char *)&(arph->arp_sha));
printf("(");
print_hostname((u_char *)&(arph->arp_spa));
printf(")->");
print_hwadd((u_char *)&(arph->arp_tha));
printf("(");
print_hostname((u_char *)&(arph->arp_tpa));
printf(")\tpacketlen:%d",header->len);
}
else if(ptype==ETHERTYPE_IP) //IP数据报
{
iph=(struct ip *)(pp+sizeof(struct ether_header));
if(iph->ip_p==1) //ICMP报文
{
strcpy(protocol,"icmp");
srcport=dstport=0;
}
else if(iph->ip_p==6) //TCP报文
{
strcpy(protocol,"tcp");
tcph=(struct tcphdr *)(pp+sizeof(struct ether_header)+4*iph->ip_hl);
srcport=ntohs(tcph->source);
dstport=ntohs(tcph->dest);
data=(u_char *)(pp+sizeof(struct ether_header)+4*iph->ip_hl+4*tcph->doff);
for(i=0;i<MAXSTRINGSIZE-1;++i)
{
if(i>=header->len-sizeof(struct ether_header)-4*iph->ip_hl-4*tcph->doff);
break;
else
tcpudpdata[i]=data[i];
}
} //TCP数据处理完毕
else if(iph->ip_p=17) //UDP报文
{
strcpy(protocol,"udp");
udph=(struct udphdr *)(pp+sizeof(struct ether_header)+4*iph->ip_hl);
srcport=ntohs(udph->source);
dstport=ntohs(udph->dest);
data=(u_char *)(pp+sizeof(struct ether_header)+4*iph->ip_hl+8);
for(i=0;i<MAXSTRINGSIZE-1;++i)
{
if(i>=header->len-sizeof(struct ether_header)-4*iph->ip_hl-8);
break;
else
tcpudpdata[i]=data[i];
}
}
tcpudpdata[i]='\0';
getportname(srcport,srcp,protocol);
getportname(dstport,dstp,protocol);
printf("ip ");
print_hwadd(eth->ether_shost);
printf("(");
print_hostname((u_char *)&(iph->ip_src));
printf(")[%s:%s]->",protocol,srcp);
print_hwadd(eth->ether_dhost);
printf("(");
print_hostname((u_char *)&(iph->ip_dst));
printf(")[%s:%s]",protocol,dstp);
printf("\tttl:%d packetlen:%d,iph->ttl,header->len);
printf("\n");
printf("%s",tcpudpdata);
printf("==endpacket==");
}
printf("\n");
}
//Main函数取数据包并初始化程序环境
int main(int argc,char ** argv)
{
char ebuf[pcap_ERRBUF_SIZE];
pcap * pd;
if(argc<=1) //参数检查
{
printf("usage:%s<network interface>\n",argv[0]);
exit(0);
}
//设置PCAP程序库
if((pd=pcap_open_live(argv[1],DEFAULT_SNAPLEN,1,1000,ebuf))=NULL)
{
(void)fprintf(stderr,"%s",ebuf);
exit(1);
}
//循环取数据包
//改变参数-1为其它值,可确定取数据包的个数,这里为无限个
if(pcap_loop(pd,-1,packet_proce,NULL)<0)
{
(void)fprintf(stderr,"pcap_loop:%s\n",pcap_geterr(pd));
exit(1);
}
pcap_colse(pd);
exit(0);
}
//程序结束
到此为止,我的这篇文章就写完了,希望能给大家以启发和帮助。平时和网友交流中发现大多数朋友在网上见到长的文章往往都没有信心看下去,其实以前我也是这样,但在这里我想告诉大家,不踏踏实实的深入进去学习,是得不到收获的。一分耕耘,一分收获。

Compiler and Linker in VC++

许多Visual C++的使用者都碰到过LNK2005:symbol already defined和LNK1169:one or more multiply defined symbols found这样的链接错误,而且通常是在使用第三方库时遇到的。对于这个问题,有的朋友可能不知其然,而有的朋友可能知其然却不知其所以然,那么本文就试图为大家彻底解开关于它的种种疑惑。

    大家都知道,从C/C++源程序到可执行文件要经历两个阶段:(1)编译器将源文件编译成汇编代码,然后由汇编器(assembler)翻译成机器指令 (再加上其它相关信息)后输出到一个个目标文件(object file,VC的编译器编译出的目标文件默认的后缀名是.obj)中;(2)链接器(linker)将一个个的目标文件(或许还会有若干程序库)链接在一起生成一个完整的可执行文件。

    编译器编译源文件时会把源文件的全局符号(global symbol)分成强(strong)和弱(weak)两类传给汇编器,而随后汇编器则将强弱信息编码并保存在目标文件的符号表中。那么何谓强弱呢?编译器认为函数与初始化了的全局变量都是强符号,而未初始化的全局变量则成了弱符号。比如有这么个源文件:

extern int errorno;
int buf[2] = {1,2};
int *p;

int main()
{
   return 0;
}

其中main、buf是强符号,p是弱符号,而errorno则非强非弱,因为它只是个外部变量的使用声明。

    有了强弱符号的概念,我们就可以看看链接器是如何处理与选择被多次定义过的全局符号:

规则1: 不允许强符号被多次定义(即不同的目标文件中不能有同名的强符号);


规则2: 如果一个符号在某个目标文件中是强符号,在其它文件中都是弱符号,那么选择强符号;


规则3: 如果一个符号在所有目标文件中都是弱符号,那么选择其中任意一个;

    由上可知多个目标文件不能重复定义同名的函数与初始化了的全局变量,否则必然导致LNK2005和LNK1169两种链接错误。可是,有的时候我们并没有在自己的程序中发现这样的重定义现象,却也遇到了此种链接错误,这又是何解?嗯,问题稍微有点儿复杂,容我慢慢道来。

    众所周知,ANSI C/C++ 定义了相当多的标准函数,而它们又分布在许多不同的目标文件中,如果直接以目标文件的形式提供给程序员使用的话,就需要他们确切地知道哪个函数存在于哪个目标文件中,并且在链接时显式地指定目标文件名才能成功地生成可执行文件,显然这是一个巨大的负担。所以C语言提供了一种将多个目标文件打包成一个文件的机制,这就是静态程序库(static library)。开发者在链接时只需指定程序库的文件名,链接器就会自动到程序库中寻找那些应用程序确实用到的目标模块,并把(且只把)它们从库中拷贝出来参与构建可执行文件。几乎所有的C/C++开发系统都会把标准函数打包成标准库提供给开发者使用(有不这么做的吗?)。

    程序库为开发者带来了方便,但同时也是某些混乱的根源。我们来看看链接器是如何解析(resolve)对程序库的引用的。
   
    在符号解析(symbol resolution)阶段,链接器按照所有目标文件和库文件出现在命令行中的顺序从左至右依次扫描它们,在此期间它要维护若干个集合:(1)集合E是将被合并到一起组成可执行文件的所有目标文件集合;(2)集合U是未解析符号(unresolved symbols,比如已经被引用但是还未被定义的符号)的集合;(3)集合D是所有之前已被加入到E的目标文件定义的符号集合。一开始,E、U、D都是空的。

(1): 对命令行中的每一个输入文件f,链接器确定它是目标文件还是库文件,如果它是目标文件,就把f加入到E,并把f中未解析的符号和已定义的符号分别加入到U、D集合中,然后处理下一个输入文件。

(2): 如果f是一个库文件,链接器会尝试把U中的所有未解析符号与f中各目标模块定义的符号进行匹配。如果某个目标模块m定义了一个U中的未解析符号,那么就把 m加入到E中,并把m中未解析的符号和已定义的符号分别加入到U、D集合中。不断地对f中的所有目标模块重复这个过程直至到达一个不动点(fixed point),此时U和D不再变化。而那些未加入到E中的f里的目标模块就被简单地丢弃,链接器继续处理下一输入文件。

(3): 如果处理过程中往D加入一个已存在的符号,或者当扫描完所有输入文件时U非空,链接器报错并停止动作。否则,它把E中的所有目标文件合并在一起生成可执行文件。

    VC带的编译器名字叫cl.exe,它有这么几个与标准程序库有关的选项: /ML、/MLd、/MT、/MTd、/MD、/MDd。这些选项告诉编译器应用程序想使用什么版本的C标准程序库。/ML(缺省选项)对应单线程静态版的标准程序库(libc.lib);/MT对应多线程静态版标准库(libcmt.lib),此时编译器会自动定义_MT宏;/MD对应多线程DLL版 (导入库msvcrt.lib,DLL是msvcrt.dll),编译器自动定义_MT和_DLL两个宏。后面加d的选项都会让编译器自动多定义一个 _DEBUG宏,表示要使用对应标准库的调试版,因此/MLd对应调试版单线程静态标准库(libcd.lib),/MTd对应调试版多线程静态标准库 (libcmtd.lib),/MDd对应调试版多线程DLL标准库(导入库msvcrtd.lib,DLL是msvcrtd.dll)。虽然我们的确在编译时明白无误地告诉了编译器应用程序希望使用什么版本的标准库,可是当编译器干完了活,轮到链接器开工时它又如何得知一个个目标文件到底在思念谁?为了传递相思,我们的编译器就干了点秘密的勾当。在cl编译出的目标文件中会有一个专门的区域(关心这个区域到底在文件中什么地方的朋友可以参考COFF和 PE文件格式)存放一些指导链接器如何工作的信息,其中有一种就叫缺省库(default library),这些信息指定了一个或多个库文件名,告诉链接器在扫描的时候也把它们加入到输入文件列表中(当然顺序位于在命令行中被指定的输入文件之后)。说到这里,我们先来做个小实验。写个顶顶简单的程序,然后保存为main.c :

/* main.c */
int main() { return 0; }

用下面这个命令编译main.c(什么?你从不用命令行来编译程序?这个......) :

cl /c main.c

/c 是告诉cl只编译源文件,不用链接。因为/ML是缺省选项,所以上述命令也相当于: cl /c /ML main.c 。如果没什么问题的话(要出了问题才是活见鬼!当然除非你的环境变量没有设置好,这时你应该去VC的bin目录下找到vcvars32.bat文件然后运行它。),当前目录下会出现一个main.obj文件,这就是我们可爱的目标文件。随便用一个文本编辑器打开它(是的,文本编辑器,大胆地去做别害怕),搜索"defaultlib"字符串,通常你就会看到这样的东西: "-defaultlib:LIBC -defaultlib:OLDNAMES"。啊哈,没错,这就
是保存在目标文件中的缺省库信息。我们的目标文件显然指定了两个缺省库,一个是单线程静态版标准库libc.lib(这与/ML选项相符),另外一个是oldnames.lib(它是为了兼容微软以前的C/C++开发系统)。

    VC的链接器是link.exe,因为main.obj保存了缺省库信息,所以可以用

link main.obj libc.lib

或者

link main.obj

来生成可执行文件main.exe,这两个命令是等价的。但是如果你用

link main.obj libcd.lib

的话,链接器会给出一个警告: "warning LNK4098: defaultlib "LIBC" conflicts with use of other libs; use /NODEFAULTLIB:library",因为你显式指定的标准库版本与目标文件的缺省值不一致。通常来说,应该保证链接器合并的所有目标文件指定的缺省标准库版本一致,否则编译器一定会给出上面的警告,而LNK2005和LNK1169链接错误则有时会出现有时不会。那么这个有时到底是什么时候?呵呵,别着急,下面的一切正是为喜欢追根究底的你准备的。

    建一个源文件,就叫mylib.c,内容如下:

/* mylib.c */
#include <stdio.h>

void foo()
{
   printf("%s","I am from mylib!\n");
}

cl /c /MLd mylib.c

命令编译,注意/MLd选项是指定libcd.lib为默认标准库。lib.exe是VC自带的用于将目标文件打包成程序库的命令,所以我们可以用

lib /OUT:my.lib mylib.obj

将mylib.obj打包成库,输出的库文件名是my.lib。接下来把main.c改成:

/* main.c */
void foo();

int main()
{
   foo();
   return 0;
}

cl /c main.c

编译,然后用

link main.obj my.lib

进行链接。这个命令能够成功地生成main.exe而不会产生LNK2005和LNK1169链接错误,你仅仅是得到了一条警告信息:"warning LNK4098: defaultlib "LIBCD" conflicts with use of other libs; use /NODEFAULTLIB:library"。我们根据前文所述的扫描规则来分析一下链接器此时做了些啥。

    一开始E、U、D都是空集,链接器首先扫描到main.obj,把它加入E集合,同时把未解析的foo加入U,把main加入D,而且因为 main.obj的默认标准库是libc.lib,所以它被加入到当前输入文件列表的末尾。接着扫描my.lib,因为这是个库,所以会拿当前U中的所有符号(当然现在就一个foo)与my.lib中的所有目标模块(当然也只有一个mylib.obj)依次匹配,看是否有模块定义了U中的符号。结果 mylib.obj确实定义了foo,于是它被加入到E,foo从U转移到D,mylib.obj引用的printf加入到U,同样地, mylib.obj指定的默认标准库是libcd.lib,它也被加到当前输入文件列表的末尾(在libc.lib的后面)。不断地在my.lib库的各模块上进行迭代以匹配U中的符号,直到U、D都不再变化。很明显,现在就已经到达了这么一个不动点,所以接着扫描下一个输入文件,就是libc.lib。链接器发现libc.lib里的printf.obj里定义有printf,于是printf从U移到D,而printf.obj被加入到E,它定义的所有符号加入到D,它里头的未解析符号加入到U。链接器还会把每个程序都要用到的一些初始化操作所在的目标模块(比如crt0.obj等)及它们所引用的模块(比如malloc.obj、free.obj等)自动加入到E中,并更新U和D以反应这个变化。事实上,标准库各目标模块里的未解析符号都可以在库内其它模块中找到定义,因此当链接器处理完libc.lib时,U一定是空的。最后处理libcd.lib,因为此时U已经为空,所以链接器会抛弃它里面的所有目标模块从而结束扫描,然后合并E中的目标模块并输出可执行文件。

    上文描述了虽然各目标模块指定了不同版本的缺省标准库但仍然链接成功的例子,接下来你将目睹因为这种不严谨而导致的悲惨失败。

    修改mylib.c成这个样子:

#include <crtdbg.h>

void foo()
{
   // just a test , don't care memory leak
   _malloc_dbg( 1, _NORMAL_BLOCK, __FILE__, __LINE__ );
}

其中_malloc_dbg不是ANSI C的标准库函数,它是VC标准库提供的malloc的调试版,与相关函数配套能帮助开发者抓各种内存错误。使用它一定要定义_DEBUG宏,否则预处理器会把它自动转为malloc。继续用

cl /c /MLd mylib.c
lib /OUT:my.lib mylib.obj

编译打包。当再次用

link main.obj my.lib

进行链接时,我们看到了什么?天哪,一堆的LNK2005加上个贵为"fatal error"的LNK1169垫底,当然还少不了那个LNK4098。链接器是不是疯了?不,你冤枉可怜的链接器了,我拍胸脯保证它可是一直在尽心尽责地照章办事。

    一开始E、U、D为空,链接器扫描main.obj,把它加入E,把foo加入U,把main加入D,把libc.lib加入到当前输入文件列表的末尾。接着扫描my.lib,foo从U转移到D,_malloc_dbg加入到U,libcd.lib加到当前输入文件列表的尾部。然后扫描 libc.lib,这时会发现libc.lib里任何一个目标模块都没有定义_malloc_dbg(它只在调试版的标准库中存在),所以不会有任何一个模块因为_malloc_dbg而加入E,但是每个程序都要用到的初始化模块(如crt0.obj等)及它们所引用的模块(比如malloc.obj、 free.obj等)还是会自动加入到E中,同时U和D被更新以反应这个变化。当链接器处理完libc.lib时,U只剩_malloc_dbg这一个符号。最后处理libcd.lib,发现dbgheap.obj定义了_malloc_dbg,于是dbgheap.obj加入到E,它里头的未解析符号加入U,它定义的所有其它符号也加入D,这时灾难便来了。之前malloc等符号已经在D中(随着libc.lib里的malloc.obj加入E而加入的),而dbgheap.obj又定义了包括malloc在内的许多同名符号,这引发了重定义冲突,链接器只好中断工作并报告错误。

    现在我们该知道,链接器完全没有责任,责任在我们自己的身上。是我们粗心地把缺省标准库版本不一致的目标文件(main.obj)与程序库 (my.lib)链接起来,导致了大灾难。解决办法很简单,要么用/MLd选项来重编译main.c;要么用/ML选项重编译mylib.c。

    在上述例子中,我们拥有库my.lib的源代码(mylib.c),所以可以用不同的选项重新编译这些源代码并再次打包。可如果使用的是第三方的库,它并没有提供源代码,那么我们就只有改变自己程序的编译选项来适应这些库了。但是如何知道库中目标模块指定的默认库呢?其实VC提供的一个小工具便可以完成任务,这就是dumpbin.exe。运行下面这个命令

dumpbin /DIRECTIVES my.lib

然后在输出中找那些"Linker Directives"引导的信息,你一定会发现每一处这样的信息都会包含若干个类似"-defaultlib:XXXX"这样的字符串,其中XXXX便代表目标模块指定的缺省库名。

    知道了第三方库指定的默认标准库,再用合适的选项编译我们的应用程序,就可以避免LNK2005和LNK1169链接错误。喜欢IDE的朋友,你一样可以到 "Project属性" -> "C/C++" -> "代码生成(code generation)" -> "运行时库(run-time library)" 项下设置应用程序的默认标准库版本,这与命令行选项的效果是一样的。

Query Internet Service By C++

1、用一文本编辑框用于向用户显示输出结果。

2、增加另一个编辑框,ID标识符为IDC_EDIT2,选中Mutiline和Want return属性。这个编辑框用于接受用户输入网址。

3、在对话框上布置5个按钮,分别用来实现HTTP、FTP、GOPHER、FINGER和WHOIS查询。ID标识符分别为 IDC_BUTTON_HTTP、IDC_BUTTON_FTP、IDC_BUTTON_GOPHER、IDC_BUTTON_FINGER、 IDC_BUTTON_WHOIS。

下面,我们开始给控件附加变量和代码,以完成程序功能。首先,我们来看如何使用HTTP协议进行查询。

在编写实际进行查询的代码前,应给两个编辑框附加变量,以实现输入和输出的目的。我们使用classwizard,给编辑框IDC_EDIT1附加变量m_out,给编辑框IDC_EDIT2附加变量m_host。它们都应为Cstring类型的变量。

下面,我们给对话框类CQueryDlg添加一个函数,它用于实现HTTP查询。函数原形为:

void TryURL(Cstring URL);

其实现代码为:

void CQueryDlg::TryURL(Cstring URL)

{

CInternetSession session;

m_out+="正在链接"+URL+"\r\n";

UpdateData(FALSE);

CInternetFile* file=NULL;

try

{

file=(CInternetFile*)session.OpenURL(URL);

}

catch(CInternetException* pEx)

{

file=NULL;

pEx->Delete();

}

if(file)

{

m_out+="已建立链接。\r\n";

Cstring line;

for(int I=0;I<20&&file->ReadString(line);I++)

{

m_out+=line+"\r\n";

}

file->Close();

delete file;

}

else

{

m_out+="本地址没有发现http主机\r\n";

}

m_out+="------------------------------------------------------\r\n";

UpdateData(FALSE);

 

}

这个函数是如何实现HTTP链接的呢,我们分析如下:

首先,建立一个internet会话,这要定义一个CInternetSession的对象。其构造函数的原形如下:

CInternetSession( LPCTSTR pstrAgent = NULL, DWORD dwContext = 1, DWORD dwAccessType = INTERNET_OPEN_TYPE_PRECONFIG, LPCTSTR pstrProxyName = NULL, LPCTSTR pstrProxyBypass = NULL, DWORD dwFlags = 0 );

这个函数需要定义很多参数,但是,本程序都使用缺省值,即“=”号后的值。CInternetSession构造函数参数说明如下:

LPCTSTR pstrAgent-应用程序名,如果为NULL,它将替你填入你在AppWizard中给定的程序名。

DWORD dwContext-本操作的设备关联符定义。

DWORD dwAccessType-访问类型,为以下参数之一,INTERNET_OPEN_TYPE_PRECONFIG (default), INTERNET_OPEN_TYPE_DIRECT, 或 INTERNET_OPEN_TYPE_PROXY

LPCTSTR pstrProxyName-如访问类型为INTERNET_OPEN_TYPE_PROXY,则给该参数赋予协议名称。

LPCTSTR pstrProxyBypass-如访问类型为INTERNET_OPEN_TYPE_PROXY,则该参数为不通过协议服务器而直接链接的一系列地址。

DWORD dwFlags-可为以下参数,INTERNET_FLAG_DON'T_CACHE, INTERNET_FLAG_ASYNC, 和INTERNET_FLAG_OFFLINE.

dwAccessType值缺省时将使用系统注册簿定义的值。显然,程序允许使用者定义访问类型将比由程序内部直接定义要好。因此,要正确使用本程序,必需先在windows系统中定义好网络访问类型,步骤如下:

1、双击桌面上“my computer"图标。

2、点击“contyol panel".

3、点击“internet ”。

4、在随后弹出的对话框中,选“connection"栏,然后填写网络连接属性。如果你是拨号上网,选中“dial”选择项,并填写相关属性。如果你是通过proxy服务器上网,选中“proxy”选项,点击“setting”按钮,设置proxy服务器地址和端口号。如果你是直接连入 internet,应使所有的选项均为非选中状态。

本程序在构造CInternetSession对象时使用缺省值,因此,构造函数将不带任何参数。如下所示:

CInternetSession session;

在构造对象session后,我们需写两行程序作一些输出,表示程序已开始工作。

m_out+="正在链接"+URL+"\r\n";

UpdateData(FALSE);

接下来我们使用session对象的成员函数OpenURL()来打开一个URL资源。该函数返回一个文件的指针,文件类型为以下四种之一:

file:// 如果访问的是本地机器,函数返回一个CStudioFile类对象的指针。

ftp:// 如果访问的是一ftp地址,函数返回一个CInternetFile类对象的指针。

gopher:// 如果访问的是一gopher地址,函数返回一个CGopherFile类对象的指针。

http:// 如果访问的是一http地址,函数返回一个CHttpFile类对象的指针。

本程序用于访问远程机器,因此,函数不会返回一个file://类型的本地文件。而CGopherFile和CHttpFile均派生自 CInternetFile,所以将该函数返回值赋给CInternetFile类的指针是安全的。当OpenURL()不能正常打开URL资源时,该函数将会抛出一个异常(exception),从而导致程序运行时错误。由于本程序用于探查未知的网址,该网址可能不提供相应服务或根本不存在,因此 OpenURL()有时可能不会正常运行。为了避免程序异常终止,我们需使用try-catch结构来处理异常。本段程序代码如下:

CInternetFile* file=NULL;

try

{

file=(CInternetFile*)session.OpenURL(URL);

}

catch(CInternetException* pEx)

{

file=NULL;//如果发生运行时错误,给file赋空值,程序将继续运行

pEx->Delete();

}

通过以上代码,程序将使用用户指定的地址来试图打开Http网址,如果失败,返回的文件为空,程序将继续运行,尝试用其它协议打开网址。随后,无论网址是否成功打开,我们都应作相应输出以提示用户。如果成功,我们用一个for循环读出返回文件的头20句并输出,如果失败,也要给出相应的提示。程序如下:

if(file) //判定链接是否成功

{

m_out+="已建立链接。\r\n";

Cstring line;

for(int I=0;I<20&&file->ReadString(line);I++)

{

m_out+=line+"\r\n";

}

file->Close();

delete file;

}

else

{

m_out+="本地址没有发现http主机\r\n";

}

m_out+="------------------------------------------------------\r\n";

UpdateData(FALSE);

用于查询的函数编写完后,我们还要给按钮的单击事件增加代码,以便按钮按下时,程序开始查询。给按钮IDC_BUTTON_HTTP按钮增加BN_CLICKEDvoid 消息的代码如下:CQueryDlg::OnQueryHttp()

{

const Cstring http = "http://";

UpdateData(TRUE);

m_out = "";

UpdateData(FALSE);

TryURL(http + m_host);

TryURL(http + "www." + m_host);

}

这段代码中,UpdateData(TRUE)的调用将给m_host赋予用户定义的值。UpdateData(FALSE)语句将清空输出编辑框变量 m_out的内容。接下来调用两次TryURL(),例如说,当用户输入yahoo.com,程序将首先试一试http://yahoo.com,然后试一试http://www.yahoo.com。好了,我们现在可以编译程序并执行,输入yahoo.com,程序将连接到internet,并输出 yahoo主页的头20行。

实现FTP链接

下面,我们来探讨如何实现FTP链接。首先给对话框类增加一个实现链接的函数TryFTP:

void CQueryDlg::TryFTP(Cstring host)

{

CInternetSession session;

m_out += "正在链接FTP地址 " + host + "\r\n";

UpdateData(FALSE);

CFtpConnection* connection = NULL;

try

{

connection = session.GetFtpConnection(host);

}

catch (CInternetException* pEx)

{

connection = NULL;

pEx->Delete();

}

if (connection)

{

m_out += "已建立链接。 \r\n";

Cstring line;

connection->GetCurrentDirectory(line);

m_out += "缺省目录为 " + line + "\r\n";

connection->Close();

delete connection;

}

else

{

m_out += "本地址没有发现ftp主机 。\r\n";

 

}

m_out += "------------------------------------------------------\r\n";

UpdateData(FALSE);

}

本函数和TryURL()很相似,不过,它不使用OpenURL()来打开一个文件,而是用GetFtpConnection()来与ftp服务器建立链接。如果链接成功,将使用GetCurrentDirectory()来得到服务器的缺省目录。

大多数ftp地址前有ftp.的前缀,但一些老的地址没有此前缀。现在,给IDC_BUTTON_FTP链接单击消息的处理函数,并编写如下:

void CQueryDlg::OnButtonFtp()

{

UpdateData(TRUE);

m_out = "";

UpdateData(FALSE);

TryFTP(m_host);

TryFTP("ftp." + m_host);

}

现在我们已完成了FPT连接的实现代码。重新编译程序并运行,键入ftp.microsoft.com。可以发现,在ftp.microsoft.com 处提供ftp服务。但是你必须有一点耐性,因为在开始查询到出结果将有较长一段时间,因为程序等结果全部探查到后再一次显示,但实际上现在的商业 internet程序都是实时显示的。如果我们希望结果实时地显示,必需使用异步套接字(asynchronous sockets)或多线程编程。

实现Gopher查询

GOPHER是一种基于文本的协议,它和WWW相似,可以通过点击文字内容,实现网络内的链接和浏览。但是,GOPHER是通过逐级文字菜单来组织链接和内容的,它不象WWW那样有丰富的多媒体页面。要实现GOPHER查询,我们应给CQueryDlg类增加另一个成员函数void TryGopher (Cstring host)。如果查询成功,该函数将返回查询地址的第一个Gopher位置(locator)。位置是gopher协议的概念,任何 gopher客户程序必需先得到一个gopher位置,然后才能进行相应的gopher操作。TryGopher()函数如下:

void CQueryDlg::TryGopher(Cstring host)

{

CInternetSession session;

m_out += "正在链接gopher地址 " + host + "\r\n";

UpdateData(FALSE);

CGopherConnection* connection = NULL;

try

{

connection = session.GetGopherConnection(host);

}

catch (CInternetException* pEx)

{

connection = NULL;

pEx->Delete();

}

if (connection)

{

m_out += "已建立链接。 \r\n";

Cstring line;

CGopherLocator locator = connection->CreateLocator(NULL, NULL, GOPHER_TYPE_DIRECTORY);

line = locator;

m_out += "第一个Gopher位置是" + line + "\r\n";

connection->Close();

delete connection;

}

else

{

m_out += "本地址没有发现gopher主机 。 \r\n";

}

m_out += "------------------------------------------------------\r\n";

UpdateData(FALSE);

}

本函数和前两个函数大致相似,在通过调用connection = session.GetGopher Connection(host);来建立 Gopher链接后,通过语句CGopherLocator locator = connection-> CreateLocator (NULL, NULL, GOPHER_TYPE_DIRECTORY);来建立一个Gopher位置(locator),函数 CreateLocator()有多个版本,现在我们使用的是其含三个参数的版本。其原形为CGopherLocator CreateLocator ( LPCTSTR pstrDisplayString, LPCTSTR pstrSelectorString, DWORD dwGopherType );。其中参数pstrDisplayString指明要查询的服务器上的具体文件或目录名,如果它为NULL,则返回服务器缺省目录名;参数 pstrSelectorString是发送给服务器的字符命令,以便检索某个项目,它可设为空;参数dwGopherType指明Gopher访问类型,本例中定义为GOPHER_TYPE_DIRECTORY,指明要访问的是目录。它的其它可取值请参考VC++5.0文档。Gopher位置 (locator)建立后,我们把它强制转换为Cstring类型,并把该位置显示出来。

现在,给IDC_BUTTON_GOPHER链接单击消息的处理函数,并编写如下:

void CQueryDlg::OnButtonGopher()

{

UpdateData(TRUE);

m_out = "";

UpdateData(FALSE);

TryGopher(m_host);

TryGopher("gopher." + m_host); // TODO: Add your control notification handler code here

 

}

重新编译程序,输入地址harvard.edu,程序将会探查出它是一个Gopher地址,并显示出第一个Gopher位置。

如何建立FINGER查询

Finger协议的作用是给你提供一个网址的具体情况,它是Internet上最古老的协议之一。在一个Finger服务器上,你可以查询它的某一个用户或整个网址的情况。当然,这对网络的安全是不利的,实际上,有经验的黑客们在攻击一个未知网络时,第一步就是向它发送Finger和Whois查询,这也是黑客网址上的黑客教程中建议的步骤。为了安全,许多网络服务器不提供Finger服务,然而,当它接受到Finger查询请求时,仍然会返回一些其它的有用信息。

在MFC和WIN32 API中,没有提供直接实现Finger查询的函数,但是,我们仍有变通的办法来实现它。所有的internet链接都需要一个宿主名和端口号,所有的著名的服务都有其特定的端口号,例如:http服务使用远程宿主机上的端口80,ftp服务使用端口21,gopher服务使用端口 70。对于finger服务来说,它使用端口79。finger是一种简单的协议,如果你向远程宿主机的端口79发送字符串,finger服务器在端口 79侦听到后,将会发送出一个finger回答。如果你发送的字符串仅仅包含\r\n,服务器通常将会把本服务器上所有用户的列表及相关信息(如用户真实姓名等)做为应答返回。因此,如果我们不使用缺省的端口70,而是使用端口79来建立gopher链接,我们就能发出finger查询。给 CQueryDlg类增加一个成员函数void TryFinger(Cstring host)如下:

void CQueryDlg::TryFinger(Cstring host)

{

CInternetSession session;

m_out += "正在链接finger地址 " + host + "\r\n";

UpdateData(FALSE);

CGopherConnection* connecti

on = NULL;

try

{

connection = session.GetGopherConnection(host,NULL,NULL,79);

}

catch (CInternetException* pEx)

{

connection = NULL;

pEx->Delete();

}

if (connection)

{

m_out += "已建立链接。 \r\n";

CGopherLocator locator = connection->CreateLocator(NULL, NULL, GOPHER_TYPE_TEXT_FILE);

CGopherFile* file =NULL;

try

{

file = connection->OpenFile(locator);

}

catch (CInternetException* pEx)

{

file = NULL;

pEx->Delete();

}

if (file)

{

Cstring line;

for (int I=0; I < 20 && file->ReadString(line); I++)

{

m_out += line + "\r\n";

}

file->Close();

delete file;

}

else

{

m_out+="finger查询失败。\r\n";

}

connection->Close();

delete connection;

}

else

{

m_out += "本地址没有发现finger主机 。 \r\n";

}

m_out += "------------------------------------------------------\r\n";

UpdateData(FALSE);

}

本函数中,语句connection = session.GetGopherConnection(host,NULL,NULL,79);用于建立 finger链接。随后,我们创立一个文本文件类型的Gopher位置用来操作服务器返回的信息:CGopherLocator locator =  connection->CreateLocator(NULL, NULL, GOPHER_TYPE_TEXT_FILE);。使用该 Gopher位置打开文件并使用一个for循环来读出该文件的头20行,随后将它显示出来。

现在,给IDC_BUTTON_FINGER按钮链接单击消息的处理函数,并编写如下:

void CQueryDlg::OnButtonFinger()

{

UpdateData(TRUE);

m_out = "";

UpdateData(FALSE);

TryFinger(m_host);

// TODO: Add your control notification handler code here

}

编译程序,输入地址whitehouse.gov,程序将会返回该服务器的e-mail地址,从返回的信息可知,出于安全考虑,该服务器的其它 finger服务已被取消了。如果你输入的网址没有提供finger服务,程序将有较长一段时间没有反应,最后弹出一个消息框,通知用户链接出现超时错误,单击ok按钮即可。

实现WHOIS查询

还有一个协议也能提供网址的相关信息,它也是一种古老的协议,MFC并不直接支持它,这就是whois协议。在整个internet上,只有少数服务器提供whois服务。whois服务建立了internet上的域名数据库,如果对某个域名进行whois查询,服务器将会返回拥有该域的机构或个人的实际姓名、地址、电话号码等信息。国际上的域名注册机构拥有whois服务器,例如,域名结尾为.com的域都在一个称为InterNIC的机构中注册,该机构拥有一个whois服务器称为rs.internic.net。whois协议和finger协议一样,是一种简单的协议,它使用端口43,如果向 whois服务器的端口43发送包含域名的字符串,则whois服务器将会返回该域拥有者的情况。给CQueryDlg类增加一个成员函数 void TryWhois(Cstring host)如下:

void CQueryDlg::TryWhois(Cstring host)

{

CInternetSession session;

m_out += "正在链接Whois地址 " + host + "\r\n";

UpdateData(FALSE);

CGopherConnection* connection = NULL;

try

{

connection = session.GetGopherConnection("rs.internic.net",NULL,NULL,43);

}

catch (CInternetException* pEx)

{

connection = NULL;

pEx->Delete();

}

if (connection)

{

m_out += "已建立链接。 \r\n";

CGopherLocator locator = connection->CreateLocator(NULL, host, GOPHER_TYPE_TEXT_FILE);

CGopherFile* file = NULL;

try

{

file = connection->OpenFile(locator);

}

catch (CInternetException* pEx)

{

file = NULL;

pEx->Delete();

}

if (file)

{

Cstring line;

for (int I=0; I < 20 && file->ReadString(line); I++)

{

m_out += line + "\r\n";

}

file->Close();

delete file;

}

else

{

m_out+="Whois查询失败。\r\n";

}

connection->Close();

delete connection;

}

else

{

m_out += "Whois查询失败。\r\n";

}

m_out += "------------------------------------------------------\r\n";

UpdateData(FALSE);

}

在本函数中,语句connection = session.GetGopherConnection("rs.internic.net",NULL,  NULL,43);使程序链接到了whois服务器rs.internic.net。随后,我们建立一个gopher位置来查询用户输入的域: CGopherLocator locator = connection->CreateLocator(NULL, host, GOPHER_TYPE_TEXT_FILE); 由于链接的域为rs.internic.net,所以,本函数只能查询结尾为.com的域。读者可以对本段程序进行扩充,链接到其它相关whois服务器,以查询其它类型的域。

现在,给IDC_BUTTON_WHOIS按钮链接单击消息的处理函数,并编写如下:

void CQueryDlg::OnButtonWhois()

{

UpdateData(TRUE);

m_out = "";

UpdateData(FALSE);

TryWhois(m_host);// TODO: Add your control notification handler code here

 

}

重新编译程序,现在,我们可以对结尾为.com的域进行whois查询了。

至此,本程序已全部结束。当然,有兴趣的读者可以对它进一步扩充,以完成更多的功能。可以对WinInet类进行一些小小的扩充,以便能通过特定的端口访问e-mail和news服务。此外,也可以链接到一些著名的网络搜索引擎上并提交查询,使程序具有搜索功能。这一切,就取决于你的创造力了。

Post Request to HTML

利用vc向html文件提交表单

 
Project --> settings --> link (Object/library modules) 加入WININET.LIB



#include <afxinet.h>



CString strHeaders =_T("Content-Type: application/x-www-form-urlencoded");

// URL-encoded form variables -

CString strFormData = _T("Content=1&FatherID=4868");



CInternetSession session;

CHttpConnection* pConnection = session.GetHttpConnection(_T("www.abc.com"));

CHttpFile* pFile =

pConnection->OpenRequest(CHttpConnection::HTTP_VERB_POST,_T("/abc/abc.asp"));

BOOL result = pFile->SendRequest(strHeaders,(LPVOID)(LPCTSTR)strFormData, strFormData.GetLength());

 
//我给一个简单一点的(MFC):

#include "afxinet.h"

void SubmitWebFormData(void)
{
CString strFormData;
CString strHeaders;
CInternetSession session;
CHttpConnection *pConnection;
CHttpFile *pFile;
BOOL result;

//假设name的值是"YourName"
strFormData=_T("name=YourName")
strHeaders=_T("Content-Type: application/x-www-form-urlencoded");

try
{
//假设连接到http://127.0.0.1
pConnection=session.GetHttpConnection(_T("127.0.0.1"));
}
catch(CInternetException *pEx)
{
//异常处理......
}
try
{
//假设响应表单的页面文件叫FileName.asp
pFile=pConnection->OpenRequest(CHttpConnection::HTTP_VERB_POST,
_T("/FileName.asp"));
}
catch(CInternetException *pEx)
{
//异常处理......
}
try
{
result = pFile->SendRequest(strHeaders,(LPVOID)(LPCTSTR)strFormData,
strFormData.GetLength());
}
catch(CInternetException *pEx)
{
//异常处理......
}
//善后处理......
}
//////////////////////////////////////////////

Get Method

//////////////////////////////////////////////


CString address_str;

m_address.GetWindowText(address_str);

if(address_str.GetLength()==0)

{

MessageBox(_T("地址栏没有输入"));

m_address.GetFocus();

return;

}
 CString result_str;

CInternetSession mySession;

CHttpResponse res;

res.

CString myData;

CHttpFile* myHttpFile=(CHttpFile*)mySession.OpenURL(address_str);

for(int i=0;i<200&&myHttpFile->ReadString(myData);i++)

{

result_str+=myData+"\r\n"; //因为每次只能读取一行,所以要循环读取数据

}

myHttpFile->Close();

delete [] myHttpFile;

mySession.Close();

m_result.SetWindowText(result_str);
posted @ 2011-01-27 02:56  alex618  阅读(7986)  评论(0编辑  收藏  举报