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
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...