温故知新,CSharp遇见IntPtr,通过System.IntPtr来操作句柄(Handle)和指针
句柄(Handle)缘来
.NET提供了一个结构体System.IntPtr
专门用来代表句柄或指针。
句柄是对象的标识符,当调用这些API创建对象时,它们并不直接返回指向对象的指针,而是会返回一个32位或64位的整数值,这个在进程或系统范围内唯一的整数值就是句柄(Handle
),随后程序再次访问对象,或者删除对象,都将句柄作为Windows API的参数来间接对这些对象进行操作。
句柄指向就是指向文件开头,在windows系统所有的东西都是文件,对象也是文件。所以句柄和指针是一样的意思。句柄是面向对象的指针的称呼。
指针是对存储区域的引用,该区域包含您感兴趣的一些数据。指针是面向过程编程的称呼。
IntPtr
类是IntPointer
的缩写。
C#中用来取代指针,也可以说对指针进行封装,指向托管内存。
它也不常用,因为C#项目中指针都被弃用了,那指针的封装—句柄自然也被弃用了。
但总有特殊的地方会用到指针,比如调用C++动态库之类的;所以微软贴心的为我们做了个句柄,毕竟指针用起来太难受了。
句柄的定义
句柄是一个结构体,简单的来说,它是指针的一个封装,是C#中指针的替代者,下面我们看下句柄的定义。
namespace System
{
//
// 摘要:
// A platform-specific type that is used to represent a pointer or a handle.
public readonly struct IntPtr : IComparable, IComparable<nint>, IEquatable<nint>, ISpanFormattable, IFormattable, ISerializable
{
//
// 摘要:
// A read-only field that represents a pointer or handle that has been initialized
// to zero.
public static readonly IntPtr Zero;
//
// 摘要:
// Initializes a new instance of System.IntPtr using the specified 32-bit pointer
// or handle.
//
// 参数:
// value:
// A pointer or handle contained in a 32-bit signed integer.
public IntPtr(int value);
//
// 摘要:
// Initializes a new instance of System.IntPtr using the specified 64-bit pointer.
//
// 参数:
// value:
// A pointer or handle contained in a 64-bit signed integer.
//
// 异常:
// T:System.OverflowException:
// On a 32-bit platform, value is too large or too small to represent as an System.IntPtr.
public IntPtr(long value);
//
// 摘要:
// Initializes a new instance of System.IntPtr using the specified pointer to an
// unspecified type.
//
// 参数:
// value:
// A pointer to an unspecified type.
[CLSCompliant(false)]
public IntPtr(void* value);
//
// 摘要:
// Represents the largest possible value of System.IntPtr.
public static IntPtr MaxValue { get; }
//
// 摘要:
// Represents the smallest possible value of System.IntPtr.
public static IntPtr MinValue { get; }
//
// 摘要:
// Gets the size of this instance.
//
// 返回结果:
// The size of a pointer or handle in this process, measured in bytes. The value
// of this property is 4 in a 32-bit process, and 8 in a 64-bit process. You can
// define the process type by setting the /platform switch when you compile your
// code with the C# and Visual Basic compilers.
public static int Size { get; }
//
// 摘要:
// Adds an offset to the value of a pointer.
//
// 参数:
// pointer:
// The pointer to add the offset to.
//
// offset:
// The offset to add.
//
// 返回结果:
// A new pointer that reflects the addition of offset to pointer.
public static IntPtr Add(IntPtr pointer, int offset);
//
// 摘要:
// Converts the string representation of a number in a specified style and culture-specific
// format to its signed native integer equivalent.
//
// 参数:
// s:
// A string containing a number to convert.
//
// style:
// A bitwise combination of the enumeration values that indicates the style elements
// that can be present in s.
//
// provider:
// An object that supplies culture-specific formatting information about s.
//
// 返回结果:
// A signed native integer equivalent to the number contained in s.
//
// 异常:
// T:System.ArgumentNullException:
// s is null.
//
// T:System.ArgumentException:
// style is not a System.Globalization.NumberStyles value or style is not a combination
// of System.Globalization.NumberStyles.AllowHexSpecifier and System.Globalization.NumberStyles.HexNumber
// values.
//
// T:System.FormatException:
// s is not in the correct format.
//
// T:System.OverflowException:
// s represents a number less than System.IntPtr.MinValue or greater than System.IntPtr.MaxValue.
public static IntPtr Parse(string s, NumberStyles style, IFormatProvider? provider);
//
// 摘要:
// Converts the read-only span of characters representation of a number in a specified
// style and culture-specific format to its signed native integer equivalent.
//
// 参数:
// s:
// A read-only span of characters containing a number to convert.
//
// style:
// A bitwise combination of the enumeration values that indicates the style elements
// that can be present in s.
//
// provider:
// An object that supplies culture-specific formatting information about s.
//
// 返回结果:
// A signed native integer equivalent to the number contained in s.
//
// 异常:
// T:System.ArgumentException:
// style is not a System.Globalization.NumberStyles value or style is not a combination
// of System.Globalization.NumberStyles.AllowHexSpecifier and System.Globalization.NumberStyles.HexNumber
// values.
//
// T:System.FormatException:
// s is not in the correct format.
//
// T:System.OverflowException:
// s represents a number less than System.IntPtr.MinValue or greater than System.IntPtr.MaxValue.
public static IntPtr Parse(ReadOnlySpan<char> s, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null);
//
// 摘要:
// Converts the string representation of a number in a specified culture-specific
// format to its signed native integer equivalent.
//
// 参数:
// s:
// A string containing a number to convert.
//
// provider:
// An object that supplies culture-specific formatting information about s.
//
// 返回结果:
// A signed native integer equivalent to the number contained in s.
//
// 异常:
// T:System.ArgumentNullException:
// s is null.
//
// T:System.FormatException:
// s is not in the correct format.
//
// T:System.OverflowException:
// s represents a number less than System.IntPtr.MinValue or greater than System.IntPtr.MaxValue.
public static IntPtr Parse(string s, IFormatProvider? provider);
//
// 摘要:
// Converts the string representation of a number in a specified style to its signed
// native integer equivalent.
//
// 参数:
// s:
// A string containing a number to convert.
//
// style:
// A bitwise combination of the enumeration values that indicates the style elements
// that can be present in s.
//
// 返回结果:
// A signed native integer equivalent to the number contained in s.
//
// 异常:
// T:System.ArgumentNullException:
// s is null.
//
// T:System.ArgumentException:
// style is not a System.Globalization.NumberStyles value or style is not a combination
// of System.Globalization.NumberStyles.AllowHexSpecifier and System.Globalization.NumberStyles.HexNumber
// values.
//
// T:System.FormatException:
// s is not in the correct format.
//
// T:System.OverflowException:
// s represents a number less than System.IntPtr.MinValue or greater than System.IntPtr.MaxValue.
public static IntPtr Parse(string s, NumberStyles style);
//
// 摘要:
// Converts the string representation of a number to its signed native integer equivalent.
//
// 参数:
// s:
// A string containing a number to convert.
//
// 返回结果:
// A signed native integer equivalent to the number contained in s.
//
// 异常:
// T:System.ArgumentNullException:
// s is null.
//
// T:System.FormatException:
// s is not in the correct format.
//
// T:System.OverflowException:
// s represents a number less than System.IntPtr.MinValue or greater than System.IntPtr.MaxValue.
public static IntPtr Parse(string s);
//
// 摘要:
// Subtracts an offset from the value of a pointer.
//
// 参数:
// pointer:
// The pointer to subtract the offset from.
//
// offset:
// The offset to subtract.
//
// 返回结果:
// A new pointer that reflects the subtraction of offset from pointer.
public static IntPtr Subtract(IntPtr pointer, int offset);
//
// 摘要:
// Converts the read-only span of characters representation of a number in a specified
// style and culture-specific format to its signed native integer equivalent. A
// return value indicates whether the conversion succeeded.
//
// 参数:
// s:
// A read-only span of characters containing a number to convert. The string is
// interpreted using the style specified by style.
//
// style:
// A bitwise combination of enumeration values that indicates the style elements
// that can be present in s.
//
// provider:
// An object that supplies culture-specific formatting information about s.
//
// result:
// When this method returns, contains the signed native integer value equivalent
// of the number contained in s, if the conversion succeeded, or zero if the conversion
// failed. The conversion fails if the s parameter is empty, is not of the correct
// format, or represents a number less than System.IntPtr.MinValue or greater than
// System.IntPtr.MaxValue. This parameter is passed uninitialized; any value originally
// supplied in result will be overwritten.
//
// 返回结果:
// true if s was converted successfully; otherwise, false.
public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, out IntPtr result);
//
// 摘要:
// Converts the read-only span of characters representation of a number to its signed
// native integer equivalent. A return value indicates whether the conversion succeeded.
//
// 参数:
// s:
// A read-only span of characters containing a number to convert.
//
// result:
// When this method returns, contains the signed native integer equivalent of the
// number contained in s, if the conversion succeeded, or zero if the conversion
// failed. The conversion fails if the s parameter is empty, is not of the correct
// format, or represents a number less than System.IntPtr.MinValue or greater than
// System.IntPtr.MaxValue. This parameter is passed uninitialized; any value originally
// supplied in result will be overwritten.
//
// 返回结果:
// true if s was converted successfully; otherwise, false.
public static bool TryParse(ReadOnlySpan<char> s, out IntPtr result);
//
// 摘要:
// Converts the string representation of a number to its signed native integer equivalent.
// A return value indicates whether the conversion succeeded.
//
// 参数:
// s:
// A string containing a number to convert.
//
// result:
// When this method returns, contains the signed native integer value equivalent
// of the number contained in s, if the conversion succeeded, or zero if the conversion
// failed. The conversion fails if the s parameter is null or empty, is not of the
// correct format, or represents a number less than System.IntPtr.MinValue or greater
// than System.IntPtr.MaxValue. This parameter is passed uninitialized; any value
// originally supplied in result will be overwritten.
//
// 返回结果:
// true if s was converted successfully; otherwise, false.
public static bool TryParse([NotNullWhen(true)] string? s, out IntPtr result);
//
// 摘要:
// Converts the string representation of a number in a specified style and culture-specific
// format to its signed native integer equivalent. A return value indicates whether
// the conversion succeeded.
//
// 参数:
// s:
// A string containing a number to convert. The string is interpreted using the
// style specified by style.
//
// style:
// A bitwise combination of enumeration values that indicates the style elements
// that can be present in s.
//
// provider:
// An object that supplies culture-specific formatting information about s.
//
// result:
// When this method returns, contains the signed native integer value equivalent
// of the number contained in s, if the conversion succeeded, or zero if the conversion
// failed. The conversion fails if the s parameter is null or empty, is not of the
// correct format, or represents a number less than System.IntPtr.MinValue or greater
// than System.IntPtr.MaxValue. This parameter is passed uninitialized; any value
// originally supplied in result will be overwritten.
//
// 返回结果:
// true if s was converted successfully; otherwise, false.
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out IntPtr result);
//
// 摘要:
// Compares the current instance with another object of the same type and returns
// an integer that indicates whether the current instance precedes, follows, or
// occurs in the same position in the sort order as the other object.
//
// 参数:
// value:
// An object to compare, or null.
//
// 返回结果:
// A value that indicates the relative order of the objects being compared. The
// return value has these meanings:
// Value – Meaning
// Less than zero – This instance precedes obj in the sort order.
// Zero – This instance occurs in the same position in the sort order as obj.
// Greater than zero – This instance follows obj in the sort order.
public int CompareTo(object? value);
//
// 摘要:
// Compares the current instance with another object of the same type and returns
// an integer that indicates whether the current instance precedes, follows, or
// occurs in the same position in the sort order as the other object.
//
// 参数:
// value:
// A signed native integer to compare.
//
// 返回结果:
// A value that indicates the relative order of the objects being compared. The
// return value has these meanings:
// Value – Meaning
// Less than zero – This instance precedes other in the sort order.
// Zero – This instance occurs in the same position in the sort order as other.
// Greater than zero – This instance follows other in the sort order.
public int CompareTo(IntPtr value);
//
// 摘要:
// Indicates whether the current object is equal to another object of the same type.
//
// 参数:
// other:
// An object to compare with this object.
//
// 返回结果:
// true if the current object is equal to other; otherwise, false.
public bool Equals(IntPtr other);
//
// 摘要:
// Returns a value indicating whether this instance is equal to a specified object.
//
// 参数:
// obj:
// An object to compare with this instance or null.
//
// 返回结果:
// true if obj is an instance of System.IntPtr and equals the value of this instance;
// otherwise, false.
public override bool Equals([NotNullWhen(true)] object? obj);
//
// 摘要:
// Returns the hash code for this instance.
//
// 返回结果:
// A 32-bit signed integer hash code.
public override int GetHashCode();
//
// 摘要:
// Converts the value of this instance to a 32-bit signed integer.
//
// 返回结果:
// A 32-bit signed integer equal to the value of this instance.
//
// 异常:
// T:System.OverflowException:
// On a 64-bit platform, the value of this instance is too large or too small to
// represent as a 32-bit signed integer.
public int ToInt32();
//
// 摘要:
// Converts the value of this instance to a 64-bit signed integer.
//
// 返回结果:
// A 64-bit signed integer equal to the value of this instance.
public long ToInt64();
//
// 摘要:
// Converts the value of this instance to a pointer to an unspecified type.
//
// 返回结果:
// A pointer to System.Void; that is, a pointer to memory containing data of an
// unspecified type.
[CLSCompliant(false)]
public void* ToPointer();
//
// 摘要:
// Converts the numeric value of the current System.IntPtr object to its equivalent
// string representation.
//
// 返回结果:
// The string representation of the value of this instance.
public override string ToString();
//
// 摘要:
// Formats the value of the current instance using the specified format.
//
// 参数:
// format:
// The format to use. -or- A null reference (Nothing in Visual Basic) to use the
// default format defined for the type of the System.IFormattable implementation.
//
// provider:
// An object that supplies culture-specific formatting information.
//
// 返回结果:
// The value of the current instance in the specified format.
public string ToString(string? format, IFormatProvider? provider);
//
// 摘要:
// Converts the numeric value of the current System.IntPtr object to its equivalent
// string representation.
//
// 参数:
// format:
// A format specification that governs how the current System.IntPtr object is converted.
//
// 返回结果:
// The string representation of the value of the current System.IntPtr object.
//
// 异常:
// T:System.FormatException:
// format is invalid or not supported.
public string ToString(string? format);
//
// 摘要:
// Converts the numeric value of this instance to its equivalent string representation
// using the specified format and culture-specific format information.
//
// 参数:
// provider:
// An object that supplies culture-specific formatting information.
//
// 返回结果:
// The string representation of the value of this instance as specified by provider.
public string ToString(IFormatProvider? provider);
//
// 摘要:
// Tries to format the value of the current instance into the provided span of characters.
//
// 参数:
// destination:
// The span where this instance's value formatted as a span of characters should
// be written.
//
// charsWritten:
// When this method returns, contains the number of characters that were written
// in destination.
//
// format:
// The characters that represent a standard or custom format string that defines
// the acceptable format for destination.
//
// provider:
// An optional object that supplies culture-specific formatting information for
// destination.
//
// 返回结果:
// true if the formatting was successful; otherwise, false.
public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider? provider = null);
//
// 摘要:
// Adds an offset to the value of a pointer.
//
// 参数:
// pointer:
// The pointer to add the offset to.
//
// offset:
// The offset to add.
//
// 返回结果:
// A new pointer that reflects the addition of offset to pointer.
public static IntPtr operator +(IntPtr pointer, int offset);
//
// 摘要:
// Subtracts an offset from the value of a pointer.
//
// 参数:
// pointer:
// The pointer to subtract the offset from.
//
// offset:
// The offset to subtract.
//
// 返回结果:
// A new pointer that reflects the subtraction of offset from pointer.
public static IntPtr operator -(IntPtr pointer, int offset);
//
// 摘要:
// Determines whether two specified instances of System.IntPtr are equal.
//
// 参数:
// value1:
// The first pointer or handle to compare.
//
// value2:
// The second pointer or handle to compare.
//
// 返回结果:
// true if value1 equals value2; otherwise, false.
public static bool operator ==(IntPtr value1, IntPtr value2);
//
// 摘要:
// Determines whether two specified instances of System.IntPtr are not equal.
//
// 参数:
// value1:
// The first pointer or handle to compare.
//
// value2:
// The second pointer or handle to compare.
//
// 返回结果:
// true if value1 does not equal value2; otherwise, false.
public static bool operator !=(IntPtr value1, IntPtr value2);
//
// 摘要:
// Converts the value of a 64-bit signed integer to an System.IntPtr.
//
// 参数:
// value:
// A 64-bit signed integer.
//
// 返回结果:
// A new instance of System.IntPtr initialized to value.
//
// 异常:
// T:System.OverflowException:
// On a 32-bit platform, value is too large to represent as an System.IntPtr.
public static explicit operator IntPtr(long value);
//
// 摘要:
// Converts the value of the specified System.IntPtr to a 32-bit signed integer.
//
// 参数:
// value:
// The pointer or handle to convert.
//
// 返回结果:
// The contents of value.
//
// 异常:
// T:System.OverflowException:
// On a 64-bit platform, the value of value is too large to represent as a 32-bit
// signed integer.
public static explicit operator int(IntPtr value);
//
// 摘要:
// Converts the value of the specified System.IntPtr to a 64-bit signed integer.
//
// 参数:
// value:
// The pointer or handle to convert.
//
// 返回结果:
// The contents of value.
public static explicit operator long(IntPtr value);
//
// 摘要:
// Converts the value of a 32-bit signed integer to an System.IntPtr.
//
// 参数:
// value:
// A 32-bit signed integer.
//
// 返回结果:
// A new instance of System.IntPtr initialized to value.
public static explicit operator IntPtr(int value);
//
// 摘要:
// Converts the value of the specified System.IntPtr to a pointer to an unspecified
// type. This API is not CLS-compliant.
//
// 参数:
// value:
// The pointer or handle to convert.
//
// 返回结果:
// The contents of value.
[CLSCompliant(false)]
public static explicit operator void*(IntPtr value);
//
// 摘要:
// Converts the specified pointer to an unspecified type to an System.IntPtr. This
// API is not CLS-compliant.
//
// 参数:
// value:
// A pointer to an unspecified type.
//
// 返回结果:
// A new instance of System.IntPtr initialized to value.
[CLSCompliant(false)]
public static explicit operator IntPtr(void* value);
}
}
我们可以看到,句柄IntPtr
里包含创建指针,获取指针长度,设置偏移量等等方法,并且为了编码方便还声明了些强制转换的方法。
看了句柄的结构体定义,相信稍微有点基础的人已经明白了,在C#中,微软是希望抛弃指针而改用更优秀的IntPtr
代替它的。
但我们还会发现,句柄里还提供一个方法是ToPointer()
,它的返回类型是Void*
,也就是说,我们还是可以从句柄里拿到C++中的指针,既然,微软期望在C#中不要使用指针,那为什么还要提供这样的方法呢?
这是因为,在项目开发中总是会有极特殊的情况,比如,你有一段C++写的非常复杂、完美的函数,而将这个函数转换成C#又及其耗时,那么最简单省力的方法就是直接在C#里启用指针进行移植。
也就是说,C#支持指针,其实是为了体现它的兼容性,并不是提倡大家去使用指针。
使用要点
- C#中的
IntPtr
类型被称之为"平台特定的整数类型",用于本机资源,例如窗口句柄。 - 资源的大小取决于使用的硬件和操作系统,即此类型的实例在32位硬件和操作系统中将是32位,在64位硬件和操作系统中将是64位;但其大小总是足以包含系统的指针(因此也可以包含资源的名称)。
- 在调用API函数时,类似含有窗口句柄参数(HANDLE)的原型函数,应显示地声明为
IntPtr
类型。 IntPtr
类型对多线程操作是安全的。Int
和IntPtr
互转。
示例模型
struct ChatDemo
{
public double X;
public double Y;
public ChatDemo(double x, double y)
{
X = x;
Y = y;
}
}
Struct与IntPtr互相转换
public void ConvertStruct()
{
var chatDemo = new ChatDemo(1, 10);
// 通过使用指定的字节数,从进程的非托管内存中分配内存
IntPtr intPtr = Marshal.AllocHGlobal
(
// 内存中的所需字节数
cb: Marshal.SizeOf(typeof(ChatDemo))
);
// 将数据从托管对象封送到非托管内存块
Marshal.StructureToPtr
(
// 包含要封送的数据的托管对象。该对象必须是格式化类的结构或实例
structure: chatDemo,
// 指向非托管内存块的指针,必须在调用此方法之前分配该指针
ptr: intPtr,
// 如果在此方法复制该数据前在DestroyStructure(IntPtr,Type)参数上调用ptr,则为true。该块必须包含有效的数据。请注意,在内存块已包含数据时传递false可能会导致内存泄漏。
fDeleteOld: true
);
// 将数据从非托管内存块封送到托管对象
var chatDemoRes = (ChatDemo)Marshal.PtrToStructure
(
// 指向非托管内存块的指针
ptr: intPtr,
// 要创建的对象的类型。此对象必须表示格式化类或结构
structureType: typeof(ChatDemo)
);
}
Struct对象数组与IntPtr互相转换
public void ConvertStructs()
{
var chatDemos = new List<ChatDemo>() { new ChatDemo(1,3), new ChatDemo(2,10), new ChatDemo(4,200)};
// 将托管对象数组转成指针
var intPtr = MarshalMangagedStruct2Array(chatDemos.ToArray());
// 将指针转成托管对象数组
MarshalUnmananagedArray2Struct(intPtr, chatDemos.Count, out ChatDemo[] chatDemoRes);
}
/// <summary>
/// 将指针转成托管对象数组
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="unmanagedArray">非托管指针</param>
/// <param name="length">数据长度</param>
/// <param name="mangagedArray">托管对象数组</param>
public void MarshalUnmananagedArray2Struct<T>(IntPtr unmanagedArray, int length, out T[] mangagedArray)
{
if (length <= 0) throw new ArgumentException(nameof(length));
// 获取对象的空间大小
var size = Marshal.SizeOf(typeof(T));
mangagedArray = new T[length];
for (int i = 0; i < length; i++)
{
// 增加指针内存位置以便定位到下一个结构体
IntPtr nextStructureMemBlock = new IntPtr(unmanagedArray.ToInt64() + i * size);
mangagedArray[i] = Marshal.PtrToStructure<T>(nextStructureMemBlock);
}
}
/// <summary>
/// 将托管对象数组转成指针
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="mangagedArray"></param>
/// <returns></returns>
public IntPtr MarshalMangagedStruct2Array<T>(T[] mangagedArray) where T : struct
{
if (mangagedArray == null || mangagedArray.Length <= 0) throw new ArgumentException(nameof(mangagedArray));
// 获取对象的空间大小
int size = Marshal.SizeOf(typeof(T));
// 得到非托管内存的总长度
int allSize = size * mangagedArray.Length;
// 申请非托管内存空间
IntPtr ptr = Marshal.AllocHGlobal(allSize);
// 写入长度属性值
Marshal.WriteInt32(ptr, mangagedArray.Length);
// 写入数据
for (int i = 0; i < mangagedArray.Length; i++)
{
// 根据指针+偏移量来写入指定序号的对象数据
Marshal.StructureToPtr<T>(mangagedArray[i], ptr + (size * i), false);
}
return ptr;
}
C++与C#类型对照关系
C++ 数据类型 | C# 数据类型 |
---|---|
WORD |
ushort |
DWORD |
uint |
UCHAR |
int/byte |
UCHAR* |
string/InPtr |
unsigned char* |
[MarshalAs(UnmanagedType.LPArray)]byte[] / IntPtr |
char* |
string |
LPCTSTR |
string |
LPTSTR |
[MarshalAs(UnmanagedType.LPTStr)] string |
long |
int |
ulong |
uint |
Handle |
IntPtr |
HWND |
IntPtr |
void* |
IntPtr |
int |
int |
int* |
ref int |
*int |
IntPtr |
unsigned int |
uint |
COLORREF |
uint |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了