读书笔记-C#8.0本质论-07
19. 平台互相操作性和不安全代码
19.1 在托管平台调用非托管代码——P/Invoke模式
CLI通过P/Invoke功能对非托管DLL所导出的函数执行API调用。和类的所有普通方法一样,必须在类的上下文中声明目标API,但要为其添加extern修饰符,从而把它声明为外部函数。
using System;
using System.Runtime.InteropServices;
namespace ConsoleApp1;
internal static class Program
{
private static void Main()
{
IntPtr test = GetCurrentProcessHandle();
}
[DllImport("kernel32.dll", EntryPoint = "GetCurrentProcess")]
private static extern IntPtr GetCurrentProcessHandle();
}
extern方法永远没有主题,而且几乎总是静态方法。具体实现由对方法声明进行修饰的DllImport特性指定。该特性要求最起码提供定义了函数的DLL文件名称,“运行时”会根据方法名判断函数名。当然,也可以使用EntryPoint参数明确提供一个函数名。
GetCurrentProcessHandle没有参数,如果想要传递参数该怎么做呢?下面以VirtualAllocEx函数为例子,演示参数如何在托管代码和非托管代码之间传递。
Win32 API中的VirtualAllocEx函数定义如下:
LPVOID VirtualAllocEx(
[in] HANDLE hProcess,
[in, optional] LPVOID lpAddress,
[in] SIZE_T dwSize,
[in] DWORD flAllocationType,
[in] DWORD flProtect
);
其中:
- hProcess:进程的句柄。 该函数在此过程的虚拟地址空间中分配内存。
- lpAddress:指定要分配的页面区域的所需起始地址的指针。如果要保留内存,函数会将此地址舍入到分配粒度中最近的倍数。如果要提交已保留的内存,函数会将此地址向下舍入到最近的页面边界。如果 lpAddress 为 NULL,则该函数确定分配区域的位置。
- dwSize:要分配的内存区域的大小(以字节为单位)。如果 lpAddress 为 NULL,则该函数将 dwSize 舍入到下一页边界。如果 lpAddress 不是 NULL,则该函数将分配包含 lpAddress 到 lpAddress+dwSize 范围内一个或多个字节的所有页面。 例如,这意味着跨页边界的 2 字节范围会导致函数分配这两个页面。
- flAllocationType:指定内存分配的类型。
- flProtect:要分配的页面区域的内存保护。
简而言之,VirtualAllocEx函数分配 操作系统 特别为 代码执行或数据 指定的虚拟内存。由于在CLR看来,HANDLE、LPVOID等类型均是未定义类型,所有要调用它,托管代码需要为每种数据类型提供相应的定义,这种定义要符合CLR的要求,还要被Win32 API接受。示例如下:
using System;
using System.Runtime.InteropServices;
namespace ConsoleApp1;
internal static class Program
{
//SetLastError:是否调用线程的最后错误代码
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr VirtualAllocEx(
IntPtr hProcess,
IntPtr lpAddress,
IntPtr dwSize,
uint flAllocationType,
uint flProtect);
}
示例1:使用C++编写C#可以调用的API
title: 几个前提条件
1. 使用`__declspec(dllexport)`修饰
2. 使用extern "C"进行修饰
3. C++项目要编译为动态链接库
C++项目代码如下:
/* CalculateData.h */
#pragma once
#define CALCULATE_EXPORTS __declspec(dllexport)
extern "C" {
CALCULATE_EXPORTS int Add(int numberA, int numberB);
CALCULATE_EXPORTS int Subtract(int numberA, int numberB);
CALCULATE_EXPORTS int Multiplication(int numberA, int numberB);
CALCULATE_EXPORTS int Divided(int numberA, int numberB);
}
/* CalculateData.cpp */
#include "CalculateData.h"
#include <iostream>
CALCULATE_EXPORTS int Add(const int numberA, const int numberB) {
return numberA + numberB;
}
CALCULATE_EXPORTS int Subtract(const int numberA, const int numberB) {
return numberA - numberB;
}
CALCULATE_EXPORTS int Multiplication(const int numberA, const int numberB) {
return numberB * numberA;
}
CALCULATE_EXPORTS int Divided(int numberA, int numberB) {
if (numberB == 0) {
std::cout << "除数不能为空" << std::endl;
}
return numberA / numberB;
}
C#项目代码如下:
using System;
using System.Runtime.InteropServices;
namespace ConsoleApp1;
internal static class Program
{
[DllImport(@"../../../../x64/Debug/ConsoleApplication1.dll")]
private static extern int Add(int numberA, int numberB);
[DllImport(@"../../../../x64/Debug/ConsoleApplication1.dll")]
private static extern int Subtract(int numberA, int numberB);
[DllImport(@"../../../../x64/Debug/ConsoleApplication1.dll")]
private static extern int Multiplication(int numberA, int numberB);
[DllImport(@"../../../../x64/Debug/ConsoleApplication1.dll")]
private static extern int Divided(int numberA, int numberB);
private static void Main()
{
Console.WriteLine("Hello, World!");
Console.WriteLine($"{Add(2, 5)}, 2+5={2 + 5}");
Console.WriteLine($"{Multiplication(2, 5)}, 2*5={2 * 5}");
Console.WriteLine($"{Subtract(2, 5)}, 2-5={2 - 5}");
Console.WriteLine($"{Divided(10, 2)}, 10/2={10/2}");
}
}
19.1.1 为按引用传递的参数使用ref或者out而不是Intptr
许多时候,非托管代码会为按引用(passed by reference)的参数使用指针。在这种情况下,P/Invoke不要求在托管代码中将数据类型映射为指针(IntPtr)。相反,应将对应参数映射为ref或者out,具体取决于参数是否只用于输出。
例如:Win32 API 中的lpflOldProtect的数据类型是PDWORD,返回指针类型,该参数被约定为只用于输出(虽然在C语言中没办法强制要求一个参数是否只用于输出)。
internal static class Program
{
private const string DllName = "kernel32.dll";
[DllImport(DllName, SetLastError = true)]
private static extern bool VirtualProtectEx(
IntPtr hProcess,
IntPtr lpAddress,
IntPtr dwSize,
uint flNewProtect,
out uint lpflOldProtect); // 这里用了out参数
}
但在随后的描述中又指出,该参数(lpflOldProtect)必须指向一个有效值变量(有输入的情况出现),而不能是NULL,如果文档中出现了这种自相矛盾的说法,那么最好还是用ref而不是out。
internal static class Program
{
private const string DllName = "kernel32.dll";
[DllImport(DllName, SetLastError = true)]
private static extern bool VirtualProtectEx(
IntPtr hProcess,
IntPtr lpAddress,
IntPtr dwSize,
uint flNewProtect,
ref uint lpflOldProtect); // 这里用了ref参数
}
19.1.2 使用StructLayoutAttribute指定内存布局
有些API涉及的类型无对应的托管类型(如非托管代码中的自定义类型),调用这些API需要用托管代码重新声明类型。默认情况下托管代码可能优化了自定义类型的内存布局,所以内存布局可能不是顺序存储的,这在与非托管代码交互时可能会带来问题(无法逐位映射)。可以用StructLayoutAttribute强制托管代码使用顺序内存布局。
[StructLayout(LayoutKind.Sequential)]
internal struct ColorRef
{
public byte Red;
public byte Green;
public byte Blue;
private byte Unused;
public ColorRef(byte red, byte green, byte blue)
{
Red = red;
Green = green;
Blue = blue;
Unused = 0;
}
}
注: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错误数据来初始化它。示例如下:
internal class VirtualMemoryManager
{
private const string Kernel32Dll = "kernel32.dll";
[DllImport(Kernel32Dll, SetLastError = true)]
private static extern IntPtr GetCurrentProcessHandle();
[DllImport(Kernel32Dll, SetLastError = true)]
private static extern IntPtr VirtualAllocEx(
IntPtr hProcess,
IntPtr lpAddress,
IntPtr dwSize,
uint flAllocationType,
uint flProtect);
[DllImport(Kernel32Dll, SetLastError = true)]
private static extern bool VirtualProtectEx(
IntPtr hProcess,
IntPtr lpAddress,
IntPtr dwSize,
uint flNewProtect,
ref uint lpflOldProtect);
[Flags]
private enum AllocationType : uint
{
Commit = 0x1000,
Reserve = 0x2000,
Reset = 0x80000,
Physical = 0x400000,
TopDown = 0x100000
}
[Flags]
private enum ProtectionOptions : uint
{
Execute = 0x10,
PageExecuteRead = 0x20,
PageExecuteReadWrite = 0x40,
//...
}
[Flags]
private enum MemoryFreeType : uint
{
DeCommit = 0x4000,
Release = 0x0000
}
public static IntPtr AllocExecutionBlock(int size, IntPtr hProcess)
{
var codeBytesPtr = VirtualAllocEx(hProcess, IntPtr.Zero, (IntPtr)size,
(uint)(AllocationType.Reserve | AllocationType.Commit),
(uint)ProtectionOptions.PageExecuteReadWrite);
if (codeBytesPtr == IntPtr.Zero) throw new System.ComponentModel.Win32Exception();
var lpflOldProtect = 0U;
if (!VirtualProtectEx(hProcess, codeBytesPtr, (IntPtr)size, (uint)ProtectionOptions.PageExecuteReadWrite,
ref lpflOldProtect))
{
throw new System.ComponentModel.Win32Exception();
}
return codeBytesPtr;
}
public static IntPtr AllocExecutionBlock(int size)
{
return AllocExecutionBlock(size, GetCurrentProcessHandle());
}
}
19.1.4 使用SafeHandle
P/Invoke经常涉及用完需要清理的资源(例如:handle),但不能强迫开发者手动清理这些资源。正确的做法是在调用非托管API的托管代码中实现IDisposeable接口和终结器。C#提供了SafeHandle类,专门用于P/Invoke模式下的资源清理。SafeHandle类实现了IDisposable接口,无需在SafeHandle的派生类中在实现一遍IDisposable接口。以SafeHandle的派生类VirtualMemoryPtr为例:
internal class VirtualMemoryPtr : SafeHandle
{
public VirtualMemoryPtr(int memorySize) : base(IntPtr.Zero, true)
{
_ProcessHandle = VirtualMemoryManager.GetCurrentProcessHandle();
_MemorySize = (IntPtr)memorySize;
_AllocatedPointer = VirtualMemoryManager.AllocExecutionBlock(memorySize, _ProcessHandle);
_isDisposed = false;
}
public readonly IntPtr _AllocatedPointer;
private readonly IntPtr _ProcessHandle;
private readonly IntPtr _MemorySize;
private bool _isDisposed;
// 定义从IntPtr到VirtualMemoryPtr的隐式类型转换
public static implicit operator IntPtr(VirtualMemoryPtr virtualMemoryPtr)
{
return virtualMemoryPtr._AllocatedPointer;
}
public override bool IsInvalid => _isDisposed;
protected override bool ReleaseHandle()
{
if (_isDisposed) return true;
// 执行释放操作
// ...
_isDisposed = true;
GC.SuppressFinalize(this);
return true;
}
}
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 设计规范
title: 设计规范
1. 不要无谓重复现有的、已经能够执行非托管API功能的托管类
2. 要将外部方法声明为私有或内部
3. 要提供使用了托管约定的公共包装器方法,包括结构化异常处理、为特殊值使用枚举等
4. 要为非必须参数选择默认值来简化包装器方法
19.2 不安全的代码——指针和地址
19.2.1 fixed语句
如果数据是非托管变量类型,但是不固定,就用fixed语句固定可移动的变量,示例如下:
internal static class Program
{
private static void Main()
{
var bytes = new byte[24];
unsafe
{
fixed (byte* pData = &bytes[0]) // 等价于fixed(byte* pData=bytes)
{
//...
}
}
}
}
在fixed语句块中,赋值的数据不会再移动。fixed语句要求变量指针在其作用域内声明。这样可防止但数据不在固定时访问到fixed语句外部的变量,但最终是由开发者来保证不会将指针赋值给fixed语句之外也能生存的变量。不安全的代码不安全是有原因的,不要依赖“运行时”来确保安全性。C#允许fixed语句声明一个char*类型的指针,并将该指针指向string。
internal static class Program
{
private static void Main()
{
var chars = new string("aaa");
unsafe
{
fixed (char* pChars = chars)
{
//...
}
}
}
}
类似的,fixed语句允许其他任何可移动类型,只要它们能隐式转换为其他类型的指针即可。
取决于执行效率和时机,fixed语句可能导致内存堆中出现碎片,这是由于GC不能compact已固定的对象,所以如果要固定数据,宁可固定几个大块也不要固定许多小块,同时还应该尽量减少内存固定的时间,降低在数据固定期间发生垃圾回收的概率。为了减少堆中的碎片,对于小对象可以考虑在栈上分配,示例如下:
internal static class Program
{
private static void Main()
{
unsafe
{
byte* bytes = stackalloc byte[42];
}
}
}
在前面提到过,string类型一旦定义就不可更改,对string类型的修改其实是销毁原先的string类型实例,然后创建新的string类型实例。如果使用指针,没有什么是不能修改的,即使用了const关键字修饰也照改不误。
internal static class Program
{
private static void Main()
{
const string text = "s5280ft";
Console.WriteLine(text);
unsafe
{
fixed (char* pText = text)
{
var p = pText;
*++p = 'm';
*++p = 'i';
*++p = 'l';
*++p = 'e';
*++p = 's';
*++p = ' ';
}
}
Console.WriteLine(text);
}
}
19.2.2 使用指针访问被引用类型中的成员
C++中使用->
或者(*x).y
来访问被引用类型中的成员,C#也是如此。
internal static class Program
{
private struct Angle
{
public Angle(double hours, double minutes, double second)
{
Hours = hours;
Minutes = minutes;
Second = second;
}
public double Hours { get; }
public double Minutes { get; }
public double Second { get; }
}
private static void Main()
{
unsafe
{
var angle = new Angle(30, 18, 0);
Angle* pAngle = ∠
Console.WriteLine($"{pAngle->Hours}°{(*pAngle).Minutes}'{pAngle->Second}\"");
}
}
}
19.2.3 通过委托执行不安全的代码
本例用汇编代码判断处理器ID,如果程序在Windows上运行就打印处理器ID,这或许是用C#所能做到的最“不安全”的事情。这个示例首先获取内存块指针,然后用机器码字节填充它,最后让委托引用新代码并执行委托。
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text;
namespace ConsoleApp1;
public static class Program
{
private unsafe delegate void MethodInvoker(byte* buffer);
public static unsafe int Main()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
byte[] codeBytes =
{
0x49, 0x89, 0xd8, // mov %rbx,%r8
0x49, 0x89, 0xc9, // mov %rcx,%r9
0x48, 0x31, 0xc0, // xor %rax,%rax
0x0f, 0xa2, // cpuid
0x4c, 0x89, 0xc8, // mov %r9,%rax
0x89, 0x18, // mov %ebx,0x0(%rax)
0x89, 0x50, 0x04, // mov %edx,0x4(%rax)
0x89, 0x48, 0x08, // mov %ecx,0x8(%rax)
0x4c, 0x89, 0xc3, // mov %r8,%rbx
0xc3 // retq
};
byte[] buffer = new byte[12];
using (var codeBytesPtr =
new VirtualMemoryPtr(codeBytes.Length))
{
Marshal.Copy(
codeBytes, 0,
codeBytesPtr, codeBytes.Length);
var method = Marshal.GetDelegateForFunctionPointer<MethodInvoker>(codeBytesPtr);
fixed (byte* newBuffer = &buffer[0])
{
method(newBuffer);
}
}
Console.Write("Processor Id: ");
Console.WriteLine(Encoding.ASCII.GetChars(buffer));
}
else
{
Console.WriteLine("This sample is only valid for Windows");
}
return 0;
}
}
public class VirtualMemoryPtr : SafeHandle
{
public VirtualMemoryPtr(int memorySize) :
base(IntPtr.Zero, true)
{
_processHandle =
VirtualMemoryManager.GetCurrentProcessHandle();
_memorySize = (IntPtr)memorySize;
_allocatedPointer =
VirtualMemoryManager.AllocExecutionBlock(
memorySize, _processHandle);
_isDisposed = false;
}
private readonly IntPtr _allocatedPointer;
private readonly IntPtr _processHandle;
private readonly IntPtr _memorySize;
private bool _isDisposed;
public static implicit operator IntPtr(
VirtualMemoryPtr virtualMemoryPointer)
{
return virtualMemoryPointer._allocatedPointer;
}
// SafeHandle abstract member
public override bool IsInvalid => _isDisposed;
// SafeHandle abstract member
protected override bool ReleaseHandle()
{
return _isDisposed = VirtualMemoryManager.VirtualFreeEx(_processHandle, _allocatedPointer, _memorySize);
}
}
internal static class VirtualMemoryManager
{
[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)]
static extern bool VirtualProtectEx(
IntPtr hProcess, IntPtr lpAddress,
IntPtr dwSize, uint flNewProtect,
ref uint lpflOldProtect);
[DllImport("kernel32.dll", EntryPoint = "GetCurrentProcess")]
internal static extern IntPtr GetCurrentProcessHandle();
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool VirtualFreeEx(
IntPtr hProcess, IntPtr lpAddress,
IntPtr dwSize, IntPtr dwFreeType);
public static bool VirtualFreeEx(
IntPtr hProcess, IntPtr lpAddress,
IntPtr dwSize)
{
var result = VirtualFreeEx(
hProcess, lpAddress, dwSize,
(IntPtr)MemoryFreeType.Decommit);
if (!result)
{
throw new Win32Exception();
}
return result;
}
public static bool VirtualFreeEx(
IntPtr lpAddress, IntPtr dwSize)
{
return VirtualFreeEx(
GetCurrentProcessHandle(), lpAddress, dwSize);
}
/// <summary>
/// The type of memory allocation. This parameter must
/// contain one of the following values.
/// </summary>
[Flags]
private enum AllocationType : uint
{
/// <summary>
/// Allocates physical storage in memory or in the
/// paging file on disk for the specified reserved
/// memory pages. The function initializes the memory
/// to zero.
/// </summary>
Commit = 0x1000,
/// <summary>
/// Reserves a range of the process's virtual address
/// space without allocating any actual physical
/// storage in memory or in the paging file on disk.
/// </summary>
Reserve = 0x2000,
/// <summary>
/// Indicates that data in the memory range specified by
/// lpAddress and dwSize is no longer of interest. The
/// pages should not be read from or written to the
/// paging file. However, the memory block will be used
/// again later, so it should not be decommitted. This
/// value cannot be used with any other value.
/// </summary>
Reset = 0x80000,
/// <summary>
/// Allocates physical memory with read-write access.
/// This value is solely for use with Address Windowing
/// Extensions (AWE) memory.
/// </summary>
Physical = 0x400000,
/// <summary>
/// Allocates memory at the highest possible address.
/// </summary>
TopDown = 0x100000,
}
/// <summary>
/// The memory protection for the region of pages to be
/// allocated.
/// </summary>
[Flags]
private enum ProtectionOptions : uint
{
/// <summary>
/// Enables execute access to the committed region of
/// pages. An attempt to read or write to the committed
/// region results in an access violation.
/// </summary>
Execute = 0x10,
/// <summary>
/// Enables execute and read access to the committed
/// region of pages. An attempt to write to the
/// committed region results in an access violation.
/// </summary>
PageExecuteRead = 0x20,
/// <summary>
/// Enables execute, read, and write access to the
/// committed region of pages.
/// </summary>
PageExecuteReadWrite = 0x40,
// ...
}
/// <summary>
/// The type of free operation
/// </summary>
[Flags]
private enum MemoryFreeType : uint
{
/// <summary>
/// Decommits the specified region of committed pages.
/// After the operation, the pages are in the reserved
/// state.
/// </summary>
Decommit = 0x4000,
/// <summary>
/// Releases the specified region of pages. After this
/// operation, the pages are in the free state.
/// </summary>
Release = 0x8000
}
public static IntPtr AllocExecutionBlock(
int size, IntPtr hProcess)
{
var 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)
{
return AllocExecutionBlock(
size, GetCurrentProcessHandle());
}
}
20. LINQ
dotnet提供了一套编程API,该API被称为 语言集成查询(Language Integrated Query),简称为LINQ,该API提供了一系列扩展方法,任何类型在实现了IEnumerable<T>
接口后,都可使用LINQ。
LINQ有许多方法,这里不再逐个介绍。需要注意的是,LINQ具有延迟执行机制,例如:
namespace ConsoleApp1;
public static class Program
{
private static void Main(string[] args)
{
var list = Init();
// 该条语句会延迟执行
var result = list.Where(patent =>
{
var isSelected = patent.YearOfPublication < 1900;
if (isSelected) Console.WriteLine($"\t {patent.Name} ({patent.YearOfPublication})");
return isSelected;
});
// 会首先执行这条语句
Console.WriteLine("Patents prior to the 1900s are: ");
// 如果没有foreach调用result,result对应的lambda表达式不会执行
foreach (var patent in result)
{
Console.WriteLine(patent.Name);
}
}
private static List<Patent> Init()
{
var list = new List<Patent>()
{
new Patent() { Name = "Phonograph", YearOfPublication = 1877 },
new Patent() { Name = "KnitScope", YearOfPublication = 1837 },
new Patent() { Name = "Electrical Telegraph", YearOfPublication = 1922 }
};
return list;
}
}
public readonly struct Patent
{
public string Name { get; init; }
public int YearOfPublication { get; init; }
}
输出结果如下:
Patents prior to the 1900s are:
Phonograph (1877)
Phonograph
KnitScope (1837)
KnitScope
从结果来看,Where中的Lambda表达式延迟执行了,也就是说lambda表达式在声明时不执行,在调用时才会执行。
读书笔记-C#8.0本质论-07的更多相关文章
- 《C#本质论》读书笔记(18)多线程处理
.NET Framework 4.0 看(本质论第3版) .NET Framework 4.5 看(本质论第4版) .NET 4.0为多线程引入了两组新API:TPL(Task Parallel Li ...
- 机器学习实战 - 读书笔记(07) - 利用AdaBoost元算法提高分类性能
前言 最近在看Peter Harrington写的"机器学习实战",这是我的学习笔记,这次是第7章 - 利用AdaBoost元算法提高分类性能. 核心思想 在使用某个特定的算法是, ...
- 强化学习读书笔记 - 06~07 - 时序差分学习(Temporal-Difference Learning)
强化学习读书笔记 - 06~07 - 时序差分学习(Temporal-Difference Learning) 学习笔记: Reinforcement Learning: An Introductio ...
- 【英语魔法俱乐部——读书笔记】 0 序&前沿
[英语魔法俱乐部——读书笔记] 0 序&前沿 0.1 以编者自身的经历引入“不求甚解,以看完为目的”阅读方式,即所谓“泛读”.找到适合自己的文章开始“由浅入深”的阅读,在阅读过程中就会见到 ...
- 《玩转Django2.0》读书笔记-探究视图
<玩转Django2.0>读书笔记-探究视图 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 视图(View)是Django的MTV架构模式的V部分,主要负责处理用户请求 ...
- 《玩转Django2.0》读书笔记-编写URL规则
<玩转Django2.0>读书笔记-编写URL规则 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. URL(Uniform Resource Locator,统一资源定位 ...
- 《玩转Django2.0》读书笔记-Django配置信息
<玩转Django2.0>读书笔记-Django配置信息 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 项目配置是根据实际开发需求从而对整个Web框架编写相应配置信息. ...
- 《玩转Django2.0》读书笔记-Django建站基础
<玩转Django2.0>读书笔记-Django建站基础 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.网站的定义及组成 网站(Website)是指在因特网上根据一 ...
- 《C# 6.0 本质论》 阅读笔记
<C# 6.0 本质论> 阅读笔记 阅读笔记不是讲述这本书的内容,只是提取了其中一部分我认为比较重要或者还没有掌握的知识,所以如果有错误或者模糊之处,请指正,谢谢! 对于C# 6.0才 ...
- 《鸟哥的Linux私房菜》读书笔记--第0章 计算机概论 硬件部分
一个下午看了不少硬件层面的知识,看得太多太快容易忘记.于是在博客上写下读书笔记. 有关硬件 个人计算机架构&接口设备 主板芯片组为“南北桥”的统称,南北桥用于控制所有组件之间的通信. 北桥连接 ...
随机推荐
- 【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 ...
- SQL Server – Concurrency 并发控制
前言 以前写过相关的, 但这篇主要讲一下概念. 帮助理解 Entity Framework with MySQL 学习笔记一(乐观并发) Asp.net core 学习笔记 ( ef core tra ...
- Azure 入门系列 (第五篇 Azure Storage)
本系列 这个系列会介绍从 0 到 1 搭建一个 Web Application 的 Server. 间中还会带上一些真实开发常用的功能. 一共 6 篇 1. Virtual Machine (VM) ...
- LeetCode 1388. Pizza With 3n Slices(3n 块披萨)(DP)
给你一个披萨,它由 3n 块不同大小的部分组成,现在你和你的朋友们需要按照如下规则来分披萨: 你挑选 任意 一块披萨.Alice 将会挑选你所选择的披萨逆时针方向的下一块披萨.Bob 将会挑选你所选择 ...
- Flutter将视频或图文分享到抖音
如何在 Flutter 中分享视频到抖音 话不多说,先上效果: 原理 发布内容至抖音 H5 场景_移动/网站应用_抖音开放平台 (open-douyin.com) 本教程没有接入抖音原生 SDK 以及 ...
- ubuntu16.04安装SSH服务
第一步:查看SSH服务是不是安装 sudo ps -e |grep ssh 如果啥都没看到,恭喜你,你没装ssh.那就开始下面的步骤. 第二步:安装SSH sudo apt-get install o ...
- uniapp电子签名盖章实现详解
项目开发中用到了电子签名.签好名的图片需要手动实现横竖屏旋转.并将绘制的签名图片放到pdf转换后的base64的图片上,可以手动拖动签名到合适的位置,最后合成签名和合同图片并导出.和以往一样,先发一下 ...
- jwt实现登录 和 接口实现动态权限
[Authorize] ==== using Microsoft.AspNetCore.Authorization; 登录的 DTO namespace login; public class ...
- 1. java + react 实现 HRM
1. 云服务的三种方式 1.1 IAAS 基础设施即服务 ,只会提供基础的设施,eg:服务器,网络等 : 1.2 PAAS 平台即服务 ,提供平台,可以把自己写好的代码部署到平台上 : 1.3 SAA ...
- 我们如何在 vue 应用我们的权限
权限可以分为用户权限和按钮权限: 用户权限,让不同的用户拥有不同的路由映射 ,具体实现方法: 1. 初始化路由实例的时候,只把静态路由规则注入 ,不要注入动态路由规则 : 2. 用户登录的时候,根据返 ...