C++ 调用7z SDK 解压

2024-03-06 23:32
文章标签 c++ sdk 调用 解压 7z

本文主要是介绍C++ 调用7z SDK 解压,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

        7z SDK 解压编译后,可以用"C 模式"解压,也可以用"CPP 模式"。C可以在内存中解压,但是很多时候不成功,是由于malloc分配不到内存。CPP不用担心内存问题,但是它解压的时候占用CPU较高。

       代码:

// Test7z.cpp : 定义控制台应用程序的入口点。
//#include "stdafx.h"
#include "Common/IntToString.h"
#include "Common/MyInitGuid.h"
#include "Common/StringConvert.h"#include "Windows/DLL.h"
#include "Windows/FileDir.h"
#include "Windows/FileFind.h"
#include "Windows/FileName.h"
#include "Windows/NtCheck.h"
#include "Windows/PropVariant.h"
#include "Windows/PropVariantConversions.h"#include "7Z/CPP/7zip/Common/FileStreams.h"#include "7Z/CPP/7zip/Archive/IArchive.h"#include "7Z/CPP/7zip/IPassword.h"
#include "7Z/CPP/7zip/MyVersion.h"// use another CLSIDs, if you want to support other formats (zip, rar, ...).
// {23170F69-40C1-278A-1000-000110070000}
DEFINE_GUID(CLSID_CFormat7z, 0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0x07, 0x00, 0x00);using namespace NWindows;typedef UINT32 (WINAPI * CreateObjectFunc)(const GUID *clsID, const GUID *interfaceID, void **outObject);void PrintString(const UString &s)
{printf("%s", (LPCSTR)GetOemString(s));
}
void PrintString(const AString &s)
{printf("%s", (LPCSTR)s);
}
void PrintNewLine()
{PrintString("\n");
}
void PrintStringLn(const AString &s)
{PrintString(s);PrintNewLine();
}
void PrintError(const AString &s)
{PrintNewLine();PrintString(s);PrintNewLine();
}static HRESULT IsArchiveItemProp(IInArchive *archive, UInt32 index, PROPID propID, bool &result)
{NCOM::CPropVariant prop;RINOK(archive->GetProperty(index, propID, &prop));if (prop.vt == VT_BOOL){result = VARIANT_BOOLToBool(prop.boolVal);}else if (prop.vt == VT_EMPTY){result = false;}else{return E_FAIL;}return S_OK;
}
static HRESULT IsArchiveItemFolder(IInArchive *archive, UInt32 index, bool &result)
{return IsArchiveItemProp(archive, index, kpidIsDir, result);
}static const wchar_t *kEmptyFileAlias = L"[Content]";//
// Archive Open callback classclass CArchiveOpenCallback: public IArchiveOpenCallback, public ICryptoGetTextPassword, public CMyUnknownImp
{
public:MY_UNKNOWN_IMP1(ICryptoGetTextPassword)STDMETHOD(SetTotal)(const UInt64 *files, const UInt64 *bytes);STDMETHOD(SetCompleted)(const UInt64 *files, const UInt64 *bytes);STDMETHOD(CryptoGetTextPassword)(BSTR *password);bool PasswordIsDefined;UString Password;CArchiveOpenCallback() : PasswordIsDefined(false) {}
};STDMETHODIMP CArchiveOpenCallback::SetTotal(const UInt64 * /* files */, const UInt64 * /* bytes */)
{return S_OK;
}
STDMETHODIMP CArchiveOpenCallback::SetCompleted(const UInt64 * /* files */, const UInt64 * /* bytes */)
{return S_OK;
}
STDMETHODIMP CArchiveOpenCallback::CryptoGetTextPassword(BSTR *password)
{if (!PasswordIsDefined){// You can ask real password here from user// Password = GetPassword(OutStream);// PasswordIsDefined = true;PrintError("Password is not defined");return E_ABORT;}return StringToBstr(Password, password);
}//
// Archive Extracting callback classstatic const wchar_t *kCantDeleteOutputFile = L"ERROR: Can not delete output file ";static const char *kTestingString    =  "Testing     ";
static const char *kExtractingString =  "Extracting  ";
static const char *kSkippingString   =  "Skipping    ";static const char *kUnsupportedMethod = "Unsupported Method";
static const char *kCRCFailed = "CRC Failed";
static const char *kDataError = "Data Error";
static const char *kUnknownError = "Unknown Error";class CArchiveExtractCallback: public IArchiveExtractCallback, public ICryptoGetTextPassword, public CMyUnknownImp
{
public:MY_UNKNOWN_IMP1(ICryptoGetTextPassword)// IProgressSTDMETHOD(SetTotal)(UInt64 size);STDMETHOD(SetCompleted)(const UInt64 *completeValue);// IArchiveExtractCallbackSTDMETHOD(GetStream)(UInt32 index, ISequentialOutStream **outStream, Int32 askExtractMode);STDMETHOD(PrepareOperation)(Int32 askExtractMode);STDMETHOD(SetOperationResult)(Int32 resultEOperationResult);// ICryptoGetTextPasswordSTDMETHOD(CryptoGetTextPassword)(BSTR *aPassword);private:CMyComPtr<IInArchive> _archiveHandler;UString _directoryPath;  // Output directoryUString _filePath;       // name inside arcvhiveUString _diskFilePath;   // full path to file on diskbool _extractMode;struct CProcessedFileInfo{FILETIME MTime;UInt32 Attrib;bool isDir;bool AttribDefined;bool MTimeDefined;} _processedFileInfo;COutFileStream *_outFileStreamSpec;CMyComPtr<ISequentialOutStream> _outFileStream;public:void Init(IInArchive *archiveHandler, const UString &directoryPath);UInt64 nFilesize;UInt64 NumErrors;bool PasswordIsDefined;UString Password;CArchiveExtractCallback() : PasswordIsDefined(false) {}
};void CArchiveExtractCallback::Init(IInArchive *archiveHandler, const UString &directoryPath)
{NumErrors = 0;_archiveHandler = archiveHandler;_directoryPath = directoryPath;NFile::NName::NormalizeDirPathPrefix(_directoryPath);
}STDMETHODIMP CArchiveExtractCallback::SetTotal(UInt64 size)
{nFilesize = size;return S_OK;
}
STDMETHODIMP CArchiveExtractCallback::SetCompleted(const UInt64 * completeValue)
{printf("%.2f\n", static_cast<float>((float)(*completeValue) / (float)nFilesize * 100.0f));return S_OK;
}
STDMETHODIMP CArchiveExtractCallback::GetStream(UInt32 index, ISequentialOutStream **outStream, Int32 askExtractMode)
{*outStream = 0;_outFileStream.Release();{// Get NameNCOM::CPropVariant prop;RINOK(_archiveHandler->GetProperty(index, kpidPath, &prop));UString fullPath;if (prop.vt == VT_EMPTY){fullPath = kEmptyFileAlias;}else{if (prop.vt != VT_BSTR){return E_FAIL;}fullPath = prop.bstrVal;}_filePath = fullPath;}if (askExtractMode != NArchive::NExtract::NAskMode::kExtract){return S_OK;}{// Get AttribNCOM::CPropVariant prop;RINOK(_archiveHandler->GetProperty(index, kpidAttrib, &prop));if (prop.vt == VT_EMPTY){_processedFileInfo.Attrib = 0;_processedFileInfo.AttribDefined = false;}else{if (prop.vt != VT_UI4){return E_FAIL;}_processedFileInfo.Attrib = prop.ulVal;_processedFileInfo.AttribDefined = true;}}RINOK(IsArchiveItemFolder(_archiveHandler, index, _processedFileInfo.isDir));{// Get Modified TimeNCOM::CPropVariant prop;RINOK(_archiveHandler->GetProperty(index, kpidMTime, &prop));_processedFileInfo.MTimeDefined = false;switch(prop.vt){case VT_EMPTY:{// _processedFileInfo.MTime = _utcMTimeDefault;break;}case VT_FILETIME:{_processedFileInfo.MTime = prop.filetime;_processedFileInfo.MTimeDefined = true;break;}default:{return E_FAIL;}}}{// Get SizeNCOM::CPropVariant prop;RINOK(_archiveHandler->GetProperty(index, kpidSize, &prop));bool newFileSizeDefined = (prop.vt != VT_EMPTY);UInt64 newFileSize;if (newFileSizeDefined){newFileSize = ConvertPropVariantToUInt64(prop);}}{// Create folders for fileint slashPos = _filePath.ReverseFind(WCHAR_PATH_SEPARATOR);if (slashPos >= 0){NFile::NDirectory::CreateComplexDirectory(_directoryPath + _filePath.Left(slashPos));}}UString fullProcessedPath = _directoryPath + _filePath;_diskFilePath = fullProcessedPath;if (_processedFileInfo.isDir){NFile::NDirectory::CreateComplexDirectory(fullProcessedPath);}else{NFile::NFind::CFileInfoW fi;if (fi.Find(fullProcessedPath)){if (!NFile::NDirectory::DeleteFileAlways(fullProcessedPath)){PrintString(UString(kCantDeleteOutputFile) + fullProcessedPath);return E_ABORT;}}_outFileStreamSpec = new COutFileStream;CMyComPtr<ISequentialOutStream> outStreamLoc(_outFileStreamSpec);if (!_outFileStreamSpec->Open(fullProcessedPath, CREATE_ALWAYS)){PrintString((UString)L"can not open output file " + fullProcessedPath);return E_ABORT;}_outFileStream = outStreamLoc;*outStream = outStreamLoc.Detach();}return S_OK;
}
STDMETHODIMP CArchiveExtractCallback::PrepareOperation(Int32 askExtractMode)
{_extractMode = false;switch (askExtractMode){case NArchive::NExtract::NAskMode::kExtract:  _extractMode = true; break;};switch (askExtractMode){case NArchive::NExtract::NAskMode::kExtract:  PrintString(kExtractingString); break;case NArchive::NExtract::NAskMode::kTest:  PrintString(kTestingString); break;case NArchive::NExtract::NAskMode::kSkip:  PrintString(kSkippingString); break;};PrintString(_filePath);return S_OK;
}
STDMETHODIMP CArchiveExtractCallback::SetOperationResult(Int32 operationResult)
{switch(operationResult){case NArchive::NExtract::NOperationResult::kOK:break;default:{NumErrors++;PrintString("     ");switch(operationResult){case NArchive::NExtract::NOperationResult::kUnSupportedMethod:PrintString(kUnsupportedMethod);break;case NArchive::NExtract::NOperationResult::kCRCError:PrintString(kCRCFailed);break;case NArchive::NExtract::NOperationResult::kDataError:PrintString(kDataError);break;default:PrintString(kUnknownError);}}}if (_outFileStream != NULL){if (_processedFileInfo.MTimeDefined){_outFileStreamSpec->SetMTime(&_processedFileInfo.MTime);}RINOK(_outFileStreamSpec->Close());}_outFileStream.Release();if (_extractMode && _processedFileInfo.AttribDefined){NFile::NDirectory::MySetFileAttributes(_diskFilePath, _processedFileInfo.Attrib);}PrintNewLine();return S_OK;
}
STDMETHODIMP CArchiveExtractCallback::CryptoGetTextPassword(BSTR *password)
{if (!PasswordIsDefined){// You can ask real password here from user// Password = GetPassword(OutStream);// PasswordIsDefined = true;PrintError("Password is not defined");return E_ABORT;}return StringToBstr(Password, password);
}//
// Archive Creating callback classstruct CDirItem
{UInt64 Size;FILETIME CTime;FILETIME ATime;FILETIME MTime;UString Name;UString FullPath;UInt32 Attrib;bool isDir() const {return (Attrib & FILE_ATTRIBUTE_DIRECTORY) != 0 ;}
};class CArchiveUpdateCallback: public IArchiveUpdateCallback2, public ICryptoGetTextPassword2, public CMyUnknownImp
{
public:MY_UNKNOWN_IMP2(IArchiveUpdateCallback2, ICryptoGetTextPassword2)// IProgressSTDMETHOD(SetTotal)(UInt64 size);STDMETHOD(SetCompleted)(const UInt64 *completeValue);// IUpdateCallback2STDMETHOD(EnumProperties)(IEnumSTATPROPSTG **enumerator);STDMETHOD(GetUpdateItemInfo)(UInt32 index, Int32 *newData, Int32 *newProperties, UInt32 *indexInArchive);STDMETHOD(GetProperty)(UInt32 index, PROPID propID, PROPVARIANT *value);STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **inStream);STDMETHOD(SetOperationResult)(Int32 operationResult);STDMETHOD(GetVolumeSize)(UInt32 index, UInt64 *size);STDMETHOD(GetVolumeStream)(UInt32 index, ISequentialOutStream **volumeStream);STDMETHOD(CryptoGetTextPassword2)(Int32 *passwordIsDefined, BSTR *password);public:CRecordVector<UInt64> VolumesSizes;UString VolName;UString VolExt;UString DirPrefix;const CObjectVector<CDirItem> *DirItems;bool PasswordIsDefined;UString Password;bool AskPassword;bool m_NeedBeClosed;UStringVector FailedFiles;CRecordVector<HRESULT> FailedCodes;CArchiveUpdateCallback(): PasswordIsDefined(false), AskPassword(false), DirItems(0) {};~CArchiveUpdateCallback() { Finilize(); }HRESULT Finilize();void Init(const CObjectVector<CDirItem> *dirItems){DirItems = dirItems;m_NeedBeClosed = false;FailedFiles.Clear();FailedCodes.Clear();}
};STDMETHODIMP CArchiveUpdateCallback::SetTotal(UInt64 /* size */)
{return S_OK;
}
STDMETHODIMP CArchiveUpdateCallback::SetCompleted(const UInt64 * /* completeValue */)
{return S_OK;
}
STDMETHODIMP CArchiveUpdateCallback::EnumProperties(IEnumSTATPROPSTG ** /* enumerator */)
{return E_NOTIMPL;
}
STDMETHODIMP CArchiveUpdateCallback::GetUpdateItemInfo(UInt32 /* index */, Int32 *newData, Int32 *newProperties, UInt32 *indexInArchive)
{if (newData != NULL){*newData = BoolToInt(true);}if (newProperties != NULL){*newProperties = BoolToInt(true);}if (indexInArchive != NULL){*indexInArchive = (UInt32)-1;}return S_OK;
}
STDMETHODIMP CArchiveUpdateCallback::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)
{NWindows::NCOM::CPropVariant prop;if (propID == kpidIsAnti){prop = false;prop.Detach(value);return S_OK;}{const CDirItem &dirItem = (*DirItems)[index];switch(propID){case kpidPath:  prop = dirItem.Name; break;case kpidIsDir:  prop = dirItem.isDir(); break;case kpidSize:  prop = dirItem.Size; break;case kpidAttrib:  prop = dirItem.Attrib; break;case kpidCTime:  prop = dirItem.CTime; break;case kpidATime:  prop = dirItem.ATime; break;case kpidMTime:  prop = dirItem.MTime; break;}}prop.Detach(value);return S_OK;
}HRESULT CArchiveUpdateCallback::Finilize()
{if (m_NeedBeClosed){PrintNewLine();m_NeedBeClosed = false;}return S_OK;
}static void GetStream2(const wchar_t *name)
{PrintString("Compressing  ");if (name[0] == 0){name = kEmptyFileAlias;}PrintString(name);
}STDMETHODIMP CArchiveUpdateCallback::GetStream(UInt32 index, ISequentialInStream **inStream)
{RINOK(Finilize());const CDirItem &dirItem = (*DirItems)[index];GetStream2(dirItem.Name);if (dirItem.isDir()){return S_OK;}{CInFileStream *inStreamSpec = new CInFileStream;CMyComPtr<ISequentialInStream> inStreamLoc(inStreamSpec);UString path = DirPrefix + dirItem.FullPath;if (!inStreamSpec->Open(path)){DWORD sysError = ::GetLastError();FailedCodes.Add(sysError);FailedFiles.Add(path);// if (systemError == ERROR_SHARING_VIOLATION){PrintNewLine();PrintError("WARNING: can't open file");// PrintString(NError::MyFormatMessageW(systemError));return S_FALSE;}// return sysError;}*inStream = inStreamLoc.Detach();}return S_OK;
}
STDMETHODIMP CArchiveUpdateCallback::SetOperationResult(Int32 /* operationResult */)
{m_NeedBeClosed = true;return S_OK;
}
STDMETHODIMP CArchiveUpdateCallback::GetVolumeSize(UInt32 index, UInt64 *size)
{if (VolumesSizes.Size() == 0){return S_FALSE;}if (index >= (UInt32)VolumesSizes.Size()){index = VolumesSizes.Size() - 1;}*size = VolumesSizes[index];return S_OK;
}
STDMETHODIMP CArchiveUpdateCallback::GetVolumeStream(UInt32 index, ISequentialOutStream **volumeStream)
{wchar_t temp[16];ConvertUInt32ToString(index + 1, temp);UString res = temp;while (res.Length() < 2){res = UString(L'0') + res;}UString fileName = VolName;fileName += L'.';fileName += res;fileName += VolExt;COutFileStream *streamSpec = new COutFileStream;CMyComPtr<ISequentialOutStream> streamLoc(streamSpec);if (!streamSpec->Create(fileName, false)){return ::GetLastError();}*volumeStream = streamLoc.Detach();return S_OK;
}
STDMETHODIMP CArchiveUpdateCallback::CryptoGetTextPassword2(Int32 *passwordIsDefined, BSTR *password)
{if (!PasswordIsDefined){if (AskPassword){// You can ask real password here from user// Password = GetPassword(OutStream);// PasswordIsDefined = true;PrintError("Password is not defined");return E_ABORT;}}*passwordIsDefined = BoolToInt(PasswordIsDefined);return StringToBstr(Password, password);
}//
// Main function#define NT_CHECK_FAIL_ACTION PrintError("Unsupported Windows version"); return 1;int MY_CDECL main(int numArgs, const char *args[])
{NT_CHECKint mmm = 0;while(mmm++ < 1){NWindows::NDLL::CLibrary lib;if (!lib.Load(L"7zxr.dll")){PrintError("Can not load 7-zip library");return 1;}CreateObjectFunc createObjectFunc = (CreateObjectFunc)lib.GetProc("CreateObject");if (createObjectFunc == 0){PrintError("Can not get CreateObject");return 1;}char c = 'x';UString archiveName = GetUnicodeString("E:\\123.7z");{bool listCommand;if (c == 'l'){listCommand = true;}else if (c == 'x'){listCommand = false;}else{PrintError("incorrect command");return 1;}CMyComPtr<IInArchive> archive;if (createObjectFunc(&CLSID_CFormat7z, &IID_IInArchive, (void **)&archive) != S_OK){PrintError("Can not get class object");return 1;}CInFileStream *fileSpec = new CInFileStream;CMyComPtr<IInStream> file = fileSpec;if (!fileSpec->Open(archiveName)){PrintError("Can not open archive file");return 1;}{CArchiveOpenCallback *openCallbackSpec = new CArchiveOpenCallback;CMyComPtr<IArchiveOpenCallback> openCallback(openCallbackSpec);openCallbackSpec->PasswordIsDefined = false;// openCallbackSpec->PasswordIsDefined = true;// openCallbackSpec->Password = L"1";if (archive->Open(file, 0, openCallback) != S_OK){PrintError("Can not open archive");return 1;}}if (listCommand){// List commandUInt32 numItems = 0;archive->GetNumberOfItems(&numItems);for (UInt32 i = 0; i < numItems; i++){{// Get uncompressed size of fileNWindows::NCOM::CPropVariant prop;archive->GetProperty(i, kpidSize, &prop);UString s = ConvertPropVariantToString(prop);//PrintString(s);//PrintString("  ");}{// Get name of fileNWindows::NCOM::CPropVariant prop;archive->GetProperty(i, kpidPath, &prop);UString s = ConvertPropVariantToString(prop);//PrintString(s);}//PrintString("\n");}}else{// Extract commandCArchiveExtractCallback *extractCallbackSpec = new CArchiveExtractCallback;CMyComPtr<IArchiveExtractCallback> extractCallback(extractCallbackSpec);extractCallbackSpec->Init(archive, L"e:\\XSBDownload"); // second parameter is output folder pathextractCallbackSpec->PasswordIsDefined = false;// extractCallbackSpec->PasswordIsDefined = true;// extractCallbackSpec->Password = L"1";HRESULT result = archive->Extract(NULL, (UInt32)(Int32)(-1), false, extractCallback);if (result != S_OK){PrintError("Extract Error");return 1;}}}}return 0;
}



demo:http://download.csdn.net/download/sz76211822/10039869

这篇关于C++ 调用7z SDK 解压的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/781713

相关文章

从入门到精通C++11 <chrono> 库特性

《从入门到精通C++11<chrono>库特性》chrono库是C++11中一个非常强大和实用的库,它为时间处理提供了丰富的功能和类型安全的接口,通过本文的介绍,我们了解了chrono库的基本概念... 目录一、引言1.1 为什么需要<chrono>库1.2<chrono>库的基本概念二、时间段(Durat

C++20管道运算符的实现示例

《C++20管道运算符的实现示例》本文简要介绍C++20管道运算符的使用与实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录标准库的管道运算符使用自己实现类似的管道运算符我们不打算介绍太多,因为它实际属于c++20最为重要的

Java中调用数据库存储过程的示例代码

《Java中调用数据库存储过程的示例代码》本文介绍Java通过JDBC调用数据库存储过程的方法,涵盖参数类型、执行步骤及数据库差异,需注意异常处理与资源管理,以优化性能并实现复杂业务逻辑,感兴趣的朋友... 目录一、存储过程概述二、Java调用存储过程的基本javascript步骤三、Java调用存储过程示

Visual Studio 2022 编译C++20代码的图文步骤

《VisualStudio2022编译C++20代码的图文步骤》在VisualStudio中启用C++20import功能,需设置语言标准为ISOC++20,开启扫描源查找模块依赖及实验性标... 默认创建Visual Studio桌面控制台项目代码包含C++20的import方法。右键项目的属性:

c++中的set容器介绍及操作大全

《c++中的set容器介绍及操作大全》:本文主要介绍c++中的set容器介绍及操作大全,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录​​一、核心特性​​️ ​​二、基本操作​​​​1. 初始化与赋值​​​​2. 增删查操作​​​​3. 遍历方

解析C++11 static_assert及与Boost库的关联从入门到精通

《解析C++11static_assert及与Boost库的关联从入门到精通》static_assert是C++中强大的编译时验证工具,它能够在编译阶段拦截不符合预期的类型或值,增强代码的健壮性,通... 目录一、背景知识:传统断言方法的局限性1.1 assert宏1.2 #error指令1.3 第三方解决

C++11委托构造函数和继承构造函数的实现

《C++11委托构造函数和继承构造函数的实现》C++引入了委托构造函数和继承构造函数这两个重要的特性,本文主要介绍了C++11委托构造函数和继承构造函数的实现,具有一定的参考价值,感兴趣的可以了解一下... 目录引言一、委托构造函数1.1 委托构造函数的定义与作用1.2 委托构造函数的语法1.3 委托构造函

C++11作用域枚举(Scoped Enums)的实现示例

《C++11作用域枚举(ScopedEnums)的实现示例》枚举类型是一种非常实用的工具,C++11标准引入了作用域枚举,也称为强类型枚举,本文主要介绍了C++11作用域枚举(ScopedEnums... 目录一、引言二、传统枚举类型的局限性2.1 命名空间污染2.2 整型提升问题2.3 类型转换问题三、C

Python中Tensorflow无法调用GPU问题的解决方法

《Python中Tensorflow无法调用GPU问题的解决方法》文章详解如何解决TensorFlow在Windows无法识别GPU的问题,需降级至2.10版本,安装匹配CUDA11.2和cuDNN... 当用以下代码查看GPU数量时,gpuspython返回的是一个空列表,说明tensorflow没有找到

C++链表的虚拟头节点实现细节及注意事项

《C++链表的虚拟头节点实现细节及注意事项》虚拟头节点是链表操作中极为实用的设计技巧,它通过在链表真实头部前添加一个特殊节点,有效简化边界条件处理,:本文主要介绍C++链表的虚拟头节点实现细节及注... 目录C++链表虚拟头节点(Dummy Head)一、虚拟头节点的本质与核心作用1. 定义2. 核心价值二