BaseContainer Manual

内容表

关于

A BaseContainer is a collection of individual values. Each value has its own ID and type. A BaseContainer can also carry any number of child containers. 90% of Cinema 4D 's internal values are stored in containers and all messages are working with containers, so this class is an essential part of the SDK. Containers can store any GeData type, including custom data types. It is recommended to use the available containers to store values in custom NodeData based plugins.

警告
Keep in mind that there is no guarantee for a value to be in the container. Use default values whenever possible when accessing container's ID data.
Use the typed access methods (for example BaseContainer::GetBool() ) whenever possible, instead of the low-level BaseContainer::GetData() 。见 Access .
Once a container value has been set using one type one must neither try to access it using another type, nor overwrite it with a value of another type. Using the wrong access will not crash, but it is illegal.
注意
To browse through all elements of a BaseContainer use the class BrowseContainer .

Access

Every BaseList2D based object of the Cinema 4D API has a BaseContainer that stores its data. This BaseContainer can be accessed with:

BaseList2D Manual .

注意
Object parameters should be edited with C4DAtom::GetParameter() / C4DAtom::SetParameter() . Not all parameters of an object may be stored in the BaseContainer .
// This example accesses the BaseContainer storing the render settings. // The BaseContainer is needed as an argument of RenderDocument().
RenderData * const rdata = doc-> GetActiveRenderData (); if (rdata == nullptr ) return maxon::UnexpectedError( MAXON_SOURCE_LOCATION );
const BaseContainer renderSettings = rdata-> GetData (); AutoAlloc<BaseBitmap> bitmap; if (bitmap == nullptr ) return maxon::OutOfMemoryError( MAXON_SOURCE_LOCATION );

// prepare target bitmap const Int32 width = renderSettings. GetInt32 ( RDATA_XRES , 1280); const Int32 height = renderSettings. GetInt32 ( RDATA_YRES , 720); const IMAGERESULT imageRes = bitmap-> Init (width, height); if (imageRes != IMAGERESULT::OK ) return maxon::UnexpectedError( MAXON_SOURCE_LOCATION );

// render the image const RENDERFLAGS flags = RENDERFLAGS::NODOCUMENTCLONE ; const RENDERRESULT res = RenderDocument (doc, renderSettings, nullptr , nullptr , bitmap, flags, nullptr ); if (res != RENDERRESULT::OK ) return maxon::UnexpectedError( MAXON_SOURCE_LOCATION );

// show the result in the Picture Viewer ShowBitmap (bitmap);

BaseContainer elements are also often used as an argument in a function call.

拷贝

The complete content of a BaseContainer object can be copied to another object:

It is also possible to copy a BaseContainer using the copy constructor.

// This example creates a BaseContainer copies in different ways: BaseContainer original; original. SetString (100, "foo" _s); original. SetString (200, "bar" _s);

// CopyTo() BaseContainer target; target. SetString (300, "foobar" _s); if (!original. CopyTo (&target, COPYFLAGS::NONE , nullptr )) return maxon::UnexpectedError( MAXON_SOURCE_LOCATION ); ApplicationOutput (target. GetString (100)); ApplicationOutput (target. GetString (200)); ApplicationOutput (target. GetString (300)); // This value is now deleted.

// GetClone() BaseContainer * clone = original. GetClone ( COPYFLAGS::NONE , nullptr ); if (clone == nullptr ) return maxon::OutOfMemoryError( MAXON_SOURCE_LOCATION ); ApplicationOutput (clone-> GetString (100)); ApplicationOutput (clone-> GetString (200)); DeleteObj (clone);

// Copy Constructor BaseContainer copy(original); ApplicationOutput (copy.GetString(100)); ApplicationOutput (copy.GetString(200));

// Assignment BaseContainer assignment = original; ApplicationOutput (assignment. GetString (100)); ApplicationOutput (assignment. GetString (200));

注意
To merge containers see BaseContainer::MergeContainer() in chapter Functionality .

数据

ID

A BaseContainer can have an ID. This ID can be used to identify the container.

