魔劍工舖

關於部落格
RPG製作大師XP腳本為主要更新

RPG製作大師VX腳本為其次更新

RPG製作大師VX_Ace目前不考慮

RPG製作大師MV腳本完全沒打算

留言完建議重新整理看看是否顯示

目前不處理本舖外腳本的相關問題

其他相關事項請觀看規定注意事項

本舖未來的經營計畫與VA的支援




var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-37462754-1']);
_gaq.push(['_trackPageview']);

(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();

  • 288039

    累積人氣

  • 1

    今日人氣

    0

    訂閱人氣

【教學】Win32API

 更新日期:2016/03/01 01:51  更新內容
Win32API類,這個沒意外的是屬於Ruby擴充庫的Win32API.rb和Win32API.so的東東
RGSS不支援so庫的加載卻使用了so庫的Win32API類,是否意味著RM需要所以才加入的?
或許常用的 p 方法或者顯示錯誤時甚至RM窗口就是用Win32API實現的也說不定(猜?!)
Win32API這個類,可以使用Windows中的dll函數,來達成RM本身做不到的事情
例如擷取滑鼠座標、調整RM的視窗、檔案的上傳與下載、獲得電腦資訊...等等
Windows API函數眾多,通常需要透過書籍或者網路上查找才知道這些函數的功能與用法
【適用範圍】XP(1.02板以上)、VX、VA(理論上)、MV(不知道)、純Ruby(需加載Win32API.rb)
我的電腦是64位元作業系統,似乎有資料型態長度不同的問題,是否會有BUG我不清楚
本篇教學主要還是教使用方法,至於運作原理啥的都只是自學來的推測?! 看看就好(喂~)
  
 
使用方法
宣告API函數 
Win32API.new(函數庫名, 函數名稱, 參數類型, 返回類型)
函數庫名:字串,指定API函數所在的dll檔案名稱,通常是引用在系統資料夾(C:WindowsSystem32)
     或者是你的遊戲所在的資料夾的dll中,名稱可全為小寫,副檔名 .dll 可省略
函數名稱:字串,指定要使用該dll中的函數名稱
參數類型:字串或者是以字串組成的陣列,依據該函數所需要的參數類型來指定對應數量的符號,有以下4種
     v:無(void),基本上指定這個在實際使用call時可不用代參數
     i:短整數(int)型態,面對較小數值時(通常是大小為16位元以下類型)使用
     l:長整數(long)型態,面對較大數值時(通常是大小為32位元以下類型)使用,通常可代替 i 來指定
     p:指標,指定記憶體指向的空間,似乎在Ruby沒辦法指定變數指向?!,所以此參數指定字串
        讓Win32API自行去引用該字串所使用的指標來做使用
     通常1個函數通常不會只有1個參數,面對多個參數時只需添加符號即可
     比如說某1函數需要3個int,則此處可指定為『'iii'』、『['i', 'i', 'i']』
     也有會這樣指定『%w(i i i)』(這樣指定等同用陣列指定)
     !有時會有不想指定指標參數的時候,可以直接指定為0或者nil即可
     參數類型的指定依據該函數所需的參數類型而定,等等下面還會再做更詳細的說明
返回類型:字串,設定函數傳回來的返回值的類型
     跟參數類型指定方式一樣,但返回值只有1個所以指定1個符號即可
     通常返回值 i(短整數)、l(長整數)、v(無返回值),通常不會是 p(指標)
     畢竟傳給地址給我們也不會用
     通常API返回的都是標誌、代碼、布林(是否執行成功)、長度...等資訊居多
     !如果對返回值類型指定 p 的話,RGSS會發生錯誤可能
      而Ruby解釋器則能正常返回對應指標的內容...
返回值:Win32API對象,此Win32API執行後,會返回Win32API對象
    之後可以透過這個對象來隨時使用這函數的功能
 
API函數的使用   
Win32API對象.call(參數...)
參數:通常為數值或字串,依據宣告時的參數類型而決定參數的數量和參數所需的類型
  
假設以MessageBox這個API函數來進行說明,它的功能跟RGSS重定義的p方法相同,彈出窗口並顯示出訊息
這個函數的資訊可以參考Microsoft對MessageBox函數的說明
往下拉到  Requirements 的部分有個表格,他的表格資料如下
Minimum supported client為一般版可使用該API的作業系統版本,得知Win2000有此函數
Minimum supported server為伺服器版可使用該API的作業系統版本
Header為關聯的 .h 檔,主要用於C語言指定標頭之類的,我們用不到
Library為關聯的 .lib 檔,封裝好的函數資料檔案,一樣用不到
DLL為該函數所在的DLL檔案,Win32API第1個參數 函數庫名 就是指他
從這欄資訊可以得知MessageBox函數在User32.dll之中
Unicode and ANSI names為這個函數的其他編碼的函數名稱
可得知這個函數MessageBoxA和MessageBoxW兩種版本編碼的函數,編碼問題稍後再提
 
這個API函數被定義在user32.dll之中,於是函數庫名指定為user32,函數名稱當然就是指定MessageBox,如下
Win32API.new('user32', 'MessageBox', 參數類型, 返回類型)
接著看到剛剛 Microsoft 網站 的Syntax,可以找到以下內容(它的定義)
[C++]
int WINAPI MessageBox(
  _In_opt_ HWND    hWnd,
  _In_opt_ LPCTSTR lpText,
  _In_opt_ LPCTSTR lpCaption,
  _In_     UINT    uType
);
首先以上的內容『WINAPI』、『_ln_opt_』、『_ln_』當作不存在,用不到(笑)
開頭的『int MessageBox』為MessageBox的返回值是 int (有符號短整數)類型
於是 返回類型指定為 i (或者l)
於是 返回類型 這個參數就有了,直接使用字串指定 i 或者 l 即可,這樣目前完成如下
Win32API.new('user32', 'MessageBox', 參數類型, 'i')
剩下的括號內為 參數類型,觀看上面的定義,每個參數用小逗號分開
由於定義中的括號內有3個小逗號,所以可以看出需指定4個類型的,來逐一觀看各個類型
  
『HWND hWnd』的hWnd是參數名稱,等同Ruby定義參數也需要指定名稱一樣,他的資料類型是HWND
代表窗口的控制碼類型,通常這參數指定MessageBox這個函數彈出的窗口是關聯哪個窗口之用
它的定義是struct(結構)是用兩個long組成,一般來說當作長整數指定 l 即可
通常控制碼的命名大多都已H作為開頭
  
『LPCSTR lpText』的lpText是參數名稱,LPCSTR為字串的指標類型,於是指定為 p
通常指標類型的參數的命名以LP作為開頭居多,這個參數用來指定彈出窗口的文字內容
  
『LPCSTR lpCaption』跟上面的lpText參數一樣是LPCSTR類型,於是指定p
此參數代表彈出窗口的標題列文字
  
『UINT uType』為UINT型態,它的定義為『unsigned int』無符號短整數,可指定為 i (或者l)
!UINT型我的認知可能有誤,還可能因作業系統是64位元的差異性可能有差,可建議用 l
  
以上4個參數分析出需指定的為lppi,可以用『'lppi'』、『['l', 'p', 'p', 'i']』或『%w(l p p i)』表示
於是宣告完成了
MessageBox = Win32API.new('user32', 'MessageBox', 'lppi', 'i')
執行後會將宣告好MessageBox函數的Win32API對象儲存到 MessageBox 常數中 (Ruby命名大寫開頭為常數)
然後就可以開始使用Win32API類中提供的call方法來使用 MessageBox 
參數類型指定4個字的字串,所以call所需要代的參數也需要4個,所以使用時為以下這樣
MessageBox = Win32API.new('user32', 'MessageBox', 'lppi', 'i')
MessageBox.call(窗口控制碼, 窗口內容, 標題內容, 類型標誌)

# 如果只是臨時使用1次API後就不用的話,也可以直接宣告然後直接使用也行
Win32API.new('user32', 'MessageBox', 'lppi', 'i').call(窗口控制碼, 窗口內容, 標題內容, 設定標誌)
窗口控制碼:由於第1個參數在宣告時指定了 l (長整數)類型,所以此處可以指定整數
      設定窗口的控制碼,來判斷這彈出窗口是透過哪個窗口彈出的
      一般來說控制碼需透過其他API函數獲取,目前先不使用,直接指定為0即可
窗口內容:由於第2個參數在宣告時指定了 p (指標)類型,所以此處指定字串(不用時則例外)
     設定彈出窗口的顯示內容,可直接指定字串
     Win32API會自動引用該字串的指標位置的內容
     !由於編碼問題的關係,所以內容先使用數字是英文等做測試,別使用中文那些,會變亂碼
      關於編碼的問題會在下面說明如何轉碼才可顯示中文,這邊先不說明如何解決(畢竟內容很長)
標題內容:由於第3個參數在宣告時指定了 p (指標)類型,所以此處指定字串(不用時則例外)
     設定彈出窗口的標題列文字,和窗口內容一樣指定字串,也一樣先不使用中文
類型標誌:由於第4個參數在宣告時指定了 i (短整數)類型,所以此處指定整數
     此參數可設定彈出窗口附設的按鈕種類和圖標等設定
     標誌的部分下面會在介紹,這邊先不使用,指定為0即可
參數設定後之後,腳本成品如下,就可以開始測試看看效果了
MessageBox = Win32API.new('user32', 'MessageBox', 'lppi', 'i')
MessageBox.call(0, 'ABCDEFG', 'Sword', 0)
# 或者
Win32API.new('user32', 'MessageBox', 'lppi', 'i').call(0, 'ABCDEFG', 'Sword', 0)
執行之後,會彈出1個小窗口,標題列顯示 Sword ,內容為 ABCDEFG 
效果就像跟直接使用『p('ABCDEFG')』很像(雖然標題列文字不一樣就是了)
!XP環境下這視窗顯示10秒之後按掉這個視窗會跑出腳本已經被備份,要注意
  
控制碼與返回值  
再舉另一個函數的例子,GetForegroundWindow函數(一樣也是 Microsoft 網站上的資料)
此函數的功能是獲取桌面上最頂端窗口的窗口識別碼,函數定義如下
[C++]
HWND WINAPI GetForegroundWindow(void);
參數類型只有寫個void,此時參數類型指定 v,表示這個參數是空的,在實際使用時可不代參數
這個函數返回HWND,為窗口的控制碼,上面已經提過了就不說明了,返回類型指定 l  
GetForegroundWindow = Win32API.new('user32','GetForegroundWindow', 'v', 'l')
Graphics.update # 防止切換窗口或縮小RM窗口導致取到別的控制碼之用(利用非活動暫停的特性)
p GetForegroundWindow.call # 隨機顯示一個數值,為RM窗口的控制碼
附註:整合腳本中定義的hWnd方法就是這樣子寫出來的
還記得MessageBox第1個參數可以指定HWND類型嗎? 現在可添加進去試試看!!
Graphics.update
hWnd = Win32API.new('user32','GetForegroundWindow', %(v), 'l').call # 獲取窗口識別碼
Win32API.new('user32', 'MessageBox', 'lppi', 'i').call(hWnd, 'Sword', 'ABC', 0)
指定了窗口識別碼給MessageBox函數後,彈出窗口如果顯示時,無法對RM窗口進行操作
例如無法移動視窗也無法按下RM窗口右上方的那些按鈕,直到彈出窗口結束之後恢復
有很多函數都會用到識別碼來判斷API要對哪個目標進行操作
所以說只要有其他程式的識別碼,想要操控不是RM的程式是可行的
除了窗口的控制碼外,還會有其他種類的控制碼,例如執行緒控制碼、磁碟搜尋控制碼...等等
  
類型標誌  
接著來講講MessageBox的第4個參數uType,也就是我稱之為 類型標誌 的參數
在 Microsoft 網站中的 MessageBox 說明中,下面有個範例的C++程式碼,擷取內容如下:
(綠色字為我自行加上的註解,原程式碼中並無)
[C++]
int msgboxID = MessageBox(
  NULL, // 窗口控制碼
  (LPCWSTR)L"Resource not availablenDo you want to try again?", // 窗口內容
  (LPCWSTR)L"Account Details", // 標題內容
  MB_ICONWARNING | MB_CANCELTRYCONTINUE | MB_DEFBUTTON2 // 類型標誌
);
以上程式碼換成Ruby的話,相當於就是這樣寫~~
msgboxID = Win32API.new('user32', 'MessageBox', 'lppi', 'i').call(
  0,  # 別指定Ruby的nil,直接用0代表NULL值即可 ( 如果是指標的話可為0或nil )
  "Resource not availablenDo you want to try again?",  
  'Account Details', 
  MB_ICONWARNING | MB_CANCELTRYCONTINUE | MB_DEFBUTTON2
)
實際執行後正常會觸發NameError錯誤,錯誤說明是 MB_ICONWARNING 沒有定義,很正常~
畢竟不能像C語言那樣,直接加個『#include <windows.h>』標頭就幫我們把API和常數那些定義好
所以要使用的話,必須將常數的儲存的實際數值來直接使用才可以
去查 Microsoft ,可發現常數 MB_ICONWARNING 為0x00000030 (10進制的48)
根據其說明得知指定這個常數的效果為:彈出窗口附加 警告 圖示 (三角型裡加驚嘆號)
而 MB_CANCELTRYCONTINUE 其值為0x00000006 (10進制的6)
根據其說明得知指定這個常數的效果為:彈出窗口的按鈕有Cancel、Try Again、Continue
按鈕名稱依使用環境會有變化,像繁中環境下按鈕為 取消、重試、繼續 這三個按鈕
而 MB_DEFBUTTON2 其值為0x00000100 (10進制的256)
根據其說明得知指定這個常數的效果為:代表預設的選項位置設定為第2個按鈕
即彈出窗口直接按Enter會選擇的選項(除非你按了Tab換了選項除外)
此時腳本只要改成以下這樣就可以使用了
msgboxID = Win32API.new('user32', 'MessageBox', 'lppi', 'i').call(
  0, 
  "Resource not availablenDo you want to try again?", 
  'Account Details', 
  0x30 | 0x6 | 0x100 # 也可以寫成『48 | 6 | 256』或 『310』或『0x136』
)
# 又或者把常數定義好也行
MB_ICONWARNING = 48
MB_CANCELTRYCONTINUE = 6
MB_DEFBUTTON2 = 256
msgboxID = Win32API.new('user32', 'MessageBox', 'lppi', 'i').call(
  0, 
  "Resource not availablenDo you want to try again?", 
  'Account Details', 
  MB_ICONWARNING | MB_CANCELTRYCONTINUE | MB_DEFBUTTON2
)
這種指定法有1種好處,看看Microsoft介紹了很多種的常數
只用了1個數值就能夠表示出多種 組合性 的效果出來,而非指定N個參數這麼麻煩
有需要多種效果,只需要將某個代表該效果的常數(即數值) 用 | 或 + 符號就可以指定多種
(不過一般來說建議都使用 | 符號來添加效果,畢竟這兩符號的功能不太一樣...)
以MessageBox的類型標誌來說,是以16進制每一個位數代表1種種類的效果
0x30 | 0x6 | 0x100 = 0x136 首先看到第一位(從右邊往左數)為按鈕項目的類型
第2位為圖標的類型,第3位為預設按鈕位置,從這就能很清楚發現
例如16進制的每1個位數來分別執行1種種類的效果,1種種類有16個樣式(含0本身)
當然會不會到16種樣子還是要看有沒有該API函數設計出來而定
從這裡就可以知道,在指定時常數標誌時,相同位數的常數值不要同時使用
 
 
緩衝區  
一開始有說到,我們傳遞的參數就只有數值(i或l)或指標(p)或者無參數(v)
如要傳遞字串給API函數,Win32API會幫我們將字串的指標取出給API函數取用
如果API函數要傳像是字串的資料給我們時,會是直接改該指標的記憶體內容
然後再讀出該記憶體內容之後轉為Ruby的字串來給我們使用(純推測)
所以提供1個適當空間大小的字串來當作API函數的寫入空間,此空間稱緩衝區
來讓API函數去修改這個字串內容,然後當讀取該字串時會發現
字串的內容被改寫了!! 改寫的內容通常就是我們要獲取的內容
一般來說除了給緩衝區外,還要提供可寫入的緩衝區大小(固定大小的則不用)
GetWindowsDirectory函數來舉例,這個API函數的功能是獲取Windows的路徑
觀看連接的Requirements處的表格,得知這函數的DLL是 kernel32.dll,參數定義如下
UINT GetWindowsDirectory(LPTSTR lpBuffer, UINT nSize);
開頭『UINT』為返回值為無符號整數,代表成功寫入緩衝區的大小,指定 i
同時也代表當緩衝區空間不夠時,用來告知所需要的緩衝區大小是多少
『LPTSTR lpBuffer』的LPTSTR為指標字串,指標指定p就對了!!(喂!!)
此參數用提供緩衝區給API函數寫入用的字串
『UINT nSize』跟返回值一樣的類型,指定i,代表緩衝區的大小
這樣就完成宣告了,如下
Win32API.new('kernel32', 'GetWindowsDirectory', 'pi', 'i')
這個API函數會將Windows的路徑寫入到你設定的緩衝區中
所以緩衝區的大小需要能裝的下完整路徑長度的緩衝區
一般來說 路徑 的緩衝區大小建議為 MAX_PATH (常數值為260)
緩衝區的空間多了沒關係(但也別太過誇張....),但少的話很容易獲取失敗
緩衝區的空間內容可自定,但通常都是使用空字元『""』或空格『" "』居多
我個人是比較建議用空字元,分割和過濾比較方便,空格則是用 p 方法檢視上比較方便而已
temp = "" * 260 # 產生1個內容有260個空字元的字串
GetWindowsDirectory = Win32API.new('kernel32', 'GetWindowsDirectory', 'pi', 'i')
GetWindowsDirectory.call(temp, temp.size)
p temp #=> "C:\Windows00000000 ...省略 ... 0000"
確實獲取到Windows的資料夾路徑了,但剩餘沒寫到的空間還維持原樣是 (顯示時為00)
還記得這API函數本身會返回成功寫入緩衝區的大小嗎? 可以稍微利用來獲取只要的內容
temp = "" * 260 # 產生1個內容有260個空字元的字串
GetWindowsDirectory = Win32API.new('kernel32', 'GetWindowsDirectory', 'pi', 'i')
n = GetWindowsDirectory.call(temp, temp.size) # 獲取路徑並儲存其大小到變數n
p temp[0..n] #=> "C:\Windows00" # C語言中,通常字串需要加 字元代表結束
p temp[0...n] #=> "C:\Windows" # 不想取得最後結尾的 ,n減1就行了
指定緩衝區大小的方法還有先call獲取其所需大小在正式使用的方式,如下
GetWindowsDirectory = Win32API.new('kernel32', 'GetWindowsDirectory', 'pi', 'i')
n = GetWindowsDirectory.call(0, 0) # 不使用,參數均0即可,獲取緩衝區所需大小到變數n
temp = "" * n
GetWindowsDirectory.call(temp, temp.size)
p temp[0..n] #=> "C:\Windows00"
p temp[0...n] #=> "C:\Windows00" # BUG?
p temp[0...(n-1)] #=> "C:\Windows"
理論上來說,指定字串取得範圍『0...n』因該能將 消除
結果跟我想的不一樣,沒消除成功,反而需要消除2個才能去地
用size判斷其長度,發現『0..n』和『0...n』長度居然都是11
只有到『0...(n-1)』才開始變成10,是RM的BUG還是我電腦問題呢?
但在純Ruby上執行又沒發現這個問題說...
於是乎,要清除尾部的 的話,個人還是使用這個
兩種大小指定法都通用能刪除尾部的
GetWindowsDirectory = Win32API.new('kernel32', 'GetWindowsDirectory', 'pi', 'i')
n = GetWindowsDirectory.call(0, 0)
temp = "" * n
GetWindowsDirectory.call(temp, temp.size)
p temp.unpack('Z*').first #=> "C:\Windows"
字串的unpack方法能照參數指定規則轉換後返回陣列,Z*為清除結尾字串
此方法返回陣列,所以需使用 first 方法或指定陣列索引0 獲取轉化的字串
等等下面的API的參數指標需要指定結構時,還會在說明其他用法  
 
     
結構指標  
在C語言中,每個變數都要宣告其變數的空間大小,定義陣列也要宣告其資料類型
比如說你宣告了int資料類型的陣列,陣列的所有元數值都是int資料型態(也就是每單元大小都一樣)
而結構可以在可在同個變數中定義其他種資料類型,排除了陣列都只能設定相同資料類型的問題
說句簡潔一點,就是把1堆變數通通合在同一個變數來使用,所以實際上所佔據的容量是相當的
 
總而言之,先舉個簡單有用到結構的API函數,在本舖滑鼠游標中使用的GetCursorPos函數
它的功能是獲取滑鼠當前在螢幕上的座標,它的定義很簡單,參數也只有1個,如下
[C++]
BOOL WINAPI GetCursorPos(
  _Out_ LPPOINT lpPoint // 緩衝區
);
它的參數資料類型是LPPOINT ,由於它開頭是由LP命名的,於是它是指標類型,可指定為 p
當你去上面的GetCursorPos的連接進去看它的參數說明,說這邊需要指定名為POINT的結構
可以先點進去看看POINT結構是怎定義的,得知如下
[C++]
typedef struct tagPOINT { // 有 struct 這保留字為結構,稍微提一下結構的辨識
  LONG x;  // 滑鼠所在的X座標
  LONG y;  // 滑鼠所在的Y座標
} POINT, *PPOINT;
從這個定義能發現,這結構裡有兩個LONG型態,實際上POINT結構等於兩個LONG而已
在Ruby中,如果要指定結構,其實只需要提供等同兩個LONG大小的字串即可
1個字元算8位元,LONG相當於32位元的大小,所以可以指定為4個字元的字串
但結構由於有兩個LONG,所以字串的長度還要增加4個字元,也就是8個字元的字串
# 這是在螢幕解析度 1680x1050 ,滑鼠指標在最右下角的執行結果
t = "" * 8
Win32API.new('user32', 'GetCursorPos', 'p', 'i').call(t)
p t #=> "21706000031040000"
由於它把數字已位元組的方式表示,寫入的只是數據資料,直接用 p 方法看不出所以然
由於是用位元組為單位代表數字,所以可以把這些數字當作256進位制來轉成座標
例如可以使用字串的each_byte方法,它會在塊中開1個循環
並每次傳遞字串中每1個位元組數值,藉此算出我們易讀的整數
t = "21706000031040000" # 上個範例的結果
t.each_byte{|byte| p(byte)} # 分別顯示143、6、0、0、25、4、0、0
以上已用4個字元代表一個LONG,所以依照順序,前4個數字代表X座標,剩下4個是Y座標
假設已256進位來看,從左邊開始為第1位,其值為 143 + (6 × 256) = 1679,這是X座標
剩下4個數字其值為 25 + (4 × 256) = 1049,這樣Y座標也算出來了
總結滑鼠所在的座標是在1679x1049,剛好是我螢幕解析度1680x1050各減1
【註】由於左上角原點包含座標 [0, 0] 也算是1個座標點,0~1679 = 1680點
  
Ruby的unpack和pack方法  
RGSS中的F1幫助文檔其實有很多Ruby有的方法都沒有介紹到
不過我想也是,畢竟RM是拿來做遊戲的,不是拿來搞應用程式的...
但不介紹不代表不能用,字串有個unpack方法,陣列還有個和它相對應的pack方法
這兩個方法能讓Win32API在指定結構的時候能夠更方便而且更方便檢視一些
比如剛剛上面的範例,只需要使用字串的unpack方法就能清楚搞定,不用特別去寫公式算
"21706000031040000".unpack('LL') #=> [1679, 1049]
你看看!! 是不是簡潔又有力呢,就將剛剛的座標給顯示出來了
這是解包結構的方式,如果參數是要傳遞結構給API的話,可用陣列的pack打包回去
p [1679, 1049].pack('LL') #=>  "21706000031040000"
打包用的字串有很多我看不懂的,於是我舉例一些可能比較會用到的,如下表
打包字串 資料型態 中文說明
c char 8位元(即1字元),有符號字元(位元組)
C unsigned char 8位元(即1字元),無符號字元(位元組)
s short 16位元(即2字元),有符號短整數
S unsigned short 16位元(即2字元),無符號短整數
i int 32位元(即4字元),有符號整數
I unsigned int 32位元(即4字元),無符號整數
l long 32位元(即4字元),有符號長整數
L unsigned long 32位元(即4字元),無符號長整數
q long long 64位元(即8字元),有符號超長整數
Q unsigned long long 64位元(即8字元),無符號超長整數
Z unsigned char(算是) 8位元(即1字元),無符號字元(位元組)
如果使用的是字串的話可以使用這個打包
打包成字串時會自動填滿空字元
解包成陣列時會自動捨去空字元
?不同作業系統或者32或64位元的差異可能位元數會變,可在打包字串加上 ! 讓系統判別適合大小 
!如果再打包字串右邊添加數字代表其打包數量,如 'sssss' 等於 's5' ,功能相同
!如果再打包字串右邊添加 * 符號,則剩餘的資料都用該類型打包
 
 
來選個結構稍微長1點的來做範例,以GetVersionEx函數來說吧
該函數的功能是獲取Windows作業系統版本資訊
它的參數只有1個,參數的資料類型為LPOSVERSIONINFO
這個資料類型查網站得知是是名為OSVERSIONINFO或者是OSVERSIONINFOEX的指標
其實兩種結構效果相當,只是OSVERSIONINFOEX能得到較多資訊
(當然結構的資料型態也比較多),這邊選用OSVERSIONINFOEX結構
定義如下
[C++]
typedef struct _OSVERSIONINFOEX {
  DWORD dwOSVersionInfoSize; // 結構大小
  DWORD dwMajorVersion; // 主版本號
  DWORD dwMinorVersion; // 次版本號
  DWORD dwBuildNumber; // 操作系統版本
  DWORD dwPlatformId; // 操作系統平台
  TCHAR szCSDVersion[128]; // 服務包名
  WORD  wServicePackMajor; // 服務包主版本號
  WORD  wServicePackMinor; // 服務包次版本號
  WORD  wSuiteMask; // 支援類型標誌
  BYTE  wProductType; // 其他訊息標誌
  BYTE  wReserved; // 系統保留 (不使用)
} OSVERSIONINFOEX, *POSVERSIONINFOEX, *LPOSVERSIONINFOEX;
打包需要1個陣列,陣列長度跟結構的參數1樣即可,代表每個參數要賦予的值
而陣列的元數值依據結構的資料型態而定,以下介紹打包字串順便介紹單元值是啥
 
一開始的DWORD有5個,DWORD的原型其實就是unsigned long資料型態
所以打包用的字串指定5個L,此時字串為 'LLLLL' ,可簡寫為 'L5'
unsigned long是無符號長整數型態,於是前5個陣列單元值為正數值
 
接著是TCHAR資料型態,它的原型是 wchar_t ,嘛......可當做char型態
[這篇教學真的大丈夫?] 
這邊稍微看一下他的參數名稱『szCSDVersion[128]』,右邊有 [128]
這是C語言中的陣列宣告方式,相當於定義長度為 128 個單元的 TCHAR
所以可以想像這邊TCHAR類型有128個!!,打包字元可用C、A等,但我推薦指定Z
( 因為Z能夠把空字元順便處理乾淨,所以上表我才沒介紹類似功能的A、a打包字串 )
所以打包要128個Z也就是 'ZZZZZZZZZ.......痾....,之就簡寫成 'Z128' 就對了
而陣列的單元值只需用長度為 128 字元的字串即可,如『"" * 128』
 
接著是3個WORD類型,WORD原型是unsigned short,指定S,即 'S3'
接著的3個陣列值也是指定正數值
 
最後是兩個BYTE,原型是unsigned char,指定C,即 'C2'
接著的2個陣列值也是指定正數值
 
所有字串組合起來之後就是我的要的打包字串 'L5Z128S3C2'
而陣列內容為 [0, 0, 0, 0, 0, ""*128, 0, 0, 0, 0, 0],使用時可寫成
_OSVERSIONINFOEX = [0, 0, 0, 0, 0, ""*128, 0, 0, 0, 0, 0].pack('L5Z128S3C2')
p Win32API.new('kernel32', 'GetVersionEx', 'p', 'i').call(_OSVERSIONINFOEX) #=> 0
p _OSVERSIONINFOEX.unpack('L5Z128S3C2') #=> [0, 0, 0, 0, 0, "", 0, 0, 0, 0, 0]
GetVersionEx返回值是BOOL類型,代表布林值(實際原型是unsigned int)
它的返回值功用是代表是否執行成功,這邊 p 出0,表示執行失敗了!!
其實結構名稱是給人看得,實際上系統指認指標而已,也就是結構資料的所在區
但由於OSVERSIONINFO和OSVERSIONINFOEX結構的資料數量不一樣
所以為了分辨是OSVERSIONINFO還是OSVERSIONINFOEX結構,於是可看到第1個參數
『DWORD dwOSVersionInfoSize;』這個參數是指定結構的大小
用於告訴API函數這個結構的長度,並已對應長度的資料寫入到記憶體中(即結構裡)
取得結構長度很簡單,對已將陣列打包好的字串使用size方法即可(即字串長度)
p [0, 0, 0, 0, 0, ""*128, 0, 0, 0, 0, 0].pack('L5Z128S3C2').size #=> 156
得知結構大小之後,直接將參數指定在對應於dwOSVersionInfoSize位置的陣列即可
即陣列第0號位置指定為128就可以了,如下:
os_size = [0, 0, 0, 0, 0, ""*128, 0, 0, 0, 0, 0].pack('L5Z128S3C2').size
_OSVERSIONINFOEX = [os_size, 0, 0, 0, 0, ""*128, 0, 0, 0, 0, 0].pack('L5Z128S3C2')
p Win32API.new('kernel32', 'GetVersionEx', 'p', 'i').call(_OSVERSIONINFOEX) #=> 1
p _OSVERSIONINFOEX.unpack('L5Z128S3C2')
#=> [156, 6, 1, 7601, 2, "Service Pack 1", 1, 0, 256, 1, 0]
函數返回1表示執行成功了,在用unpack方法指定相同的打包字串即可拆回來
就是API傳遞回給我們的資料了,這些資料代表啥意思可查OSVERSIONINFOEX下面的表格
可得知我的作業系統為 Windows 7 Service Pack 1
 
 
文字編碼(亂碼)問題
  
RGSS環境下,預設的編碼為UTF-8,但由於API函數幾乎不是用UTF-8編碼格式
於是RM在獲取的時候,會將得到的資料硬是當成UTF-8來解讀,而產生亂碼的問題
一開始在介紹MessageBox函數的參數時有說先不要使用中文,因為使用後結果是...
Win32API.new('user32', 'MessageBox', 'lppi', 'i').call(0, '魔劍工舖', '測試', 0)
其結果為....
所以說當要使用API函數是跟非半形英文數字符號的文字有關的時候,需要進行轉碼
MessageBox函數有另外兩種名子,分別叫做MessageBoxA和MessageBoxW
MessageBoxA函數為MessageBox函數的 ANSI 編碼版本
不過MessageBox本身就是ANSI編碼,所以基本上無差異,但實際使用要使用 ANSI 編碼的話
函數可使用MessageBoxA直接指明是要用ANSI編碼的,避免未來微軟把MessageBox本身有改編碼的疑慮?!
而MessageBoxW函數為MessageBox函數的 Unicode 編碼版本,通常我轉成這邊碼居多
因為我要使用的轉碼方式只需轉1次即可,而轉成ANSI需要轉兩次,所以較少轉成ANSI編碼
 
傳入已轉碼的字串給API  
廢話說1堆了,轉編碼的方式有兩種,1種是直接計算改為相對應的位元資料成UTF-8的
另1種是使用API函數內定寫好的轉碼幫你轉,本篇以API教學為重,當然是用API轉(其實是我懶)
轉碼會需要使用兩個API函數,分別是MultiByteToWideChar以及WideCharToMultiByte函數
先來看看MultiByteToWideChar函數的,它的參數功能如下註解所示
[C++]
int MultiByteToWideChar(
  UINT CodePage, // 字元集
  DWORD dwFlags, // 設定標誌
  LPCSTR lpMultiByteStr, // 被轉換的字串
  int cchMultiByte, // 被轉換的大小
  LPWSTR lpWideCharStr, // 緩衝區
  int cchWideChar // 緩衝區大小
);
字元集:用於告訴API你提供的字串是何種編碼,RGSS為UTF-8編碼,所以指定CP_UTF8(常數值65001)
設定標誌:用來指定一些標誌常數的功能,不過我們用不到這些,指定為0即可
被轉換的字串:當然就是提供我們要轉的字串啦~
被轉換的大小:設定要轉換的字元數,可以直接指定-1即可,等同整個字串的大小
緩衝區:指一個字串的空間,已經被轉碼過後的字串將會被寫入在裡面,建議使用空字元填充空間
緩衝區大小:跟緩衝區的字串等長即可
返回值:會返回轉碼後的字串大小 
MultiByteToWideChar = Win32API.new('kernel32', 'MultiByteToWideChar', 'ilpipi', 'i') # 宣告
multi_proc = Proc.new do |text| # 定義Proc將轉編碼的程式碼統一,也可以改用def定義也行
  text_temp = "" * MultiByteToWideChar.call(65001, 0, text, -1, 0, 0) # 建立預估大小後的緩衝區
  MultiByteToWideChar.call(65001, 0, text, -1, text_temp, text_temp.size) # 開始轉碼
  text_temp # 返回轉碼過後的字串
end
Win32API.new('user32', 'MessageBoxW', 'lppi', 'i').call( # 改用 MessageBoxW 函數
  0, multi_proc.call('魔劍工舖'), multi_proc.call('測試'), 0)
來實際執行看看結果吧~~

沒錯,顯示正常了,如果用 p 方法去檢視已轉碼的字串,則變成預設 p 顯示的是亂碼了
因為我們等於是拿Unicode編碼的字串資料用UTF-8編碼解讀方式去解讀的關係~
  
ANSI 編碼  
雖然轉成Unicode編碼就能應付大多的情況,但還是稍微講一下如何轉乘ANSI編碼好了
以防函數本身沒提供Unicode編碼版本但有ANSI編碼的困擾
首先轉成ANSI編碼除了使用剛剛的MultiByteToWideChar函數外,還須使用WideCharToMultiByte函數
WideCharToMultiByte函數基本上就是MultiByteToWideChar的相反函數
為將Unicode編碼的字串轉為其他編碼的字串(也可用這個轉回UTF-8,後述~)
它們函數定義和指定方式也非常相似,只是多了幾個參數而已,以下是它的定義
[C++]
int WideCharToMultiByte(
  UINT CodePage, // 字元集
  DWORD dwFlags, // 設定標誌
  LPCWSTR lpWideCharStr, // Unicode編碼字串
  int cchWideChar, // Unicode編碼字串大小
  LPSTR lpMultiByteStr, // 緩衝區
  int cchMultiByte, // 緩衝區大小
  LPCSTR lpDefaultChar, // 替代文字
  LPBOOL pfUsedDefaultChar // 失敗標誌
);
基本上參數跟MultiByteToWideChar函數相當,所以我只取其中幾個參數再做說明
字元集:原本是告訴你提供的是何種編碼,現在則是指定要轉換成的編碼是哪個
    由於現在要轉換成ANSI編碼,所以指定為CP_ACP(常數值0)
Unicode編碼字串:原是提供UTF-8的字串(也就是直接給還沒轉化過的字串)
         現在則是給Unicode編碼的字串,就是剛用MultiByteToWideChar函數獲得的字串
Unicode編碼字串大小:一樣為-1即可,功能相同
替代文字、失敗標誌:這是在Unicode轉化成指定編碼時,沒有對應相同的字可以轉換時的處理
          例如把無對應可轉換的字替換或者是得知是有有無碰到這問題而用
          基本上用不到,也不希望能用到!! ,這兩個均指定為0即可
以下範例為先轉成Unicode編碼後測試看看3種MessageBox函數是否顯示亂碼或正常顯示
再將Unicode編碼轉換成ANSI編碼後,一樣用3種MessageBox函數看看是否有亂碼情形
text = '魔劍工舖'
# 宣告API函數
MultiByteToWideChar = Win32API.new('kernel32', 'MultiByteToWideChar', 'ilpipi', 'i')
WideCharToMultiByte = Win32API.new('kernel32', 'WideCharToMultiByte', 'ilpipipp', 'i')
# 先轉換成 Unicode 編碼
temp = "" * MultiByteToWideChar.call(65001, 0, text, -1, 0, 0)
MultiByteToWideChar.call(65001, 0, text, -1, temp, temp.size)
# 結果測試
Win32API.new("user32", "MessageBox", 'lppl', 'l').call(0, temp, temp, 0) # 亂碼顯示
Win32API.new("user32", "MessageBoxW", 'lppl', 'l').call(0, temp, temp, 0) # 正常顯示
Win32API.new("user32", "MessageBoxA", 'lppl', 'l').call(0, temp, temp, 0) # 亂碼顯示
# 再將 Unicode 編碼轉換成 ANSI 編碼
text = temp
temp = "" * WideCharToMultiByte.call(0, 0, text, -1, 0, 0, 0, 0)
WideCharToMultiByte.call(0, 0, text, -1, temp, temp.size, 0, 0)
# 結果測試
Win32API.new('user32', 'MessageBox', 'lppl', 'l').call(0, temp, temp, 0) # 正常顯示
Win32API.new('user32', 'MessageBoxW', 'lppl', 'l').call(0, temp, temp, 0) # 亂碼顯示
Win32API.new('user32', 'MessageBoxA', 'lppl', 'l').call(0, temp, temp, 0) # 正常顯示
  
轉回UTF-8編碼  
除了傳遞適合該API的編碼之外,有API也會傳回資料給我們(利用緩衝區的方式)
但傳回來的資訊通常不是UTF-8編碼,反而變成在RGSS環境下使用反而是亂碼的問題
【註】如果API寫入給你的字串是ANSI編碼而但只有半形英文數字符號等則不需要轉碼
   因為ANSI編碼和UTF-8編碼的半形英文數字符號等都是相同的資料排列方式
轉回UTF-8編碼的方法很簡單,一樣使用WideCharToMultiByte函數來轉
只是字元集從指定CP_ACP(常數值0)改成CP_UTF8(常數值65001)而已
但這也只限於字串是Unicode編碼的情況下,如果是從ANSI編碼轉成UTF-8的話
跟UTF-8轉成ANSI一樣,都需要先轉成Unicode編碼再轉成你所需要的編碼
這次我選用GetUserNameEx函數來當範例,它的功能是獲取目前使用者帳戶的名稱
一樣使用前看剛提供的連接最下面的表格,可得知函數所在Secur32.dll中
然後有GetUserNameExW (Unicode編碼) 和 GetUserNameExA (ANSI編碼) 兩個函數
【註】通常在命名函數時,結尾為大寫W為Unicode編碼,而大寫A為ANSI編碼
   順便一提,函數名稱尾部加上 Ex 表示擴展版本,通常參數和功能比沒加Ex多
GetUserNameEx函數的定義如下
[C++]
BOOLEAN WINAPI GetUserNameEx(
  _In_    EXTENDED_NAME_FORMAT NameFormat, // 名稱格式
  _Out_   LPTSTR               lpNameBuffer, // 緩衝區
  _Inout_ PULONG               lpnSize // 緩衝區大小
);
名稱格式:這個個資料型態為EXTENDED_NAME_FORMAT,但可別直覺性看成是結構
     先看看它的定義吧!!
[C++]
typedef enum  { 
  NameUnknown           = 0,
  NameFullyQualifiedDN  = 1,
  NameSamCompatible     = 2,
  NameDisplay           = 3,
  NameUniqueId          = 6,
  NameCanonical         = 7,
  NameUserPrincipal     = 8,
  NameCanonicalEx       = 9,
  NameServicePrincipal  = 10,
  NameDnsDomain         = 12
} EXTENDED_NAME_FORMAT, *PEXTENDED_NAME_FORMAT;
它是透過C語言的enum保留字所定義,其功能是定義列舉,其功用類似定義常數表
基本上算是可以想成Ruby的這樣吧?!
module EXTENDED_NAME_FORMAT
  NameUnknown           = 0
  NameFullyQualifiedDN  = 1
  NameSamCompatible     = 2
  NameDisplay           = 3
  NameUniqueId          = 6
  # ... 省略 ...
  NameDnsDomain         = 12
end
p EXTENDED_NAME_FORMAT::NameSamCompatible  #=> 2
總而言之這個就當常數來用吧,指定它裡面定義的數值使用即可,參數類型為 i 或 l 即可
此參數我指定為EXTENDED_NAME_FORMAT.NameDisplay (常數值3),即獲取全名  
緩衝區大小:PULONG類型,看它的參數名稱是lp開頭的,所以是指定指標
      由於這個API函數返回值有其他意義,所以它把指定緩衝區大小和其結果
      都統一在這個參數中,所以此參數除了傳遞資料外同時也是個緩衝區
      所以在使用時,需產生LONG長度的緩衝區大小位元組資料的字串來使用
返回值:BOOLEAN類型,看到返回值包含BOOL字樣,基本上可以當步林了....
    執行失敗返回0;執行成功返回非0值
函數介紹完畢,總感覺離題太久了,回正題回正題~~~
 
使用GetUserNameExAW函數獲取使用者帳戶全名的話,緩衝區寫入的編碼格視為 Unicode 編碼
可使用WideCharToMultiByte函數指定字元集為CP_UTF8(常數值65001)轉為UTF-8編碼[範例1]
 
如果使用GetUserNameEx或GetUserNameExA函數,則緩衝區寫入的編碼格式為 ANSI 編碼
需先使用MultiByteToWideChar函數將字串轉成 Unicode 編碼
再用WideCharToMultiByte函數指定字元集為CP_UTF8(常數值65001)轉為UTF-8編碼[範例2]
# 範例1:Unicode編碼轉為UTF-8編碼
# 宣告 API 函數
WideCharToMultiByte = Win32API.new('kernel32', 'WideCharToMultiByte', 'ilpipipp', 'i')
GetUserName = Win32API.new('secur32', 'GetUserNameExW', 'ipp', 'i') # 名稱結尾W
# 獲取使用者帳戶全名
UNLEN = 257
buffer = "" * UNLEN
uSize = [UNLEN].pack('L') # 緩衝區大小轉為位元組形式的字串
GetUserName.call(3, buffer, uSize)
p buffer.unpack('Z*').first #=> "215RT233B233"
# Unicode編碼轉為UTF-8編碼
temp = "" * WideCharToMultiByte.call(65001, 0, buffer, -1, 0, 0, 0, 0)
WideCharToMultiByte.call(65001, 0, buffer, -1, temp, temp.size, 0, 0)
p temp.unpack('Z*').first #=> "劍魔魂"
# 範例2:ANSI編碼轉為UTF-8編碼
# 以下跟範例1相當,只是多宣告了MultiByteToWideChar
函數
MultiByteToWideChar = Win32API.new('kernel32', 'MultiByteToWideChar', 'ilpipi', 'i')
WideCharToMultiByte = Win32API.new('kernel32', 'WideCharToMultiByte', 'ilpipipp', 'i')
GetUserName = Win32API.new('secur32', 'GetUserNameExA', 'ipp', 'i') # 名稱結尾A
UNLEN = 257
buffer = "" * UNLEN
uSize = [UNLEN].pack('L')
GetUserName.call(3, buffer, uSize)
# ANSI編碼轉為Unicode編碼
temp = "" * MultiByteToWideChar.call(0, 0, buffer, -1, 0, 0)
MultiByteToWideChar.call(0, 0, buffer, -1, temp, temp.size)
p temp.unpack('Z*').first #=> "215RT233" # 此處雖然跟範例一不一樣,但最後結果正確
# Unicode編碼轉為UTF-8編碼
temp2 = "" * WideCharToMultiByte.call(65001, 0, temp, -1, 0, 0, 0, 0)
WideCharToMultiByte.call(65001, 0, temp, -1, temp2, temp2.size, 0, 0)
p temp2.unpack('Z*').first #=> "劍魔魂"
 
  
查看函數與常數的定義  
我查看API函數或常數的定義通常是去微軟網站找,也就是我以上提供的連接都是...
如果要中文的使用說明則是去百度百科找(沒顧慮的話)或是看我之前在記事發過的那本書
不過我最主要看定義的方式還是直接使用微軟提供的 Visual Studio 軟體來查看定義
我目前使用的是 Visual Studio 2013 Express ,他功能強大、方便、官方的、不用錢!!
有興趣的話可以下載來安裝(雖然可能一安裝就會灌很多不一定會用到的東西?!) 
圖可點擊放大
安裝好打開後,按 [檔案(F)] → [新增專案(P)...] → [Visual C++] → [Win32 主控台應用程式]

確定後會打開 [Win32 應用程式精靈] 的視窗,直接按 [完成] 即可產生1個C++的專案
預設的程式碼為以下: 
[C++]
// CAddAdd.cpp : 定義主控台應用程式的進入點。
//
#include "stdafx.h"
int _tmain(int argc, _TCHAR* argv[])
{
return 0;
}
已查看MessageBox函數的定義為例,上面說過要查看函數庫是哪個DLL檔
可以看微軟MessageBox說明頁面下面的Requirements表格的DLL欄位得知
其中看到Header欄位他會寫 Winuser.h (include Windows.h) ,這檔案包含它的定義
所以要查看MessageBox函數的定義,需要指定Windows.h這個標頭檔才有這個定義資訊
可以在『#include "stdafx.h"』下方插入『#include <windows.h>』之後
就可以直接的在 _tmain 的 { 括號下面插入一行空白行
然後直接無腦的打上MessageBox然後按下右鍵選 [移至定義] 或 F12
程式會幫你打開 WinUser.h 的內容 (實際定義在這h檔,windows.h只是包含這標頭檔而已)
並且指向定義的那行,而他的附近就可以看到以下它的定義內容
[C++]
WINUSERAPI
int
WINAPI
MessageBoxA(
    _In_opt_ HWND hWnd,
    _In_opt_ LPCSTR lpText,
    _In_opt_ LPCSTR lpCaption,
    _In_ UINT uType);
WINUSERAPI
int
WINAPI
MessageBoxW(
    _In_opt_ HWND hWnd,
    _In_opt_ LPCWSTR lpText,
    _In_opt_ LPCWSTR lpCaption,
    _In_ UINT uType);
#ifdef UNICODE
#define MessageBox  MessageBoxW
#else
#define MessageBox  MessageBoxA
#endif // !UNICODE
稍微簡簡單單的解說一下這定義,沒提到的當作不用管沒關係!!(其實我也不懂,哈哈)  
定義在一開始就講講過了,就不再敘述,比較有趣的是會發現它沒定義MessageBox
只定義了MessageBoxA和MessageBoxW這兩個編碼的函數(功能一樣但編碼不同)
定義完的下面會看到5行內容發現了 MessageBox 的蹤跡,其中1行還是灰色的
#ifdef 功能類似條件分歧,不過他是在編譯的時候用的(因該),條件內容為UNICODE
這邊可以看做當字元集為UNICODE編碼時,執行『#define MessageBox  MessageBoxW』
否則 ( #else ) 就執行『#define MessageBox  MessageBoxA』
#define相當於定義別名(類似而已),將MessageBoxW定義一個別名MessageBox
所以MessageBoxW和MessageBox實際上是執行相同的函數
由於『#define MessageBox  MessageBoxA』是灰色的,表示這行編譯時是不列入參考的
也就是說在這環境下 MessageBox 函數反而是Unicode編碼 而非 ANSI編碼!!
如果要改為讓MessageBox是用MessageBoxA取的話可直接改這標頭檔的內容(非常不建議)
或是再方案總管選擇自己的專案名稱右鍵 [屬性],在 字元集 欄位中選擇 [未設定] 即可

此時會發現反而是 『#define MessageBox  MessageBoxW』這行變成灰色的了
就稍微說到這裡了,畢竟這不是C/C++教學什麼的...而但其實我也說不了什麼就是了
 
你甚至還可以繼續在這裡繼續移至定義,比如說想看看參數 UINT 是怎定義的
一樣點一下UINT讓文字游標在UINT上,右鍵移至定義或F12,可看UINT的定義
[C++]
typedef unsigned int        UINT;
到這裡就無法再對 unsigned int 進行移至定義了,因為unsigned int是C本身的資料型態了
不過看到 unsigned int 就可以知道他是 無符號的整數型態
 
常數的值也是用相同的方式,比如說上面提到的 MB_CANCELTRYCONTINUE 常數
一樣打上 MB_CANCELTRYCONTINUE 然後移至定義即可觀看他代表甚麼數值
也或者可以用滑鼠指著MB_CANCELTRYCONTINUE,也會這樣顯示出來給你看
這樣就可以知道MB_CANCELTRYCONTINUE常數實際值為16進制0x6 (L無視)
有興趣的話,也可以用 Visual Studio 寫應用程式、圖形化介面程式、DLL
語言雖然沒有Ruby(貌似有但要額外安裝,但我用不成功,不知道是不是版本較新的關係?!)
但也有較簡單的 VB.NET 可以寫,論實用度的話選用VC++我認為比較好
  
以下留言區域歡迎專業的來糾正我的教學(苦笑中)    
 
 
錯誤代碼函數 NEW 
有挺多API函數執行失敗時,只會告訴你是否執行成功,但沒有給出原因
因為有些錯誤會統整然後使用GetLastError函數來去獲取錯誤代碼
通常API函數執行失敗會設置GetLastError函數通常會特別註明
比如說微軟MessageBox的說明,再返回值處有這段
If the function fails, the return value is zero. To get extended error information, call GetLastError.
經過渣翻為:如果函數失敗,返回值為0,想獲得更多錯誤訊息,請用GetLastError
 
GetLastError函數的原型很簡單
[C++]
DWORD WINAPI GetLastError(void);
不用參數,返回無符號長整數,從這基本上能看出是用數值來代表錯誤說明
來實際使用看看吧,首先故意讓MessageBox執行失敗
MessageBox第1個參數,也就是窗口的識別碼指定隨便1個數字,此處我選1
這樣子MessageBox就會執行失敗了(原因是找不到識別碼為1的窗口)
! 0可視為NULL使用,而MessageBox為NULL時通常代表不用(又或者是指桌面)  
GetLastError = Win32API.new('kernel32', 'GetLastError', nil, 'l')
MessageBox = Win32API.new('user32', 'MessageBox', 'lppi', 'i')
p MessageBox.call(1, 'ABCDEFG', 'Sword', 0) #=> 0
p GetLastError.call #=> 0
一般來說MessageBox返回值代表按了訊息視窗哪個選項,通常不會返回0
當MessageBox返回0的時候就表示執行失敗,連訊息窗口都沒有出現
第1次 p 出來的為MessageBox的返回值,由於是0,所以該函數執行失敗
第2次 p 出來的為GetLastError的返回值,由於是0,代表前個函數正常執行
因GetLastError由於是0,所以MessageBox執行成功了~~~耶~皆大歡喜~~
才怪啦!! MessageBox都返回0了最好是執行成功了!!!
恩哼~總之...由於RGSS重定義的 p 和 print 方法似乎用MessageBox
所以在 p 出MessageBox的返回值的時候又正常的執行了1次MessageBox
此時 GetLastError 設置的結果是 p 方法裡面的MessageBox的可能
所以要使用GetLastError獲取錯誤訊息的話,需先暫停使用 p 和 print 方法
GetLastError = Win32API.new('kernel32', 'GetLastError', nil, 'l')
MessageBox = Win32API.new('user32', 'MessageBox', 'lppi', 'i')
a = MessageBox.call(1, 'ABCDEFG', 'Sword', 0)
p a, GetLastError.call #=> 0, 1400
這樣GetLastError就返回了 1400 這個錯誤代碼給我們
好了,現在問題又來了,這1400這數字是啥意思? 當然是去網路上查!!
雖然說這個說法是沒錯,但還有1種方式可以得知錯誤代碼的意思
就是使用FormatMessage函數,這個函數可以將錯誤代碼轉為錯誤說明
而定它會依照環境而改變顯示的語言,也就是說我們用會顯示繁體中文喔~
而它的定義如下
[C++]
DWORD WINAPI FormatMessage(
  _In_     DWORD   dwFlags, // 標誌
  _In_opt_ LPCVOID lpSource, // 依lpSource參數而定
  _In_     DWORD   dwMessageId, // 錯誤代碼
  _In_     DWORD   dwLanguageId, // ?
  _Out_    LPTSTR  lpBuffer, // 緩衝區
  _In_     DWORD   nSize, // 緩衝區大小
  _In_opt_ va_list *Arguments // ?
);
其實這個函數我不會用...(那還教),還是參考其他語言的範例推倒過來的...
第1個參數 dwFlage 指定常數 FORMAT_MESSAGE_FROM_SYSTEM (常數值0x1000)
根據搜尋資料表示是搜尋訊息表的資源,大概是去查錯誤列表之類的吧?!
參數dwLanguageId根據資料是語言標誌?!,不知是否換語系用的,0即可
va_list資料類型是 字元指標,也不知道做啥的,0即可(l i p 隨便...)
總而言之,標誌指定好後,只需要設定已會用的 錯誤代碼、緩衝區那些即可
# 宣告API函數
FormatMessage = Win32API.new('Kernel32', 'FormatMessageW', 'lpllplp','l') # W
WideCharToMultiByte = Win32API.new('kernel32', 'WideCharToMultiByte', 'ilpipipp', 'i')
# 獲取錯誤說明
t = "" * 256
FormatMessage.call(0x1000, 0, 1400, 0, t, 256, 0)
# 轉為UTF-8
temp = "" * 256
WideCharToMultiByte.call(65001, 0, t, -1, temp, temp.size, 0, 0)
p temp.unpack('Z*').first #=> "無效的視窗控制代碼。rn"
無聊的話也可以開個循環去看看每個錯誤代碼代表的意思~
# 宣告API函數
FormatMessage = Win32API.new('Kernel32', 'FormatMessageW', 'lpllplp','l') # W
WideCharToMultiByte = Win32API.new('kernel32', 'WideCharToMultiByte', 'ilpipipp', 'i')
# 觀看0~7000號錯誤代碼的說明
for i in 0..7000 
  t = "" * 256
  FormatMessage.call(0x1000, 0, i, 0, t, 256, 0)
  temp = "" * 256
  WideCharToMultiByte.call(65001, 0, t, -1, temp, temp.size, 0, 0)
  temp = temp.unpack('Z*').first
  p("錯誤代碼:#{i}", temp.unpack('Z*').first) if temp.size > 0
end
如要關閉視窗,請按住F12之後再按右上角的關閉按鈕

相簿設定
標籤設定
相簿狀態