19. 平台互相操作性和不安全代码

19.1 在托管平台调用非托管代码——P/Invoke模式

CLI通过P/Invoke功能对非托管DLL所导出的函数执行API调用。和类的所有普通方法一样,必须在类的上下文中声明目标API,但要为其添加extern修饰符,从而把它声明为外部函数。

  1. using System;
  2. using System.Runtime.InteropServices;
  3. namespace ConsoleApp1;
  4. internal static class Program
  5. {
  6. private static void Main()
  7. {
  8. IntPtr test = GetCurrentProcessHandle();
  9. }
  10. [DllImport("kernel32.dll", EntryPoint = "GetCurrentProcess")]
  11. private static extern IntPtr GetCurrentProcessHandle();
  12. }

extern方法永远没有主题,而且几乎总是静态方法。具体实现由对方法声明进行修饰的DllImport特性指定。该特性要求最起码提供定义了函数的DLL文件名称,“运行时”会根据方法名判断函数名。当然,也可以使用EntryPoint参数明确提供一个函数名。

GetCurrentProcessHandle没有参数,如果想要传递参数该怎么做呢?下面以VirtualAllocEx函数为例子,演示参数如何在托管代码和非托管代码之间传递。

Win32 API中的VirtualAllocEx函数定义如下:

  1. LPVOID VirtualAllocEx(
  2. [in] HANDLE hProcess,
  3. [in, optional] LPVOID lpAddress,
  4. [in] SIZE_T dwSize,
  5. [in] DWORD flAllocationType,
  6. [in] DWORD flProtect
  7. );

其中:

  1. hProcess:进程的句柄。 该函数在此过程的虚拟地址空间中分配内存。
  2. lpAddress:指定要分配的页面区域的所需起始地址的指针。如果要保留内存,函数会将此地址舍入到分配粒度中最近的倍数。如果要提交已保留的内存,函数会将此地址向下舍入到最近的页面边界。如果 lpAddress 为 NULL,则该函数确定分配区域的位置。
  3. dwSize:要分配的内存区域的大小(以字节为单位)。如果 lpAddress 为 NULL,则该函数将 dwSize 舍入到下一页边界。如果 lpAddress 不是 NULL,则该函数将分配包含 lpAddress 到 lpAddress+dwSize 范围内一个或多个字节的所有页面。 例如,这意味着跨页边界的 2 字节范围会导致函数分配这两个页面。
  4. flAllocationType:指定内存分配的类型。
  5. flProtect:要分配的页面区域的内存保护。

简而言之,VirtualAllocEx函数分配 操作系统 特别为 代码执行或数据 指定的虚拟内存。由于在CLR看来,HANDLE、LPVOID等类型均是未定义类型,所有要调用它,托管代码需要为每种数据类型提供相应的定义,这种定义要符合CLR的要求,还要被Win32 API接受。示例如下:

  1. using System;
  2. using System.Runtime.InteropServices;
  3. namespace ConsoleApp1;
  4. internal static class Program
  5. {
  6. //SetLastError:是否调用线程的最后错误代码
  7. [DllImport("kernel32.dll", SetLastError = true)]
  8. private static extern IntPtr VirtualAllocEx(
  9. IntPtr hProcess,
  10. IntPtr lpAddress,
  11. IntPtr dwSize,
  12. uint flAllocationType,
  13. uint flProtect);
  14. }

示例1:使用C++编写C#可以调用的API

  1. title: 几个前提条件
  2. 1. 使用`__declspec(dllexport)`修饰
  3. 2. 使用extern "C"进行修饰
  4. 3. C++项目要编译为动态链接库

C++项目代码如下:

  1. /* CalculateData.h */
  2. #pragma once
  3. #define CALCULATE_EXPORTS __declspec(dllexport)
  4. extern "C" {
  5. CALCULATE_EXPORTS int Add(int numberA, int numberB);
  6. CALCULATE_EXPORTS int Subtract(int numberA, int numberB);
  7. CALCULATE_EXPORTS int Multiplication(int numberA, int numberB);
  8. CALCULATE_EXPORTS int Divided(int numberA, int numberB);
  9. }
  1. /* CalculateData.cpp */
  2. #include "CalculateData.h"
  3. #include <iostream>
  4. CALCULATE_EXPORTS int Add(const int numberA, const int numberB) {
  5. return numberA + numberB;
  6. }
  7. CALCULATE_EXPORTS int Subtract(const int numberA, const int numberB) {
  8. return numberA - numberB;
  9. }
  10. CALCULATE_EXPORTS int Multiplication(const int numberA, const int numberB) {
  11. return numberB * numberA;
  12. }
  13. CALCULATE_EXPORTS int Divided(int numberA, int numberB) {
  14. if (numberB == 0) {
  15. std::cout << "除数不能为空" << std::endl;
  16. }
  17. return numberA / numberB;
  18. }