// This example shows how GetId() is used to identify the message sent to GeDialog::Message(). Int32 Message( const BaseContainer & msg, BaseContainer & result) { switch (msg. GetId ()) { case BFM_INTERACTSTART : { // interaction start; if the value is changed create an undo _interactStart = true ; break ; } case BFM_INTERACTEND : { // interaction end, if an undo was created, end it if (_undoStarted) { BaseDocument * const doc = GetActiveDocument (); doc-> EndUndo (); _undoStarted = false ; } _interactStart = false ; break ; } } return SUPER::Message(msg, result); }

Access

A BaseContainer stores its data using GeData objects. It is possible to access these GeData objects or the stored values directly using typed access functions. It is recommended to prefer the typed access functions.

A copy of a GeData element can be obtained with:

注意
The DescLevel::id property is used as the actual ID.
// This example stores some data in // the BaseContainer using SetParameter(). BaseContainer bc; bc. SetParameter ( DescID { 100 }, GeData { "foobar" }); GeData data; bc. GetParameter ( DescID { 100 }, data); ApplicationOutput (data. GetString ());

The GeData elements are also accessible via:

// This example stores some data in the // BaseContainer using a GeData object. BaseContainer bc; bc. SetData (100, GeData { "foobar" }); GeData data = bc. GetData (100); ApplicationOutput (data. GetString ());

For fast access read-only pointers to the GeData elements are accessible:

// This example accesses the data stored // in the BaseContainer using data pointers. BaseContainer bc; bc. SetData (100, GeData { "foo" }); bc. SetData (200, GeData { "bar" }); const Int count = 2; const GeData * data[count]; Int32 ids[] = { 100, 200 }; bc. GetDataPointers (ids, count, data); if (data[0]) ApplicationOutput (data[0]->GetString()); if (data[1]) ApplicationOutput (data[1]->GetString());

A GeData element is also accessible via its index in the BaseContainer :

// This example accesses the data stored in the // BaseContainer using the element index. BaseContainer bc; bc. SetData (100, GeData { "foo" }); bc. SetData (200, GeData { "bar" }); GeData * const first = bc. GetIndexData (0); if (first != nullptr ) ApplicationOutput (first-> GetString ()); GeData * const second = bc. GetIndexData (1); if (second != nullptr ) ApplicationOutput (second-> GetString ());

New GeData elements can be added to the BaseContainer :

// This example inserts some data into the // BaseContainer after the first element. BaseContainer bc; bc. SetData (100, "foo" ); bc. SetData (200, "foobar" ); GeData * const data = bc. GetIndexData (0); bc. InsDataAfter (150, String ( "bar" ), data);

To access the values of primitive data types, these access functions are available:

另请参阅 Primitive Data Types Manual (Classic) .

A BaseContainer can store a void pointer.

注意
This should not be used to store a reference to a C4DAtom based element; instead a BaseLink should be used.
// This example stores a void pointer // in the BaseContainer object. BaseContainer bc; bc. SetVoid (100, & object );

// ... SomeObject* const obj = static_cast< SomeObject* > (bc. GetVoid (100));

A BaseContainer can store raw memory:

// This example stores a BaseArray as raw memory. (in that case an array of Vectors)

// Calculates the size of the memory maxon::Int size = myArray.GetCount() * SIZEOF ( maxon::Vector ); if (size == 0) { bc. SetMemory (uniqueID, nullptr , 0); } else { // Retrieves the memory pointer using Disconnect. (so the memory will not be freed twice) void * pArray = myArray.Disconnect().Begin().GetPtr(); if (pArray == nullptr ) return maxon::UnknownError( MAXON_SOURCE_LOCATION );

// Sets the memory in the BaseContainer bc. SetMemory (uniqueID, pArray, size); }

// This Example reads a BaseArray stored as raw memory.

// Creates an array maxon::BaseArray<maxon::Vector> myOtherArray;

// Creates a block to retrieves the memory from the block. maxon::Block<maxon::Vector> myblock;

// Retrieves the memory, use GetMemoryAndRelease to release the pointer or the memory will be freed twice) void * memFromContainer = bc. GetMemoryAndRelease (( Int32 )uniqueID, size); if (memFromContainer != nullptr && size > 0) { // Calculates the number of elements for vectors size = size / SIZEOF ( maxon::Vector ); maxon::Vector * myMem = static_cast< maxon::Vector * > (memFromContainer); if (myMem == nullptr ) return maxon::NullptrError( MAXON_SOURCE_LOCATION );

// Fills the block with the memory. myblock. Set (myMem, size);

