編譯c#專案,在專案屬性中,平臺與目標平臺的區別是什麼?
多長的一個標題,不是嗎?這是因為在一些情況下,你必需要做一大堆事情才能讓你的.NET應用程式同時在x86和x64環境下成功執行,尤其是在你還需要使用一些非安全程式碼(unmanaged code)或者需要訪問登錄檔的時候。
編譯和執行.NET元件(Compiling and running assemblies)
原生程式碼(例如C++等等)都會被編譯成為平臺相關的二進位制程式碼。這就是你在編譯時候需要指定目標平臺為x86或者x64的原因。在.NET平臺下,情況有所不同。就與你可能已經知道的一樣,你的VB或者C#程式碼並不會編譯成為二進位制程式碼,而是MSIL程式碼。這些程式碼載入時,JIT(Just-in-time)編譯器
既然如上所述,我為什麼還要為.NET元件指定目標平臺呢? "Any CPU"不應該是最好的選擇嗎? 是的。在絕大多數情況下,程式設計師開發的都是純.NET應用程式,它不依賴任何非託管程式碼,也不需要進一步的優化。所以"Any CPU"是最好的選擇(讓JIT編譯器動態確定編譯成什麼樣的程式碼)。但是仍然有些情況你會想去指定目標平臺,例如:
- 如果你使用了任何非託管程式碼(它是平臺相關的),就需要注意目標平臺的問題。因為當JIT編譯器選擇了一個和你的非託管程式碼不相容的模式去做編譯時,你的應用程式可能無法載入這些非託管程式碼。
- 如果你想對你的程式碼做平臺相關優化。例如,你的應用程式需要做大量的RAM記憶體訪問時,你可能只想讓它執行在64位模式下。
另外,還有其他和目標平臺相關的問題,比如登錄檔訪問。我們會在這篇文章的後面討論它。這篇博文對目標平臺有進一步的討論。
平臺相容性(Platform compatibility)
- 如果你的.NET元件使用"Any CPU"選項編譯
-
- 它可以執行在X86環境中(JIT編譯器把它編譯成X86二進位制程式碼)
- 它可以執行在X64環境中(JIT編譯器把它編譯成X64二進位制程式碼)
- 如果你的.NET元件使用"X64"作為目標平臺編譯
-
- 它不可以執行在X86環境中
- 它可以執行在X64環境中
- 如果你的.NET元件使用"X86"作為目標平臺編譯
-
- 它可以執行在X86環境中
- 它可以執行在X64環境中(在WOW64模式下)
WOW64模式
因為向後相容對於產品銷售很有幫助,所以Windows 64位作業系統提供了一個為執行32位應用程式的相容模式。它被稱為WOW模式(Windows on Windows)。你有使用模擬器在PC上玩過電視遊戲嗎?它就類似這個模擬器。WOW64是一個在64位機器上執行32位應用程式的模擬器。儘管我們還會在後面討論一些關於WOW64模式的問題,你可以先訪問這裡來了解WOW模式的更多細節。
檢測"Any CPU"程序的位數(Detecting the bitness of a "Any CPU" process)
當你把.NET元件編譯成為"Any CPU"時,會發生什麼事情呢?如果你知道這個.NET元件總是執行在X86環境下,那執行這個元件的程序也會是32位的。但是如果這個.NET元件可以執行在X86和X64環境下,那執行這個元件的程序是32位還是64位呢?更進一步說,如果它執行在X64作業系統上,JIT編譯器會選擇32位還是64位來編譯它呢?一般來說,它是64位的。但是怎樣能夠檢測到這個呢?很簡單,如下程式碼所示:
1 if (IntPtr.Size ==4) 2 return eArchitecture.x86; 3 elseif4 (IntPtr.Size ==8) 5 return eArchitecture.x64;System.IntPtr被設計用來儲存記憶體地址的。在.NET中,它提供了一個非常方便的屬性告訴我們它的長度。因此,如果它的長度是8個位元組,我們就是在用64位地址。如果是4位元組,我們就在32位環境中。就是如此簡單!
注意:如果你的元件編譯時選擇X86為目標平臺,執行它的程序永遠都是32位。即使執行在64位環境中,它使用的是WOW64模式(模擬32位),並且仍然是32位程序
Windows 64位基礎結構(Windows 64-bit infrastructure)
如上所述,Windows 64位作業系統提供了WOW64模式來允許執行X86應用程式。微軟的工程師們決定不在你的硬碟上混淆32位和64位應用程式,所有X64應用程式被安裝到預設的"Program Files"資料夾下面,同時,X86應用程式被安裝到新的"Program Files(X86)"資料夾下面。
Windows X64登錄檔
在X64的Windows作業系統中,WOW64模式為32位應用程式和64位應用程式提供了分開的登錄檔邏輯檢視(logical view),這讓應用程式像訪問兩個獨立的登錄檔。當為X64平臺開發或者部署應用程式時,這是需要考慮的重要一點,因為X86-targeted的安裝工程會以不同的方式訪問登錄檔。簡單來說,它用以下的方式工作:
Windows截獲來自應用程式和元件對於登錄檔的呼叫,並且重定向到相應的登錄檔邏輯檢視。這個過程對應用程式是透明的。可以從這裡瞭解到登錄檔重定向的更多資訊。
儘管下面的命名方式不是百分百正確,我們仍然分別稱這兩個登錄檔邏輯檢視為32位登錄檔和64位登錄檔。
使用Visual Studio安裝工程部署應用程式(Deploy Application with Visual Studio Setup Projects)
和很多人一樣,如果你也是使用Visual Studio安裝工程和Windows 安裝包來部署你的應用程式,並且你想讓你的應用程式同時相容"X86"和"X64"平臺,這裡有一組你需要考慮的事情。
Visual Studio工程的目標平臺
就像一般的元件一樣,安裝工程也有目標平臺屬性,當你在工程瀏覽器(Solution Explorer)中選中安裝工程時,你可以在屬性頁中看到這個屬性。如下圖所示:
不幸的是這裡沒有"Any CPU"選項,所以我們必須自己選擇平臺。這個平臺選擇會影響到下面的幾個方面:
- 你應用程式的預設安裝目錄:"Program Files" 或者"Program Files (X86)"。
- 如果安裝工程需要修改登錄檔,這個會影響到安裝程式是更新64位登錄檔還是WOW64程序中重定向的32位登錄檔。如果你的安裝工程用到登錄檔,這是一個重要的事情。
所以,我應該給安裝工程選擇怎樣的目標平臺呢?它依賴於你的應用程式怎樣被編譯的和怎樣為執行在X64環境下做準備。具體如下:
- 如果你的應用程式編譯成X86平臺相關的,選擇X86作為安裝工程的目標平臺。
- 如果你的應用程式編譯成X64平臺相關的,選擇X64作為安裝工程的目標平臺。
- 如果你的應用程式使用"Any CPU"編譯:
-
- 如果你的應用程式已經為執行在X64環境下準備好了(例如,登錄檔訪問已經考慮到WOW64模式),選擇X64
- 如果你不能確信你的應用程式已經為X64環境準備好了,選擇X86
那我的登錄檔鍵值在哪裡呢?如上所述,如果你的安裝工作使用X86作為目標平臺,它就不會修改64位登錄檔,而是重定向到由WOW64模式提供的32位登錄檔。因此,當你正常開啟"regedit.exe"時,並不能找到它們。取而代之的是你需要開啟這個特別的RegEdit去顯示32位登錄檔:[Windows Installation path]\SysWOW64\regedit.exe
從C#訪問登錄檔(Acessing the rigistry from C#)
你已經理解了Windows X64基礎架構怎樣設計的,以及你的應用程式怎樣被安裝工程安裝的。現在,你可能需要學習怎樣處理Window X64中兩個登錄檔檢視(Windows 64位登錄檔和Windows 32位登錄檔)。尤其是當你的應用程式以X86編譯的並執行在WOW64模式下時。
常見的.NET訪問登錄檔方式
想象你現在需要訪問"LOCAL_MACHINE\Software"登錄檔節點下面的某個鍵。在.NET中,一般用下面的程式碼來做這件事情(使用名字空間Microsoft.Win32):
1 RegistryKey key = Registry.LocalMachine.OpenSubKey("Software\\[Your Key Here]");這種做法在Windows X64機器中將只能訪問到"64位"登錄檔,即使你的程序是執行在WOW64模式下面。因此,如果你的應用程式是用X86安裝工程安裝的,我們上面要找的登錄檔項就會找不到(因為安裝包目標平臺是X86,所有的登錄檔項安裝時都寫到"32位"登錄檔中)。我們真正需要訪問的是由WOW64提供的"32位"登錄檔。
從C#訪問32位WOW64登錄檔
到目前為止,.NET平臺還沒有方法讓我們直接訪問WOW64模式下面的32位登錄檔。為了實現這個功能,我們必須要使用Windows自身的API呼叫,匯入"advapi32.dll"並使用函式RegOpenKeyEx。這個函式有一個引數"samDesired",它是用來指定訪問選項的標識組合,其中一個選項(KEY_WOW64_32KEY)就是指定我們希望開啟的登錄檔檢視是WOW64下的32位登錄檔。
samDesired中的標識
Value | Meaning |
---|---|
KEY_ALL_ACCESS (0xF003F) |
Combines the STANDARD_RIGHTS_REQUIRED, KEY_QUERY_VALUE, KEY_SET_VALUE, KEY_CREATE_SUB_KEY, KEY_ENUMERATE_SUB_KEYS, KEY_NOTIFY, and KEY_CREATE_LINK access rights. |
KEY_CREATE_LINK (0x0020) |
Reserved for system use. |
KEY_CREATE_SUB_KEY (0x0004) |
Required to create a subkey of a registry key. |
KEY_ENUMERATE_SUB_KEYS (0x0008) |
Required to enumerate the subkeys of a registry key. |
KEY_EXECUTE (0x20019) |
Equivalent to KEY_READ. |
KEY_NOTIFY (0x0010) |
Required to request change notifications for a registry key or for subkeys of a registry key. |
KEY_QUERY_VALUE (0x0001) |
Required to query the values of a registry key. |
KEY_READ (0x20019) |
Combines the STANDARD_RIGHTS_READ, KEY_QUERY_VALUE, KEY_ENUMERATE_SUB_KEYS, and KEY_NOTIFY values. |
KEY_SET_VALUE (0x0002) |
Required to create, delete, or set a registry value. |
KEY_WOW64_32KEY (0x0200) |
Indicates that an application on 64-bit Windows should operate on the 32-bit registry view. For more information, see Accessing an Alternate Registry View. This flag must be combined using the OR operator with the other flags in this table that either query or access registry values. Windows 2000: This flag is not supported. |
KEY_WOW64_64KEY (0x0100) |
Indicates that an application on 64-bit Windows should operate on the 64-bit registry view. For more information, see Accessing an Alternate Registry View. This flag must be combined using the OR operator with the other flags in this table that either query or access registry values. Windows 2000: This flag is not supported. |
KEY_WRITE (0x20006) |
Combines the STANDARD_RIGHTS_WRITE, KEY_SET_VALUE, and KEY_CREATE_SUB_KEY access rights. |
實現程式碼
"OpenSubKey"函式將會返回搜尋的登錄檔項,它允許你指定從正常的登錄檔中讀取還是從WOW64模式下的32位登錄檔中讀取。下面的例子就是從WOW64模式下的32位登錄檔中讀取登錄檔項:
你只需要把上面程式碼中的"[Key]"替換成你想要搜尋的登錄檔項即可。
怎樣讓你的應用程式是登錄檔讀取安全的(How to make your application a bulletproof registry reader)
現在,你已經有方法讀取X64環境下的兩個登錄檔了,我建議你用下面的方式來處理登錄檔訪問問題
- 首先,使用.NET中的標準方式"Registry.LocalMachine.OpenSubKey"查詢登錄檔項。這個方式能夠覆蓋你的程式執行在32位Windows上和執行在64位Windows上非WOW64模式的情況。
- 如果登錄檔項沒有找到,用下面方式中的一種進行處理:
-
- 檢測Windows版本
-
- 32位:異常,登錄檔沒有找到
- 64位:嘗試使用上面的程式碼去訪問WOW64模式下的登錄檔。如果同樣沒有找到,啟動異常處理
- 直接嘗試去訪問WOW64模式下的登錄檔,但把這段程式碼放到"try-catch"語句內。如果捕獲到異常,或者登錄檔項沒有找到,啟動異常處理
備註
-
x86 將程式集編譯為由相容 x86 的 32 位公共語言執行庫執行。
-
Itanium 將程式集編譯為由採用 Itanium 處理器的計算機上的 64 位公共語言執行庫執行。
-
x64 將程式集編譯為由支援 AMD64 或 EM64T 指令集的計算機上的 64 位公共語言執行庫執行。
-
anycpu(預設值)將程式集編譯為在任意平臺上執行。
在 64 位 Windows 作業系統上:
-
用 /platform:x86 編譯的程式集將在 WOW64 下執行的 32 位 CLR 上執行。
-
用 /platform:anycpu 編譯的可執行檔案將在 64 位 CLR 上執行。
-
用 /platform:anycpu 編譯的 DLL 將在與載入它的程序相同的 CLR 上執行。
有關開發在 Windows 64 位作業系統上執行的應用程式的更多資訊,請參見 64 位應用程式。
======================================
如果引用的庫包含X86的庫,或則可能需要在X86執行並且也需要支援X64位系統。
我個人為配置平臺選擇AnyCPU。
目標平臺推薦使用X86。這樣X86和X64位均可以執行,只不過在X64上是以WOW的CLR執行。
如果要在X86和X64位執行,建議所有宿主程式或者啟動程式為X86優化,動態庫可以為ANY CPU;
以下是我看到的一篇文章。作為佐證。
http://blog.csdn.net/sundacheng1989/article/details/18349503
分類: 技術積累2014-01-16
09:14 1705人閱讀 評論(1) 收藏 舉報
電腦硬體CPU可以分為x86與x64, x86的機器只能安裝32位的作業系統,如XP, WIN7_86, x64的機器既可以安裝32位的系統,又可以安裝64位的系統,只是在x64的機器上安裝32位的系統,不能夠很充分的利用這臺機器的資源。x86程式,即適用於32為作業系統的程式,x64即適用於64位作業系統的程式。64位系統上依然可以執行32位的程式,但是這是通過WOW64來執行,通俗上講,就是模擬出一個32位的CPU來執行這個程式。接下來是C#程式的編譯執行,分為兩步,第一步是編譯成IL,在編寫C#程式的時候,需要考慮到在最終程式需要在哪種環境下執行。Build中預設的環境是Any CPU,還有X86,X64. 這些有什麼不同?根據名字,我們可能認為,編譯器會根據選擇的環境不同來生成不同的二進位制檔案。然而,C#編譯器只是把程式碼編譯成為了IL程式碼,以dll的形式。然後再程式執行的時候,JIT編譯器才把IL程式碼編譯為CPU能夠識別的二進位制碼。所以,無論選擇哪個環境,都不會影響dll的生成,只是在dll的標頭檔案中加入了一些平臺資訊,最終執行的時候JIT會根據這些資訊來編譯dll給CPU處理。
x86編譯的dll和x64編譯的dll是不能相互引用的,否則會丟擲程式集不能下載的異常。Any CPU編譯的dll,被哪種型別的程式呼叫,就會編譯成哪種型別的dll.現在考慮一種情況
A專案是Console Application, B專案是Class Library, 在64位作業系統的機器上。A專案為Start Project,決定了這個程式的32位還是64位的屬性。如果A設定為x86, A決定了這個程式為32位,B設定為x86, 因為兩個專案都是同一型別,雖然都是32位,但可以在64位系統上執行。如果A為Any CPU, B為x86,就會出問題。因為A雖然為Any CPU,但是在64位作業系統上執行,就會轉換成64位的dll,A決定了這個程式為64位,然後再呼叫32位的dll,就會出問題。如果A為x86, B為Any CPU, 就不會出問題。 因為A決定了這個程式為32位,呼叫B的時候就會把B轉成32位,一切正常。
有人說,難道不可以一直把Project設定為Any CPU嗎?因為現在很多的系統都是64位的,所以我們的程式設定成Any CPU, 在64位機器上跑的時候就是64位的程式,但是這些程式很多時候需要使用一些元件,比如COM元件等,而這些元件僅僅是32位的dll,根本不支援64位,當我們用64位的程式呼叫這些dll的時候就會有問題。所以,這時候,我們就需要把我們的程式設定成x86.
那麼有人說,那都設定為x86不就可以了?那麼現在我們就要保證我們的程式引用的dll都是32位的。雖然很多第三方的dll都提供32位與64位兩個版本,基本上可以保證這一點。但是如果沒有必要做成32位的程式,單純是為了方便,我們前邊說過64位的系統執行32位的程式,效率不高,浪費了我們的資源。