C#项目代码如下:

  1. using System;
  2. using System.Runtime.InteropServices;
  3. namespace ConsoleApp1;
  4. internal static class Program
  5. {
  6. [DllImport(@"../../../../x64/Debug/ConsoleApplication1.dll")]
  7. private static extern int Add(int numberA, int numberB);
  8. [DllImport(@"../../../../x64/Debug/ConsoleApplication1.dll")]
  9. private static extern int Subtract(int numberA, int numberB);
  10. [DllImport(@"../../../../x64/Debug/ConsoleApplication1.dll")]
  11. private static extern int Multiplication(int numberA, int numberB);
  12. [DllImport(@"../../../../x64/Debug/ConsoleApplication1.dll")]
  13. private static extern int Divided(int numberA, int numberB);
  14. private static void Main()
  15. {
  16. Console.WriteLine("Hello, World!");
  17. Console.WriteLine($"{Add(2, 5)}, 2+5={2 + 5}");
  18. Console.WriteLine($"{Multiplication(2, 5)}, 2*5={2 * 5}");
  19. Console.WriteLine($"{Subtract(2, 5)}, 2-5={2 - 5}");
  20. Console.WriteLine($"{Divided(10, 2)}, 10/2={10/2}");
  21. }
  22. }

19.1.1 为按引用传递的参数使用ref或者out而不是Intptr

许多时候,非托管代码会为按引用(passed by reference)的参数使用指针。在这种情况下,P/Invoke不要求在托管代码中将数据类型映射为指针(IntPtr)。相反,应将对应参数映射为ref或者out,具体取决于参数是否只用于输出。

例如:Win32 API 中的lpflOldProtect的数据类型是PDWORD,返回指针类型,该参数被约定为只用于输出(虽然在C语言中没办法强制要求一个参数是否只用于输出)。

  1. internal static class Program
  2. {
  3. private const string DllName = "kernel32.dll";
  4. [DllImport(DllName, SetLastError = true)]
  5. private static extern bool VirtualProtectEx(
  6. IntPtr hProcess,
  7. IntPtr lpAddress,
  8. IntPtr dwSize,
  9. uint flNewProtect,
  10. out uint lpflOldProtect); // 这里用了out参数
  11. }

但在随后的描述中又指出,该参数(lpflOldProtect)必须指向一个有效值变量(有输入的情况出现),而不能是NULL,如果文档中出现了这种自相矛盾的说法,那么最好还是用ref而不是out。

  1. internal static class Program
  2. {
  3. private const string DllName = "kernel32.dll";
  4. [DllImport(DllName, SetLastError = true)]
  5. private static extern bool VirtualProtectEx(
  6. IntPtr hProcess,
  7. IntPtr lpAddress,
  8. IntPtr dwSize,
  9. uint flNewProtect,
  10. ref uint lpflOldProtect); // 这里用了ref参数
  11. }

19.1.2 使用StructLayoutAttribute指定内存布局

有些API涉及的类型无对应的托管类型(如非托管代码中的自定义类型),调用这些API需要用托管代码重新声明类型。默认情况下托管代码可能优化了自定义类型的内存布局,所以内存布局可能不是顺序存储的,这在与非托管代码交互时可能会带来问题(无法逐位映射)。可以用StructLayoutAttribute强制托管代码使用顺序内存布局。

  1. [StructLayout(LayoutKind.Sequential)]
  2. internal struct ColorRef
  3. {
  4. public byte Red;
  5. public byte Green;
  6. public byte Blue;
  7. private byte Unused;
  8. public ColorRef(byte red, byte green, byte blue)
  9. {
  10. Red = red;
  11. Green = green;
  12. Blue = blue;
  13. Unused = 0;
  14. }
  15. }

注:C#语言在设计时也考虑到了这种与非托管代码交互的情况,C#里的值类型均默认使用顺序内存布局(LayoutKind.Sequential),C#里的引用类型均默认使用了自动内存布局(LayoutKind.Auto)。所以,上述示例中的StructLayoutAttribute可以省略。