// Connects the array with the block. myOtherArray. 连接 (myblock, size); } // Prints the value of the array. for ( auto & value : myOtherArray) ApplicationOutput ( "Value is @" , value);

A BaseContainer offers also access functions for mathematical data types:

另请参阅 Vector Manual (Classic) and Matrix Manual (Classic) .

A BaseContainer offers further access functions for typical Cinema 4D data types:

另请参阅 String Manual (Classic) and Filename Manual .

另请参阅 BaseTime 手册 .

A BaseContainer also offers special functions to store and handle BaseLink objects:

另请参阅 BaseLink Manual .

// store the current selection in the BaseContainer BaseContainer bc; bc. SetLink (100, doc-> GetActiveMaterial ()); bc. SetLink (200, doc-> GetActiveObject ()); bc. SetLink (300, doc-> GetActiveTag ());

// ...

// get the material BaseMaterial * const mat = bc. GetMaterialLink (100, doc); if (mat) ApplicationOutput (mat-> GetName ());

// get the object BaseObject * const obj = bc. GetObjectLink (200, doc); if (obj) ApplicationOutput (obj-> GetName ());

// get tag BaseList2D * const link = bc. GetLink (300, doc); if (link) ApplicationOutput (link-> GetName ());

A BaseContainer object can also store further sub-containers:

// This example stores a sub-container // in the BaseContainer object. BaseContainer subcontainer; subcontainer. SetString (100, "foobar" _s); BaseContainer bc; bc. SetContainer (100, subcontainer);

// ... BaseContainer sub = bc. GetContainer (100); ApplicationOutput (sub. GetString (100));

A BaseContainer can also store custom data types. To set the value of the custom data type a GeData object is needed.

// This example gets the current time and saves is as DateTimeData in the BaseContainer. // Then the DateTimeData is received from that BaseContainer. DateTime time; GetDateTimeNow (time); AutoAlloc<DateTimeData> date; if (date == nullptr ) return maxon::OutOfMemoryError( MAXON_SOURCE_LOCATION ); date-> SetDateTime (time); GeData data; data. SetCustomDataType ( DATETIME_DATA , *date); BaseContainer bc; bc. SetData (100, data);

// ... const CustomDataType * const customData = bc. GetCustomDataType (100, DATETIME_DATA ); const DateTimeData * const dateTimeData = static_cast< const DateTimeData * > (customData); if (dateTimeData) { const DateTime dt = dateTimeData-> GetDateTime (); String result; result += String::IntToString (dt. year ) + ":" ; result += String::IntToString (dt. month ) + ":" ; result += String::IntToString (dt. day ) + " - " ; result += String::IntToString (dt. hour ) + ":" ; result += String::IntToString (dt. minute ) + ":" ; result += String::IntToString (dt. second );

// print output ApplicationOutput (result); }

These functions can be used to limit the values of a 向量 or Float value inside a BaseContaier:

注意
These functions simply apply ClampValue() , see also Mathematical Functions Manual (Classic) .

索引

The elements stored within a BaseContainer are accessible through their index and their ID:

另请参阅 BaseContainer::GetIndexData() above. To loop through values see also BrowseContainer .

// This example loops through the values of the BaseContainer. Int32 i = 0; while ( true ) { const Int32 id = bc. GetIndexId (i++); if ( id == NOTOK ) break ; else ApplicationOutput (bc. GetData ( id ). GetString ()); }

移除

Elements can be removed from a BaseContainer :

// This example removes some element from // the BaseContainer object. BaseContainer bc; bc. SetData (100, "foo" ); bc. SetData (200, "bar" );

// remove data entry 100 if (bc. RemoveData (100)) { GeData data = bc. GetData (100);

// check if data has no type (data is not set) if (data. GetType () == DA_NIL ) ApplicationOutput ( "Data removed" _s); }

Functionality

Several operations can be performed on a BaseContainer 对象。

// This example merges the content of two // BaseContainer objects. BaseContainer bc; bc. SetData (100, "foo" ); bc. SetData (200, "bar" ); BaseContainer bc2; bc2. SetData (100, "100" ); bc2. SetData (300, "300" ); bc. MergeContainer (bc2);

// this will result in the values // 100: "100" // 200: "bar" // 300: "300"

比较

Two BaseContainer are identical if they have the same ID, the same number of entries and if the entries are also identical and in the same order.

