VC中為普通程序添加ODBC應用

摘要:本文通過用VC++6.0實現一個程序實例來介紹如何在一個普通的無數據庫關聯的應用程序中添加ODBC應用,使應用程序能在原有的功能基礎上增加對數據庫的操作。

  關鍵字:VC++ 、ODBC、數據庫

  一、 引言

  數據庫屬于最流行的應用程序之一,幾乎每個商業部門都使用數據庫來記錄、管理各種各樣的數據。在VC下我們可以在創建工程時選擇數據庫支持,并選定數據源和相關的表,并選擇CRecordView作為我們這個程序的基類,這樣做可以毫不費力的將應用程序和數據庫建立了關聯,而幾乎不用編什么代碼,但這樣做的前提是在新建工程時已明確知道用到哪個數據庫,并且有相關的數據庫。事實上我們往往有許多已做好的應用程序和類,其功能除了未和數據源建立關聯外以基本滿足要求,我們只要在其基礎上添加ODBC接口,使之與數據庫建立關聯即可,這樣做避免了與數據庫無關部分代碼的重新編寫所造成的重復性操作,大大提高了代碼的重用性和利用率。所以在普通程序上通過添加ODBC應用而與數據庫建立關聯的方法是完全行之有效的。

  二、 ODBC技術

  ODBC(Open Database Conectivity 開放式數據庫互聯)技術,作為Microsoft公司對數據庫進行訪問的標準應用程序接口(API)和Windows開放式服務體系結構OSA的一個重要組成部分已廣為眾多的Windows程序員所熟悉、認可。ODBC的工作依賴于數據庫制造商提供的驅動程序,使用ODBC API的時候,Windows的ODBC管理程序,把數據庫訪問的請求傳遞給正確的驅動程序,驅動程序再使用SQL語句指示DBMS完成數據庫訪問工作,因此,ODBC的存在為我們開發應用數據庫程序提供了非常強大的能力和靈活性。

  三、 程序示例

  (一)打開Visual C++,在"File"菜單上點選"New…",然后在彈出的"New"對話框中選定"MFC AppWizard(exe)"類的項目,"Project name"為Normal,按下OK鍵,下一Step 1屏幕中選"Single document"單文檔支持,用到后面的選項除在最后一步選擇"CFormView"作為本工程視類的基類外均為確省值,此時即可按下Finish鍵,結果系統將生成一個新的項目Normal。

  我們就將此工程當做原有的工程,接下來我們便在此工程基礎上對其添加ODBC應用,使該工程能同數據源建立關聯,能對數據庫中的數據進行操作和管理。

  (二)打開"控制面板"上的"ODBC (32bits)",對數據源進行注冊。為了使ODBC能與數據庫一起工作,就必須把數據庫注冊到ODBC驅動程序管理器,這項工作可以通過定義一個DSN或數據源名字來完成。在彈出的"ODBC數據源管理器"中選擇"User DSN"屬性頁,點擊"Add…"按鈕。選擇"Microsoft Access Driver(*.mdb)"作為數據源的驅動器,點擊"完成"按鈕。在彈出的"ODBC Microsoft Access 97 Setup"對話框中在"Data Source Name:"欄添入RP97,"Description:"欄只起注釋說明的作用,可以不填,然后點擊"Select…"按鈕,選擇所要注冊的數據源,然后點擊"OK"就完成了對數據源的注冊,到這一步,本機上的任意程序只要通過ODBC接口和數據源名"RP97"就能完成對數據庫的訪問了。

  (三)在VC的"Workspace"活動窗口中選擇"FileView"屬性頁,打開標準框架頭文件"StdAfx.h",并在最后一個#include后面添加對"afxdb.h"的引用:#include

  (四) 在"Workspace"活動窗口中選擇"ClassView"屬性頁,在"Normal Classes"上右鍵,選"New Class…",在彈出的"New Class"對話框的"Base Class"欄選擇"CRecordSet"做為新添加的類的基類,在"Name"欄填寫類名"CODBCSet",點擊"OK",在隨后彈出的對話框的"ODBC"欄選擇剛注冊的"RP97"數據源,點擊"OK"后選擇該數據庫的一個表,點擊"OK"在"ClassView"里就多了一個以CRecordSet為基類的新類"CODBCSet"。下面三個函數完成了數據庫各級元素的綁定工作:

CString CODBCSet::GetDefaultConnect()
{
 return _T("ODBC;DSN=RP97");
}

CString CODBCSet::GetDefaultSQL()
{
 return _T("[單據表]");
}

void CODBCSet::DoFieldExchange(CFieldExchange* pFX)
{
 //{{AFX_FIELD_MAP(CODBCSet)
 pFX->SetFieldType(CFieldExchange::outputColumn);
 RFX_Text(pFX, _T("[單據ID]"), m_column1);
 RFX_Text(pFX, _T("[單據名稱]"), m_column2);
 RFX_Text(pFX, _T("[報帳人]"), m_column3);
 //}}AFX_FIELD_MAP
}

  (五)按同樣的方法再添加一個基于"generic CWnd"的新類"CConnectDB"。在該類的源文件里添加對"ODBCSet.h"的引用:#include "ODBCSet.h"。在該類的頭文件的"class CconnectDB"前添加class CODBCSet;并在該類里添加公有型成員變量和函數:

CDatabase m_dbData;
CODBCSet* m_pSet;
void CConnectDB::Initial()
{
 //打開數據源RP97
 CString os=_T("odbc; dsn=RP97");
 m_dbData.Open(NULL,FALSE,FALSE,0);
 m_pSet=new CODBCSet(&m_dbData);
 //通過SQL結構化查詢語言打開RP97里的單據表
 CString sql="SELECT * FROM 單據表";
 m_pSet->Open(AFX_DB_USE_DEFAULT_TYPE,sql);
}


  (六)在Form上添加一個"測試"按鈕及其響應函數OnTest():

void CNormalView::OnTest()
{
 CConnectDB connectDB;
 //執行完Initial()后m_pSet指針才不為空,方可安全使用。
 connectDB.Initial();
 if(connectDB.m_pSet==NULL)
  return;
 connectDB.m_pSet->MoveFirst();
 CString str=connectDB.m_pSet->m_column3;
 AfxMessageBox(str);
}

  最后在該文件開始處添加兩個引用:

#include "ConnectDB.h"
#include "ODBCSet.h"

  四、 運行與測試

  編譯運行程序,點擊"測試"按鈕,就會將"RP97"數據庫的"單據表"的第一條記錄的"報帳人"字段所在的內容通過對話框彈出來。

  小結:

  本程序的關鍵在于對數據庫指針m_pSet的獲取,當類CConnectDB 的成員函數Initial()被執行完時,m_pSet就已被獲取到了,而在此之前該指針是空的,是不能使用的,所以在實際應用中必須保證在使用m_pSet之前調用過函數Initial()。當m_pSet被獲取到之后,就可以想其他ODBC應用程序一樣使用CrecordSet類里的各種函數對數據庫進行各種需要的操作和管理了。
--------------------------------------------------------------------------------------------------------------------

CDatabase\CRecordSet\CRecordView

?

10.4 CDatabase

  要建立與數據源的連接,首先應構造一個CDatabase對象,然后再調用CDatabase的Open成員函數.Open函數負責建立連接,其聲明為

virtual BOOL Open( LPCTSTR lpszDSN, BOOL bExclusive = FALSE, BOOL bReadOnly = FALSE, LPCTSTR lpszConnect = “ODBC;”, BOOL bUseCursorLib = TRUE ); throw( CDBException, CMemoryException );

 

  參數lpszDSN指定了數據源名(構造數據源的方法將在后面介紹),在lpszConnect參數中也可包括數據源名,此時lpszDSN必需為NULL,若在函數中未提供數據源名且使lpszDSN為NULL,則會顯示一個數據源對話框,用戶可以在該對話框中選擇一個數據源.參數bExclusive說明是否獨占數據源,由于目前版本的類庫還不支持獨占方式,故該參數的值應該是FALSE,這說明數據源是被共享的.參數bReadOnly若為TRUE則對數據源的連接是只讀的.參數lpszConnect指定了一個連接字符串,連接字符串中可以包括數據源名、用戶帳號(ID)和口令等信息,字符串中的"ODBC"表示要連接到一個ODBC數據源上.參數bUseCursorLib若為TRUE,則會裝載光標庫,否則不裝載,快照需要光標庫,動態集不需要光標庫. 若連接成功,函數返回TRUE,若返回FALSE,則說明用戶在數據源對話框中按了Cancel按鈕。若函數內部出現錯誤,則框架會產生一個異常。

  下面是一些調用Open函數的例子。

CDatabase m_db; //在文檔類中嵌入一個CDatabase對象

//連接到一個名為"Student Registration"的數據源

m_db.Open("Student Registration");

//在連接數據源的同時指定了用戶帳號和口令

m_db.Open(NULL,FALSE,FALSE,"ODBC;DSN=Student Registration;UID=ZYF;PWD=1234");

m_db.Open(NULL); //將彈出一個數據源對話框

 

  要從一個數據源中脫離,可調用函數Close。在脫離后,可以再次調用Open函數來建立一個新的連接.調用IsOpen可判斷當前是否有一個連接,調用GetConnect可返回當前的連接字符串。函數的聲明為

virtual void Close( );

BOOL IsOpen( ) const; //返回TRUE則表明當前有一個連接

const CString& GetConnect( ) const;

  CDatabase的析構函數會調用Close,所以只要刪除了CDatabase對象就可以與數據源脫離。


10.5 CRecordset

  CRecordset類代表一個記錄集.該類是MFC的ODBC類中最重要、功能最強大的類。

10.5.1 動態集、快照、光標和光標庫

  在多任務操作系統或網絡環境中,多個用戶可以共享同一個數據源。共享數據的一個主要問題是如何協調各個用戶對數據源的修改。例如,當某一個應用改變了數據源中的記錄時,別的連接至該數據源的應用應該如何處理。對于這個問題,基于MFC的ODBC應用程序可以采取幾種不同的處理辦法,這將由程序采用哪種記錄集決定。

  記錄集主要分為快照(Snapshot) 和動態集(Dynaset)兩種,CRecordset類對這兩者都支持。這兩種記錄集的不同表現在它們對別的應用改變數據源記錄采取了不同的處理方法。

  快照型記錄集提供了對數據的靜態視.快照是個很形象的術語,就好象對數據源的某些記錄照了一張照片一樣.當別的用戶改變了記錄時(包括修改、添加和刪除),快照中的記錄不受影響,也就是說,快照不反映別的用戶對數據源記錄的改變.直到調用了CRecordset::Requery重新查詢后,快照才會反映變化.對于象產生報告或執行計算這樣的不希望中途變動的工作,快照是很有用的。需要指出的是,快照的這種靜態特性是相對于別的用戶而言的,它會正確反映由本身用戶對記錄的修改和刪除,但對于新添加的記錄直到調用Requery后才能反映到快照中.

  動態集提供了數據的動態視.當別的用戶修改或刪除了記錄集中的記錄時,會在動態集中反映出來:當滾動到修改過的記錄時對其所作的修改會立即反映到動態集中,當記錄被刪除時,MFC代碼會跳過記錄集中的刪除部分.對于其它用戶添加的記錄,直到調用Requery時,才會在動態集中反映出來。本身應用程序對記錄的修改、添加和刪除會反映在動態集中。當數據必須是動態的時侯,使用動態集是最適合的。例如,在一個火車票聯網售票系統中,顯然應該用動態集隨時反映出共享數據的變化。

  在記錄集中滾動,需要有一個標志來指明滾動后的位置(當前位置)。ODBC驅動程序會維護一個光標,用來跟蹤記錄集的當前記錄,可以把光標理解成跟蹤記錄集位置的一種機制。

  光標庫(Cursor Library)是處于ODBC驅動程序管理器和驅動程序之間的動態鏈接庫(ODBCCR32.DLL).光標庫的主要功能是支持快照以及為底層驅動程序提供雙向滾動能力,高層次的驅動程序不需要光標庫,因為它們是可滾動的.光標庫管理快照記錄的緩沖區,該緩沖區反映本程序對記錄的修改和刪除,但不反映其它用戶對記錄的改變,由此可見,快照實際上相當于當前的光標庫緩沖區.

  應注意的是,快照是一種靜態光標(Static Cursor).靜態光標直到滾動到某個記錄才能取得該記錄的數據.因此,要保證所有的記錄都被快照,可以先滾動到記錄集的末尾,然后再滾動到感興趣的第一個記錄上.這樣做的缺點是滾動到末尾需要額外的開銷,會降低性能.

  與快照不同,動態集不用光標庫維持的緩沖區來存放記錄.實際上,動態集是不使用光標庫的,因為光標庫會屏蔽掉一些支持動態集的底層驅動程序功能.動態集是一種鍵集驅動光標(Keyset-Driven Cursor),當打開一個動態集時,驅動程序保存記錄集中每個記錄的鍵.只要光標在動態集中滾動,驅動程序就會通過鍵來從數據源中檢取當前記錄,從而保證選取的記錄與數據源同步.

  從上面的分析中可以看出,快照和動態集有一個共同的特點,那就是在建立記錄集后,記錄集中的成員就已經確定了.這就是為什么兩種記錄集都不能反映別的用戶添加記錄的原因.

10.5.2 域數據成員與數據交換

  CRecordset類代表一個記錄集.用戶一般需要用ClassWizard創建一個CRecordset的派生類.ClassWizard可以為派生的記錄集類創建一批數據成員,這些數據成員與記錄的各字段相對應,被稱為字段數據成員或域數據成員.例如,對于表10.2所示的將在后面例子中使用的數據庫表,ClassWizard會在派生類中加入6個域數據成員,如清單10.1所示.可以看出域數據成員與表中的字段名字類似,且類型匹配.

 

表10.2 stdreg32.mdb中的Section表

CourseID

(Text)

SectionNo

(Text)

InstructorID

(Text)

RoomNo

(Text)

Schedule

(Text)

Capacity

(int)

MATH101

1

KLAUSENJ

KEN-12

MWF10-11

40

MATH101

2

ROGERSN

WIL-1088

TTH3:30-5

15

MATH201

1

ROGERSN

WIL-1034

MWF2-3

20

MATH201

2

SMITHJ

WIL-1054

MWF3-4

25

MATH202

1

KLA

WIL-1054

MWF9-10

20

MATH202

2

ROGERSN

KEN-12

TTH9:30-11

15

MATH202

3

KLAUSENJ

WIL-2033

TTH3-4:30

15

清單10.1 派生類中的域數據成員

class CSectionSet : public CRecordset

{

public:

. . . . . .

//{{AFX_FIELD(CSectionSet, CRecordset)

CString m_CourseID;

CString m_SectionNo;

CString m_InstructorID;

CString m_RoomNo;

CString m_Schedule;

int m_Capacity;

//}}AFX_FIELD

. . . . . .

};

 

  域數據成員用來保存某條記錄的各個字段,它們是程序與記錄之間的緩沖區.域數據成員代表當前記錄,當在記錄集中滾動到某一記錄時,框架自動地把記錄的各個字段拷貝到記錄集對象的域數據成員中.當用戶要修改當前記錄或增加新記錄時,程序先將各字段的新值放入域數據成員中,然后調用相應的CRecordset成員函數把域數據成員設置到數據源中.

  不難看出,在記錄集與數據源之間有一個數據交換問題.CRecordset類使用"記錄域交換"(Record Field Exchange,縮寫為RFX)機制自動地在域數據成員和數據源之間交換數據.RFX機制與對話數據交換(DDX)類似.CRecordset的成員函數DoFieldExchange負責數據交換任務,在該函數中調用了一系列RFX函數.當用戶用ClassWizard加入域數據成員時,ClassWizard會自動在DoFieldExchange中建立RFX.典型DoFieldExchange如清單10.2所示:

清單10.2 典型的DoFieldExchange函數

void CSectionSet::DoFieldExchange(CFieldExchange* pFX)

{

//{{AFX_FIELD_MAP(CSectionSet)

pFX->SetFieldType(CFieldExchange::outputColumn);

RFX_Text(pFX, _T("[CourseID]"), m_CourseID);

RFX_Text(pFX, _T("[SectionNo]"), m_SectionNo);

RFX_Text(pFX, _T("[InstructorID]"), m_InstructorID);

RFX_Text(pFX, _T("[RoomNo]"), m_RoomNo);

RFX_Text(pFX, _T("[Schedule]"), m_Schedule);

RFX_Int(pFX, _T("[Capacity]"), m_Capacity);

//}}AFX_FIELD_MAP

}

 

 

10.5.3 SQL查詢

  記錄集的建立實際上主要是一個查詢過程,SQL的SELECT語句用來查詢數據源.在建立記錄集時,CRecordset會根據一些參數構造一個SELECT語句來查詢數據源,并用查詢的結果創建記錄集.明白這一點對理解CRecordset至關重要.SELECT語句的句法如下:

SELECT rfx-field-list FROM table-name [WHERE m_strFilter]

[ORDER BY m_strSort]

  其中table-name是表名,rfx-field-list是選擇的列(字段).WHERE和ORDER BY是兩個子句,分別用來過濾和排序。下面是SELECT語句的一些例子:

SELECT CourseID, InstructorID FROM Section

SELECT * FROM Section WHERE CourseID=‘MATH202’ AND Capacity=15

SELECT InstructorID FROM Section ORDER BY CourseID ASC

  其中第一個語句從Section表中選擇CourseID和InstructorID字段.第二個語句從Section表中選擇CourseID為MATH202且Capacity等于15的記錄,在該語句中使用了象"AND"或"OR"這樣的邏輯連接符.要注意在SQL語句中引用字符串、日期或時間等類型的數據時要用單引號括起來,而數值型數據則不用.第三個語句從Section表中選擇InstructorID列并且按CourseID的升序排列,若要降序排列,可使用關鍵字DESC.

提示:如果列名或表名中包含有空格,則必需用方括號把該名稱包起來。例如,如果有一列名為“Client Name”,則應該寫成“[Client Name]”。

 

10.5.4 記錄集的建立和關閉

  要建立記錄集,首先要構造一個CRecordset派生類對象,然后調用Open成員函數查詢數據源中的記錄并建立記錄集.在Open函數中,可能會調用GetDefaultConnect和GetDefaultSQL函數.函數的聲明為

CRecordset( CDatabase* pDatabase = NULL);
參數pDatabase指向一個CDatabase對象,用來獲取數據源.如果pDatabase為NULL,則會在Open函數中自動構建一個CDatabase對象.如果CDatabase對象還未與數據源連接,那么在Open函數中會建立連接,連接字符串(參見10.3.1)由成員函數GetDefaultConnect提供.

virtual CString GetDefaultConnect( );
該函數返回缺省的連接字符串.Open函數在必要的時侯會調用該函數獲取連接字符串以建立與數據源的連接.一般需要在CRecordset派生類中覆蓋該函數并在新版的函數中提供連接字符串.

virtual BOOL Open( UINT nOpenType = AFX_DB_USE_DEFAULT_TYPE, LPCTSTR lpszSQL = NULL, DWORD dwOptions = none );
throw( CDBException, CMemoryException );
該函數使用指定的SQL語句查詢數據源中的記錄并按指定的類型和選項建立記錄集.參數nOpenType說明了記錄集的類型,如表10.3所示,如果要求的類型驅動程序不支持,則函數將產生一個異常.參數lpszSQL是一個SQL的SELECT語句,或是一個表名.函數用lpszSQL來進行查詢,如果該參數為NULL,則函數會調用GetDefaultSQL獲取缺省的SQL語句.參數dwOptions可以是一些選項的組合,常用的選項在表10.4中列出.若創建成功則函數返回TRUE,若函數調用了CDatabase::Open且返回FALSE,則函數返回FALSE.

 

表10.3 記錄集的類型

類型

含義

AFX_DB_USE_DEFAULT_TYPE

使用缺省值.

CRecordset::dynaset

可雙向滾動的動態集.

CRecordset::snapshot

可雙向滾動的快照.

CRecordset::dynamic

提供比動態集更好的動態特性,大部分ODBC驅動程序不支持這種記錄集.

CRecordset::forwardOnly

只能前向滾動的只讀記錄集.

 

 

表10.4 創建記錄集時的常用選項

選項

含義

CRecordset::none

無選項(缺省).

CRecordset::appendOnly

不允許修改和刪除記錄,但可以添加記錄.

CRecordset::readOnly

記錄集是只讀的.

CRecordset::skipDeletedRecords

有些數據庫(如FoxPro)在刪除記錄時并不真刪除,而是做個刪除標記,在滾動時將跳過這些被刪除的記錄.

 

 

virtual CString GetDefaultSQL( );
Open函數在必要時會調用該函數返回缺省的SQL語句或表名以查詢數據源中的記錄.一般需要在CRecordset派生類中覆蓋該函數并在新版的函數中提供SQL語句或表名.下面是一些返回字符串的例子.
“Section” //選擇Section表中的所有記錄到記錄集中
“Section, Course” //合并Section表和Course表的各列到記錄集中

//對Section表中的所有記錄按CourseID的升序進行排序,然后建立記錄集

“SELECT * FROM Section ORDER BY CourseID ASC”

  上面的例子說明,通過合理地安排SQL語句和表名,Open函數可以十分靈活地查詢數據源中的記錄.用戶可以合并多個表的字段,也可以只選擇記錄中的某些字段,還可以對記錄進行過濾和排序.

  上一小節說過,在建立記錄集時,CRecordset會構造一個SELECT語句來查詢數據源.如果在調用Open時只提供了表名,那么SELECT語句還缺少選擇列參數rfx-field-list(參見10.5.3).框架規定,如果只提供了表名,則選擇列的信息從DoFieldExchange中的RFX語句里提取.例如,如果在調用Open時只提供了"Section"表名,那么將會構造如下一個SELECT語句:

SELECT CourseID,SectionNo,InstructorID,RoomNo, Schedule,Capacity FROM Section

 

  建立記錄集后,用戶可以隨時調用Requery成員函數來重新查詢和建立記錄集.Requery有兩個重要用途:

  • 使記錄集能反映用戶對數據源的改變(參見10.5.1).

  • 按照新的過濾或排序方法查詢記錄并重新建立記錄集.

 

  在調用Requery之前,可調用CanRestart來判斷記錄集是否支持Requery操作.要記住Requery只能在成功調用Open后調用,所以程序應調用IsOpen來判斷記錄集是否已建立.函數的聲明為

virtual BOOL Requery( );throw( CDBException, CMemoryException );
返回TRUE表明記錄集建立成功,否則返回FALSE.若函數內部出錯則產生異常.

BOOL CanRestart( ) const; //若支持Requery則返回TRUE

BOOL IsOpen( ) const; //若記錄集已建立則返回TRUE

  CRecordset類有兩個公共數據成員m_strFilter和m_strSort用來設置對記錄的過濾和排序.在調用Open或Requery前,如果在這兩個數據成員中指定了過濾或排序,那么Open和Requery將按這兩個數據成員指定的過濾和排序來查詢數據源.

成員m_strFilter用于指定過濾器.m_strFilter實際上包含了SQL的WHERE子句的內容,但它不含WHERE關鍵字.使用m_strFilter的一個例子為:

m_pSet->m_strFilter=“CourseID=‘MATH101’”; //只選擇CourseID為MATH101的記錄

if(m_pSet->Open(CRecordset::snapshot, “Section”))

. . . . . .

  成員m_strSort用于指定排序.m_strSort實際上包含了ORDER BY子句的內容,但它不含ORDER BY關鍵字.m_strSort的一個例子為

m_pSet->m_strSort=“CourseID DESC”; //按CourseID的降序排列記錄

m_pSet->Open();

. . . . . .

  事實上,Open函數在構造SELECT語句時,會把m_strFilter和m_strSort的內容放入SELECT語句的WHERE和ORDER BY子句中.如果在Open的lpszSQL參數中已包括了WHERE和ORDER BY子句,那么m_strFilter和m_strSort必需為空.

  調用無參數成員函數Close可以關閉記錄集.在調用了Close函數后,程序可以再次調用Open建立新的記錄集.CRecordset的析構函數會調用Close函數,所以當刪除CRecordset對象時記錄集也隨之關閉。

10.5.5 滾動記錄

  CRecordset提供了幾個成員函數用來在記錄集中滾動,如下所示.當用這些函數滾動到一個新記錄時,框架會自動地把新記錄的內容拷貝到域數據成員中.

void MoveNext( ); //前進一個記錄

void MovePrev( ); //后退一個記錄

void MoveFirst( ); //滾動到記錄集中的第一個記錄

void MoveLast( ); //滾動到記錄集中的最后一個記錄

void SetAbsolutePosition( long nRows );
該函數用于滾動到由參數nRows指定的絕對位置處.若nRows為負數,則從后往前滾動.例如,當nRows為-1時,函數就滾動到記錄集的末尾.注意,該函數不會跳過被刪除的記錄.

virtual void Move( long nRows, WORD wFetchType = SQL_FETCH_RELATIVE );
該函數功能強大.通過將wFetchType參數指定為SQL_FETCH_NEXT、SQL_FETCH_PRIOR、SQL_FETCH_FIRST、SQL_FETCH_LAST和SQL_FETCH_ABSOLUTE,可以完成上面五個函數的功能.若wFetchType為SQL_FETCH_RELATIVE,那么將相對當前記錄移動,若nRows為正數,則向前移動,若nRows為負數,則向后移動.

 

  如果在建立記錄集時選擇了CRecordset::skipDeletedRecords選項,那么除了SetAbsolutePosition外,在滾動記錄時將跳過被刪除的記錄,這一點對象FoxPro這樣的數據庫十分重要.

  如果記錄集是空的,那么調用上述函數將產生異常.另外,必須保證滾動沒有超出記錄集的邊界.調用IsEOF和IsBOF可以進行這方面的檢測.

 

BOOL IsEOF( ) const;
如果記錄集為空或滾動過了最后一個記錄,那么函數返回TRUE,否則返回FALSE.

BOOL IsBOF( ) const;
如果記錄集為空或滾動過了第一個記錄,那么函數返回TRUE,否則返回FALSE.

 

下面是一個使用IsEOF的例子:

while(!m_pSet->IsEOF( ))

m_pSet->MoveNext( );

調用GetRecordCound可獲得記錄集中的記錄總數,該函數的聲明為

long GetRecordCount( ) const;
要注意這個函數返回的實際上是用戶在記錄集中滾動的最遠距離.要想真正返回記錄總數,只有調用MoveNext移動到記錄集的末尾(MoveLast不行).

 

10.5.6 修改、添加和刪除記錄

要修改當前記錄,應該按下列步驟進行:

調用Edit成員函數.調用該函數后就進入了編輯模式,程序可以修改域數據成員.注意不要在一個空的記錄集中調用Edit,否則會產生異常.Edit函數會把當前域數據成員的內容保存在一個緩沖區中,這樣做有兩個目的,一是可以與域數據成員作比較以判斷哪些字段被改變了,二是在必要的時侯可以恢復域數據成員原來的值.若再次調用Edit,則將從緩沖區中恢復域數據成員,調用后程序仍處于編輯模式.調用Move(AFX_MOVE_REFRESH)或Move(0)可退出編輯模式(AFX_MOVE_REFRESH的值為0),同時該函數會從緩沖區中恢復域數據成員.

設置域數據成員的新值.

調用Update完成編輯.Update把變化后的記錄寫入數據源并結束編輯模式.

 

要向記錄集中添加新的記錄,應該按下列步驟進行:

調用AddNew成員函數.調用該函數后就進入了添加模式,該函數把所有的域數據成員都設置成NULL(注意,在數據庫術語中,NULL是指沒有值,這與C++的NULL是不同的).與Edit一樣,AddNew會把當前域數據成員的內容保存在一個緩沖區中,在必要的時侯,程序可以再次調用AddNew取消添加操作并恢復域數據成員原來的值,調用后程序仍處于添加模式.調用Move(AFX_MOVE_REFRESH)可退出添加模式,同時該函數會從緩沖區中恢復域數據成員.

設置域數據成員.

調用Update.Update把域數據成員中的內容作為新記錄寫入數據源,從而結束了添加.

 

如果記錄集是快照,那么在添加一個新的記錄后,需要調用Requery重新查詢,因為快照無法反映添加操作.

要刪除記錄集的當前記錄,應按下面兩步進行:

調用Delete成員函數.該函數會同時給記錄集和數據源中當前記錄加上刪除標記.注意不要在一個空記錄集中調用Delete,否則會產生一個異常.

滾動到另一個記錄上以跳過刪除記錄.

 

上面提到的函數聲明為:

virtual void Edit( );throw( CDBException, CMemoryException );

virtual void AddNew( );throw( CDBException );

virtual void Delete( );throw( CDBException );

virtual BOOL Update( );throw( CDBException );
若更新失敗則函數返回FALSE,且會產生一個異常.

 

  在對記錄集進行更改以前,程序也許要調用下列函數來判斷記錄集是否是可以更改的,因為如果在不能更改的記錄集中進行修改、添加或刪除將導致異常的產生.

BOOL CanUpdate( ) const; //返回TRUE表明記錄是可以修改、添加和刪除的.

BOOL CanAppend( ) const; //返回TRUE則表明可以添加記錄.


10.6 CRecordView

  CRecordView(記錄視圖)是CFormView的派生類,它提供了一個表單視圖(參見6.4.1)來顯示當前記錄.一個典型的記錄視圖如圖10.3所示,用戶可以通過表單視圖顯示當前記錄.通過記錄視圖,可以修改、添加和刪除數據.用戶一般需要創建一個CRecordView的派生類并在其對應的對話框模板中加入控件.

T10_3.tif (174388 bytes)

圖10.3 典型的記錄視圖

 

  記錄視圖使用DDX數據交換機制在表單中的控件和記錄集之間交換數據。在前面介紹的DDX都是在控件和控件父窗口的數據成員之間交換數據,而記錄視圖則是在控件和一個外部對象(CRecordset的派生類對象)之間交換數據.清單10.3顯示了一個CRecordView的派生類的DoDataExchange函數,讀者可以看出,該函數是與m_pSet指針指向的記錄集對象的域數據成員交換數據的,而且,交換數據的代碼是ClassWizard自動加入的.在后面的例子中,將向讀者介紹用ClassWizard連接記錄視圖與記錄集對象的方法.

 

清單10.3 用來與記錄集對象的域數據成員交換數據的DoDataExchange函數

void CSectionForm::DoDataExchange(CDataExchange* pDX)

{

CRecordView::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CSectionForm)

DDX_FieldText(pDX, IDC_COURSE, m_pSet->m_CourseID, m_pSet);