19.1.3 错误处理

由于历史原因,非托管代码中的API经常返回一个值(例如:0,-1,false等)来指示错误,有些API还会设置一个只用于输出的引用参数来指示错误;除此之外,如果想了解错误细节可能还要单独调用错误处理API。总之,非托管代码的错误报告很少通过异常来生成。

对于Win32 API,P/Invoke设计者专门提供了处理机制(将DllImportAttribute中的SetLastError设为true)。这样就可实例化一个System.ComponentModel.Win32Exception,在P/Invoke调用之后,会自动用Win32错误数据来初始化它。示例如下:

  1. internal class VirtualMemoryManager
  2. {
  3. private const string Kernel32Dll = "kernel32.dll";
  4. [DllImport(Kernel32Dll, SetLastError = true)]
  5. private static extern IntPtr GetCurrentProcessHandle();
  6. [DllImport(Kernel32Dll, SetLastError = true)]
  7. private static extern IntPtr VirtualAllocEx(
  8. IntPtr hProcess,
  9. IntPtr lpAddress,
  10. IntPtr dwSize,
  11. uint flAllocationType,
  12. uint flProtect);
  13. [DllImport(Kernel32Dll, SetLastError = true)]
  14. private static extern bool VirtualProtectEx(
  15. IntPtr hProcess,
  16. IntPtr lpAddress,
  17. IntPtr dwSize,
  18. uint flNewProtect,
  19. ref uint lpflOldProtect);
  20. [Flags]
  21. private enum AllocationType : uint
  22. {
  23. Commit = 0x1000,
  24. Reserve = 0x2000,
  25. Reset = 0x80000,
  26. Physical = 0x400000,
  27. TopDown = 0x100000
  28. }
  29. [Flags]
  30. private enum ProtectionOptions : uint
  31. {
  32. Execute = 0x10,
  33. PageExecuteRead = 0x20,
  34. PageExecuteReadWrite = 0x40,
  35. //...
  36. }
  37. [Flags]
  38. private enum MemoryFreeType : uint
  39. {
  40. DeCommit = 0x4000,
  41. Release = 0x0000
  42. }
  43. public static IntPtr AllocExecutionBlock(int size, IntPtr hProcess)
  44. {
  45. var codeBytesPtr = VirtualAllocEx(hProcess, IntPtr.Zero, (IntPtr)size,
  46. (uint)(AllocationType.Reserve | AllocationType.Commit),
  47. (uint)ProtectionOptions.PageExecuteReadWrite);
  48. if (codeBytesPtr == IntPtr.Zero) throw new System.ComponentModel.Win32Exception();
  49. var lpflOldProtect = 0U;
  50. if (!VirtualProtectEx(hProcess, codeBytesPtr, (IntPtr)size, (uint)ProtectionOptions.PageExecuteReadWrite,
  51. ref lpflOldProtect))
  52. {
  53. throw new System.ComponentModel.Win32Exception();
  54. }
  55. return codeBytesPtr;
  56. }
  57. public static IntPtr AllocExecutionBlock(int size)
  58. {
  59. return AllocExecutionBlock(size, GetCurrentProcessHandle());
  60. }
  61. }

19.1.4 使用SafeHandle

P/Invoke经常涉及用完需要清理的资源(例如:handle),但不能强迫开发者手动清理这些资源。正确的做法是在调用非托管API的托管代码中实现IDisposeable接口和终结器。C#提供了SafeHandle类,专门用于P/Invoke模式下的资源清理。SafeHandle类实现了IDisposable接口,无需在SafeHandle的派生类中在实现一遍IDisposable接口。以SafeHandle的派生类VirtualMemoryPtr为例:

  1. internal class VirtualMemoryPtr : SafeHandle
  2. {
  3. public VirtualMemoryPtr(int memorySize) : base(IntPtr.Zero, true)
  4. {
  5. _ProcessHandle = VirtualMemoryManager.GetCurrentProcessHandle();
  6. _MemorySize = (IntPtr)memorySize;
  7. _AllocatedPointer = VirtualMemoryManager.AllocExecutionBlock(memorySize, _ProcessHandle);
  8. _isDisposed = false;
  9. }
  10. public readonly IntPtr _AllocatedPointer;
  11. private readonly IntPtr _ProcessHandle;
  12. private readonly IntPtr _MemorySize;
  13. private bool _isDisposed;
  14. // 定义从IntPtr到VirtualMemoryPtr的隐式类型转换
  15. public static implicit operator IntPtr(VirtualMemoryPtr virtualMemoryPtr)
  16. {
  17. return virtualMemoryPtr._AllocatedPointer;
  18. }
  19. public override bool IsInvalid => _isDisposed;
  20. protected override bool ReleaseHandle()
  21. {
  22. if (_isDisposed) return true;
  23. // 执行释放操作
  24. // ...
  25. _isDisposed = true;
  26. GC.SuppressFinalize(this);
  27. return true;
  28. }
  29. }