Detect Changes

The dirty state of a BaseContainer changes incrementally when a value stored in the BaseContainer is changed.

// This example checks the dirty state after a value was added and changed. BaseContainer bc; bc. SetData (100, "foo" ); UInt32 dirty = bc. GetDirty (); ApplicationOutput ( "Dirty State: " + String::UIntToString (dirty)); bc. SetData (100, "bar" ); dirty = bc. GetDirty (); ApplicationOutput ( "Dirty State: " + String::UIntToString (dirty));

BrowseContainer

The BrowseContainer class can be used to browse through the values stored in a BaseContainer . See also 索引 .

// This example loops through all values of the given BaseContainer. Int32 id; GeData * dat = nullptr ;

// init BrowseContainer BrowseContainer browse(&bc);

// loop through the values while (browse.GetNext(& id , &dat)) { ApplicationOutput ( "id: " + String::IntToString ( id ));

// check if the current data stores a String if (dat != nullptr ) { if (dat-> GetType () == DA_STRING ) ApplicationOutput ( "value: " + dat-> GetString ()); } }

Disc I/O

A BaseContainer can be stored in a HyperFile .

// This example stores a BaseContainer in a HyperFile on disc. AutoAlloc<HyperFile> hf; if (hf == nullptr ) return maxon::OutOfMemoryError( MAXON_SOURCE_LOCATION );

// open HyperFile to write if (hf-> Open (123, filename, FILEOPEN::WRITE , FILEDIALOG::ANY )) { hf-> WriteContainer (bc); hf-> 关闭 (); } else { return maxon::IoError( MAXON_SOURCE_LOCATION , MaxonConvert (filename, MAXONCONVERTMODE::NONE ), "Could not open file." _s); }

// This example reads a BaseContainer from a HyperFile on disc. AutoAlloc<HyperFile> hf; if (hf == nullptr ) return maxon::OutOfMemoryError( MAXON_SOURCE_LOCATION );

// open HyperFile to read if (hf-> Open (123, filename, FILEOPEN::READ , FILEDIALOG::ANY )) { BaseContainer bc; hf-> ReadContainer (&bc, true );

另请参阅 HyperFile Manual on BaseContainer .

延伸阅读

BaseContainer::GetData
const GeData & GetData(Int32 id) const
定义: c4d_basecontainer.h:262
BaseList2D::GetData
BaseContainer GetData()
定义: c4d_baselist.h:2266
DateTimeData::SetDateTime
void SetDateTime(const DateTime &d, Bool bSetDate=true, Bool bSetTime=true)
IMAGERESULT::OK
@ OK
Image loaded/created.
BaseContainer::CopyTo
Bool CopyTo(BaseContainer *dst, COPYFLAGS flags, AliasTrans *trans) const
定义: c4d_basecontainer.h:110
BaseContainer::GetObjectLink
BaseObject * GetObjectLink(Int32 id, const BaseDocument *doc) const
BaseContainer::SetMemory
void SetMemory(Int32 id, void *mem, Int count)
定义: c4d_basecontainer.h:548
BaseList2D
定义: c4d_baselist.h:2144
GetActiveDocument
BaseDocument * GetActiveDocument(void)
maxon::BaseArray::Connect
MAXON_ATTRIBUTE_FORCE_INLINE void Connect(const Block< T > &block, Int capacity=0)
定义: basearray.h:1417
BaseContainer::SetData
GeData * SetData(Int32 id, const GeData &n)
定义: c4d_basecontainer.h:255
RDATA_YRES
@ RDATA_YRES
定义: drendersettings.h:153
DeleteObj
#define DeleteObj(obj)
定义: newobj.h:159
Int
maxon::Int Int
定义: ge_sys_math.h:62
GeData::SetCustomDataType
void SetCustomDataType(Int32 datatype, const CustomDataType &v)
定义: c4d_gedata.h:664
BaseContainer::GetMaterialLink
BaseMaterial * GetMaterialLink(Int32 id, const BaseDocument *doc) const
RENDERRESULT
RENDERRESULT
定义: ge_prepass.h:409
String::UIntToString
static String UIntToString(UInt32 v)
定义: c4d_string.h:511
BaseContainer::InsDataAfter
GeData * InsDataAfter(Int32 id, const GeData &n, GeData *last)
定义: c4d_basecontainer.h:247
BaseContainer::SetVoid
void SetVoid(Int32 id, void *v)
定义: c4d_basecontainer.h:540
BaseObject
定义: c4d_baseobject.h:224
BaseContainer::GetIndexData
GeData * GetIndexData(Int32 index) const
定义: c4d_basecontainer.h:229
DescID
定义: lib_description.h:327
GeData::GetType
Int32 GetType(void) const
定义: c4d_gedata.h:407
BFM_INTERACTEND
@ BFM_INTERACTEND
Sent when user interaction ends.
定义: gui.h:917
FILEDIALOG::ANY
@ ANY
Show an error dialog for any error.
BaseContainer::SetString
void SetString(Int32 id, const maxon::String &s)
定义: c4d_basecontainer.h:569
DateTime::minute
Int32 minute
Minute.
定义: customgui_datetime.h:67
UInt32
maxon::UInt32 UInt32
定义: ge_sys_math.h:59
RenderDocument
RENDERRESULT RenderDocument(BaseDocument *doc, const BaseContainer &rdata, ProgressHook *prog, void *private_data, BaseBitmap *bmp, RENDERFLAGS renderflags, BaseThread *th, WriteProgressHook *wprog=nullptr, void *data=nullptr)
BaseDocument::GetActiveMaterial
BaseMaterial * GetActiveMaterial(void)
RENDERFLAGS
RENDERFLAGS
定义: ge_prepass.h:4423
GetDateTimeNow
void GetDateTimeNow(DateTime &t)
BaseContainer::GetLink
BaseList2D * GetLink(Int32 id, const BaseDocument *doc, Int32 instanceof=0) const
定义: c4d_basecontainer.h:443
DA_NIL
@ DA_NIL
No value.
定义: c4d_gedata.h:38
MAXONCONVERTMODE::NONE
@ NONE
No check if file exists under case-sensitive drives.
MAXON_SOURCE_LOCATION
#define MAXON_SOURCE_LOCATION
定义: memoryallocationbase.h:66
BaseDocument::GetActiveTag
BaseTag * GetActiveTag(void)
DateTimeData
定义: customgui_datetime.h:156
maxon::BaseArray
定义: basearray.h:366
maxon::Block::Set
void Set(T *ptr, Int size, Int stride=(STRIDED &&GENERIC) ? -1 :SIZEOF(StrideType))
定义: block.h:528
BaseContainer::RemoveData
Bool RemoveData(Int32 id)
定义: c4d_basecontainer.h:160
BaseContainer::GetMemoryAndRelease
void * GetMemoryAndRelease(Int32 id, Int &count, void *preset=nullptr)
定义: c4d_basecontainer.h:354
BaseBitmap::Init
static IMAGERESULT Init(BaseBitmap *&res, const Filename &name, Int32 frame=-1, Bool *ismovie=nullptr, BitmapLoaderPlugin **loaderplugin=nullptr, const maxon::Delegate< void(Float progress)> &progressCallback=nullptr)
DA_STRING
@ DA_STRING
String.
定义: c4d_gedata.h:47
BaseContainer::GetId
Int32 GetId() const
定义: c4d_basecontainer.h:131
HyperFile::Open
Bool Open(Int32 ident, const Filename &filename, FILEOPEN mode, FILEDIALOG error_dialog)
MaxonConvert
maxon::Url MaxonConvert(const Filename &fn, MAXONCONVERTMODE convertMode)
BaseContainer::GetCustomDataType
const CustomDataType * GetCustomDataType(Int32 id, Int32 datatype) const
String
定义: c4d_string.h:38
String::IntToString
static String IntToString(Int32 v)
定义: c4d_string.h:495
RENDERRESULT::OK
@ OK
Function was successful.
FILEOPEN::WRITE
@ WRITE
BaseContainer::GetParameter
Bool GetParameter(const DescID &id, GeData &t_data) const
定义: c4d_basecontainer.h:628
DateTime
Represents a date and time.
定义: customgui_datetime.h:38
maxon::Vec3< Float, 1 >
BaseContainer::GetContainer
BaseContainer GetContainer(Int32 id) const
定义: c4d_basecontainer.h:418
SIZEOF
#define SIZEOF(x)
Calculates the size of a datatype or element.
定义: apibasemath.h:205
maxon::Int
Int64 Int
signed 32/64 bit int, size depends on the platform
定义: apibase.h:184
RDATA_XRES
@ RDATA_XRES
定义: drendersettings.h:152
HyperFile::ReadContainer
Bool ReadContainer(BaseContainer *v, Bool flush)
NOTOK
#define NOTOK
定义: ge_sys_math.h:265
BaseContainer::GetClone
BaseContainer * GetClone(COPYFLAGS flags, AliasTrans *trans) const
定义: c4d_basecontainer.h:101
GeData
定义: c4d_gedata.h:82
Int32
maxon::Int32 Int32
定义: ge_sys_math.h:58
ApplicationOutput
#define ApplicationOutput(formatString,...)
定义: debugdiagnostics.h:207
DateTime::day
Int32 day
Day in month.
定义: customgui_datetime.h:64
ShowBitmap
Bool ShowBitmap(const Filename &fn)
BaseContainer::GetDataPointers
void GetDataPointers(const Int32 *ids, Int32 cnt, const GeData **data) const
定义: c4d_basecontainer.h:217
BaseContainer::GetVoid
void * GetVoid(Int32 id, void *preset=nullptr) const
定义: c4d_basecontainer.h:343
BaseContainer::SetParameter
Bool SetParameter(const DescID &id, const GeData &t_data)
定义: c4d_basecontainer.h:635
BaseContainer::MergeContainer
void MergeContainer(const BaseContainer &src)
BaseContainer::SetLink
void SetLink(Int32 id, C4DAtomGoal *link)
定义: c4d_basecontainer.h:604
CustomDataType
Base class for custom data types.
定义: c4d_customdatatype.h:50
GeData::GetString
const String & GetString(void) const
定义: c4d_gedata.h:463
BaseDocument::EndUndo
Bool EndUndo(void)
HyperFile::WriteContainer
Bool WriteContainer(const BaseContainer &v)
DateTimeData::GetDateTime
DateTime GetDateTime() const
FILEOPEN::READ
@ READ
Open the file for reading.
BaseDocument::GetActiveRenderData
RenderData * GetActiveRenderData(void)
HyperFile::Close
Bool Close()
AutoAlloc
定义: ge_autoptr.h:36
BaseContainer::GetString
String GetString(Int32 id, const maxon::String &preset=maxon::String()) const
定义: c4d_basecontainer.h:387
DATETIME_DATA
#define DATETIME_DATA
DateTime custom data ID.
定义: customgui_datetime.h:23
BaseContainer::SetContainer
void SetContainer(Int32 id, const BaseContainer &s)
定义: c4d_basecontainer.h:597
DateTime::year
Int32 year
Year.
定义: customgui_datetime.h:62
RenderData
定义: c4d_basedocument.h:136
BFM_INTERACTSTART
@ BFM_INTERACTSTART
定义: gui.h:898
BaseDocument::GetActiveObject
BaseObject * GetActiveObject(void)
BrowseContainer
定义: c4d_gedata.h:673
BaseList2D::GetName
String GetName() const
定义: c4d_baselist.h:2318
IMAGERESULT
IMAGERESULT
定义: ge_prepass.h:3659
DateTime::hour
Int32 hour
Hour.
定义: customgui_datetime.h:66
DateTime::month
Int32 month
Month.
定义: customgui_datetime.h:63
BaseMaterial
定义: c4d_basematerial.h:27
BaseContainer::GetIndexId
Int32 GetIndexId(Int32 index) const
定义: c4d_basecontainer.h:197
BaseContainer::GetInt32
Int32 GetInt32(Int32 id, Int32 preset=0) const
定义: c4d_basecontainer.h:303
DateTime::second
Int32 second
Second.
定义: customgui_datetime.h:68
BaseContainer::GetDirty
UInt32 GetDirty() const
定义: c4d_basecontainer.h:148
BaseDocument
定义: c4d_basedocument.h:490
COPYFLAGS::NONE
@ NONE
None.
BaseContainer
定义: c4d_basecontainer.h:46
maxon::Block
定义: apibase.h:300
RENDERFLAGS::NODOCUMENTCLONE
@ NODOCUMENTCLONE
Set to avoid an automatic clone of the scene sent to RenderDocument().

Copyright  © 2014-2025 乐数软件    

工业和信息化部: 粤ICP备14079481号-1