C#學習筆記15

来源:http://www.cnblogs.com/zwt-blog/archive/2017/01/23/6344779.html
-Advertisement-
Play Games

1.平臺互操作性和不安全的代碼:C#功能強大,但有些時候,它的表現仍然有些“力不從心”,所以我們只能摒棄它所提供的所有安全性,轉而退回到記憶體地址和指針的世界。 C#通過3種方式對此提供支持。 (1)第一種方式是通過平臺調用(Platform Invoke,P/Invoke)來調用非托管代碼DLL所公 ...


1.平臺互操作性和不安全的代碼:C#功能強大,但有些時候,它的表現仍然有些“力不從心”,所以我們只能摒棄它所提供的所有安全性,轉而退回到記憶體地址和指針的世界。

  C#通過3種方式對此提供支持。

(1)第一種方式是通過平臺調用(Platform Invoke,P/Invoke)來調用非托管代碼DLL所公開的API。

(2)第二種方式是通過不安全的代碼,它允許我們訪問記憶體指針和地址。很多情況下,代碼需要綜合運用這兩種方式。

(3)第三種方式是通過COM Interop(COM互操作)。

2.平臺調用:

(1)外部函數的聲明:確定了要調用的目標函數以後,P/Invoke的下一步便是用托管代碼聲明函數,和普通方法一樣,必須在一個類中聲明目標API,但要為它添加extern修飾符,從而把它聲明為外部函數,extern方法始終是靜態方法,因此不包含任何實現。相反,附加在方法聲明之前的DllImport特性指向實現。該特性需要定義該函數的DLL的“名稱”,導入的DLL必須在路徑內,其中包含可執行文件的目錄,以使其能夠載入成功。“運行時”根據方法名來判斷函數名,然而也可以用EntryPoint具名參數來重寫預設行為,明確提供一個函數名。

(2)參數的數據類型:在確定目標DLL和導出函數,那麼要標識或創建與外部函數中的非托管數據類型對應的托管數據類型。

3.為順序佈局使用StructLayoutAttribute:有些API涉及的類型沒有對應的托管類型,要調用這些API,需要托管代碼重新聲明類型。例如,可以使用托管代碼來聲明非托管的COLORREF struct,如ColorRef結構清單。代碼中聲明的關鍵之處在於StructLayoutAttribute,預設情況下,托管代碼可以優化類型的記憶體佈局,所以,記憶體佈局可能不是從一個欄位到另一個欄位順序存儲。為了強制順序佈局,使類型能夠直接映射,而且可以在托管和非托管代碼之間逐位地複製,你需要添加StructLayoutAttribute特性,並指定LayoutKind.Sequential枚舉值。

4.平臺調用(P/Invoke)的錯誤處理:Win32 API編程的一個不便之處在於,錯誤經常以不一致的方式來報告,如有API返回0、1、false等,有API以out參數來處理,非托管代碼中的Win32錯誤報告很少通過異常來生成。P/Invoke設計者為此提供了相應的處理機制,要啟用這一機制,DllImport特性的SetLastError具名參數要設為true,這樣就可以實例化一個System.ComponentModel.Win32Exception。在P/Invoke調用之後,會自動用Win32錯誤數據來初始化它,如VirtualMemoryManger類的代碼清單。這樣一來,開發人員就可以提供每個API使用的自定義錯誤檢查,同時仍然可以使用一種標準方式來報告錯誤。

5.使用SafeHandle:很多時候,P/Invoke會涉及一個資源,比如視窗句柄(Window handle),等等。在用完此類資源之後,代碼需要清理它們。但是,不要強迫開發人員記住這一點,並每次都人工編寫代碼,而是應該提供實現IDisposable介面和終結器的類。為了對此提供內建的支持,如下麵的VirtualMemoryPtr類,該類派生自System.Runtime.InteropServices.SafeHandle。SafeHandle類包含兩個抽象成員:IsInvalid和ReleaseHandle()。在後者中,你可以放入對資源進行清理的代碼,前者則指出是否執行了資源清理代碼。可查看VirtualMemoryPtr類代碼清單。

6.P/Invoke指導原則:

(1)核實確實沒有托管類型已經公開你想要的API。

(2)將API外部方法定義為private,或者在簡單的情況下定義為Internal。

(3)圍繞外部方法提供公共包裝方法,執行數據類型轉換和錯誤處理。

(4)重載包裝方法,並通過為外部方法調用插入預設值,減少所需的參數數目。

(5)在聲明API的同時,使用enum或const為API提供常量值。

(6)針對支持GetLastError()的所有P/Invoke方法,務必將SetLastError命名特性的值設為true。這樣一來,就可以通過System.ComponentModel.Win32Exception報告錯誤。

(7)將句柄之類的資源包裝,包裝在從System.Runtime.InteropServices.SafeHandle派生或者支持IDisposable的類中。

(8)非托管代碼中的函數指針映射到托管代碼中的委托實例。通常,這需要聲明一個特定的委托類型,它與非托管函數指針的簽名是匹配的。

(9)將輸入/輸出參數和輸出參數映射到ref參數,而不是依賴於指針。

7.不安全的代碼:可以使用unsafe用作類型或者類型內部的特定成員的修飾符。unsafe修飾符對生成的CIL代碼本身沒有影響。它只是一個預編譯指令,作用是向編譯器指明允許在不安全的代碼塊內操作指針和地址。