19.1.5 为外部函数中的枚举型参数提供常量或等价枚举类型

在19.1.3的示例中,外部函数VirtualAllocEx和VirtualProtectEx使用了带有标志(flag)的枚举型,永远不要让调用者定义这些东西,应该将这些枚举型当作API的一部分提供给调用者。

19.1.6 适当包装调用非托管DLL的托管代码

无论错误处理,常量、结构体还是其他相关代码,优秀的开发者都应该提供一个简化的托管API将底层Win32 API包装起来。就像19.1.3的示例所作的那样,将管理虚拟内存用的Win32 API都包装到了托管类VirutalMemoryManager中。

19.1.7 函数指针映射到委托

C/C++中存在函数指针,函数指针的作用等价于C#中的委托。例如,为了设置计时器,需要给计时器提供一个计时到期后能回调其他方法的handle。在C/C++中,这个handle使用函数指针实现,在C#中使用委托或事件实现,但为了与C/C++的回调机制相匹配,这个handle只能用委托实现(自定义委托)。

19.1.8 设计规范

  1. title: 设计规范
  2. 1. 不要无谓重复现有的、已经能够执行非托管API功能的托管类
  3. 2. 要将外部方法声明为私有或内部
  4. 3. 要提供使用了托管约定的公共包装器方法,包括结构化异常处理、为特殊值使用枚举等
  5. 4. 要为非必须参数选择默认值来简化包装器方法

19.2 不安全的代码——指针和地址

19.2.1 fixed语句

如果数据是非托管变量类型,但是不固定,就用fixed语句固定可移动的变量,示例如下:

  1. internal static class Program
  2. {
  3. private static void Main()
  4. {
  5. var bytes = new byte[24];
  6. unsafe
  7. {
  8. fixed (byte* pData = &bytes[0]) // 等价于fixed(byte* pData=bytes)
  9. {
  10. //...
  11. }
  12. }
  13. }
  14. }

在fixed语句块中,赋值的数据不会再移动。fixed语句要求变量指针在其作用域内声明。这样可防止但数据不在固定时访问到fixed语句外部的变量,但最终是由开发者来保证不会将指针赋值给fixed语句之外也能生存的变量。不安全的代码不安全是有原因的,不要依赖“运行时”来确保安全性。C#允许fixed语句声明一个char*类型的指针,并将该指针指向string。

  1. internal static class Program
  2. {
  3. private static void Main()
  4. {
  5. var chars = new string("aaa");
  6. unsafe
  7. {
  8. fixed (char* pChars = chars)
  9. {
  10. //...
  11. }
  12. }
  13. }
  14. }

类似的,fixed语句允许其他任何可移动类型,只要它们能隐式转换为其他类型的指针即可。

取决于执行效率和时机,fixed语句可能导致内存堆中出现碎片,这是由于GC不能compact已固定的对象,所以如果要固定数据,宁可固定几个大块也不要固定许多小块,同时还应该尽量减少内存固定的时间,降低在数据固定期间发生垃圾回收的概率。为了减少堆中的碎片,对于小对象可以考虑在栈上分配,示例如下:

  1. internal static class Program
  2. {
  3. private static void Main()
  4. {
  5. unsafe
  6. {
  7. byte* bytes = stackalloc byte[42];
  8. }
  9. }
  10. }

在前面提到过,string类型一旦定义就不可更改,对string类型的修改其实是销毁原先的string类型实例,然后创建新的string类型实例。如果使用指针,没有什么是不能修改的,即使用了const关键字修饰也照改不误。

  1. internal static class Program
  2. {
  3. private static void Main()
  4. {
  5. const string text = "s5280ft";
  6. Console.WriteLine(text);
  7. unsafe
  8. {
  9. fixed (char* pText = text)
  10. {
  11. var p = pText;
  12. *++p = 'm';
  13. *++p = 'i';
  14. *++p = 'l';
  15. *++p = 'e';
  16. *++p = 's';
  17. *++p = ' ';
  18. }
  19. }
  20. Console.WriteLine(text);
  21. }
  22. }