DDX_FieldText(pDX, IDC_SECTION, m_pSet->m_SectionNo, m_pSet);

DDX_FieldText(pDX, IDC_INSTRUCTOR, m_pSet->m_InstructorID, m_pSet);

DDX_FieldText(pDX, IDC_ROOM, m_pSet->m_RoomNo, m_pSet);

DDX_FieldText(pDX, IDC_SCHEDULE, m_pSet->m_Schedule, m_pSet);

DDX_FieldText(pDX, IDC_CAPACITY, m_pSet->m_Capacity, m_pSet);

//}}AFX_DATA_MAP

}

  作為總結,圖10.4顯示了MFC的ODBC應用程序中的DDX和RFX數據交換.

點擊查看原始尺寸

圖10.4 DDX和RFX數據交換機制

CRecordView本身提供了對下面四個命令的支持:

ID_RECORD_FIRST //滾動到記錄集的第一個記錄

ID_RECORD_LAST //滾動到記錄集的最后一個記錄

ID_RECORD_NEXT //前進一個記錄

ID_RECORD_PREV //后退一個記錄

 

CRecordView提供了OnMove成員函數處理這四個命令消息,OnMove函數對用戶是透明的,清單10.4列出了OnMove的源代碼.

 

清單10.4 OnMove函數

BOOL CRecordView::OnMove(UINT nIDMoveCommand)

{

CRecordset* pSet = OnGetRecordset();

if (pSet->CanUpdate())

{

pSet->Edit();

if (!UpdateData())

return TRUE;

 

pSet->Update();

}

 

switch (nIDMoveCommand)

{

case ID_RECORD_PREV:

pSet->MovePrev();

if (!pSet->IsBOF())

break;

 

case ID_RECORD_FIRST:

pSet->MoveFirst();

break;

 

case ID_RECORD_NEXT:

pSet->MoveNext();

if (!pSet->IsEOF())

break;

if (!pSet->CanScroll())

{

// clear out screen since we're sitting on EOF

pSet->SetFieldNull(NULL);

break;

}

 

case ID_RECORD_LAST:

pSet->MoveLast();

break;

 

default:

// Unexpected case value

ASSERT(FALSE);

}

 

// Show results of move operation

UpdateData(FALSE);

return TRUE;

}

  在函數的開頭先調用CRecordset::Edit進入編輯模式,接著調用UpdateData將控件中的數據更新到記錄集對象的域數據成員中,然后調用CRecordset::Update將域數據成員的值寫入數據源.這說明OnMove在滾動記錄的同時會完成對原來記錄的修改.

  在函數的中間有一個分支語句用來處理四個不同的命令,在這個分支語句中調用了CRecordset的各種用于滾動記錄的成員函數,這些函數在滾動到一個新的記錄時會把該記錄的內容設置到域數據成員中.在函數的末尾調用UpdateData(FALSE)把新的當前記錄的內容設置到表單的控件中。

  由此可見,OnMove一來一回完成了兩次表單控件和數據源的數據交換過程.通過分析該函數,讀者可以學會在瀏覽記錄時如何控制DDX和DFX數據交換.

 


引用:同一個世界,不同的你我

VC ODBC使用總結

1.打開數據庫

CDatabase database;
database.OpenEx( _T( "DSN=zhuxue" ),CDatabase::noOdbcDialog);//zhuxue為數據源名稱

2.關聯記錄集

CRecordset recset(&database);

3.查詢記錄

CString sSql1="";
?sSql1 = "SELECT * FROM tablename" ;???
? recset.Open(CRecordset::forwardOnly, sSql1, CRecordset::readOnly);

int ti=0;
CDBVariant var;//var可以轉換為其他類型的值

?while (!recset.IsEOF())
??????? {
???//讀取Excel內部數值
???recset.GetFieldValue("id",var);
???jiangxiang[ti].id=var.m_iVal;
???recset.GetFieldValue("name", jiangxiang[ti].name);
???ti++;
???recset.MoveNext();
? }

recset.Close();//關閉記錄集

4.執行sql語句

CString sSql="";
?sSql+="delete * from 院系審核";//清空表
?database.ExecuteSQL(sSql);

sSql也可以為Insert ,Update等語句

5.讀取字段名

?sSql = "SELECT * FROM Sheet1" ;??? //讀取的文件有Sheet1表的定義,或為本程序生成的表.???????
????
???// 執行查詢語句
?? recset.Open(CRecordset::forwardOnly, sSql, CRecordset::readOnly);
???int excelColCount=recset.GetODBCFieldCount();//列數
?? CString excelfield[30];
??//得到記錄集的字段集合中的字段的總個數??
??for( i=0;i<excelColCount;i++)???
??{???
???CODBCFieldInfo fieldinfo;
???recset.GetODBCFieldInfo(i,fieldinfo);
???excelfield[i].name =fieldinfo.m_strName;//字段名
???
????}?

6.打開excel文件

CString sDriver = "MICROSOFT EXCEL DRIVER (*.XLS)"; // Excel安裝驅動
?CString sSql,sExcelFile; //sExcelFile為excel的文件路徑

TRY
?{
??// 創建進行存取的字符串
??sSql.Format("DRIVER={%s};DSN='';FIRSTROWHASNAMES=1;READONLY=FALSE;CREATE_DB=\"%s\";DBQ=%s",sDriver, sExcelFile, sExcelFile);
??
??// 創建數據庫 (既Excel表格文件)
??if( database.OpenEx(sSql,CDatabase::noOdbcDialog) )
{

//可以把excel作為一個數據庫操作

}

?}
?catch(e)
?{
??TRACE1("Excel驅動沒有安裝: %s",sDriver);
??AfxMessageBox("讀取失敗,請檢查是否定義數據區Sheet1");
?}

本文原創出處 couple_xiewei@163.com,例程原代碼請發郵件索取

摘要: 本文介紹了在 Visual C++ ODBC 訪問 SQL Server 數據庫的方法,包括如何在程序中動態配置 SQL Server 數據源,如何與數據源建立連接,如何得到數據庫結構信息等,并給出了示例程序。

關鍵詞: ODBC, SQL Server, 數據源

ODBC Open Database Connectivity ,開放數據庫連接)是由 Microsoft 定義的一種數據庫訪問標準,它提供了一種標準的數據庫訪問方法以訪問不同數據庫提供商的數據庫,其本質上是一組數據庫訪問 API 。雖然數據庫訪問有多種方法,但 ODBC 以其編程相對簡單,在實際編程中被廣泛使用。

?????? VC++ 中提供了一組封裝了 ODBC API MFC ODBC 類,以減少程序代碼編寫量。在 VC++ 中使用 MFC ODBC 類訪問數據庫時,一般都是先配置或選擇已有的數據源,再構造 CDatabase 類對象,打開數據源,用該數據庫類對象和 SQL (結構化查詢語言)可以對庫進行訪問,再構造 CRecordset 類或其繼承類對象,用數據集類對象和 SQL 可以實現對庫中的表的操作。在 VC++ 中用 MFC ODBC 類操作 SQL Server 數據庫基本上也是這個思路。

1.???????? 配置數據源

在程序中根據用戶選擇動態配置數據源而不調用 ODBC 數據源管理器,對于應用程序開發有時是十分必要的。畢竟 ODBC 數據源管理器對于對數據庫不熟悉的用戶顯的復雜了,且 ODBC 數據源管理器的界面風格有可能與整個應用程序的界面風格不一致,對于嚴謹的應用程序開發者來說,這些都是不能容忍的。

配置 SQL Server 數據源時,必須有 SQL Server 服務器名和服務器中的目標數據庫名 ( 否則打開該數據源時,就會彈出配置數據源對話框或失敗 ) 。這與配置 Access 等數據庫的數據源時指定數據庫文件的路徑不同。

可以通過 ODBC API 函數 SQLBrowseConnect 得到本地所有的 SQL Server 服務器、服務器中的庫、語言信息等。其使用方法如下所示:

得到服務器名

??? SQLBrowseConnect(hdbc, "DRIVER={SQL Server};", SQL_NTS, BrowseResult, sizeof(BrowseResult), &BrowseResultLen);

?????? 其中 hdbc 是用 SQLAllocHandle 函數得到的連結句柄,第二個參數是指定連結屬性的輸入字符串,第三個參數是輸入字符串的長度,第四個參數是輸出字符串的指針,我們要得到的信息就存于這個字符串中,第五個參數指定輸出字符串的長度,第六個參數是實際返回字符串的長度。 BrowseResult 所指向的函數返回的字符串中含有形如 “SERVER:Server={Server_name1,Server_name2, }” 的字符串,其中 Server_name1 Server_name2 就是 SQL Server 服務器名。

?????? 得到庫名

?????? 當用戶選定一個服務器之后,可以進一步利用 SQLBrowseConnect 函數得到服務器中存在的庫或語言信息(如果需要的話)。

SQLBrowseConnect(hdbc,"SERVER=Server_name1;UID=sa;PWD=515578;",SQL_NTS, BrowseResult, sizeof(BrowseResult), &BrowseResultLen);

其中 UID PWD 是訪問該服務器的用戶名和密碼。這次 BrowseResult 所指向的函數返回的字符串中含有形如 DATABASE:Database={master,model, } LANGUAGE:Language={Arabic, Brazilian, English, } 的字符串,其中 master model 為服務器中的數據庫名。

利用這些得到信息就可以配置 SQL Server 數據源了:

SQLConfigDataSource(NULL,ODBC_ADD_DSN,"SQL Server","DSN=myDSN\0 SERVER=xhm\0DATABASE=pubs\0\0" ))

?????? 作者有幸在網上找到 Santosh Rao 編寫的 CSQLInfoEnumerator 類,該類有 EnumerateSQLServers EnumerateDatabase EnumerateDatabaseLanguage 三個成員函數,其封裝了 SQLBrowseConnect 函數并進行相應的字符處理,大大簡化了 SQLBrowseConnect 函數的使用。

2.???????? 與數據源建立連接

如果使用 ODBC API 函數進行連結,函數 SQLConnect SQLDriverConnect SQLBrowseConnect 都可以實現。其中 SQLConnect 函數用于給定所有參數直接建立與數據源的連接; SQLDriverConnect 用于給定了部分連接參數,并彈出數據源瀏覽窗口與用戶交互,獲得足夠的參數后建立與數據源的連接;而 SQLBrowseConnect 是通過迭代獲取連結參數再進行連接,其使用如前所述。

MFC 為連接數據源提供了一個數據庫類 CDatabase ,通過它我們可以非常方便地與數據源建立連結:

//m_db CDatabase 的對象

//m_szUserId m_szPassword CString 對象,為訪問數據源的用戶名和密碼

?????? CString str;

?????? str = _T("DSN=xhmtest;UID=")+m_szUserId+_T(";PWD=")+m_szPassword;

?????? if (! m_db.OpenEx(str, CDatabase::noOdbcDialog ) ){

????????????? AfxMessageBox(" 打開數據源失敗 ");

}

另外,如果我們想對數據源進行操作,就可以利用打開的 CDatabase 對象執行 SQL 語句實現。如新建一張表:

??? str = "CREATE TABLE TableDemo (Column1TEXT, Column2 NUMBER)";

??? m_db.ExecuteSQL(sSql);

3.???????? 得到數據庫的結構信息

VC++ 中可以單獨利用 ODBC API 獲取數據庫的結構信息,但方法非常復雜,各種參數也不易理解,且返回的結果不易獲取。 MFC 中也沒有為應用程序開發者得到數據庫結構信息而提供單獨的類。但是我們還是可以通過一些已有的方法方便地獲得數據庫的結構信息。

3.1 得到數據庫中的表

利用 ODBC API 函數 SQLTables 可以得到數據庫中的表信息,但使用不易。

MSDN sample 中有一個 catalog 例程,它示范了如果得到一個數據庫的表信息和表中的字段信息,該程序中有兩個非常實用的類: Ctables Ccolumns 。其中 CTables 繼承 CRecordset 類,通過非常巧妙的方法封裝了 SQLTables 函數,并將結果集存于 CRecordset 類,方便獲取。

在程序中我們可以這樣應用:

????????????? //m_db 是已經打開的 CDatabse 對象 ,

????????????? //m_strTableOwner CComboBox 對象

?????? CTables rs(&m_db);

?????? rs.Open(NULL, NULL, NULL, "TABLE");

?????? CString strTableRef;

?????? m_combTable.ResetContent();

?????? while (!rs.IsEOF())

?????? {

????????????? strTableRef = _T("[");

????????????? if (!rs.m_strTableOwner.IsEmpty())

???????????????????? strTableRef += rs.m_strTableOwner + _T("].[");

????????????? strTableRef += rs.m_strTableName + _T("]");

????????????? m_combTable.AddString(strTableRef);

????????????? rs.MoveNext();

?????? }

?????? rs.Close();

CTables 對象 Open 方法第四個能數指定 “TABLE” 是指返回庫中的表,當該參數為 “SYSTEM TABLE” 時返回系統表,當為 NULL 時,返回所有表。

3.2 得到表中字段結構

?????? 得到表中字段結構有三種具體實現方式:

直接利用 ODBC API 函數 SQLColumn ;其實現也同樣復雜。

利用前面提到的封裝了 SQLColumns 函數的 CColumns 類;其使用方法與 CTables 類似,其成員變量 m_strColumnName 即為字段名

利用 CRecordset 類的成員函數。 MFC CRecordset 類中提供了獲取表結構信息方法,使用非常方便。(但很奇怪為什么 CDatabase 類沒有獲取庫結構信息的類方法?)

我們以利用 CRecodrset 為例簡單說明怎樣得到表結構。假設現在要得到用戶在 CComboBox 對象中所選表的所有字段名,并將它埴入一個 CListCtrl 對象(其 View 屬性被設為 Report )的列名中去:

?????? //m_strTableOwner 和上同,為 CComboBox 對象

?????? //m_listData ClistCtrl 對象

?????? CRecordset rs

?????? CODBCFieldInfo info;

CString strSQL;

?????? m_combTable.GetLBText(m_combTable.GetCurSel(), strSQL);

?????? strSQL = _T("SELECT * FROM ") + strSQL;

