C线程和mfc线程

C多线程

tls-thread local storage

1
2
3
4
TlsAlloc
TlsFree
TlsSetValue
TlsGetValue

TlsAlloc

image-20240713232023182

1
2
3
4
5
函数原型:DWORD TlsAlloc();
作用:为调用该函数的线程分配一个线程局部存储序号,供其它三个函数使用
注意:一个进程最多分配TLS_MINIMUM_AVAILABLE个索引,目前定义为64,一个进程中所有线程分配的索引不会重复
使用:
int index=TlsAlloc()

DWORD TlsAlloc(void); // 返回一个TLS索引

每个线程创建时系统给它分配一个LPVOID指针的数组(叫做TLS数组,长度为TLS_MINIMUM_AVAILABLE),TlsAlloc()函数的作用是返回访问当前线程的TLS数组的一个下标(索引),不同线程得到不同的索引变量值。

当调用TlsAlloc的时候,系统会挨个检查这个数组中成员的值,直到找到一个值为FREE的成员。把找到的成员的值由FREE改为INUSE后,TlsAlloc函数返回该成员的索引。如果不能找到一个值为FREE的成员,TlsAlloc函数就返回TLS_OUT_OF_INDEXES(在WinBase.h文件中定义为-1)

TlsSetValue

image-20240713231930135

1
2
3
4
函数原型:BOOL TlsSetValue(DWORD dwTlsIndex,LPVOID lpTlsValue);
作用:在指定的序号内存储数据,如TlsAlloc分配成功返回的序号
使用:
BOOL ret=TlsSetValue(index,(LPVOID)0x123456)

BOOL TlsSetValue(

DWORD dwTlsIndex, // TLS 索引

LPVOID lpTlsValue // 要设置的值

);

将参数lpTlsValue指定的值放入索引为dwTlsIndex的线程数组成员中,调用成功,会返回TRUE。

TlsGetValue

image-20240713231956514

1
2
3
4
函数原型:LPVOID TlsGetValue(DWORD dwTlsIndex);
作用:取得指定序号内存储的数据
使用:
LPVOID lp=TlsGetValue(index);

LPVOID TlsGetValue(DWORD dwTlsIndex ); // TLS索引

先检查TLS 索引值合法与否,如果合法,取得线程数组中索引为dwTlsIndex的成员的值。

TlsFree
image-20240713232102910

1
2
3
4
函数原型:BOOL TlsFree(DWORD dwTlsIndex);
作用:释放指定序号
使用:
TlsFree(index);

释放局部存储索引。

新建控制台应用

image-20240713223629395

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// CThread.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <windows.h>
#include <conio.h>
#include <stdio.h>

DWORD WINAPI ThreadFunc(LPVOID lpParam)
{
DWORD dwIdx = TlsAlloc();
TlsSetValue(dwIdx, (LPVOID)GetCurrentThreadId());
for (int i = 0; i < 0x100000; ++i)
{
Sleep(10);
DWORD dwVal = (DWORD)TlsGetValue(dwIdx);
char szMsg[80];
wsprintf(szMsg, "Parameter=%d\tidx:%d\tval:%d\r\n", GetCurrentThreadId(),dwIdx,dwVal);
printf(szMsg);
}
TlsFree(dwIdx);
return 0;
}
int main()
{
DWORD dwThreadId, dwThrdParam = 1;
HANDLE hThread;
char szMsg[80];
for (int i = 0; i < 80; ++i)
{
hThread = CreateThread(
NULL,
0,
ThreadFunc,
&dwThrdParam,
0,
&dwThreadId
);
}
if (hThread == NULL)
{
wsprintf(szMsg, "CreateThread failed.");
MessageBox(NULL, szMsg, "main", MB_OK);
}
else
{
_getch();
CloseHandle(hThread);
}
return 0;
}

image-20240713233451653

MT 多线程(M),静态库(T)
MTD 多线程(M),静态库(T),调试版(d)
MD 多线程(M),动态库(D)
MDd 多线程(M),动态库(D),调试版(d)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//线程的开始
unsigned long _beginthread(
void(_cdecl *start_address)(void *), //声明为void (*start_address)(void *)形式
unsigned stack_size, //是线程堆栈大小,一般默认为0
void *arglist //向线程传递的参数,一般为结构体
);

unsigned long _beginthreadex( //推荐使用
void *security, //安全属性,NULL表示默认安全性
unsigned stack_size, //是线程堆栈大小,一般默认为0
unsigned(_stdcall *start_address)(void *), //声明为unsigned(*start_address)(void *)形式
void *argilist, //向线程传递的参数,一般为结构体
unsigned initflag, //新线程的初始状态,0表示立即执行,CREATE_SUSPEND表示创建后挂起。
unsigned *thrdaddr //该变量存放线程标识符,它是CreateThread函数中的线程ID。
); //创建成功条件下的将线程句柄转化为unsigned long型返回,创建失败条件下返回0


//线程的结束
//释放线程空间、释放线程TLS空间、调用ExiteThread结束线程。
void _endthread(void);

// retval:设定的线程结束码,与ExiteThread函数的参数功能一样,
//其实这个函数释放线程TLS空间,再调用ExiteThread函数,但没有释放线程空间。
void _endthreadex(unsigned retval);

两组函数都是用来创建和结束线程的。这两对函数的不同点如下:
(1)从形式上开,_beginthreadex()更像CreateThread()。_beginthreadex()比_beginthread()多3个参数:intiflag,security和threadaddr。
(2)两种创建方式的线程函数不同。_beginthreadex()的线程函数必须调用_stdcall调用方式,而且必须返回一个unsigned int型的退出码。
(3)_beginthreadex()在创建线程失败时返回0,而_beginthread()在创建线程失败时返回-1。这一点是在检查返回结果是必须注意的。
(4)如果是调用_beginthread()创建线程,并相应地调用_endthread()结束线程时,系统自动关闭线程句柄;而调用_beginthreadx()创建线程,并相应地调用_endthreadx()结束线程时,系统不能自动关闭线程句柄。因此调用_beginthreadx()创建线程还需程序员自己关闭线程句柄,以清除线程的地址空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// CThread.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <windows.h>
#include <conio.h>
#include <stdio.h>
#include <process.h>

unsigned int WINAPI ThreadFunc(LPVOID lpParam)
{
DWORD dwIdx = TlsAlloc();
TlsSetValue(dwIdx, (LPVOID)GetCurrentThreadId());
for (int i = 0; i < 0x100000; ++i)
{
Sleep(10);
DWORD dwVal = (DWORD)TlsGetValue(dwIdx);
char szMsg[80];
wsprintf(szMsg, "Parameter=%d\tidx:%d\tval:%d\r\n", GetCurrentThreadId(),dwIdx,dwVal);
printf(szMsg);
}
TlsFree(dwIdx);
return 0;
}
int main()
{
unsigned int dwThreadId, dwThrdParam = 1;
HANDLE hThread;
char szMsg[80];
for (int i = 0; i < 80; ++i)
{
hThread = (HANDLE)_beginthreadex(
NULL,
0,
ThreadFunc,
&dwThrdParam,
0,
&dwThreadId
);
}
if (hThread == NULL)
{
wsprintf(szMsg, "CreateThread failed.");
MessageBox(NULL, szMsg, "main", MB_OK);
}
else
{
_getch();
CloseHandle(hThread);
}
return 0;
}

运行结果

image-20240714101145917

mfc多线程

新建MFC应用程序

image-20240714101402739

AfxBeginThread