19.2.2 使用指针访问被引用类型中的成员

C++中使用->或者(*x).y来访问被引用类型中的成员,C#也是如此。

  1. internal static class Program
  2. {
  3. private struct Angle
  4. {
  5. public Angle(double hours, double minutes, double second)
  6. {
  7. Hours = hours;
  8. Minutes = minutes;
  9. Second = second;
  10. }
  11. public double Hours { get; }
  12. public double Minutes { get; }
  13. public double Second { get; }
  14. }
  15. private static void Main()
  16. {
  17. unsafe
  18. {
  19. var angle = new Angle(30, 18, 0);
  20. Angle* pAngle = &angle;
  21. Console.WriteLine($"{pAngle->Hours}°{(*pAngle).Minutes}'{pAngle->Second}\"");
  22. }
  23. }
  24. }

19.2.3 通过委托执行不安全的代码

本例用汇编代码判断处理器ID,如果程序在Windows上运行就打印处理器ID,这或许是用C#所能做到的最“不安全”的事情。这个示例首先获取内存块指针,然后用机器码字节填充它,最后让委托引用新代码并执行委托。

  1. using System.ComponentModel;
  2. using System.Runtime.InteropServices;
  3. using System.Text;
  4. namespace ConsoleApp1;
  5. public static class Program
  6. {
  7. private unsafe delegate void MethodInvoker(byte* buffer);
  8. public static unsafe int Main()
  9. {
  10. if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
  11. {
  12. byte[] codeBytes =
  13. {
  14. 0x49, 0x89, 0xd8, // mov %rbx,%r8
  15. 0x49, 0x89, 0xc9, // mov %rcx,%r9
  16. 0x48, 0x31, 0xc0, // xor %rax,%rax
  17. 0x0f, 0xa2, // cpuid
  18. 0x4c, 0x89, 0xc8, // mov %r9,%rax
  19. 0x89, 0x18, // mov %ebx,0x0(%rax)
  20. 0x89, 0x50, 0x04, // mov %edx,0x4(%rax)
  21. 0x89, 0x48, 0x08, // mov %ecx,0x8(%rax)
  22. 0x4c, 0x89, 0xc3, // mov %r8,%rbx
  23. 0xc3 // retq
  24. };
  25. byte[] buffer = new byte[12];
  26. using (var codeBytesPtr =
  27. new VirtualMemoryPtr(codeBytes.Length))
  28. {
  29. Marshal.Copy(
  30. codeBytes, 0,
  31. codeBytesPtr, codeBytes.Length);
  32. var method = Marshal.GetDelegateForFunctionPointer<MethodInvoker>(codeBytesPtr);
  33. fixed (byte* newBuffer = &buffer[0])
  34. {
  35. method(newBuffer);
  36. }
  37. }
  38. Console.Write("Processor Id: ");
  39. Console.WriteLine(Encoding.ASCII.GetChars(buffer));
  40. }
  41. else
  42. {
  43. Console.WriteLine("This sample is only valid for Windows");
  44. }
  45. return 0;
  46. }
  47. }
  48. public class VirtualMemoryPtr : SafeHandle
  49. {
  50. public VirtualMemoryPtr(int memorySize) :
  51. base(IntPtr.Zero, true)
  52. {
  53. _processHandle =
  54. VirtualMemoryManager.GetCurrentProcessHandle();
  55. _memorySize = (IntPtr)memorySize;
  56. _allocatedPointer =
  57. VirtualMemoryManager.AllocExecutionBlock(
  58. memorySize, _processHandle);
  59. _isDisposed = false;
  60. }
  61. private readonly IntPtr _allocatedPointer;
  62. private readonly IntPtr _processHandle;
  63. private readonly IntPtr _memorySize;
  64. private bool _isDisposed;
  65. public static implicit operator IntPtr(
  66. VirtualMemoryPtr virtualMemoryPointer)
  67. {
  68. return virtualMemoryPointer._allocatedPointer;
  69. }
  70. // SafeHandle abstract member
  71. public override bool IsInvalid => _isDisposed;
  72. // SafeHandle abstract member
  73. protected override bool ReleaseHandle()
  74. {
  75. return _isDisposed = VirtualMemoryManager.VirtualFreeEx(_processHandle, _allocatedPointer, _memorySize);
  76. }
  77. }
  78. internal static class VirtualMemoryManager
  79. {
  80. [DllImport("kernel32.dll", SetLastError = true)]
  81. private static extern IntPtr VirtualAllocEx(
  82. IntPtr hProcess,
  83. IntPtr lpAddress,
  84. IntPtr dwSize,
  85. AllocationType flAllocationType,
  86. uint flProtect);
  87. [DllImport("kernel32.dll", SetLastError = true)]
  88. static extern bool VirtualProtectEx(
  89. IntPtr hProcess, IntPtr lpAddress,
  90. IntPtr dwSize, uint flNewProtect,
  91. ref uint lpflOldProtect);
  92. [DllImport("kernel32.dll", EntryPoint = "GetCurrentProcess")]
  93. internal static extern IntPtr GetCurrentProcessHandle();
  94. [DllImport("kernel32.dll", SetLastError = true)]
  95. static extern bool VirtualFreeEx(
  96. IntPtr hProcess, IntPtr lpAddress,
  97. IntPtr dwSize, IntPtr dwFreeType);
  98. public static bool VirtualFreeEx(
  99. IntPtr hProcess, IntPtr lpAddress,
  100. IntPtr dwSize)
  101. {
  102. var result = VirtualFreeEx(
  103. hProcess, lpAddress, dwSize,
  104. (IntPtr)MemoryFreeType.Decommit);
  105. if (!result)
  106. {
  107. throw new Win32Exception();
  108. }
  109. return result;
  110. }
  111. public static bool VirtualFreeEx(
  112. IntPtr lpAddress, IntPtr dwSize)
  113. {
  114. return VirtualFreeEx(
  115. GetCurrentProcessHandle(), lpAddress, dwSize);
  116. }
  117. /// <summary>
  118. /// The type of memory allocation. This parameter must
  119. /// contain one of the following values.
  120. /// </summary>
  121. [Flags]
  122. private enum AllocationType : uint
  123. {
  124. /// <summary>
  125. /// Allocates physical storage in memory or in the
  126. /// paging file on disk for the specified reserved
  127. /// memory pages. The function initializes the memory
  128. /// to zero.
  129. /// </summary>
  130. Commit = 0x1000,
  131. /// <summary>
  132. /// Reserves a range of the process's virtual address
  133. /// space without allocating any actual physical
  134. /// storage in memory or in the paging file on disk.
  135. /// </summary>
  136. Reserve = 0x2000,
  137. /// <summary>
  138. /// Indicates that data in the memory range specified by
  139. /// lpAddress and dwSize is no longer of interest. The
  140. /// pages should not be read from or written to the
  141. /// paging file. However, the memory block will be used
  142. /// again later, so it should not be decommitted. This
  143. /// value cannot be used with any other value.
  144. /// </summary>
  145. Reset = 0x80000,
  146. /// <summary>
  147. /// Allocates physical memory with read-write access.
  148. /// This value is solely for use with Address Windowing
  149. /// Extensions (AWE) memory.
  150. /// </summary>
  151. Physical = 0x400000,
  152. /// <summary>
  153. /// Allocates memory at the highest possible address.
  154. /// </summary>
  155. TopDown = 0x100000,
  156. }
  157. /// <summary>
  158. /// The memory protection for the region of pages to be
  159. /// allocated.
  160. /// </summary>
  161. [Flags]
  162. private enum ProtectionOptions : uint
  163. {
  164. /// <summary>
  165. /// Enables execute access to the committed region of
  166. /// pages. An attempt to read or write to the committed
  167. /// region results in an access violation.
  168. /// </summary>
  169. Execute = 0x10,
  170. /// <summary>
  171. /// Enables execute and read access to the committed
  172. /// region of pages. An attempt to write to the
  173. /// committed region results in an access violation.
  174. /// </summary>
  175. PageExecuteRead = 0x20,
  176. /// <summary>
  177. /// Enables execute, read, and write access to the
  178. /// committed region of pages.
  179. /// </summary>
  180. PageExecuteReadWrite = 0x40,
  181. // ...
  182. }
  183. /// <summary>
  184. /// The type of free operation
  185. /// </summary>
  186. [Flags]
  187. private enum MemoryFreeType : uint
  188. {
  189. /// <summary>
  190. /// Decommits the specified region of committed pages.
  191. /// After the operation, the pages are in the reserved
  192. /// state.
  193. /// </summary>
  194. Decommit = 0x4000,
  195. /// <summary>
  196. /// Releases the specified region of pages. After this
  197. /// operation, the pages are in the free state.
  198. /// </summary>
  199. Release = 0x8000
  200. }
  201. public static IntPtr AllocExecutionBlock(
  202. int size, IntPtr hProcess)
  203. {
  204. var codeBytesPtr = VirtualAllocEx(
  205. hProcess, IntPtr.Zero,
  206. (IntPtr)size,
  207. AllocationType.Reserve | AllocationType.Commit,
  208. (uint)ProtectionOptions.PageExecuteReadWrite);
  209. if (codeBytesPtr == IntPtr.Zero)
  210. {
  211. throw new Win32Exception();
  212. }
  213. uint lpflOldProtect = 0;
  214. if (!VirtualProtectEx(
  215. hProcess, codeBytesPtr,
  216. (IntPtr)size,
  217. (uint)ProtectionOptions.PageExecuteReadWrite,
  218. ref lpflOldProtect))
  219. {
  220. throw new Win32Exception();
  221. }
  222. return codeBytesPtr;
  223. }
  224. public static IntPtr AllocExecutionBlock(int size)
  225. {
  226. return AllocExecutionBlock(
  227. size, GetCurrentProcessHandle());
  228. }
  229. }