?????? rs.Open(Open(CRecordset::snapshot, strSQL, CRecordset::readOnly);

?????? int nColumns = rs.GetODBCFieldCount();

?????? for (int nNum = 0; nNum < nColumns; nNum++)

?????? {

????????????? prs->GetODBCFieldInfo(nNum, info);

????????????? m_listData.InsertColumn(nNum, info.m_strName, LVCFMT_LEFT, 80)

?????? }

另外,我們可以利用 CRecordSet::GetFieldValue 函數通過指定列名或列序數得到記錄集中游標所指記錄的值,通過 CRecordset::AddNew CRecordset::CancelUpdate CRecordset::Delete CRecordset::Edit CRecordset::Update 等函數操作 CRecordset 打開的表。

4.???????? 本文的例程

本文所附的例程演示了如何編程讀取局域網中 SQL Server 服務器中的數據。

點擊 SQL Server 下拉列表,選擇局域網中可以連接的 SQL Server 服務器名,再輸入選定服務器的用戶名和密碼,點擊 Database 下拉列表得到服務器中存在的數據庫。然后點配置數據源按鍵,配置數據源并和數據源并建立連接,在 Table 下拉列表欄中選擇要打開的表就可以在下面的列表中顯示表中的數據了。因為表中數據量可能比較大,程序每次只讀取 25 條記錄到內存中。

參考文獻:

[1] MSDN Library, the July 2000 release, Microsoft Corporation

[2] 劉刀桂 孟繁晶, Visual C++ 實踐與提高 - 數據庫篇,中國鐵道出版社, 2001

?

今天懷著虞城的心來探索打印CListCtrl的方法,可惜忙到現在被老掉牙的數據加載給絆倒。但是從中卻學到了不少新東西,以前沒有遇到過的。現在就寫出來和大家分享。
ODBC數據源與CListCtrl的連接已經算是老生常談的事情了。
1、先建立數據庫(這里以一個PrintTest為數據源名來處理,該數據庫包含一張表info,里面有四個字段,ID,NAME,GROUP,AGE,只是測試用因此隨便列出幾個字段,其中ID為數字類型,其余為文本,采用Access數據庫來建立。方法就是添加一張表,然后分別對表中填充一些數據,這里就不再講述!)
2、ODBC數據源與程序的連接
???a.在stdafx.h文件的尾部添加#include "afxdb.h"
???b.針對于整個工程的全局函數CDatabase db;
???c.在APP文件的初始化進程中添加打開數據庫的語句
?????????

????CWinApp::InitInstance();????//此句為系統自動生成的;

????
if?(!db.IsOpen())
????????db.Open(
"PrintTest;");
????
else
????????AfxMessageBox(
"數據庫連接失敗!");

????AfxEnableControlContainer();????
//此句為系統自動生成的;

至此,與數據庫的連接基本上完成了,也正是因為這個db.Open("PrintTest;");才有了這篇文章。
3、向記錄集(CRecordset)填充數據,在這里我們必須要談到(數據庫和記錄集對象之間的)記錄字段交換 (RFX)。(大家可以在MSDN中查閱相關信息。)現在只將步驟做一個簡述:
???a.在“類視圖”中添加類,然后選擇“MFC ODBC 使用者”;
???b.在向導中,數據源按鈕后選擇“機器數據源”選擇我們設置的ODBC數據源,這里為PrintTest;確定;
???c.在彈出的對話框中選擇info表,確定;
???d.修改類名,文件名(如果有必要的話),這里改為CInfoRS,InfoRS.h,InfoRS.cpp
???e.注意下面的選擇是“動態集”,確定。(警告關閉)
這時候你可以在類向導中看到添加的新類,CInfoRS
4、在初始化對話框的時候進行數據的讀入。
值得注意的是:我們剛才定義的是全局的變量,但是定義的語句是在APP文件中寫入的,因此在Dlg文件中調用的時候我們仍然需要去聲明一下它是全局變量,也就是對Dlg來說,它是在外部已經定義過的變量。因此在Dlg.cpp的開頭我們補充extern CDatabase db;
在BOOL CPrintListCtrlDlg::OnInitDialog()中添加

????//?TODO:?在此添加額外的初始化代碼//注意找到該函數中的這句話,在其后添加以下代碼
????m_cList.SetExtendedStyle(LVS_EX_GRIDLINES);??????//設置ListCtrl的風格
????int?nWidth=110;
????m_cList.InsertColumn(
0,"會員編號",LVCFMT_LEFT,nWidth*2/3);
????m_cList.InsertColumn(
1,"會員姓名",LVCFMT_LEFT,nWidth);
????m_cList.InsertColumn(
2,"會員組織",LVCFMT_LEFT,nWidth*3/2);
????m_cList.InsertColumn(
3,"年齡",LVCFMT_LEFT,nWidth/2);
????CInfoRS?rs(
&db);

????UpdateList(rs);


在這個應用程序中同樣要注意到,因為我們需要的是一張類似Access表的表格,而不是類似我的電腦的圖標形式的風格,因此我們需要在添加的ListControl的時候,將其屬性中的View設置為Report。

?UpdateList(rs);是我們自己添加的一個函數,再此我再介紹一下使用類向導添加函數的方法。
在類視圖中,右鍵,添加->添加函數,然后添加相關參數,比如返回值類型,參數類型等,記得添加參數的時候按“添加”將其添加到參數列表中。
下面列出UpdateList的代碼:

void?CPrintListCtrlDlg::UpdateList(CInfoRS&?rs)
{
????
int?i=0;
????CString?strID;
????rs.Open();
????m_cList.DeleteAllItems();
????
while(!rs.IsEOF())
????
{
????????m_cList.InsertItem(i,
"");
????????strID.Format(
"%d",rs.m_ID);
????????m_cList.SetItemText(i,
0,strID);
????????m_cList.SetItemText(i,
1,rs.m_NAME?);
????????m_cList.SetItemText(i,
2,rs.m_GROUP);
????????m_cList.SetItemText(i,
3,rs.m_AGE);
????????rs.MoveNext();
????????i
++;
????}

????rs.Close();
}
至此這個程序基本上就編寫完成了,但是使用Ctrl+F5調試的時候出現了錯誤。錯誤具體就不再描述,只告訴解決的方法。
1、一個是CInfoRS類中一句#error Security Issue: The connection string may contain a password
解決方法:注釋掉
2、類型不匹配:
??m_cList.SetItemText(i,1,rs.m_NAME );
??m_cList.SetItemText(i,2,rs.m_GROUP);
??m_cList.SetItemText(i,3,rs.m_AGE);
將提示rs.m_NAME,rs.m_GROUP,rs.m_AGE不能從“CStringW”轉換為“LPCTSTR”
解決方法:在類視圖中定位到CInfoRS,在其變量(藍色方塊后),隨便點一個進入。按以下方法修改:
//將以下代碼:
????long????m_ID;
????CStringW????m_NAME;
????CStringW????m_GROUP;
????CStringW????m_AGE;
//修改為:
????long????m_ID;
????CString????m_NAME;
????CString????m_GROUP;
????CString????m_AGE;
//即將這里的CStringW替換為CString


//其實可以注意到之前的一大段話:以下字符串類型(如果存在)反映數據庫字段(ANSI 數據類型的 CStringA 和 Unicode數據類型的 CStringW)的實際數據類型。這是為防止 ODBC 驅動程序執行可能不必要的轉換。如果希望,可以將這些成員更改為CString 類型,ODBC 驅動程序將執行所有必要的轉換。(注意: 必須使用 3.5 版或更高版本的 ODBC 驅動程序以同時支持 Unicode 和這些轉換)。
至此,編譯通過。
但是,隨后彈出錯誤:ODBC數據源不支持動態集
修正這個問題的方法有二:
一、在剛才添加的時候,將動態集(默認)改為快照,在這里,將不會出現錯誤。
二、令人慶幸的是我曾經做過一個例子,里面確實是使用動態集,但并沒有出現這個錯誤,于是我找到了另一個程序,再次調試通過。于是就很是郁悶。于是用斷點調試,最終確定問題是發生在rs.Open();的位置。
于是查找MSDN發現(For CRecordset, the default value is CRecordset::snapshot.翻譯:對于CRecordset默認值類對象,默認值是 CRecordset::snapshot,也就是快照模式),而我們所用的是動態集的模式。這里可以將rs.Open();改為rs.Open(CRecordset::forwardOnly);再次編譯就可以通過了。
另外可以將最初的db.Open("PrintTest;");改為db.OpenEx("DSN=PrintTest;");就可以解決問題了。這其中的奧妙就不是很清楚,只能當作經驗來和大家分享一下。也希望有知道的人能夠留言告訴我。謝謝先~!


以下將本示例的代碼以及數據庫打包供大家學習下載!
http://www.cppblog.com/Files/mymsdn/PrintListCtrl.rar

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

有關MFC ODBC類對打開的CRecordset數據集無法進行更新操作釋疑— VC6自帶的MFC4.2中CString.Format與CRecordSet的兼容性問題
在用MFC數據庫類CDatabase和CRecordset類聲明的對像無法對打開的數據集進行編輯、添加、刪除。偶然間讓我碰到個這樣的問題讓我費了不少精神,不知道為什么數據庫能正常打開,數據集也正常Open。可是就是沒辦法進入編輯狀態,老是會彈出“記錄集只讀”的提示。
------------------------------------------------------------------------------------------------------------------------------
?CDatabase db;
?db.OpenEx (_T"DSN=strtab");
?CRecordset m_Set (&db);
?m_Set.Open (AFX_DB_USE_DEFAULT_TYPE, _T"select *from strtab");
?if (!m_Set.IsOpen ()) AfxMessageBox ("數據集沒能打開!");
?m_Set.Edit ();
--------------------------------------------------------------------------------------------------------------------------------
?????????檢查一下自己的程序,發現不管是聲明的CDatabase對像還是通過CRecordset對像打開的記錄集都使用的是缺省參數,而在我的記憶中CDatabase::OpenEx()和CRecordset::Open()這兩個成員函數的缺省參數打開的記錄集和數據庫對像都是非只讀的。倒底是那里的問題呢?
BOOL CDatabase::OpenEx(LPCTSTR lpszConnectString, DWORD dwOptions)
virtual BOOL Open(UINT nOpenType = AFX_DB_USE_DEFAULT_TYPE,
??LPCTSTR lpszSQL = NULL, DWORD dwOptions = none);
?????????我在我的程序中打開數據集的Open()處下了斷點并跟蹤進去,發現CRecordset對像的m_bUpdatable標志位最初始一直是為TRUE,即可更新狀態。隨后進入了如下一串令人眼花繚亂的函數調用里,終于找到m_bUpdatable的值在那里被更改,這一切的問題出自那里!
-----------------------------------------------------------------------------------------------------------------------------------------------
BuildSQL(lpszSQL);
if ((m_bUpdatable || m_bAppendable) && !IsRecordsetUpdatable())
if (!IsSQLUpdatable(m_strSQL))
return IsSelectQueryUpdatable(lpszSQL);
lpchTokenFrom = FindSQLToken(strSQL, _afxFrom); //
AFX_STATIC_DATA const TCHAR _afxFrom[] = _T(" FROM ");
lpszFoundToken = _tcsstr(lpszFoundToken + nTokenOffset, lpszSQLToken);
--------------------------------------------------------------------------------------------------------------------------------------------------
?????????上面這每一行是跟蹤進去的函數,藍色字是函數名。這些函數是分析傳遞進來的SQL語句的并進行相應的設置來打開數據集。注意第五行_afxFrom的值是什么!這是MFC定義的一個常量,它的訂義在注釋那里。問題就在這里了,MFC定義這個常量用來分析SQL語句中是否包含用FROM關鍵字以及其后的表名,要有這個關鍵字那么數據集才是以可更新的狀態打開的。但是,很明顯我的程序中打開數據集用的SQL語句是有這個FROM關鍵字和表名的。那么我應該得到一個可更新的數據集才對啊,為什么我不能更新呢?仔細看看MFC定義的這個_afxFrom常量,它的值是_T(" FROM ")FROM的前后各有一個空格,因此真相大白了。問題就是我的SQL語句因為要返回所有的字段,就沒有給出字段名而是直接用*號表示要返回所有字段,而這個*號緊挨著FROM關鍵字,所以在最后一行中MFC用_tcsstr()函數在SQL語句中查找_afxFrom,結果是永遠也找不到MFC定義的這個兩頭各有一個空格的FROM關鍵。_tcsstr()返回一個空指針,接著這些函數一路返回FALSE,這樣m_bUpdatable的值就為FALSE,我所打開的數據集也是不可更新數據集。
?????????這樣的問題為什么以前沒有出現呢?那是因為以前我要不是只返回特定的字段,構造好的SQL語句中的FROM也是同樣前后都有空格。就是直接CRecordset::Open()一個參數不帶的執行這個Open()函數。這樣由MFC自行構造的缺省SQL語句肯定是符合他自己的要求。
?????????實際上,我這樣的*號緊挨FROM的SQL語句寫法也是很多人的習慣。所以,我相信也有不少的朋友也碰到這樣的問題。
-------------------------------------------------------------------------------------------------------------------

今天我在BBS的VC版上轉悠,看到由個哥們出了這樣的問題:
說他在編寫MFC的數據庫程序(ODBC)的時候出現了錯誤,再插入新記錄后調用Update的時候出現了Assert,由于再BBS上,我和他通過信息交流了一下,發現他在AddNew和Update之間調用了Format。直覺告訴我問題出在這里。
于是分析了一下。這個是我在BBS上發的帖子。

這個問題我仔細看了一下,問題出在MFC內部:下面所述僅適用于VC6帶的MFC4.2

我們在使用ODBC進行數據庫的插入操作時,都是這么一個流程:
AddNew()
//給成員賦值
Update()
而在MFC的源文件dbcore.cpp 1040行,有這樣一行注釋:
// Buffer address must not change - ODBC's SQLBindCol depends upon this
由于MFC在進行默認的數據源綁定時,使用CString綁定字符串型的成員,而CString使用的是動態的內存管理方式,因此這個緩沖區地址其實是可以改變的,因此,在dbcore.cpp的1041行開始便是這樣幾句:

void* pvBind;
pvBind = value.GetBuffer(0);
value.ReleaseBuffer();
if (pvBind != pInfo->m_pvBindAddress)
{
?? TRACE1("Error: CString buffer (column %u) address has changed!\n",
?? nField);
?? ASSERT(FALSE);
}

因此,如果你在調用AddNew和Update之間把CString的緩沖區移動了,對不起,你必須收到一個ASSERT。(Nickshen好像就是這里的問題吧)

這樣問題就很清楚了,就是在你調用AddNew和Update之間不能移動緩沖區。但是據我和nickshen私下討論的結果,他只是在其中調用了CString的成員Format,想要把一個浮點數轉換成字符串,如果這么做就會有問題,但是直接賦值就不會,難道Format會移動CString的緩沖區?

于是我跟蹤了一下CString的Format函數,發現在被Format函數調用的FormatV函數的流程是這樣的:先根據格式串算出大約格式化之后的字符串要占多大的空間,然后就是看是否分配新的緩沖區,然后sprintf。這個學過數據結構的都可以理解。遠離很簡單,但是有這么一個問題:在FormatV函數中,有這么一段

(strex.cpp, 659行起)

case 'f':
? va_arg(argList, DOUBLE_ARG);
? nItemLen = 128; // width isn't truncated
? // 312 == strlen("-1+(309 zeroes).")
? // 309 zeroes == max precision of a double
? nItemLen = max(nItemLen, 312+nPrecision);
? break;

這個是Format對于格式串中的%f的處理,在一個switch塊中。
switch之后,

// adjust nMaxLen for output nItemLen
? nMaxLen += nItemLen;
...
GetBuffer(nMaxLen);

看到了沒?如果你使用了%f,MFC會很保守地認為你的一個%f會占用312的字符的位置(的確夠保守的,至于為何時312,注釋說得很清楚),于是用這個巨大的數調用GetBuffer。

然后是CString的operator=(LPCTSTR),這個就簡單多了,不用保守的計算,源字符串有多少個字符就分配多少個字節,同樣通過GetBuffer。

在GetBuffer的實現中,簡單的說就是看看原來的長度夠不夠,不夠重新分配一塊夠大的,然后memcpy,于是,緩沖區移動了。

慢著!
如果說長度不夠就要移動緩沖區,而且兩種操作都會移動緩沖區,那么為何只有Format會出錯,賦值不會?

誰說不會?你嘗試賦給你的變量一個長度超過256的字符串試試,肯定出錯,我試過了。
那么,這個256又是何處來的?你在用一個RecordSet第一步一定是Open吧。跟蹤一下發現,Open中有一步是BindFieldToColumns (dbcore.cpp 3854),經過一系列的分發,程序到了dbrfx.cpp 777:

case CFieldExchange::BindFieldToColumn:
...
// Constrain to user specified max length, subject to 256 byte min
if (cbColumn > (UINT)nMaxLength || cbColumn < 256)
??? cbColumn = nMaxLength;// Set up binding addres
void* pvData;
value.GetBufferSetLength(cbColumn+1);
pvData = value.LockBuffer();??? // will be overwritten if UNICODE

那么這個nMaxLength是多少呢?這個看看AfxDB.h中對于RFX_Text的聲明,255!
明白了?

這么一說事情就很清楚了,所有的一切都是由于MFC內部造成的,由于我們大多數時候都不會像數據庫中插入一個長度超過255的字符串(事實上Access和SQL Server都只支持最多255個字符),因此不會有問題,但是偏偏MFC的工程師們在做Format函數的時候保守了,于是,只要你用了%f格式符,就有問題無疑了。

知道了原因,解決方案就很簡單了:
1、如果你可以改數據庫,不妨把那個string(varchar)類型的字段改成double。
2、如果你沒有這個權限,或者數據太多已經不能改了,那么只有退而求其次,先定義一個buffer,sprintf一下,然后賦值給CString。

的確很麻煩。

同樣的程序,再VC7下調試沒有問題,有空再跟蹤一下看看吧。MFC7.1的CString已
經完全重寫了...

引用自:菜鳥心情—————C.ZHANG`s Mind—————

在vc下設定如IP地址及Netsh使用方法

經過幾天的摸索,在程序中實現了系統tcp/ip屬性設定網絡參數的功能,并且還提供了其它的信息,如網卡的各種信息等。實現方法自己知道的有三種:1,調用IP Help的API;2、從注冊表中讀取;3,設定IP等參數時使用netsh命令。

自己最初的工作量都在IP Help上了,并且實現了大部分的功能,但是有一點其中的AddIPAddress和DeleteIPAddress的使用上,DeleteIPAddress所刪的IP只能是由AddIPAddress加上去的,不能刪除靜態IP(自己猜的)。AddIPAddress加到接口上的IP在機子重啟,或者網絡連接禁用再啟用后就沒了。所以不長久,不過這對于adhoc中的地址自動配置夠用了。不過在window下,它只提供了為每個連接設定一個IP的界面,實際上一個interface可以由多個IP。

第二種方法是從注冊表中讀取SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces\\下的鍵值,但這種方法設定IP,子網掩碼、默認網關等參數后,一般需要重啟機器。但是在vckbase上下到一個例子程序,可以免重啟,它添加了NotifyIPChange函數做到的。操作注冊表,太麻煩,而且當os不同時候鍵值可能會不同。

第三種方法只用來配置靜態IP地址,因為IP Help雖然好用但是不能改靜態ip,自己試驗總是error。調用netsh命令就可以很好的解決這個問題。

?netsh interface ip set address local static 192.168.0.79 255.255.255.0 192.168.0.1 1 這個命令詳細用法見它的幫助,它設定了本機靜態ip,mask和默認的gateway,最后的1是到gateway的metric。下為關鍵代碼:

?UpdateData(TRUE);
?struct in_addr ia;
?ia.S_un.S_addr=htonl(m_ipctrl);
?CString strIP=inet_ntoa(ia) ;
?ia.S_un.S_addr=htonl(m_ipMask);
?CString strMask=inet_ntoa(ia);
?ia.S_un.S_addr=htonl(m_DGateway);
?CString strGate=inet_ntoa(ia);

?CString strParam = " interface ip set address \"本地連接\" static ";
?strParam+= strIP+" "+strMask+" "+strGate+" "+"1";
?ShellExecute(NULL,"open", "netsh",strParam,NULL,SW_HIDE);

存在的一個問題是“本地連接”這個名字可以在機子上改,這樣就不好了,關于如何獲得還不知道,以為是調用IP help中的GetIfTable函數得到一個結構體中有這項,(msdn這么說的)但是輸出總是亂碼,不知道原因還。誰要知道告訴我一聲。

引用自:http://gniq0418.tianya.cn/blogger/post_show.asp?BlogID=359755&PostID=4593024

Netsh使用方法

Windows 2K/XP 修改IP,不用開啟網絡連接屬性的對話框,用Netsh更改IP,是不需要這么麻繁
使用windows 內建的網路工具netsh便可輕松解決!
Netsh - Creates a shell for network information
微軟官方有一堆文件,有興趣練習英文閱讀的人可以去看看。
How to Use the NETSH Command to Change from Static IP Address to DHCP in Windows 2000(http://support.microsoft.com/?kbid=257748)
Using Netsh (http://www.microsoft.com/technet/treeview/default.asp?url=/technet/prodtechnol/
winxppro/proddocs/netsh.asp)
Managing Windows2000 Networking Components with Netsh
(http://www.microsoft.com/technet/community/columns/cableguy/cg1101.mspx)

在這里跟大家介紹幾個簡單的指令
1.Show IP
1.1Cmd Mode
直接在cmd下面輸入
netsh interface ip show address
亦可簡寫為
netsh int ip sh ad
看看,指令是不是和Cisco的nos指令很像!非常懷疑是抄襲Cisco的。

1.2Netsh Mode
您也可以進入netsh的命令模式下
netsh????????????????????????? //進入到 netsh mode
netsh>int??????????????????? //進入到 interface 子選項。
interface>ip??????????????? //進入到 ip 子選項。
interface ip>show
show address - 顯示 IP 位址。
show config - 顯示 IP 位址及其他資料。
show dns - 顯示 DNS 服務器位址。
show icmp - 顯示 ICMP 統計
show interface - 顯示 IP 介面統計
show ipaddress - 顯示 IP 位址
show ipnet - 顯示 IP net-to-media 對應
show ipstats - 顯示 IP 統計
show joins - 顯示加入的多點傳送群組
show offload - 顯示 offload 內容
show tcpconn - 顯示 TCP 連線
show tcpstats - 顯示 TCP 統計
show udpconn - 顯示 UDP 連線
show udpstats - 顯示 UDP 統計
show wins - 顯示 WINS 服務器位址。
2.Set IP
下列是所有可用的指令。
這個內容中的指令:
set address - 在指定的介面設定 IP 位址或預設網關。
set dns - 設定 DNS 服務器模式及位址。
set wins - 設定 WINS 服務器模式及位址。

2.1.設定IP位址
2.1.1.DHCP
設定
若您希望由DHCP取得IP位址可輸入
interface ip>set ad "
區域連接" DHCP
或簡寫成
interface ip>set ad "
區域連接" D

2.1.2.
靜態IP設定
2.1.2.1.
設定IP位址與子網關
netsh -
進入到 netsh mode
netsh>int -
進入到 interface 子選項。
interface>ip -
進入到 ip 子選項。
interface ip>set address name = "
區域連接" source = static addr = 10.2.2.100 mask = 255.255.255.0
可簡寫成
interface ip>set ad "
區域連接" s 10.2.2.100 255.255.255.0

2.1.2.2.
設定IP路由
interface ip>set address name = "
區域連接" gateway = 10.2.2.254 gwmetric = 1
可簡寫成
interface ip>set ad "
區域連接" ga=10.2.2.254 gw = 1

2.1.2.3
同時設定IP位址和路由
interface ip>set address name = "
區域連接" source = static addr = 10.2.2.100 mask = 255.255.255.0 gateway = 10.2.2.254 gwmetric = 1
可簡寫成
interface ip>set ad "
區域連接" s 10.2.2.100 255.255.255.0 10.2.2.254 1

設定完後,記得用sh ad去看一下設定的對不對。

2.3
設定DNS來源
若是由DHCP取得,請輸入
interface ip>set dns "
區域連接" source=dhcp
若是使用靜態設定,請輸入
interface ip>set dns name = "
區域連接" source = static addr = 10.2.5.2
新增第二組DNS,請輸入
interface ip>add dns name = "
區域連接" addr = 10.2.5.3

2.4
設定WINS來源
若是由DHCP取得,請輸入
interface ip>set wins "
區域連接" source=dhcp
若是使用靜態設定,請輸入
interface ip>set wins name = "
區域連接" source = static addr = 10.2.5.10
新增第二組WINS,請輸入
interface ip>add wins name = "
區域連接" addr = 10.2.5.17


3.
將網路狀態設定導出/導入
3.1
導出
netsh -c interface dump >c:/netset.txt
當然,interface可以簡寫成intdump更可簡化成d,所以就變成了
netsh -c int d >c:/netset.txt
3.2
導入
netsh -f c:/netset.txt
既可
這個指令我覺得蠻好用的,適用在幫客戶裝機時,我們先將網路狀態設定儲存起來。將來若
發生客戶不小心變更了設定等,任何網路設定上的問題,可以執行一個批次檔,呼叫netsh
把設定取回。不然有時候,網路設定跑掉了,想用VNC連線修改都不行。
------------------------------------------------------------------------------------------------------------------
( 這個blog系統自動把windows的路徑分隔符號右斜線自動屏蔽了,我只好用/代替。閱讀時候請注意) netsh 是windows系統本身提供的功能強大的網絡配置命令行工具。

導出配置腳本:?? netsh -c interface ip dump > c:/interface.txt
導入配置腳本:?? netsh -f c:/interface.txt
進入netsh環境后,在根級目錄用exec命令也可以加載一個配置腳

本。還有對wins、route、ras等網絡服務的配置也可以通過Netsh的內置命令操作。

下面是配置示例:



C:/Documents and Settings/Administrator>netsh /?

用法: netsh [-a AliasFile] [-c Context] [-r RemoteMachine] [-u [DomainName]UserName] [-p Password | *]
???????????? [Command | -f ScriptFile]

下列指令有效:

此上下文中的命令:
?????????????? - 顯示命令列表。
aaaa?????????? - 更改到 `netsh aaaa' 上下文。
add??????????? - 在項目列表上添加一個配置項目。
delete???????? - 在項目列表上刪除一個配置項目。
dhcp?????????? - 更改到 `netsh dhcp' 上下文。
diag?????????? - 更改到 `netsh diag' 上下文。
dump?????????? - 顯示一個配置腳本。
exec?????????? - 運行一個腳本文件。
help?????????? - 顯示命令列表。
interface????? - 更改到 `netsh interface' 上下文。
ipsec????????? - 更改到 `netsh ipsec' 上下文。
ras??????????? - 更改到 `netsh ras' 上下文。
routing??????? - 更改到 `netsh routing' 上下文。
rpc??????????? - 更改到 `netsh rpc' 上下文。
set??????????? - 更新配置設置。
show?????????? - 顯示信息。
wins?????????? - 更改到 `netsh wins' 上下文。

下列的子上下文可用:
aaaa dhcp diag interface ipsec ras routing rpc wins



家庭網絡自動配置:home.cmd?? ip-home.txt

home.cmd:

@echo off
echo.
echo ************ Ip切換器 By 蔣進平 ************
echo.
echo 正在設置成家庭網絡IP,請稍等 . . .& netsh -f c:/iphome.txt
echo 設置成功,現在可以使用家庭網絡了
echo.
echo ************ 2004 年 8 月 30 號 ************
echo.
pause
exit

ip-home.txt :

# ----------------------------------
# 接口 IP 配置
# ----------------------------------
pushd interface ip


# "本地連接" 的接口 IP? 配置

set address name="本地連接" source=static addr=192.168.0.10 mask=255.255.255.0
set address name="本地連接" gateway=192.168.0.1 gwmetric=0
set dns name="本地連接" source=static addr=211.99.129.210 register=NONE
add dns name="本地連接" addr=211.99.129.211 index=2
set wins name="本地連接" source=static addr=none

popd
# 接口 IP 配置結束

XP的各種網絡命令
1、net系列命令
2、TCP/IP命令
3、netsh工具
一、net系列命令
net help
net send
net start


1.1 網絡信使
net send? 注意不能跨網段
net stop messenger 停止信使服務,也可以在“服務”修改 net start messenger 開始信使服務
1.2 在網絡鄰居上隱藏你的計算機
net config server /hidden:yes
net config server /hidden:no 則為開啟
二、TCP/IP命令
ipconfig
arp
netstat
ping
nslookup
tracert
route

三、netsh工具
netsh.exe可以用來配置TCP/IP設置:IP地址、子網掩碼、默認網關、DNS和WINS地址和其他選項。
3.1 顯示TCP/IP設置
netsh interface ip show config
3.2 配置IP地址
netsh interface ip set address name=“本地連接" static 192.168.0.100 255.255.255.0 192.168.0.1 1
3.3 export your current IP settings
netsh -c interface dump > c:\location1.txt

3.4 import your IP settings
netsh -f c:\location1.txt
或者是? netsh exec c:\location2.txt
3.5 自動獲得IP地址和DNS地址
netsh interface ip set address “本地連接” dhcp
netsh interface ip set dns “本地連接” dhcp
3.6 configure DNS and WINS addresses
netsh interface ip set dns “本地連接" static 192.168.0.200
netsh interface ip set wins “本地連接" static 192.168.0.200

----------------------------------------------------------------------------------------------------------------
快速更改自己的IP地址
2007-01-31 11:00

第一招:批處理

我們知道在命令行下用netsh命令更改IP的步驟是:
1。在運行欄裡輸入cmd打開命令提示符
2。輸入netsh?回車
3。輸入int?ip?回車
4。輸入set?address?name="本地連接"?source="static"?addr=ip?mask=255.255.255.0?Gateway?1

解釋一下:
set?address?是更改IP的命令
name?=?你要更改IP的連接名稱
source?=?設置成靜態的IP
addr?=?要更改成的IP
mask=子網掩碼
gateway是你的網關IP,后面的1是到達網關的躍點數


等待幾秒鐘會出現一個”確定“的信息,表示你的IP已經更改成功了,不信用ipconfig?/all檢驗一下。
知道了命令的用戶我們就可以把它寫成批處理如下:

@?echo?off
echo?This?Programe?will?change?your?Ipaddress?and?Gateway.
echo?Press?any?key?to?continue
pause?>nul

rem?設置變量
set?Nic=本地連接??
rem?//可以根據你的需要更改,
set?Add=202.96.134.9?
rem?//可以根據你的需要更改
set?Gat=202.96.134.60

netsh?interface?ip?set?address?name=%Nic%?source=static?addr=%add%?mask=255.255.255.0?%Gat%?1
rem?//順便把DNS也改掉
netsh?interface?ip?set?dns?name=%Nic%?source=static?addr=%add%?primary

echo?OK!

注:把上面代碼復制到空白的記事本裡,把“Nic=、Add=?Gat=”更改成你自己的值然后另存為*.bat即可

第二招?利用Dump導出導入配置文件

在命令提示符下輸入netsh?-c?int?ip?dump?>c:\net.txt
然后打開C盤,你將會看到一個net.txt的文本文檔打開它會看到下列信息
注:各人電腦上的信息會有所不同

#?----------------------------------?
#?介面?IP?設定?????????
#?----------------------------------?
pushd?interface?ip


#?"Local?Area?Connection"?的介面?IP?設定

set?address?name="Local?Area?Connection"?source=static?addr=202.96.134.9?mask=255.255.255.0
set?address?name="Local?Area?Connection"?gateway=202.96.134.60?gwmetric=0
set?dns?name="Local?Area?Connection"?source=static?addr=202.96.134.60?register=PRIMARY
add?dns?name="Local?Area?Connection"?addr=202.96.134.1?index=2
set?wins?name="Local?Area?Connection"?source=static?addr=none

popd
#?介面?IP?設定結束,把“addr=、gateway=?改成你自巳的值即可?注意第一個addr?=后面跟著的是你的IP地址、第二個addr?=?后面跟著的是你的主DNS地址,更改后把它別存為net1.txt。再次打開命令提示符,輸入netsh?-f?c:\net1.txt,稍等一會,使用Ipconfig?/all查查看ip是不是已經更改成功了。以后你就可以使用netsh?-f?c:\net1.txt
或進netsh?-f?c:\net.txt?在兩者之間快速切換了。當然你也可以把它們寫成兩個批處理或者創建一個快捷方式更方便的執行。

第三招?利用Netsh?的exec命令

打開記事本輸入
int?ip
set?address?name="Local?Area?Connection"?source=static?addr=202.96.134.9?mask=255.255.255.0?202.96.134.60?1
set?dns?name="Local?Area?Connection"?source=static?addr=202.96.134.60?register=PRIMARY
注:addr=更改成你自己的值。
然后把它另存為c:\*.sh
打開命令提示符輸入netsh?exec?c:\*.sh

稍等一會你的IP就更改成功了。

利用上面的三種方法再加以優化我相信你一定會把更改IP做得更好更簡單.例如我們可以把第三種方法改成一鍵更改IP地址。
新建了一個*.sh文件之后我們在桌面上新建一個快捷方式,命令指向為c:\windows\system32\netsh.exe?exec?c:\*.sh?把它取一個名字。然后右擊你剛創建的快捷方式切換到“快捷方式”選項卡在“快捷鍵”裡指定一個快捷鍵例如F6,在“運行方式”裡選擇“最小化”。單擊確定以后你只要按一下F6鍵就可以悄無聲息的更改IP了。夠快夠簡單吧!




?
1月29日

DOS常用命令

DOS 常用命令:

  dir 列文件名 deltree 刪除目錄樹 cls 清屏 cd 改變當前目錄

  copy 拷貝文件 diskcopy 復制磁盤 del 刪除文件 format 格式化磁盤

  edit 文本編輯 mem 查看內存狀況 md 建立子目錄 move 移動文件、改目錄名

  more 分屏顯示 type 顯示文件內容 rd 刪除目錄 sys 制作DOS系統盤

  ren 改變文件名 xcopy 拷貝目錄與文件 chkdsk 檢查磁盤 attrib 設置文件屬性

  fdisk 硬盤分區 date 顯示及修改日期 label 設置卷標號 defrag 磁盤碎片整理

  msd 系統檢測 path 設置搜尋目錄 share 文件共享 memmaker內存優化管理

  help 幫助 restore 恢復備份文件 set 設置環境變量 time 顯示及修改時間

  tree 列目錄樹 debug 隨機調試程序 doskey 重新調用DOS命令 prempt 設置提示符 undelete恢復被刪的文件 scandisk檢測、修理磁盤

  不常用DOS命令:

  diskcomp磁盤比較  append 設置非執行文件路徑

  expand 還原DOS文件 fasthelp快速顯示幫助信息

  fc 文件比較 interink啟動服務器

  setver 設置版本 intersvr啟動客戶機

  subst 路徑替換 qbasic Basic集成環境

  vsafe 防病毒 unformat恢復已格式化的磁盤

  ver 顯示DOS版本號 smartdrv設置磁盤加速器

  vol 顯示磁盤卷標號 lh 將程序裝入高端內存

  ctty 改變控制設備 emm386 擴展內存管理

  常用命令具體介紹:

  一、Dir

  顯示目錄文件和子目錄列表,呵呵,這個當然是人人要知道的。

  可以使用通配符(? 和 *),?表通配一個字符,*表通配任意字符

  *.后綴  指定要查看后綴的文件。 上面其實也可以為“ . 后綴”,例如dir *.exe 等于dir .exe

  /p  每次顯示一個列表屏幕。要查看下一屏,請按鍵盤上的任意鍵。

  /w  以寬格式顯示列表,在每一行上最多顯示 5 個文件名或目錄名。

  /s  列出指定目錄及所有子目錄中出現的每個指定的文件名。比win環境下的查找快多了

  dir *.* -> a.txt 把當前目錄文件列表寫入a.txt

  dir *.* /s -> a.txt 把當前目錄文件列表寫入a.txt,包括子目錄下文件。

  二、Attrib

  顯示、設置或刪除指派給文件或目錄的只讀、存檔、系統以及隱藏屬性。如果在不含參數的情況下使用,則 attrib 會顯示當前目錄中所有文件的屬性。

  +r  設置只讀屬性。

  -r  清除只讀屬性。

  +a  設置存檔文件屬性。

  -a  清除存檔文件屬性。

  +s  設置系統屬性。

  -s  清除系統屬性。

  +h  設置隱藏屬性。

  -h  清除隱藏屬性。

  三、Cls

  清除顯示在命令提示符窗口中的所有信息,并返回空窗口,即“清屏”

  四、Exit

  退出當前命令解釋程序并返回到系統。

  五、format

  格式化

  /q  執行快速格式化。刪除以前已格式化卷的文件表和根目錄,但不在扇區之間掃描損壞區域。使用 /q 命令行選項應該僅格式化以前已格式化的完好的卷。

  六、Ipconfig

  顯示所有當前的 TCP/IP 網絡配置值、刷新動態主機配置協議 (DHCP) 和域名系統 (DNS) 設置。使用不帶參數的 ipconfig 可以顯示所有適配器的 IP 地址、子網掩碼、默認網關。

  /all  顯示所有適配器的完整 TCP/IP 配置信息。

  ipconfig 等價于 winipcfg,后者在ME、98 和 95 上可用。盡管 Windows XP 沒有提供象 winipcfg 命令一樣的圖形化界面,但可以使用“網絡連接”查看和更新 IP 地址。要做到這一點,請打開 網絡連接,右鍵單擊某一網絡連接,單擊“狀態”,然后單擊“支持”選項卡。

  該命令最適用于配置為自動獲取 IP 地址的計算機。它使用戶可以確定哪些 TCP/IP 配置值是由 DHCP、自動專用 IP 地址 (APIPA) 和其他配置配置的。

  七、md

  創建目錄或子目錄

  八、Move

  將一個或多個文件從一個目錄移動到指定的目錄。

  九、Nbtstat

  顯示本地計算機和遠程計算機的基于 TCP/IP (NetBT) 協議的 NetBIOS 統計資料、NetBIOS 名稱表和 NetBIOS 名稱緩存。Nbtstat 可以刷新 NetBIOS 名稱緩存和注冊的 Windows Internet 名稱服務 (WINS) 名稱。使用不帶參數的 nbtstat 顯示幫助。Nbtstat 命令行參數區分大小寫。

  -a remotename  顯示遠程計算機的 NetBIOS 名稱表,其中,RemoteName 是遠程計算機的 NetBIOS 計算機名稱。

  -A IPAddress  顯示遠程計算機的 NetBIOS 名稱表,其名稱由遠程計算機的 IP 地址指定(以小數點分隔)。

  十、Netstat

  顯示活動的 TCP 連接、計算機偵聽的端口、以太網統計信息、IP 路由表、IPv4 統計信息(對于 IP、ICMP、TCP 和 UDP 協議)以及 IPv6 統計信息(對于 IPv6、ICMPv6、通過 IPv6 的 TCP 以及通過 IPv6 的 UDP 協議)。使用時如果不帶參數,netstat 顯示活動的 TCP 連接。

  -a  顯示所有活動的 TCP 連接以及計算機偵聽的 TCP 和 UDP 端口。

  十一、Ping

  通過發送“網際消息控制協議 (ICMP)”回響請求消息來驗證與另一臺 TCP/IP 計算機的 IP 級連接。回響應答消息的接收情況將和往返過程的次數一起顯示出來。Ping 是用于檢測網絡連接性、可到達性和名稱解析的疑難問題的主要 TCP/IP 命令。如果不帶參數,ping 將顯示幫助。名稱和Ip地址解析是它的最簡單應用也是用的最多的。

  -t  指定在中斷前 ping 可以持續發送回響請求信息到目的地。要中斷并顯示統計信息,請按 CTRL-BREAK。要中斷并退出 ping,請按 CTRL-C。

  -lSize  指定發送的回響請求消息中“數據”字段的長度(以字節表示)。默認值為 32。size 的最大值是 65,527。

  十二、Rename (Ren)

  更改文件的名稱。

  例如 ren *.abc *.cba

  十三、Set

  顯示、設置或刪除環境變量。如果沒有任何參數,set 命令將顯示當前環境設置。

  十四、Shutdown

  允許您關閉或重新啟動本地或遠程計算機。如果沒有使用參數,shutdown 將注銷當前用戶。

  -m ComputerName  指定要關閉的計算機。

  -t xx  將用于系統關閉的定時器設置為 xx 秒。默認值是 20 秒。

  -l  注銷當前用戶,這是默認設置。-m ComputerName 優先。

  -s  關閉本地計算機。

  -r  關閉之后重新啟動。

  -a  中止關閉。除了 -l 和 ComputerName 外,系統將忽略其它參數。在超時期間,您只可以使用 -a。

  十五、System File Checker (sfc)

  win下才有,在重新啟動計算機后掃描和驗證所有受保護的系統文件。

  /scannow  立即掃描所有受保護的系統文件。

  /scanonce  一次掃描所有受保護的系統文件。

  /purgecache  立即清除“Windows 文件保護”文件高速緩存,并掃描所有受保護的系統文件。

  /cachesize=x  設置“Windows 文件保護”文件高速緩存的大小,以 MB 為單位。

  十六、type

  顯示文本文件的內容。使用 type 命令查看文本文件或者是bat文件而不修改文件

  十七、Tree

  圖像化顯示路徑或驅動器中磁盤的目錄結構。

  十八、Xcopy

  復制文件和目錄,包括子目錄。

  /s  復制非空的目錄和子目錄。如果省略 /s,xcopy 將在一個目錄中工作。

  /e  復制所有子目錄,包括空目錄。

  十九、copy

  將一個或多個文件從一個位置復制到其他位置

  二十、del

  刪除指定文件。

  ftp和bat批命令和net和telnet由于子命令太多,這里不說了,不過這幾個都是常用到的。

在DOS下輸入MD X:\文件名..\ 回車

所創建的文件夾,直接雙擊是打不開的,也刪除不了.

例:在F盤創建一個名為123的絕密文件夾,再刪除他.

在運行里輸入CMD回車

輸入MD F:\123..\ 回車

打開F盤時會看到有一個名為:"123."的文件夾,雙擊打開時,出現了一個對話框說.........不能打開.

在運行里輸入F:\123..\ 試一試看 會發現該文件被打開了

但是要怎樣才能刪除此文件呢?請往下看.

在DOS下輸入RD F:\123..\ 回車

如果該文件下還有其他文件.就要在后面加 /S 也就是RD F:\123..\ /S 回車.

覺的好的話.就頂吧!

方法一:用copy con DOS內部命令
a:\>copy con lx.txt
I'm a teacher
按ctrl+Z或者F6鍵
即可
方法二:用edit DOS外部命令
?在Windows 98下的MS-DOS下,用鍵盤新建文件,文件內容為:“I'm a teacher.”,文件名為LX.TXT,文件保存在A盤。

在模擬環境中運行的,其實在實際環境中我在命令行下鍵入:echo "I'm a teacher." > A:\LX.TXT,可以達到目的,但是在模擬環境中卻不行,哪位知道是怎么回事啊?謝謝!
1月26日

CAsyncSocket對象不能跨線程之分析

現象

用多線程方法設計socket程序時,你會發現在跨線程使用CAsyncSocket及其派生類時,會出現程序崩潰。所謂跨線程,是指該對象在一個線程中調用Create/AttachHandle/Attach函數,然后在另外一個線程中調用其他成員函數。下面的例子就是一個典型的導致崩潰的過程:
CAsyncSocket Socket;
UINT Thread(LPVOID)
{
       Socket.Close ();
       return 0;
}
void CTestSDlg::OnOK() 
{
       // TODO: Add extra validation here
       Socket.Create(0);
       AfxBeginThread(Thread,0,0,0,0,0);
}

其中Socket對象在主線程中被調用,在子線程中被關閉。

跟蹤分析

這個問題的原因可以通過單步跟蹤(F11)的方法來了解。我們在Socket.Create(0)處設斷點,跟蹤進去會發現下面的函數被調用:

void PASCAL CAsyncSocket::AttachHandle(
          SOCKET hSocket, CAsyncSocket* pSocket, BOOL bDead)
{
    _AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState;
    BOOL bEnable = AfxEnableMemoryTracking(FALSE);
    if (!bDead)
    {
             ASSERT(CAsyncSocket::LookupHandle(hSocket, bDead) == NULL);
             if (pState->m_pmapSocketHandle->IsEmpty())
             {
                  ASSERT(pState->m_pmapDeadSockets->IsEmpty());
                  ASSERT(pState->m_hSocketWindow == NULL);
                  CSocketWnd* pWnd = new CSocketWnd;
                  pWnd->m_hWnd = NULL;
                  if (!pWnd->CreateEx(0, AfxRegisterWndClass(0),
                                   _T("Socket Notification Sink"),
                                 WS_OVERLAPPED, 0, 0, 0, 0, NULL, NULL))
                 {
                       TRACE0("Warning: unable to create socket notify window!\n");
                       AfxThrowResourceException();
                 }
                 ASSERT(pWnd->m_hWnd != NULL);
                 ASSERT(CWnd::FromHandlePermanent(pWnd->m_hWnd) == pWnd);
                 pState->m_hSocketWindow = pWnd->m_hWnd;
            }
            pState->m_pmapSocketHandle->SetAt((void*)hSocket, pSocket);
    }
    else
    {
           int nCount;
           if (pState->m_pmapDeadSockets->Lookup((void*)hSocket, (void*&)nCount))
                     nCount++;
           else
                     nCount = 1;
           pState->m_pmapDeadSockets->SetAt((void*)hSocket, (void*)nCount);
   }
   AfxEnableMemoryTracking(bEnable);
}

在這個函數的開頭,首先獲得了一個pState的指針指向_afxSockThreadState對象。從名字可以看出,這似乎是一個和線程相關的變量,實際上它是一個宏,定義如下:

#define _afxSockThreadState AfxGetModuleThreadState()

我們沒有必要去細究這個指針的定義是如何的,只要知道它是和當前線程密切關聯的,其他線程應該也有類似的指針,只是指向不同的結構。

在這個函數中,CAsyncSocket創建了一個窗口,并把如下兩個信息加入到pState所管理的結構中:

    pState->m_pmapSocketHandle->SetAt((void*)hSocket, pSocket);
    pState->m_pmapDeadSockets->SetAt((void*)hSocket, (void*)nCount);
    pState->m_hSocketWindow = pWnd->m_hWnd;
    pState->m_pmapSocketHandle->SetAt((void*)hSocket, pSocket);

當調用Close時,我們再次跟蹤,就會發現在KillSocket中,下面的函數出現錯誤:

    void PASCAL CAsyncSocket::KillSocket(SOCKET hSocket, CAsyncSocket* pSocket)
    {
            ASSERT(CAsyncSocket::LookupHandle(hSocket, FALSE) != NULL);

我們在這個ASSERT處設置斷點,跟蹤進LookupHandle,會發現這個函數定義如下:

CAsyncSocket* PASCAL CAsyncSocket::LookupHandle(SOCKET hSocket, BOOL bDead)
{
     CAsyncSocket* pSocket;
     _AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState;
     if (!bDead)
     {
             pSocket = (CAsyncSocket*)
             pState->m_pmapSocketHandle->GetValueAt((void*)hSocket);
             if (pSocket != NULL)
                  return pSocket;
    }
    else
    {
             pSocket = (CAsyncSocket*)
                  pState->m_pmapDeadSockets->GetValueAt((void*)hSocket);
             if (pSocket != NULL)
                   return pSocket;
    }
    return NULL;
}

顯然,這個函數試圖從當前線程查詢關于這個 socket的信息,可是這個信息放在創建這個socket的線程中,因此這種查詢顯然會失敗,最終返回NULL。

有人會問,既然它是ASSERT出錯,是不是Release就沒問題了。這只是自欺欺人。ASSERT/VERIFY都是檢驗一些程序正常運行必須正確的條件。如果ASSERT都失敗,在Release中也許不會顯現,但是你的程序肯定運行不正確,啥時候出錯就不知道了。

如何在多線程之間傳遞socket

有些特殊情況下,可能需要在不同線程之間傳遞socket。當然我不建議在使用CAsyncSOcket的時候這么做,因為這增加了出錯的風險(尤其當出現拆解包問題時,有人稱為粘包,我基本不認同這種稱呼)。如果一定要這么做,方法應該是:

  1. 當前擁有這個socket的線程調用Detach方法,這樣socket句柄和C++對象及當前線程脫離關系
  2. 當前線程把這個對象傳遞給另外一個線程
  3. 另外一個線程創建新的CAsyncSocket對象,并調用Attach

上面的例子,我稍微做修改,就不會出錯了:

CAsyncSocket Socket;
UINT Thread(LPVOID sock)
{
         Socket.Attach((SOCKET)sock);//如果在線程外close(),記得在離開時Detach().
         Socket.Close ();
         return 0;
}
void CTestSDlg::OnOK() 
{
         // TODO: Add extra validation here
         Socket.Create(0);
         SOCKET hSocket = Socket.Detach ();
         AfxBeginThread(Thread,(LPVOID)hSocket,0,0,0,0);
}
引用自:馨榮家園
----------------------------------------------------------------------------------------------------

本文以及另外兩篇相關文章解釋 Windows Sockets 編程方面的一些問題。本文介紹阻塞。其他問題包含在 Windows Sockets:字節排序Windows Sockets:轉換字符串文章中。

如果使用 CAsyncSocket 類或從其派生,則您需要自己管理這些問題。如果您使用 CSocket 類或從其派生,則由 MFC 管理它們。阻塞

套接字可以處于“阻塞模式”或“無阻塞模式”。處于阻塞(或同步)模式時,套接字的函數直到可以完成自己的操作時才返回。這稱為“阻塞”,因為函數被調用的套接字在調用返回前無法執行任何操作──它被阻塞了。例如,對 Receive 成員函數的調用可能需要任意長的時間才能完成,因為它要等待發送應用程序來發送(使用 CSocket 或使用帶阻塞的 CAsyncSocke 即是如此)。如果 CAsyncSocket 對象處于無阻塞模式(異步操作),調用會立即返回,而當前錯誤代碼(可使用 GetLastError 成員函數檢索)為 WSAEWOULDBLOCK ,它指出由于模式的原因,調用若不立即返回則將阻塞。( CSocket 永遠不返回 WSAEWOULDBLOCK 。該類為您管理阻塞。)

在 32 位操作系統(如 Windows 95 或 Windows 98)和 16 位操作系統(如 Windows 3.1)下,套接字的行為是不同的。與 16 位操作系統不同,32 位操作系統使用搶占式多任務處理技術并提供多線程運行方式。在 32 位操作系統下,可以將套接字放在單獨的輔助線程中。線程中的套接字可以在不妨礙應用程序中其他活動的情況下阻塞,并且不必在阻塞上花費計算時間。有關多線程編程的信息,請參見文章多線程編程。

注意: 在多線程應用程序中,可以使用 CSocket 的阻塞特性來簡化程序設計,而不影響用戶界面的響應。通過處理主線程中的用戶交互和備用線程中的 CSocket 處理,可以將這些邏輯操作分開。在非多線程的應用程序中,這兩個活動必須合并為單個線程來處理。這通常意味著使用 CAsyncSocket 以根據需要處理通信請求,或重寫 CSocket::OnMessagePending 以在漫長的同步活動中處理用戶操作。

?

其余的討論針對以 16 位操作系統為目標的程序員:

通常,如果使用的是 CAsyncSocket ,則應避免使用阻塞操作,而應使用異步操作。例如,在異步操作中,從調用 Receive 后接收到 WSAEWOULDBLOCK 錯誤代碼那一刻開始,您將一直等到 OnReceive 成員函數被調用以通知您可以再次讀取。通過回調套接字的適當回調通知函數(如 OnReceive)來完成異步調用。

在 Windows 下,阻塞調用被認為是錯誤的做法。默認情況下,CAsyncSocket 支持異步調用,而且您必須使用回調通知自己管理阻塞。另一方面,CSocket 類是同步的。它抽取 Windows 消息并為您管理阻塞。

有關阻塞的更多信息,請參見 Windows Sockets 規范。有關“On”函數的更多信息,請參見 Windows Sockets:套接字通知和 Windows Sockets:從套接字類派生。

本文以及另外兩篇相關文章解釋 Windows Sockets 編程方面的一些問題。本文介紹轉換字符串。其他問題在 Windows Sockets:阻塞Windows Sockets:字節排序中介紹。

如果使用 CAsyncSocket 類或從其派生,則您需要自己管理這些問題。如果您使用 CSocket 類或從其派生,則由 MFC 管理它們。轉換字符串

如果在使用以不同的寬字符格式(如 Unicode 或多字節字符集 MBCS)存儲的字符串的應用程序間通信,或在這些應用程序與使用 ANSI 字符串的應用程序間通信,您必須使用 CAsyncSocket 自己管理轉換。和 CSocket 對象一起使用的 CArchive 對象通過 CString 類的功能管理此轉換。有關更多信息,請參見位于 Platform SDK 中的 Windows Sockets 規范。

Windows Sockets:使用 CAsyncSocket 類

http://tech.163.com/school · 2005-10-09 16:08:40 · 來源: MSDN

?

本文介紹 CAsyncSocket 類的用法。請注意,該類在非常低的級別上封裝 Windows Sockets API。 CAsyncSocket 適合那些對網絡通信細節很了解,但希望利用回調的便利通知網絡事件的程序員使用。基于該假定,本文僅提供基本說明。如果想利用 Windows Sockets 方便地處理 MFC 應用程序中的多個網絡協議,而又不想放棄靈活性,可以考慮使用 CAsyncSocket 。您可能也會感覺到,自己直接編寫通信程序要比使用 CSocket 類的通用替換模型效果更好。

“MFC 參考”中對 CAsyncSocket 進行了描述。Visual C++ 也提供了位于 Platform SDK 中的 Windows Sockets 規范。具體細節由您決定。Visual C++ 不提供 CAsyncSocket 的示例應用程序。

如果您對網絡通信不是很了解,希望獲得一個簡單的解決方案,請使用帶 CArchive 對象的 CSocket 類。有關更多信息,請參見 Windows Sockets:使用帶存檔的套接字

本文包括:

  • 創建和使用 CAsyncSocket 對象。
  • 您具有的 CAsyncSocket 責任。
創建和使用 CAsyncSocket 對象

?

使用 CAsyncSocket

  1. 構造一個 CAsyncSocket 對象并使用該對象創建基礎 SOCKET 句柄。

    ?

    套接字的創建遵循兩階段構造的 MFC 模式。

    例如: CAsyncSocket sock;sock.Create( ); // Use the default parameters
    - 或 -
    CAsyncSocket* pSocket = new CAsyncSocket;int nPort = 27;pSocket-> Create( nPort, SOCK_DGRAM );

    上面的第一個構造函數在堆棧上創建一個 CAsyncSocket 對象,第二個構造函數在堆上創建 CAsyncSocket 。上面的第一個 Create 調用使用默認參數創建流式套接字,第二個 Create 調用創建具有指定端口和地址的數據文報套接字。(任一個 Create 版本都可以和任一種構造方法一起使用。)

    Create 的參數有:

    • “端口”:短整型。

      ?

      對于服務器套接字,必須指定端口。對于客戶端套接字,通常接受此參數的默認值,該值允許 Windows Sockets 選擇端口。

    • 套接字類型: SOCK_STREAM (默認值)或 SOCK_DGRAM
    • 套接字“地址”,如“ftp.microsoft.com”或“128.56.22.8”。

      ?

      該地址為網絡上的網際協議 (IP) 地址。很可能要始終依賴此參數的默認值。

    ?

    關于術語“端口”和“套接字地址”的解釋見 Windows Sockets:端口和套接字地址

  2. 如果套接字是客戶端,則使用 CAsyncSocket::Connect 將此套接字對象連接到服務器套接字。

    ?

    - 或 -

    如果套接字是服務器,則將套接字設置為開始偵聽(使用 CAsyncSocket::Listen)來自客戶端的連接嘗試。接收到連接請求時,用 CAsyncSocket::Accept 接受該請求。

    接受連接后,可以執行驗證密碼等任務。

    注意 Accept 成員函數采用對新的空 CSocket 對象的引用作為它的參數。在調用 Accept 之前,必須構造該對象。如果此套接字對象超出范圍,則連接關閉。不要對這個新套接字對象調用 Create 。有關示例,請參見文章 Windows Sockets:操作順序
  3. 通過調用 CAsyncSocket 對象的封裝 Windows Sockets API 函數的成員函數,與其他套接字進行通信。

    ?

    請參見“MFC 參考”中的 Windows Sockets 規范和 CAsyncSocket 類。

  4. 銷毀 CAsyncSocket 對象。

    ?

    如果在堆棧上創建了套接字對象,當包含函數超出范圍時將調用此對象的析構函數。如果使用 new 運算符在堆上創建了套接字對象,則您必須負責使用 delete 運算符銷毀此對象。

    析構函數在銷毀對象之前調用對象的 Close 成員函數。

?

有關代碼中該順序的示例(實際上是對于 CSocket 對象),請參見 Windows Sockets:操作順序。您對 CAsyncSocket 的責任

創建 CAsyncSocket 類的對象后,該對象封裝 Windows SOCKET 句柄并提供對此句柄的操作。使用 CAsyncSocket 時,如果您直接使用 API,則必須處理可能面對的所有問題。例如:

  • “阻塞”方案。
  • 發送和接收計算機之間的字節順序差異。
  • 在 Unicode 和多字節字符集 (MBCS) 字符串之間轉換。

?

有關這些術語的定義和其他信息,請參見 Windows Sockets:阻塞Windows Sockets:字節排序Windows Sockets:轉換字符串

盡管存在這些問題,但如果應用程序需要您能獲得所有的靈活性和控制能力, CAsycnSocket 類可能是正確的選擇。如果應用程序沒有這種需求,可考慮使用 CSocket 類。 CSocket 向您隱藏大量詳細信息:它在阻塞調用期間抽取 Windows 消息并賦予您訪問 CArchive 的權限,而 CArchive 為您管理字節順序差異和字符串轉換。

有關更多信息,請參見:

    Windows Sockets:背景知識

    Windows Sockets:流式套接字

    Windows Sockets:數據文報套接字

    Windows Sockets:使用帶存檔的套接字

    http://tech.163.com/school · 2005-10-09 15:22:30 · 來源: MSDN

    ?

    本文描述 CSocket 編程模型。CSocket 類提供了比 CAsyncSocket 類抽象化級別更高的套接字支持。CSocket 使用 MFC 序列化協議的一種版本,通過 MFC CArchive 對象將數據傳遞給套接字對象,或者從套接字對象傳出數據。CSocket 提供阻塞(同時管理 Windows 消息的后臺處理),并賦予您訪問 CArchive 的權限,而 CArchive 則管理著必須由您自己使用原始 API 或 CAsyncSocket 類來管理的通信的許多方面。

    提示:可以單獨使用 CSocket 類作為 CAsyncSocket 的更方便版本,但最簡單的編程模型是使用帶 CArchive 對象的 CSocket

    ?

    有關帶存檔的套接字實現的工作機制的更多信息,請參見 Windows Sockets:帶存檔的套接字的工作方式。有關示例代碼,請參見 Windows Sockets:操作順序Windows Sockets:帶存檔的套接字示例。有關通過從套接字類派生自己的類獲得的某些功能的信息,請參見 Windows Sockets:從套接字類派生

    注意:如果正在編寫與已建立的(非 MFC)服務器通信的 MFC 客戶程序,則不要通過存檔發送 C++ 對象。除非該服務器是一個 MFC 應用程序,它知道您要發送的對象的類型,否則服務器將無法接收和反序列化這些對象。有關與非 MFC 應用程序通信的主題的相關材料,另請參見文章 Windows Sockets:字節排序
    CSocket 編程模型

    ?

    使用 CSocket 對象涉及創建數個 MFC 類對象并將它們關聯起來。在下面的一般過程中,服務器套接字和客戶端套接字都將采取每一步驟(步驟 3 除外,此步驟中每個套接字類型要求不同的操作)。

    提示:在運行時,服務器應用程序通常首先做好準備然后“偵聽”客戶端應用程序何時尋求連接。如果客戶端嘗試連接時服務器未準備好,一般需要用戶應用程序稍后再嘗試連接。

    ?

    設置服務器套接字和客戶端套接字之間的通信

    1. 構造一個 CSocket 對象。
    2. 使用此對象創建基礎 SOCKET 句柄。

      ?

      對于 CSocket 客戶端對象,除非需要數據文報套接字,否則通常應使用默認參數來 Create 該對象。對于 CSocket 服務器對象,則必須在 Create 調用中指定端口。

      注意:CArchive 不適用于數據文報套接字。如果想將 CSocket 用于數據文報套接字,必須像使用 CAsyncSocket 那樣使用該類,即不帶存檔。因為數據文報是不可靠的(不保證送達,并且可能重復或順序不對),它們不能通過存檔與序列化兼容。而您期望序列化操作可以可靠地、按順序完成。如果試圖將帶 CArchive 對象的 CSocket 用于數據文報,則 MFC 斷言失敗。
    3. 如果套接字是客戶端對象,則調用 CAsyncSocket::Connect 將此套接字對象連接到服務器套接字。

      ?

      - 或 -

      如果套接字是服務器端對象,則調用 CAsyncSocket::Listen 開始偵聽來自客戶端的連接嘗試。接收到連接請求時,調用 CAsyncSocket::Accept 接受該請求。

      注意:Accept 成員函數采用對新的空 CSocket 對象的引用作為它的參數。在調用 Accept 之前,必須構造該對象。如果此套接字對象超出范圍,則連接關閉。不要對這個新套接字對象調用 Create
    4. 創建一個 CSocketFile 對象,將 CSocket 對象與它關聯起來。
    5. 創建一個 CArchive 對象用于加載(接收)或存儲(發送)數據。此存檔與 CSocketFile 對象關聯。

      ?

      注意:CArchive 不適用于數據文報套接字。

    6. 使用 CArchive 對象在客戶端套接字與服務器套接字之間傳遞數據。

      ?

      注意,不管是加載(接收)還是存儲(發送),給定的 CArchive 對象只在一個方向上移動數據。某些情況下,需要使用兩個 CArchive 對象:一個用于發送數據,一個用于接收確認。

      接受連接并設置存檔后,可以執行驗證密碼之類的任務。

    7. 銷毀存檔、套接字文件和套接字對象。
      注意CArchive 類提供了專門與 CSocket 類一起使用的 IsBufferEmpty 成員函數。例如,如果緩沖區包含多條數據消息,則需要一直循環到讀完所有消息和清空緩沖區。否則,下一個指示有數據要接收的通知可能會無限期延遲。使用 IsBufferEmpty 可確保檢索所有數據。有關使用 IsBufferEmpty 的示例,請參見 CHATSRVR 示例應用程序。有關 MFC 示例的源代碼和信息,請參見 MFC 示例。

    ?

    Windows Sockets:操作順序一文用示例代碼闡釋了此進程的兩端。

    Windows Sockets:背景知識

    http://tech.163.com/school · 2005-10-09 14:54:00 · 來源: MSDN

    ?

    本文介紹 Windows Sockets 的性質和用途。其他內容還包括:

    • 定義術語“套接字”。
    • 描述 SOCKET 句柄數據類型。
    • 描述套接字的用途。

    ?

    Windows Sockets 規范為 Microsoft Windows 定義了一個二進制兼容網絡編程接口。Windows Sockets 基于 Berkeley Software Distribution(BSD,4.3 版)中的 UNIX 套接字實現,后者是美國加州大學伯克利分校開發的。該規范包括針對 Windows 的 BSD 樣式套接字例程和擴展。通過使用 Windows Sockets,應用程序能夠在任何符合 Windows Sockets API 的網絡上通信。在 Win32 上,Windows Sockets 提供線程安全。

    許多網絡軟件供應商支持網絡協議下的 Windows Sockets,這些協議包括:傳輸控制協議/網際協議 (TCP/IP)、Xerox 網絡系統 (XNS)、Digital Equipment Corporation 的 DECNet 協議和 Novell Corporation 的互聯網包交換協議/順序分組報文交換協議 (IPX/SPX) 等。雖然目前的 Windows Sockets 規范定義了 TCP/IP 的套接字抽象化,但任何網絡協議都可以通過提供自己版本的、實現 Windows Sockets 的動態鏈接庫 (DLL) 來滿足 Windows Sockets。用 Windows Sockets 編寫的商用應用程序示例包括 X Windows 服務器、終端模擬器和電子郵件系統。

    注意: Windows Sockets 的用途是將基礎網絡抽象出來,這樣,您不必對網絡非常了解,并且您的應用程序可在任何支持套接字的網絡上運行。因此,本文檔不討論網絡協議的細節內容。

    ?

    Microsoft 基礎類庫 (MFC) 通過提供兩個類來支持使用 Windows Sockets API 進行編程。其中一個類為 CSocket ,它提供高級抽象化來簡化網絡通信編程。

    Windows Sockets 規范“Windows Sockets:用于 Microsoft Windows 環境下的網絡計算的開放接口”現在為 1.1 版本,它是 TCP/IP 群體中一個由個人和公司組成的大團體開發的,是一個開放的網絡標準,可免費使用。套接字編程模型當前支持一個“通信域”,該“通信域”使用網際協議組 (Internet Protocol Suite)。該規范可在 Platform SDK 中獲得。

    提示: 因為套接字使用網際協議組,所以它們對于支持“信息高速公路”上 Internet 通信的應用程序是首選方式。

    套接字的定義

    ?

    套接字是一個通信終結點,它是 Windows Sockets 應用程序用來在網絡上發送或接收數據包的對象。套接字具有類型,與正在運行的進程相關聯,并且可以有名稱。目前,套接字一般只與使用網際協議組的同一“通信域”中的其他套接字交換數據。

    這兩種套接字都是雙向的,是可以同時在兩個方向上(全雙工)進行通信的數據流。

    可用的套接字類型有以下兩種:

    • 流式套接字

      ?

      流式套接字提供沒有記錄邊界的數據流,即字節流。字節流能確保以正確的順序無重復地被送達。

    • 數據文報套接字

      ?

      數據文報套接字支持面向記錄的數據流,但不能確保能被送達,也無法確保按照發送順序或不重復。

    ?

    “有序”指數據包按發送的順序送達。“不重復”指一個特定的數據包只能獲取一次。

    注意: 在某些網絡協議下(如 XNS),流可以面向記錄,即作為記錄流而非字節流。但在更常用的 TCP/IP 協議下,流為字節流。Windows Sockets 提供與基礎協議無關的抽象化級別。

    ?

    有關上述類型以及各種套接字適用情形的信息,請參見 Windows Sockets:流式套接字Windows Sockets:數據文報套接字

    SOCKET 數據類型

    每一個 MFC 套接字對象封裝一個 Windows Sockets 對象的句柄。該句柄的數據類型為 SOCKET。SOCKET 句柄類似于窗口的 HWND。MFC 套接字類提供對封裝句柄的操作。

    Platform SDK 中詳細描述了 SOCKET 數據類型。

    套接字的用途

    套接字的作用非常大,至少在下面三種通信上下文中如此:

    • 客戶端/服務器模型。
    • 對等網絡方案,如聊天應用程序。
    • 通過讓接收應用程序將消息解釋為函數調用來進行遠程過程調用 (RPC)。
    提示: 最適合使用 MFC 套接字的情況是當同時編寫通信的兩端時:在兩端都使用 MFC。有關該主題(包括如何管理與非 MFC 應用程序通信的情況)的更多信息,請參見 Windows Sockets:字節排序

    Windows Sockets:流式套接字

    http://tech.163.com/school · 2005-10-09 15:03:48 · 來源: MSDN

    ?

    本文描述流式套接字,它是兩種可用的 Windows Sockets 類型中的一種。(另一種類型是數據文報套接字。)

    流式套接字提供沒有記錄邊界的數據流:可以是雙向的字節流(應用程序是全雙工:可以通過套接字同時傳輸和接收)。可依賴流傳遞有序的、不重復的數據。(“有序”指數據包按發送順序送達。“不重復”指一個特定的數據包只能獲取一次。)這能確保收到流消息,而流非常適合處理大量數據。

    網絡傳輸層可將數據拆分為或分組為若干個大小適當的數據包。 CSocket 類將為您處理打包和解包。

    流基于顯式連接:套接字 A 請求與套接字 B 建立連接;套接字 B 接受或拒絕此連接請求。

    打電話的情況與流非常相似:正常情況下,接聽方聽到您的話和您講話時的順序一樣,沒有重復和遺漏。流套接字適合文件傳輸協議 (FTP) 這類實現,此協議有利于傳輸任意大小的 ASCII 或二進制文件。

    如果必須保證數據送達而且數據大小很大時,流式套接字優于數據文報套接字。有關流式套接字的更多信息,請參見 Windows Sockets 規范。該規范可在 Platform SDK 中獲得。

    MFC 示例 CHATTERCHATSRVR 都使用流式套接字。這些示例可能已經設計為使用數據文報套接字向網絡上的所有接收套接字廣播。而目前的設計更好,這是因為:

    • 廣播模型受制于網絡“洪水”(或“風暴”)問題。
    • 后來采用的客戶端-服務器模型更有效。
    • 流式模型提供可靠的數據傳輸,數據文報模型則未提供。
    • 最終模型利用在 CArchive 類借給 CSocket 類的 Unicode 和 ANSI 套接字應用程序之間通信的能力。

    注意: 如果使用 CSocket 類,則必須使用流。如果將套接字類型指定為 SOCK_DGRAM ,則 MFC 斷言失敗。

    Windows Sockets:數據文報套接字

    http://tech.163.com/school · 2005-10-09 15:15:43 · 來源: MSDN

    ?

    本文描述數據文報套接字,它是兩種可用的 Windows Sockets 類型中的一種。(另一種類型是 流式套接字。)

    數據文報套接字支持雙向數據流,此數據留不能保證按順序和不重復送達。數據文報也不保證是可靠的;它們可能無法到達目的地。數據文報可能不按順序到達并且可能會重復,但只要記錄的大小沒有超過接收端的內部大小限制,就會保持數據中的記錄邊界。您負責管理順序和可靠性。(可靠性在局域網 [LAN] 上往往很好,但在廣域網 [WAN] 如 Internet 上卻不太好。)

    數據文報為“無連接”的,也就是不建立顯式連接。可將數據文報消息發送到指定的套接字,然后從指定的套接字接收消息。

    數據文報套接字的一個示例是使網絡上的系統時鐘保持同步的應用程序。這闡釋了數據文報套接字的一個附加功能,即至少在某些設置中,向大量的網絡地址廣播消息。

    在面向記錄的數據方面,數據文報套接字優于流式套接字。有關數據文報套接字的更多信息,請參見 Platform SDK 中的 Windows Sockets 規范。

    Windows Sockets:字節排序

    http://tech.163.com/school · 2005-10-09 16:30:27 · 來源: MSDN

    ?

    本文以及另外兩篇相關文章解釋 Windows Sockets 編程方面的一些問題。本文介紹字節排序。其他問題在文章 Windows Sockets:阻塞Windows Sockets:轉換字符串中介紹。

    如果使用 CAsyncSocket 類或從其派生,則您需要自己管理這些問題。如果您使用 CSocket 類或從其派生,則由 MFC

    管理它們。

    字節排序

    不同的計算機結構有時使用不同的字節順序存儲數據。例如,基于 Intel 的計算機存儲數據的順序與 Macintosh

    (Motorola) 計算機相反。Intel 字節順序稱為“Little-Endian”,與網絡標準“Big-Endian”順序也相反。下表解

    釋這些術語。

    Big-Endian 和 Little-Endian 字節排序

    字節排序 含義
    Big-Endian 最重要的字節在詞的左端。
    Little-Endian 最重要的字節在詞的右端。

    通常,您不必為在網絡上發送和接收的數據的字節順序轉換擔心,但在有些情況下,您必須轉換字節順序。

    何時必須轉換字節順序

    在下列情況中需要轉換字節順序:

    • 傳遞的信息需要由網絡解釋(與發送到另一臺計算機的數據相反)。例如,可能傳遞端口和地址,這些信息

    必須為網絡理解。

    • 與之通信的服務器應用程序不是 MFC 應用程序(并且沒有它的源代碼)。如果兩臺計算機不共享相同的字節

    排序,則需要字節順序轉換。

    何時不必轉換字節順序

    在下列情況下可以免去轉換字節順序的工作:

    • 兩端的計算機可以同意不交換字節,并且這兩臺計算機使用相同的字節順序。
    • 與之通信的服務器是 MFC 應用程序。
    • 有與之通信的服務器的源代碼,因此可以明確地判斷是否必須轉換字節順序。
    • 可以將服務器移植到 MFC。這樣做相當容易,所得到的通常是更小、更快的代碼。

    使用 CAsyncSocket 時,您必須自己管理任何必需的字節順序轉換。Windows Sockets 將“Big-Endian”字節順

    序模型標準化,并提供在該順序和其他順序之間轉換的函數。然而,與 CSocket 一起使用的 CArchive 使用相

    反的順序(“Little-Endian”),但 CArchive 為您管理字節順序轉換的細節。通過在應用程序中使用這種標準

    排序,或通過使用 Windows Sockets 字節順序轉換函數,可增強代碼的可移植性。

    最適合使用 MFC 套接字的情況是當編寫通信的兩端時:在兩端都使用 MFC。如果正在編寫將與非 MFC 應用程序

    通信的應用程序(如 FTP 服務器),則在向存檔對象傳遞數據前,您可能需要使用 Windows Sockets 轉換例程

    ntohs ntohl htons htonl 自己管理字節交換。本文稍后將給出這些用于與非 MFC 應用程序通信的函數

    示例。

    注意 當通信的另一端不是 MFC 應用程序時,也必須避免將從 CObject 派生的 C++ 對象以流的形式輸入存

    檔,因為接收端無法處理它們。請參見 Windows Sockets:使用帶存檔的套接字中的說明。

    有關字節順序的更多信息,請參見 Platform SDK 中的 Windows Sockets 規范。

    字節順序轉換示例

    下面的示例顯示使用存檔的 CSocket 對象的一個序列化函數。它還闡釋了在 Windows Sockets API 中如何使用

    字節順序轉換函數。

    該示例顯示這樣的情形:您正在編寫與非 MFC 服務器應用程序通信的客戶程序,而您沒有訪問該服務器應用程序

    源代碼的權限。在這種情況下,必須假設非 MFC 服務器使用標準的網絡字節順序。相反,MFC 客戶端應用程序對

    CSocket 對象使用 CArchive 對象,而 CArchive 使用與網絡標準相反的“Little-Endian”字節順序。

    假設要與之通信的非 MFC 服務器具有如下已建立的消息包協議:

    struct Message { long MagicNumber; unsigned short Command; short Param1; long Param2; }; 
    

    上述內容用 MFC 術語表示則為:

    struct Message { long m_lMagicNumber; short m_nCommand; short m_nParam1; long m_lParam2; void Serialize 
    
    ( CArchive& ar ); };

    在 C++ 中, struct 和類在本質上是一回事。 Message 結構可以有成員函數,如以上聲明的 Serialize 成員函數。

    Serialize 成員函數可能為如下形式: void Message::Serialize(CArchive& ar)
    {
    if (ar.IsStoring())
    {
    ar < < (DWORD)htonl(m_lMagicNumber);
    ar < < (WORD)htons(m_nCommand);
    ar < < (WORD)htons(m_nParam1);
    ar < < (DWORD)htonl(m_lParam2);
    }
    else
    {
    WORD w;
    DWORD dw;
    ar > > dw;
    m_lMagicNumber = ntohl((long)dw);
    ar > > w ;
    m_nCommand = ntohs((short)w);
    ar > > w;
    m_nParam1 = ntohs((short)w);
    ar > > dw;
    m_lParam2 = ntohl((long)dw);
    }
    }

    該示例要求進行數據字節順序轉換,因為一端的非 MFC 服務器應用程序的字節排序與另一端在 MFC 客戶端應用程序中使用的 CArchive 明顯不匹配。該示例闡釋了 Windows Sockets 提供的幾個字節順序轉換函數。下表描述了這些函數。

    Windows Sockets 字節順序轉換函數

    函數 作用
    ntohs 將 16 位數量從網絡字節順序轉換為主機字節順序(從 Big-Endian 轉換為 Little-Endian)。
    ntohl 將 32 位數量從網絡字節順序轉換為主機字節順序(從 Big-Endian 轉換為 Little-Endian)。
    htons 將 16 位數量從主機字節順序轉換為網絡字節順序(從 Little-Endian 轉換為 Big-Endian)。
    htonl 將 32 位數量從主機字節順序轉換為網絡字節順序(從 Little-Endian 轉換為 Big-Endian)。

    此示例的另一個要點是,當通信另一端的套接字應用程序為非 MFC 應用程序時,必須避免出現如下列語句的操作:

    ar pMsg; 
    

    這里的 pMsg 是指向從 CObject 類派生的 C++ 對象的指針。這將發送多余的與對象關聯的 MFC 信息,而服務器并不理解這些信息,因為只有服務器是 MFC 應用程序時才理解。

    有關更多信息,請參見: