技术标签: c++ Windows 基础编程 测试工具 windows 业余学习
提示:本文修改后包含编程方法以及附注的工具方法,传统的资源管理器交互方法等等。
由于文件是安全对象,因此访问它们受访问控制模型控制,该模型控制对 Windows 中所有其他安全对象的访问。
更改文件或目录对象的安全描述符,需要调用 SetNamedSecurityInfo 或 SetSecurityInfo 等函数。
ACL:Access Control List,用来表示用户(组)权限的列表,包括 DACL 和 SACL ;
ACE:Access Control Entry,ACL 中的元素;
DACL:Discretionary Access Control List,用来表示安全对象权限的列表;
SACL:System Access Control List,用来记录对安全对象访问的日志。
关于这几个概念已经有很多资料解释,这里就不详细展开了。(以下仅介绍通过 Win32API 修改文件安全属性的一些内容)。
本文以 SetNamedSecurityInfo 函数为例,简单讲解如何通过编程修改指定文件的 ACL 主体和添加 ACE 条目。后文修改的文件比较特殊,它们受到本地内置安全主体 TrustedInstaller 的完全控制保护,本文旨在通过相关 API 实现修改这类文件的完全控制权限。有关内置安全主体 TrustedInstaller 的信息,需要具有一定的知识基础,限于篇幅原因这里不详细展开。
SetNamedSecurityInfo 函数用于在指定对象的安全描述符(ACL)中设置指定的安全信息。 调用方按名称标识对象。
这里的按名称标识对象,表示设置安全信息时候,目标对象名是字符串格式,且对于不同对象类型的字符串格式,可以参阅说明:SE_OBJECT_TYPE。我们这里要修改NTFS文件系统中指定文件的安全属性,按照标准需要填写文件绝对路径的宽字符串,其中路径以反斜杠 (" / ") 或者双斜杠 (" \\ ") 给出,并且要以 NULL 结尾。
下面给出该函数的参数:
DWORD SetNamedSecurityInfoW(
[in] LPWSTR pObjectName,
[in] SE_OBJECT_TYPE ObjectType,
[in] SECURITY_INFORMATION SecurityInfo,
[in, optional] PSID psidOwner,
[in, optional] PSID psidGroup,
[in, optional] PACL pDacl,
[in, optional] PACL pSacl
);
这些参数的定义是:
pObjectName 指定目标对象的名称;
ObjectType 指示 pObjectName 的类型,如果修改 NTFS 文件则填写为 SE_FILE_OBJECT;
SecurityInfo 指示要设置的安全信息的类型;
psidOwner 指向标识对象所有者的 SID 结构的指针;
psidGroup 指向标识对象主组的 SID 的指针;
pDacl 指向 对象的新 DACL 的指针;
pSacl 指向 对象的新 SACL 的指针。
其中,如果 SecurityInfo 填写为 OWNER_SECURITY_INFORMATION 表明对安全主体的操作(也就是控制列表所有者);如果填写为 DACL_SECURITY_INFORMATION 则表明对安全对象(ACE)的权限的操作。这里需要注意:如果调用方令牌对应的账户标识符(SID)不是文件安全主体指向的账户标识符,则对安全对象权限的操作会被拒绝。
修改文件安全属性需要进程的令牌具有一定的访问控制权限,这里我们需要获取两个权限 SE_SECURITY_NAME 和 SE_TAKE_OWNERSHIP_NAME 。这两个权限可以通过 AdjustTokenPrivileges 函数获得,而赋予新的令牌则需要调用方线程具有管理员权限。所以进程需要以提升的令牌启动才能正确获取权限。
从零开始的项目都是不容易的,有一个参考最好不过了。这里我们可以参考资源管理器的功能实现文件安全属性的修改,下面的图片展示了通过资源管理器查看的目标文件的安全属性页面信息:
通过观察,我们可以发现,当前的所有者为 TrustedInstaller 并且只有它具有完全控制权限,其他安全对象只具有读取和执行的权限。此外我们还发现,对于该文件权限修改的按钮都是灰色或者具有盾牌图标。我们将其与用户具有完全访问控制权的文件安全信息进行对比,就可以发现所有者信息起到了至关重要的作用,调用方必须为文件的安全主体或者权限比安全主体高,否则不能修改安全属性表。所以当正确获取权限后,我们首先需要修改文件的安全主体(所有者)。
首先我们简述一下通过资源管理器进行修改的步骤:
Step 1: 首先选中要修改的文件(夹),打开右键菜单,在菜单中找到“属性”项目,点击打开属性对话框;
Step 2: 在属性对话框中选中“安全”选项卡,右下角点击“高级(安全设置)”按钮;
Step 3: 可以看到该页面包含两个主要部分:1)所有者;2)成员及其权限
并且后面都有带有盾牌的“更改”按钮,再结合说明文档可知,如果当前进程代表的账户权限低于所有者所具有的权限,则低权限进程不具有修改高权限进程的权限能力;
这里我们需要先修改所有者,点击最上面的TrustedInstaller字样后面的更改按钮,此时可能弹出UAC对话框提示用户需要提升权限,也可能没有提示直接提升(取决于计算机配置的UAC警告级别)
Step 4: 此时会弹出选择所有者的对话框,点击“高级”按钮展开这个编辑器窗口:
Step 5: 在高级窗口中点击立即查找,在查找的列表中找到当前登陆用户的用户名,然后点击确定,或者选择everyone(字面意思,代表任何用户)。
Step 6: 逐级确定并应用,可以发现所有者被成功更改:
最后一步点击“应用”,然后再点击确定,应用更改,随后重新打开高级安全设置页面,选择要修改权限的成员,并发现编辑区域按钮被点亮,点击“添加/编辑”:
随后,在打开的窗口中,先点击蓝色字“选择主体”,类似于修改所有者的操作完成权限配置,并点击确定,最后点击应用即可完成整个修改操作:
最后,补充一下修改完成后改回TrustedInstaller的方法:在更改所有者时候,直接输入 NT SERVICE\TrustedInstaller 需要注意不能有拼写错误!然后其他步骤不变。
我们通过设置 SetNamedSecurityInfo 函数的 SecurityInfo 参数为 OWNER_SECURITY_INFORMATION 并指定 psidOwner 为指向新所有者的安全描述符 (SID) 结构的指针。
SID 就相当于所有者的身份证,那么我们如何获取到所需要的 SID 呢?
LookupAccountName 函数可供获取指定用户名对应的 SID 。
其参数如下:
BOOL LookupAccountNameW(
[in, optional] LPCWSTR lpSystemName,
[in] LPCWSTR lpAccountName,
[out, optional] PSID Sid,
[in, out] LPDWORD cbSid,
[out, optional] LPWSTR ReferencedDomainName,
[in, out] LPDWORD cchReferencedDomainName,
[out] PSID_NAME_USE peUse
);
其中,lpAccountName 指向指定帐户名称的以 NULL 结尾的字符串的指针。这里的本地账户名称可以通过输入已知的字符串如 "Administrators" 。注:采用 domain_name\user_name 格式的完全限定字符串来确保 LookupAccountName 函数在所需域中查找帐户,但本文不谈及域内操作,只谈本地操作。
关于账户名称的获取,有多种方法,如 GetUserName 可以获得创建当前调用线程所在帐户名称的字符串,环境变量函数 GetEnvironmentVariable 通过设置 lpName 参数为"USERNAME"可以获得当前所在帐户名称的字符串;使用 Windows Terminal Session API 获取当前会话对应的账户名称字符串,等等。这里通过 WTSQuerySessionInformation 函数并指定相关参数设置以获取当前登录账户的名称:
WTSQuerySessionInformationW(
WTS_CURRENT_SERVER_HANDLE,
WTS_CURRENT_SESSION,
WTS_INFO_CLASS::WTSUserName,
&userName,
&nameSize);
然后将得到的字符串传递给 LookupAccountName 函数,获取对应的 SID 。
随后,调用 SetNamedSecurityInfo 并设置 pObjectName, ObjectType, SecurityInfo 和 psidOwner 参数即可修改目标对象的所有者。
本文以在原有DACL表上添加新的对象权限为例进行讲解。有关于覆盖、删除、复制的方法需要自行调试。
一种通用的更新方法就是先获取到原始的DACL表,然后将其与要添加的内容合并为一张新的表。最后将新的表更新到文件对应的表上。
整个过程我们需要这几个步骤:
Step1: 调用 GetNamedSecurityInfo 并设置 DACL_SECURITY_INFORMATION 类型,获取旧的DACL表;
Step2: 调用 BuildExplicitAccessWithName 用于构建一个 ACE,包含需要的权限;
Step3: 调用 SetEntriesInAcl 合并 ACE 到 旧的DACL,返回合并后的新的 DACL;
Step4: 调用 SetNamedSecurityInfo 绑定新的 DACL 到文件对象。
一个典型的示例代码如下,其中 Step2 也可以直接为结构体成员赋值:
DWORD AddAceToObjectsSecurityDescriptor(
LPTSTR pszObjName, // name of object
SE_OBJECT_TYPE ObjectType, // type of object
LPWSTR pszTrustee, // trustee for new ACE
TRUSTEE_FORM TrusteeForm, // format of trustee structure
DWORD dwAccessRights, // access mask for new ACE
ACCESS_MODE AccessMode, // type of ACE
DWORD dwInheritance // inheritance flags for new ACE
)
{
DWORD dwRes = 0;
PACL pOldDACL = NULL, pNewDACL = NULL;
PSECURITY_DESCRIPTOR pSD = NULL;
EXPLICIT_ACCESS ea = {};
if (NULL == pszObjName)
return ERROR_INVALID_PARAMETER;
printf("|*|:为用户 %ws 获取文件:%ws 的访问权限。\n", pszTrustee, pszObjName);
// Get a pointer to the existing DACL.
dwRes = GetNamedSecurityInfoW(pszObjName, ObjectType,
DACL_SECURITY_INFORMATION,
NULL, NULL, &pOldDACL, NULL, &pSD);
if (ERROR_SUCCESS != dwRes) {
printf("GetNamedSecurityInfo Error %u\n", dwRes);
goto Cleanup;
}
// Initialize an EXPLICIT_ACCESS structure for the new ACE.
ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS));
//ea.grfAccessPermissions = dwAccessRights;
//ea.grfAccessMode = AccessMode;
//ea.grfInheritance = dwInheritance;
//ea.Trustee.TrusteeForm = TrusteeForm;
//ea.Trustee.ptstrName = pszTrustee;
// 生成指定用户帐户的访问控制信息(这里指定赋予全部的访问权限)
BuildExplicitAccessWithNameW(&ea, pszTrustee, dwAccessRights, AccessMode, dwInheritance);
// Create a new ACL that merges the new ACE
// into the existing DACL.
dwRes = SetEntriesInAclW(1, &ea, pOldDACL, &pNewDACL);
if (ERROR_SUCCESS != dwRes) {
printf("SetEntriesInAcl Error %u\n", dwRes);
goto Cleanup;
}
// Attach the new ACL as the object's DACL.
dwRes = SetNamedSecurityInfoW(pszObjName, ObjectType,
DACL_SECURITY_INFORMATION,
NULL, NULL, pNewDACL, NULL);
if (ERROR_SUCCESS != dwRes) {
printf("SetNamedSecurityInfo Error %u\n", dwRes);
goto Cleanup;
}
else {
printf("成功:已经为用户 %ws 获取文件:%ws 的访问权限。\n", pszTrustee, pszObjName);
return dwRes;
}
Cleanup:
if (pSD != NULL)
LocalFree((HLOCAL)pSD);
if (pNewDACL != NULL)
LocalFree((HLOCAL)pNewDACL);
std::cout << "|*|:操作失败! " << std::endl;
return -1;
}
通过以上方法即可对指定路径文件进行安全访问权限的修改。
下面是按照上文思路编写的简单实现代码,需要将编译好的程序在提升的命令行中执行,命令行参数为 TestFile.exe <文件路径> 。(编译环境:Visual Studio C++)
注意:运行环境必须是管理员令牌,这样才能启用相应的权限,可以通过应用程序清单或者编程的方式(ShellExecuteEx, lpVerb + "runas")来请求管理员权限。
#include <iostream>
#include <stdio.h>
#include <aclapi.h>
#include <windows.h>
#include <tchar.h>
#include <stdlib.h>
#include <aclapi.h>
#include <wtsapi32.h>
#pragma comment(lib, "Advapi32.lib")
#pragma comment(lib, "Wtsapi32.lib")
#define MAX_NAME 260
BOOL SetPrivilege(
HANDLE hToken, // access token handle
LPCWSTR lpszPrivilege, // name of privilege to enable/disable
BOOL bEnablePrivilege // to enable or disable privilege
)
{
TOKEN_PRIVILEGES tp;
LUID luid;
if (!LookupPrivilegeValueW(
NULL, // lookup privilege on local system
lpszPrivilege, // privilege to lookup
&luid)) // receives LUID of privilege
{
printf("LookupPrivilegeValue error: %u\n", GetLastError());
return FALSE;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
if (bEnablePrivilege)
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
else
tp.Privileges[0].Attributes = 0;
// Enable the privilege or disable all privileges.
if (!AdjustTokenPrivileges(
hToken,
FALSE,
&tp,
sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES)NULL,
(PDWORD)NULL))
{
printf("AdjustTokenPrivileges error: %u\n", GetLastError());
return FALSE;
}
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
{
printf("The token does not have the specified privilege. \n");
return FALSE;
}
return TRUE;
}
BOOL SeTakeOwnershipToken()
{
printf("|*|操作:获取相关权限。该特权可以修改目标文件的 Ownership。\n");
HANDLE hToken;
BOOL bRet = OpenProcessToken(
GetCurrentProcess(), // 进程句柄(当前进程)
TOKEN_ALL_ACCESS, // 全权访问令牌
&hToken // 返回的参数 进程令牌句柄 (就是AdjustTokenPrivileges的第一个参数)
); // 获取进程的令牌句柄
if (bRet != TRUE) {
printf("获取令牌句柄失败!\nPlease wait...\n");
return FALSE;
}
// 提升权限
BOOL set_SECURITY = SetPrivilege(hToken, SE_SECURITY_NAME, TRUE);
if (!set_SECURITY || GetLastError() != ERROR_SUCCESS)
{
printf("提升SECURITY权限失败 Error: %u\n|*|:操作失败!\nPlease wait...\n", GetLastError());
Sleep(5000);
return FALSE;
}
// 提升获得所有者权限
BOOL set_OWNER = SetPrivilege(hToken, SE_TAKE_OWNERSHIP_NAME, TRUE);
if (!set_OWNER || GetLastError() != ERROR_SUCCESS)
{
printf("提升TAKE OWNERSHIP权限失败 Error: %u\n|*|:操作失败!\nPlease wait...\n", GetLastError());
Sleep(5000);
return FALSE;
}
printf("提升权限成功! \n");
return TRUE;
}
BOOL ChangeTrusteeViaObjectsSecurity(
LPTSTR pszObjName, // name of object
LPTSTR pszTrustee, // trustee for new ACE
SE_OBJECT_TYPE ObjectType, // type of object. value :SE_FILE_OBJECT, /* 注册表为:SE_REGISTRY_KEY */
SECURITY_INFORMATION SecurityInfo // Security Operational Information. value:OWNER_SECURITY_INFORMATION
)
{
DWORD dwRes = 0;
// LookupAccountName函数所需要的变量
wchar_t* userName = nullptr;
wchar_t sid[MAX_NAME]{ L'\0' };
DWORD nameSize = 0;
WTSQuerySessionInformationW(
WTS_CURRENT_SERVER_HANDLE,
WTS_CURRENT_SESSION,
WTS_INFO_CLASS::WTSUserName,
&userName,
&nameSize);
wchar_t userSID[MAX_NAME]{ L'\0' };
wchar_t userDomain[MAX_NAME]{ L'\0' };
DWORD sidSize = sizeof(userSID);
DWORD signSidSize = sizeof(userSID);
DWORD domainSize = sizeof(userDomain) / sizeof(WCHAR);
SID_NAME_USE snu;
dwRes = LookupAccountNameW(NULL,
userName,
userSID,
&sidSize,
userDomain,
&domainSize,
&snu);
WTSFreeMemory(userName);
//获取用户名SID
if (ERROR_SUCCESS != dwRes)// 调用成功返回值为0
{
PSID_IDENTIFIER_AUTHORITY psia = GetSidIdentifierAuthority(userSID);
signSidSize = swprintf_s(sid, sizeof(sid)/sizeof(wchar_t), L"S-%lu-", SID_REVISION);
signSidSize = (signSidSize + swprintf_s(sid + wcslen(sid), sizeof(sid), L"%-lu", psia->Value[5]));
int i = 0;
int subAuthorities = *GetSidSubAuthorityCount(userSID);
for (i = 0; i < subAuthorities; i++)
{
signSidSize += swprintf_s(sid + signSidSize, sizeof(sid), L"-%lu", *GetSidSubAuthority(userSID, i));
}
printf("Account SID: %ws\n", sid);
// 更改所有者
if (!SetNamedSecurityInfoW
(pszObjName,
ObjectType, /* 注册表为:SE_REGISTRY_KEY */
SecurityInfo, /* 更改所有者 */
&userSID, /* 需要更改所有者的SID */
NULL, NULL, NULL))
{
printf("成功更改所有者! \n");
return TRUE;
}
else {
printf("Security Info:OWNER_SECURITY_INFORMATION.\nSetNamedSecurityInfo Error %u\nPlease wait...\n", dwRes);
Sleep(5000);
}
}
else {
printf("LookupAccountName Error %u\nPlease wait...\n", dwRes);
Sleep(5000);
}
return FALSE;
}
DWORD AddAceToObjectsSecurityDescriptor(
LPTSTR pszObjName, // name of object
SE_OBJECT_TYPE ObjectType, // type of object
LPWSTR pszTrustee, // trustee for new ACE
TRUSTEE_FORM TrusteeForm, // format of trustee structure
DWORD dwAccessRights, // access mask for new ACE
ACCESS_MODE AccessMode, // type of ACE
DWORD dwInheritance // inheritance flags for new ACE
)
{
DWORD dwRes = 0;
PACL pOldDACL = NULL, pNewDACL = NULL;
PSECURITY_DESCRIPTOR pSD = NULL;
EXPLICIT_ACCESS ea = {};
if (NULL == pszObjName)
return ERROR_INVALID_PARAMETER;
printf("|*|:为用户 %ws 获取文件:%ws 的访问权限。\n", pszTrustee, pszObjName);
// Get a pointer to the existing DACL.
dwRes = GetNamedSecurityInfoW(pszObjName, ObjectType,
DACL_SECURITY_INFORMATION,
NULL, NULL, &pOldDACL, NULL, &pSD);
if (ERROR_SUCCESS != dwRes) {
printf("GetNamedSecurityInfo Error %u\n", dwRes);
goto Cleanup;
}
// Initialize an EXPLICIT_ACCESS structure for the new ACE.
ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS));
//ea.grfAccessPermissions = dwAccessRights;
//ea.grfAccessMode = AccessMode;
//ea.grfInheritance = dwInheritance;
//ea.Trustee.TrusteeForm = TrusteeForm;
//ea.Trustee.ptstrName = pszTrustee;
// 生成指定用户帐户的访问控制信息(这里指定赋予全部的访问权限)
BuildExplicitAccessWithNameW(&ea, pszTrustee, dwAccessRights, AccessMode, dwInheritance);
// Create a new ACL that merges the new ACE
// into the existing DACL.
dwRes = SetEntriesInAclW(1, &ea, pOldDACL, &pNewDACL);
if (ERROR_SUCCESS != dwRes) {
printf("SetEntriesInAcl Error %u\n", dwRes);
goto Cleanup;
}
// Attach the new ACL as the object's DACL.
dwRes = SetNamedSecurityInfoW(pszObjName, ObjectType,
DACL_SECURITY_INFORMATION,
NULL, NULL, pNewDACL, NULL);
if (ERROR_SUCCESS != dwRes) {
printf("SetNamedSecurityInfo Error %u\n", dwRes);
goto Cleanup;
}
else {
printf("成功:已经为用户 %ws 获取文件:%ws 的访问权限。\n", pszTrustee, pszObjName);
return dwRes;
}
Cleanup:
if (pSD != NULL)
LocalFree((HLOCAL)pSD);
if (pNewDACL != NULL)
LocalFree((HLOCAL)pNewDACL);
std::cout << "|*|:操作失败! " << std::endl;
return -1;
}
int wmain(int argc, wchar_t* argv[])
{
setlocale(LC_CTYPE, "CHS");//让wprintf()支持中文
if (argc != 2)
{
std::cout << "Invalid Parameters!" << std::endl;
return 0;
}
wchar_t* path = argv[1];
wchar_t* pszObjName = path;
// 获取当前用户名
wchar_t* currentUser = nullptr;
DWORD dwSize_currentUser = 0;
WTSQuerySessionInformationW(
WTS_CURRENT_SERVER_HANDLE,
WTS_CURRENT_SESSION,
WTS_INFO_CLASS::WTSUserName,
¤tUser,
&dwSize_currentUser);
if (!SeTakeOwnershipToken()) {
std::cout << "|*|:在未获得SE_TAKE_OWNERSHIP_NAME特权时,继续更改文件所有者可能失败! " << std::endl;
}
// 更改ACL下的安全主体,以获得ACE控制权
if (ChangeTrusteeViaObjectsSecurity(pszObjName,
currentUser, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION))
{
// 覆盖ACL以获得完全控制的访问权限
if (AddAceToObjectsSecurityDescriptor(pszObjName,
SE_FILE_OBJECT,
currentUser,
TRUSTEE_IS_OBJECTS_AND_SID,
GENERIC_ALL,
GRANT_ACCESS,
SUB_CONTAINERS_AND_OBJECTS_INHERIT) == -1)
{
std::cout << "ACE操作已执行。" << std::endl;
}
}
else {
std::cout << "无法更改ACL下的安全主体,继续更改ACE控制权可能会失败。" << std::endl;
// 覆盖ACL以获得完全控制的访问权限
if (AddAceToObjectsSecurityDescriptor(pszObjName,
SE_FILE_OBJECT,
currentUser,
TRUSTEE_IS_OBJECTS_AND_SID,
GENERIC_ALL,
GRANT_ACCESS,
SUB_CONTAINERS_AND_OBJECTS_INHERIT) == -1)
{
std::cout << "ACE操作已执行。" << std::endl;
}
}
std::cout << "所有操作已完成。" << std::endl;
system("pause");
return 0;
}
下图是在提升的命令提示符中执行的效果:
从图中可见程序执行完毕,并且没有错误。
下图展示的是程序执行完毕后,目标对象的属性变化:
从图中可以看出目标对象的所有者发生变化,且为当前登陆账户添加了完全控制权限。
icacls
Intergrity Control Access Control List: 完整性权限控制列表
Windows系统下控制文件及文件夹的访问权限的命令行指令,相当于Linux中的chmod
原命令cacls已经被废弃。
这一篇博客园作者整理的命令详细信息比较全面清晰:Cacls和ICacls - 傩舞 - 博客园 (cnblogs.com)
就不详细列出了,这里给出一篇知乎,整理的也比较全面:渗透技巧——Windows下的Access Control List - 知乎
本文为原创文章,转载请注明出处:
https://blog.csdn.net/qq_59075481/article/details/132459343
发布于:2023/08/23;修改于:2023/09/19,2024/03/14.
文章浏览阅读101次。4.class可以有⽆参的构造函数,struct不可以,必须是有参的构造函数,⽽且在有参的构造函数必须初始。2.Struct适⽤于作为经常使⽤的⼀些数据组合成的新类型,表示诸如点、矩形等主要⽤来存储数据的轻量。1.Class⽐较适合⼤的和复杂的数据,表现抽象和多级别的对象层次时。2.class允许继承、被继承,struct不允许,只能继承接⼝。3.Struct有性能优势,Class有⾯向对象的扩展优势。3.class可以初始化变量,struct不可以。1.class是引⽤类型,struct是值类型。
文章浏览阅读586次。想实现的功能是点击顶部按钮之后按关键字进行搜索,已经可以从服务器收到反馈的json信息,但从json信息的解析开始就会闪退,加载listview也不知道行不行public abstract class loadlistview{public ListView plv;public String js;public int listlength;public int listvisit;public..._rton转json为什么会闪退
文章浏览阅读219次。如何使用wordnet词典,得到英文句子的同义句_get_synonyms wordnet
文章浏览阅读521次。系统项目报表导出 导出任务队列表 + 定时扫描 + 多线程_积木报表 多线程
文章浏览阅读1.1k次,点赞9次,收藏9次。使用AJAX技术的好处之一是它能够提供更好的用户体验,因为它允许在不重新加载整个页面的情况下更新网页的某一部分。另外,AJAX还使得开发人员能够创建更复杂、更动态的Web应用程序,因为它们可以在后台与服务器进行通信,而不需要打断用户的浏览体验。在Web开发中,AJAX(Asynchronous JavaScript and XML)是一种常用的技术,用于在不重新加载整个页面的情况下,从服务器获取数据并更新网页的某一部分。使用AJAX,你可以创建异步请求,从而提供更快的响应和更好的用户体验。_ajax 获取http数据
文章浏览阅读2.8k次。登录退出、修改密码、关机重启_字符终端
文章浏览阅读3.8k次,点赞3次,收藏51次。前段时间看到一位发烧友制作的超声波雷达扫描神器,用到了Arduino和Processing,可惜啊,我不会Processing更看不懂人家的程序,咋办呢?嘿嘿,所以我就换了个思路解决,因为我会一点Python啊,那就动手吧!在做这个案例之前先要搞明白一个问题:怎么将Arduino通过超声波检测到的距离反馈到Python端?这个嘛,我首先想到了串行通信接口。没错!就是串口。只要Arduino将数据发送给COM口,然后Python能从COM口读取到这个数据就可以啦!我先写了一个测试程序试了一下,OK!搞定_超声波扫描建模 python库
文章浏览阅读4.2k次。端—端加密指信息由发送端自动加密,并且由TCP/IP进行数据包封装,然后作为不可阅读和不可识别的数据穿过互联网,当这些信息到达目的地,将被自动重组、解密,而成为可读的数据。不可逆加密算法的特征是加密过程中不需要使用密钥,输入明文后由系统直接经过加密算法处理成密文,这种加密后的数据是无法被解密的,只有重新输入明文,并再次经过同样不可逆的加密算法处理,得到相同的加密密文并被系统重新识别后,才能真正解密。2.使用时,加密者查找明文字母表中需要加密的消息中的每一个字母所在位置,并且写下密文字母表中对应的字母。_凯撒加密
文章浏览阅读5.7k次。CIP报文解析常用到的几个字段:普通类型服务类型:[0x00], CIP对象:[0x02 Message Router], ioi segments:[XX]PCCC(带cmd和func)服务类型:[0x00], CIP对象:[0x02 Message Router], cmd:[0x101], fnc:[0x101]..._cip协议embedded_service_error
文章浏览阅读2.4k次,点赞9次,收藏13次。有时候我们在MFC项目开发过程中,需要用到一些微软已经提供的功能,如VC++使用EXCEL功能,这时候我们就能直接通过VS2019到如EXCEL.EXE方式,生成对应的OLE头文件,然后直接使用功能,那么,我们上篇文章中介绍了vs2017及以前的版本如何来添加。但由于微软某些方面考虑,这种方式已被放弃。从上图中可以看出,这一功能,在从vs2017版本15.9开始,后续版本已经删除了此功能。那么我们如果仍需要此功能,我们如何在新版本中添加呢。_vs添加mfc库
文章浏览阅读785次。用ac3编码,执行编码函数时报错入如下:[ac3 @ 0x7fed7800f200] frame_size (1536) was not respected for anon-last frame (avcodec_encode_audio2)用ac3编码时每次送入编码器的音频采样数应该是1536个采样,不然就会报上述错误。这个数字并非刻意固定,而是跟ac3内部的编码算法原理相关。全网找不到,国内音视频之路还有很长的路,音视频人一起加油吧~......_frame_size (1024) was not respected for a non-last frame
文章浏览阅读230次,点赞2次,收藏2次。创建Android应用程序一个项目里面可以有很多模块,而每一个模块就对应了一个应用程序。项目结构介绍_在安卓移动应用开发中要在活动类文件中声迷你一个复选框变量