OpenCL编程指南-6.2程序对象

news2024/11/16 23:29:39

创建和构建程序

要创建程序对象,可以传入OpenCL C源代码文本,或者利用程序二进制码来创建。由OpenCL C源代码创建程序对象是开发人员创建程序对象的一般做法。OpenCL C程序的源代码放在一个外部文件中(例如,就像我们的示例代码中的.cl文件),应用程序将使用clcreateProgramwithsource()函数由源代码创建程序对象,另一种做法是由为设备预编译的二进制码创建程序对象,这个方法将在后面讨论。现在我们先来介绍如何使用clcreateProgramwithSource(从源代码创建程序对象:

cl_program clCreateProgramWithSource(cl_context context,
                                     cl_uint count,
                                     const char **strings,
                                     const size_t *lengths,
                                     cl_int *errcode_ret)
/*
context 创建对象的上下文
count strings 参数中字符串指针的数目
strings 包含count个字符串的指针。将这个参数中包含的所有字符串结合在一起,就构成了创建程序对象的完整的源代码
lengths 这是一个大小为count的数组,包含strings中各个元素的字符数。这个参数可以为NULL,在这种情况下,则认为字符串是以null终止的
errcode_ret 如果非NULL,函数返回的错误代码将由这个参数返回
*/

调用clCreateProgramwWithSource()会使用传入的源代码创建一个新的程序对象。返回值是与上下文关联的一个新程序对象。一般地,调用clCreateProgramWithSource()之后,下一步就是使用clBuildProgram()构建这个程序对象:

cl_int clBuildProgram(cl_program program,
                      cl_uint num_devices,
                      const cl_device_id *device_list,
                      const char *options,
                      void(CL_CALLBACK *pfn_notify)
                             (cl_program program,
                              void *user_data),
                      void *user_data)
                 
/*
program 一个合法的程序对象
num_devices 要构建程序对象的设备数
device_list 这是一个数组,包含将构建程序的所有num_devices的设备ID.
            如果device_list为NULL,则为创建程序对象的上下文中所创建的设备构建这个程序对象
           
options 这是一个字符串,包含程序的构建选项
pfn_notify 使用pfn_notify参数可以完成异步构建。如果pfn_notify为NULL,那么完成构建之前clBuildProgram不会返回到调用者。
           不过,如果用户传入pfn_notify,那么clBuildProgram可以在完成构建之前返回,并在程序构建过程中调用pfn_notify.
           这个参数的一个用法是让所有构建排队,使之异步完成,与此同时应用程序可以完成其他工作。
           不过需要说明的是,即使传入了pfn_notify,OpenCL实现仍可能选择以同步方式返回。
           如果确实需要异步构建应用程序,保证异步执行最可靠的方法是在单独的应用程序线程中执行构建

user_data 如果pfn_notify不为NULL,那么这是作为pfn_notify的参数传入的任意数据
*/

调用clBuildProgram()会为指定的所有设备构建程序对象(或者,如果没有指定设备列表,则为与上下文关联的所有设备构建程序对象)。这一步实际上等价于在一个C程序上调用编译器/链接器。options参数包含一个构建选项字符串,包括预处理器定义和各种优化和代码生成选项(例如,– DUSE_FEATURE=1-cl-mad-enable)。这些选项将在最后详细说明。可执行代码存储在所有目标设备的程序对象中。如果程序在所有设备上成功构建,则clBuildProgram()函数返回CL_SUCCESS;否则,会返回一个错误码。如果存在一个构建错误,则可以调用clGetProgramBuildInfo()并指定param_name为CL_PROGRAM_BUILD_LOG来查看详细的构建日志。

cl_int clGetProgramBuildInfo(cl_program program,
                             cl_device_id device,
                             cl_program_build_info param_name,
                             size_t param_value_size,
                             void *param_value,
                             size_t *param_value_size_ret)

/*
program 一个合法的程序对象
device 要获取相应构建信息的设备。这必须是要构建程序的设备之一。
       程序要为指定的设备构建,对于不同的设备可能会有不同的错误,所以必须单独查询日志

param_name 要查询的参数。接受以下参数:
           CL_PROGRAM_BUILD_STATUS(cl_build_status)返回构建的状态,可以是以下状态:
           CL_BUILD_NONE: 未完成任何构建
           CL_BUILD_ERROR: 最后一次构建有一个错误
           CL_BUILD_SUCCESS: 最后一次构建成功
           CL_BUILD_IN_PROGRESS: 还在运行一个异步构建。如果为clBuildProgram 提供了一个函数指针,则可能出现这个状态。
           CL_PROGRAM_BUILD_OPTIONS(char[]): 返回一个字符串,其中包含传递给clBuildProgram的options参数。
           CL_PROGRAM_BUILD_LOG(char[]): 返回一个字符串,其中包含为这个设备最后一次构建的构建日志。

param_value_size param_value的字节数,这必须足够大,足以存储指定查询的结果。param_value 存储查询结果的内存位置的指针。
param_value_size_ret 实际复制到param_value的字节数。
*/

综合以上内容,下面代码展示了如何从源代码创建一个程序对象,为所有关联的设备构建这个程序对象,并为一个设备查询构建结果。

cl_program CreateProgram(cl_context context, cl_device_id device, const char* fileName)
{
	cl_int errNum;
	cl_program program;

	std::ifstream kernelFile(fileName, std::ios::in);
	if (!kernelFile.is_open())
	{
		std::cerr << "Failed to open file for reading: " << fileName << std::endl;
		return NULL;
	}

	std::ostringstream oss;
	oss << kernelFile.rdbuf();

	std::string srcStdStr = oss.str();
	const char* srcStr = srcStdStr.c_str();
	program = clCreateProgramWithSource(context, 1,
		(const char**)&srcStr,
		NULL, NULL);
	if (program == NULL)
	{
		std::cerr << "Failed to create CL program from source." << std::endl;
		return NULL;
	}

	errNum = clBuildProgram(program, 0, NULL, NULL, NULL, NULL);
	if (errNum != CL_SUCCESS)
	{
		// Determine the reason for the error
		char buildLog[16384];
		clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG,
			sizeof(buildLog), buildLog, NULL);

		std::cerr << "Error in kernel: " << std::endl;
		std::cerr << buildLog;
		clReleaseProgram(program);
		return NULL;
	}

	return program;
}