20. LINQ

dotnet提供了一套编程API,该API被称为 语言集成查询(Language Integrated Query),简称为LINQ,该API提供了一系列扩展方法,任何类型在实现了IEnumerable<T>接口后,都可使用LINQ。

LINQ有许多方法,这里不再逐个介绍。需要注意的是,LINQ具有延迟执行机制,例如:

  1. namespace ConsoleApp1;
  2. public static class Program
  3. {
  4. private static void Main(string[] args)
  5. {
  6. var list = Init();
  7. // 该条语句会延迟执行
  8. var result = list.Where(patent =>
  9. {
  10. var isSelected = patent.YearOfPublication < 1900;
  11. if (isSelected) Console.WriteLine($"\t {patent.Name} ({patent.YearOfPublication})");
  12. return isSelected;
  13. });
  14. // 会首先执行这条语句
  15. Console.WriteLine("Patents prior to the 1900s are: ");
  16. // 如果没有foreach调用result,result对应的lambda表达式不会执行
  17. foreach (var patent in result)
  18. {
  19. Console.WriteLine(patent.Name);
  20. }
  21. }
  22. private static List<Patent> Init()
  23. {
  24. var list = new List<Patent>()
  25. {
  26. new Patent() { Name = "Phonograph", YearOfPublication = 1877 },
  27. new Patent() { Name = "KnitScope", YearOfPublication = 1837 },
  28. new Patent() { Name = "Electrical Telegraph", YearOfPublication = 1922 }
  29. };
  30. return list;
  31. }
  32. }
  33. public readonly struct Patent
  34. {
  35. public string Name { get; init; }
  36. public int YearOfPublication { get; init; }
  37. }