8.指針的聲明:由於指針(本身只是恰好指向記憶體地址的一些整形值)不會被垃圾回收,所以C#不允許非托管類型之外的被引用物類型。換言之,類型不能是引用類型,不能是泛型類型,而且內部不能包含引用類型。如 byte* pData;指針是一種全新的類型,和結構、枚舉、類不同,指針的終極基類不是System.Object,甚至不能轉換成System.Object,相反,它們能轉換成System.IntPtr(後者能轉換成System.Object)。

9.指針的賦值:我們需要使用地址運算符(&)來獲取值類型的地址。無論哪種方法,為了將一些數據的地址賦值給一個指針,要求如下。

(1)數據必須屬於一個變數。

(2)數據必須是一個非托管類型。

(3)變數需要用fixed固定,不能移動。

  如 byte* pData = &bytes[0];//編譯錯誤,數據可能發生移動,需要固定。

  如 byte[] bytes = new bytes[24]; fixed (byte* pData = &bytes[0]){}//編譯正確

10.指針的解引用:為了訪問指針引用的一個類型值,要求你解引用指針,即在指針類型之前添加一個間接定址運算符*。如 byte data = *pData;不能對void*類型的指針應用解引用運算符,void*數據類型代表的是指向一個未知類型的指針。由於數據類型未知,所以不能解引用到另一種類型。相反,為了訪問void*引用的數據,必須把它轉換成其他任何指針類型的變數,然後對後一種類型執行解引用。

[StructLayout(LayoutKind.Sequential)]
public struct ColorRef
{
    public byte Red;
    public byte Green;
    public byte Blue;
    private byte Unused;

    public ColorRef(byte red, byte green, byte blue) : this()
    {
        Red = red;
        Green = green;
        Blue = blue;
        Unused = 0;
    }
}

public class VirtualMemoryManger
{
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, IntPtr dwSize, IntPtr dwFreeType);

    [DllImport("kernel32.dll", EntryPoint = "GetCurrentProcess")]
    internal static extern IntPtr GetCurrentProcessHandle();

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, IntPtr dwSize, AllocationType flAllocationType, uint flProtect);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool VirtualProtectEx(IntPtr hPorcess, IntPtr lpAddress, IntPtr dwSize, uint flNewProtect, ref uint lpflOldProtect);

    public static IntPtr AllocExecutionBlock(int size, IntPtr hProcess)
    {
        IntPtr codeBytesPtr = VirtualAllocEx(hProcess, IntPtr.Zero, (IntPtr)size, AllocationType.Reserve | AllocationType.Commit, (uint)ProtectionOptions.PageExecuteReadWrite);
        if (codeBytesPtr == IntPtr.Zero)
        {
            throw new Win32Exception();
        }
        uint lpflOldProtect = 0;
        if (!VirtualProtectEx(hProcess, codeBytesPtr, (IntPtr)size, (uint)ProtectionOptions.PageExecuteReadWrite, ref lpflOldProtect))
        {
            throw new Win32Exception();
        }
        return codeBytesPtr;
    }

    public static IntPtr AllocExecutionBlock(int size)
    {
        //通常應該將方法封裝到公共包裝裡面,從而降低P/Invoke API調用的複雜性,這樣可以增強API的可用性,同時更有利於轉向面向對象的類型結構。
        return AllocExecutionBlock(size, GetCurrentProcessHandle());
    }

    public static bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, IntPtr dwSize)
    {
        bool result = VirtualFreeEx(hProcess, lpAddress, dwSize, (IntPtr)MemoryFreeType.Decommit);
        if (!result)
        {
            throw new Win32Exception();
        }
        return result;
    }

    public static bool VirtualFreeEx(IntPtr lpAddress, IntPtr dwSize)
    {
        //無論錯誤處理、struct、還是常量值,優秀的API開發人員都應該提供一個簡化的托管API,降低層的Win32API包裝起來。
        return VirtualFreeEx(GetCurrentProcessHandle(), lpAddress, dwSize);
    }
}

public class VirtualMemoryPtr:SafeHandle
{
    public readonly IntPtr AllocatedPointer;
    private readonly IntPtr ProcessHandle;
    private readonly IntPtr MemorySize;
    private bool Disposed;
    public VirtualMemoryPtr(int memorySize) : base(IntPtr.Zero, true)
    {
        ProcessHandle = VirtualMemoryManger.GetCurrentProcessHandle();
        MemorySize = (IntPtr) memorySize;
        AllocatedPointer = VirtualMemoryManger.AllocExecutionBlock(memorySize, ProcessHandle);
        Disposed = false;
    }

    public static implicit operator IntPtr(VirtualMemoryPtr virtualAMemoryPointer)
    {
        return virtualAMemoryPointer.AllocatedPointer;
    }

    protected override bool ReleaseHandle()
    {
        if (!Disposed)
        {
            Disposed = true;
            GC.SuppressFinalize(this);
            VirtualMemoryManger.VirtualFreeEx(ProcessHandle,AllocatedPointer,MemorySize);
        }
        return true;
    }

    public override bool IsInvalid { get { return Disposed; } }
}

[Flags]
public enum AllocationType
{
    Reserve = 0x2000,
    Commit = 0x1000,
    Reset = 0x8000,
    Physical = 0x400000,
    TopDown = 0x100000,
}

[Flags]
public enum ProtectionOptions
{
    PageExecuteReadWrite = 0x40,
    PageExecuteRead = 0x20,
    Execute = 0x10
}

[Flags]
public enum MemoryFreeType
{
    Decommit = 0x4000,
    Release = 0x8000
}
View Code
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...