TCP 的逻辑实现,可以和客户端连接发送数据
This commit is contained in:
24
.gitignore
vendored
24
.gitignore
vendored
@@ -11,3 +11,27 @@
|
|||||||
*.app
|
*.app
|
||||||
.snapshots/*
|
.snapshots/*
|
||||||
|
|
||||||
|
# CMake 生成的构建目录
|
||||||
|
build/
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# 通用构建产物
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
*.log
|
||||||
|
*.tmp
|
||||||
|
*.exe
|
||||||
|
*.dll
|
||||||
|
*.lib
|
||||||
|
*.pdb
|
||||||
|
|
||||||
|
# Visual Studio / MSVC 相关中间文件
|
||||||
|
*.vcxproj
|
||||||
|
*.vcxproj.user
|
||||||
|
*.vcxproj.filters
|
||||||
|
*.sln
|
||||||
|
Debug/
|
||||||
|
Release/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
|
||||||
|
|||||||
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"clangd.arguments": [
|
||||||
|
"--compile-commands-dir=build"
|
||||||
|
]
|
||||||
|
}
|
||||||
28
CMakeLists.txt
Normal file
28
CMakeLists.txt
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
project(DTU_HMI)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 99)
|
||||||
|
|
||||||
|
# 导出编译数据库,供 Cursor/clangd 使用
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
add_compile_options(/utf-8)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_executable(DTU-HMI
|
||||||
|
src/main.c
|
||||||
|
src/menu.c
|
||||||
|
src/thread_utils.c
|
||||||
|
src/remoteDisplay.c
|
||||||
|
src/TCP/tcp.c
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(DTU-HMI PRIVATE include src src/TCP)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
target_link_libraries(DTU-HMI PRIVATE ws2_32)
|
||||||
|
else()
|
||||||
|
target_link_libraries(DTU-HMI PRIVATE pthread)
|
||||||
|
endif()
|
||||||
|
|
||||||
135
README.md
135
README.md
@@ -1,3 +1,134 @@
|
|||||||
# DTU-HMI
|
# PC_HMI – 本地 HMI 菜单逻辑的 PC 端模拟
|
||||||
|
|
||||||
用 PC 端模拟 DTU 的人机交互逻辑
|
本目录提供一个 **纯 C 语言的控制台程序**,在 PC 上模拟嵌入式 HMI 的菜单交互逻辑,方便调试和理解 `Menu_Route` / `Menu_Show_Proc` 的行为。
|
||||||
|
|
||||||
|
## 环境搭建
|
||||||
|
|
||||||
|
### 1. 安装 CMake
|
||||||
|
|
||||||
|
1. 访问 CMake 官网下载页:[`https://cmake.org/download/`](https://cmake.org/download/)
|
||||||
|
2. 下载适用于 Windows 的安装包(通常为 `cmake-*-windows-x86_64.msi`),安装时**勾选将 CMake 添加到 PATH**。
|
||||||
|
3. 安装完成后打开终端(PowerShell / CMD),执行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cmake --version
|
||||||
|
```
|
||||||
|
|
||||||
|
能看到版本信息即表示安装成功。
|
||||||
|
|
||||||
|
### 2. 编译器与编码说明
|
||||||
|
|
||||||
|
- Windows 下默认使用 **Visual Studio / MSVC** 工具链进行编译。
|
||||||
|
- 源文件采用 **UTF‑8 编码并包含中文注释**,`CMakeLists.txt` 中已经为 MSVC 打开 `/utf-8` 选项,无需额外配置即可正常编译。
|
||||||
|
|
||||||
|
|
||||||
|
## 目录结构
|
||||||
|
|
||||||
|
- `CMakeLists.txt` – CMake 构建配置
|
||||||
|
- `include/menu.h` – 菜单数据结构与接口声明
|
||||||
|
- `include/tcp.h` – TCP 通信接口(客户端/服务端)
|
||||||
|
- `src/menu.c` – 简单菜单树与菜单调度逻辑(PC 版)
|
||||||
|
- `src/main.c` – 主程序入口,从键盘读取按键并驱动菜单
|
||||||
|
- `src/tcp.c` – TCP 实现(Windows Winsock / Linux socket)
|
||||||
|
|
||||||
|
## 构建步骤
|
||||||
|
|
||||||
|
### 1. Windows 示例(当前工程默认方式)
|
||||||
|
|
||||||
|
在仓库根目录下执行(注意路径中有空格时要加引号):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
mkdir build
|
||||||
|
cd "D:\Code\DTU 程序\DTU-HMI\build"
|
||||||
|
cmake ..
|
||||||
|
cmake --build .
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 通用(Linux / macOS 等)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
cmake ../
|
||||||
|
cmake --build .
|
||||||
|
```
|
||||||
|
|
||||||
|
生成的可执行文件名为:
|
||||||
|
|
||||||
|
```text
|
||||||
|
pc_hmi # Windows / Linux 下均可
|
||||||
|
```
|
||||||
|
|
||||||
|
## 运行与按键映射
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./pc_hmi
|
||||||
|
```
|
||||||
|
|
||||||
|
在控制台中使用以下按键进行菜单操作:
|
||||||
|
|
||||||
|
- `W` / `w` – 上(对应嵌入式的 `CN_KEY_U`)
|
||||||
|
- `S` / `s` – 下(`CN_KEY_D`)
|
||||||
|
- `A` / `a` – 左(`CN_KEY_L`)
|
||||||
|
- `D` / `d` – 右(`CN_KEY_R`)
|
||||||
|
- `Enter` – 确认 / 进入子菜单(`CN_KEY_ENT`)
|
||||||
|
- `Esc` – 返回上一级(`CN_KEY_ESC`)
|
||||||
|
|
||||||
|
界面中会用 `[*名称*]` 标出当前选中的菜单项,并在底部显示对应的提示信息。
|
||||||
|
|
||||||
|
> 注意:当前只是一个 **最小化演示环境**,菜单树结构与嵌入式工程中的 `g_tMenuModelTab` 不完全一致,但交互方式相仿,可作为移植和调试时的参考。
|
||||||
|
|
||||||
|
## TCP 通信
|
||||||
|
|
||||||
|
项目已集成 **跨平台 TCP 模块**(`include/tcp.h` + `src/tcp.c`),可用于与设备或上位机进行 TCP 通信。
|
||||||
|
|
||||||
|
- **Windows**:使用 Winsock2,已自动链接 `ws2_32`。
|
||||||
|
- **Linux**:使用 BSD socket,无需额外库。
|
||||||
|
|
||||||
|
### 使用前初始化
|
||||||
|
|
||||||
|
程序入口已调用 `Tcp_Init()`,退出前会调用 `Tcp_Cleanup()`。若在其它线程或模块中使用 TCP,无需重复初始化。
|
||||||
|
|
||||||
|
### TCP 客户端示例
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "../include/tcp.h"
|
||||||
|
|
||||||
|
/* 连接服务器 */
|
||||||
|
int sock = TcpClient_Connect("192.168.1.100", 502);
|
||||||
|
if (sock == TCP_INVALID_SOCKET) {
|
||||||
|
/* 连接失败 */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 发送数据 */
|
||||||
|
const char *msg = "hello";
|
||||||
|
int sent = TcpClient_Send(sock, msg, strlen(msg));
|
||||||
|
|
||||||
|
/* 接收数据 */
|
||||||
|
char buf[256];
|
||||||
|
int n = TcpClient_Recv(sock, buf, sizeof(buf) - 1);
|
||||||
|
if (n > 0) {
|
||||||
|
buf[n] = '\0';
|
||||||
|
printf("recv: %s\n", buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 关闭连接 */
|
||||||
|
TcpClient_Close(sock);
|
||||||
|
```
|
||||||
|
|
||||||
|
### TCP 服务端示例
|
||||||
|
|
||||||
|
```c
|
||||||
|
int server = TcpServer_Listen(8080);
|
||||||
|
if (server == TCP_INVALID_SOCKET) return;
|
||||||
|
|
||||||
|
int client = TcpServer_Accept(server); /* 阻塞等待客户端 */
|
||||||
|
if (client != TCP_INVALID_SOCKET) {
|
||||||
|
TcpClient_Send(client, "welcome", 7);
|
||||||
|
TcpClient_Close(client);
|
||||||
|
}
|
||||||
|
TcpServer_Close(server);
|
||||||
|
```
|
||||||
|
|
||||||
|
接口说明见 `include/tcp.h`。
|
||||||
|
|||||||
10
include/types.h
Normal file
10
include/types.h
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#define uint8_t unsigned char
|
||||||
|
#define uint16_t unsigned short
|
||||||
|
#define uint32_t unsigned int
|
||||||
|
#define uint64_t unsigned long long
|
||||||
|
#define int8_t char
|
||||||
|
#define int16_t short
|
||||||
|
#define int32_t int
|
||||||
|
#define int64_t long long
|
||||||
|
|
||||||
|
|
||||||
13058
src/Drv/Ascii.c
Normal file
13058
src/Drv/Ascii.c
Normal file
File diff suppressed because it is too large
Load Diff
165
src/TCP/tcp.c
Normal file
165
src/TCP/tcp.c
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
/*
|
||||||
|
* 跨平台 TCP 实现:Windows Winsock2 / Linux BSD socket
|
||||||
|
*/
|
||||||
|
#define _WINSOCK_DEPRECATED_NO_WARNINGS 1
|
||||||
|
#include "tcp.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#pragma comment(lib, "ws2_32.lib")
|
||||||
|
static int g_wsa_started = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int Tcp_Init(void)
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
WSADATA wsa;
|
||||||
|
if (g_wsa_started)
|
||||||
|
return 0;
|
||||||
|
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
|
||||||
|
return -1;
|
||||||
|
g_wsa_started = 1;
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tcp_Cleanup(void)
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (g_wsa_started) {
|
||||||
|
WSACleanup();
|
||||||
|
g_wsa_started = 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void close_sock(int sock)
|
||||||
|
{
|
||||||
|
if (sock == TCP_INVALID_SOCKET)
|
||||||
|
return;
|
||||||
|
#ifdef _WIN32
|
||||||
|
closesocket((SOCKET)sock);
|
||||||
|
#else
|
||||||
|
close(sock);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int TcpClient_Connect(const char *ip, uint16_t port)
|
||||||
|
{
|
||||||
|
struct sockaddr_in addr;
|
||||||
|
SOCKET s;
|
||||||
|
|
||||||
|
memset(&addr, 0, sizeof(addr));
|
||||||
|
addr.sin_family = AF_INET;
|
||||||
|
addr.sin_port = htons(port);
|
||||||
|
#ifdef _WIN32
|
||||||
|
addr.sin_addr.s_addr = inet_addr(ip);
|
||||||
|
if (addr.sin_addr.s_addr == INADDR_NONE)
|
||||||
|
return TCP_INVALID_SOCKET;
|
||||||
|
#else
|
||||||
|
if (inet_pton(AF_INET, ip, &addr.sin_addr) <= 0)
|
||||||
|
return TCP_INVALID_SOCKET;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||||
|
if (s == (SOCKET)INVALID_SOCKET)
|
||||||
|
return TCP_INVALID_SOCKET;
|
||||||
|
if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
|
||||||
|
close_sock((int)s);
|
||||||
|
return TCP_INVALID_SOCKET;
|
||||||
|
}
|
||||||
|
return (int)s;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TcpClient_Send(int sock, const void *buf, size_t len)
|
||||||
|
{
|
||||||
|
int n;
|
||||||
|
if (sock == TCP_INVALID_SOCKET || !buf)
|
||||||
|
return -1;
|
||||||
|
#ifdef _WIN32
|
||||||
|
n = send((SOCKET)sock, (const char *)buf, (int)len, 0);
|
||||||
|
#else
|
||||||
|
n = (int)send(sock, buf, len, 0);
|
||||||
|
#endif
|
||||||
|
return (n == SOCKET_ERROR) ? -1 : n;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TcpClient_Recv(int sock, void *buf, size_t len)
|
||||||
|
{
|
||||||
|
int n;
|
||||||
|
if (sock == TCP_INVALID_SOCKET || !buf || len == 0)
|
||||||
|
return -1;
|
||||||
|
#ifdef _WIN32
|
||||||
|
n = recv((SOCKET)sock, (char *)buf, (int)len, 0);
|
||||||
|
#else
|
||||||
|
n = (int)recv(sock, buf, len, 0);
|
||||||
|
#endif
|
||||||
|
if (n == SOCKET_ERROR)
|
||||||
|
return -1;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TcpClient_Close(int sock)
|
||||||
|
{
|
||||||
|
close_sock(sock);
|
||||||
|
}
|
||||||
|
|
||||||
|
int TcpServer_Listen(uint16_t port)
|
||||||
|
{
|
||||||
|
struct sockaddr_in addr;
|
||||||
|
SOCKET s;
|
||||||
|
int opt = 1;
|
||||||
|
|
||||||
|
memset(&addr, 0, sizeof(addr));
|
||||||
|
addr.sin_family = AF_INET;
|
||||||
|
addr.sin_addr.s_addr = INADDR_ANY;
|
||||||
|
addr.sin_port = htons(port);
|
||||||
|
|
||||||
|
s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||||
|
if (s == (SOCKET)INVALID_SOCKET)
|
||||||
|
return TCP_INVALID_SOCKET;
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt)) != 0) {
|
||||||
|
close_sock((int)s);
|
||||||
|
return TCP_INVALID_SOCKET;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) != 0) {
|
||||||
|
close_sock(s);
|
||||||
|
return TCP_INVALID_SOCKET;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
|
||||||
|
close_sock((int)s);
|
||||||
|
return TCP_INVALID_SOCKET;
|
||||||
|
}
|
||||||
|
if (listen(s, TCP_DEFAULT_BACKLOG) != 0) {
|
||||||
|
close_sock((int)s);
|
||||||
|
return TCP_INVALID_SOCKET;
|
||||||
|
}
|
||||||
|
return (int)s;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TcpServer_Accept(int server_sock)
|
||||||
|
{
|
||||||
|
SOCKET client;
|
||||||
|
struct sockaddr_in peer;
|
||||||
|
socklen_t len = sizeof(peer);
|
||||||
|
|
||||||
|
if (server_sock == TCP_INVALID_SOCKET)
|
||||||
|
return TCP_INVALID_SOCKET;
|
||||||
|
#ifdef _WIN32
|
||||||
|
client = accept((SOCKET)server_sock, (struct sockaddr *)&peer, &len);
|
||||||
|
#else
|
||||||
|
client = accept(server_sock, (struct sockaddr *)&peer, &len);
|
||||||
|
#endif
|
||||||
|
if (client == (SOCKET)INVALID_SOCKET)
|
||||||
|
return TCP_INVALID_SOCKET;
|
||||||
|
return (int)client;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TcpServer_Close(int server_sock)
|
||||||
|
{
|
||||||
|
close_sock(server_sock);
|
||||||
|
}
|
||||||
46
src/TCP/tcp.h
Normal file
46
src/TCP/tcp.h
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#ifndef PC_HMI_TCP_H
|
||||||
|
#define PC_HMI_TCP_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 跨平台 TCP 通信模块
|
||||||
|
* - Windows: Winsock2
|
||||||
|
* - Linux/其他: BSD socket
|
||||||
|
* 提供 TCP 客户端:连接、发送、接收、断开
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#else
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
typedef int SOCKET;
|
||||||
|
#define INVALID_SOCKET (-1)
|
||||||
|
#define SOCKET_ERROR (-1)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define TCP_INVALID_SOCKET (-1)
|
||||||
|
#define TCP_DEFAULT_BACKLOG 5
|
||||||
|
|
||||||
|
/* 初始化/清理网络库(Windows 下必须最先/最后调用) */
|
||||||
|
int Tcp_Init(void);
|
||||||
|
void Tcp_Cleanup(void);
|
||||||
|
|
||||||
|
/* TCP 客户端 */
|
||||||
|
int TcpClient_Connect(const char *ip, uint16_t port);
|
||||||
|
int TcpClient_Send(int sock, const void *buf, size_t len);
|
||||||
|
int TcpClient_Recv(int sock, void *buf, size_t len);
|
||||||
|
void TcpClient_Close(int sock);
|
||||||
|
|
||||||
|
/* TCP 服务器 */
|
||||||
|
int TcpServer_Listen(uint16_t port);
|
||||||
|
int TcpServer_Accept(int server_sock);
|
||||||
|
void TcpServer_Close(int server_sock);
|
||||||
|
|
||||||
|
#endif /* PC_HMI_TCP_H */
|
||||||
119
src/main.c
Normal file
119
src/main.c
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
/* ============================================================================
|
||||||
|
* main.c - PC 端 HMI 菜单主程序
|
||||||
|
* 功能:菜单交互(主线程)+ TCP 服务器(独立线程),按 Q 退出
|
||||||
|
* ========================================================================== */
|
||||||
|
|
||||||
|
/* 标准输入输出:printf、fprintf、stderr 等 */
|
||||||
|
#include <stdio.h>
|
||||||
|
/* 标准库:malloc、exit、system 等 */
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
/* Windows:控制台无回显读键 _getch() */
|
||||||
|
#include <conio.h>
|
||||||
|
#else
|
||||||
|
/* Linux/Unix:终端属性与读键实现 */
|
||||||
|
#include <termios.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
/* 无回显读入一个字符(Linux 下替代 _getch) */
|
||||||
|
static int getch(void)
|
||||||
|
{
|
||||||
|
struct termios oldt, newt; /* 当前/新终端属性 */
|
||||||
|
int ch;
|
||||||
|
tcgetattr(STDIN_FILENO, &oldt); /* 保存当前终端属性 */
|
||||||
|
newt = oldt; /* 复制一份用于修改 */
|
||||||
|
newt.c_lflag &= ~(ICANON | ECHO); /* 关闭规范模式与回显,实现“按即得” */
|
||||||
|
tcsetattr(STDIN_FILENO, TCSANOW, &newt); /* 应用新属性 */
|
||||||
|
ch = getchar(); /* 读一个字符 */
|
||||||
|
tcsetattr(STDIN_FILENO, TCSANOW, &oldt); /* 恢复原终端属性 */
|
||||||
|
return ch;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* 菜单:MenuItem、MenuCtrl、Menu_InitPC、Menu_LoopStep、Menu_MapKey、KEY_* */
|
||||||
|
#include "menu.h"
|
||||||
|
/* TCP:Tcp_Init/Cleanup、TcpServer_Listen/Accept/Close、TcpClient_Send/Recv/Close */
|
||||||
|
#include "TCP/tcp.h"
|
||||||
|
/* 线程:Thread_Create、Thread_Join、thread_handle_t */
|
||||||
|
#include "thread_utils.h"
|
||||||
|
/* 远程显示 / TCP 服务器线程:server_thread_arg_t、tcp_server_thread_fn */
|
||||||
|
#include "remoteDisplay.h"
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* 程序入口
|
||||||
|
* ---------------------------------------------------------------------------- */
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
/* Windows:将控制台代码页设为 UTF-8,避免中文乱码(如“监听”“退出”等) */
|
||||||
|
system("chcp 65001 > nul");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* 初始化网络库(Windows 下必须最先调用,否则 Winsock 不可用) */
|
||||||
|
if (Tcp_Init() != 0) {
|
||||||
|
fprintf(stderr, "Tcp_Init failed\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 主线程持有的监听 socket:由服务器线程在 Listen 成功后回写,退出时主线程用来 Close 以唤醒 Accept */
|
||||||
|
int server_sock = TCP_INVALID_SOCKET;
|
||||||
|
/* 退出标志:主线程置 1 后,服务器线程在下一轮循环或 Accept 返回后退出 */
|
||||||
|
volatile int server_quit = 0;
|
||||||
|
/* 传给服务器线程的参数(端口、回写 socket 的指针、退出标志指针) */
|
||||||
|
server_thread_arg_t server_arg = {
|
||||||
|
.port = 7070, /* TCP 监听端口 */
|
||||||
|
.p_server_sock = &server_sock, /* 服务器线程将监听 socket 写回此处 */
|
||||||
|
.p_quit = &server_quit, /* 主线程通过此处通知服务器线程退出 */
|
||||||
|
};
|
||||||
|
|
||||||
|
thread_handle_t server_th; /* 服务器线程句柄,用于后续 Thread_Join 等待其结束 */
|
||||||
|
if (Thread_Create(tcp_server_thread_fn, &server_arg, &server_th) != 0) { /* 创建并启动服务器线程 */
|
||||||
|
fprintf(stderr, "Thread_Create(server) failed\n");
|
||||||
|
Tcp_Cleanup(); /* 创建失败则清理网络库后退出 */
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
Sleep(200); /* Windows:等待 200ms,让服务器线程完成 Listen 并写回 server_sock */
|
||||||
|
#else
|
||||||
|
usleep(200000); /* Linux:同上,200ms = 200000 微秒 */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
MenuCtrl ctrl; /* 菜单控制结构:当前选中项、0 级头节点等 */
|
||||||
|
int ch, key; /* ch:键盘原始字符;key:映射后的 KEY_U/KEY_D/KEY_ENT 等 */
|
||||||
|
|
||||||
|
Menu_InitPC(&ctrl); /* 初始化菜单树(运行界面/定值设置/查看数据等) */
|
||||||
|
printf("PC 端 HMI 菜单模拟启动(TCP 服务在单独线程,端口 7070)。\n");
|
||||||
|
printf("--------------------------------------------------\n");
|
||||||
|
Menu_LoopStep(&ctrl, KEY_NONE); /* 用 KEY_NONE 触发一次重绘,显示当前菜单 */
|
||||||
|
|
||||||
|
while (1) { /* 主循环:处理键盘并驱动菜单,直到用户按 Q 退出 */
|
||||||
|
printf("\n按键(W/S/A/D/Enter/Esc),Q 退出:");
|
||||||
|
#ifdef _WIN32
|
||||||
|
ch = _getch(); /* Windows:无回显读键 */
|
||||||
|
#else
|
||||||
|
ch = getch(); /* Linux:上面实现的 getch() */
|
||||||
|
#endif
|
||||||
|
if (ch == 'q' || ch == 'Q') { /* 用户选择退出 */
|
||||||
|
printf("\n正在退出...\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
key = Menu_MapKey(ch); /* 将字符映射为菜单键(W→KEY_U 等) */
|
||||||
|
if (key == KEY_NONE) { /* 非菜单键则忽略 */
|
||||||
|
printf(" (忽略)\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
Menu_LoopStep(&ctrl, key); /* 根据按键更新当前菜单项并重绘 */
|
||||||
|
}
|
||||||
|
|
||||||
|
server_quit = 1; /* 通知服务器线程退出:下一轮 while 或 Accept 返回后会结束循环 */
|
||||||
|
if (server_sock != TCP_INVALID_SOCKET) { /* 若监听 socket 已有效(服务器线程已启动成功) */
|
||||||
|
TcpServer_Close(server_sock); /* 关闭监听,使 Accept 返回,服务器线程得以退出 */
|
||||||
|
server_sock = TCP_INVALID_SOCKET; /* 避免重复关闭 */
|
||||||
|
}
|
||||||
|
Thread_Join(server_th); /* 等待服务器线程完全退出 */
|
||||||
|
Tcp_Cleanup(); /* 清理网络库(Windows 下必须调用) */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
164
src/menu.c
Normal file
164
src/menu.c
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
#include "menu.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* 简单的静态菜单树:
|
||||||
|
*
|
||||||
|
* 层级 0: [运行界面] [定值设置] [查看数据]
|
||||||
|
* │ │
|
||||||
|
* 层级 1: (子菜单...) (子菜单...)
|
||||||
|
*/
|
||||||
|
|
||||||
|
static MenuItem g_menuRun;
|
||||||
|
static MenuItem g_menuSet;
|
||||||
|
static MenuItem g_menuSee;
|
||||||
|
static MenuItem g_menuSet1;
|
||||||
|
static MenuItem g_menuSet2;
|
||||||
|
|
||||||
|
void Menu_InitPC(MenuCtrl *ctrl)
|
||||||
|
{
|
||||||
|
memset(&g_menuRun, 0, sizeof(g_menuRun));
|
||||||
|
memset(&g_menuSet, 0, sizeof(g_menuSet));
|
||||||
|
memset(&g_menuSee, 0, sizeof(g_menuSee));
|
||||||
|
memset(&g_menuSet1, 0, sizeof(g_menuSet1));
|
||||||
|
memset(&g_menuSet2, 0, sizeof(g_menuSet2));
|
||||||
|
|
||||||
|
/* 0 级菜单 */
|
||||||
|
g_menuRun.name = "运行界面";
|
||||||
|
g_menuRun.tip = "显示运行状态";
|
||||||
|
g_menuRun.level = 0;
|
||||||
|
g_menuRun.pos = 0;
|
||||||
|
|
||||||
|
g_menuSet.name = "定值设置";
|
||||||
|
g_menuSet.tip = "进入定值设置菜单";
|
||||||
|
g_menuSet.level = 0;
|
||||||
|
g_menuSet.pos = 1;
|
||||||
|
|
||||||
|
g_menuSee.name = "查看数据";
|
||||||
|
g_menuSee.tip = "查看测量值和记录";
|
||||||
|
g_menuSee.level = 0;
|
||||||
|
g_menuSee.pos = 2;
|
||||||
|
|
||||||
|
/* 0 级链表关系 */
|
||||||
|
g_menuRun.before = &g_menuSee;
|
||||||
|
g_menuRun.behind = &g_menuSet;
|
||||||
|
|
||||||
|
g_menuSet.before = &g_menuRun;
|
||||||
|
g_menuSet.behind = &g_menuSee;
|
||||||
|
|
||||||
|
g_menuSee.before = &g_menuSet;
|
||||||
|
g_menuSee.behind = &g_menuRun;
|
||||||
|
|
||||||
|
/* 1 级菜单:定值设置下的两个子菜单 */
|
||||||
|
g_menuSet1.name = "保护定值";
|
||||||
|
g_menuSet1.tip = "编辑保护定值";
|
||||||
|
g_menuSet1.level = 1;
|
||||||
|
g_menuSet1.pos = 0;
|
||||||
|
g_menuSet1.higher = &g_menuSet;
|
||||||
|
|
||||||
|
g_menuSet2.name = "通信参数";
|
||||||
|
g_menuSet2.tip = "编辑通信参数";
|
||||||
|
g_menuSet2.level = 1;
|
||||||
|
g_menuSet2.pos = 1;
|
||||||
|
g_menuSet2.higher = &g_menuSet;
|
||||||
|
|
||||||
|
g_menuSet1.before = &g_menuSet2;
|
||||||
|
g_menuSet1.behind = &g_menuSet2;
|
||||||
|
|
||||||
|
g_menuSet2.before = &g_menuSet1;
|
||||||
|
g_menuSet2.behind = &g_menuSet1;
|
||||||
|
|
||||||
|
g_menuSet.lower = &g_menuSet1;
|
||||||
|
|
||||||
|
/* 初始化控制结构 */
|
||||||
|
ctrl->head0 = &g_menuRun;
|
||||||
|
ctrl->current = &g_menuRun;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 控制台绘制当前菜单状态 */
|
||||||
|
static void Menu_Draw(const MenuCtrl *ctrl)
|
||||||
|
{
|
||||||
|
const MenuItem *cur = ctrl->current;
|
||||||
|
const MenuItem *p;
|
||||||
|
|
||||||
|
printf("\n==== PC HMI 菜单模拟 ====\n");
|
||||||
|
printf("使用 W/S/A/D/Enter/Esc 操作,Ctrl+C 退出。\n\n");
|
||||||
|
|
||||||
|
/* 显示 0 级菜单 */
|
||||||
|
printf("[0级菜单]: ");
|
||||||
|
p = ctrl->head0;
|
||||||
|
if (!p) {
|
||||||
|
printf("(空)\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const MenuItem *start = p;
|
||||||
|
do {
|
||||||
|
if (p == cur)
|
||||||
|
printf(" [*%s*] ", p->name);
|
||||||
|
else
|
||||||
|
printf(" %s ", p->name);
|
||||||
|
p = p->behind;
|
||||||
|
} while (p && p != start);
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
/* 若当前有下级,则显示 1 级菜单 */
|
||||||
|
if (cur->lower) {
|
||||||
|
printf(" 子菜单:\n");
|
||||||
|
p = cur->lower;
|
||||||
|
start = p;
|
||||||
|
do {
|
||||||
|
printf(" - %s\n", p->name);
|
||||||
|
p = p->behind;
|
||||||
|
} while (p && p != start);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 提示信息 */
|
||||||
|
printf("\n提示: %s\n", cur->tip ? cur->tip : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Menu_LoopStep(MenuCtrl *ctrl, int key)
|
||||||
|
{
|
||||||
|
MenuItem *cur = ctrl->current;
|
||||||
|
|
||||||
|
switch (key) {
|
||||||
|
case KEY_U:
|
||||||
|
if (cur->before)
|
||||||
|
cur = cur->before;
|
||||||
|
break;
|
||||||
|
case KEY_D:
|
||||||
|
if (cur->behind)
|
||||||
|
cur = cur->behind;
|
||||||
|
break;
|
||||||
|
case KEY_L:
|
||||||
|
if (cur->higher)
|
||||||
|
cur = cur->higher;
|
||||||
|
break;
|
||||||
|
case KEY_R:
|
||||||
|
case KEY_ENT:
|
||||||
|
if (cur->lower)
|
||||||
|
cur = cur->lower;
|
||||||
|
printf("\n>>> 进入 [%s] 功能(模拟用户层)...\n", cur->name);
|
||||||
|
break;
|
||||||
|
case KEY_ESC:
|
||||||
|
if (cur->higher)
|
||||||
|
cur = cur->higher;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrl->current = cur;
|
||||||
|
Menu_Draw(ctrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Menu_MapKey(int ch)
|
||||||
|
{
|
||||||
|
if (ch == 'w' || ch == 'W') return KEY_U;
|
||||||
|
if (ch == 's' || ch == 'S') return KEY_D;
|
||||||
|
if (ch == 'a' || ch == 'A') return KEY_L;
|
||||||
|
if (ch == 'd' || ch == 'D') return KEY_R;
|
||||||
|
if (ch == '\r' || ch == '\n') return KEY_ENT;
|
||||||
|
if (ch == 27) return KEY_ESC; /* ESC */
|
||||||
|
return KEY_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
56
src/menu.h
Normal file
56
src/menu.h
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#ifndef PC_HMI_MENU_H
|
||||||
|
#define PC_HMI_MENU_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PC 端菜单模拟环境
|
||||||
|
*
|
||||||
|
* 目标:
|
||||||
|
* - 用纯 C 语言在控制台模拟嵌入式 HMI 的菜单逻辑
|
||||||
|
* - 键值映射:W/S/A/D/Enter/Esc → 上/下/左/右/确认/退出
|
||||||
|
* - 结构设计尽量贴近原工程的 g_tMenuCtrl / Menu_Route / Menu_Show_Proc
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
typedef struct MenuItem MenuItem;
|
||||||
|
|
||||||
|
struct MenuItem {
|
||||||
|
const char *name; /* 菜单名称 */
|
||||||
|
const char *tip; /* 底部提示信息 */
|
||||||
|
|
||||||
|
MenuItem *higher; /* 上级菜单 */
|
||||||
|
MenuItem *lower; /* 下级第一个菜单 */
|
||||||
|
MenuItem *before; /* 同级上一个 */
|
||||||
|
MenuItem *behind; /* 同级下一个 */
|
||||||
|
|
||||||
|
uint8_t level; /* 菜单级别:0/1/2/... */
|
||||||
|
uint8_t pos; /* 在同级菜单中的序号(从 0 开始) */
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
MenuItem *head0; /* 0 级菜单头结点 */
|
||||||
|
MenuItem *current; /* 当前选中的菜单结点 */
|
||||||
|
} MenuCtrl;
|
||||||
|
|
||||||
|
/* 初始化一棵简单的测试菜单树 */
|
||||||
|
void Menu_InitPC(MenuCtrl *ctrl);
|
||||||
|
|
||||||
|
/* 处理一帧输入,并在控制台绘制菜单 */
|
||||||
|
void Menu_LoopStep(MenuCtrl *ctrl, int key);
|
||||||
|
|
||||||
|
/* 键值定义:与嵌入式逻辑对应的抽象键 */
|
||||||
|
enum {
|
||||||
|
KEY_NONE = 0,
|
||||||
|
KEY_U,
|
||||||
|
KEY_D,
|
||||||
|
KEY_L,
|
||||||
|
KEY_R,
|
||||||
|
KEY_ENT,
|
||||||
|
KEY_ESC,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* 从键盘输入字符映射到 KEY_* 抽象键(W/S/A/D/Enter/Esc) */
|
||||||
|
int Menu_MapKey(int ch);
|
||||||
|
|
||||||
|
#endif /* PC_HMI_MENU_H */
|
||||||
|
|
||||||
53
src/remoteDisplay.c
Normal file
53
src/remoteDisplay.c
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* remoteDisplay.c - TCP 服务器线程实现
|
||||||
|
* 逻辑:等待客户端连接,循环接收消息并原样回发(echo),直到客户端断开或主线程请求退出
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "TCP/tcp.h"
|
||||||
|
#include "remoteDisplay.h"
|
||||||
|
|
||||||
|
void tcp_server_thread_fn(void* arg)
|
||||||
|
{
|
||||||
|
server_thread_arg_t* ctx = (server_thread_arg_t*)arg; /* 强转为参数结构体 */
|
||||||
|
int server_sock = TcpServer_Listen((uint16_t)ctx->port); /* 在指定端口创建监听 socket */
|
||||||
|
if (server_sock == TCP_INVALID_SOCKET) { /* 监听失败(端口占用或权限等) */
|
||||||
|
fprintf(stderr, "[TCP Server] TcpServer_Listen(%d) failed\n", ctx->port);
|
||||||
|
return; /* 线程直接退出,主线程通过 server_sock 仍为 INVALID 可知失败 */
|
||||||
|
}
|
||||||
|
*ctx->p_server_sock = server_sock; /* 回写监听 socket,主线程可用来关闭以结束 Accept 阻塞 */
|
||||||
|
printf("[TCP Server] 监听端口 %d,等待客户端连接...\n", ctx->port);
|
||||||
|
|
||||||
|
char recv_buf[1024]; /* 接收缓冲区,用于收客户端数据并回发 */
|
||||||
|
|
||||||
|
while (!*ctx->p_quit) { /* 主线程未请求退出时一直循环 */
|
||||||
|
int client = TcpServer_Accept(server_sock); /* 阻塞等待一个客户端连接 */
|
||||||
|
if (client == TCP_INVALID_SOCKET) { /* Accept 失败(含:主线程已关闭 server_sock) */
|
||||||
|
if (*ctx->p_quit) /* 若是因退出请求导致,则跳出循环 */
|
||||||
|
break;
|
||||||
|
continue; /* 否则忽略本次错误,继续下一次 Accept */
|
||||||
|
}
|
||||||
|
printf("[TCP Server] 客户端连接成功\n");
|
||||||
|
/* 只要该客户端还连着,就循环等待消息并回发 */
|
||||||
|
while (!*ctx->p_quit) {
|
||||||
|
printf("[TCP Server] 等待客户端消息\n");
|
||||||
|
memset(recv_buf, 0, sizeof(recv_buf));
|
||||||
|
int n = TcpClient_Recv(client, recv_buf, sizeof(recv_buf) - 1);
|
||||||
|
if (n > 0) {
|
||||||
|
printf("recv: %s\n", recv_buf);
|
||||||
|
TcpClient_Send(client, recv_buf, (size_t)n);
|
||||||
|
} else {
|
||||||
|
/* n==0 表示对方关闭连接,n<0 表示出错,退出本连接循环 */
|
||||||
|
printf("[TCP Server] 客户端关闭连接\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TcpClient_Close(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
TcpServer_Close(server_sock); /* 关闭监听 socket */
|
||||||
|
*ctx->p_server_sock = TCP_INVALID_SOCKET; /* 通知主线程 socket 已关闭,避免重复 Close */
|
||||||
|
printf("[TCP Server] 已退出\n");
|
||||||
|
}
|
||||||
21
src/remoteDisplay.h
Normal file
21
src/remoteDisplay.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#ifndef REMOTE_DISPLAY_H
|
||||||
|
#define REMOTE_DISPLAY_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 远程显示 / TCP 服务器相关接口
|
||||||
|
* - server_thread_arg_t: TCP 服务器线程参数
|
||||||
|
* - tcp_server_thread_fn: 服务器线程入口函数(用于 Thread_Create)
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int port; /* 监听端口号(如 7070) */
|
||||||
|
int* p_server_sock; /* 线程内赋值为监听 socket,主线程用其关闭监听 */
|
||||||
|
volatile int* p_quit; /* 主线程置 1 通知线程退出 */
|
||||||
|
} server_thread_arg_t;
|
||||||
|
|
||||||
|
/* TCP 服务器线程函数:在独立线程中监听端口并处理客户端收/发 */
|
||||||
|
void tcp_server_thread_fn(void* arg);
|
||||||
|
|
||||||
|
#endif /* REMOTE_DISPLAY_H */
|
||||||
|
|
||||||
107
src/thread_utils.c
Normal file
107
src/thread_utils.c
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* thread_utils.c - 跨平台线程封装实现
|
||||||
|
* - Windows: _beginthreadex / WaitForSingleObject / CloseHandle
|
||||||
|
* - Linux: pthread_create / pthread_join
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "thread_utils.h"
|
||||||
|
#include <stdint.h> /* uintptr_t,用于 Windows 线程句柄转换 */
|
||||||
|
#include <stdlib.h> /* malloc / free */
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <process.h> /* _beginthreadex */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
/*
|
||||||
|
* Windows 线程入口:__stdcall,参数为 Thread_Create 传入的包装结构体指针
|
||||||
|
* 从结构中取出用户函数与参数,释放结构体后调用用户函数
|
||||||
|
*/
|
||||||
|
static unsigned int __stdcall thread_wrapper(void* arg)
|
||||||
|
{
|
||||||
|
struct {
|
||||||
|
thread_fn_t fn;
|
||||||
|
void* arg;
|
||||||
|
}* p = (void*)arg;
|
||||||
|
thread_fn_t fn = p->fn; /* 用户提供的线程函数 */
|
||||||
|
void* fn_arg = p->arg; /* 传给用户函数的参数 */
|
||||||
|
free(p); /* 包装结构体仅在此处使用,用完即释 */
|
||||||
|
fn(fn_arg); /* 调用用户线程函数 */
|
||||||
|
return 0; /* _beginthreadex 要求返回 unsigned int */
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
/*
|
||||||
|
* Linux 线程入口:参数为 Thread_Create 传入的包装结构体指针
|
||||||
|
* 从结构中取出用户函数与参数,释放结构体后调用用户函数
|
||||||
|
*/
|
||||||
|
static void* thread_wrapper(void* arg)
|
||||||
|
{
|
||||||
|
struct {
|
||||||
|
thread_fn_t fn;
|
||||||
|
void* arg;
|
||||||
|
}* p = arg;
|
||||||
|
thread_fn_t fn = p->fn;
|
||||||
|
void* fn_arg = p->arg;
|
||||||
|
free(p);
|
||||||
|
fn(fn_arg);
|
||||||
|
return NULL; /* pthread 入口要求返回 void* */
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 创建新线程,在新线程中执行 fn(arg)
|
||||||
|
*
|
||||||
|
* fn: 线程函数,类型 void fn(void* arg)
|
||||||
|
* arg: 传给 fn 的参数,由调用方保证在线程使用期间有效
|
||||||
|
* out_handle: 输出线程句柄,用于后续 Thread_Join 等待
|
||||||
|
*
|
||||||
|
* 返回: 0 成功,-1 失败(参数无效、malloc 失败或系统创建线程失败)
|
||||||
|
*/
|
||||||
|
int Thread_Create(thread_fn_t fn, void* arg, thread_handle_t* out_handle)
|
||||||
|
{
|
||||||
|
if (!fn || !out_handle)
|
||||||
|
return -1;
|
||||||
|
/* 在堆上分配包装结构,因线程启动前就返回,线程入口需要独立副本 */
|
||||||
|
struct {
|
||||||
|
thread_fn_t fn;
|
||||||
|
void* arg;
|
||||||
|
}* p = malloc(sizeof(*p));
|
||||||
|
if (!p)
|
||||||
|
return -1;
|
||||||
|
p->fn = fn;
|
||||||
|
p->arg = arg;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
{
|
||||||
|
uintptr_t t = _beginthreadex(NULL, 0, thread_wrapper, p, 0, NULL);
|
||||||
|
if (t == 0) { /* 0 表示创建失败 */
|
||||||
|
free(p);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
*out_handle = (HANDLE)(uintptr_t)t; /* 保存句柄供 Thread_Join 使用 */
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (pthread_create(out_handle, NULL, thread_wrapper, p) != 0) {
|
||||||
|
free(p);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 等待指定线程结束,并释放系统资源
|
||||||
|
*
|
||||||
|
* handle: Thread_Create 返回的线程句柄
|
||||||
|
* Windows 下会 CloseHandle,之后 handle 不可再使用
|
||||||
|
*/
|
||||||
|
void Thread_Join(thread_handle_t handle)
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (handle != NULL && handle != (HANDLE)0)
|
||||||
|
WaitForSingleObject(handle, INFINITE); /* 阻塞直到线程退出 */
|
||||||
|
if (handle != NULL && handle != (HANDLE)0)
|
||||||
|
CloseHandle(handle); /* 释放线程内核对象 */
|
||||||
|
#else
|
||||||
|
pthread_join(handle, NULL); /* 阻塞直到线程退出,不取返回值 */
|
||||||
|
#endif
|
||||||
|
}
|
||||||
35
src/thread_utils.h
Normal file
35
src/thread_utils.h
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#ifndef PC_HMI_THREAD_UTILS_H
|
||||||
|
#define PC_HMI_THREAD_UTILS_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 跨平台线程封装
|
||||||
|
* - Windows: _beginthreadex
|
||||||
|
* - Linux: pthread
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <process.h>
|
||||||
|
#include <windows.h>
|
||||||
|
typedef HANDLE thread_handle_t;
|
||||||
|
#else
|
||||||
|
#include <pthread.h>
|
||||||
|
typedef pthread_t thread_handle_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* 线程函数类型:void thread_fn(void* arg) */
|
||||||
|
typedef void (*thread_fn_t)(void* arg);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 创建线程,fn 在新区程中执行,arg 传给 fn
|
||||||
|
* 返回 0 成功,非 0 失败;out_handle 用于后续 Thread_Join
|
||||||
|
*/
|
||||||
|
int Thread_Create(thread_fn_t fn, void* arg, thread_handle_t* out_handle);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 等待线程结束
|
||||||
|
*/
|
||||||
|
void Thread_Join(thread_handle_t handle);
|
||||||
|
|
||||||
|
#endif /* PC_HMI_THREAD_UTILS_H */
|
||||||
Reference in New Issue
Block a user