2011/6/9

.Net Interoperation 入門

就字面上來看, Interoperation 是一個英文複合字; 如果你在電腦上打入這個字, 一些 editor 的 spell checker 會告訴你拼字錯誤, 並建議你更改成 Inter-operation 甚至其它拼法。其實 Interoperation 是工程界普遍使用的字眼, 專指在不同系統中搭起的簡易或臨時的元件, 藉由製訂某些共通的協定, 以便這些系統可以共同作業。由於這個溝通界面可能並非非常嚴謹, 所以當其運作時, 必須特別有人從旁監督、隨時調整和修改。

在.Net 中, Interoperation (簡稱 Interop) 專指從.Net 應用程式中存取 unmanaged 程式元件的行為。我們知道, 當我們撰寫 .Net 應用程式或元件時, 這些元件都是 managed (受 .Net 執行環境所管理的)。但是如果是使用VB6/C/C++ 所撰寫的非 .Net 程式的 COM 元件, 對 .Net 而言, 都算是 unmanaged。如果我們企圖在 .Net 程式中存取那些 unmanaged 程式或資源, 就必須透過 Interoperation 技巧與COM 介接。

此外, 許多 Windows API 並沒有 .Net 的對應函式。換句話說, 如果我們要撰寫 Windows 應用程式, 而且我們必須使用到 Windows API 的話, 我們沒辦法從 .Net Framework 執行環境中找到對應的方法, 而必須透過 Interoperation 去呼叫 Windows API。

簡單範例

以下我將使用一個很簡單的範例程式, 讓大家很快的了解 Interoperation 的用途。

首先, 請從你的 Visual Studio 中建立一個專案(Console, Windows Form, Web Form 都可以, 但以下我使用 ASP.NET 網頁作為範例), 然後在主程式中輸入以下程式碼:

using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Runtime.InteropServices;

namespace WebApplication1
{
    enum BeepTypes : ulong
    {
        Simple = 0xFFFFFFFF, 
        MB_OK = 0x00000000L, 
        MB_ICONSTOP = 0x00000010L, 
        MB_ICONERROR = 0x00000010L, 
        MB_ICONHAND = 0x00000010L,
        MB_ICONQUESTION = 0x00000020L, 
        MB_ICONEXCLAMATION = 0x00000030L, 
        MB_ICONWARNING = 0x00000030L, 
        MB_ICONINFORMATION = 0x00000040L,
        MB_ICONASTERISK = 0x00000040L
    }

    public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        { 
           MessageBeep((UInt32)BeepTypes.MB_OK); 
        }

        [DllImport("User32.dll", SetLastError = true)]
        static extern Boolean MessageBeep(UInt32 uType);
    }
}

在以上程式中, 我從 ASP.NET 程式 (Managed) 中呼叫了 Win32 API 中的 MessageBeep 函式 (Unmanaged), 而沒有使用 .Net 所提供的方法。當這個網頁載入時, 就可以聽到一個嗶聲 (當然, 只有在你的本機聽得到, 客戶端是聽不到的)。先不要管這個程式的實用性 (畢竟本程式只是用來示範而已), 其實質上的意義在於我們在 Managed 程式中去呼叫 Unmanaged 的 Windows API 的做法。

現在, 請到 MSDN 網站看一下 MessageBeep 這個函式的說明: http://msdn.microsoft.com/en-us/library/ms680356(VS.85).aspx

從這個 MSDN 網頁中, 我們不妨往網頁的左邊看過去, 沿著導覽版面中其樹狀分枝往上走, 直到 Windows Development 這個項目為止, 該項目以下的許多樹狀分枝, 就是完整 Windows API 列表及說明。在龐大的 Windows API 中, 有許多功能是 .Net Framework 並未提供的; 如果你想呼叫這些功能, 只能透過 Interoperation。

不過, 話又說回來, 我其實並不十分鼓勵初學者沒事去呼叫 Windows API。你既然要寫 .Net 程式, 最好是盡量使用 .Net 所提供的方法或資源。要記得, 如果你要直接存取 Windows API, 那麼你就會動用到 Unmanaged 程式或資源, 而 Unmanaged 程式或資源往往是在 .Net 中造成最難除錯, 也是造成最多不明問題、造成最多效能低落的可能原因。或許你是習慣寫 C/C++ 的老程式設計師, 而 Windows API 是你最熟悉的工具; 但是直接去呼叫 Windows API 在 .Net 程式中總是有代價的, 別忘記這一點。

在 Visual Studio 中安裝 PInvoke.Net Addin

在 .Net 習慣用語中, 從 Managed 程式碼中呼叫 Unmanaged 程式碼的動作稱為 Platform Invoke (平台調用), 簡稱為 P/Invoke。而從上一節簡單範例中, 我們已經看到如何把 Windows API 中的 MessageBeep 函式包裝成可以從 .Net 程式直接呼叫的 MessageBeep() 方法。

很可惜的, 我們並不能從 MSDN Library 中找到如何將 Windows API 包裝成 .Net 方法的說明。很明顯的, Microsoft 似乎並不歡迎使用者在 .Net 程式中直接呼叫 Windows API。因此, 如果你企圖呼叫某個 Windows API 時, 你馬上會遇到不知從何處著手的冏境。

例如, 假設你從 MSDN Library 中找到如下的 Windows API 函式:

WORD WINAPI CascadeWindows(
    __in_opt  HWND hwndParent,
    __in      UINT wHow,
    __in_opt  const RECT *lpRect,
    __in      UINT cKids,
    __in_opt  const HWND *lpKids);

我相信, 除非你是個已經很熟悉 P/Invoke 的老練程式設計師, 否則對初學者而言, 要讓它可以從 .Net 中呼叫, 這根本是件根本不可能的任務。為什麼?

首先, Win32 程式中的型別與 .Net 的型別是有巨大差距的, 而且各個型別之間並不是一一對應。而 C/C++ 語言中有許多帶有指標的變數, 對於平常的 .Net 程式設計師也是一個相當大的挑戰。其次, Windows 程式設計中時常有 handle 這種變數, 還有許多令人眼花瞭亂的 structure、const 以及 enum 充斥在程式中間; 對於完全不了解 Windows 程式設計的人而言, 這些都是極高的門檻。

很幸運的, 我們可以透過 PInvoke.Net 這個網站以及其 Visual Studio Addin 來協助降低以上所提的各種門檻。

關於 PInvoke.Net 的安裝及簡單介紹, 請參考「在 VS2008 及 VS2010 安裝 PInvoke.Net」一文。

PInvoke.Net Addin 的安裝並非必要的, 它只是方便你直接在 Visual Studio 上面操作而已。你也可以直接在 http://www.pinvoke.net 網站上進行搜尋, 結果是一樣的。

不過, 容我回頭再潑你一盆冷水。即使有了 PInvoke.Net 的協助, 以上所提的所有門檻通通依然存在, 並沒有消失。PInvoke.Net 上面所有內容都是志願者提供的, 並未經過完整的正式測試 (我敢說有些甚至沒有測試過)。或許你可以囫圇吞棗的抄一些現成的程式碼來用, 但是如果你願意在這方面繼續鑽研透徹一點, 我相信你的程式碼會變得更嚴謹、問題更少一點。

沒有留言:

張貼留言