================================================================================
标题: 共享对象模板
作者: 叶飞虎
日期: 2018.12.02
在多线程编程中,涉及多线程对象共享,为了保证对象有效,防止出现一个线程正在操作
对象而另外线程正在或已经释放对象的情况。共享对象模板就是针对多线程共享对象而设计,
通过对象引用计数来保证对象有效及对象释放的向后延迟。
共享对象的头文件(KYShareObj.h)如下:
// =======================================
// Unit : share object(共享对象)
// Version: 4.0.0.0 (build 2018.12.02)
// Author : Kyee Ye
// Email : kyee_ye(at)126.com
// Copyright (C) Kyee workroom
// =======================================
#ifndef _KYShareObj_H_
#define _KYShareObj_H_
#include "KYInterlock.h"
// KYLib 2.0 开始使用 KYLib 命名空间
namespace KYLib
{
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/* TKYShare - 共享数据类 */
class TKYShare
{
public:
// 释放数据的回调函数类型
typedef void (*TDoFree)(void* AData, int ASize);
// 共享数据项
typedef struct
{
int RefCount; // 引用计数, 注: 不允许外部修改值
int Size; // 数据缓冲区尺寸, 若为 0 表示尺寸未知
void* Data; // 数据指针
TDoFree DoFree; // 释放 Data 的回调函数, 若为 NULL 则调用 free 释放
} TItem, *PItem;
public:
// 构造函数
TKYShare() { FItem = NULL; }
TKYShare(TItem* AItem) { FItem = _IncRef(AItem); }
TKYShare(const TKYShare& AValue) { FItem = _IncRef(AValue.FItem); }
TKYShare(void* AData, int ASize = 0, TDoFree ADoFree = NULL)
{ FItem = _New(AData, ASize, ADoFree); }
// 析构函数
~TKYShare() { _Reset(FItem); }
// 赋值操作
TKYShare& operator=(TItem* AItem)
{ return Set(AItem); }
TKYShare& operator=(const TKYShare& AValue)
{ return Set(AValue); }
// 类型转换
operator const void* () const { return Data(); }
operator void* () const { return Data(); }
// 取数据指针并返回缓冲区尺寸
void* Data(int& ASize) const;
// 取数据指针
void* Data() const { return FItem != NULL ? FItem->Data : NULL; }
// 取数据缓冲区尺寸
// 1. 若 FItem == NULL 则返回值为 -1
// 2. 若返回值为 0 则表示缓冲区尺寸未知
int Size() const { return FItem != NULL ? FItem->Size : -1; }
// 判断是否为空/有效
bool IsNull() const { return FItem == NULL; }
bool IsValid() const { return FItem != NULL; }
// 复位共享数据项
TKYShare& Reset() { _Reset(FItem); return *this; }
// 设置共享数据项
TKYShare& Set(TItem* AItem) { _Move(FItem, _IncRef(AItem)); return *this; }
TKYShare& Set(const TKYShare& AValue)
{
if (&AValue != this)
_Move(FItem, _IncRef(AValue.FItem));
return *this;
}
// 设置共享数据项
TKYShare& Set(void* AData, int ASize = 0, TDoFree ADoFree = NULL)
{
_Move(FItem, _New(AData, ASize, ADoFree));
return *this;
}
// 替换共享数据项指针, 注: 未加引用 AItem
TKYShare& Move(TItem* AItem) { _Move(FItem, AItem); return *this; }
private:
TItem* FItem; // 共享数据项指针
public:
// 分配共享数据项
// 参数:
// AData 共享数据指针, 若为 NULL 则返回值为 NULL
// ASize 共享数据缓冲区尺寸, 若为 0 表示未知
// ADoFree 释放 AData 的回调函数, 若为 NULL 则调用 free 释放
// 返回值:
// NULL 分配失败或 AData == NULL
// (非空) 分配成功, 注: 返回值最后必须调用 _DecRef 释放项
static TItem* _New(void* AData, int ASize = 0, TDoFree ADoFree = NULL);
// 共享数据项加引用
static TItem* _IncRef(const TKYShare& AShare)
{ return _IncRef(AShare.FItem); }
static TItem* _IncRef(TItem* AItem);
static TItem* _IncRef(TItem* AItem, int& ARefCount);
// 共享数据项减引用, 注: AItem == NULL 则返回值为 -1
static int _DecRef(TItem* AItem);
static int _DecRefEx(PItem& AItem)
{
PItem pItem = (PItem)InterlockedExchangePointer((void**)&AItem, NULL);
return _DecRef(pItem);
}
// 共享数据项替换/复位
static void _Move(PItem& AItem, TItem* ANew)
{ ANew = (PItem)InterlockedExchangePointer((void**)&AItem, (void*)ANew);
_DecRef(ANew); }
static void _Reset(PItem& AItem) { _Move(AItem, NULL); }
public:
// 分配/释放共享数据项的回调函数类型
typedef TItem* (*TNewItem)();
typedef void (*TFreeItem)(TItem* AItem);
// 初始化共享数据类中分配和释放共享数据项的函数指针
// 参数:
// ANew 分配共享数据项的函数指针, 若为 NULL 则调用 malloc 分配项
// AFree 释放共享数据项的函数指针, 若为 NULL 则调用 free 释放项
// 注:
// 1. 此函数只能调用一次;
// 2. 若不调用 _Init 函数则默认使用 malloc 和 free 来分配和释放共享数据项
static bool _InitFuncs(TNewItem ANew, TFreeItem AFree);
private:
static bool _IsInited; // 是否已经初始化
static TNewItem _FuncNew; // 分配共享数据项的函数指针
static TFreeItem _FuncFree; // 释放共享数据项的函数指针
};
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/* TKYShareObj - 共享对象类 */
template <class T>
class TKYShareObj
{
private:
// 重定义项类型
typedef TKYShare::TItem TItem;
public:
// 构造函数
TKYShareObj() { FItem = NULL; }
TKYShareObj(T* AObj, int ATag = 0) { FItem = TKYShare::_New(AObj, ATag,
(TKYShare::TDoFree)&TKYShareObj<T>::_DoFree); }
TKYShareObj(const TKYShareObj<T>& AValue)
{ FItem = TKYShare::_IncRef(AValue.FItem); }
// 析构函数
~TKYShareObj() { TKYShare::_Reset(FItem); }
// 赋值操作
TKYShareObj<T>& operator=(const TKYShareObj<T>& AValue)
{ return Set(AValue); }
// -> 操作符重载, 注: 共享对象必须有效, 即 FItem != NULL
T* operator->() const { return (T*)FItem->Data; }
// 类型转换
operator const T* () const { return Obj(); }
operator T* () const { return Obj(); }
// 取对象指针并返回 tag
T* Obj(int& ATag) const;
// 取对象指针
T* Obj() const { return FItem != NULL ? (T*)FItem->Data : NULL; }
// 取对象的 tag, 若 FItem == NULL 则返回值为 -1
int Tag() const { return FItem != NULL ? FItem->Size : -1; }
// 判断是否为空/有效
bool IsNull() const { return FItem == NULL; }
bool IsValid() const { return FItem != NULL; }
// 复位共享对象
TKYShareObj<T>& Reset() { TKYShare::_Reset(FItem); return *this; }
// 设置共享对象
TKYShareObj<T>& Set(const TKYShareObj<T>& AValue)
{
if (&AValue != this)
TKYShare::_Move(FItem, TKYShare::_IncRef(AValue.FItem));
return *this;
}
// 新建共享对象
TKYShareObj<T>& New(T* AObj, int ATag = 0)
{
TItem* pNew = TKYShare::_New(AObj, ATag,
(TKYShare::TDoFree)&TKYShareObj<T>::_DoFree);
TKYShare::_Move(FItem, pNew);
return *this;
}
private:
TItem* FItem;
private:
// 释放对象函数
static void _DoFree(T* AObj, int ATag);
};
// 释放对象函数
template <class T>
void TKYShareObj<T>::_DoFree(T* AObj, int ATag)
{
delete AObj;
}
// 取对象指针并返回 tag
template <class T>
T* TKYShareObj<T>::Obj(int& ATag) const
{
TItem* pItem = FItem;
if (pItem != NULL)
{
ATag = pItem->Size;
return (T*)pItem->Data;
}
else
{
ATag = -1;
return NULL;
}
}
}
#endif
共享对象的代码文件(KYShareObj.cpp)如下:
// =======================================
// Unit : share object(共享对象)
// Version: 4.0.0.0 (build 2018.12.02)
// Author : Kyee Ye
// Email : kyee_ye(at)126.com
// Copyright (C) Kyee workroom
// =======================================
#include "KYShareObj.h"
// KYLib 2.0 开始使用 KYLib 命名空间
namespace KYLib
{
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/* TKYShare - 共享数据类 */
// ---------------- 内部函数 ----------------
// 分配共享数据项
static TKYShare::TItem* L_NewItem()
{
return (TKYShare::TItem*)malloc(sizeof(TKYShare::TItem));
}
// 释放共享数据项
static void L_FreeItem(TKYShare::TItem* AItem)
{
free(AItem);
}
// ---------------- 静态成员 ----------------
// 分配/释放共享数据项的函数指针
bool TKYShare::_IsInited = false;
TKYShare::TNewItem TKYShare::_FuncNew = L_NewItem;
TKYShare::TFreeItem TKYShare::_FuncFree = L_FreeItem;
// ---------------- 静态函数 ----------------
// 初始化共享数据类中分配和释放共享数据项的函数指针
// 参数:
// ANew 分配共享数据项的函数指针, 若为 NULL 则调用 malloc 分配项
// AFree 释放共享数据项的函数指针, 若为 NULL 则调用 free 释放项
// 注:
// 1. 此函数只能调用一次;
// 2. 若不调用 _Init 函数则默认使用 malloc 和 free 来分配和释放共享数据项
bool TKYShare::_InitFuncs(TNewItem ANew, TFreeItem AFree)
{
// 初始化
bool result = false;
// 判断是否未初始化
if (!_IsInited)
{
_IsInited = true;
result = true;
if (ANew != NULL)
_FuncNew = ANew;
if (AFree != NULL)
_FuncFree= AFree;
}
// 返回结果
return result;
}
// 分配共享数据项
// 参数:
// AData 共享数据指针, 若为 NULL 则返回值为 NULL
// ASize 共享数据缓冲区尺寸, 若为 0 表示未知
// ADoFree 释放 AData 的回调函数, 若为 NULL 则调用 free 释放
// 返回值:
// NULL 分配失败或 AData == NULL
// (非空) 分配成功, 注: 返回值最后必须调用 _DecRef 释放项
TKYShare::TItem* TKYShare::_New(void* AData, int ASize, TDoFree ADoFree)
{
// 初始化
TItem* result = NULL;
// 检查参数
if (AData != NULL)
{
result = _FuncNew();
if (result != NULL)
{
result->DoFree = ADoFree;
result->Size = ASize;
result->Data = AData;
result->RefCount = 1;
}
}
// 返回结果
return result;
}
// 共享数据项加引用
TKYShare::TItem* TKYShare::_IncRef(TItem* AItem)
{
// 初始化
TItem* result = NULL;
// 检查参数
if (AItem == NULL)
;
else if (InterlockedIncrement(&AItem->RefCount) > 1)
result = AItem;
else
InterlockedDecrement(&AItem->RefCount);
// 返回结果
return result;
}
// 共享数据项加引用
TKYShare::TItem* TKYShare::_IncRef(TItem* AItem, int& ARefCount)
{
// 初始化
TItem* result = NULL;
// 检查参数
ARefCount = -1;
if (AItem != NULL)
{
ARefCount = InterlockedIncrement(&AItem->RefCount);
if (ARefCount > 1)
result = AItem;
else
InterlockedDecrement(&AItem->RefCount);
}
// 返回结果
return result;
}
// 共享数据项减引用
int TKYShare::_DecRef(TItem* AItem)
{
// 初始化
int result = -1;
// 检查参数
if (AItem != NULL)
{
result = InterlockedDecrement(&AItem->RefCount);
if (result == 0)
{
// 拷贝项
void* pData = InterlockedExchangePointer(&AItem->Data, NULL);
int intSize = AItem->Size;
TDoFree funcFree = AItem->DoFree;
// 释放项
_FuncFree(AItem);
if (pData == NULL)
;
else if (funcFree != NULL)
funcFree(pData, intSize);
else
free(pData);
}
}
// 返回结果
return result;
}
// ---------------- 构造函数和析构函数 ----------------
// 构造函数
// 析构函数
// ---------------- 私有函数 ----------------
// ---------------- 保护函数 ----------------
// ---------------- 公有函数 ----------------
// 取数据指针并返回缓冲区尺寸
void* TKYShare::Data(int& ASize) const
{
TItem* pItem = FItem;
if (pItem != NULL)
{
ASize = pItem->Size;
return pItem->Data;
}
else
{
ASize = -1;
return NULL;
}
}
}
共享对象的示例(test.cpp)如下:
// test.cpp : Defines the entry point for the console application.
//
#include <stdio.h>
#include "KYLib.h"
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
static void L_DoFree(void* AData, int ASize)
{
printf("~~~ free {data: %p, size: %d}\n", AData, ASize);
}
void Test1()
{
TKYShare::TItem* pItem = NULL;
TKYShare objS1;
printf("{ test1\n");
printf("... S1 {is-valid: %5s, is-null: %5s, data: %p, size: %d}\n",
Bool_Str[objS1.IsValid()], Bool_Str[objS1.IsNull()], (const void*)objS1, objS1.Size());
{
TKYShare objS2((void*)0x123456, 123, &L_DoFree);
printf("... S2 {is-valid: %5s, is-null: %5s, data: %p, size: %d}\n",
Bool_Str[objS2.IsValid()], Bool_Str[objS2.IsNull()], (const void*)objS2, objS2.Size());
objS1 = objS2;
printf("... S1 {is-valid: %5s, is-null: %5s, data: %p, size: %d}\n",
Bool_Str[objS1.IsValid()], Bool_Str[objS1.IsNull()], (void*)objS1, objS1.Size());
objS2.Reset();
printf("... S2 {is-valid: %5s, is-null: %5s, data: %p, size: %d}\n",
Bool_Str[objS2.IsValid()], Bool_Str[objS2.IsNull()], (const void*)objS2, objS2.Size());
pItem = TKYShare::_IncRef(objS1);
printf("^^^ item {ref-count: %d, data: %p, size: %d, do-free: %p}\n",
pItem->RefCount, pItem->Data, pItem->Size, pItem->DoFree);
objS2.Set(pItem);
printf("... S2 {is-valid: %5s, is-null: %5s, data: %p, size: %d}\n",
Bool_Str[objS2.IsValid()], Bool_Str[objS2.IsNull()], (const void*)objS2, objS2.Size());
printf("^^^ item {ref-count: %d, data: %p, size: %d, do-free: %p}\n",
pItem->RefCount, pItem->Data, pItem->Size, pItem->DoFree);
TKYShare objS3((void*)0x3333, 33, &L_DoFree);
printf("... S3 {is-valid: %5s, is-null: %5s, data: %p, size: %d}\n",
Bool_Str[objS3.IsValid()], Bool_Str[objS3.IsNull()], (const void*)objS3, objS3.Size());
objS2.Move(TKYShare::_IncRef(objS3));
printf("... S2 {is-valid: %5s, is-null: %5s, data: %p, size: %d}\n",
Bool_Str[objS2.IsValid()], Bool_Str[objS2.IsNull()], (const void*)objS2, objS2.Size());
printf("^^^ item {ref-count: %d, data: %p, size: %d, do-free: %p}\n",
pItem->RefCount, pItem->Data, pItem->Size, pItem->DoFree);
}
printf("^^^ item {ref-count: %d, data: %p, size: %d, do-free: %p}\n",
pItem->RefCount, pItem->Data, pItem->Size, pItem->DoFree);
if (pItem != NULL)
printf("--- dec-ref {ref-count: %d}\n", TKYShare::_DecRef(pItem));
printf("} test1\n");
}
int StrRefCount(const KYString& AStr)
{
int result = -1;
char* pStr = (char*)AStr;
if (pStr != NULL)
result = ((KYString::TItem*)(pStr - __Offset__(KYString::TItem, Content)))->RefCount;
return result;
}
typedef TKYShareObj<KYString> TShareStr;
void Test2()
{
KYString S1 = "1111111";
KYString S2 = "22222222222222";
printf("{ test2\n");
printf("... S1 {ref-count: %d, length: %d, str: %s}\n",
StrRefCount(S1), S1.Length(), (const char*)S1);
printf("... S2 {ref-count: %d, length: %d, str: %s}\n",
StrRefCount(S2), S2.Length(), (const char*)S2);
{
KYString* objP1 = new KYString(S1);
KYString* objP2 = new KYString(S2);
printf("\nKYString* objP1 = new KYString(S1)\n");
printf("KYString* objP2 = new KYString(S2)\n");
printf("... p1 {ref-count: %d, length: %d, obj: %p, str: %s}\n",
StrRefCount(S1), objP1->Length(), objP1, (const char*)*objP1);
printf("... p2 {ref-count: %d, length: %d, obj: %p, str: %s}\n",
StrRefCount(S2), objP2->Length(), objP2, (const char*)*objP2);
TShareStr objX1(objP1, 111);
TShareStr objX2(objP2, 222);
printf("\nTShareStr objX1(objP1, 111)\n");
printf("TShareStr objX2(objP2, 111)\n");
printf("... S1 {ref-count: %d, length: %d, str: %s}\n",
StrRefCount(S1), S1.Length(), (const char*)S1);
printf("... S2 {ref-count: %d, length: %d, str: %s}\n",
StrRefCount(S2), S2.Length(), (const char*)S2);
printf("... x1 {ref-count: %d, length: %d, obj: %p, tag: %d, str: %s}\n",
StrRefCount(*objX1.Obj()), objX1->Length(), objX1.Obj(), objX1.Tag(), (const char*)*objX1.Obj());
printf("... x2 {ref-count: %d, length: %d, obj: %p, tag: %d, str: %s}\n",
StrRefCount(*objX2.Obj()), objX2->Length(), objX2.Obj(), objX2.Tag(), (const char*)*objX2.Obj());
*objP2 = S1;
printf("\n*objP2 = S1\n");
printf("... S1 {ref-count: %d, length: %d, str: %s}\n",
StrRefCount(S1), S1.Length(), (const char*)S1);
printf("... S2 {ref-count: %d, length: %d, str: %s}\n",
StrRefCount(S2), S2.Length(), (const char*)S2);
printf("... x1 {ref-count: %d, length: %d, obj: %p, tag: %d, str: %s}\n",
StrRefCount(*objX1.Obj()), objX1->Length(), objX1.Obj(), objX1.Tag(), (const char*)*objX1.Obj());
printf("... x2 {ref-count: %d, length: %d, obj: %p, tag: %d, str: %s}\n",
StrRefCount(*objX2.Obj()), objX2->Length(), objX2.Obj(), objX2.Tag(), (const char*)*objX2.Obj());
*(KYString*)objX2 = S2;
printf("\n*(KYString*)objX2 = S2\n");
printf("... S1 {ref-count: %d, length: %d, str: %s}\n",
StrRefCount(S1), S1.Length(), (const char*)S1);
printf("... S2 {ref-count: %d, length: %d, str: %s}\n",
StrRefCount(S2), S2.Length(), (const char*)S2);
printf("... x1 {ref-count: %d, length: %d, obj: %p, tag: %d, str: %s}\n",
StrRefCount(*objX1.Obj()), objX1->Length(), objX1.Obj(), objX1.Tag(), (const char*)*objX1.Obj());
printf("... x2 {ref-count: %d, length: %d, obj: %p, tag: %d, str: %s}\n",
StrRefCount(*objX2.Obj()), objX2->Length(), objX2.Obj(), objX2.Tag(), (const char*)*objX2.Obj());
objX1 = objX2;
printf("\nobjX1 = objX2\n");
printf("... S1 {ref-count: %d, length: %d, str: %s}\n",
StrRefCount(S1), S1.Length(), (const char*)S1);
printf("... S2 {ref-count: %d, length: %d, str: %s}\n",
StrRefCount(S2), S2.Length(), (const char*)S2);
printf("... x1 {ref-count: %d, length: %d, obj: %p, tag: %d, str: %s}\n",
StrRefCount(*objX1.Obj()), objX1->Length(), objX1.Obj(), objX1.Tag(), (const char*)*objX1.Obj());
printf("... x2 {ref-count: %d, length: %d, obj: %p, tag: %d, str: %s}\n",
StrRefCount(*objX2.Obj()), objX2->Length(), objX2.Obj(), objX2.Tag(), (const char*)*objX2.Obj());
}
printf("... S1 {ref-count: %d, length: %d, str: %s}\n",
StrRefCount(S1), S1.Length(), (const char*)S1);
printf("... S2 {ref-count: %d, length: %d, str: %s}\n",
StrRefCount(S2), S2.Length(), (const char*)S2);
printf("} test2\n");
}
// 主函数
int main(int argc, char* argv[])
{
// 初始化
InitKYLib(NULL);
Test1();
printf("\n");
Test2();
printf("\n");
getchar();
return 0;
}
共享对象的示例运行结果如下:
{ test1
... S1 {is-valid: false, is-null: true, data: 00000000, size: -1}
... S2 {is-valid: true, is-null: false, data: 00123456, size: 123}
... S1 {is-valid: true, is-null: false, data: 00123456, size: 123}
... S2 {is-valid: false, is-null: true, data: 00000000, size: -1}
^^^ item {ref-count: 2, data: 00123456, size: 123, do-free: 00401880}
... S2 {is-valid: true, is-null: false, data: 00123456, size: 123}
^^^ item {ref-count: 3, data: 00123456, size: 123, do-free: 00401880}
... S3 {is-valid: true, is-null: false, data: 00003333, size: 33}
... S2 {is-valid: true, is-null: false, data: 00003333, size: 33}
^^^ item {ref-count: 2, data: 00123456, size: 123, do-free: 00401880}
~~~ free {data: 00003333, size: 33}
^^^ item {ref-count: 2, data: 00123456, size: 123, do-free: 00401880}
--- dec-ref {ref-count: 1}
} test1
~~~ free {data: 00123456, size: 123}
{ test2
... S1 {ref-count: 1, length: 7, str: 1111111}
... S2 {ref-count: 1, length: 14, str: 22222222222222}
KYString* objP1 = new KYString(S1)
KYString* objP2 = new KYString(S2)
... p1 {ref-count: 2, length: 7, obj: 01FE5E60, str: 1111111}
... p2 {ref-count: 2, length: 14, obj: 01FE5E78, str: 22222222222222}
TShareStr objX1(objP1, 111)
TShareStr objX2(objP2, 222)
... S1 {ref-count: 2, length: 7, str: 1111111}
... S2 {ref-count: 2, length: 14, str: 22222222222222}
... x1 {ref-count: 2, length: 7, obj: 01FE5E60, tag: 111, str: 1111111}
... x2 {ref-count: 2, length: 14, obj: 01FE5E78, tag: 222, str: 22222222222222}
*objP2 = S1
... S1 {ref-count: 3, length: 7, str: 1111111}
... S2 {ref-count: 1, length: 14, str: 22222222222222}
... x1 {ref-count: 3, length: 7, obj: 01FE5E60, tag: 111, str: 1111111}
... x2 {ref-count: 3, length: 7, obj: 01FE5E78, tag: 222, str: 1111111}
*(KYString*)objX2 = S2
... S1 {ref-count: 2, length: 7, str: 1111111}
... S2 {ref-count: 2, length: 14, str: 22222222222222}
... x1 {ref-count: 2, length: 7, obj: 01FE5E60, tag: 111, str: 1111111}
... x2 {ref-count: 2, length: 14, obj: 01FE5E78, tag: 222, str: 22222222222222}
objX1 = objX2
... S1 {ref-count: 1, length: 7, str: 1111111}
... S2 {ref-count: 2, length: 14, str: 22222222222222}
... x1 {ref-count: 2, length: 14, obj: 01FE5E78, tag: 222, str: 22222222222222}
... x2 {ref-count: 2, length: 14, obj: 01FE5E78, tag: 222, str: 22222222222222}
... S1 {ref-count: 1, length: 7, str: 1111111}
... S2 {ref-count: 1, length: 14, str: 22222222222222}
} test2
共享对象模板及示例代码(ShareObj.rar)下载如下:
https://pan.baidu.com/s/1eTWTYD0#list/path=%2Fshares%2Fsources%2F_open
================================================================================
来源:CSDN
作者:很土
链接:https://blog.csdn.net/kyee/article/details/104196212