image-20240714101844047

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*返回值: 成功时返回一个指向新线程的线程对象的指针,否则NULL。
*pfnThreadProc : 线程的入口函数,声明一定要如下: UINT MyThreadFunction(LPVOID pParam),不能设置为NULL;
*pParam : 传递入线程的参数,注意它的类型为:LPVOID,所以我们可以传递一个结构体入线程.
*nPriority : 线程的优先级,一般设置为 0 .让它和主线程具有共同的优先级.
*nStackSize : 指定新创建的线程的栈的大小.如果为 0,新创建的线程具有和主线程一样的大小的栈
*dwCreateFlags : 指定创建线程以后,线程有怎么样的标志.可以指定两个值:
*CREATE_SUSPENDED : 线程创建以后,会处于挂起状态,直到调用:ResumeThread
0 : 创建线程后就开始运行.
*lpSecurityAttrs : 指向一个 SECURITY_ATTRIBUTES 的结构体,用它来标志新创建线程的安全性.如果为 NULL,*/
CWinThread* AfxBeginThread(AFX_THREADPROC pfnThreadProc,
  LPVOID lParam,
  int nPriority = THREAD_PRIORITY_NORMAL,
  UINT nStackSize = 0,
  DWORD dwCreateFlags = 0,
  LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
);//用于创建工作者线程

image-20240714102811010

1
2
3
4
5
6
7
8
9
10
11
12
UINT MyControllingFunction(LPVOID pParam)
{
CString strFmt;
strFmt.Format("hello mfc thread %d", GetCurrentThreadId);
AfxMessageBox(strFmt);
return 0;
}

void CMFCThreadDlg::OnBnClickedButton1()
{
AfxBeginThread(MyControllingFunction, NULL);
}

运行结果

image-20240714103240400

image-20240714103300705

新建MFC类

image-20240714103455146

image-20240714103529867

新建类向导

image-20240714103709056

新建Run虚函数

image-20240714103827123

CMyThread.cpp

1
2
3
4
5
6
7
8
9
10
11
int CMyThread::Run()
{
// TODO: 在此添加专用代码和/或调用基类
AfxMessageBox("Run");
return CWinThread::Run();
}

void CMyThread::OnMmTest(UINT wParam, LONG lParam)
{
AfxMessageBox("OnMnTest");
}

CMyThread.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#pragma once



// CMyThread
#define MM_TEST WM_USER+1//添加
class CMyThread : public CWinThread
{
DECLARE_DYNCREATE(CMyThread)

protected:
CMyThread(); // 动态创建所使用的受保护的构造函数
virtual ~CMyThread();

public:
virtual BOOL InitInstance();
virtual int ExitInstance();

protected:
DECLARE_MESSAGE_MAP()
public:
virtual int Run();
afx_msg void OnMmTest(UINT wParam, LONG lParam);//添加
};

image-20240714105436739

1
2
3
4
5
6
#include "CMyThread.h"
CWinThread* pThread = NULL;
void CMFCThreadDlg::OnBnClickedButton2()
{
pThread = AfxBeginThread(RUNTIME_CLASS(CMyThread));
}

image-20240714105938386

1
2
3
4
void CMFCThreadDlg::OnBnClickedButton3()
{
pThread->PostThreadMessageA(MM_TEST, NULL, NULL);
}

运行后先点击方法二运行起来,然后点击发消息来发送消息

image-20240714110427367

image-20240714110547218

ini文件操作

不带Private的操作的是系统配置文件(win.ini),带Private的是操作程序自己的配置文件,注意文件名给全路径

image-20240714110914699

WriteProfileString

把信息写入系统的win.ini文件

image-20240714112717997

1
2
3
4
5
BOOL WriteProfileString(
LPCTSTR lpAppName, // 节的名字,是一个以0结束的字符串
LPCTSTR lpKeyName, // 键的名字,是一个以0结束的字符串。若为NULL,则删除整个节
LPCTSTR lpString // 键的值,是一个以0结束的字符串。若为NULL,则删除对应的键
)

WritePrivateProfileString

image-20240714111451490

函数功能:

将lpString(CString型)变量存入lpFileName(Cfg.ini)文件里面,按照lpAppName字段进行分类索引。

各个参数详解:

BOOL WritePrivateProfileString(

   LPCTSTRlpAppName,

   LPCTSTRlpKeyName,

   LPCTSTRlpString,

   LPCTSTRlpFileName

   );

//其中各参数的意义:

LPCTSTR lpAppName; //是INI文件中的一个字段名.

LPCTSTR lpKeyName;//是lpAppName下的一个键名,通俗讲就是变量名.

LPCTSTR lpString; //是键值, 也就是变量的值,不过必须为LPCTSTR型或CString型的.

LPCTSTR lpFileName;//是完整的INI文件名.

调用:

WritePrivateProfileString(“StudentInfo1”,”身份证”,”44022520070001”,”.\ConfigFile\ConfigInit.ini”);

GetCurrentDirectory

image-20240714113415395

函数功能
获取当前进程的当前目录。
参数说明
nBufferLength 缓冲区的长度
lpBuffer 指定一个预定义字串,用于装载当前目录
返回值
调用成功 返回装载到lpBuffer的字节数。
使用GetLastError函数可获得错误信息。

新建控制台应用(如果权限不够则以管理员身份运行)

image-20240714111159308

1
2
3
4
5
6
7
8
9
10
// Reg.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <windows.h>
#include <iostream>

int main()
{
DWORD dwRet = WriteProfileString("CR40", "年龄", "10");
std::cout << "Hello World!\n";
}

有值说明运行成功(若为0则可能是没有权限,以管理员身份运行)

image-20240714112022587

找到win.ini

image-20240714112302531

image-20240714112317473

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Reg.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <windows.h>
#include <iostream>

int main()
{
char szPath[MAX_PATH] = {};
GetCurrentDirectory(MAX_PATH, szPath);
DWORD dwRet = WriteProfileString("CR40", "年龄", "10");
char szIniPath[MAX_PATH] = {};
wsprintf(szIniPath, "%s\\test.ini", szPath);
BOOL bRet = WritePrivateProfileString("CR40", "年龄", "12", "test.ini");
std::cout << "Hello World!\n";
}

image-20240714114405162

image-20240714113215790

注册表

RegCreatekey

image-20240714120321463

该函数用于打开指定的键,如果键不存在,则新建一个键或子键
LONG RegCreatekey(HKEY hKey,LPCTSTR lpSubKey,PHKEY phkResult);
hKey:打开键的句柄
lpSubKey:函数打开或创建的键名
phkResult:函数返回的打开或创建键的句柄指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Reg.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <windows.h>
#include <iostream>

int main()
{
HKEY h361 = NULL;
LSTATUS nRet = RegCreateKey(HKEY_CURRENT_USER, "Software\\361", &h361);
if (nRet != ERROR_SUCCESS)
{
return 0;
}
}

创建成功

image-20240714120711783

RegSetValue

image-20240714133048387

设置一个注册表键的缺省数据值,不能用此函数设置除了缺省值以外的任何值。

参数

hKey–键句柄

lpSubKey–子键名称或路径

dwType–数据类型,只能接受 REG_SZ

lpData–所设置的字符串数据

cbData–lpData字符串的长度,须包含Chr(0)字符

返回值:=0 成功 ≠0 失败

RegSetValueEx

image-20240714134434601

RegSetValueEx函数可以设置注册表中键的值
各个参数及返回值的含义如下:
hKey的含义同RegCreateKeyEx函数中的hKey参数。
lpValueName为一个指向包含值名的字符串指针。
Reserved保留,通常必须设置为0。
dwType确定了设置的值的类型同RegQueryValueKeyEx的lyType参数。
lpData为一个指向包含数据的缓冲区的指针。
cbData以字节为单位,指定数据的长度。
返回值同RegCreateKeyEx函数的返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Reg.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <windows.h>
#include <iostream>

int main()
{
HKEY h361 = NULL;
LSTATUS nRet = RegCreateKey(HKEY_CURRENT_USER, "Software\\361", &h361);
if (nRet != ERROR_SUCCESS)
{
return 0;
}
char szData[] = { "hello" };
nRet = RegSetValueEx(h361, "world", 0, REG_SZ, (LPBYTE)szData, sizeof(szData));
if (nRet != ERROR_SUCCESS)
{
return 0;
}
DWORD dwValue = 1;
nRet = RegSetValueEx(h361, "no", 0, REG_DWORD, (LPBYTE)&dwValue, sizeof(dwValue));
if (nRet != ERROR_SUCCESS)
{
return 0;
}
}

