技术文章
CSocket 編程之阻塞和非阻塞模式
我通過幾個采用 CSocket 類編寫并基于 Client/Server (客戶端 / 服務端)的網絡聊天和傳輸文件的程序 ( 詳見: 源代碼參考 ) ,在調試這些程序的過程中,追蹤深入至 CSocket 類核心源碼 Sockcore.cpp , 對于CSocket 類的運行機制可謂是一覽無遺,并且對于阻塞和非阻塞方式下的 socket 程序的編寫也是稍有體會。閱讀本文請先注意:
這里的阻塞和非阻塞的概念僅適用于 Server 端 socket 程序。socket 意為套接字,它與 Socket 不同,請注意首字母的大小寫。
客戶端與服務端的通信簡單來講:服務端 socket 負責監聽,應答,接收和發送消息,而客戶端 socket 只是連接,應答,接收,發送消息。此外,如果你對于采用 CSocket 類編寫 Client/Server 網絡程序的原理不是很了解,請先查詢一下( 詳見:參考書籍和在線幫助 )。
在此之前,有必要先講述一下: 網絡傳輸服務提供者, ws2_32.dll , socket 事件 和 socket window 。
1、網絡傳輸服務提供者(網絡傳輸服務進程), Socket 事件, Socket Window
網絡傳輸服務提供者 ( transport service provider )是以 DLL 的形式存在的,在 windows 操作系統啟動時由服務進程 svchost.exe 加載。當 socket 被創建時,調用 API 函數 Socket (在 ws2_32.dll 中), Socket 函數會傳遞三個參數 : 地址族,套接字類型 ( 注 2 ) 和協議,這三個參數決定了是由哪一個類型的 網絡傳輸服務提供者 來啟動網絡傳輸服務功能。所有的網絡通信正是由網絡傳輸服務提供者完成 , 這里將 網絡傳輸服務提供者 稱為 網絡傳輸服務進程 更有助于理解,因為前文已提到 網絡傳輸服務提供者 是由 svchost.exe 服務進程所加載的。
下圖描述了網絡應用程序、 CSocket ( WSock32.dll )、 Socket API(ws2_32.dll) 和 網絡傳輸服務進程 之間的接口層次關系:
當 Client 端 socket 與 Server 端 socket 相互通信時,兩端均會觸發 socket 事件。這里僅簡要說明兩個 socket 事件:
- FD_CONNECT: 連接事件 , 通常 Client 端 socket 調用 socket API 函數 Connect 時所觸發,這個事件發生在 Client 端。
- FD_ACCEPT :正在引入的連接事件,通常 Server 端 socket 正在接收來自 Client 端 socket 連接時觸發,這個事件發生在 Server 端。
網絡傳輸服務進程 將 socket 事件 保存至 socket 的事件隊列中。此外, 網絡傳輸服務進程 還會向 socket window 發送消息 WM_SOCKET_NOTIFY , 通知有 socket 事件 產生,見下文對 socket window 的詳細說明。
調用 CSocket::Create 函數后,socket 被創建。 socket 創建過程中調用 CAsyncSocket::AttachHandle(SOCKET hSocket, CAsyncSocket* pSocket, BOOL bDead) 。該函數的作用是:
- 將 socket 實例句柄和 socket 指針添加至 當前模塊狀態 ( 注 1 )的一個映射表變量 m_pmapSocketHandle 中。
- 在 AttachHandle 過程中,會 new 一個 CSocketWnd 實例 ( 基于 CWnd 派生 ) ,這里將這個實例稱之為 socket window ,進一步理解為它是存放所有 sockets 的消息池 ( window 消息),請仔細查看,這里 socket 后多加了一個 s ,表示創建的多個 socket 將共享一個 消息池 。
- 當 Client 端 socket 與 Server 端相互通信時 , 此時 網絡傳輸服務進程 向 socket window 發送消息 WM_SOCKET_NOTIFY ,需要說明的是 CSocketWnd 窗口句柄保存在 當前模塊狀態 的 m_hSocketWindow 變量中。
2、阻塞模式
阻塞模式下 Server 端與 Client 端之間的通信處于同步狀態下。在 Server 端直接實例化 CSocket 類,調用 Create 方法創建 socket ,然后調用方法 Listen 開始偵聽,最后用一個 while 循環阻塞調用 Accept 函數用于等待來自 Client 端的連接,如果這個 socket 在主線程(主程序)中運行,這將導致主線程的阻塞。因此,需要創建一個新的線程以運行 socket 服務。
調試跟蹤至 CSocket::Accept 函數源碼:
PumpMessage(FD_ACCEPT); else return FALSE; } 它不斷調用 CAsyncSocket::Accept ( CSocket 派生自 CAsyncSocket 類)判斷 Server 端 socket 的事件隊列中是否存在正在引入的連接事件 - FD_ACCEPT (見 1 ),換句話說,就是判斷是否有來自 Client 端 socket 的連接請求。
如果當前 Server 端 socket 的事件隊列中存在正在引入的連接事件, Accept 返回一個非 0 值。否則, Accept 返回 0,此時調用 GetLastError 將返回錯誤代碼 WSAEWOULDBLOCK ,表示隊列中無任何連接請求。注意到在循環體內有一句代碼: PumpMessage(FD_ACCEPT);
PumpMessage 作為一個消息泵使得 socket window 中的消息能夠維持在活動狀態。實際跟蹤進入 PumpMessage 中,發現這個消息泵與 Accept 函數的調用并不相關,它只是使很少的 socket window 消息(典型的是 WM_PAINT 窗口重繪消息)處于活動狀態,而絕大部分的 socket window 消息被阻塞,被阻塞的消息中含有 WM_SOCKET_NOTIFY。
很顯然,如果沒有來自 Client 端 socket 的連接請求, CSocket 就會不斷調用 Accept 產生循環阻塞,直到有來自 Client 端 socket 的連接請求而解除阻塞。
阻塞解除后,表示 Server 端 socket 和 Client 端 socket 已成功連接, Server 端與 Client 端彼此相互調用 Send 和 Receive 方法開始通信。
3、非阻塞模式
在非阻塞模式下 利用 socket 事件 的消息機制, Server 端與 Client 端之間的通信處于異步狀態下。
通常需要從 CSocket 類派生一個新類,派生新類的目的是重載 socket 事件 的消息函數,然后在 socket 事件 的消息函數中添入合適的代碼以完成 Client 端與 Server 端之間的通信,與阻塞模式相比,非阻塞模式無需創建一個新線程。
這里將討論當 Server 端 socket 事件 - FD_ACCEPT 被觸發后,該事件的處理函數 OnAccept 是如何進一步被觸發的。其它事件的處理函數如 OnConnect, OnReceive 等的觸發方式與此類似。
在 1 中已提到 Client/Server 端通信時, Server 端 socket 正在接收來自 Client 端 socket 連接請求,這將會觸發 FD_ACCEPT 事件,同時 Server 端的 網絡傳輸服務進程 向 Server 端的 socket window (CSocketWnd )發送事件通知消息 WM_SOCKET_NOTIFY , 通知有 FD_ACCEPT 事件產生 , CsocketWnd 在收到事件通知消息后,調用消息處理函數 OnSocketNotify:
ProcessAuxQueue 是實質處理 socket 事件的函數,在該函數中有這樣一句代碼: CAsyncSocket* pSocket = CAsyncSocket::LookupHandle((SOCKET)wParam, TRUE);
其實也就是由 socket 句柄得到發送事件通知消息的 socket 指針 pSocket:從 m_pmapSocketHandle 中查找(見 1 )!
最后, WSAGETSELECTEVENT(lParam) 會取出事件類型,在一個簡單的 switch 語句中判斷事件類型并調用事件處理函數。在這里,事件類型是 FD_ACCEPT ,當然就調用 pSocket->OnAccept !
結束語
Server 端 socket 處于阻塞調用模式下,它必須在一個新創建的線程中工作,防止主線程被阻塞。
當有多個 Client 端 socket 與 Server 端 socket 連接及通信時, Server 端采用阻塞模式就顯得不適合了,應該采用非阻塞模式 , 利用 socket 事件 的消息機制來接受多個 Client 端 socket 的連接請求并進行通信。
在非阻塞模式下,利用 CSocketWnd 作為所有 sockets 的消息池,是實現 socket 事件 的消息機制的關鍵技術。文中存在用詞不妥和可能存在的技術問題,請大家原諒,也請批評指正,謝謝!
注:
源代碼參考:
WinSock學習筆記
Socket(套接字)◆先看定義:
typedef unsigned int u_int;
typedef u_int SOCKET;◆Socket相當于進行網絡通信兩端的插座,只要對方的Socket和自己的Socket有通信聯接,雙方就可以發送和接收數據了。其定義類似于文件句柄的定義。
◆Socket有五種不同的類型:
1、流式套接字(stream socket)
定義:
#define SOCK_STREAM 1 流式套接字提供了雙向、有序的、無重復的以及無記錄邊界的數據流服務,適合處理大量數據。它是面向聯結的,必須建立數據傳輸鏈路,同時還必須對傳輸的數據進行驗證,確保數據的準確性。因此,系統開銷較大。
2、 數據報套接字(datagram socket)
定義:
#define SOCK_DGRAM 2 數據報套接字也支持雙向的數據流,但不保證傳輸數據的準確性,但保留了記錄邊界。由于數據報套接字是無聯接的,例如廣播時的聯接,所以并不保證接收端是否正在偵聽。數據報套接字傳輸效率比較高。
3、原始套接字(raw-protocol interface)
定義:
#define SOCK_RAW 3 原始套接字保存了數據包中的完整IP頭,前面兩種套接字只能收到用戶數據。因此可以通過原始套接字對數據進行分析。
其它兩種套接字不常用,這里就不介紹了。
◆Socket開發所必須需要的文件(以WinSock V2.0為例):
頭文件:Winsock2.h
庫文件:WS2_32.LIB
動態庫:W32_32.DLL
一些重要的定義
1、數據類型的基本定義:這個大家一看就懂。
typedef unsigned char u_char;
typedef unsigned short u_short;
typedef unsigned int u_int;
typedef unsigned long u_long;2、 網絡地址的數據結構,有一個老的和一個新的的,請大家留意,如果想知道為什么,
請發郵件給Bill Gate。其實就是計算機的IP地址,不過一般不用用點分開的IP地
址,當然也提供一些轉換函數。
◆ 舊的網絡地址結構的定義,為一個4字節的聯合:
struct in_addr {
union {
struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
struct { u_short s_w1,s_w2; } S_un_w;
u_long S_addr;
} S_un;
#define s_addr S_un.S_addr /* can be used for most tcp & ip code */
//下面幾行省略,反正沒什么用處。
};其實完全不用這么麻煩,請看下面:
◆ 新的網絡地址結構的定義:
非常簡單,就是一個無符號長整數 unsigned long。舉個例子:IP地址為127.0.0.1的網絡地址是什么呢?請看定義:
#define INADDR_LOOPBACK 0x7f0000013、 套接字地址結構
(1)、sockaddr結構:
struct sockaddr {
u_short sa_family; /* address family */
char sa_data[14]; /* up to 14 bytes of direct address */
};sa_family為網絡地址類型,一般為AF_INET,表示該socket在Internet域中進行通信,該地址結構隨選擇的協議的不同而變化,因此一般情況下另一個與該地址結構大小相同的sockaddr_in結構更為常用,sockaddr_in結構用來標識TCP/IP協議下的地址。換句話說,這個結構是通用socket地址結構,而下面的sockaddr_in是專門針對Internet域的socket地址結構。
(2)、sockaddr_in結構
struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};sin _family為網絡地址類型,必須設定為AF_INET。sin_port為服務端口,注意不要使用已固定的服務端口,如HTTP的端口80等。如果端口設置為0,則系統會自動分配一個唯一端口。sin_addr為一個unsigned long的IP地址。sin_zero為填充字段,純粹用來保證結構的大小。
◆ 將常用的用點分開的IP地址轉換為unsigned long類型的IP地址的函數:
unsigned long inet_addr(const char FAR * cp )用法:
unsigned long addr=inet_addr("192.1.8.84")◆ 如果將sin_addr設置為INADDR_ANY,則表示所有的IP地址,也即所有的計算機。
#define INADDR_ANY (u_long)0x000000004、 主機地址:
先看定義:
struct hostent {
char FAR * h_name; /* official name of host */
char FAR * FAR * h_aliases; /* alias list */
short h_addrtype; /* host address type */
short h_length; /* length of address */
char FAR * FAR * h_addr_list; /* list of addresses */
#define h_addr h_addr_list[0] /* address, for backward compat */
};
h_name為主機名字。
h_aliases為主機別名列表。
h_addrtype為地址類型。
h_length為地址類型。
h_addr_list為IP地址,如果該主機有多個網卡,就包括地址的列表。另外還有幾個類似的結構,這里就不一一介紹了。
5、 常見TCP/IP協議的定義:
#define IPPROTO_IP 0
#define IPPROTO_ICMP 1
#define IPPROTO_IGMP 2
#define IPPROTO_TCP 6
#define IPPROTO_UDP 17
#define IPPROTO_RAW 255 具體是什么協議,大家一看就知道了。
套接字的屬性
為了靈活使用套接字,我們可以對它的屬性進行設定。
1、 屬性內容:
//允許調試輸出
#define SO_DEBUG 0x0001 /* turn on debugging info recording */
//是否監聽模式
#define SO_ACCEPTCONN 0x0002 /* socket has had listen() */
//套接字與其他套接字的地址綁定
#define SO_REUSEADDR 0x0004 /* allow local address reuse */
//保持連接
#define SO_KEEPALIVE 0x0008 /* keep connections alive */
//不要路由出去
#define SO_DONTROUTE 0x0010 /* just use interface addresses */
//設置為廣播
#define SO_BROADCAST 0x0020 /* permit sending of broadcast msgs */
//使用環回不通過硬件
#define SO_USELOOPBACK 0x0040 /* bypass hardware when possible */
//當前拖延值
#define SO_LINGER 0x0080 /* linger on close if data present */
//是否加入帶外數據
#define SO_OOBINLINE 0x0100 /* leave received OOB data in line */
//禁用LINGER選項
#define SO_DONTLINGER (int)(~SO_LINGER)
//發送緩沖區長度
#define SO_SNDBUF 0x1001 /* send buffer size */
//接收緩沖區長度
#define SO_RCVBUF 0x1002 /* receive buffer size */
//發送超時時間
#define SO_SNDTIMEO 0x1005 /* send timeout */
//接收超時時間
#define SO_RCVTIMEO 0x1006 /* receive timeout */
//錯誤狀態
#define SO_ERROR 0x1007 /* get error status and clear */
//套接字類型
#define SO_TYPE 0x1008 /* get socket type */2、 讀取socket屬性:
int getsockopt(SOCKET s, int level, int optname, char FAR * optval, int FAR * optlen)s為欲讀取屬性的套接字。level為套接字選項的級別,大多數是特定協議和套接字專有的。如IP協議應為 IPPROTO_IP。
optname為讀取選項的名稱
optval為存放選項值的緩沖區指針。
optlen為緩沖區的長度用法:
int ttl=0; //讀取TTL值
int rc = getsockopt( s, IPPROTO_IP, IP_TTL, (char *)&ttl, sizeof(ttl));
//來自MS platform SDK 20033、 設置socket屬性:
int setsockopt(SOCKET s,int level, int optname,const char FAR * optval, int optlen)s為欲設置屬性的套接字。
level為套接字選項的級別,用法同上。
optname為設置選項的名稱
optval為存放選項值的緩沖區指針。
optlen為緩沖區的長度
用法:
int ttl=32; //設置TTL值
int rc = setsockopt( s, IPPROTO_IP, IP_TTL, (char *)&ttl, sizeof(ttl)); 套接字的使用步驟
1、啟動Winsock:對Winsock DLL進行初始化,協商Winsock的版本支持并分配必要的
資源。(服務器端和客戶端)
int WSAStartup( WORD wVersionRequested, LPWSADATA lpWSAData )
wVersionRequested為打算加載Winsock的版本,一般如下設置:
wVersionRequested=MAKEWORD(2,0)
或者直接賦值:wVersionRequested=2
LPWSADATA為初始化Socket后加載的版本的信息,定義如下:
typedef struct WSAData {
WORD wVersion;
WORD wHighVersion;
char szDescription[WSADESCRIPTION_LEN+1];
char szSystemStatus[WSASYS_STATUS_LEN+1];
unsigned short iMaxSockets;
unsigned short iMaxUdpDg;
char FAR * lpVendorInfo;
} WSADATA, FAR * LPWSADATA;如果加載成功后數據為:
wVersion=2表示加載版本為2.0。
wHighVersion=514表示當前系統支持socket最高版本為2.2。
szDescription="WinSock 2.0"
szSystemStatus="Running"表示正在運行。
iMaxSockets=0表示同時打開的socket最大數,為0表示沒有限制。
iMaxUdpDg=0表示同時打開的數據報最大數,為0表示沒有限制。
lpVendorInfo沒有使用,為廠商指定信息預留。該函數使用方法:
WORD wVersion=MAKEWORD(2,0);
WSADATA wsData;
int nResult= WSAStartup(wVersion,&wsData);
if(nResult !=0)
{
//錯誤處理
}2、創建套接字:(服務器端和客戶端)
SOCKET socket( int af, int type, int protocol );
af為網絡地址類型,一般為AF_INET,表示在Internet域中使用。
type為套接字類型,前面已經介紹了。
protocol為指定網絡協議,一般為IPPROTO_IP。用法:
SOCKET sock=socket(AF_INET,SOCK_STREAM,IPPROTO_IP);
if(sock==INVALID_SOCKET)
{
//錯誤處理
}3、套接字的綁定:將本地地址綁定到所創建的套接字上。(服務器端和客戶端)
int bind( SOCKET s, const struct sockaddr FAR * name, int namelen )
s為已經創建的套接字。
name為socket地址結構,為sockaddr結構,如前面討論的,我們一般使用sockaddr_in
結構,在使用再強制轉換為sockaddr結構。
namelen為地址結構的長度。
用法:
sockaddr_in addr;
addr. sin_family=AF_INET;
addr. sin_port= htons(0); //保證字節順序
addr. sin_addr.s_addr= inet_addr("192.1.8.84")
int nResult=bind(s,(sockaddr*)&addr,sizeof(sockaddr));
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}4、 套接字的監聽:(服務器端)
int listen(SOCKET s, int backlog )s為一個已綁定但未聯接的套接字。
backlog為指定正在等待聯接的最大隊列長度,這個參數非常重要,因為服務器一般可
以提供多個連接。
用法:
int nResult=listen(s,5) //最多5個連接
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}5、套接字等待連接::(服務器端)
SOCKET accept( SOCKET s, struct sockaddr FAR * addr, int FAR * addrlen )s為處于監聽模式的套接字。
sockaddr為接收成功后返回客戶端的網絡地址。
addrlen為網絡地址的長度。
用法:
sockaddr_in addr;
SOCKET s_d=accept(s,(sockaddr*)&addr,sizeof(sockaddr));
if(s==INVALID_SOCKET)
{
//錯誤處理
}6、套接字的連結:將兩個套接字連結起來準備通信。(客戶端)
int connect(SOCKET s, const struct sockaddr FAR * name, int namelen )s為欲連結的已創建的套接字。
name為欲連結的socket地址。
namelen為socket地址的結構的長度。
用法:
sockaddr_in addr;
addr. sin_family=AF_INET;
addr. sin_port=htons(0); //保證字節順序
addr. sin_addr.s_addr= htonl(INADDR_ANY) //保證字節順序
int nResult=connect(s,(sockaddr*)&addr,sizeof(sockaddr));
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}7、套接字發送數據:(服務器端和客戶端)
int send(SOCKET s, const char FAR * buf, int len, int flags )s為服務器端監聽的套接字。
buf為欲發送數據緩沖區的指針。
len為發送數據緩沖區的長度。
flags為數據發送標記。
返回值為發送數據的字符數。
◆這里講一下這個發送標記,下面8中討論的接收標記也一樣:
flag取值必須為0或者如下定義的組合:0表示沒有特殊行為。
#define MSG_OOB 0x1 /* process out-of-band data */
#define MSG_PEEK 0x2 /* peek at incoming message */
#define MSG_DONTROUTE 0x4 /* send without using routing tables */
MSG_OOB表示數據應該帶外發送,所謂帶外數據就是TCP緊急數據。
MSG_PEEK表示使有用的數據復制到緩沖區內,但并不從系統緩沖區內刪除。
MSG_DONTROUTE表示不要將包路由出去。
用法:
char buf[]="xiaojin";
int nResult=send(s,buf,strlen(buf));
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}8、 套接字的數據接收:(客戶端)
int recv( SOCKET s, char FAR * buf, int len, int flags )s為準備接收數據的套接字。
buf為準備接收數據的緩沖區。
len為準備接收數據緩沖區的大小。
flags為數據接收標記。
返回值為接收的數據的字符數。
用法:
char mess[1000];
int nResult =recv(s,mess,1000,0);
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}9、中斷套接字連接:通知服務器端或客戶端停止接收和發送數據。(服務器端和客戶端)
int shutdown(SOCKET s, int how)s為欲中斷連接的套接字。
How為描述禁止哪些操作,取值為:SD_RECEIVE、SD_SEND、SD_BOTH。
#define SD_RECEIVE 0x00
#define SD_SEND 0x01
#define SD_BOTH 0x02用法:
int nResult= shutdown(s,SD_BOTH);
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}10、 關閉套接字:釋放所占有的資源。(服務器端和客戶端)
int closesocket( SOCKET s )s為欲關閉的套接字。
用法:
int nResult=closesocket(s);
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}
與socket有關的一些函數介紹
1、讀取當前錯誤值:每次發生錯誤時,如果要對具體問題進行處理,那么就應該調用這個函數取得錯誤代碼。
int WSAGetLastError(void );
#define h_errno WSAGetLastError()
錯誤值請自己閱讀Winsock2.h。
2、將主機的unsigned long值轉換為網絡字節順序(32位):為什么要這樣做呢?因為不同的計算機使用不同的字節順序存儲數據。因此任何從Winsock函數對IP地址和端口號的引用和傳給Winsock函數的IP地址和端口號均時按照網絡順序組織的。
u_long htonl(u_long hostlong);
舉例:htonl(0)=0
htonl(80)= 1342177280
3、將unsigned long數從網絡字節順序轉換位主機字節順序,是上面函數的逆函數。
u_long ntohl(u_long netlong);
舉例:ntohl(0)=0
ntohl(1342177280)= 80
4、將主機的unsigned short值轉換為網絡字節順序(16位):原因同2:
u_short htons(u_short hostshort);
舉例:htonl(0)=0
htonl(80)= 20480
5、將unsigned short數從網絡字節順序轉換位主機字節順序,是上面函數的逆函數。
u_short ntohs(u_short netshort);
舉例:ntohs(0)=0
ntohsl(20480)= 80
6、將用點分割的IP地址轉換位一個in_addr結構的地址,這個結構的定義見筆記(一),實際上就是一個unsigned long值。計算機內部處理IP地址可是不認識如192.1.8.84之類的數據。
unsigned long inet_addr( const char FAR * cp );
舉例:inet_addr("192.1.8.84")=1409810880
inet_addr("127.0.0.1")= 16777343
如果發生錯誤,函數返回INADDR_NONE值。
7、將網絡地址轉換位用點分割的IP地址,是上面函數的逆函數。
char FAR * inet_ntoa( struct in_addr in );
舉例:char * ipaddr=NULL;
char addr[20];
in_addr inaddr;
inaddr. s_addr=16777343;
ipaddr= inet_ntoa(inaddr);
strcpy(addr,ipaddr); 這樣addr的值就變為127.0.0.1。
注意意不要修改返回值或者進行釋放動作。如果函數失敗就會返回NULL值。
8、獲取套接字的本地地址結構:
int getsockname(SOCKET s, struct sockaddr FAR * name, int FAR * namelen );
s為套接字
name為函數調用后獲得的地址值
namelen為緩沖區的大小。
9、獲取與套接字相連的端地址結構:
int getpeername(SOCKET s, struct sockaddr FAR * name, int FAR * namelen );
s為套接字
name為函數調用后獲得的端地址值
namelen為緩沖區的大小。
10、獲取計算機名:
int gethostname( char FAR * name, int namelen );
name是存放計算機名的緩沖區
namelen是緩沖區的大小
用法:
char szName[255];
memset(szName,0,255);
if(gethostname(szName,255)==SOCKET_ERROR)
{
//錯誤處理
}
返回值為:szNmae="xiaojin"
11、根據計算機名獲取主機地址:
struct hostent FAR * gethostbyname( const char FAR * name );
name為計算機名。
用法:
hostent * host;
char* ip;
host= gethostbyname("xiaojin");
if(host->h_addr_list[0])
{
struct in_addr addr;
memmove(&addr, host->h_addr_list[0],4);
//獲得標準IP地址
ip=inet_ ntoa (addr);
}
返回值為:hostent->h_name="xiaojin"
hostent->h_addrtype=2 //AF_INET
hostent->length=4
ip="127.0.0.1"
Winsock 的I/O操作:
1、 兩種I/O模式
阻塞模式:執行I/O操作完成前會一直進行等待,不會將控制權交給程序。套接字 默認為阻塞模式。可以通過多線程技術進行處理。
非阻塞模式:執行I/O操作時,Winsock函數會返回并交出控制權。這種模式使用 起來比較復雜,因為函數在沒有運行完成就進行返回,會不斷地返回 WSAEWOULDBLOCK錯誤。但功能強大。
為了解決這個問題,提出了進行I/O操作的一些I/O模型,下面介紹最常見的三種:
2、select模型:
通過調用select函數可以確定一個或多個套接字的狀態,判斷套接字上是否有數據,或
者能否向一個套接字寫入數據。
int select( int nfds, fd_set FAR * readfds, fd_set FAR * writefds,
fd_set FAR *exceptfds, const struct timeval FAR * timeout );
◆先來看看涉及到的結構的定義:
a、 d_set結構:
#define FD_SETSIZE 64?
typedef struct fd_set {
u_int fd_count; /* how many are SET? */
SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */
} fd_set; fd_count為已設定socket的數量
fd_array為socket列表,FD_SETSIZE為最大socket數量,建議不小于64。這是微軟建
議的。
B、timeval結構:
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* and microseconds */
};
tv_sec為時間的秒值。
tv_usec為時間的毫秒值。
這個結構主要是設置select()函數的等待值,如果將該結構設置為(0,0),則select()函數
會立即返回。
◆再來看看select函數各參數的作用:
nfds:沒有任何用處,主要用來進行系統兼容用,一般設置為0。
readfds:等待可讀性檢查的套接字組。
writefds;等待可寫性檢查的套接字組。
exceptfds:等待錯誤檢查的套接字組。
timeout:超時時間。
函數失敗的返回值:調用失敗返回SOCKET_ERROR,超時返回0。
readfds、writefds、exceptfds三個變量至少有一個不為空,同時這個不為空的套接字組
種至少有一個socket,道理很簡單,否則要select干什么呢。 舉例:測試一個套接字是否可讀:
fd_set fdread;
//FD_ZERO定義
// #define FD_ZERO(set) (((fd_set FAR *)(set))->fd_count=0)
FD_ZERO(&fdread);
FD_SET(s,&fdread); //加入套接字,詳細定義請看winsock2.h
if(select(0,%fdread,NULL,NULL,NULL)>0
{
//成功
if(FD_ISSET(s,&fread) //是否存在fread中,詳細定義請看winsock2.h
{
//是可讀的
}
}◆I/O操作函數:主要用于獲取與套接字相關的操作參數。
int ioctlsocket(SOCKET s, long cmd, u_long FAR * argp ); s為I/O操作的套接字。
cmd為對套接字的操作命令。
argp為命令所帶參數的指針。
常見的命令:
//確定套接字自動讀入的數據量
#define FIONREAD _IOR(''''f'''', 127, u_long) /* get # bytes to read */
//允許或禁止套接字的非阻塞模式,允許為非0,禁止為0
#define FIONBIO _IOW(''''f'''', 126, u_long) /* set/clear non-blocking i/o */
//確定是否所有帶外數據都已被讀入
#define SIOCATMARK _IOR(''''s'''', 7, u_long) /* at oob mark? */
3、WSAAsynSelect模型:
WSAAsynSelect模型也是一個常用的異步I/O模型。應用程序可以在一個套接字上接收以
WINDOWS消息為基礎的網絡事件通知。該模型的實現方法是通過調用WSAAsynSelect函
數 自動將套接字設置為非阻塞模式,并向WINDOWS注冊一個或多個網絡時間,并提供一
個通知時使用的窗口句柄。當注冊的事件發生時,對應的窗口將收到一個基于消息的通知。
int WSAAsyncSelect( SOCKET s, HWND hWnd, u_int wMsg, long lEvent); s為需要事件通知的套接字
hWnd為接收消息的窗口句柄
wMsg為要接收的消息
lEvent為掩碼,指定應用程序感興趣的網絡事件組合,主要如下:
#define FD_READ_BIT 0
#define FD_READ (1 << FD_READ_BIT)
#define FD_WRITE_BIT 1
#define FD_WRITE (1 << FD_WRITE_BIT)
#define FD_OOB_BIT 2
#define FD_OOB (1 << FD_OOB_BIT)
#define FD_ACCEPT_BIT 3
#define FD_ACCEPT (1 << FD_ACCEPT_BIT)
#define FD_CONNECT_BIT 4
#define FD_CONNECT (1 << FD_CONNECT_BIT)
#define FD_CLOSE_BIT 5
#define FD_CLOSE (1 << FD_CLOSE_BIT)
用法:要接收讀寫通知:
int nResult= WSAAsyncSelect(s,hWnd,wMsg,FD_READ|FD_WRITE);
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}
取消通知:
int nResult= WSAAsyncSelect(s,hWnd,0,0);
當應用程序窗口hWnd收到消息時,wMsg.wParam參數標識了套接字,lParam的低字標明
了網絡事件,高字則包含錯誤代碼。
4、WSAEventSelect模型
WSAEventSelect模型類似WSAAsynSelect模型,但最主要的區別是網絡事件發生時會被發
送到一個事件對象句柄,而不是發送到一個窗口。
使用步驟如下:
a、 創建事件對象來接收網絡事件:
#define WSAEVENT HANDLE
#define LPWSAEVENT LPHANDLE
WSAEVENT WSACreateEvent( void );
該函數的返回值為一個事件對象句柄,它具有兩種工作狀態:已傳信(signaled)和未傳信
(nonsignaled)以及兩種工作模式:人工重設(manual reset)和自動重設(auto reset)。默認未
未傳信的工作狀態和人工重設模式。
b、將事件對象與套接字關聯,同時注冊事件,使事件對象的工作狀態從未傳信轉變未
已傳信。
int WSAEventSelect( SOCKET s,WSAEVENT hEventObject,long lNetworkEvents ); s為套接字
hEventObject為剛才創建的事件對象句柄
lNetworkEvents為掩碼,定義如上面所述
c、I/O處理后,設置事件對象為未傳信
BOOL WSAResetEvent( WSAEVENT hEvent );Hevent為事件對象
成功返回TRUE,失敗返回FALSE。
d、等待網絡事件來觸發事件句柄的工作狀態:
DWORD WSAWaitForMultipleEvents( DWORD cEvents,
const WSAEVENT FAR * lphEvents, BOOL fWaitAll,
DWORD dwTimeout, BOOL fAlertable );lpEvent為事件句柄數組的指針
cEvent為為事件句柄的數目,其最大值為WSA_MAXIMUM_WAIT_EVENTS
fWaitAll指定等待類型:TRUE:當lphEvent數組重所有事件對象同時有信號時返回;
FALSE:任一事件有信號就返回。
dwTimeout為等待超時(毫秒)
fAlertable為指定函數返回時是否執行完成例程
對事件數組中的事件進行引用時,應該用WSAWaitForMultipleEvents的返回值,減去
預聲明值WSA_WAIT_EVENT_0,得到具體的引用值。例如:
nIndex=WSAWaitForMultipleEvents(…);
MyEvent=EventArray[Index- WSA_WAIT_EVENT_0];e、判斷網絡事件類型:
int WSAEnumNetworkEvents( SOCKET s,
WSAEVENT hEventObject, LPWSANETWORKEVENTS lpNetworkEvents );s為套接字
hEventObject為需要重設的事件對象
lpNetworkEvents為記錄網絡事件和錯誤代碼,其結構定義如下:
typedef struct _WSANETWORKEVENTS {
long lNetworkEvents;
int iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;f、關閉事件對象句柄:
BOOL WSACloseEvent(WSAEVENT hEvent);調用成功返回TRUE,否則返回FALSE。
mfc 添加自定義消息
Windows?應用程序所要做的每項工作幾乎都是基于消息處理的,?Windows?系統消息分為常用?Windows?消息,控件通知消息和命令。然而,有時我們需要定義自己的消息來通知程序什么事情發生了,這就是用戶自定義消息。?ClassWizard?并沒有提供增加用戶自定義消息的功能,所以要使用用戶自定義消息,必須手工編寫代碼。然后?ClassWizard?才可以象處理其它消息一樣處理你自定義的消息。具體做法如下詳解:??第一步:定義消息。一個消息實際上是開發?Windows95?應用程序時,?Microsoft?推薦用戶自定義消息至少是?WM_USER+100?,因為很多新控件也要使用?WM_USER?消息。?
?第二步:實現消息處理函數。該函數使用?WPRAM?和?LPARAM?參數并返回?LPESULT?。?
?LPESULT?CMainFrame::OnMyMessage(WPARAM?wParam,?LPARAM?lParam){//?TODO:?處理用戶自定義消息?AfxMessageBox("?處理用戶自定義消息?");?return?0;}?
?第三步:在類頭文件的?AFX_MSG?塊中說明消息處理函數:?
?class?CMainFrame:public?CMDIFrameWnd{
?...
?//?一般消息映射函數?
?protected:
?//?{{AFX_MSG(CMainFrame)
?afx_msg?int?OnCreate(LPCREATESTRUCT?lpCreateStruct);
?afx_msg?void?OnTimer(UINT?nIDEvent);
?afx_msg?LRESULT?OnMyMessage(WPARAM?wParam,?LPARAM?lParam);
?//}}AFX_MSG
?DECLARE_MESSAGE_MAP()}
?第四步:在用戶類的消息塊中,使用?ON_MESSAGE?宏指令將消息映射到消息處理函數中。?
?BEGIN_MESSAGE_MAP(CMainFrame,?CMDIFrameWnd)
?//{{AFX_MSG_MAP(CMainFrame)
?ON_WM_CREATE()
?ON_WM_TIMER()
?ON_MESSAGE(WM_MY_MESSAGE,?OnMyMessage)
?//}}AFX_MSG_MAPEND_MESSAGE_MAP()
?這樣,一個用戶自定義消息就可以使用了,如果用戶需要一個整個系統唯一的消息,可以調用?SDK?函數?RegisterWindowMessage?并使用?ON_REGISTER_MESSAGE?宏指令取代?ON_MESSAGE?宏指令,其余步驟同上。?
?VC++?為程序員提供了一套功能強大、方便快捷的編程工具,它可以幫你方便的生成窗口、菜單等用戶界面,可惜就是做出來的東西都一樣,沒有一點個性。下面,就介紹一些方法,讓我們可以按照自己的設計定制出更加符合自己程序風格的窗口。 如果用戶需要一個定義整個系統唯一的消息,可以調用SDK函數RegisterWindowMessage定義消息:
| static UINT WM_MY_MESSAGE=RegisterWindowMessage("User"); |
并使用ON_REGISTERED_MESSAGE宏指令取代ON_MESSAGE宏指令,其余步驟同上。
當需要使用自定義消息時,可以在相應類中的函數中調用函數PostMessage或SendMessage發送消息PoseMessage(WM_MY_MESSAGE,O,O); 如果向其他進程發送消息可通過如下方法發送消息:
| DWORD result; SendMessageTimeout(wnd->m_hWnd, // 目標窗口 WM_MY_MESSAGE, // 消息 0, // WPARAM 0, // LPARAM SMTO_ABORTIFHUNG | SMTO_NORMAL, TIMEOUT_INTERVAL, &result); |
以避免其它進程如果被阻塞而造成系統死等狀態。
可是如果需要向其它類(如主框架、子窗口、視類、對話框、狀態條、工具條或其他控件等)發送消息時,上述方法顯得無能為力,而在編程過程中往往需要獲取其它類中的某個識別信號,MFC框架給我們造成了種種限制,但是可以通過獲取某個類的指針而向這個類發送消息,而自定義消息的各種動作則在這個類中定義,這樣就可以自由自在的向其它類發送消息了。
下面舉的例子敘述了向視類和框架類發送消息的方法:
在主框架類中向視類發送消息:
視類中定義消息:
| ON_REGISTERED_MESSAGE(WM_MY_MESSAGE,OnMyMessage) //定義消息映射 視類定義消息處理函數: // 消息處理函數 LRESULT CMessageView::OnMyMessage(WPARAM wParam, LPARAM lParam) { // TODO: 處理用戶自定義消息 ... return 0; } //發送消息的測試函數 void CMainFrame::OnTest() { CView * active = GetActiveView();//獲取當前視類指針 if(active != NULL) active->PostMessage(WM_MY_MESSAGE,0,0); } |
在其它類中向視類發送消息:
| //發送消息的測試函數 ? 在視類中向主框架發送消息:
在其它類中向不同的類發送消息可依次方法類推,這樣我們的程序就可以的不受限制向其它類和進程發送消息,而避免了種種意想不到的風險。 下面一個例子程序為多文檔程序里在一對話框中向視類發送消息,詳述了發送自定義消息的具體過程。 實現步驟:
在Message.h頭文件中添加如下語句:
第四步:在視類中添加自定義消息: 在頭文件MessageView.h中添加消息映射
添加相應的0消息處理函數
在MessageView.h中添加布爾變量 public:BOOL test; 在視類構造函數中初始化 test變量:test=FALSE; 修改CMessageView::OnDraw()函數
第五步:顯示測試對話框 在MainFrame類中包含對話框頭文件:
運行程序,在測試菜單打開對話框,點擊測試按鈕即可看到結果。 |
vc 實現毫秒定時器
定時器使用方法定時器在VC中的使用頻繁,以下討論定義器的使用方法。
定時器的原型是:
WINUSERAPI UINT WINAPI SetTimer ( HWND hWnd , UINT nIDEvent, UINT uElapse, TIMERPROC lpTimerFunc);
??? hWnd????? 是欲設置定時器的窗體句柄。定時時間到時,系統會向該窗體發送WM_TIMER消息。
??? nIDEvent????? 定時器標識符。在一個窗體內可以使用多個定時器,不同的定時器根據nIDEvent來區分。
??? uElapse???????? 定時時間,單位是毫秒。
??? lpTimerFunc 定時器的回調函數。如果該值為NULL,定時時間到時,定時器發送的消息WM_TIMER由窗體映像該消息的函數處理;否則由回調函數處理,說白一點,這里的回調函數就是取代OnTimer的處理函數。
???????? 通常,我們在使用定時器時,只用到三個參數,即
??? UINT CWnd::SetTimer(?
????????????UINT nIDEvent,?
?????UINT nElapse,?
??????void (CALLBACK EXPORT* lpfnTimer)(?HWND, UINT, UINT, DWORD)?
??? );
??? 其實,這個函數只是MFC對API的封裝,其實現函數為:
??? _AFXWIN_INLINE UINT CWnd::SetTimer(UINT nIDEvent, UINT nElapse,
??????? void (CALLBACK* lpfnTimer)(HWND, UINT, UINT, DWORD))
??{
???????????? ASSERT(::IsWindow(m_hWnd));
??? ???? return ::SetTimer(m_hWnd, nIDEvent, nElapse,(TIMERPROC)lpfnTimer);?
??? }
??? 由此可見,CWnd::SetTimer只是將API函數SetTimer的第一個參數設置成它自己的句柄而已。
??? 有了上面的認識,對定時器的使用就清楚了,下面舉例說明定時器的具體使用。
??? 1.打開VC,新建一基于對話框的工程,工程名為Test在對話框上添加一按鈕,將其ID改為IDC_BUTTON_START,Caption改為Start. 映像該按鈕的BN_CLICKED消息,void CTestDlg::OnButtonStart();
??? 2.再在對話框上添加一按鈕,ID為ID_BUTTON_STOP,Caption改為Stop,映像消息為void CTestDlg::OnButtonStop();
??? 3.添加一個Lable,ID改為IDC_STATIC_TIME,用于記數,表明定時器函數的執行。
4.映像對話框的WM_TIMER消息,void CTestDlg::OnTimer(UINT nIDEvent);
??? 以上的實現函數如下所示:
void CTestDlg::OnButtonStart()
{
??? SetTimer(1,1000,NULL);//啟動定時器1,定時時間是1秒
}
?
void CTestDlg::OnButtonStop()
{
??? KillTimer(1);??????? //關閉定時器1。
}
?
void CTestDlg::OnTimer(UINT nIDEvent)
{
??? static int nTimer=0;
??? CString strTmp="";
??? strTmp.Format("Timer:??? %d",nTimer++);
??? CWnd *pWnd=GetDlgItem(IDC_STATIC_TIME);
??? pWnd->SetWindowText(strTmp);? //在Lable<SPAN lang=ZH-CN style="FONT-SIZE: 10pt; FONT-FAMILY: SimSun; mso-ascii-font-family: 細明體; mso-hansi-font-family: 細明體; mso-fareast-language: ZH-CN
opengl----使用3dmax建模后怎樣把模型導入
//? importmodel.h/ #include <math.h>#include <vector>
#include <windows.h>??// Header File For Windows
#include <stdio.h>???// Header File For Standard Input/Output
#include <gl/gl.h>???// Header File For The OpenGL32 Library
#include <gl/glu.h>???// Header File For The GLu32 Library
#include <gl/glaux.h>??// Header File For The Glaux Library
#include <math.h>
//? 基本塊(Primary Chunk),位于文件的開始
#define PRIMARY?????? 0x4D4D
//? 主塊(Main Chunks)
#define OBJECTINFO??? 0x3D3D??// 網格對象的版本號
#define VERSION?????? 0x0002??// .3ds文件的版本
#define EDITKEYFRAME? 0xB000??// 所有關鍵幀信息的頭部
//? 對象的次級定義(包括對象的材質和對象)
#define MATERIAL?? 0xAFFF??// 保存紋理信息
#define OBJECT??? 0x4000??// 保存對象的面、頂點等信息
//? 材質的次級定義
#define MATNAME?????? 0xA000??// 保存材質名稱
#define MATDIFFUSE??? 0xA020??// 對象/材質的顏色
#define MATMAP??????? 0xA200??// 新材質的頭部
#define MATMAPFILE??? 0xA300??// 保存紋理的文件名
#define OBJ_MESH?? 0x4100??// 新的網格對象
#define MAX_TEXTURES? 100???// 最大的紋理數目
//? OBJ_MESH的次級定義
#define OBJ_VERTICES? 0x4110??// 對象頂點
#define OBJ_FACES?? 0x4120??// 對象的面
#define OBJ_MATERIAL? 0x4130??// 對象的材質
#define OBJ_UV??? 0x4140??// 對象的UV紋理坐標 #define MAP_W?????? 32?????? // size of map along x-axis 32
#define MAP_SCALE?? 24.0f???? // the scale of the terrain map
#define MAP???MAP_W*MAP_SCALE/2
#define KEY_DOWN(vk_code)((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define RAND_COORD(x)?? ((float)rand()/RAND_MAX * (x))
#define FRAND?? (((float)rand()-(float)rand())/RAND_MAX) using namespace std; class CVector3??//定義3D點的類,用于保存模型中的頂點
{public:?float x, y, z;
}; class CVector2??//定義2D點類,用于保存模型的UV紋理坐標
{public:?float x, y;
};
struct tFace??//面的結構定義
{?int vertIndex[3];???// 頂點索引
?int coordIndex[3];???// 紋理坐標索引
};
struct tMatInfo//材質信息結構體
{?char? strName[255];???// 紋理名稱
?char? strFile[255];???// 如果存在紋理映射,則表示紋理文件名稱
?BYTE? color[3];????// 對象的RGB顏色
?int?? texureId;????// 紋理ID
?float uTile;????// u 重復
?float vTile;????// v 重復
?float uOffset;?????? // u 紋理偏移
?float vOffset;????// v 紋理偏移
} ;
struct t3DObject?//對象信息結構體
{?int? numOfVerts;???// 模型中頂點的數目
?int? numOfFaces;???// 模型中面的數目
?int? numTexVertex;???// 模型中紋理坐標的數目
?int? materialID;???// 紋理ID
?bool bHasTexture;???// 是否具有紋理映射
?char strName[255];???// 對象的名稱
?CVector3? *pVerts;???// 對象的頂點
?CVector3? *pNormals;??// 對象的法向量
?CVector2? *pTexVerts;??// 紋理UV坐標
?tFace *pFaces;????// 對象的面信息
};
struct t3DModel?//模型信息結構體
{?int numOfObjects;???// 模型中對象的數目
?int numOfMaterials;???// 模型中材質的數目
?vector<tMatInfo>pMaterials;?// 材質鏈表信息
?vector<t3DObject> pObject;?// 模型中對象鏈表信息
};
struct tChunk?//保存塊信息的結構
{?unsigned short int ID;??// 塊的ID??
?unsigned int length;??// 塊的長度
?unsigned int bytesRead;??// 需要讀的塊數據的字節數
}; class CLoad3DS// CLoad3DS類處理所有的裝入代碼
{
public:
?CLoad3DS();????????// 初始化數據成員
?virtual ~CLoad3DS();
?void show3ds(int j0,float tx,float ty,float tz,float size);//顯示3ds模型
?void Init(char *filename,int j);
private:
?bool Import3DS(t3DModel *pModel, char *strFileName);// 裝入3ds文件到模型結構中
?void CreateTexture(UINT textureArray[],LPSTR strFileName,int textureID);//? 從文件中創建紋理
?int? GetString(char *);????????// 讀一個字符串
?void ReadChunk(tChunk *);???????// 讀下一個塊
?void ReadNextChunk(t3DModel *pModel, tChunk *);??// 讀下一個塊
?void ReadNextObjChunk(t3DModel *pModel,t3DObject *pObject,tChunk *);// 讀下一個對象塊
?void ReadNextMatChunk(t3DModel *pModel, tChunk *);?// 讀下一個材質塊
?void ReadColor(tMatInfo *pMaterial, tChunk *pChunk);// 讀對象顏色的RGB值
?void ReadVertices(t3DObject *pObject, tChunk *);?// 讀對象的頂點
?void ReadVertexIndices(t3DObject *pObject,tChunk *);// 讀對象的面信息
?void ReadUVCoordinates(t3DObject *pObject,tChunk *);// 讀對象的紋理坐標
?void ReadObjMat(t3DModel *pModel,t3DObject *pObject,tChunk *pPreChunk);// 讀賦予對象的材質名稱
?void ComputeNormals(t3DModel *pModel);????// 計算對象頂點的法向量
?void CleanUp();??????????// 關閉文件,釋放內存空間
?FILE?*m_FilePointer;????????// 文件指針
?tChunk?*m_CurrentChunk;
?tChunk?*m_TempChunk;
}; // ///ImportModel.cpp #include "StdAfx.h"
//#include "Set3ds.h"
#include "ImportModel.h"
#include <windows.h>??// Header File For Windows
#include <stdio.h>???// Header File For Standard Input/Output
#include <gl/gl.h>???// Header File For The OpenGL32 Library
#include <gl/glu.h>???// Header File For The GLu32 Library
#include <gl/glaux.h>??// Header File For The Glaux Library
#include <math.h>
UINT g_Texture[10][MAX_TEXTURES] = {0};?
t3DModel g_3DModel[10];?
????????
int?? g_ViewMode?? = GL_TRIANGLES;
bool? g_bLighting???? = true;?? CLoad3DS::CLoad3DS()//? 構造函數的功能是初始化tChunk數據
{?m_CurrentChunk = new tChunk;?// 初始化并為當前的塊分配空間
?m_TempChunk = new tChunk;??// 初始化一個臨時塊并分配空間
}
CLoad3DS::~CLoad3DS()
{?CleanUp();// 釋放內存空間
?for(int j = 0; j <10;j++)
?for(int i = 0; i < g_3DModel[j].numOfObjects; i++)
?{?delete [] g_3DModel[j].pObject[i].pFaces;// 刪除所有的變量
??delete [] g_3DModel[j].pObject[i].pNormals;
??delete [] g_3DModel[j].pObject[i].pVerts;
??delete [] g_3DModel[j].pObject[i].pTexVerts;
?}
}
void CLoad3DS::Init(char *filename,int j)//
{?Import3DS(&g_3DModel[j], filename);???// 將3ds文件裝入到模型結構體中
?for(int i =0; i<g_3DModel[j].numOfMaterials;i++)
?{if(strlen(g_3DModel[j].pMaterials[i].strFile)>0)// 判斷是否是一個文件名
?CreateTexture(g_Texture[j], g_3DModel[j].pMaterials[i].strFile, i);//使用紋理文件名稱來裝入位圖???
? g_3DModel[j].pMaterials[i].texureId = i;// 設置材質的紋理ID
?}
}
//? 從文件中創建紋理
void CLoad3DS::CreateTexture(UINT textureArray[], LPSTR strFileName, int textureID)
{?AUX_RGBImageRec *pBitmap = NULL;
?if(!strFileName) return;???????// 如果無此文件,則直接返回
?pBitmap = auxDIBImageLoad(strFileName);????// 裝入位圖,并保存數據
?if(pBitmap == NULL)??exit(0);?????// 如果裝入位圖失敗,則退出
?// 生成紋理
?glGenTextures(1, &textureArray[textureID]);
?// 設置像素對齊格式
?glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
?glBindTexture(GL_TEXTURE_2D, textureArray[textureID]);
?gluBuild2DMipmaps(GL_TEXTURE_2D, 3, pBitmap->sizeX, pBitmap->sizeY, GL_RGB, GL_UNSIGNED_BYTE, pBitmap->data);
?glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);
?glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR_MIPMAP_LINEAR);
?if (pBitmap)??????????// 釋放位圖占用的資源
?{?if (pBitmap->data)?free(pBitmap->data);???????
??free(pBitmap);?????
?}
}
void CLoad3DS::show3ds(int j0,float tx,float ty,float tz,float size) //顯示3ds模型
{
?glPushAttrib(GL_CURRENT_BIT);//保存現有顏色屬實性
?glPushMatrix();
?glDisable(GL_TEXTURE_2D);
?::glTranslatef( tx, ty, tz);
?::glScaled(size,size,size);
?glRotatef(90, 0, 1.0f, 0);
?// 遍歷模型中所有的對象
?for(int i = 0; i < g_3DModel[j0].numOfObjects; i++)
?{if(g_3DModel[j0].pObject.size() <= 0) break;// 如果對象的大小小于0,則退出
? t3DObject *pObject = &g_3DModel[j0].pObject[i];// 獲得當前顯示的對象
? if(pObject->bHasTexture)// 判斷該對象是否有紋理映射
?? {?glEnable(GL_TEXTURE_2D);// 打開紋理映射
???glBindTexture(GL_TEXTURE_2D, g_Texture[j0][pObject->materialID]);
?? }
? else?glDisable(GL_TEXTURE_2D);// 關閉紋理映射
//這里原來有錯,不行正確調用模型的貼圖,g_Texture應該為2維數組?
? glColor3ub(255, 255, 255); glBegin(g_ViewMode);//開始以g_ViewMode模式繪制?????
? for(int j = 0; j < pObject->numOfFaces; j++)??// 遍歷所有的面
? {for(int tex = 0; tex < 3; tex++)?????// 遍歷三角形的所有點
??{int index = pObject->pFaces[j].vertIndex[tex];?// 獲得面對每個點的索引
?? glNormal3f(pObject->pNormals[index].x,pObject->pNormals[index].y,?
?????????? pObject->pNormals[index].z);??// 給出法向量
?? if(pObject->bHasTexture)??????// 如果對象具有紋理
?? {?if(pObject->pTexVerts)??????// 確定是否有UVW紋理坐標
????glTexCoord2f(pObject->pTexVerts[index].x,pObject->pTexVerts[index].y);
?? }
?? else
?? {?if(g_3DModel[j0].pMaterials.size() && pObject->materialID>= 0)
???{?BYTE *pColor = g_3DModel[j0].pMaterials[pObject->materialID].color;
????glColor3ub(pColor[0],pColor[1],pColor[2]);
???}
?? }
?? glVertex3f(pObject->pVerts[index].x,pObject->pVerts[index].y,pObject->pVerts[index].z);
??}
? }
?glEnd();// 繪制結束
?}
?glEnable(GL_TEXTURE_2D);
?glPopMatrix();
?glPopAttrib();//恢復前一屬性
}
//
//? 打開一個3ds文件,讀出其中的內容,并釋放內存
bool CLoad3DS::Import3DS(t3DModel *pModel, char *strFileName)
{?char strMessage[255] = {0};
?// 打開一個3ds文件
?m_FilePointer = fopen(strFileName, "rb");
?// 確保所獲得的文件指針合法
?if(!m_FilePointer)
?{?sprintf(strMessage, "Unable to find the file: %s!", strFileName);
??MessageBox(NULL, strMessage, "Error", MB_OK);
??return false;
?}
?// 當文件打開之后,首先應該將文件最開始的數據塊讀出以判斷是否是一個3ds文件
?// 如果是3ds文件的話,第一個塊ID應該是PRIMARY
?// 將文件的第一塊讀出并判斷是否是3ds文件
?ReadChunk(m_CurrentChunk);
?// 確保是3ds文件
?if (m_CurrentChunk->ID != PRIMARY)
?{?sprintf(strMessage, "Unable to load PRIMARY chuck from file: %s!", strFileName);
??MessageBox(NULL, strMessage, "Error", MB_OK);
??return false;
?}
?// 現在開始讀入數據,ReadNextChunk()是一個遞歸函數
?// 通過調用下面的遞歸函數,將對象讀出
?ReadNextChunk(pModel, m_CurrentChunk);
?// 在讀完整個3ds文件之后,計算頂點的法線
?ComputeNormals(pModel);
?// 釋放內存空間
//?CleanUp();
?return true;
}
//? 下面的函數釋放所有的內存空間,并關閉文件
void CLoad3DS::CleanUp()
{?// 遍歷場景中所有的對象
?fclose(m_FilePointer);??????// 關閉當前的文件指針
?delete m_CurrentChunk;??????// 釋放當前塊
?delete m_TempChunk;???????// 釋放臨時塊
} //? 下面的函數讀出3ds文件的主要部分
void CLoad3DS::ReadNextChunk(t3DModel *pModel, tChunk *pPreChunk)
{?t3DObject newObject = {0};?????// 用來添加到對象鏈表
?tMatInfo newTexture = {0};????// 用來添加到材質鏈表
?unsigned int version = 0;?????// 保存文件版本
?int buffer[50000] = {0};?????// 用來跳過不需要的數據
?m_CurrentChunk = new tChunk;????// 為新的塊分配空間??
?//? 下面每讀一個新塊,都要判斷一下塊的ID,如果該塊是需要的讀入的,則繼續進行
?//? 如果是不需要讀入的塊,則略過
?// 繼續讀入子塊,直到達到預定的長度
?while (pPreChunk->bytesRead < pPreChunk->length)
?{?// 讀入下一個塊
??ReadChunk(m_CurrentChunk);
??// 判斷塊的ID號
??switch (m_CurrentChunk->ID)
??{
??case VERSION:???????// 文件版本號
???// 在該塊中有一個無符號短整型數保存了文件的版本
???// 讀入文件的版本號,并將字節數添加到bytesRead變量中
???m_CurrentChunk->bytesRead += fread(&version, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);
???// 如果文件版本號大于3,給出一個警告信息
???if (version > 0x03)
????MessageBox(NULL, "This 3DS file is over version 3 so it may load incorrectly", "Warning", MB_OK);
???break;
??case OBJECTINFO:??????// 網格版本信息
???// 讀入下一個塊
???ReadChunk(m_TempChunk);
???// 獲得網格的版本號
???m_TempChunk->bytesRead += fread(&version, 1, m_TempChunk->length - m_TempChunk->bytesRead, m_FilePointer);
???// 增加讀入的字節數
???m_CurrentChunk->bytesRead += m_TempChunk->bytesRead;
???// 進入下一個塊
???ReadNextChunk(pModel, m_CurrentChunk);
???break;
??case MATERIAL:???????// 材質信息
???// 材質的數目遞增
???pModel->numOfMaterials++;
???// 在紋理鏈表中添加一個空白紋理結構
???pModel->pMaterials.push_back(newTexture);
???// 進入材質裝入函數
???ReadNextMatChunk(pModel, m_CurrentChunk);
???break;
??case OBJECT:???????// 對象的名稱
???// 該塊是對象信息塊的頭部,保存了對象了名稱
???// 對象數遞增
???pModel->numOfObjects++;
???// 添加一個新的tObject節點到對象鏈表中
???pModel->pObject.push_back(newObject);
???// 初始化對象和它的所有數據成員
???memset(&(pModel->pObject[pModel->numOfObjects - 1]), 0, sizeof(t3DObject));
???// 獲得并保存對象的名稱,然后增加讀入的字節數
???m_CurrentChunk->bytesRead += GetString(pModel->pObject[pModel->numOfObjects - 1].strName);
???// 進入其余的對象信息的讀入
???ReadNextObjChunk(pModel, &(pModel->pObject[pModel->numOfObjects - 1]), m_CurrentChunk);
???break;
??case EDITKEYFRAME:
???// 跳過關鍵幀塊的讀入,增加需要讀入的字節數
???m_CurrentChunk->bytesRead += fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);
???break;
??default:
???//? 跳過所有忽略的塊的內容的讀入,增加需要讀入的字節數
???m_CurrentChunk->bytesRead += fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);
???break;
??}
??// 增加從最后塊讀入的字節數
??pPreChunk->bytesRead += m_CurrentChunk->bytesRead;
?}
?// 釋放當前塊的內存空間
?delete m_CurrentChunk;
?m_CurrentChunk = pPreChunk;
}
//? 下面的函數處理所有的文件中對象的信息
void CLoad3DS::ReadNextObjChunk(t3DModel *pModel, t3DObject *pObject, tChunk *pPreChunk)
{?int buffer[50000] = {0};?????// 用于讀入不需要的數據
?// 對新的塊分配存儲空間
?m_CurrentChunk = new tChunk;
?// 繼續讀入塊的內容直至本子塊結束
?while (pPreChunk->bytesRead < pPreChunk->length)
?{?// 讀入下一個塊
??ReadChunk(m_CurrentChunk);
??// 區別讀入是哪種塊
??switch (m_CurrentChunk->ID)
??{
??case OBJ_MESH:?????// 正讀入的是一個新塊
???// 使用遞歸函數調用,處理該新塊
???ReadNextObjChunk(pModel, pObject, m_CurrentChunk);
???break;
??case OBJ_VERTICES:????// 讀入是對象頂點
???ReadVertices(pObject, m_CurrentChunk);
???break;
??case OBJ_FACES:?????// 讀入的是對象的面
???ReadVertexIndices(pObject, m_CurrentChunk);
???break;
??case OBJ_MATERIAL:????// 讀入的是對象的材質名稱
???// 該塊保存了對象材質的名稱,可能是一個顏色,也可能是一個紋理映射。同時在該塊中也保存了
???// 紋理對象所賦予的面
???// 下面讀入對象的材質名稱
???ReadObjMat(pModel, pObject, m_CurrentChunk);???
???break;
??case OBJ_UV:??????// 讀入對象的UV紋理坐標
???// 讀入對象的UV紋理坐標
???ReadUVCoordinates(pObject, m_CurrentChunk);
???break;
??default:?
???// 略過不需要讀入的塊
???m_CurrentChunk->bytesRead += fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);
???break;
??}
??// 添加從最后塊中讀入的字節數到前面的讀入的字節中
??pPreChunk->bytesRead += m_CurrentChunk->bytesRead;
?}
?// 釋放當前塊的內存空間,并把當前塊設置為前面塊
?delete m_CurrentChunk;
?m_CurrentChunk = pPreChunk;
}
//? 下面的函數處理所有的材質信息
void CLoad3DS::ReadNextMatChunk(t3DModel *pModel, tChunk *pPreChunk)
{?int buffer[50000] = {0};?????// 用于讀入不需要的數據
?// 給當前塊分配存儲空間
?m_CurrentChunk = new tChunk;
?// 繼續讀入這些塊,知道該子塊結束
?while (pPreChunk->bytesRead < pPreChunk->length)
?{?// 讀入下一塊
??ReadChunk(m_CurrentChunk);
??// 判斷讀入的是什么塊
??switch (m_CurrentChunk->ID)
??{
??case MATNAME:???????// 材質的名稱
???// 讀入材質的名稱
???m_CurrentChunk->bytesRead += fread(pModel->pMaterials[pModel->numOfMaterials - 1].strName, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);
???break;
??case MATDIFFUSE:??????// 對象的R G B顏色
???ReadColor(&(pModel->pMaterials[pModel->numOfMaterials - 1]), m_CurrentChunk);
???break;
??case MATMAP:???????// 紋理信息的頭部
???// 進入下一個材質塊信息
???ReadNextMatChunk(pModel, m_CurrentChunk);
???break;
??case MATMAPFILE:??????// 材質文件的名稱
???// 讀入材質的文件名稱
???m_CurrentChunk->bytesRead += fread(pModel->pMaterials[pModel->numOfMaterials - 1].strFile, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);
???break;
??default:?
???// 掠過不需要讀入的塊
???m_CurrentChunk->bytesRead += fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);
???break;
??}
??// 添加從最后塊中讀入的字節數
??pPreChunk->bytesRead += m_CurrentChunk->bytesRead;
?}
?// 刪除當前塊,并將當前塊設置為前面的塊
?delete m_CurrentChunk;
?m_CurrentChunk = pPreChunk;
}
//? 下面函數讀入塊的ID號和它的字節長度
void CLoad3DS::ReadChunk(tChunk *pChunk)
{?// 讀入塊的ID號,占用了2個字節。塊的ID號象OBJECT或MATERIAL一樣,說明了在塊中所包含的內容
?pChunk->bytesRead = fread(&pChunk->ID, 1, 2, m_FilePointer);
?// 然后讀入塊占用的長度,包含了四個字節
?pChunk->bytesRead += fread(&pChunk->length, 1, 4, m_FilePointer);
}
//? 下面的函數讀入一個字符串
int CLoad3DS::GetString(char *pBuffer)
{?int index = 0;
?// 讀入一個字節的數據
?fread(pBuffer, 1, 1, m_FilePointer);
?// 直到結束
?while (*(pBuffer + index++) != 0) {
??// 讀入一個字符直到NULL
??fread(pBuffer + index, 1, 1, m_FilePointer);
?}
?// 返回字符串的長度
?return strlen(pBuffer) + 1;
}
//? 下面的函數讀入RGB顏色
void CLoad3DS::ReadColor(tMatInfo *pMaterial, tChunk *pChunk)
{?// 讀入顏色塊信息
?ReadChunk(m_TempChunk);
?// 讀入RGB顏色
?m_TempChunk->bytesRead += fread(pMaterial->color, 1, m_TempChunk->length - m_TempChunk->bytesRead, m_FilePointer);
?// 增加讀入的字節數
?pChunk->bytesRead += m_TempChunk->bytesRead;
}
//? 下面的函數讀入頂點索引
void CLoad3DS::ReadVertexIndices(t3DObject *pObject, tChunk *pPreChunk)
{?unsigned short index = 0;?????// 用于讀入當前面的索引
?// 讀入該對象中面的數目
?pPreChunk->bytesRead += fread(&pObject->numOfFaces, 1, 2, m_FilePointer);
?// 分配所有面的存儲空間,并初始化結構
?pObject->pFaces = new tFace [pObject->numOfFaces];
?memset(pObject->pFaces, 0, sizeof(tFace) * pObject->numOfFaces);
?// 遍歷對象中所有的面
?for(int i = 0; i < pObject->numOfFaces; i++)
?{?for(int j = 0; j < 4; j++)
??{?// 讀入當前面的第一個點
???pPreChunk->bytesRead += fread(&index, 1, sizeof(index), m_FilePointer);
???if(j < 3)
???{?// 將索引保存在面的結構中
????pObject->pFaces[i].vertIndex[j] = index;
???}
??}
?}
}
//? 下面的函數讀入對象的UV坐標
void CLoad3DS::ReadUVCoordinates(t3DObject *pObject, tChunk *pPreChunk)
{?// 為了讀入對象的UV坐標,首先需要讀入UV坐標的數量,然后才讀入具體的數據
?// 讀入UV坐標的數量
?pPreChunk->bytesRead += fread(&pObject->numTexVertex, 1, 2, m_FilePointer);
?// 分配保存UV坐標的內存空間
?pObject->pTexVerts = new CVector2 [pObject->numTexVertex];
?// 讀入紋理坐標
?pPreChunk->bytesRead += fread(pObject->pTexVerts, 1, pPreChunk->length - pPreChunk->bytesRead, m_FilePointer);
}
//? 讀入對象的頂點
void CLoad3DS::ReadVertices(t3DObject *pObject, tChunk *pPreChunk)
{?// 在讀入實際的頂點之前,首先必須確定需要讀入多少個頂點。
?// 讀入頂點的數目
?pPreChunk->bytesRead += fread(&(pObject->numOfVerts), 1, 2, m_FilePointer);
?// 分配頂點的存儲空間,然后初始化結構體
?pObject->pVerts = new CVector3 [pObject->numOfVerts];
?memset(pObject->pVerts, 0, sizeof(CVector3) * pObject->numOfVerts);
?// 讀入頂點序列
?pPreChunk->bytesRead += fread(pObject->pVerts, 1, pPreChunk->length - pPreChunk->bytesRead, m_FilePointer);
?// 現在已經讀入了所有的頂點。
?// 因為3D Studio Max的模型的Z軸是指向上的,因此需要將y軸和z軸翻轉過來。
?// 具體的做法是將Y軸和Z軸交換,然后將Z軸反向。
?// 遍歷所有的頂點
?for(int i = 0; i < pObject->numOfVerts; i++)
?{?// 保存Y軸的值
??float fTempY = pObject->pVerts[i].y;
??// 設置Y軸的值等于Z軸的值
??pObject->pVerts[i].y = pObject->pVerts[i].z;
??// 設置Z軸的值等于-Y軸的值
??pObject->pVerts[i].z = -fTempY;
?}
}
//? 下面的函數讀入對象的材質名稱
void CLoad3DS::ReadObjMat(t3DModel *pModel, t3DObject *pObject, tChunk *pPreChunk)
{?char strMaterial[255] = {0};???// 用來保存對象的材質名稱
?int buffer[50000] = {0};????// 用來讀入不需要的數據
?// 材質或者是顏色,或者是對象的紋理,也可能保存了象明亮度、發光度等信息。
?// 下面讀入賦予當前對象的材質名稱
?pPreChunk->bytesRead += GetString(strMaterial);
?// 遍歷所有的紋理
?for(int i = 0; i < pModel->numOfMaterials; i++)
?{?//如果讀入的紋理與當前的紋理名稱匹配
??if(strcmp(strMaterial, pModel->pMaterials[i].strName) == 0)
??{?// 設置材質ID
???pObject->materialID = i;
???// 判斷是否是紋理映射,如果strFile是一個長度大于1的字符串,則是紋理
???if(strlen(pModel->pMaterials[i].strFile) > 0) {
????// 設置對象的紋理映射標志
????pObject->bHasTexture = true;
???}?
???break;
??}
??else
??{?// 如果該對象沒有材質,則設置ID為-1
???pObject->materialID = -1;
??}
?}
?pPreChunk->bytesRead += fread(buffer, 1, pPreChunk->length - pPreChunk->bytesRead, m_FilePointer);
}???
//? 下面的這些函數主要用來計算頂點的法向量,頂點的法向量主要用來計算光照
// 下面的宏定義計算一個矢量的長度
#define Mag(Normal) (sqrt(Normal.x*Normal.x + Normal.y*Normal.y + Normal.z*Normal.z))
// 下面的函數求兩點決定的矢量
CVector3 Vector(CVector3 vPoint1, CVector3 vPoint2)
{?CVector3 vVector;???????
?vVector.x = vPoint1.x - vPoint2.x;???
?vVector.y = vPoint1.y - vPoint2.y;???
?vVector.z = vPoint1.z - vPoint2.z;???
?return vVector;????????
}
// 下面的函數兩個矢量相加
CVector3 AddVector(CVector3 vVector1, CVector3 vVector2)
{?CVector3 vResult;???????
?vResult.x = vVector2.x + vVector1.x;??
?vResult.y = vVector2.y + vVector1.y;??
?vResult.z = vVector2.z + vVector1.z;??
?return vResult;????????
}
// 下面的函數處理矢量的縮放
CVector3 DivideVectorByScaler(CVector3 vVector1, float Scaler)
{?CVector3 vResult;???????
?vResult.x = vVector1.x / Scaler;???
?vResult.y = vVector1.y / Scaler;???
?vResult.z = vVector1.z / Scaler;???
?return vResult;????????
}
// 下面的函數返回兩個矢量的叉積
CVector3 Cross(CVector3 vVector1, CVector3 vVector2)
{?CVector3 vCross;????????
?vCross.x = ((vVector1.y * vVector2.z) - (vVector1.z * vVector2.y));
?vCross.y = ((vVector1.z * vVector2.x) - (vVector1.x * vVector2.z));
?vCross.z = ((vVector1.x * vVector2.y) - (vVector1.y * vVector2.x));
?return vCross;????????
}
// 下面的函數規范化矢量
CVector3 Normalize(CVector3 vNormal)
{?double Magnitude;???????
?Magnitude = Mag(vNormal);?????// 獲得矢量的長度
?vNormal.x /= (float)Magnitude;????
?vNormal.y /= (float)Magnitude;????
?vNormal.z /= (float)Magnitude;????
?return vNormal;????????
}
//? 下面的函數用于計算對象的法向量
void CLoad3DS::ComputeNormals(t3DModel *pModel)
{?CVector3 vVector1, vVector2, vNormal, vPoly[3];
?// 如果模型中沒有對象,則返回
?if(pModel->numOfObjects <= 0)
??return;
?// 遍歷模型中所有的對象
?for(int index = 0; index < pModel->numOfObjects; index++)
?{?// 獲得當前的對象
??t3DObject *pObject = &(pModel->pObject[index]);
??// 分配需要的存儲空間
??CVector3 *pNormals??= new CVector3 [pObject->numOfFaces];
??CVector3 *pTempNormals?= new CVector3 [pObject->numOfFaces];
??pObject->pNormals??= new CVector3 [pObject->numOfVerts];
??// 遍歷對象的所有面
??for(int i=0; i < pObject->numOfFaces; i++)
??{?vPoly[0] = pObject->pVerts[pObject->pFaces[i].vertIndex[0]];
???vPoly[1] = pObject->pVerts[pObject->pFaces[i].vertIndex[1]];
???vPoly[2] = pObject->pVerts[pObject->pFaces[i].vertIndex[2]];
???// 計算面的法向量
???vVector1 = Vector(vPoly[0], vPoly[2]);??// 獲得多邊形的矢量
???vVector2 = Vector(vPoly[2], vPoly[1]);??// 獲得多邊形的第二個矢量
???vNormal? = Cross(vVector1, vVector2);??// 獲得兩個矢量的叉積
???pTempNormals[i] = vNormal;?????// 保存非規范化法向量
???vNormal? = Normalize(vNormal);????// 規范化獲得的叉積
???pNormals[i] = vNormal;??????// 將法向量添加到法向量列表中
??}
??//? 下面求頂點法向量
??CVector3 vSum = {0.0, 0.0, 0.0};
??CVector3 vZero = vSum;
??int shared=0;
??// 遍歷所有的頂點
??for (i = 0; i < pObject->numOfVerts; i++)???
??{?for (int j = 0; j < pObject->numOfFaces; j++)?// 遍歷所有的三角形面
???{????????????// 判斷該點是否與其它的面共享
????if (pObject->pFaces[j].vertIndex[0] == i ||
?????pObject->pFaces[j].vertIndex[1] == i ||
?????pObject->pFaces[j].vertIndex[2] == i)
????{?vSum = AddVector(vSum, pTempNormals[j]);
?????shared++;????????
????}
???}?????
???pObject->pNormals[i] = DivideVectorByScaler(vSum, float(-shared));
???// 規范化最后的頂點法向
???pObject->pNormals[i] = Normalize(pObject->pNormals[i]);?
???vSum = vZero;????????
???shared = 0;??????????
??}
??// 釋放存儲空間,開始下一個對象
??delete [] pTempNormals;
??delete [] pNormals;
?}
} // 使用模型: class basicpic
{
public:
?basicpic();
?virtual ~basicpic(); GLUquadricObj *obj; CLoad3DS* m_3ds;
?void Scene(int obj,float size);
}; basicpic::basicpic()
{
?m_3ds=new CLoad3DS();
?
?m_3ds->Init("炮管1組.3DS",0);
??? m_3ds->Init("炮蓋1.3DS",1);
?m_3ds->Init("炮蓋2.3DS",2);
?m_3ds->Init("炮蓋3.3DS",3);
?m_3ds->Init("炮蓋4.3DS",4);
?m_3ds->Init("導彈1.3DS",5);
?m_3ds->Init("導彈2.3DS",6);
?m_3ds->Init("導彈3.3DS",7);
?m_3ds->Init("導彈4.3DS",8);
?glEnable(GL_TEXTURE_2D); }
void basicpic::Scene(int obj,float size)
{
?m_3ds->show3ds(obj,0,0,0,size);
} 到入時:用scene(模型號,大小);
?
opengl--貼圖
| 這段代碼用來加載位圖文件。如果文件不存在,返回 NULL 告知程序無法加載位圖。在我開始解釋這段代碼之前,關于用作紋理的圖像我想有幾點十分重要,并且您必須明白。此圖像的寬和高必須是2的n次方;寬度和高度最小必須是64象素;并且出于兼容性的原因,圖像的寬度和高度不應超過256象素。如果您的原始素材的寬度和高度不是64,128,256象素的話,使用圖像處理軟件重新改變圖像的大小。可以肯定有辦法能繞過這些限制,但現在我們只需要用標準的紋理尺寸。 首先,我們創建一個文件句柄。句柄是個用來鑒別資源的數值,它使程序能夠訪問此資源。我們開始先將句柄設為 NULL 。 | |||||||||
| AUX_RGBImageRec *LoadBMP(char *Filename) // 載入位圖圖象 { FILE *File=NULL; // 文件句柄 | |||||||||
| 接下來檢查文件名是否已提供。因為 LoadBMP() 可以無參數調用,所以我們不得不檢查一下。您可不想什么都沒載入吧.....:) | |||||||||
| if (!Filename) // 確保文件名已提供。 { return NULL; // 如果沒提供,返回 NULL } | |||||||||
| 接著檢查文件是否存在。下面這一行嘗試打開文件。 | |||||||||
| File=fopen(Filename,"r"); //嘗試打開文件 | |||||||||
| 如果我們能打開文件的話,很顯然文件是存在的。使用 fclose(File) 關閉文件。 auxDIBImageLoad(Filename) 讀取圖象數據并將其返回。 | |||||||||
| if (File) // 文件存在么? { fclose(File); // 關閉句柄 return auxDIBImageLoad(Filename); //載入位圖并返回指針 } | |||||||||
| 如果我們不能打開文件,我們將返回NULL。這意味著文件無法載入。程序在后面將檢查文件是否已載入。如果沒有,我們將退出程序并彈出錯誤消息。 | |||||||||
| return NULL; // 如果載入失敗,返回 NULL } | |||||||||
| 下一部分代碼載入位圖(調用上面的代碼)并轉換成紋理。 | |||||||||
| int LoadGLTextures() // 載入位圖(調用上面的代碼)并轉換成紋理 { | |||||||||
| 然后設置一個叫做 Status 的變量。我們使用它來跟蹤是否能夠載入位圖以及能否創建紋理。 Status 缺省設為 FALSE (表示沒有載入或創建任何東東)。 | |||||||||
| int Status=FALSE; // Status 狀態指示器 | |||||||||
| 現在我們創建存儲位圖的圖像記錄。次記錄包含位圖的寬度、高度和數據。 | |||||||||
| AUX_RGBImageRec *TextureImage[1]; // 創建紋理的存儲空間 | |||||||||
| 清除圖像記錄,確保其內容為空。 | |||||||||
| memset(TextureImage,0,sizeof(void *)*1); // 將指針設為 NULL | |||||||||
| 現在載入位圖,并將其轉換為紋理。 TextureImage[0]=LoadBMP("Data/NeHe.bmp") 調用 LoadBMP() 的代碼。載入 Data 目錄下的 NeHe.bmp 位圖文件。如果一切正常,圖像數據將存放在 TextureImage[0] 中, Status 被設為 TRUE ,然后我們開始創建紋理。 | |||||||||
| // 載入位圖,檢查有無錯誤,如果位圖沒找到則退出。 if (TextureImage[0]=LoadBMP("Data/NeHe.bmp")) { Status=TRUE; // 將 Status 設為 TRUE | |||||||||
| 現在使用中 TextureImage[0] 的數據創建紋理。第一行 glGenTextures(1, &texture[0]) 告訴OpenGL我們想生成一個紋理名字(如果您想載入多個紋理,加大數字)。值得注意的是,開始我們使用 GLuint texture[1] 來創建一個紋理的存儲空間,您也許會認為第一個紋理就是存放在 &texture[1] 中的,但這是錯的。正確的地址應該是 &texture[0] 。同樣如果使用 GLuint texture[2] 的話,第二個紋理存放在 texture[1] 中。『譯者注:學C的,在這里應該沒有障礙,數組就是從零開始的嘛。』 第二行 glBindTexture(GL_TEXTURE_2D, texture[0]) 告訴OpenGL將紋理名字 texture[0] 綁定到紋理目標上。2D紋理只有高度(在 Y 軸上)和寬度(在 X 軸上)。主函數將紋理名字指派給紋理數據。本例中我們告知OpenGL, &texture[0] 處的內存已經可用。我們創建的紋理將存儲在 &texture[0] 的 指向的內存區域。 | |||||||||
| glGenTextures(1, &texture[0]); // 創建紋理 // 使用來自位圖數據生成 的典型紋理 glBindTexture(GL_TEXTURE_2D, texture[0]); | |||||||||
| 下來我們創建真正的紋理。下面一行告訴OpenGL此紋理是一個2D紋理 ( GL_TEXTURE_2D )。數字零代表圖像的詳細程度,通常就由它為零去了。數字三是數據的成分數。因為圖像是由紅色數據,綠色數據,藍色數據三種組分組成。 TextureImage[0]->sizeX 是紋理的寬度。如果您知道寬度,您可以在這里填入,但計算機可以很容易的為您指出此值。 TextureImage[0]->sizey 是紋理的高度。數字零是邊框的值,一般就是零。 GL_RGB 告訴OpenGL圖像數據由紅、綠、藍三色數據組成。 GL_UNSIGNED_BYTE 意味著組成圖像的數據是無符號字節類型的。最后... TextureImage[0]->data 告訴OpenGL紋理數據的來源。此例中指向存放在 TextureImage[0] 記錄中的數據。 | |||||||||
| // 生成紋理 glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data); | |||||||||
| 下面的兩行告訴OpenGL在顯示圖像時,當它比放大得原始的紋理大 ( GL_TEXTURE_MAG_FILTER )或縮小得比原始得紋理小( GL_TEXTURE_MIN_FILTER )時OpenGL采用的濾波方式。通常這兩種情況下我都采用 GL_LINEAR 。這使得紋理從很遠處到離屏幕很近時都平滑顯示。使用 GL_LINEAR 需要CPU和顯卡做更多的運算。如果您的機器很慢,您也許應該采用 GL_NEAREST 。過濾的紋理在放大的時候,看起來斑駁的很『譯者注:馬賽克啦』。您也可以結合這兩種濾波方式。在近處時使用 GL_LINEAR ,遠處時 GL_NEAREST 。 | |||||||||
| glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); // 線形濾波 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); // 線形濾波 } | |||||||||
| 現在我們釋放前面用來存放位圖數據的內存。我們先查看位圖數據是否存放在處。如果是的話,再查看數據是否已經存儲。如果已經存儲的話,刪了它。接著再釋放 TextureImage[0] 圖像結構以保證所有的內存都能釋放。 | |||||||||
| if (TextureImage[0]) // 紋理是否存在 { if (TextureImage[0]->data) // 紋理圖像是否存在 { free(TextureImage[0]->data); // 釋放紋理圖像占用的內存 } free(TextureImage[0]); // 釋放圖像結構 } | |||||||||
| 最后返回狀態變量。如果一切OK,變量 Status 的值為 TRUE 。否則為 FALSE 。 | |||||||||
| return Status; // 返回 Status ? ----------------------------------------------------------- int InitGL(GLvoid) // 此處開始對OpenGL進行所有設置 ............. ---------------------------------------------------------------------------------------------
|
opengl---設置光源
| 接著設置用來創建光源的數組。我們將使用兩種不同的光。第一種稱為環境光。環境光來自于四面八方。所有場景中的對象都處于環境光的照射中。第二種類型的光源叫做漫射光。漫射光由特定的光源產生,并在您的場景中的對象表面上產生反射。處于漫射光直接照射下的任何對象表面都變得很亮,而幾乎未被照射到的區域就顯得要暗一些。這樣在我們所創建的木板箱的棱邊上就會產生的很不錯的陰影效果。 創建光源的過程和顏色的創建完全一致。前三個參數分別是RGB三色分量,最后一個是alpha通道參數。 因此,下面的代碼我們得到的是半亮(0.5f)的白色環境光。如果沒有環境光,未被漫射光照到的地方會變得十分黑暗。 | |||||||||||||||||
| GLfloat LightAmbient[]= { 0.5f, 0.5f, 0.5f, 1.0f }; //環境光參數 ( 新增 ) | |||||||||||||||||
| 下一行代碼我們生成最亮的漫射光。所有的參數值都取成最大值1.0f。它將照在我們木板箱的前面,看起來挺好。 | |||||||||||||||||
| GLfloat LightDiffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f }; // 漫射光參數 ( 新增 ) | |||||||||||||||||
| 最后我們保存光源的位置。前三個參數和glTranslate中的一樣。依次分別是XYZ軸上的位移。由于我們想要光線直接照射在木箱的正面,所以XY軸上的位移都是0.0f。第三個值是Z軸上的位移。為了保證光線總在木箱的前面,所以我們將光源的位置朝著觀察者(就是您哪。)挪出屏幕。我們通常將屏幕也就是顯示器的屏幕玻璃所處的位置稱作Z軸的0.0f點。所以Z軸上的位移最后定為2.0f。假如您能夠看見光源的話,它就浮在您顯示器的前方。當然,如果木箱不在顯示器的屏幕玻璃后面的話,您也無法看見箱子。『譯者注:我很欣賞NeHe的耐心。說真的有時我都打煩了,這么簡單的事他這么廢話干嘛?但如果什么都清楚,您還會翻著這樣的頁面看個沒完么?』 最后一個參數取為1.0f。這將告訴OpenGL這里指定的坐標就是光源的位置,以后的教程中我會多加解釋。 | |||||||||||||||||
| GLfloat LightPosition[]= { 0.0f, 0.0f, 2.0f, 1.0f }; // 光源位置
? |
OpenGL--著色透明
glColor3f(r,g,b)。括號中的三個參數依次是紅、綠、藍三色分量。取值范圍可以從0,0f到1.0f。 OpenGL中的絕大多數特效都與某些類型的(色彩)混合有關。混色的定義為,將某個象素的顏色和已繪制在屏幕上與其對應的象素顏色相互結合。至于如何結合這兩個顏色則依賴于顏色的alpha通道的分量值,以及/或者所使用的混色函數。Alpha通常是位于顏色值末尾的第4個顏色組成分量。前面這些課我們都是用GL_RGB來指定顏色的三個分量。相應的GL_RGBA可以指定alpha分量的值。更進一步,我們可以使用glColor4f()來代替glColor3f()。 透明: 先聲明:OpenGL::init(): glColor4f(1.0f,1.0f,1.0f,0.5f);?? // 全亮度,50% Alpha 混合( 新增)?glBlendFunc(GL_SRC_ALPHA,GL_ONE);? // 基于源象素alpha通道值的半透明混合函數( 新增) 使用的時候: if(flag_bp == true)
?{
??glEnable(GL_BLEND);???// Turn Blending On
???? glDisable(GL_DEPTH_TEST);?// Turn Depth Testing Off
?}
?Obj->Scene(0,1.0);//3dmax載入模型
?glPopMatrix(); glDisable(GL_BLEND);??// Turn Blending Off
?glEnable(GL_DEPTH_TEST);?// Turn Depth Testing On ps: flag_bp is true,Turn Blending On.or off.
opengl---平移旋轉
| int DrawGLScene(GLvoid) // 此過程中包括所有的繪制代碼 { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清除屏幕及深度緩存 glLoadIdentity(); // 重置視口 | |
| 當您調用glLoadIdentity()之后,您實際上講當前點移到了屏幕中心,X坐標軸從左至右,Y坐標軸從下至上,Z坐標軸從里至外。OpenGL屏幕中心的坐標值是X和Y軸上的0.0f點。中心左面的坐標值是負值,右面是正值。移向屏幕頂端是正值,移向屏幕底端是負值。移入屏幕深處是負值,移出屏幕則是正值。 glTranslatef(x, y, z)沿著 X, Y 和 Z 軸移動。根據前面的次序,下面的代碼沿著X軸左移1.5個單位,Y軸不動(0.0f),最后移入屏幕6.0f個單位。注意在glTranslatef(x, y, z)中當您移動的時候,您并不是相對屏幕中心移動,而是相對與當前所在的屏幕位置。 | |
| glTranslatef(-1.5f,0.0f,-6.0f); // 左移 1.5 單位,并移入屏幕 6.0 | |
| 現在我們已經移到了屏幕的左半部分,并且將視圖推入屏幕背后足夠的距離以便我們可以看見全部的場景-創建三角形。glBegin(GL_TRIANGLES)的意思是開始繪制三角形,glEnd() 告訴OpenGL三角形已經創建好了。通常您會需要畫3個頂點,可以使用GL_TRIANGLES。在絕大多數的顯卡上,繪制三角形是相當快速的。如果要畫四個頂點,使用GL_QUADS的話會更方便。但據我所知,絕大多數的顯卡都使用三角形來為對象著色。最后,如果您想要畫更多的頂點時,可以使用GL_POLYGON。 本節的簡單示例中,我們只畫一個三角形。如果要畫第二個三角形的話,可以在這三點之后,再加三行代碼(3點)。所有六點代碼都應包含在glBegin(GL_TRIANGLES) 和 glEnd()之間。在他們之間再不會有多余的點出現,也就是說,(GL_TRIANGLES) 和 glEnd()之間的點都是以三點為一個集合的。這同樣適用于四邊形。如果您知道實在繪制四邊形的話,您必須在第一個四點之后,再加上四點為一個集合的點組。另一方面,多邊形可以由任意個頂點,(GL_POLYGON)不在乎glBegin(GL_TRIANGLES) 和 glEnd()之間有多少行代碼。 glBegin之后的第一行設置了多邊形的第一個頂點,glVertex 的第一個參數是X坐標,然后依次是Y坐標和Z坐標。第一個點是上頂點,然后是左下頂點和右下頂點。glEnd()告訴OpenGL沒有其他點了。這樣將顯示一個填充的三角形。 {譯者:這里要注意的是存在兩種不同的坐標變換方式,glTranslatef(x, y, z)中的x, y, z是相對與您當前所在點的位移,但glVertex(x,y,z)是相對于glTranslatef(x, y, z)移動后的新原點的位移。因而這里可以認為glTranslate移動的是坐標原點,glVertex中的點是相對最新的坐標原點的坐標值。} |
D. Michael Traub:提供了對 Xvector , Yvector 和 Zvector 的上述解釋。
為了更好的理解X, Y 和 Z的旋轉,我舉些例子...
X軸-您正在使用一臺臺鋸。鋸片中心的軸從左至右擺放(就像OpenGL中的X軸)。尖利的鋸齒繞著X軸狂轉,看起來要么向上轉,要么向下轉。取決于鋸片開始轉時的方向。這與我們在OpenGL中繞著X軸旋轉什么的情形是一樣的。(譯者注:這會兒您要把臉蛋湊向顯示器的話,保準被鋸開了花 ^-^。)
Y軸-假設您正處于一個巨大的龍卷風中心,龍卷風的中心從地面指向天空(就像OpenGL中的Y軸)。垃圾和碎片圍著Y軸從左向右或是從右向左狂轉不止。這與我們在OpenGL中繞著Y軸旋轉什么的情形是一樣的。
Z軸-您從正前方看著一臺風扇。風扇的中心正好朝著您(就像OpenGL中的Z軸)。風扇的葉片繞著Z軸順時針或逆時針狂轉。這與我們在OpenGL中繞著Z軸旋轉什么的情形是一樣的。
OpenGL--我的編碼框架
// stdafx.h : include file for standard system include files,
//? or project specific include files that are used frequently, but
//????? are changed infrequently
//
#if !defined(AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_)
#define AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#define WIN32_LEAN_AND_MEAN??// Exclude rarely-used stuff from Windows headers
#include <windows.h>?// Windows的頭文件
#include <mmsystem.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <assert.h>
#include <gl/gl.h>??// OpenGL32庫的頭文件
#include <gl/glu.h>??// GLu32庫的頭文件
#include <gl/glaux.h>?// GLaux庫的頭文件
#pragma comment( lib, "winmm.lib")
#pragma comment( lib, "opengl32.lib")?// OpenGL32連接庫
#pragma comment( lib, "glu32.lib")??// GLu32連接庫
#pragma comment( lib, "glaux.lib")??// GLaux連接庫
// TODO: reference additional headers your program requires here
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_)
----------------------------------------------------------------------------------
// stdafx.cpp : source file that includes just the standard includes
//?OpenGL的基本圖形.pch will be the pre-compiled header
//?stdafx.obj will contain the pre-compiled type information
#include "stdafx.h"
// TODO: reference any additional headers you need in STDAFX.H
// and not in this file
----------------------------------------------------------------------------------------
#include "stdafx.h"
#include "OpenGL.h"
//
OpenGL* m_OpenGL;
HDC??hDC;??// GDI設備句柄,將窗口連接到 GDI( 圖形設備接口)
HGLRC?hRC=NULL;?// 渲染描述句柄,將OpenGL調用連接到設備描述表
HWND?hWnd=NULL;?// 保存 Windows 分配給程序的窗口句柄
int??Width = 800;// 窗口寬
int??Height= 600;// 窗口高
int??bits? = 16;?// 顏色深度
void GameLoop()
{?? MSG msg;
??? BOOL fMessage;
??? PeekMessage(&msg, NULL, 0U, 0U, PM_NOREMOVE);
??? while(msg.message != WM_QUIT)?// 消息循環
??? {?? fMessage = PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE);
??????? if(fMessage)????//有消息
???{ TranslateMessage(&msg);
????????????? DispatchMessage(&msg);
???}
??????? else? m_OpenGL->Render();?//無消息
??? }
}
LRESULT WINAPI MsgProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam )// 消息處理
{?switch(message)
?{?case WM_CREATE:??????// 建立窗口
???hDC = GetDC(hWnd);????// 獲取當前窗口的設備句柄
???m_OpenGL->SetupPixelFormat(hDC);// 調用顯示模式安裝功能
???return 0;??break;
??case WM_CLOSE:??????// 關閉窗口
???m_OpenGL->CleanUp();???// 結束處理
???PostQuitMessage(0);
???return 0;??break;
??case WM_SIZE:??????// 窗口尺寸變化
???Height = HIWORD(lParam);??// 窗口的高
???Width? = LOWORD(lParam);??// 窗口的寬
???if (Height==0)?Height=1;??// 防止被0 除
???m_OpenGL->init(Width,Height);
???return 0;??break;
??case WM_DESTROY:?????// 退出消息
??????????? PostQuitMessage(0);
??????????? return 0;??break;
??????? case WM_KEYUP:??????// 按ESC退出,全屏模式必需要加入的退出方式。
??????????? switch (wParam)
??????????? { case VK_ESCAPE:
?????m_OpenGL->CleanUp();?// 結束處理
??????? PostQuitMessage(0);
??????? return 0;break;
??????????? }
??default:???break;
?}
?return (DefWindowProc(hWnd, message, wParam, lParam));
}
INT WINAPI WinMain(HINSTANCE hInst,HINSTANCE,LPSTR,INT )// WinMain程序入口
{?? // 注冊窗口類
?bool fullScreen =TRUE;
?DWORD?dwExStyle;??// Window 擴展風格
?DWORD?dwStyle;??// Window 窗口風格
?RECT?windowRect;??// 窗口尺寸
?int??nX=0,nY=0;
/*?
?if (MessageBox(NULL,"使用全屏模式嗎?", "將進入OpenGL,選擇顯示模式",
???????????? MB_YESNO|MB_ICONQUESTION|MB_SYSTEMMODAL)==IDNO)
??{fullScreen =false;}???// 選擇窗口模式
?if (fullScreen)??????// 選擇全屏模式
?{?DEVMODE dmScr;?????// 設備模式
??memset(&dmScr,0,sizeof(dmScr));?// 確保內存分配
??dmScr.dmSize=sizeof(dmScr);??// Devmode 結構的大小
??dmScr.dmPelsWidth = Width;??// 屏幕寬
??dmScr.dmPelsHeight= Height;??// 屏幕高
??dmScr.dmBitsPerPel= 16;???// 色彩深度
??dmScr.dmDisplayFrequency=75;?// 刷屏速度
??dmScr.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT|DM_DISPLAYFREQUENCY;
??if (ChangeDisplaySettings(&dmScr, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
???{fullScreen=FALSE;}
??dwExStyle=WS_EX_APPWINDOW;??// Window 擴展風格
??dwStyle=WS_POPUP;????// Window 窗口風格
??ShowCursor(FALSE);????// 隱藏鼠標
?}
?else*/
?{?dwExStyle=WS_EX_APPWINDOW|WS_EX_WINDOWEDGE;?// 使窗口具有3D外觀
??dwStyle=WS_OVERLAPPEDWINDOW;????// 使用標準窗口
??//WS_OVERLAPPEDWINDOW是有標題欄,窗口菜單,最大、小化按鈕和可調整尺寸的窗口
??int wid=GetSystemMetrics(SM_CXSCREEN);??// 獲取當前屏幕寬
??int hei=GetSystemMetrics(SM_CYSCREEN);??// 獲取當前屏幕高
??nX=(wid-Width)/2;nY=(hei-Height)/2;???// 計算窗口居中用
?}
//-------------------------------------------------------------------
?AdjustWindowRectEx(&windowRect,dwStyle,FALSE,dwExStyle);
?????????//根據窗口風格來調整窗口尺寸達到要求的大小
?char cc[]="tml";
??? WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L,
????????????????????? GetModuleHandle(NULL), NULL, NULL, NULL, NULL,
????????????????????? cc, NULL };
??? RegisterClassEx( &wc );
?m_OpenGL=new OpenGL();//
?hWnd = CreateWindowEx(NULL,cc,"學OpenGL編3D游戲 [ 2.OpenGL的基本圖形 ])",
??????? dwStyle|WS_CLIPCHILDREN|WS_CLIPSIBLINGS,
??????? nX, nY,Width, Height,
??????? NULL,NULL,hInst,NULL);?// 創建窗口
?ShowWindow( hWnd, SW_SHOWDEFAULT );????// 顯示窗口
?UpdateWindow( hWnd );???????// 刷新窗口
?GameLoop();??????????// 進入消息循環
??? return 0;
}
--------------------------------------------------------------------------------------------------------
// OpenGL.h: interface for the OpenGL class.
//
//
#if !defined(AFX_OPENGL_H__17B7289C_7956_41C5_89B9_621E3C435389__INCLUDED_)
#define AFX_OPENGL_H__17B7289C_7956_41C5_89B9_621E3C435389__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include "bsipic.h"
class OpenGL?
{?public:?OpenGL();
?virtual ~OpenGL();
?public:
?bsipic? m_bsipic;?// 定義bsipic類變量
?HDC??hDC;??// GDI設備描述表
?HGLRC?hRC;??// 永久著色描述表
?BOOL?SetupPixelFormat(HDC hDC);
?void?init(int Width, int Height);
?void?Render();
?void?CleanUp();
?void?play();
};
#endif // !defined(AFX_OPENGL_H__17B7289C_7956_41C5_89B9_621E3C435389__INCLUDED_)
------------------------------------------------------------------------------------------------------------
// OpenGL.cpp: implementation of the OpenGL class.
//
#include "stdafx.h"
#include "OpenGL.h"
//
#include "stdafx.h"
#include "OpenGL.h"
//
extern HWND?hWnd;
float?r;
//
OpenGL::OpenGL()
{
}
OpenGL::~OpenGL()
{?CleanUp();
}
BOOL OpenGL::SetupPixelFormat(HDC hDC0)//檢測安裝OpenGL
{?int nPixelFormat;?????? // 象素點格式
?hDC=hDC0;
?PIXELFORMATDESCRIPTOR pfd = {
???? sizeof(PIXELFORMATDESCRIPTOR),??? // pfd結構的大小
???? 1,??????????????????????????????? // 版本號
???? PFD_DRAW_TO_WINDOW |????????????? // 支持在窗口中繪圖
???? PFD_SUPPORT_OPENGL |????????????? // 支持 OpenGL
???? PFD_DOUBLEBUFFER,???????????????? // 雙緩存模式
???? PFD_TYPE_RGBA,??????????????????? // RGBA 顏色模式
???? 16,?????????????????????????????? // 24 位顏色深度
???? 0, 0, 0, 0, 0, 0,???????????????? // 忽略顏色位
???? 0,??????????????????????????????? // 沒有非透明度緩存
???? 0,??????????????????????????????? // 忽略移位位
???? 0,??????????????????????????????? // 無累加緩存
???? 0, 0, 0, 0,?????????????????????? // 忽略累加位
???? 16,?????????????????????????????? // 32 位深度緩存????
???? 0,??????????????????????????????? // 無模板緩存
???? 0,??????????????????????????????? // 無輔助緩存
???? PFD_MAIN_PLANE,?????????????????? // 主層
???? 0,??????????????????????????????? // 保留
???? 0, 0, 0?????????????????????????? // 忽略層,可見性和損毀掩模
?};
?if (!(nPixelFormat = ChoosePixelFormat(hDC, &pfd)))
??{ MessageBox(NULL,"沒找到合適的顯示模式","Error",MB_OK|MB_ICONEXCLAMATION);
?????? return FALSE;
??}
?SetPixelFormat(hDC,nPixelFormat,&pfd);//設置當前設備的像素點格式
?hRC = wglCreateContext(hDC);????????? //獲取渲染描述句柄
?wglMakeCurrent(hDC, hRC);???????????? //激活渲染描述句柄
?return TRUE;
}
void OpenGL::init(int Width, int Height)
{?glViewport(0,0,Width,Height);???// 設置OpenGL視口大小。?
?glMatrixMode(GL_PROJECTION);???// 設置當前矩陣為投影矩陣。
?glLoadIdentity();??????// 重置當前指定的矩陣為單位矩陣
?gluPerspective???????// 設置透視圖
??( 54.0f,???????// 透視角設置為 45 度
??? (GLfloat)Width/(GLfloat)Height,?// 窗口的寬與高比
??? 0.1f,????????// 視野透視深度:近點1.0f
??? 3000.0f???????// 視野透視深度:始點0.1f遠點1000.0f
??);
?// 這和照象機很類似,第一個參數設置鏡頭廣角度,第二個參數是長寬比,后面是遠近剪切。
?glMatrixMode(GL_MODELVIEW);????// 設置當前矩陣為模型視圖矩陣
?glLoadIdentity();??????// 重置當前指定的矩陣為單位矩陣
//====================================================
}
void OpenGL::Render()//OpenGL圖形處理
{?glClearColor(0.0f, 0.0f, 0.6f, 1.0f);??? // 設置刷新背景色
?glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);// 刷新背景
?glLoadIdentity();???????? // 重置當前的模型觀察矩陣
?play();
?glFlush();?????????? // 更新窗口
?SwapBuffers(hDC);???????? // 切換緩沖區
?r+=1;if(r>360) r=0;
}
void OpenGL::CleanUp()
{? wglMakeCurrent(hDC, NULL);?????????????????????? //清除OpenGL
? wglDeleteContext(hRC);?????????????????????????? //清除OpenGL
}
void OpenGL::play()
{?glPushMatrix();
?glPointSize(4);?
?glTranslatef (-5, 4,-13);glRotatef(r,1.0,1.0,1.0);
?glColor3f(1.0f, 0.0f, 0.0f);m_bsipic.Point();?
?glPopMatrix();
?glPushMatrix();
?glTranslatef ( 0, 4,-13);glRotatef(r,1.0,1.0,1.0);
?glColor3f(0.0f, 1.0f, 0.0f);m_bsipic.Line();?
?glPopMatrix();
?glPushMatrix();
?glTranslatef ( 5, 4,-13);glRotatef(r,1.0,1.0,1.0);
?glColor3f(0.0f, 0.0f, 1.0f);m_bsipic.Triangle();
?glPopMatrix();
?glPushMatrix();
?glTranslatef (-5, 0,-13);glRotatef(r,1.0,1.0,1.0);
?glColor3f(1.0f, 1.0f, 0.0f);m_bsipic.Square();
?glPopMatrix();
?glPushMatrix();
?glTranslatef ( 0, 0,-13);glRotatef(r,1.0,1.0,1.0);
?glColor3f(0.0f, 1.0f, 1.0f);m_bsipic.Esquare();?
?glPopMatrix();
?glPushMatrix();
?glTranslatef ( 5, 0,-13);glRotatef(r,1.0,1.0,1.0);
?glColor3f(1.0f, 0.0f, 1.0f);m_bsipic.Park();
?glPopMatrix();
?glPushMatrix();
?glTranslatef (-5,-4,-13);glRotatef(r,1.0,1.0,1.0);
?glColor3f(1.0f, 1.0f, 1.0f);m_bsipic.Pillar();?
?glPopMatrix();
?glPushMatrix();
?glTranslatef ( 0, -4,-13);glRotatef(r,1.0,1.0,1.0);
?glColor3f(0.7f, 0.7f, 0.7f);auxSolidCone(1,1);
?glPopMatrix();
?glPushMatrix();
?glTranslatef ( 5,-4,-13);glRotatef(r,1.0,1.0,1.0);
?glColor3f(0.4f, 0.4f, 0.4f);auxWireTeapot(1);
?glPopMatrix();
}
----------------------------------------------------------------------------------------------------
// bsipic.h: interface for the bsipic class.
//
//
#if !defined(AFX_BSIPIC_H__726BAD2E_C0FE_4F4C_8282_BD53CCED1A9D__INCLUDED_)
#define AFX_BSIPIC_H__726BAD2E_C0FE_4F4C_8282_BD53CCED1A9D__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
class bsipic?
{
public:
?bsipic();
?virtual ~bsipic();
?void?Point();
?void?Line();
?void?Triangle();?
?void?Square();
?void?Esquare();
?void?Park();?
?void?Pillar();
};
#endif // !defined(AFX_BSIPIC_H__726BAD2E_C0FE_4F4C_8282_BD53CCED1A9D__INCLUDED_)
------------------------------------------------------------------------------------------------------------
// bsipic.cpp: implementation of the bsipic class.
//
#include "stdafx.h"
#include "bsipic.h"
//
bsipic::bsipic()
{
}
bsipic::~bsipic()
{
}
///
void bsipic::Point()//畫點
{ glBegin(GL_POINTS);//
?? glVertex3f( 0.0f, 1.0f,-1.0f);//a點
?? glVertex3f(-1.0f,-1.0f, 0.0f);//b點
?? glVertex3f( 1.0f,-1.0f, 0.0f);//c點
? glEnd();
}
void bsipic::Line()//畫線
{ glBegin(GL_LINE_LOOP); //
?? glVertex3f( 0.0f, 1.0f,-1.0f);//a點
?? glVertex3f(-1.0f,-1.0f, 0.0f);//b點
?? glVertex3f( 1.0f,-1.0f, 0.0f);//c點
? glEnd();
}
void bsipic::Triangle()//畫面
{ glBegin(GL_POLYGON);//
?glVertex3f( 0.0f, 1.0f,-1.0f);//a點
?glVertex3f(-1.0f,-1.0f, 0.0f);//b點
?glVertex3f( 1.0f,-1.0f, 0.0f);//c點
? glEnd();
}
void bsipic::Square()//畫正方面
{ glBegin(GL_POLYGON);//
?glVertex3f(0.0f,0.0f ,0.0f);//a點
?glVertex3f(1.0f,0.0f, 0.0f);//b點
?glVertex3f(1.0f,0.0f,-1.0f);//c點
?glVertex3f(0.0f,0.0f,-1.0f);//d點
? glEnd();
}
void bsipic::Esquare()//畫正方體
{ glBegin(GL_QUAD_STRIP);//
??? glVertex3f(0.0f,0.0f ,0.0f);//a0點
??? glVertex3f(0.0f,1.0f ,0.0f);//a1點
??? glVertex3f(1.0f,0.0f, 0.0f);//b0點
??? glVertex3f(1.0f,1.0f, 0.0f);//b1點
??? glVertex3f(1.0f,0.0f,-1.0f);//c0點
??? glVertex3f(1.0f,1.0f,-1.0f);//c1點
??? glVertex3f(0.0f,0.0f,-1.0f);//d0點
??? glVertex3f(0.0f,1.0f,-1.0f);//d1點
??? glVertex3f(0.0f,0.0f ,0.0f);//a0點
??? glVertex3f(0.0f,1.0f ,0.0f);//a1點
? glEnd();
? glBegin(GL_POLYGON);//
?glVertex3f(0.0f,0.0f ,0.0f);//a0點
?glVertex3f(1.0f,0.0f, 0.0f);//b0點
?glVertex3f(1.0f,0.0f,-1.0f);//c0點
?glVertex3f(0.0f,0.0f,-1.0f);//d0點
?glVertex3f(0.0f,1.0f ,0.0f);//a1點
?glVertex3f(1.0f,1.0f, 0.0f);//b1點
?glVertex3f(1.0f,1.0f,-1.0f);//c1點
?glVertex3f(0.0f,1.0f,-1.0f);//d1點
? glEnd();
}
void bsipic::Park ()//畫園
{ glBegin(GL_TRIANGLE_FAN);//
?? glVertex3f(0,0,0.0f );??
?? for(int i=0;i<=390;i+=30)
?? {float p=(float)(i*3.14/180);
??? glVertex3f((float)sin(p),(float)cos(p),0.0f );
?? }
? glEnd();
}
void bsipic::Pillar () //園柱
{glBegin(GL_QUAD_STRIP);//
?? for(int i=0;i<=390;i+=30)
?? { float p=(float)(i*3.14/180);
?glVertex3f((float)sin(p)/2,(float)cos(p)/2,1.0f );
?glVertex3f((float)sin(p)/2,(float)cos(p)/2,0.0f );
?? }
?glEnd();
}
寫一些OPENGL-新手上路
我們要畫出一個圖形首先要做的:(openGL編碼的框架)| 代碼的前4行包括了我們使用的每個庫文件的頭文件。如下所示: | |
| #include <windows.h> #include <gl/gl.h> #include <gl/glu.h> #include <gl/glaux.h> | // Windows的頭文件 // OpenGL32庫的頭文件 // GLu32庫的頭文件 // GLaux庫的頭文件 |
| 接下來您需要設置您計劃在您的程序中使用的所有變量。本節中的例程將創建一個空的OpenGL窗口,因此我們暫時還無需設置大堆的變量。余下需要設置的變量不多,但十分重要。您將會在您以后所寫的每一個OpenGL程序中用到它們。 第一行設置的變量是Rendering Context(著色描述表)。每一個OpenGL都被連接到一個著色描述表上。著色描述表將所有的OpenGL調用命令連接到Device Context(設備描述表)上。我將OpenGL的著色描述表定義為 hRC 。要讓您的程序能夠繪制窗口的話,還需要創建一個設備描述表,也就是第二行的內容。Windows的設備描述表被定義為 hDC 。DC將窗口連接到GDI(Graphics Device Interface圖形設備接口)。而RC將OpenGL連接到DC。第三行的變量 hWnd 將保存由Windows給我們的窗口指派的句柄。最后,第四行為我們的程序創建了一個Instance(實例)。 | |
| HGLRC hRC=NULL; HDC hDC=NULL; HWND hWnd=NULL; HINSTANCE hInstance; | // 永久著色描述表 // 私有GDI設備描述表 // 保存我們的窗口句柄 // 保存程序的實例 |
| 下面的第一行設置一個用來監控鍵盤動作的數組。有許多方法可以監控鍵盤的動作,但這里的方法很可靠,并且可以處理多個鍵同時按下的情況。 active 變量用來告知程序窗口是否處于最小化的狀態。如果窗口已經最小化的話,我們可以做從暫停代碼執行到退出程序的任何事情。我喜歡暫停程序。這樣可以使得程序不用在后臺保持運行。 fullscreen 變量的作用相當明顯。如果我們的程序在全屏狀態下運行, fullscreen 的值為TRUE,否則為FALSE。這個全局變量的設置十分重要,它讓每個過程都知道程序是否運行在全屏狀態下。 | |
| bool keys[256]; bool active=TRUE; bool fullscreen=TRUE; | // 用于鍵盤例程的數組 // 窗口的活動標志,缺省為TRUE // 全屏標志缺省設定成全屏模式 |
| 現在我們需要先定義WndProc()。必須這么做的原因是CreateGLWindow()有對WndProc()的引用,但WndProc()在CreateGLWindow()之后才出現。在C語言中,如果我們想要訪問一個當前程序段之后的過程和程序段的話,必須在程序開始處先申明所要訪問的程序段。所以下面的一行代碼先行定義了WndProc(),使得CreateGLWindow()能夠引用WndProc()。 | |
| LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); | // WndProc的定義 ? |
| 下面的代碼的作用是重新設置OpenGL場景的大小,而不管窗口的大小是否已經改變(假定您沒有使用全屏模式)。甚至您無法改變窗口的大小時(例如您在全屏模式下),它至少仍將運行一次--在程序開始時設置我們的透視圖。OpenGL場景的尺寸將被設置成它顯示時所在窗口的大小。 | |
| GLvoid ReSizeGLScene(GLsizei width, GLsizei height) { if (height==0) { height=1; } glViewport(0, 0, width, height); | // 重置并初始化GL窗口大小 // 防止被零除 // 將Height設為1 // 重置當前的視口(Viewport) |
| 下面幾行為透視圖設置屏幕。意味著越遠的東西看起來越小。這么做創建了一個現實外觀的場景。此處透視按照基于窗口寬度和高度的45度視角來計算。0.1f,100.0f是我們在場景中所能繪制深度的起點和終點。 glMatrixMode(GL_PROJECTION)指明接下來的兩行代碼將影響projection matrix(投影矩陣)。投影矩陣負責為我們的場景增加透視。 glLoadIdentity()近似于重置。它將所選的矩陣狀態恢復成其原始狀態。調用 glLoadIdentity()之后我們為場景設置透視圖。 glMatrixMode(GL_MODELVIEW)指明任何新的變換將會影響 modelview matrix(模型觀察矩陣)。模型觀察矩陣中存放了我們的物體訊息。最后我們重置模型觀察矩陣。如果您還不能理解這些術語的含義,請別著急。在以后的教程里,我會向大家解釋。只要知道如果您想獲得一個精彩的透視場景的話,必須這么做。 | |
| glMatrixMode(GL_PROJECTION); glLoadIdentity(); // 計算窗口的外觀比例 gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } | // 選擇投影矩陣 // 重置投影矩陣 // 選擇模型觀察矩陣 // 重置模型觀察矩陣 ? |
| 接下的代碼段中,我們將對OpenGL進行所有的設置。我們將設置清除屏幕所用的顏色,打開深度緩存,啟用smooth shading(陰影平滑),等等。這個例程直到OpenGL窗口創建之后才會被調用。此過程將有返回值。但我們此處的初始化沒那么復雜,現在還用不著擔心這個返回值。 | |
| int InitGL(GLvoid) { | // 此處開始對OpenGL進行所有設置 ? |
| 下一行啟用smooth shading(陰影平滑)。陰影平滑通過多邊形精細的混合色彩,并對外部光進行平滑。我將在另一個教程中更詳細的解釋陰影平滑。 | |
| glShadeModel(GL_SMOOTH); | // 啟用陰影平滑 |
| 下一行設置清除屏幕時所用的顏色。如果您對色彩的工作原理不清楚的話,我快速解釋一下。色彩值的范圍從0.0f到1.0f。0.0f代表最黑的情況,1.0f就是最亮的情況。glClearColor 后的第一個參數是Red Intensity(紅色分量),第二個是綠色,第三個是藍色。最大值也是1.0f,代表特定顏色分量的最亮情況。最后一個參數是Alpha值。當它用來清除屏幕的時候,我們不用關心第四個數字。現在讓它為0.0f。我會用另一個教程來解釋這個參數。 通過混合三種原色(紅、綠、藍),您可以得到不同的色彩。希望您在學校里學過這些。因此,當您使用glClearColor(0.0f,0.0f,1.0f,0.0f),您將用亮藍色來清除屏幕。如果您用 glClearColor(0.5f,0.0f,0.0f,0.0f)的話,您將使用中紅色來清除屏幕。不是最亮(1.0f),也不是最暗 (0.0f)。要得到白色背景,您應該將所有的顏色設成最亮(1.0f)。要黑色背景的話,您該將所有的顏色設為最暗(0.0f)。 | |
| glClearColor(0.0f, 0.0f, 0.0f, 0.0f); | // 黑色背景 |
| 接下來的三行必須做的是關于depth buffer(深度緩存)的。將深度緩存設想為屏幕后面的層。深度緩存不斷的對物體進入屏幕內部有多深進行跟蹤。我們本節的程序其實沒有真正使用深度緩存,但幾乎所有在屏幕上顯示3D場景OpenGL程序都使用深度緩存。它的排序決定那個物體先畫。這樣您就不會將一個圓形后面的正方形畫到圓形上來。深度緩存是OpenGL十分重要的部分。 | |
| glClearDepth(1.0f); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); | // 設置深度緩存 // 啟用深度測試 // 所作深度測試的類型 |
| 接著告訴OpenGL我們希望進行最好的透視修正。這會十分輕微的影響性能。但使得透視圖看起來好一點。 | |
| glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); | // 真正精細的透視修正 |
| 最后,我們返回TRUE。如果我們希望檢查初始化是否OK,我們可以查看返回的 TRUE或FALSE的值。如果有錯誤發生的話,您可以加上您自己的代碼返回FALSE。目前,我們不管它。 | |
| return TRUE; } | // 初始化 OK ? |
| 下一段包括了所有的繪圖代碼。任何您所想在屏幕上顯示的東東都將在此段代碼中出現。以后的每個教程中我都會在例程的此處增加新的代碼。如果您對OpenGL已經有所了解的話,您可以在 glLoadIdentity()調用之后,返回TRUE值之前,試著添加一些OpenGL代碼來創建基本的形。如果您是OpenGL新手,等著我的下個教程。目前我們所作的全部就是將屏幕清除成我們前面所決定的顏色,清除深度緩存并且重置場景。我們仍沒有繪制任何東東。 返回TRUE值告知我們的程序沒有出現問題。如果您希望程序因為某些原因而中止運行,在返回TRUE值之前增加返回FALSE的代碼告知我們的程序繪圖代碼出錯。程序即將退出。 | |
| int DrawGLScene(GLvoid) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);? glLoadIdentity();? return TRUE;? } | // 從這里開始進行所有的繪制 // 清除屏幕和深度緩存 // 重置當前的模型觀察矩陣 // 一切 OK ? |
| 下一段代碼只在程序退出之前調用。KillGLWindow() 的作用是依次釋放著色描述表,設備描述表和窗口句柄。我已經加入了許多錯誤檢查。如果程序無法銷毀窗口的任意部分,都會彈出帶相應錯誤消息的訊息窗口,告訴您什么出錯了。使您在您的代碼中查錯變得更容易些。 | |
| GLvoid KillGLWindow(GLvoid)? { | // 正常銷毀窗口 ? |
| 我們在KillGLWindow()中所作的第一件事是檢查我們是否處于全屏模式。如果是,我們要切換回桌面。我們本應在禁用全屏模式前先銷毀窗口,但在某些顯卡上這么做可能會使得桌面崩潰。所以我們還是先禁用全屏模式。這將防止桌面出現崩潰,并在Nvidia和3dfx顯卡上都工作的很好! | |
| if (fullscreen)? { | // 我們處于全屏模式嗎? ? |
| 我們使用ChangeDisplaySettings(NULL,0)回到原始桌面。將NULL作為第一個參數,0作為第二個參數傳遞強制Windows使用當前存放在注冊表中的值(缺省的分辨率、色彩深度、刷新頻率,等等)來有效的恢復我們的原始桌面。切換回桌面后,我們還要使得鼠標指針重新可見。 | |
| ChangeDisplaySettings(NULL,0);? ShowCursor(TRUE);? } | // 是的話,切換回桌面 // 顯示鼠標指針 ? |
| 接下來的代碼查看我們是否擁有著色描述表(hRC)。如果沒有,程序將跳轉至后面的代碼查看是否擁有設備描述表。 | |
| if (hRC)? { | // 我們擁有著色描述表嗎? ? |
| 如果存在著色描述表的話,下面的代碼將查看我們能否釋放它(將 hRC從hDC分開)。這里請注意我使用的的查錯方法。基本上我只是讓程序嘗試釋放著色描述表(通過調用wglMakeCurrent(NULL,NULL),然后我再查看釋放是否成功。巧妙的將數行代碼結合到了一行。 | |
| if (!wglMakeCurrent(NULL,NULL))? { | // 我們能否釋放DC和RC描述表? ? |
| 如果不能釋放DC和RC描述表的話,MessageBox()將彈出錯誤消息,告知我們DC和RC無法被釋放。NULL意味著消息窗口沒有父窗口。其右的文字將在消息窗口上出現。"SHUTDOWN ERROR"出現在窗口的標題欄上。MB_OK的意思消息窗口上帶有一個寫著OK字樣的按鈕。 MB_ICONINFORMATION將在消息窗口中顯示一個帶圈的小寫的i(看上去更正式一些)。? | |
| MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION); } | |
| 下一步我們試著刪除著色描述表。如果不成功的話彈出錯誤消息。 | |
| if (!wglDeleteContext(hRC))? { | // 我們能否刪除RC? ? |
| 如果無法刪除著色描述表的話,將彈出錯誤消息告知我們RC未能成功刪除。然后hRC被設為NULL。 | |
| MessageBox(NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION); } hRC=NULL;??????????????????????????????????????????????????????????????????????????????????????????????????? ? // 將RC設為 NULL } | |
| 現在我們查看是否存在設備描述表,如果有嘗試釋放它。如果不能釋放設備描述表將彈出錯誤消息,然后hDC設為NULL。 | |
| if (hDC && !ReleaseDC(hWnd,hDC))???????????????????????????????????????????????????????? // 我們能否釋放 DC? { MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION); hDC=NULL;???????????????????????????????????????????????????????????????????????????????????????????????????? // 將 DC 設為 NULL } | |
| 現在我們來查看是否存在窗口句柄,我們調用 DestroyWindow( hWnd )來嘗試銷毀窗口。如果不能的話彈出錯誤窗口,然后hWnd被設為NULL。 | |
| if (hWnd && !DestroyWindow(hWnd))??????????????????????????????????????????????????????? // 能否銷毀窗口? { MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION); hWnd=NULL;????????????????????????????????????????????????????????????????????????????????????????????????? // 將 hWnd 設為 NULL } | |
| 最后要做的事是注銷我們的窗口類。這允許我們正常銷毀窗口,接著在打開其他窗口時,不會收到諸如"Windows Class already registered"(窗口類已注冊)的錯誤消息。 | |
| if (!UnregisterClass("OpenGL",hInstance))?????????????????????????????????????????????? ? // 能否注銷類? { MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION); hInstance=NULL;????????????????????????????????????????????????????????????????????????????????????????? // 將 hInstance 設為 NULL } } | |
| 接下來的代碼段創建我們的OpenGL窗口。我花了很多時間來做決定是否創建固定的全屏模式這樣不需要許多額外的代碼,還是創建一個容易定制的友好的窗口但需要更多的代碼。當然最后我選擇了后者。我經常在EMail中收到諸如此類的問題:怎樣創建窗口而不使用全屏幕?怎樣改變窗口的標題欄?怎樣改變窗口的分辨率或pixel format(象素格式)?以下的代碼完成了所有這一切!盡管最好要學學材質,這會讓您寫自己的OpenGL程序變得容易的多! 正如您所見,此過程返回布爾變量(TRUE 或 FALSE)。他還帶有5個參數:窗口的標題欄,窗口的寬度,窗口的高度,色彩位數(16/24/32),和全屏標志(TRUE --全屏模式, FALSE--窗口模式 )。返回的布爾值告訴我們窗口是否成功創建。 | |
| BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag) { | |
| 當我們要求Windows為我們尋找相匹配的象素格式時,Windows尋找結束后將模式值保存在變量PixelFormat中。 | |
| GLuint PixelFormat; | // 保存查找匹配的結果 |
| wc用來保存我們的窗口類的結構。窗口類結構中保存著我們的窗口信息。通過改變類的不同字段我們可以改變窗口的外觀和行為。每個窗口都屬于一個窗口類。當您創建窗口時,您必須為窗口注冊類。 | |
| WNDCLASS wc; | // 窗口類結構 |
| dwExStyle和dwStyle存放擴展和通常的窗口風格信息。我使用變量來存放風格的目的是為了能夠根據我需要創建的窗口類型(是全屏幕下的彈出窗口還是窗口模式下的帶邊框的普通窗口);來改變窗口的風格。 | |
| DWORD dwExStyle; DWORD dwStyle; | // 擴展窗口風格 // 窗口風格 |
| 下面的5行代碼取得矩形的左上角和右下角的坐標值。我們將使用這些值來調整我們的窗口使得其上的繪圖區的大小恰好是我們所需的分辨率的值。通常如果我們創建一個640x480的窗口,窗口的邊框會占掉一些分辨率的值。 | |
| RECT WindowRect; WindowRect.left=(long)0; WindowRect.right=(long)width; WindowRect.top=(long)0; WindowRect.bottom=(long)height; | // 取得矩形的左上角和右下角的坐標值 // 將Left?? 設為 0 // 將Right? 設為要求的寬度 // 將Top??? 設為 0 // 將Bottom 設為要求的高度 |
| 下一行代碼我們讓全局變量fullscreen等于fullscreenflag。如果我們希望在全屏幕下運行而將fullscreenflag設為TRUE,但沒有讓變量fullscreen等于fullscreenflag的話,fullscreen變量將保持為FALSE。當我們在全屏幕模式下銷毀窗口的時候,變量fullscreen的值卻不是正確的TRUE值,計算機將誤以為已經處于桌面模式而無法切換回桌面。上帝啊,但愿這一切都有意義。就是一句話,fullscreen的值必須永遠fullscreenflag的值,否則就會有問題。{CKER也覺得此處太廢話,懂的人都要不懂啦.....:(? } | |
| fullscreen=fullscreenflag; | // 設置全局全屏標志 |
| 下一部分的代碼中,我們取得窗口的實例,然后定義窗口類。 CS_HREDRAW 和 CS_VREDRAW 的意思是無論何時,只要窗口發生變化時就強制重畫。CS_OWNDC為窗口創建一個私有的DC。這意味著DC不能在程序間共享。WndProc是我們程序的消息處理過程。由于沒有使用額外的窗口數據,后兩個字段設為零。然后設置實例。接著我們將hIcon設為NULL,因為我們不想給窗口來個圖標。鼠標指針設為標準的箭頭。背景色無所謂(我們在GL中設置)。我們也不想要窗口菜單,所以將其設為NULL。類的名字可以您想要的任何名字。出于簡單,我將使用"OpenGL"。 | |
| hInstance = GetModuleHandle(NULL); wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_WINLOGO); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = NULL; wc.lpszMenuName = NULL; wc.lpszClassName = "OpenGL"; | // 取得我們窗口的實例 // 移動時重畫,并為窗口取得DC // WndProc處理消息 // 無額外窗口數據 // 無額外窗口數據 // 設置實例 // 裝入缺省圖標 // 裝入鼠標指針 // GL不需要背景 // 不需要菜單 // 設定類名字 |
| 現在注冊類名字。如果有錯誤發生,彈出錯誤消息窗口。按下上面的OK按鈕后,程序退出。 | |
| if (!RegisterClass(&wc))??????????????????????????????????? // 嘗試注冊窗口類 { MessageBox(NULL,"Failed To Register The Window Class.","ERROR",MB_OK|MB_ICONEXCLAMATION); return FALSE;?????????????????????????????????????????????? file://退出并返回FALSE } | |
| 查看程序應該在全屏模式還是窗口模式下運行。如果應該是全屏模式的話,我們將嘗試設置全屏模式。 | |
| if (fullscreen) { | // 要嘗試全屏模式嗎? ? |
| 下一部分的代碼看來很多人都會有問題要問關于.......切換到全屏模式。在切換到全屏模式時,有幾件十分重要的事您必須牢記。必須確保您在全屏模式下所用的寬度和高度等同于窗口模式下的寬度和高度。最最重要的是要在創建窗口之前設置全屏模式。這里的代碼中,您無需再擔心寬度和高度,它們已被設置成與顯示模式所對應的大小。 | |
| DEVMODE dmScreenSettings;? memset(&dmScreenSettings,0,sizeof(dmScreenSettings));? dmScreenSettings.dmSize=sizeof(dmScreenSettings);? dmScreenSettings.dmPelsWidth = width;? dmScreenSettings.dmPelsHeight = height;? dmScreenSettings.dmBitsPerPel = bits;? dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT; | // 設備模式 // 確保內存分配 // Devmode 結構的大小 // 所選屏幕寬度 // 所選屏幕高度 // 每象素所選的色彩深度 ? |
| 上面的代碼中,我們分配了用于存儲視頻設置的空間。設定了屏幕的寬,高,色彩深度。下面的代碼我們嘗試設置全屏模式。我們在dmScreenSettings中保存了所有的寬,高,色彩深度訊息。下一行使用ChangeDisplaySettings來嘗試切換成與dmScreenSettings所匹配模式。我使用參數CDS_FULLSCREEN來切換顯示模式,因為這樣做不僅移去了屏幕底部的狀態條,而且它在來回切換時,沒有移動或改變您在桌面上的窗口。 | |
| // 嘗試設置顯示模式并返回結果。注: CDS_FULLSCREEN 移去了狀態條。 if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL) { | |
| 如果模式未能設置成功,我們將進入以下的代碼。如果不能匹配全屏模式,彈出消息窗口,提供兩個選項:在窗口模式下運行或退出。 | |
| // 若模式失敗,提供兩個選項:退出或在窗口內運行。 if (MessageBox(NULL,"The Requested Fullscreen Mode Is Not Supported By/nYour Video Card. Use Windowed Mode Instead?","NeHe GL",MB_YESNO|MB_ICONEXCLAMATION)==IDYES) { | |
| 如果用戶選擇窗口模式,變量fullscreen 的值變為FALSE,程序繼續運行。 | |
| fullscreen=FALSE; } else { | // 選擇窗口模式(Fullscreen=FALSE) ? |
| 如果用戶選擇退出,彈出消息窗口告知用戶程序將結束。并返回FALSE告訴程序窗口未能成功創建。程序退出。 | |
| // Pop Up A Message Box Letting User Know The Program Is Closing. MessageBox(NULL,"Program Will Now Close.","ERROR",MB_OK|MB_ICONSTOP); return FALSE;??????????????????????????????????????????????????????????????? //退出并返回 FALSE } } } | |
| 由于全屏模式可能失敗,用戶可能決定在窗口下運行,我們需要在設置屏幕/窗口之前,再次檢查fullscreen的值是TRUE或FALSE。 | |
| if (fullscreen) { | // 仍處于全屏模式嗎? ? |
| 如果我們仍處于全屏模式,設置擴展窗體風格為WS_EX_APPWINDOW,這將強制我們的窗體可見時處于最前面。再將窗體的風格設為WS_POPUP。這個類型的窗體沒有邊框,使我們的全屏模式得以完美顯示。 最后我們禁用鼠標指針。當您的程序不是交互式的時候,在全屏模式下禁用鼠標指針通常是個好主意。 | |
| dwExStyle=WS_EX_APPWINDOW; dwStyle=WS_POPUP; ShowCursor(FALSE); } else { | // 擴展窗體風格 // 窗體風格 // 隱藏鼠標指針 ? |
| 如果我們使用窗口而不是全屏模式,我們在擴展窗體風格中增加了 WS_EX_WINDOWEDGE,增強窗體的3D感觀。窗體風格改用 WS_OVERLAPPEDWINDOW,創建一個帶標題欄、可變大小的邊框、菜單和最大化/最小化按鈕的窗體。 | |
| dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; dwStyle=WS_OVERLAPPEDWINDOW; } | // 擴展窗體風格 // 窗體風格 ? |
| 下一行代碼根據創建的窗體類型調整窗口。調整的目的是使得窗口大小正好等于我們要求的分辨率。通常邊框會占用窗口的一部分。使用AdjustWindowRectEx 后,我們的OpenGL場景就不會被邊框蓋住。實際上窗口變得更大以便繪制邊框。全屏模式下,此命令無效。 | |
| AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle); | // 調整窗口達到真正要求的大小 |
| 下一段代碼開始創建窗口并檢查窗口是否成功創建。我們將傳遞CreateWindowEx()所需的所有參數。如擴展風格、類名字(與您在注冊窗口類時所用的名字相同)、窗口標題、窗體風格、窗體的左上角坐標(0,0 是個安全的選擇)、窗體的寬和高。我們沒有父窗口,也不想要菜單,這些參數被設為NULL。還傳遞了窗口的實例,最后一個參數被設為NULL。 注意我們在窗體風格中包括了 WS_CLIPSIBLINGS 和 WS_CLIPCHILDREN。要讓OpenGL正常運行,這兩個屬性是必須的。他們阻止別的窗體在我們的窗體內/上繪圖。 | |
| if (!(hWnd=CreateWindowEx( dwExStyle, "OpenGL", title, WS_CLIPSIBLINGS | WS_CLIPCHILDREN | dwStyle, 0, 0, WindowRect.right-WindowRect.left, WindowRect.bottom-WindowRect.top, NULL, NULL, hInstance, NULL))) | // 擴展窗體風格 // 類名字 // 窗口標題 // 必須的窗體風格屬性 // 必須的窗體風格屬性 // 選擇的窗體屬性 // 窗口位置 // 計算調整好的窗口寬度 // 計算調整好的窗口高度 // 無父窗口 // 無菜單 // 實例 // 不向WM_CREATE傳遞任何東東 |
| 下來我們檢查看窗口是否正常創建。如果成功, hWnd保存窗口的句柄。如果失敗,彈出消息窗口,并退出程序。 | |
| { KillGLWindow();????????????????????????????????????????????????????????????? // 重置顯示區 MessageBox(NULL,"Window Creation Error.","ERROR",MB_OK|MB_ICONEXCLAMATION); return FALSE;??????????????????????????????????????????????????????????????? // 返回 FALSE } | |
| 下面的代碼描述象素格式。我們選擇了通過RGBA(紅、綠、藍、alpha通道)支持OpenGL和雙緩存的格式。我們試圖找到匹配我們選定的色彩深度(16位、24位、32位)的象素格式。最后設置16位Z-緩存。其余的參數要么未使用要么不重要(stencil buffer模板緩存和accumulation buffer聚集緩存除外)。 | |
| static PIXELFORMATDESCRIPTOR pfd= { sizeof(PIXELFORMATDESCRIPTOR), 1, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, PFD_TYPE_RGBA, bits, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, PFD_MAIN_PLANE, 0, 0, 0, 0 }; | //pfd 告訴窗口我們所希望的東東 file://上訴格式描述符的大小 // 版本號 // 格式必須支持窗口 // 格式必須支持OpenGL // 必須支持雙緩沖 // 申請 RGBA 格式 // 選定色彩深度 // 忽略的色彩位 // 無Alpha緩存 // 忽略Shift Bit // 無聚集緩存 // 忽略聚集位 // 16位 Z-緩存 (深度緩存) // 無模板緩存 // 無輔助緩存 // 主繪圖層 // 保留 // 忽略層遮罩 ? |
| 如果前面創建窗口時沒有錯誤發生,我們接著嘗試取得OpenGL設備描述表。若無法取得DC,彈出錯誤消息程序退出(返回FALSE)。 | |
| if (!(hDC=GetDC(hWnd)))????????????????????????????????????????????????????? //取得設備描述表了么? { KillGLWindow();????????????????????????????????????????????????????????????? // 重置顯示區 MessageBox(NULL,"Can't Create A GL Device Context.","ERROR",MB_OK|MB_ICONEXCLAMATION); return FALSE;??????????????????????????????????????????????????????????????? // 返回 FALSE } | |
| 設法為OpenGL窗口取得設備描述表后,我們嘗試找到對應與此前我們選定的象素格式的象素格式。如果Windows不能找到的話,彈出錯誤消息,并退出程序(返回FALSE)。 | |
| if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd)))???????????????????????????? // Windows 找到相應的象素格式了嗎? { KillGLWindow();???????????????????????????????????????????????????????????? // 重置顯示區 MessageBox(NULL,"Can't Find A Suitable PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION); return FALSE;?????????????????????????????????????????????????????????????? // 返回 FALSE } | |
| Windows 找到相應的象素格式后,嘗試設置象素格式。如果無法設置,彈出錯誤消息,并退出程序(返回FALSE)。 | |
| if(!SetPixelFormat(hDC,PixelFormat,&pfd))?????????????????????????????????? // 能夠設置象素格式么? { KillGLWindow();???????????????????????????????????????????????????? ??????? // 重置顯示區 MessageBox(NULL,"Can't Set The PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION); return FALSE;????????????????????????????????????????????????? ???????????? // 返回 FALSE } | |
| 正常設置象素格式后,嘗試取得著色描述表。如果不能取得著色描述表的話,彈出錯誤消息,并退出程序(返回FALSE)。 | |
| if (!(hRC=wglCreateContext(hDC)))?????????????????????????????????????????? // 能否取得著色描述表? { KillGLWindow();???????????????????????????????????????????????????? ??????? // 重置顯示區 MessageBox(NULL,"Can't Create A GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION); return FALSE;????????????????????????????????????????????????? ???????????? // 返回 FALSE } | |
| 如果到現在仍未出現錯誤的話,我們已經設法取得了設備描述表和著色描述表。接著要做的是激活著色描述表。如果無法激活,彈出錯誤消息,并退出程序(返回FALSE)。 | |
| if(!wglMakeCurrent(hDC,hRC))?????????????????????????????????????????????? // 嘗試激活著色描述表 { KillGLWindow();???????????????????????????????????????????????????? ??????? // 重置顯示區 MessageBox(NULL,"Can't Activate The GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION); return FALSE;????????????????????????????????????????????????? ???????????? // 返回 FALSE } | |
| 一切順利的話,OpenGL窗口已經創建完成,接著可以顯示它啦。將它設為前端窗口(給它更高的優先級),并將焦點移至此窗口。然后調用ReSizeGLScene 將屏幕的寬度和高度設置給透視OpenGL屏幕。 | |
| ShowWindow(hWnd,SW_SHOW); SetForegroundWindow(hWnd); SetFocus(hWnd); ReSizeGLScene(width, height); | // 顯示窗口 // 略略提高優先級 // 設置鍵盤的焦點至此窗口 // 設置透視 GL 屏幕 |
| 跳轉至 InitGL(),這里可以設置光照、紋理、等等任何需要設置的東東。您可以在 InitGL()內部自行定義錯誤檢查,并返回 TRUE (一切正常)或FALSE (有什么不對)。例如,如果您在InitGL()內裝載紋理并出現錯誤,您可能希望程序停止。如果您返回 FALSE的話,下面的代碼會彈出錯誤消息,并退出程序。 | |
| if (!InitGL())????????????????????????????????????????????????????????????? // 初始化新建的GL窗口 { KillGLWindow();???????????????????????????????????????????????????????????? // 重置顯示區 MessageBox(NULL,"Initialization Failed.","ERROR",MB_OK|MB_ICONEXCLAMATION); return FALSE;?????????????????????????????????????????????????????????????? // 返回 FALSE } | |
| 到這里可以安全的推定創建窗口已經成功了。我們向WinMain()返回TRUE,告知WinMain()沒有錯誤,以防止程序退出。 | |
| return TRUE; // 成功 } | |
| 下面的代碼處理所有的窗口消息。當我們注冊好窗口類之后,程序跳轉到這部分代碼處理窗口消息。 | |
| LRESULT CALLBACK WndProc( HWND hWnd,? UINT uMsg,? WPARAM wParam,? LPARAM lParam)? { | // 窗口的句柄 // 窗口的消息 // 附加的消息內容 // 附加的消息內容 ? |
| 下來的代碼比對uMsg的值,然后轉入case處理,uMsg 中保存了我們要處理的消息名字。 | |
| switch (uMsg)? { | // 檢查Windows消息 ? |
| 如果uMsg等于WM_ACTIVE,查看窗口是否仍然處于激活狀態。如果窗口已被最小化,將變量active設為FALSE。如果窗口已被激活,變量active的值為TRUE。 | |
| case WM_ACTIVATE:? { if (!HIWORD(wParam))? { active=TRUE;? } else { active=FALSE;? } return 0;? } | // 監視窗口激活消息 // 檢查最小化狀態 // 程序處于激活狀態 // 程序不再激活 // 返回消息循環 ? |
| 如果消息是WM_SYSCOMMAND(系統命令),再次比對wParam。如果wParam 是 SC_SCREENSAVE 或 SC_MONITORPOWER的話,不是有屏幕保護要運行,就是顯示器想進入節電模式。返回0可以阻止這兩件事發生。 | |
| case WM_SYSCOMMAND: { switch (wParam)? { case SC_SCREENSAVE:? case SC_MONITORPOWER:? return 0;? } break;? } | // 中斷系統命令Intercept System Commands // 檢查系統調用Check System Calls // 屏保要運行? // 顯示器要進入節電模式? // 阻止發生 // 退出 ? |
| 如果 uMsg是WM_CLOSE,窗口將被關閉。我們發出退出消息,主循環將被中斷。變量done被設為TRUE,WinMain()的主循環中止,程序關閉。 | |
| case WM_CLOSE: { PostQuitMessage(0);? return 0;? } | //收到Close消息? // 發出退出消息 ? |
| 如果鍵盤有鍵按下,通過讀取wParam的信息可以找出鍵值。我將鍵盤數組keys[ ]相應的數組組成員的值設為TRUE。這樣以后就可以查找key[ ]來得知什么鍵被按下。允許同時按下多個鍵。 | |
| case WM_KEYDOWN:? { keys[wParam] = TRUE;? return 0;? } | // 有鍵按下么? // 如果是,設為TRUE // 返回 ? |
| 同樣,如果鍵盤有鍵釋放,通過讀取wParam的信息可以找出鍵值。然后將鍵盤數組keys[ ]相應的數組組成員的值設為FALSE。這樣查找key[ ]來得知什么鍵被按下,什么鍵被釋放了。鍵盤上的每個鍵都可以用0-255之間的一個數來代表。舉例來說,當我們按下40所代表的鍵時,keys[40]的值將被設為TRUE。放開的話,它就被設為FALSE。這也是key數組的原理。 | |
| case WM_KEYUP:? { keys[wParam] = FALSE;? return 0;? } | // 有鍵放開么? // 如果是,設為FALSE // 返回 ? |
| 當調整窗口時,uMsg 最后等于消息WM_SIZE。讀取lParam的LOWORD 和HIWORD可以得到窗口新的寬度和高度。將他們傳遞給ReSizeGLScene(),OpenGL場景將調整為新的寬度和高度。 | |
| case WM_SIZE:? { ReSizeGLScene(LOWORD(lParam),HIWORD(lParam));?? return 0;? } } | // 調整OpenGL窗口大小 // LoWord=Width,HiWord=Height // 返回 |
| 其余無關的消息被傳遞給DefWindowProc,讓Windows自行處理。 | |
| //向 DefWindowProc傳遞所有未處理的消息。 return DefWindowProc(hWnd,uMsg,wParam,lParam); } | |
| 下面是我們的Windows程序的入口。將會調用窗口創建例程,處理窗口消息,并監視人機交互。 | |
| int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,? int nCmdShow)? { | //實例 // 前一個實例 // 命令行參數 // 窗口顯示狀態 ? |
| 我們設置兩個變量。msg 用來檢查是否有消息等待處理。done的初始值設為FALSE。這意味著我們的程序仍未完成運行。只要程序done保持FALSE,程序繼續運行。一旦done的值改變為TRUE,程序退出。 | |
| MSG msg;? BOOL done=FALSE;? | // Windowsx消息結構 // 用來退出循環的Bool 變量 |
| 這段代碼完全可選。程序彈出一個消息窗口,詢問用戶是否希望在全屏模式下運行。如果用戶單擊NO按鈕,fullscreen變量從缺省的TRUE改變為FALSE,程序也改在窗口模式下運行。 | |
| // 提示用戶選擇運行模式 if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO) { fullscreen=FALSE; file://窗口模式 } | |
| 接著創建OpenGL窗口。CreateGLWindow函數的參數依次為標題、寬度、高度、色彩深度,以及全屏標志。就這么簡單!我很欣賞這段代碼的簡潔。如果未能創建成功,函數返回FALSE。程序立即退出。 | |
| // 創建OpenGL窗口 if (!CreateGLWindow("NeHe's OpenGL Framework",640,480,16,fullscreen)) { return 0; // 失敗退出 } | |
| 下面是循環的開始。只要done保持FALSE,循環一直進行。 | |
| while(!done) // 保持循環直到 done=TRUE { | |
| 我們要做的第一件事是檢查是否有消息在等待。使用PeekMessage()可以在不鎖住我們的程序的前提下對消息進行檢查。許多程序使用GetMessage(),也可以很好的工作。但使用GetMessage(),程序在收到paint消息或其他別的什么窗口消息之前不會做任何事。 | |
| if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) { | ?//有消息在等待嗎? ? |
| 下面的代碼查看是否出現退出消息。如果當前的消息是由PostQuitMessage(0)引起的WM_QUIT,done變量被設為TRUE,程序將退出。 | |
| if (msg.message==WM_QUIT) { done=TRUE; } else { | //收到退出消息? // 是,則done=TRUE // 不是,處理窗口消息 |
| 如果不是退出消息,我們翻譯消息,然后發送消息,使得WndProc() 或 Windows能夠處理他們。 | |
| TranslateMessage(&msg); DispatchMessage(&msg); } } else { | // 翻譯消息 // 發送消息 // 如果沒有消息 |
| 如果沒有消息,繪制我們的OpenGL場景。代碼的第一行查看窗口是否激活。如果按下ESC鍵,done變量被設為TRUE,程序將會退出。 | |
| // 繪制場景。監視ESC鍵和來自DrawGLScene()的退出消息 if (active) { if (keys[VK_ESCAPE]) { done=TRUE; } else { | // 程序激活的么? // ESC 按下了么? // ESC 發出退出信號 // 不是退出的時候,刷新屏幕 |
| 如果程序是激活的且ESC沒有按下,我們繪制場景并交換緩存(使用雙緩存可以實現無閃爍的動畫)。我們實際上在另一個看不見的"屏幕"上繪圖。當我們交換緩存后,我們當前的屏幕被隱藏,現在看到的是剛才看不到的屏幕。這也是我們看不到場景繪制過程的原因。場景只是即時顯示。 | |
| DrawGLScene(); SwapBuffers(hDC); } } | // 繪制場景 // 交換緩存 (雙緩存) |
| 下面的一點代碼是最近新加的(05-01-00)。允許用戶按下F1鍵在全屏模式和窗口模式間切換。 | |
| if (keys[VK_F1]) { keys[VK_F1]=FALSE; KillGLWindow(); fullscreen=!fullscreen; // 重建 OpenGL 窗口 if (!CreateGLWindow("NeHe's OpenGL Framework",640,480,16,fullscreen)) { return 0; } } } } | //? F1鍵按下了么? // 若是,使對應的Key數組中的值為 FALSE // 銷毀當前的窗口 // 切換 全屏 / 窗口 模式 // 如果窗口未能創建,程序退出 |
| If the done variable is no longer FALSE, the program quits. We kill the OpenGL window properly so that everything is freed up, and we exit the program. 如果done變量不再是FALSE,程序退出。正常銷毀OpenGL窗口,將所有的內存釋放,退出程序。 | |
| // 關閉程序 KillGLWindow(); return (msg.wParam); } | // 銷毀窗口 // 退出程序 |
?
?
韜光養晦
成大事者必備的9種手段每個人做人辦事的手段都是不一樣的,可以講,一個人就有一種手段,一個人就有一種靠自己手段獲得成功的途徑。無數事實表明,有些人就是太過于自信,想念自己確認的手段能夠解決任何問題,但不知道這種往往是起不到任何作用。因此,他們總覺得離成功的目標不是越來越近,而實際上越來越遠。
人生的計劃和行動,是需要靠章法來完成的,而不是靠一些怪招去謀劃的。這就好比在拳擊臺比賽一樣:兩個拳手相互較量,激戰正酣,進退躲閃、撲讓攻守,都有相當靈活的步伐和拳路,他們的一招一式都是為成功而做準備的,這一招一式就叫手段。可惜的是,有很多人并不能看到這一招一式的寓意。
手段是成功的保證,沒有手段的行動和計劃一定是事倍功半的,孫悟空與牛魔王一比高低,靠的是什么?靠的是他七十二變的手段:“飛人”喬丹叱咤NBA賽場靠什么?靠的是他靈活自如、左右盤帶,飛身灌藍的手段。一名話,沒有手段,你永遠吃不到成功的甜果。
手段從何而來?對于那些成大事者來說,他們善于總結自己、反思自己、比較自己,從而避實就虛,找到自己人生的強項——自己究竟能干什么和不能干什么,并付出實際的行動。這個過程就是確立自己成大事手段的過程。不明白這一點,一個人永遠就會在錯誤的方向走下去。
成大事的九種手段:
1、敢于決斷——克服猶豫不定的習性
很多人之所以一事無成,最大的毛病就是缺乏敢于決斷的手段,總是左顧右盼、思前想后,從而錯失成功的最佳時機。成大事者在看到事情的成功可能性到來時,敢于做出重大決斷,因此取得先機。
2、挑戰弱點——徹底改變自己的缺陷
人人都有弱點,不能成大事者總是固守自己的弱點,一生都不會發生重大轉變;能成大事者總是善于從自己的弱點上開刀,去把自己變成一個能力超強的人。一個連自己的缺陷都不能糾正的人,只能是失敗者!
3、突破困境——從失敗中撮成功的資本
人生總要面臨各種困境的挑戰,甚至可以說困境就是“鬼門關”。一般人會在困境面前渾身發抖,而成大事者則能把困境變為成功的有力跳板。
4、抓住機遇——善于選擇、善于創造
機遇就是人生最大的財富。有些人浪費機遇輕而易舉,所以一個個有巨大潛力的機遇都悄然溜跑,成大事都是絕對不允許溜走,并且能縱身撲向機遇。
5、發揮強項——做自己最擅長的事情
一個能力極弱的人肯定難以打開人生局面,他必定是人生舞臺上重量級選手的犧牲品;成大事者關于在自己要做的事情上,充分施展才智,一步一步地拓寬成功之路。
6、調整心態——切忌讓情緒傷害自己
心態消極的人,無論如何都挑不起生活和重擔,因為他們無法直面一個個人生挫折,成大事者則關于高速心態,即使在毫無希望時,也能看到一線成功的亮光。
7、立即行動——只說不做,徒勞無益
一次行動勝過百遍心想。有些人是“語言的巨人,行動的矮子”,所以看不到更為實際現實的事情在他身上發生;成大事者是每天都靠行動來落實自己的人生計劃的。
8、善于交往——巧妙利用人力資源
一個人不懂得交往,必然會推動人際關系的力量。成大事者的特點之一是:善于靠借力、借熱去營造成功的局勢,從而能把一件件難以辦成的事辦成,實現自己人生的規劃。
9、重新規劃——站到更高的起點上
人生是一個過程,成功也是一個過程。你如果滿足于小成功,就會推動大成功。成大事者懂得從小到大的艱辛過程,所以在實現了一個個小成功之后,能繼續拆開下一個人生的“密封袋”。
可以講任何一種手段,都可以導致一種結果,但這個結果是不是最佳的結果,恐怕就很難說了。成大事者總是關于選擇最佳的手段,達到最完善的結果,這就是非一般人所能做到的。因此在成功之路上,你要想成大事,首先要解決的問題就是:你的手段對你推動成功的計劃是否立竿見影!
空指針問題
編碼的時候遇到的一個問題,我覺得我確實應該好好學學模式。 一個包里面的一個類向調用另一個包里面的類的成員函數~ 比如~//"A.class"
package one;
import two.B;
public class A
{
? B b;//這里不能為B b=new B();因為構造函數不能聲明為PUBLIC
? b.test();
} //"B.class"
package two;
public class B
{
B(){}//構造函數 public void test()//要調用的函數
{} } 這樣調用產生下面的空指針錯誤
Exception in thread "main" java.lang.NullPointerException
請問在不把兩個包合并的情況下如何解決~
謝謝~ 網上大大的解決方法: package two;
public class B
{
private B(){}//構造函數 public static B getInstance(){
??? return new B();
} public void test()//要調用的函數
{} } 在A類中用下面的方法調用以創建一個B的實例:
B b=B.getInstance();
使用JDBC訪問DB2的問題
昨天下午遇到一個問題:使用DB2自帶的驅動db2java.zip文件中的type2類型的驅動訪問DB2,總是報錯:
java.sql.SQLException: java.lang.UnsatisfiedLinkError: no db2jdbc in java.library.path
或者:
java.lang.ClassNotFoundException: COM.ibm.db2.jdbc.app.DB2Driver
要么就報:沒有合適的驅動。
我開始查classpath,把多余的驅動都刪除了,還是報錯,后來我又把db2java.zip文件改名為db2java.jar,也還是不行。
折騰了一個小時,我意識到訪問DB2和訪問Oracle不太一樣,于是google,結果找到這篇文章:
http://www-128.ibm.com/developerworks/cn/db2/library/techarticles/0402chenjunwei/0402chenjunwei.html
原來DB2的驅動還分幾個版本,不同的DB2使用的版本的效果也不太一樣。我開始懷疑使是我的驅動的問題,于是拿MyEclipse來試,結果MyEclipse也連接不上,但用DB2的客戶端可以連接到遠程服務器,后來在同事的幫助下,MyEclipse連上了,解決辦法就是把我改成jar的后綴再改回來:o(。
但使用程序還是訪問不了DB2,經過MyEclipse的測試,可以肯定那個驅動是沒有問題的,所以,還是使用方法或者配置不對,我又搜了很多文章,結果發現這個問題很多人問,國外的帖子也很多,但沒有詳細回答的,又郁悶:o(,后來從幾篇文章中零碎的找到一些提示,是db2jdbc.dll文件找不到,于是我試著把這個文件從DB2的bin目錄下復制到System32目錄下
,還是不行,我又把它復制到Java_Home/bin下面,重啟機器,OK!一定要記住:是bin下面!!!
db2java.zip文件要改名為db2java.jar,并且放到Common/lib下。
解決方法很簡單,問題是很多人知道了,這樣可以節省大家的時間,但沒人寫下來。
另外還有一篇參考文章:
http://www-912.ibm.com/s_dir/slkbase.NSF/0/3f2a44217ec5c05786256c3e007194b3?OpenDocument
但對這個問題并沒有提出什么有意義的意見。
DB2中幾種遇到的SQL1032N出錯的解決
在使用DB2以來,碰到了幾次出現提示SQL1032N錯誤,每次出錯時出錯信息大概如下:11/21/2004 22:15:33 0 0 SQL1042C 發生意外的系統錯誤。
?
SQL1032N 未發出啟動數據庫管理器的命令。 SQLSTATE=57019。
每次出現問題后,都到網上找了很多資料,也問了許多人,費了些力才搞定的。幾次出錯的原因和解決方法都不盡相同,解決后我也只做了個簡單的記錄。一直想把它們寫下來,方便方便后來也遇到同樣問題,跟我一樣到處查找的人,中間也寫了一些廢話,比如我如何查找錯誤,甚至于作了哪些無用功。
第一種SQL1032N出錯,是某天DB2的實例突然無法啟動了,用db2start就提示大概如下的出錯信息:
12/30/2004 11:28:39 0 0 SQL1042C 發生意外的系統錯誤。
SQL1032N 未發出啟動數據庫管理器的命令。 SQLSTATE=57019。
初次遇到這種問題,還以為會不會是數據庫沒起來,情急之下什么命令比如激活數據庫只類的,都拿來試了試,實例都起不來,當時運行這些命令,肯定都是不行的了。
后來突然發現,在開啟機器的時候,提示有個服務出錯了沒啟動,由此推想應該就是在Window服務里設置為自動啟動的DB2實例服務沒有正常啟動,我在服務里面手動啟動它,提示這樣的錯誤:
WINDOWS不能在本地計算機啟動DB2-DB2-0.有關更多信息,查閱系統事件日志.并參考特定服務代碼-8000.
查看事件管理器,有這樣的記錄:
DB2-DB2-0服務因4294959296服務性錯誤而停止.來源SERVICE CONTROL 事件ID:7024
做了這么多,全都是無用功,只限于知道了服務沒起來,等于沒找。
之后通過各方詢問,終于找到了原因:License到期了。
在db2cmd界面下運行db2licm -l,可以很明顯的看到許可證已經過期了。
知道原因所在了,剩下的,就是自己想辦法去解決這個問題了。
小結:直到現在,在有些論壇中,還很經常看到有人發這種帖子來問,至少我在兩個月內就碰到了三次這種帖子。所以,如果不是可以確定已經有永久授權的情況下,發生這種情況,用db2licm -l查一下,也不算壞事。
還有一種情況,跟前面的差不多
也是在啟動實例的時候出現如上的SQL1032N錯誤。在windows NT服務中無法啟動DB2-DB0服務,同時提示:
出錯1069,登陸失敗錯誤。
這個錯誤比較簡單,是用來啟動服務的用戶名或密碼錯誤。只需要在服務的屬性中,選擇登陸選項卡,選擇用戶,并填好密碼,重新啟動服務就可以了。
小結:這種情況,一般發生在切換用戶登陸NT系統或者更改了DB2用戶的密碼的情況下。
第三種情況是這樣的:
最開始,是突然DB2的客戶端連接不上server了,提示如下:
?C:/Documents and Settings/Administrator>db2 connect to fjdldw user install using install3211
?SQL30081N? 檢測到通信錯誤。正在使用的通信協議:"TCP/IP"。正在使用的通信API:
?"SOCKETS"。檢測到錯誤的位置:"10.142.12.1"。檢測到錯誤的通信函數:"connect"。協
?議特定的錯誤代碼:"10061"、"*"、"*"。? SQLSTATE=08001
我本來還以為真是什么TCP/IP協議的問題,去查找了很多與SQL30081N錯誤相關的信息,都無法解決問題。后來到了在服務器上檢查,發現DB2實例未起來。
用db2start命令,仍是提示:
?D:/Program QLLIB/BIN>db2start
?12/30/2004 11:28:39 0 0 SQL1042C 發生意外的系統錯誤。
?SQL1032N 未發出啟動數據庫管理器的命令。 SQLSTATE=57019
?用db2 get dbm cfg查看配置文件,因未作過其他操作,所以沒有什么異常
查看相應實例下的db2diag.log文件,摘取真正有用的部分出錯日志:
?Failed to create the memory segment used for communication with fenced routines. If re-starting db2, ensure no db2fmp processes were on the instance prior to start. Otherwise, you can ajust this value through DB2_FMP_COMM_HEAPSZ db2set value, or by decreasing your ASLHEAPSZ setting.
?
依據ensure no db2fmp processes were on the instance prior to start,將任務管理器里的db2fmp進程全部殺掉,然后重新啟動實例。db2start,OK!
小結:后來查了一查,db2fmp進程用于執行受保護的存儲過程,或者自定義函數。這次出錯的原因,一直沒有弄清楚。但是,通過這次解決,可以說明一點,出了錯誤,查查db2diag.log文件,總是不會錯的。^_^
JDBC連接數據庫經驗技巧集萃
JDBC是如何連接數據庫的?
jdbc.sql.Driver d =
??????????? (Driver)Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");???????? jdbc.sql.Connection con =
??????????? DriverManager.getConnection("jdbc:odbc:pubs","sa","");?
以上的語句您已經很熟了吧 但
?到底是怎么連上數據庫的?:-)
java.sql包中的 java.sql.Driver, jdbc.sql.Connection等提供給程序開發人員統一的開發接口,數據庫提供商提供相應的實現,對程序開發人員來講只要知道這些接口都有哪些方法就可以了,但我們可以深入一些 看看到底這里面都做了那些事, 同時也可以學習其中的編程模式(如Interface模式等)
1 Class.forName(String classname) 的源碼為:
public final
class Class implements java.io.Serializable {
...
public static Class forName(String className)
??? throws ClassNotFoundException {
?return forName0(className, true, ClassLoader.getCallerClassLoader());
}
...
}
關于forName0 請自己查看jdk source.
目的是把指定的Class裝載到JVM中來。(注意class的裝載、初始化過程)
在裝載過程中將執行被裝載類的static塊(如下)
2 sun的JdbcOdbcDriver 源碼:
public class JdbcOdbcDriver extends JdbcOdbcObject
??? implements JdbcOdbcDriverInterface
{
? ...
? /**
?? * connect to DB
?? */
? public synchronized Connection connect(String s, Properties properties)
??????? throws SQLException
??? {
??????? if(JdbcOdbcObject.isTracing())
??????????? JdbcOdbcObject.trace("*Driver.connect (" + s + ")");
??????? if(!acceptsURL(s))
??????????? return null;
??????? if(hDbc != 0)
??????? {
??????????? disconnect(hDbc);
??????????? closeConnection(hDbc);
??????????? hDbc = 0;
??????? }
??????? if(!initialize())
??????? {
??????????? return null;
??????? }
??????? else
??????? {
??????????? JdbcOdbcConnection jdbcodbcconnection = new JdbcOdbcConnection(OdbcApi, hEnv, this);
??????????? jdbcodbcconnection.initialize(getSubName(s), properties, DriverManager.getLoginTimeout());
??????????? jdbcodbcconnection.setURL(s);
??????????? return jdbcodbcconnection;
??????? }
?? }
?
? static
??? {
??????? if(JdbcOdbcObject.isTracing())
??????????? JdbcOdbcObject.trace("JdbcOdbcDriver class loaded");
??????? JdbcOdbcDriver jdbcodbcdriver = new JdbcOdbcDriver();
??????? try
??????? {
??????????? DriverManager.registerDriver(jdbcodbcdriver);
??????? }
??????? catch(SQLException sqlexception)
??????? {
??????????? if(JdbcOdbcObject.isTracing())
??????????????? JdbcOdbcObject.trace("Unable to register driver");
??????? }
??? }
}
public interface JdbcOdbcDriverInterface
??? extends Driver
{
? ...
}
3 連接過程
jdbc.sql.Connection con =
??????????? DriverManager.getConnection("jdbc:odbc:pubs","sa","");
public class DriverManager {
public static synchronized Connection getConnection(String url,
????????????? String user, String password) throws SQLException {
??????? java.util.Properties info = new java.util.Properties();
??????? // Gets the classloader of the code that called this method, may
?// be null.
?ClassLoader callerCL = DriverManager.getCallerClassLoader();
?if (user != null) {
???? info.put("user", user);
?}
?if (password != null) {
???? info.put("password", password);
?}
??????? return (getConnection(url, info, callerCL));
}
private static synchronized Connection getConnection(
??????? String url,
??????? java.util.Properties info,
??????? ClassLoader callerCL) throws SQLException
{
...
Connection result = di.driver.connect(url, info);
...
}?????????????
}
4? 結構圖:
請點擊查看 結構圖?? http://jdeveloper.myrice.com/doc/jdbc/images/howjdbc.jpg
jdbc.sql.Driver d =
??????????? (Driver)Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");???????? jdbc.sql.Connection con =
??????????? DriverManager.getConnection("jdbc:odbc:pubs","sa","");?
以上的語句您已經很熟了吧 但
?到底是怎么連上數據庫的?:-)
java.sql包中的 java.sql.Driver, jdbc.sql.Connection等提供給程序開發人員統一的開發接口,數據庫提供商提供相應的實現,對程序開發人員來講只要知道這些接口都有哪些方法就可以了,但我們可以深入一些 看看到底這里面都做了那些事, 同時也可以學習其中的編程模式(如Interface模式等)
1 Class.forName(String classname) 的源碼為:
public final
class Class implements java.io.Serializable {
...
public static Class forName(String className)
??? throws ClassNotFoundException {
?return forName0(className, true, ClassLoader.getCallerClassLoader());
}
...
}
關于forName0 請自己查看jdk source.
目的是把指定的Class裝載到JVM中來。(注意class的裝載、初始化過程)
在裝載過程中將執行被裝載類的static塊(如下)
2 sun的JdbcOdbcDriver 源碼:
public class JdbcOdbcDriver extends JdbcOdbcObject
??? implements JdbcOdbcDriverInterface
{
? ...
? /**
?? * connect to DB
?? */
? public synchronized Connection connect(String s, Properties properties)
??????? throws SQLException
??? {
??????? if(JdbcOdbcObject.isTracing())
??????????? JdbcOdbcObject.trace("*Driver.connect (" + s + ")");
??????? if(!acceptsURL(s))
??????????? return null;
??????? if(hDbc != 0)
??????? {
??????????? disconnect(hDbc);
??????????? closeConnection(hDbc);
??????????? hDbc = 0;
??????? }
??????? if(!initialize())
??????? {
??????????? return null;
??????? }
??????? else
??????? {
??????????? JdbcOdbcConnection jdbcodbcconnection = new JdbcOdbcConnection(OdbcApi, hEnv, this);
??????????? jdbcodbcconnection.initialize(getSubName(s), properties, DriverManager.getLoginTimeout());
??????????? jdbcodbcconnection.setURL(s);
??????????? return jdbcodbcconnection;
??????? }
?? }
?
? static
??? {
??????? if(JdbcOdbcObject.isTracing())
??????????? JdbcOdbcObject.trace("JdbcOdbcDriver class loaded");
??????? JdbcOdbcDriver jdbcodbcdriver = new JdbcOdbcDriver();
??????? try
??????? {
??????????? DriverManager.registerDriver(jdbcodbcdriver);
??????? }
??????? catch(SQLException sqlexception)
??????? {
??????????? if(JdbcOdbcObject.isTracing())
??????????????? JdbcOdbcObject.trace("Unable to register driver");
??????? }
??? }
}
public interface JdbcOdbcDriverInterface
??? extends Driver
{
? ...
}
3 連接過程
jdbc.sql.Connection con =
??????????? DriverManager.getConnection("jdbc:odbc:pubs","sa","");
public class DriverManager {
public static synchronized Connection getConnection(String url,
????????????? String user, String password) throws SQLException {
??????? java.util.Properties info = new java.util.Properties();
??????? // Gets the classloader of the code that called this method, may
?// be null.
?ClassLoader callerCL = DriverManager.getCallerClassLoader();
?if (user != null) {
???? info.put("user", user);
?}
?if (password != null) {
???? info.put("password", password);
?}
??????? return (getConnection(url, info, callerCL));
}
private static synchronized Connection getConnection(
??????? String url,
??????? java.util.Properties info,
??????? ClassLoader callerCL) throws SQLException
{
...
Connection result = di.driver.connect(url, info);
...
}?????????????
}
4? 結構圖:
請點擊查看 結構圖?? http://jdeveloper.myrice.com/doc/jdbc/images/howjdbc.jpg
JDBC連接DB2數據庫詳解??
關于DB2數據庫的JDBC連接文章有很多,比較出名的有諸如“JDBC數據庫連接大全”和“JSP的DB2連接數據庫”,雖然都是很詳細的資料,也都說解決了前人沒有解決的問題,但還是有許多紕漏。我就這兩天的經驗給大家寫一篇關于JDBC連接數據庫的文章,以解決一部分人的疑問。
?????第一:JDBC是JDK的一部分(至少在Java?Tiger?Development?Kits中是這樣),使用JDBC直接在程序文件中寫import?java.sql.*;即可使用了。
?????第二:連接字符串的格式。本地連接的連接字符串格式為jdbc:product_name:database_name,遠程連接的格式為:product_name://host_name/port_number:database_name。即如果我的數據庫名字為rdb,則本地連接字符串為jdbc:db2:rdb(當然rdb一定是處于DB2的默認實例之中的),而遠程連接字符串為jdbc:db2://192.168.1.10/50000:rdb(這里192.168.1.10為數據庫所在服務器IP地址,而50000為DB2連接服務的端口號)。
?????第三:?安裝DB2數據庫提供的為JDBC準備的類庫(在.NET中叫Provider,在Java中怎么叫還沒研究過)。查找IBM?DB2?UDB的安裝目錄或者Java??Tiger的JDK目錄你會找到db2java.zip,把它先做一個副本以后就它最有用了。現在我們開始討論數據庫連接的程序代碼。
?????應用程序連接DB2數據庫:
先將db2java.zip解壓縮,把COM目錄轉移到代碼的當前目錄,然后我們開始注冊這個Provider的實例,代碼為:
?????Class.forName(“COM.ibm.db2.jdbc.app.DB2Driver”).newInstance();
?????Class.forName(“COM.ibm.db2.jdbc.net.DB2Driver”).newInstance():
這兩句任選其一,作用稍有不同,前者是具有DB2客戶端的Provider實例,后者是無DB2客戶端的Provider實例。
此后再寫Connection?con=DriverManager.getConnection();即可得到數據庫連接的實例。
???JSP中連接DB2數據庫:
???這里以Tomcat作為Servlet容器,如果想在Tomcat中使用DB2?
???Provider必須把db2java.zip更名成db2java.jar然后復制到tomcat主目錄下c
Class.forName("com.ibm.db2.jdbc.app.DB2Driver").newInstance();
String url="jdbc:db2://localhost:5000/sample"; //sample為你的數據庫名
String user="admin";
String password="";
Connection conn= DriverManager.getConnection(url,user,password);??
總結
- 上一篇: 【EXP】函数使用技巧
- 下一篇: EXP3算法