匯編語言編程實例一這一章,我們要把我們已學的知識集合起來。具體來講,我們來寫一個使用ODBC APIs的程序.為簡單起見,這個程序中我使用Microsoft的Access數據庫(Microsoft Access 97) .
注意:如果你使用的windows.inc 是1.18及其以下版本,在開始編譯之前要修改其中的一個小bug.在windows.inc中查找 "SQL_NULL_HANDLE",將得到下面這行:
SQL_NULL_HANDLE equ 0L
將0後面的"L"刪除,象這樣:
SQL_NULL_HANDLE equ 0
這個程序是一個基於對話框的程序,有一個簡單的菜單.當用戶選擇"connect"時,它將試圖連接test.mdb數據庫,如果連接成功,將顯示由ODBC驅動程序返回的完整連接字符串.接下來,用戶可選擇"View All Records"命令,程序會使用listview control來顯示數據庫中的所有數據.用戶還可以選擇"Query"命令來查詢特定的記錄.例子程序將會顯示一個小對話框提示用戶輸入想找的人名.當用戶按下OK鈕或回車鍵,程序將執行一個查詢來查找符合條件的記錄.當用戶完成對數據庫的操作時,可以選擇"disconnect"命令與數據庫斷開連接.
現在看一下源程序:
.386
.model flat,stdcall
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\odbc32.inc
include \masm32\include\comctl32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\odbc32.lib
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
IDD_MAINDLG equ 101
IDR_MAINMENU equ 102
IDC_DATALIST equ 1000
IDM_CONNECT equ 40001
IDM_DISCONNECT equ 40002
IDM_QUERY equ 40003
IDC_NAME equ 1000
IDC_OK equ 1001
IDC_CANCEL equ 1002
IDM_CUSTOMQUERY equ 40004
IDD_QUERYDLG equ 102
DlgProc proto hDlg:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
QueryProc proto hDlg:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
SwitchMenuState proto :DWORD
ODBCConnect proto :DWORD
ODBCDisconnect proto :DWORD
RunQuery proto :DWORD
.data?
hInstance dd ?
hEnv dd ?
hConn dd ?
hStmt dd ?
Conn db 256 dup(?)
StrLen dd ?
hMenu dd ? ; 主菜單句柄
hList dd ? ; listview control句柄
TheName db 26 dup(?)
TheSurname db 26 dup(?)
TelNo db 21 dup(?)
NameLength dd ?
SurnameLength dd ?
TelNoLength dd ?
SearchName db 26 dup(?)
ProgPath db 256 dup(?)
ConnectString db 1024 dup(?)
.data
SQLStatement db "select * from main",0
WhereStatement db " where name=?",0
strConnect db "DRIVER={Microsoft Access Driver (*.mdb)};DBQ=",0
DBName db "test.mdb",0
ConnectCaption db "Complete Connection String",0
Disconnect db "Disconnect successful",0
AppName db "ODBC Test",0
AllocEnvFail db "Environment handle allocation failed",0
AllocConnFail db "Connection handle allocation failed",0
SetAttrFail db "Cannot set desired ODBC version",0
NoData db "You must type the name in the edit box",0
ExecuteFail db "Execution of SQL statement failed",0
ConnFail db "Connection attempt failed",0
AllocStmtFail db "Statement handle allocation failed",0
Heading1 db "Name",0
Heading2 db "Surname",0
Heading3 db "Telephone No.",0
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
call GetProgramPath
invoke DialogBoxParam, hInstance, IDD_MAINDLG,0,addr DlgProc,0
invoke ExitProcess,eax
invoke InitCommonControls
DlgProc proc hDlg:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
.if uMsg==WM_INITDIALOG
invoke GetMenu, hDlg
mov hMenu,eax
invoke GetDlgItem, hDlg, IDC_DATALIST
mov hList,eax
call InsertColumn
.elseif uMsg==WM_CLOSE
invoke GetMenuState, hMenu, IDM_CONNECT,MF_BYCOMMAND
.if eax==MF_GRAYED
invoke ODBCDisconnect, hDlg
.endif
invoke EndDialog,hDlg, 0
.elseif uMsg==WM_COMMAND
.if lParam==0
mov eax,wParam
.if ax==IDM_CONNECT
invoke ODBCConnect,hDlg
.elseif ax==IDM_DISCONNECT
invoke ODBCDisconnect,hDlg
.elseif ax==IDM_QUERY
invoke RunQuery,hDlg
.elseif ax==IDM_CUSTOMQUERY
invoke DialogBoxParam, hInstance, IDD_QUERYDLG,hDlg, addr QueryProc, 0
.endif
.endif
.else
mov eax,FALSE
ret
.endif
mov eax,TRUE
ret
DlgProc endp
GetProgramPath proc
invoke GetModuleFileName, NULL,addr ProgPath,sizeof ProgPath
std
mov edi,offset ProgPath
add edi,sizeof ProgPath-1
mov al,"\"
mov ecx,sizeof ProgPath
repne scasb
cld
mov byte ptr [edi+2],0
ret
GetProgramPath endp
SwitchMenuState proc Flag:DWORD
.if Flag==TRUE
invoke EnableMenuItem, hMenu, IDM_CONNECT, MF_GRAYED
invoke EnableMenuItem, hMenu, IDM_DISCONNECT, MF_ENABLED
invoke EnableMenuItem, hMenu, IDM_QUERY, MF_ENABLED
invoke EnableMenuItem, hMenu, IDM_CUSTOMQUERY, MF_ENABLED
.else
invoke EnableMenuItem, hMenu, IDM_CONNECT, MF_ENABLED
invoke EnableMenuItem, hMenu, IDM_DISCONNECT, MF_GRAYED
invoke EnableMenuItem, hMenu, IDM_QUERY, MF_GRAYED
invoke EnableMenuItem, hMenu, IDM_CUSTOMQUERY, MF_GRAYED
.endif
ret
SwitchMenuState endp
ODBCConnect proc hDlg:DWORD
invoke SQLAllocHandle, SQL_HANDLE_ENV, SQL_NULL_HANDLE, addr hEnv
.if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO
invoke SQLSetEnvAttr, hEnv,SQL_ATTR_ODBC_VERSION, SQL_OV_ODBC3,0
.if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO
invoke SQLAllocHandle, SQL_HANDLE_DBC, hEnv, addr hConn
.if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO
invoke lstrcpy,addr ConnectString,addr strConnect
invoke lstrcat,addr ConnectString, addr ProgPath
invoke lstrcat, addr ConnectString,addr DBName
invoke SQLDriverConnect, hConn, hDlg, addr ConnectString, sizeof ConnectString, addr Conn, sizeof Conn,addr StrLen, SQL_DRIVER_COMPLETE
.if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO
invoke SwitchMenuState,TRUE
invoke MessageBox,hDlg, addr Conn,addr ConnectCaption,MB_OK+MB_ICONINFORMATION
.else
invoke SQLFreeHandle, SQL_HANDLE_DBC, hConn
invoke SQLFreeHandle, SQL_HANDLE_ENV, hEnv
invoke MessageBox, hDlg, addr ConnFail, addr AppName, MB_OK+MB_ICONERROR
.endif
.else
invoke SQLFreeHandle, SQL_HANDLE_ENV, hEnv
invoke MessageBox, hDlg, addr AllocConnFail, addr AppName, MB_OK+MB_ICONERROR
.endif
.else
invoke SQLFreeHandle, SQL_HANDLE_ENV, hEnv
invoke MessageBox, hDlg, addr SetAttrFail, addr AppName, MB_OK+MB_ICONERROR
.endif
.else
invoke MessageBox, hDlg, addr AllocEnvFail, addr AppName, MB_OK+MB_ICONERROR
.endif
ret
ODBCConnect endp
ODBCDisconnect proc hDlg:DWORD
invoke SQLDisconnect, hConn
invoke SQLFreeHandle, SQL_HANDLE_DBC, hConn
invoke SQLFreeHandle, SQL_HANDLE_ENV, hEnv
invoke SwitchMenuState, FALSE
invoke ShowWindow,hList, SW_HIDE
invoke MessageBox,hDlg,addr Disconnect, addr AppName,MB_OK+MB_ICONINFORMATION
ret
ODBCDisconnect endp
InsertColumn proc
LOCAL lvc:LV_COLUMN
mov lvc.imask,LVCF_TEXT+LVCF_WIDTH
mov lvc.pszText,offset Heading1
mov lvc.lx,150
invoke SendMessage,hList, LVM_INSERTCOLUMN,0,addr lvc
mov lvc.pszText,offset Heading2
invoke SendMessage,hList, LVM_INSERTCOLUMN, 1 ,addr lvc
mov lvc.pszText,offset Heading3
invoke SendMessage,hList, LVM_INSERTCOLUMN, 3 ,addr lvc
ret
InsertColumn endp
FillData proc
LOCAL lvi:LV_ITEM
LOCAL row:DWORD
invoke SQLBindCol, hStmt,1,SQL_C_CHAR, addr TheName, sizeof TheName,addr NameLength
invoke SQLBindCol, hStmt,2,SQL_C_CHAR, addr TheSurname, sizeof TheSurname,addr SurnameLength
invoke SQLBindCol, hStmt,3,SQL_C_CHAR, addr TelNo, sizeof TelNo,addr TelNoLength
mov row,0
.while TRUE
mov byte ptr ds:[TheName],0
mov byte ptr ds:[TheSurname],0
mov byte ptr ds:[TelNo],0
invoke SQLFetch, hStmt
.if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO
mov lvi.imask,LVIF_TEXT+LVIF_PARAM
push row
pop lvi.iItem
mov lvi.iSubItem,0
mov lvi.pszText, offset TheName
push row
pop lvi.lParam
invoke SendMessage,hList, LVM_INSERTITEM,0, addr lvi
mov lvi.imask,LVIF_TEXT
inc lvi.iSubItem
mov lvi.pszText,offset TheSurname
invoke SendMessage,hList,LVM_SETITEM, 0,addr lvi
inc lvi.iSubItem
mov lvi.pszText,offset TelNo
invoke SendMessage,hList,LVM_SETITEM, 0,addr lvi
inc row
.else
.break
.endif
.endw
ret
FillData endp
RunQuery proc hDlg:DWORD
invoke ShowWindow, hList, SW_SHOW
invoke SendMessage, hList, LVM_DELETEALLITEMS,0,0
invoke SQLAllocHandle, SQL_HANDLE_STMT, hConn, addr hStmt
.if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO
invoke SQLExecDirect, hStmt, addr SQLStatement, sizeof SQLStatement
.if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO
invoke FillData
.else
invoke ShowWindow, hList, SW_HIDE
invoke MessageBox,hDlg,addr ExecuteFail, addr AppName, MB_OK+MB_ICONERROR
.endif
invoke SQLCloseCursor, hStmt
invoke SQLFreeHandle, SQL_HANDLE_STMT, hStmt
.else
invoke ShowWindow, hList, SW_HIDE
invoke MessageBox,hDlg,addr AllocStmtFail, addr AppName, MB_OK+MB_ICONERROR
.endif
ret
RunQuery endp
QueryProc proc hDlg:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
.if uMsg==WM_CLOSE
invoke SQLFreeHandle, SQL_HANDLE_STMT, hStmt
invoke EndDialog, hDlg,0
.elseif uMsg==WM_INITDIALOG
invoke ShowWindow, hList, SW_SHOW
invoke SQLAllocHandle, SQL_HANDLE_STMT, hConn, addr hStmt
.if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO
invoke lstrcpy, addr Conn, addr SQLStatement
invoke lstrcat, addr Conn, addr WhereStatement
invoke SQLBindParameter,hStmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR,25,0, addr SearchName,25,addr StrLen
invoke SQLPrepare, hStmt, addr Conn, sizeof Conn
.else
invoke ShowWindow, hList, SW_HIDE
invoke MessageBox,hDlg,addr AllocStmtFail, addr AppName, MB_OK+MB_ICONERROR
invoke EndDialog, hDlg,0
.endif
.elseif uMsg==WM_COMMAND
mov eax, wParam
shr eax,16
.if ax==BN_CLICKED
mov eax,wParam
.if ax==IDC_OK
invoke GetDlgItemText, hDlg, IDC_NAME, addr SearchName, 25
.if ax==0
invoke MessageBox, hDlg,addr NoData, addr AppName, MB_OK+MB_ICONERROR
invoke GetDlgItem, hDlg, IDC_NAME
invoke SetFocus, eax
.else
invoke lstrlen,addr SearchName
mov StrLen,eax
invoke SendMessage, hList, LVM_DELETEALLITEMS,0,0
invoke SQLExecute, hStmt
invoke FillData
invoke SQLCloseCursor, hStmt
.endif
.else
invoke SQLFreeHandle, SQL_HANDLE_STMT, hStmt
invoke EndDialog, hDlg,0
.endif
.endif
.else
mov eax,FALSE
ret
.endif
mov eax,TRUE
ret
QueryProc endp
end start
分析
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
call GetProgramPath
當程序開始時,將獲得實例句柄並獲得所在路徑.默認情況下數據庫 test.mdb應與程序處於同一文件夾.
GetProgramPath proc
invoke GetModuleFileName, NULL,addr ProgPath,sizeof ProgPath
std
mov edi,offset ProgPath
add edi,sizeof ProgPath-1
mov al,"\"
mov ecx,sizeof ProgPath
repne scasb
cld
mov byte ptr [edi+2],0
ret
GetProgramPath endp
GetProgramPath調用GetModuleFileName來獲得程序的全路徑名.接著在路徑中查找最後一個"\"符",通過將文件名的第一個字符置為0獲得(truncate)" 文件名. 因此我們在ProgPath中獲得了程序的路徑名.
然後程序將用DialogBoxParam顯示主對話框.當主對話框第一次被載入時,它將獲得菜單句柄和listview control句柄.接下來在listview control中插入三列(因為我們已經知道結果集將包含三列.因為是我們先建的表.)
現在,它就等待用戶的動作了.如果用戶在菜單中選擇"connect",將會調用ODBCConnect函數.
ODBCConnect proc hDlg:DWORD
invoke SQLAllocHandle, SQL_HANDLE_ENV, SQL_NULL_HANDLE, addr hEnv
.if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO
它做的第一件事是調用SQLAllocHandle來分配一個環境句柄.
invoke SQLSetEnvAttr, hEnv,SQL_ATTR_ODBC_VERSION, SQL_OV_ODBC3,0
.if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO
獲得環境句柄後,程序調用SQLSetEnvAttr來表示將要使用ODBC 3.x的語法.
invoke SQLAllocHandle, SQL_HANDLE_DBC, hEnv, addr hConn
.if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO
如果一切順利,程序將通過調用SQLAllocHandle獲得連接句柄來實現連接.
invoke lstrcpy,addr ConnectString,addr strConnect
invoke lstrcat,addr ConnectString, addr ProgPath
invoke lstrcat, addr ConnectString,addr DBName
接著填寫連接字符串.完整的連接字符串將被用在ConnectionString
invoke SQLDriverConnect, hConn, hDlg, addr ConnectString, sizeof ConnectString, addr Conn, sizeof Conn,addr StrLen, SQL_DRIVER_COMPLETE
.if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO
invoke SwitchMenuState,TRUE
invoke MessageBox,hDlg, addr Conn,addr ConnectCaption,MB_OK+MB_ICONINFORMATION
當連接字符串完成,程序將調用SQLDriverConnect來通過MS Access ODBC 驅動程序連接test.mdb數據庫.如果文件test.mdb不存在,ODBC driver將提示用戶輸入該文件的位置,因為我們已經設定了SQL_DRIVER_COMPLETE標志.當SQLDriverConnect成功返回時, Conn 被填入由ODBC驅動程序創建的完整連接字符串.我們通過一個message box來將其顯示給用戶. SwitchMenuState是一個單純切換菜單選項可用的函數.
現在,到數據庫的連接已經建立並被打開,並一直保持打開狀態直到用戶選擇關閉.
當用戶選擇了"View All Records"命令, 對話框過程將調用RunQuery.函數
RunQuery proc hDlg:DWORD
invoke ShowWindow, hList, SW_SHOW
invoke SendMessage, hList, LVM_DELETEALLITEMS,0,0
由於listview control在創建時是不可見的,現在我們把它顯示出來.還有 要把其中的所有元素(如果有的話)刪掉.
invoke SQLAllocHandle, SQL_HANDLE_STMT, hConn, addr hStmt
.if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO
接下來,程序將獲得一個語句句柄.
invoke SQLExecDirect, hStmt, addr SQLStatement, sizeof SQLStatement
.if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO
通過 SQLExecDirect執行已准備好的SQL語句.我這裡選擇SQLExecDirect 的原因是只須執行一次.
invoke FillData
執行SQL語句後,將返回一個結果集.我們使用 FillData函數來從結果集中解出數據並將其放入listview control中.
FillData proc
LOCAL lvi:LV_ITEM
LOCAL row:DWORD
invoke SQLBindCol, hStmt,1,SQL_C_CHAR, addr TheName, sizeof TheName,addr NameLength
invoke SQLBindCol, hStmt,2,SQL_C_CHAR, addr TheSurname, sizeof TheSurname,addr SurnameLength
invoke SQLBindCol, hStmt,3,SQL_C_CHAR, addr TelNo, sizeof TelNo,addr TelNoLength
現在,結果集被返回.我們要綁定結果集的所有三列到我們提供的緩沖區中.這是調用SQLBindCol來實現的.注意我們要對每一列分別調用.並且我們並不需要綁定所有的列:只要綁定要獲得數據的列就行了.
mov row,0
.while TRUE
mov byte ptr ds:[TheName],0
mov byte ptr ds:[TheSurname],0
mov byte ptr ds:[TelNo],0
當列中沒有數據時,我們初始化緩沖區為NULLs.更好的方法是用SQLBindCol指定的變量中數據的長度.在我們的例子中,我們可以檢查NameLength, SurnameLength和TelNoLength中的值的確切長度.
invoke SQLFetch, hStmt
.if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO
mov lvi.imask,LVIF_TEXT+LVIF_PARAM
push row
pop lvi.iItem
mov lvi.iSubItem,0
mov lvi.pszText, offset TheName
push row
pop lvi.lParam
invoke SendMessage,hList, LVM_INSERTITEM,0, addr lvi
其它都很簡單了.調用SQLFetch 來獲得結果集的一行,並將其存入listview control的緩沖區中.當沒有更多的行供檢索時(已到達文件尾), SQLFetch返回SQL_NO_DATA並且程序跳出循環.
invoke SQLCloseCursor, hStmt
invoke SQLFreeHandle, SQL_HANDLE_STMT, hStmt
當完成對結果集的操作時,調用SQLCloseCursor關閉結果集並調用SQLFreeHandle釋放語句句柄.
當用戶選擇"Query"命令,程序顯示另一個對話框供用戶輸入要查詢的名字.
.elseif uMsg==WM_INITDIALOG
invoke ShowWindow, hList, SW_SHOW
invoke SQLAllocHandle, SQL_HANDLE_STMT, hConn, addr hStmt
.if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO
invoke lstrcpy, addr Conn, addr SQLStatement
invoke lstrcat, addr Conn, addr WhereStatement
invoke SQLBindParameter,hStmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR,25,0, addr SearchName,25,addr StrLen
invoke SQLPrepare, hStmt, addr Conn, sizeof Conn
對話框做的第一件事是顯示listview control.接下來分配一個語句句柄以創建SQL語句.這個SQL語句有一個"where"子句及一個參數標志符"?". 完整的SQL語句是:
select * from main where name=?
接著程序調用SQLBindParameter 來建立參數標志符與緩沖區SearchName的連接,這樣當SQL語句被執行時,ODBC驅動程序就可從SearchName中獲得需要的字符串.接下來,程序調用SQLPrepare來編譯SQL語句. 這樣我們只要准備/編譯SQL語句一次就可多次使用.因為SQL語句已被編譯過,接下來的執行過程會快一些.
.if ax==IDC_OK
invoke GetDlgItemText, hDlg, IDC_NAME, addr SearchName, 25
.if ax==0
invoke MessageBox, hDlg,addr NoData, addr AppName, MB_OK+MB_ICONERROR
invoke GetDlgItem, hDlg, IDC_NAME
invoke SetFocus, eax
.else
當用戶在編輯框(edit control)中填入了一些名字並按下回車鍵, 程序將獲得編輯框中的文本並檢查是否是空字符串.如果是,則顯示一個message box並將鍵盤焦點設在編輯框上,提示用戶輸入名字.
invoke lstrlen,addr SearchName
mov StrLen,eax
invoke SendMessage, hList, LVM_DELETEALLITEMS,0,0
invoke SQLExecute, hStmt
invoke FillData
invoke SQLCloseCursor, hStmt
如果編輯框中已有字符串,程序會獲得它的長度並將其放入StrLen中供ODBC驅動程序使用(記住我們已將StrLen的地址傳送給了SQLBindParameter). 接下來程序使用獲得的語句句柄調用SQLExecute執行已准備好的SQL語句.當 SQLExecute返回時,程序調用FillData在listview control顯示結果.因為我們不會再用到結果集,調用SQLCloseCursor來關閉它.