image-20240714134410926

RegDeleteKey

image-20240714134912409

该函数用于从注册表中删除某个子键
LONG RegDeleteKey(HKEY hkey,LPCWSTR lpSubKey);
hKey:当前打开的父键句柄
lpSubKey:将要删除的子键名称

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Reg.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <windows.h>
#include <iostream>

int main()
{
HKEY h361 = NULL;
LSTATUS nRet = RegCreateKey(HKEY_CURRENT_USER, "Software\\361", &h361);
if (nRet != ERROR_SUCCESS)
{
return 0;
}
char szData[] = { "hello" };
nRet = RegSetValueEx(h361, "world", 0, REG_SZ, (LPBYTE)szData, sizeof(szData));
if (nRet != ERROR_SUCCESS)
{
return 0;
}
DWORD dwValue = 1;
nRet = RegSetValueEx(h361, "no", 0, REG_DWORD, (LPBYTE)&dwValue, sizeof(dwValue));
if (nRet != ERROR_SUCCESS)
{
return 0;
}
RegCloseKey(h361);
nRet = RegDeleteKey(HKEY_CURRENT_USER,"Software\\361");
if (nRet != ERROR_SUCCESS)
{
return 0;
}
}

运行后原来的361被删除

image-20240714135451211

RegDeleteValue

image-20240714135616687

参数说明:

hKey 同RegCreateKeyEx函数中的hKey参数。

lpSubKey 同RegCreateKeyEx函数中的lpSubKey参数。

返回值 (同上)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Reg.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <windows.h>
#include <iostream>

int main()
{
HKEY h361 = NULL;
LSTATUS nRet = RegCreateKey(HKEY_CURRENT_USER, "Software\\361", &h361);
if (nRet != ERROR_SUCCESS)
{
return 0;
}
char szData[] = { "hello" };
nRet = RegSetValueEx(h361, "world", 0, REG_SZ, (LPBYTE)szData, sizeof(szData));
if (nRet != ERROR_SUCCESS)
{
return 0;
}
DWORD dwValue = 1;
nRet = RegSetValueEx(h361, "no", 0, REG_DWORD, (LPBYTE)&dwValue, sizeof(dwValue));
if (nRet != ERROR_SUCCESS)
{
return 0;
}
nRet = RegDeleteValue(h361, "world");//添加
RegCloseKey(h361);//添加
nRet = RegDeleteKey(HKEY_CURRENT_USER,"Software\\361");
if (nRet != ERROR_SUCCESS)
{
return 0;
}
}

下断点后运行

image-20240714140132975

删除前已经完成了361的创建

image-20240714140159683

删除后world被成功删除

image-20240714140246194

运行结束后整个361都被删除

image-20240714140344792

RegQueryValue

image-20240714145455598

RegQueryValueEx找回一个打开的注册表键值相关联的给定的变量数据或者变量。

