1.打开和关闭子键
注册表函数对注册表的操作是通过句柄来完成的,与文件操作一样,在对某个键下的子键或者键值项进行操作之前,需要先将这个键打开,然后使用键句柄来引用这个键,在操作完毕以后再将键句柄关闭。注册表的根键不需要打开,它们的句柄是固定不变的,要使用根键的时候只要把这些句柄直接拿来用就是了,Windows.inc中已经预定义了它们的数值:
HKEY_CLASSES_ROOT equ 80000000h
HKEY_CURRENT_USER equ 80000001h
HKEY_LOCAL_MACHINE equ 80000002h
HKEY_USERS equ 80000003h
HKEY_PERFORMANCE_DATA equ 80000004h
HKEY_CURRENT_CONFIG equ 80000005h
HKEY_DYN_DATA equ 80000006h
在程序中可以随时将这些助记符当做句柄来引用对应的根键。在程序结束的时候,不需要关闭这些根键句柄。
打开子键使用RegOpenKeyEx函数,在Win16中还存在一个RegOpenKey函数,虽然在Win32中这个函数仍然存在,但这仅是为了兼容的目的而设置的。API手册中推荐使用RegOpenKeyEx函数:
invoke RegOpenKeyEx,hKey,lpSubKey,dwOptions,samDesired,phkResult
函数的hKey参数指定父键句柄,lpSubKey指向一个字符串,用来表示要打开的子键名称,在系统中一个子键的全称是以“根键\第1层子键\第2层子键\第n层子键”类型的字符串表示的,中间用“\”隔开,字符串的最后以0字符结束,这和目录名的表示方法是很像的。
既然子键的全称是这样表示的,那么要打开一个子键的时候,下面的两种表示方法有什么不同呢?
(1)父键=HKEY_LOCAL_MACHINE,子键=Software\RegTest\MySubkey
(2)父键=HKEY_LOCAL_MACHINE\Software,子键=RegTest\MySubkey
答案是:这两种表示方法是完全相同的。在使用RegOpenKeyEx函数打开子键的时候,既可以将hKey参数设置为HKEY_LOCAL_MACHINE根键的句柄,并将lpSubKey参数指向“Software\RegTest\MySubkey”字符串;也可以将hKey参数设置为“HKEY_LOCAL_ MACHINE\Software”的句柄,将lpSubKey参数指向“RegTest\MySubkey”字符串,得到的结果是一样的。但是,使用第一种方法时,hKey参数可以直接使用助记符HKEY_LOCAL_ MACHINE来表示,因为根键的句柄是固定的,不需要打开;而使用第二种方法时,还需要先打开“HKEY_LOCAL_MACHINE\Software”键来获取它的句柄,所以具体使用哪种方法还要根据具体情况灵活选用。
函数的其他几个参数的含义如下。
● dwOptions参数——系统保留参数,必须指定为0。
● samDesired参数——子键的打开方式,根据使用子键的方式,可以设置为下列取值的组合,只有指定了打开的方式,才能在打开子键后进行相应的操作:
■ KEY_ALL_ACCESS——允许所有的存取。
■ KEY_CREATE_LINK——允许建立符号列表。
■ KEY_CREATE_SUB_KEY——允许建立下一层子键。
■ KEY_ENUMERATE_SUB_KEYS——允许枚举下一层子键。
■ KEY_EXECUTE——允许读操作。
■ KEY_QUERY_VALUE——允许查询键值数据。
■ KEY_READ—KEY_QUERY_VALUE,KEY_ENUMERATE_SUB_KEYS和KEY_ NOTIFY的组合。
■ KEY_SET_VALUE——允许修改或创建键值数据。
■ KEY_WRITE——KEY_SET_VALUE和KEY_CREATE_SUB_KEY的组合。
● phkResult参数——指向一个双字变量,函数在这里返回打开的子键句柄。
如果函数执行成功,返回值是ERROR_SUCCESS,并且函数在phkResult参数指向的变量中返回子键句柄。
当不再需要继续使用键句柄的时候,可以使用RegCloseKey函数将它关闭:
invoke RegCloseKey,hKey
如果句柄被成功关闭,函数返回ERROR_SUCCESS。
2.创建和删除子键
创建一个子键可以使用RegCreateKeyEx函数:
invoke RegCreateKeyEx,hKey,lpSubKey,Reserved,lpClass,dwOptions,\
samDesired,lpSecurityAttributes,phkResult,lpdwDisposition
函数中与RegOpenKeyEx函数中同名参数的含义和用法是相同的,hKey也是用来指定父键句柄,lpSubKey指向要创建的子键名称字符串,samDesired参数指明子键建立后的操作方式,phkResult指向用来返回键句柄的双字变量。
其余一些参数的含义如下。
● Reserved参数——保留参数,必须设置为0。
● lpClass参数——为创建的子键定义一个类名,这个参数一般设置为NULL。
● dwOptions参数——创建子键时的选项,它可以是以下取值之一:
■ REG_OPTION_NON_VOLATILE——默认值,子键被创建到注册表文件中。
■ REG_OPTION_VOLATILE——创建易失性的子键,子键被保存在内存中,当系统重新启动的时候,子键消失。这个选项仅对Windows NT系统有效,在9x系统中被忽略。
● lpSecurityAttributes参数——指向一个SECURITY_ATTRIBUTES结构,用来指定键句柄的继承性,如果句柄不需要被继承,可以使用NULL。
● lpdwDisposition参数——这个参数一般使用NULL。
当需要创建的子键已经存在的时候,函数仅起到RegOpenKeyEx函数的作用;如果子键不存在,那么函数将创建子键。如果函数执行成功,返回值是ERROR_SUCCESS。
如果要创建“HKEY_LOCAL_MACHINE\Key1\Key2\Key3”子键,既可以将hKey参数设置为HKEY_LOCAL_MACHINE,将lpSubKey参数指向“Key1\Key2\Key3”字符串;也可以先打开“HKEY_LOCAL_MACHINE\Key1”键,将hKey设置为打开的键句柄,然后将lpSubKey参数指向“Key2\Key3”字符串,这和RegOpenKeyEx函数中的用法是类似的。在第二种用法中,打开父键的时候注意要指定KEY_CREATE_SUB_KEY方式。
当被创建子键的上层键不存在的时候,函数连上层的子键一起创建。如上面的例子中,假如Key2也不存在,那么函数先在“HKEY_LOCAL_MACHINE\Key1”下创建Key2,然后在Key2下继续创建Key3。
_Reg.asm文件中的大部分子程序首先用RegOpenKeyEx函数打开子键以便进行下一步操作,但是保存键值用的_RegSetValue子程序中使用的是RegCreateKeyEx函数,这样当子键已经存在的时候,函数仅打开它,如果子键不存在的话则创建子键。
删除子键使用RegDeleteKey函数:
invoke RegDeleteKey,hKey,lpSubKey
hKey参数为父键句柄,lpSubKey参数指向要删除的子键名称字符串。函数仅删除最后一层子键以及下面的全部键值项。比如在“HKEY_LOCAL_MACHINE\Key1\Key2\Key3”子键存在的情况下,当hKey指定为HKEY_LOCAL_MACHINE,lpSubKey指向“Key1\Key2\Key3”的时候,函数仅删除Key3子键,不会连同Key2,Key1全部删除。但如果Key3子键下有键值项的话,这些键值项会被一起删除。
如果要删除的子键下还存在下一层子键,比如上例中的Key3子键下还存在Key4子键,那么对Key3子键进行删除时,Windows 9x和Windows NT系统的做法是不同的:在Windows 9x中,Key3子键本身、Key3子键下所有的键值项和下层子键(包括上面举例的Key4)会被全部删除;而在Windows NT中,只有在不存在下层子键的情况下删除才能成功,如果Key3子键下还存在Key4子键,那么对Key3子键的删除是不会成功的。
应用程序不能直接在HKEY_LOCAL_MACHINE根键下面创建和删除子键,只能在下一层由系统定义的子键下进行操作,如果要保存配置信息的话,用户应用程序一般在HKEY_LOCAL_MACHINE\SOFTWARE子键下再创建自己的子键,然后将键值项保存在自己的子键中。
3 管理键值
配置信息是存放在键值项中的,打开或者创建一个键的最终目的都是为了在键下面存取键值项,这就像磁盘上的目录是用来合理组织和管理文件用的,数据还是存放在文件中的。当使用打开或者创建键的函数得到键句柄后,就可以通过它来存取键值项了。
3.1 设置键值项
在一个键下面设置和创建键值项使用RegSetValueEx函数:
invoke RegSetValueEx,hKey,lpValueName,Reserved,dwType,lpData,cbData
hKey参数指定一个键句柄,键值项将保存在这个键下,lpValueName参数指向定义键值项名称的字符串。假如lpValueName参数指向一个空串或者设置为NULL,并且设置的键值类型是REG_SZ的话,那么函数设置的是键的默认值(图15.2中所示的“默认”项)。
Reserved参数是保留的,必须设置为0。
dwType参数指出了要设置的键值数据的类型,可以使用的类型如表15.1所示。
lpData参数是一个指针,指向包含键值数据的缓冲区,cbData参数为要保存的数据长度。缓冲区中的数据格式以及cbData参数指定的数据长度需要和dwType参数指出的键值类型相对应,比如要设置REG_SZ类型的键值项,就要将cbData参数设置为字符串的长度+1(加上尾部的0);同样对于REG_MULTI_SZ类型的键值项来说,最后的两个0的长度都必须包括到cbData参数中;对于REG_DWORD类型的键值项,需要将双字数据放在缓冲区中并将cbData参数设置为4(不像其他函数一样当参数是双字的时候一般将双字在参数中直接传递)。
当子键中的键值项不存在的时候,函数新建键值项;当键值项已经存在的时候,函数将新的键值数据写入。如果键值数据保存成功,函数返回ERROR_SUCCESS。
虽然键值数据的最大长度没有规定,其大小仅受限于可用的内存大小,应用程序甚至可以使用REG_BINARY格式的键值项将整个文件都保存到注册表中,但在实际的使用中还是建议不要将大于2 KB的数据放到注册表中,因为这将影响注册表的使用效率。
要在一个键中创建或修改键值项,键的打开方式中必须包括KEY_SET_VALUE方式。
3.2 查询键值数据
读取键值项中的数据或者查询键值项的属性使用RegQueryValueEx函数,用法如下:
invoke RegQueryValueEx,hKey,lpValueName,lpReserved,\
lpType,lpData,lpcbData
参数hKey和lpValueName用来指定要读取的键值项所处的子键句柄和键值项的名称, lpReserved参数是保留参数,必须使用0。lpData参数指向一个缓冲区,用来接收返回的键值数据。
函数的其余几个参数使用时必须注意的是它们都是指向双字变量的指针,这一点和使用RegSetValueEx函数时是不同的:
● lpType参数——函数在这个参数指向的双字变量中返回读取的键值类型,如果不需要返回键值项的类型,可以将这个参数设置为NULL。
● lpcbData参数——在调用的时候,程序必须在这个参数指向的双字变量中放置缓冲区的长度(并不是直接用lpcbData参数指出缓冲区长度)。当函数返回的时候,双字变量被函数改为返回到缓冲区中的数据的实际长度。
当函数执行成功的时候,函数的返回值是ERROR_SUCCESS。当程序指定的缓冲区长度不足以容纳返回的数据的时候,函数的返回值是ERROR_MORE_DATA,这时lpcbData参数指向的双字变量中返回需要的长度。
如果仅需要查询键值长度而不需要返回实际的数据,可以将lpData参数设置为NULL,但是lpcbData参数不能为NULL,这时函数会在lpcbData参数指向的双字变量中返回键值数据的长度。如果仅想查询键值项的类型,也可以同时将lpcbData和lpData参数设置为NULL。在这些情况下如果函数查询成功,返回值也是ERROR_SUCCESS。
如果要在一个键中查询键值数据的话,键的打开方式中必须包括KEY_QUERY_VALUE方式。
3.3 删除键值项
删除一个键值项的操作则比较简单,使用RegDeleteValue函数就可以了:
invoke RegDeleteValue,hKey,lpValueName
hKey参数和lpValueName指定父键句柄和被删除键值项的名称。惟一需要注意的是父键句柄的打开方式必须包括KEY_SET_VALUE。如果键值项被成功删除,则函数返回ERROR_SUCCESS。
4 子键和键值的枚举
在实际的应用中往往需要对一个键下的子键或者键值项进行列表操作,就像在DOS系统下常用Dir命令一样,这就要用到子键和键值的枚举函数。在注册表函数中,枚举子键和枚举键值项使用的函数是不一样的,不像FindFirstFile等文件列表函数那样将文件连同子目录混在一起列出来。下面分别介绍这两种函数。
4.1 枚举子键
例子程序中枚举子键和键值项的操作是在_EnumKey子程序中完成的,读者可以参考一下相应的代码,在这个子程序中,程序首先使用RegEnumKeyEx函数来枚举子键:
invoke RegEnumKeyEx,hKey,dwIndex,lpName,lpcbName,lpReserved,\
lpClass,lpcbClass,lpftLastWriteTime
hKey参数指定被枚举的键句柄,dwIndex参数指定需要返回信息的子键索引编号,lpName指向一个缓冲区,函数在这里返回子键名称,lpClass指向用于返回子键类名的缓冲区,lpftLastWriteTime指向一个FILETIME结构,函数在这里返回子键上一次被写入的时间。lpReserved参数是保留参数,必须设置为0。
要注意的是:lpcbName和lpcbClass指向两个双字变量,调用函数前,这两个双字变量中必须放入lpName和lpClass指定的缓冲区的长度,当函数返回的时候,函数在里面返回实际返回到缓冲区中的字符串的长度。如果函数执行成功,返回值是ERROR_SUCCESS。
RegEnumKeyEx函数每次返回一个子键的名称信息,所以要枚举全部子键的话,必须用循环多次调用这个函数,并且每次将dwIndex参数指定的子键索引号递增,当子键全部被枚举后,继续调用函数将得到一个ERROR_NO_MORE_ITEMS返回值,这时就可以结束循环了。下面是循环的典型写法:
.data
dwIndex dd ?
dwSize dd ?
szBuffer db 256 dup (?)
.code
... ...
mov dwIndex,0
.while TRUE
mov dwSize,sizeof szBuffer
invoke RegEnumKeyEx,hKey,dwIndex,addr szBuffer,addr dwSize,\
NULL,NULL,NULL,NULL
.break .if eax == ERROR_NO_MORE_ITEMS
;处理获取的子键
inc dwIndex
.endw
在循环开始前,程序初始化当做索引用的dwIndex变量,每次调用RegEnumKeyEx后将索引加1,当检测到函数的返回值是ERROR_NO_MORE_ITEMS的时候,使用 .break语句退出循环。程序不使用 .break .if eax != ERROR_SUCCESS语句当做结束循环的条件是因为:当出现缓冲区不够长等意外情况时,函数的调用可能失败,但是这时子键可能还没有全部被枚举,所以只有判断返回值是ERROR_NO_MORE_ITEMS才能保证全部子键被枚举。
每次调用函数之前,程序必须重新将dwSize变量的值设置为szBuffer缓冲区的大小,这是因为每次函数返回时,dwSize中会变成返回的子键名称字符串的长度,如果不重新设置,下一次调用时函数就会将这个长度认为是缓存区的长度。
当进行枚举子键操作时,父键的打开方式中必须包括KEY_ENUMERATE_SUB_KEYS方式(KEY_READ方式中已经包括KEY_ENUMERATE_SUB_KEYS)。
4.2 枚举键值
RegEnumKeyEx函数仅枚举一个键下面的全部子键,对键下面的键值项则不会去理会。如果要枚举一个键下面的键值项,那么必须使用RegEnumValue函数:
invoke RegEnumValue,hKey,dwIndex,lpValueName,lpcbValueName,\
lpReserved,lpType,lpData,lpcbData
函数的hKey,dwIndex和lpReserved参数的使用同RegEnumKeyEx函数中的同名参数。其余的一些参数中,lpValueName和lpData参数指向两个缓存区,函数在里面分别返回键值项的名称和数据。lpcbValueName和lpcbData参数指向两个双字变量,调用函数前里面必须放入键值项名称缓冲区和键值数据缓冲区的长度,函数返回后这两个变量的值被改为返回到缓冲区中的数据长度。lpType参数则指向一个用于返回键值数据类型的双字变量。
如果不需要返回键值数据,lpData和lpcbData参数可以设置为NULL,如果不需要返回键值数据类型,lpType参数也可以设置为NULL。
下面是一段典型的用于枚举键值项的循环代码:
.data
dwIndex dd ?
dwType dd ?
dwNameSize dd ?
szName db 256 dup (?)
dwDataSize dd ?
szData db 256 dup (?)
.code
...
mov dwIndex,0
.while TRUE
mov dwNameSize,sizeof szName
mov dwDataSize,sizeof szData
invoke RegEnumValue,hKey,dwIndex,addr szName,\
addr dwNameSize,NULL,addr dwType,\
addr szData,addr dwDataSize
.break .if eax == ERROR_NO_MORE_ITEMS
;处理获取的键值项
inc dwIndex
.endw
这个循环的结构和使用RegEnumKeyEx函数的循环是大同小异的。
要进行枚举键值项的操作,父键的打开方式中必须包括KEY_QUERY_VALUE方式。
4.3 查询键属性
在枚举子键和键值项的时候往往会遇到这样一个问题:注册表函数对键值数据的长度并没有限制,在预留缓冲区的时候如果申请太大的内存比较浪费,申请太小的内存则无法枚举成功,对于返回的子键名称和键值项名称也是如此。那么,究竟该留多大的缓冲区呢?其实在枚举之前可以先用RegQueryInfoKey函数查看一下键的统计信息。
RegQueryInfoKey函数返回的信息有:一个键下面子键的数量、键值项的数量、子键名称和键值名称字符串的最大长度及键值数据的最大长度等。根据这些信息,就能方便地申请足够大的缓冲区来保证枚举成功。函数还能返回创建子键时指定的类名和最后一次写入子键的时间等信息。
RegQueryInfoKey函数的用法是:
invoke RegQueryInfoKey,hKey,lpClass,lpcbClass,lpReserved,\
lpcSubKeys,lpcbMaxSubKeyLen,lpcbMaxClassLen,\
lpcValues,lpcbMaxValueNameLen,lpcbMaxValueLen,\
lpcbSecurityDescriptor,lpftLastWriteTime
函数的参数比较多,但并不复杂,各参数的含义是:
● hKey—指定要获取信息的键句柄,键的打开方式中必须包括KEY_QUERY_VALUE。
● lpClass——指向一个缓冲区,用来返回创建键时指定的Class字符串。
● lpcbClass——指向一个双字变量,调用函数时变量中必须放入lpClass指定的缓冲区的长度,函数返回时在这里放入返回到缓冲区中的字符串长度。
● lpReserved——保留参数,必须设置为0。
● lpcSubKeys——指向一个双字,用来返回键中的子键数量。
● lpcbMaxSubKeyLen——指向一个双字,用来返回所有子键中最长的名称字符串长度,返回的长度不包括字符串结尾的0字符。
● lpcbMaxClassLen——指向一个双字,用来返回所有子键中最长的Class字符串长度,返回的长度不包括字符串结尾的0字符。
● lpcValues——指向一个双字,用来返回键下面的键值项数量。
● lpcbMaxValueNameLen——指向一个双字,用来返回所有键值项中最长的名称字符串长度,返回的长度不包括字符串结尾的0字符。
● lpcbMaxValueLen——指向一个双字,用来返回所有键值数据的最大长度。
● lpcbSecurityDescriptor——指向一个双字,用来返回安全描述符的长度。
● lpftLastWriteTime——指向一个FILETIME结构,用来返回最后一次修改键的时间。
可以看到,除hKey外其他的参数都是指针,指向用来返回数据的变量或结构,如果不需要返回某种信息的话,可以将对应的指针参数设置为NULL。另外,所有返回的最长名称字符串长度中都不包括结尾的0字符。
除了前面介绍的函数外,系统中还存在一些不常用的注册表函数,比如可以用RegLoadKey和RegReplaceKey 函数从指定的文件中恢复注册表的子键信息,也可以通过RegSaveKey函数将键信息保存到指定的文件中。另外,可以通过RegConnectRegistry等函数操作远程注册表。对于这些函数,本节就不详细介绍了。
---------------------------------------------------------------------------------------------------------------------------------------
为了让读者不经修改就可以将这些子程序用在其他程序中,将这些注册表子程序放在一个单独的_Reg.asm文件中并在主程序中使用include语句包含进来,文件内容如下:
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 查询键值
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_RegQueryValue proc _lpszKey,_lpszValueName,\
_lpszValue,_lpdwSize,_lpdwType
local @hKey,@dwReturn
mov @dwReturn,-1
invoke RegOpenKeyEx,HKEY_LOCAL_MACHINE,_lpszKey,NULL,\
KEY_QUERY_VALUE,addr @hKey
.if eax == ERROR_SUCCESS
invoke RegQueryValueEx,@hKey,_lpszValueName,NULL,\
_lpdwType,_lpszValue,_lpdwSize
mov @dwReturn,eax
invoke RegCloseKey,@hKey
.endif
mov eax,@dwReturn
ret
_RegQueryValue endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 设置键值
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_RegSetValue proc _lpszKey,_lpszValueName,_lpszValue,\
_dwValueType,_dwSize
local @hKey
invoke RegCreateKey,HKEY_LOCAL_MACHINE,_lpszKey,addr @hKey
.if eax == ERROR_SUCCESS
invoke RegSetValueEx,@hKey,_lpszValueName,NULL,\
_dwValueType,_lpszValue,_dwSize
invoke RegCloseKey,@hKey
.endif
ret
_RegSetValue endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 创建子键
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_RegCreateKey proc _lpszKey,_lpszSubKeyName
local @hKey,@hSubkey,@dwDisp
invoke RegOpenKeyEx,HKEY_LOCAL_MACHINE,_lpszKey,NULL,\
KEY_CREATE_SUB_KEY,addr @hKey
.if eax == ERROR_SUCCESS
invoke RegCreateKeyEx,@hKey,_lpszSubKeyName,NULL,\
NULL,NULL,NULL,NULL,addr @hSubkey,addr @dwDisp
invoke RegCloseKey,@hKey
invoke RegCloseKey,@hSubkey
.endif
ret
_RegCreateKey endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 删除键值
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_RegDelValue proc _lpszKey,_lpszValueName
local @hKey
invoke RegOpenKeyEx,HKEY_LOCAL_MACHINE,_lpszKey,NULL,\
KEY_WRITE,addr @hKey
.if eax == ERROR_SUCCESS
invoke RegDeleteValue,@hKey,_lpszValueName
invoke RegCloseKey,@hKey
.endif
ret
_RegDelValue endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 删除子键
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_RegDelSubKey proc _lpszKey,_lpszSubKeyName
local @hKey
invoke RegOpenKeyEx,HKEY_LOCAL_MACHINE,_lpszKey,NULL,\
KEY_WRITE,addr @hKey
.if eax == ERROR_SUCCESS
invoke RegDeleteKey,@hKey,_lpszSubKeyName
invoke RegCloseKey,@hKey
.endif
ret
_RegDelSubKey endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
form:http://blog.csdn.net/cyg0810/article/details/7771902
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!