取自于https://blog.csdn.net/qq_36314864/article/details/130606515里面HelloWorld代码

程序构建选项

本节前面提到,clBuildProgram()取一个字符串参数(const char *options),控制多种不同类型的构建选项:
1)预处理器选项。
2)浮点数选项(数学性质)。·
3)优化选项。
4)杂项选项。

与C或C++编译器类似,OpenCL有大量选项来控制程序编译的行为。OpenCL程序编译器有一个预处理器,可以在clBuildProgram()的 options参数中定义预处理器的选项。表6-1列出了可以为预处理器指定的选项。
在这里插入图片描述
关于定义预处理器变量,需要说明的是,对于构建程序的所有设备,程序对象的内核函数签名必须相同。例如,有以下内核源代码:

#ifdef SOME_MACRO
__kernel void my_kernel(__global const float* p)
{
    //...
}
#else //!SOME_MACRO
__kernel void my_kernel(__global const int* p)
{
    //...
}
#endif //!SOME_MACRO

在这个例子中,my_kernel()函数签名根据SOME_MACRO的值会有所不同(其参数是__global const float*__global const int float*)。这本身并不成问题。不过,如果为同一个程序对象的各个设备单独调用clBuildProgram(),一旦为一个设备传人-DSOME_MACRO,如果没有为另一个设备定义SOME_MACRO,得到的内核在程序中会有不同的函数签名,这会失败。也就是说,对于构建一个程序对象的所有设备,内核函数签名必须相同。可以传入不同的预处理器指令,从而对各个设备以不同方式影响程序构建,这是可以接受的,但是不能改变内核函数签名。对于构建一个程序对象的各个设备,内核函数签名必须完全相同。

OpenCL程序编译器还有一些选项来控制浮点数学运算的行为。这些选项见表6-2。与预处理器选项类似,这些选项可以在clBuildProgram()的options参数中指定。
在这里插入图片描述
还可以控制OpenCL C编译器运行完成的优化
在这里插入图片描述
最后,列出了OpenCL C编译器接受的最后一组杂项选项。
在这里插入图片描述

由二进制码创建程序

除了从源代码创建程序对象外,还有一种方法是从二进制码创建程序对象。程序二进制码是对应一个指定设备的源代码的编译版本。程序二进制码的数据格式是不透明的。也就是说,二进制码的内容没有标准化的格式。一个OpenCL实现可能选择将程序的可执行版本存储在二进制码中,也可能选择存储一个中间表示,这个中间表示可以在运行时转换为可执行版本。

由于程序二进制码已经经过编译(可能部分编译为中间表示,或者完全编译为一个可执行文件),所以加载时速度会更快,需要的内存更少,从而可以减少应用程序的加载时间。使用程序二进制码的另一个好处是保护知识产权:可以在安装时生成程序二进制码,而不用将原始OpenCL C源代码存储在磁盘上。一般情况是在安装时生成程序二进制码,或者首先运行并在磁盘上存储二进制码以备以后加载。生成程序二进制码的做法是,首先使用OpenCL从源代码构建程序,然后查询得到的程序二进制码。要从一个已构建的程序得到程序的二进制码,可以使用clGetProgramInfo():

cl_int clGetProgramInfo(cl_program program,
                        cl_program_info param_name,
                        size_t param_value_size,
                        void *param_value,
                        size_t *param_value_size_ret)

program 一个合法的程序对象
param_name 要向程序查询哪一个参数的信息,可以接受以下参数:
           CL_PROGRAM_REFERENCE_COUNT(cl_uint): 程序的引用数.这可以用于标识是否存在资源泄露
           CL_PROGRAM_CONTEXT(cl_context):程序关联到哪个上下文
           CL_PROGRAM_NUM_DEVICES(cl_uint):程序关联的设备数
           CL_PROGRAM_DEVICES(cl_device_id[]):返回一个cl_device_id数组,其中包含程序关联的设备的ID
           CL_PROGRAM_SOURCE(char[]): 将创建程序所用的所有源字符串连接到一个字符串中返回。如果程序对象由二进制码创建,则不会返回任何字符。
           CL_PROGRAM_BINARY_SIZES(size_t[]): 返回一个size_t数组,其大小等于与程序关联的设备数。每个元素分别是各设备二进制码的大小。
           CL_PROGRAM_BINARIES(unsigned char*[]): 返回一个unsigned char*数组,其中各个元素包含相应设备的程序二进制码。各个数组的大小可以由CL_PROGRAM_BINARY_SIZES查询的结果确定。

param_value_size param_value的字节数
param_value 存储结果的位置指针。这个位置必须分配足够的字节来存储请求的结果。
param_value_size_ret 实际写至param_value的字节数

查询得到程序对象的二进制码之后,可以将这个二进制码存储在磁盘上以备将来运行。下一次运行程序时,可以使用clCreateProgramwithBinary()创建程序对象:

cl_program clCreateProgramWithBinary(cl_context context,
                                     cl_uint num_devices,
                                     const cl_device_id *device_list,
                                     const unsigned char **binaries,
                                     cl_int *binary_status,
                                     cl_int *errcode_ret)

context 创建程序对象的上下文
num_devices 构建程序对象的设备数
device_list 这是一个数组,其中包含将构建程序的所有num_devices的设备ID.
            如果device_list为NULL,将为创建程序对象的上下文中创建的所有设备构建程序对象

lengths 这是大小为count的一个数组,包含binaries中各元素的字节数
binaries 这是一个指针数组,所指向的字节包含对应各个设备的各个程序二进制码。
         各个二进制码的大小必须为传入lengths中相关元素的大小

binary_status 这个数组包含是否成功加载各个设备二进制码的结果。
              如果成功,各元素会设置为CL_SUCCESS;如果失败,会报告一个错误码

errode_ret 如果这个参数不为NULL,则函数返回的错误代码将由这个参数返回

HelloBinaryWorld示例展示了如何由二进制码创建一个程序。这里对之前 HelloWorld示例稍做修改。两者的区别是,这一章的HelloBinaryWorld示例会在应用首次运行时尝试获取程序二进制码,并把它存储到HelloWorld cl. bin。将来执行时,应用程序会从生成的这个二进制码加载程序。完成这个缓存的主要逻辑在代码清单6-2中给出,这个代码取自HelloBinaryWorld 的main()函数。

首次运行时,缓存程序二进制码

program = CreateProgramFromBinary(context, device, "HelloBinaryWorld.cl.bin");
if (program == NULL)
{
	std::cout << "Binary not loaded, create from source..." << std::endl;
	program = CreateProgram(context, device, "HelloWorld.cl");
	if (program == NULL)
	{
		Cleanup(context, commandQueue, program, kernel, memObjects);
		return 1;
	}

	std::cout << "Save program binary for future run..." << std::endl;
	if (SaveProgramBinary(program, device, "HelloWorld.cl.bin") == false)
	{
		std::cerr << "Failed to write program binary" << std::endl;
		Cleanup(context, commandQueue, program, kernel, memObjects);
		return 1;
	}
}
else
{
	std::cout << "Read program from binary." << std::endl;
}

首先来看SaveProgramBinary(),这是查询和存储程序二进制码的函数。这个函数假设已经由源代码创建并构建了程序对象。SaveProgramBinary()的代码见代码清单6-3。这个函数首先调用clGetProgramInfo()查询与程序关联的设备数。接下来获取与各个设备关联的设备ID。得到设备列表之后,这个函数再获取对应各个设备的各个程序二进制码的大小以及程序二进码本身。获取所有程序二进码之后,函数会循环处理设备,找出作为SaveProgramBinary()的参数传入的设备。这个程序二进制码最终会使用fwrite()写至磁盘上的文件Helloworld.cl.bin。

bool SaveProgramBinary(cl_program program, cl_device_id device, const char* fileName)
{
	cl_uint numDevices = 0;
	cl_int errNum;

	// 1 - Query for number of devices attached to program
	errNum = clGetProgramInfo(program, CL_PROGRAM_NUM_DEVICES, sizeof(cl_uint),
		&numDevices, NULL);
	if (errNum != CL_SUCCESS)
	{
		std::cerr << "Error querying for number of devices." << std::endl;
		return false;
	}

	// 2 - Get all of the Device IDs
	cl_device_id* devices = new cl_device_id[numDevices];
	errNum = clGetProgramInfo(program, CL_PROGRAM_DEVICES,
		sizeof(cl_device_id) * numDevices,
		devices, NULL);
	if (errNum != CL_SUCCESS)
	{
		std::cerr << "Error querying for devices." << std::endl;
		delete[] devices;
		return false;
	}

	// 3 - Determine the size of each program binary
	size_t* programBinarySizes = new size_t[numDevices];
	errNum = clGetProgramInfo(program, CL_PROGRAM_BINARY_SIZES,
		sizeof(size_t) * numDevices,
		programBinarySizes, NULL);
	if (errNum != CL_SUCCESS)
	{
		std::cerr << "Error querying for program binary sizes." << std::endl;
		delete[] devices;
		delete[] programBinarySizes;
		return false;
	}

	unsigned char** programBinaries = new unsigned char* [numDevices];
	for (cl_uint i = 0; i < numDevices; i++)
	{
		programBinaries[i] = new unsigned char[programBinarySizes[i]];
	}

	// 4 - Get all of the program binaries
	errNum = clGetProgramInfo(program, CL_PROGRAM_BINARIES, sizeof(unsigned char*) * numDevices,
		programBinaries, NULL);
	if (errNum != CL_SUCCESS)
	{
		std::cerr << "Error querying for program binaries" << std::endl;

		delete[] devices;
		delete[] programBinarySizes;
		for (cl_uint i = 0; i < numDevices; i++)
		{
			delete[] programBinaries[i];
		}
		delete[] programBinaries;
		return false;
	}

	// 5 - Finally store the binaries for the device requested out to disk for future reading.
	for (cl_uint i = 0; i < numDevices; i++)
	{
		// Store the binary just for the device requested.  In a scenario where
		// multiple devices were being used you would save all of the binaries out here.
		if (devices[i] == device)
		{
			FILE* fp = fopen(fileName, "wb");
			fwrite(programBinaries[i], 1, programBinarySizes[i], fp);
			fclose(fp);
			break;
		}
	}

	// Cleanup
	delete[] devices;
	delete[] programBinarySizes;
	for (cl_uint i = 0; i < numDevices; i++)
	{
		delete[] programBinaries[i];
	}
	delete[] programBinaries;
	return true;
}

关于程序二进制码,开发人员还要了解一些重要的问题。首先,一个程序二进制码只对创建它的相应设备有效。OpenCL实现可能会选择用其二进制格式存储程序的一个中间表示,也可能存储可执行代码。这是实现所做的选择,应用程序无从得知。因此,如果假设一个二进制码可以在其他设备上工作,那么这是不安全的,除非OpenCL开发商明确做出保证。一般地,要为新设备重新编译二进制码,这对于确保兼容性非常重要。

代码清单6-4给出了一个程序二进制码的例子,这是NVIDIA OpenCL实现生成的。这个代码清单对于熟悉CUDA 的开发人员来说并不陌生。NVIDIA二进制格式采用专用PTX格式存储。Apple和AMD也采用其自己的格式存储二进制码。这些二进制码都无法做到对多个开发商兼容。尽管PTX格式恰好是可读文本,但程序二进制码完全可以是人不可读的二进制位。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/687996.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【网络知识面试】初识协议栈和套接字及连接阶段的三次握手

接上一篇&#xff1a;【网络面试必问】浏览器如何委托协议栈完成消息的收发 1. 协议栈 一直对操作系统系统的内核协议栈理解的模模糊糊&#xff0c;借着这一篇博客做一下简单梳理。 我觉得最直白的理解&#xff0c;内核协议栈就是操作系统中的一个网络控制软件&#xff0c;就是…

【git】git常用指令(项目一般使用流程示例)

文章目录 创建开发环境clone到本地查看分支创建自己的开发分支切换到开发分支 开发完成上传到仓库判断目前本地仓库的状态新内容提交到暂存区新内容更新到本地仓库新内容推到远端仓库dev1.0并入主分支1.切换到主分支2.合并3.推主分支上远端仓库 回退版本主分支更新了&#xff0…

软件产品登记测试为何如此重要?

软件产品登记测试为何如此重要&#xff1f; 软件产品登记测试报告&#xff0c;是对客户的软件产品进行功能性的检测和验证&#xff0c;确保这些功能都得以实现并能正常运行&#xff0c;可作为国家高新、增值税退税、双软评估、首套台软件的检测证明材料。 软件登记测试是“双软…

three.js中聚光灯及其属性介绍

一、聚光灯及其属性介绍 Three.js中的聚光灯&#xff08;SpotLight&#xff09;是一种用于在场景中创建聚焦光照的光源类型。它有以下属性&#xff1a; color&#xff1a;聚光灯的颜色。 intensity&#xff1a;聚光灯的强度。 distance&#xff1a;聚光灯的有效距离。 angl…

知识管理工具:在信息时代下的组织智慧管理

随着信息时代的到来&#xff0c;企业面临着前所未有的信息爆炸和快速变化的挑战。如何高效地管理和利用这些信息已经成为了企业生存和发展的关键。在这种背景下&#xff0c;知识管理工具应运而生&#xff0c;为企业提供了优秀的解决方案。 知识管理工具的定义与特点 知识管理的…

DAMA数据治理CDGA/CDGP认证考试备考经验分享

一&#xff0c;关于DAMA中国和CDGA/CDGP考试 国际数据管理协会&#xff08;DAMA国际&#xff09;是一个全球性的专业组织&#xff0c;由数据管理和相关的专业人士组成&#xff0c;非营利性机构&#xff0c;厂商中立。协会自1980年成立以来&#xff0c;一直致力于数据管理和数字…

gralylog介绍与安装

介绍 Graylog是一个开源的日志管理和分析平台&#xff0c;用于收集、存储、分析和可视化大量日志数据。它提供了一个集中化的解决方案&#xff0c;可以帮助组织有效地处理分散在各种系统和应用程序中的日志信息。 以下是Graylog的主要特点和功能&#xff1a; 日志收集&#x…

【AI工具】-MockingBird-语音合成语音克隆

简介 MockingBird&#xff1a; 英文翻译&#xff1a;反舌鸟&#xff0c;也可能来自《杀死一只知更鸟》&#xff08;英语&#xff1a;To Kill a Mockingbird&#xff09;&#xff0c;台译“梅冈城故事”&#xff0c;中国大陆译“杀死一只知更鸟”&#xff0c;直译应为“杀死一…

【Python】python进阶篇之数据库操作