1
2
3
4
5
6
7
8
LONG RegQueryValueEx(
HKEY hKey, // handle to key 主键句柄
LPCTSTR lpValueName, // value name 子键名称
LPD [word](https://blog.csdn.net/csd3176/article/details/tag-127-1.html) lpReserved, // reserved 保留
LPDWORD lpType, // type buffer
LPBYTE lpData, // data buffer [存储](https://blog.csdn.net/csd3176/article/details/tag-240-1.html)返回值的缓冲区
LPDWORD lpcbData // size of data buffer存储返回值的缓冲区的大小
);

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// Reg.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <windows.h>
#include <iostream>

void QueryKey(HKEY hKey);
int main()
{
#if 0
char szPath[MAX_PATH] = {};
GetCurrentDirectory(MAX_PATH, szPath);
DWORD dwRet = WriteProfileString("CR40", "年龄", "10");
char szIniPath[MAX_PATH] = {};
wsprintf(szIniPath, "%s\\test.ini", szPath);
BOOL bRet = WritePrivateProfileString("CR40", "年龄", "12", szIniPath);
std::cout << "Hello World!\n";
#endif // 0
#if 0
HKEY h361 = NULL;
LSTATUS nRet = RegCreateKey(HKEY_CURRENT_USER, "Software\\361", &h361);
if (nRet != ERROR_SUCCESS)
{
return 0;
}
char szData[] = { "hello" };
nRet = RegSetValueEx(h361, "world", 0, REG_SZ, (LPBYTE)szData, sizeof(szData));
if (nRet != ERROR_SUCCESS)
{
return 0;
}
DWORD dwValue = 1;
nRet = RegSetValueEx(h361, "no", 0, REG_DWORD, (LPBYTE)&dwValue, sizeof(dwValue));
if (nRet != ERROR_SUCCESS)
{
return 0;
}
nRet = RegDeleteValue(h361, "world");
RegCloseKey(h361);
nRet = RegDeleteKey(HKEY_CURRENT_USER, "Software\\361");
if (nRet != ERROR_SUCCESS)
{
return 0;
}
#endif // 0
HKEY h361 = NULL;
LSTATUS nRet = RegCreateKey(HKEY_CURRENT_USER, "Software", &h361);
if (nRet != ERROR_SUCCESS)
{
return 0;
}
QueryKey(h361);
}

#define MAX_VALUE_NAME 1024
void QueryKey(HKEY hKey)
{
CHAR achKey[MAX_PATH]; // buffer for subkey name
CHAR achClass[MAX_PATH] = TEXT(""); // buffer for class name
DWORD cchClassName = MAX_PATH; // size of class string
DWORD cSubKeys = 0; // number of subkeys
DWORD cbMaxSubKey = 0; // longest subkey size
DWORD cchMaxClass = 0; // longest class string
DWORD cValues = 0; // number of values for key
DWORD cchMaxValue = 0; // longest value name
DWORD cbMaxValueData = 0; // longest value data
DWORD cbSecurityDescriptor = 0; // size of security descriptor
FILETIME ftLastWriteTime; // last write time

DWORD i = 0, j = 0, retCode = 0, retValue = 0;
CHAR achValue[MAX_VALUE_NAME];
DWORD cchValue = MAX_VALUE_NAME;
CHAR achBuff[80];

// Get the class name and the value count.
RegQueryInfoKey(
hKey, // key handle
achClass, // buffer for class name
&cchClassName, // size of class string
NULL, // reserved
&cSubKeys, // number of subkeys
&cbMaxSubKey, // longest subkey size
&cchMaxClass, // longest class string
&cValues, // number of values for this key
&cchMaxValue, // longest value name
&cbMaxValueData, // longest value data
&cbSecurityDescriptor, // security descriptor
&ftLastWriteTime); // last write time

for (i = 0, retCode = ERROR_SUCCESS;
retCode == ERROR_SUCCESS; i++)
{
DWORD dwNamelen = MAX_PATH;
retCode = ::RegEnumKeyEx(hKey,
i,
achKey,
&dwNamelen,
NULL,
NULL,
NULL,
&ftLastWriteTime);

if (retCode == (DWORD)ERROR_SUCCESS)
{
printf("Key:%s\r\n", achKey);
}
}

if (cValues)
{
for (j = 0, retValue = ERROR_SUCCESS;
j < cValues; j++)
{
cchValue = MAX_VALUE_NAME;
achValue[0] = '\0';
retCode = ::RegEnumValue(hKey, j,
achValue,
&cchValue,
NULL,
NULL,
NULL,
NULL);

if (retValue != (DWORD)ERROR_SUCCESS &&
retValue != ERROR_INSUFFICIENT_BUFFER)
{
wsprintf(achBuff,
"Line:%d 0 based index = %d,retValue = %d,"
"ValueLen = %d",
__LINE__, j, retValue, cchValue);
printf(achBuff);
}
}
}
}

运行结果

image-20240714144703298