输出结果如下:

  1. Patents prior to the 1900s are:
  2. Phonograph (1877)
  3. Phonograph
  4. KnitScope (1837)
  5. KnitScope

从结果来看,Where中的Lambda表达式延迟执行了,也就是说lambda表达式在声明时不执行,在调用时才会执行。

读书笔记-C#8.0本质论-07的更多相关文章

  1. 《C#本质论》读书笔记(18)多线程处理

    .NET Framework 4.0 看(本质论第3版) .NET Framework 4.5 看(本质论第4版) .NET 4.0为多线程引入了两组新API:TPL(Task Parallel Li ...

  2. 机器学习实战 - 读书笔记(07) - 利用AdaBoost元算法提高分类性能

    前言 最近在看Peter Harrington写的"机器学习实战",这是我的学习笔记,这次是第7章 - 利用AdaBoost元算法提高分类性能. 核心思想 在使用某个特定的算法是, ...

  3. 强化学习读书笔记 - 06~07 - 时序差分学习(Temporal-Difference Learning)

    强化学习读书笔记 - 06~07 - 时序差分学习(Temporal-Difference Learning) 学习笔记: Reinforcement Learning: An Introductio ...

  4. 【英语魔法俱乐部——读书笔记】 0 序&前沿

    [英语魔法俱乐部——读书笔记] 0 序&前沿   0.1 以编者自身的经历引入“不求甚解,以看完为目的”阅读方式,即所谓“泛读”.找到适合自己的文章开始“由浅入深”的阅读,在阅读过程中就会见到 ...

  5. 《玩转Django2.0》读书笔记-探究视图

    <玩转Django2.0>读书笔记-探究视图 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 视图(View)是Django的MTV架构模式的V部分,主要负责处理用户请求 ...

  6. 《玩转Django2.0》读书笔记-编写URL规则

    <玩转Django2.0>读书笔记-编写URL规则 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. URL(Uniform Resource Locator,统一资源定位 ...

  7. 《玩转Django2.0》读书笔记-Django配置信息

    <玩转Django2.0>读书笔记-Django配置信息 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 项目配置是根据实际开发需求从而对整个Web框架编写相应配置信息. ...

  8. 《玩转Django2.0》读书笔记-Django建站基础

    <玩转Django2.0>读书笔记-Django建站基础 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.网站的定义及组成 网站(Website)是指在因特网上根据一 ...

  9. 《C# 6.0 本质论》 阅读笔记

    <C# 6.0 本质论> 阅读笔记   阅读笔记不是讲述这本书的内容,只是提取了其中一部分我认为比较重要或者还没有掌握的知识,所以如果有错误或者模糊之处,请指正,谢谢! 对于C# 6.0才 ...

  10. 《鸟哥的Linux私房菜》读书笔记--第0章 计算机概论 硬件部分

    一个下午看了不少硬件层面的知识,看得太多太快容易忘记.于是在博客上写下读书笔记. 有关硬件 个人计算机架构&接口设备 主板芯片组为“南北桥”的统称,南北桥用于控制所有组件之间的通信. 北桥连接 ...

随机推荐

  1. 【YashanDB知识库】stmt未close,导致YAS-00103 no free block in sql main pool part 0报错分析

    问题现象 问题单:YAS-00103 no free block in sql main pool part 0,YAS-00105 out of memory to allocate hash ta ...

  2. SQL Server – Concurrency 并发控制

    前言 以前写过相关的, 但这篇主要讲一下概念. 帮助理解 Entity Framework with MySQL 学习笔记一(乐观并发) Asp.net core 学习笔记 ( ef core tra ...

  3. Azure 入门系列 (第五篇 Azure Storage)

    本系列 这个系列会介绍从 0 到 1 搭建一个 Web Application 的 Server. 间中还会带上一些真实开发常用的功能. 一共 6 篇 1. Virtual Machine (VM) ...

  4. LeetCode 1388. Pizza With 3n Slices(3n 块披萨)(DP)

    给你一个披萨,它由 3n 块不同大小的部分组成,现在你和你的朋友们需要按照如下规则来分披萨: 你挑选 任意 一块披萨.Alice 将会挑选你所选择的披萨逆时针方向的下一块披萨.Bob 将会挑选你所选择 ...

  5. Flutter将视频或图文分享到抖音

    如何在 Flutter 中分享视频到抖音 话不多说,先上效果: 原理 发布内容至抖音 H5 场景_移动/网站应用_抖音开放平台 (open-douyin.com) 本教程没有接入抖音原生 SDK 以及 ...

  6. ubuntu16.04安装SSH服务

    第一步:查看SSH服务是不是安装 sudo ps -e |grep ssh 如果啥都没看到,恭喜你,你没装ssh.那就开始下面的步骤. 第二步:安装SSH sudo apt-get install o ...

  7. uniapp电子签名盖章实现详解

    项目开发中用到了电子签名.签好名的图片需要手动实现横竖屏旋转.并将绘制的签名图片放到pdf转换后的base64的图片上,可以手动拖动签名到合适的位置,最后合成签名和合同图片并导出.和以往一样,先发一下 ...

  8. jwt实现登录 和 接口实现动态权限

    [Authorize]   ====   using Microsoft.AspNetCore.Authorization; 登录的 DTO namespace login; public class ...

  9. 1. java + react 实现 HRM

    1. 云服务的三种方式 1.1 IAAS 基础设施即服务 ,只会提供基础的设施,eg:服务器,网络等 : 1.2 PAAS 平台即服务 ,提供平台,可以把自己写好的代码部署到平台上 : 1.3 SAA ...

  10. 我们如何在 vue 应用我们的权限

    权限可以分为用户权限和按钮权限: 用户权限,让不同的用户拥有不同的路由映射 ,具体实现方法: 1. 初始化路由实例的时候,只把静态路由规则注入 ,不要注入动态路由规则 : 2. 用户登录的时候,根据返 ...