数据库操作 pip3安装mysql依赖 pip3 list|grep mysqlpip3 install mysql-connector-python #指定版本 pip3 install mysql-connector-python版本号 #升降版本 pip3 install --upgrade mysql-connector-python版本号原生SQL操作 操作mysql可以使用pymsql或mysql-connector-py…

基于html+css的图展示138

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

第三章 决策树

文章目录 第三章 决策树3.1基本流程3.2划分选择3.2.1信息增益3.2.2增益率3.2.3基尼指数 3.3剪枝处理3.3.1预剪枝3.3.2后剪枝 3.4连续与缺失值3.4.1连续值处理3.4.2缺失值处理 3.5多变量决策树3.7实验 第三章 决策树 3.1基本流程 决策过程&#xff1a; 基本算法&#xff1a; …

灵雀云获Gartner® 首份《DevOps平台魔力象限报告》“荣誉提及”

随着平台工程理念的崛起&#xff0c;企业使用的独立的DevOps工具链逐渐向更先进、更便捷的DevOps平台演进。Gartner发布了首份DevOps平台魔力象限报告&#xff08;Gartner Magic Quadrant for DevOps Platforms&#xff09;。在这个备受关注的报告中&#xff0c;中国云原生厂商…

大势智慧软硬件技术答疑第五期

1.控制点误差表达到多少就可以&#xff1f; 答&#xff1a;水平和高程误差在0.01左右就可以&#xff0c;图示精度是满足的。 2.三维影像有颜色&#xff0c;为什么生成的是二维影像是黑色的&#xff1f; 答&#xff1a;使用dasviewer的工具-输出正射图再试试。 3.最新模方对ps版…

JMeter中常见的四种参数化实现方式是什么?

1 参数化释义 什么是参数化&#xff1f;从字面上去理解的话&#xff0c;就是事先准备好数据&#xff08;广义上来说&#xff0c;可以是具体的数据值&#xff0c;也可以是数据生成规则&#xff09;&#xff0c;而非在脚本中写死&#xff0c;脚本执行时从准备好的数据中取值。 参…

浅析移动警务App中的技术痛点与挑战

移动警务是指警务机关利用移动通信技术和移动设备&#xff0c;实现警务信息化、智能化和移动化的一种工作模式。通过移动警务&#xff0c;警务人员可以随时随地进行警务工作&#xff0c;提高警务反应速度和效率。 移动警务通常包括以下方面的内容&#xff1a; 移动巡逻&#x…

【ArcGIS Pro二次开发】(43):线闭合

当我们需要将多段线【polyline】转为面【polygon】的时候&#xff0c;必须保证线是闭合的&#xff0c;不然是无法生成面的&#xff0c;如下图&#xff1a; 如果cad线段&#xff0c;可以在属性里将闭合选项设置为是&#xff0c;实现线的闭合&#xff1a; 但如果是在ArcGIS Pro里…

【inpho精品教程】07 动连点自动提取和区域网平差

【inpho精品教程】07 动连点自动提取和区域网平差 文章目录 【inpho精品教程】07 动连点自动提取和区域网平差一、数据及参数检查二、参数设置三、运行四、 连接点提取失败的原因五、 连接点较差的原因一、数据及参数检查 在动连点自动提取和区域网平差之前,首先要检查工程中…

Miniconda安装使用教程

下载 官网地址&#xff1a;https://docs.conda.io/en/latest/miniconda.html 支持的系统&#xff1a; Windows 10 or newer, 64-bit macOS 10.13, or Linux, including Ubuntu, RedHat, CentOS 7, and others. 支持的系统架构&#xff1a; Windows- 64-bit x86, 32-bit x86; m…

Json 数据通用提取工具,使用 HTML JavaScript [单文件离线版]

Gitee 地址链接 Json2Csv js html 实现的一款通用型JSON数据提取工具&#xff0c;支持自动识别JSON数据节点并有序提取为CSV文件。 Convenient JSON data extraction tool. 使用简介 数据路径&#xff1a;设置Json中数据所处路径&#xff0c;如’root.topics.data’ ‘data.…

JavaWeb小记——请求对象request

目录 request对象 获取请求行 获取请求方式 获取项目的上下文路径 获取URI 获取URL 获取IP地址 获取协议版本 获取请求参数 获取请求头 获取日期头 获取数字头 获取请求体 获取请求参数 通用方式获取请求参数 单个获取 针对复选框 获取所有参数&#xff08;map&…