欢迎光临112期刊网!
网站首页 > 论文范文 > 计算机论文 > 计算机应用 > C++类的动态组件化技术

C++类的动态组件化技术

日期:2023-01-24 阅读量:0 所属栏目:计算机应用


  论文关键词:com组件 接口 生命周期 c++类 atl组件类 c++基类 atl模板基类 继承

  论文摘要:在组件化编程的时代,如何复用历史累积的大量没有组件特性的c++类?本文从工程的角度对这一问题进行探讨,利用现有组件技术,提出了一套将c++类平滑过渡到com组件的完整解决方案。

  1. 问题的提出

  自从microsoft公布了com(component object model,组件对象模型,简称com)技术以后,windows平台上的开发模式发生了巨大的变化,以com为基础的一系列组件技术将windows编程带入了组件化时代,传统的面向对象的软件开发方法已经逐渐被面向组件的方法所取代。

  com标准建立在二进制可执行代码级的基础上,不论何种工具、语言开发的组件,只要符合com规范,就可复用于vc、vb、delphi、bc等各种开发环境中。com的语言无关性将软件复用的层次从源代码级推进到了二进制级,复用更方便,也更安全。

  然而,com技术带来全新的软件设计和开发模式的同时,也带来了新的问题。

  许多软件公司在开发自己的软件产品过程中,都累积了大量c++类,这些代码设计精良,功能完备,以面向对象的标准来检验无可挑剔。然而,这些代码不支持com,将无法在com时代继续被复用。如果它们在软件组件化的趋势中被淘汰,那对软件公司和开发人员来说都是极大的损失。

  com专家don box曾说过,“com is a super c++”。这给了我们一个启示,是否可以实现一种技术,能够动态的为普通c++类加上一层com的封装呢?这样,既可以保持这些代码自身的完整和特性,使它们能继续应用于原来的系统,也可以在需要作为组件使用的时候,把它们动态转变成组件,复用于新系统。

  一个自然而然的想法是,为每一个c++类开发一个只暴露一个接口的com组件,将原c++类的每个public方法都对应于该接口的一个方法,接口方法的实现可以简单的调用相对应的c++类方法即可。这样,程序逻辑由原有的c++类控制,但com层的封装则由组件提供。基本思路如下图所示:

  

  本文就这一技术展开讨论,最终提供一套由普通c++类平滑过渡到com组件的完整解决方案。我们选用atl(active template library,活动模板库,简称atl)作为com组件的开发工具,开发环境为visual studio 6.0。如没有特殊说明,下文中的“c++类”指没有组件特性c++类,“c++对象”指c++类的实例;“atl组件类”指用于包装的atl类,“atl对象”指atl组件类的实例。

  2. 用atl包装c++类
  按上述思路将c++对象动态组件化后,所得的组件实际上由两部分组成:atl组件对象和绑定的c++对象。两者的生命周期互相牵制,但要保持一致。生命周期的管理是c++类动态组件化的首要难点。

  c++类分为两种,一种是简单的c++类,一种是集合型的c++类。集合型的c++对象管理一组c++对象,负责其创建和删除,维护它们的生命周期。下面,分别就简单c++类和集合型c++类的组件化技术进行说明,展示解决方案的核心技术。

  2.1. 简单c++类的组件化
  为使atl组件类可以自由调用c++类的方法,需要:

  l 为atl组件类安插一个指针成员变量,指向c++类

  l 提供atl对象和c++对象的绑定机制

  我们可以在atl组件类初始化时创建一个c++类,用成员变量m_pcppobj记录,在析构时删除,从而实现atl组件类和c++类的天然绑定。但出于灵活性考虑,使得atl组件对象可以绑定任意c++类的对象,我们为atl组件类添加一个绑定函数link2cppobj(cimplement* pobj)。

  在atl组件类的构造函数内,创建一个c++对象,用m_pcppobj记录。

  如果调用了link2cppobj,则将m_pcppobj指向的对象删除,改用传入的c++对象。

  在atl组件类的的析构函数内,删除其绑定的c++对象。由构造函数和link2cppobj函数的定义可知,m_pcppobj指针总是有意义的。

  简单c++类组件化的思想如下图所示:

  

  2.2. 集合型c++类的组件化
  集合型c++类的情况有所不同。

  集合型c++类以数组(array)、列表(list)、映射表(map)的形式管理其它c++对象。集合对象和它管理的元素对象都被包装成组件后,集合型atl对象可能调用一个“destroy”方法,期望删除某一个元素atl对象;这一操作的实质却是,集合型c++对象的“destroy”方法被调用,将元素c++对象删除了,而元素atl对象却不知道。这一操作的结果导致了元素的atl对象存在,而其绑定的c++对象却被删除的情况,两者的生命周期出现了不一致。

  为了解决这个问题,我们需要在c++对象被删除时,能将atl对象同时删除;而在atl对象的引用计数为0需要删除自身时,也能把c++对象删除。可行的解决方案是:

  l 在c++类中保存一个接口指针,指向绑定在一起的atl对象;为该接口指针赋值的最佳地点显然是提供绑定机制的link2cppobj函数内部,为此,还需要给link2cppobj添加一个iunknown*参数

  l 在c++类的析构函数中,判断该接口指针是否为空,如果不为空,则release对接口的引用,引发atl对象自身的析构

  现在,技术方案如下图所示:

  

  2.3. 内部创建的组件和外部创建的组件
  集合型c++类组件化后仍然是集合型atl组件,它可以创建、删除自己管理的组件。这样,组件的创建就可能有两种情况:

  l 由客户直接创建

  l 由客户调用集合型组件的接口方法间接创建

  创建方式的不同导致了组件生命周期管理的复杂性。一般说来,组件的创建者负责维护组件的生命周期。上述两种情况下,分别由客户和集合型组件维护被创建组件的生命周期。然而,另有一种情况是,客户创建了一个组件,然后送交一个集合型组件管理,现在维护组件生命周期的责任就由客户转交给了集合型组件。

  我们的解决方案必须提供这样的健壮性和灵活性,以维护各种情况下组件的生命周期。我们为atl组件类添加一个boo成员m_binnermanage,作为组件的维护标识。内部维护意味着组件的生命周期由其它组件(集合型组件)维护;外部维护则是由客户维护。

  

  缺省情况下,组件是外部创建并维护的,在组件的构造函数内设置外部维护标识。集合型组件创建元素时,需要为元素分别创建一个c++对象和一个atl对象,然后调用atl对象的link2cppobj函数将两者绑定在一起,在link2cppobj函数内修改维护标识。对于第三种情况,可以在外部创建组件由客户转交给集合型组件时,在集合型组件相应方法内重新设置维护标识。

  2.4. c++基类
  为了对现有c++类的改动最小,我们设计一个基类封装需要为c++类添加的功能。所有需要动态组件化的c++类都必须从这个基类派生,以保证动态组件化中c++对象与atl对象生命周期的一致。如下图示:

  
  实现代码如下所示:

class ccpp2atlobjbase

{

ccpp2atlobjbase ();

public:

// iunknown指针,反指向封装该cpp类的接口

iunknown* m_passociatlunk;

protected:

virtual ~ ccpp2atlobjbase ();

};

ccpp2atlobjbase::ccpp2atlobjbase()

{

// 将iunknown指针初始化为0

m_passociatlunk = null;

}

ccpp2atlobjbase::~ccpp2atlobjbase()

{

// cpp类的对象析构时,release对接口的引用

if (m_passociatlunk)

m_passociatlunk->release();

}

然后,修改现有各个c++类,使之从ccpp2atlobjbase派生,如下面代码片断所示:

class cimplement : public ccpp2atlobjbase

{

……

};

  必须指出的是,在ccpp2atlobjbase基类中,我们设置的m_passociatlunk变量存在和现有c++类成员命名冲突的问题。但是,考虑到原c++类并没有组件特性,也应该不会有“iunknown”型指针,因此,只要各个类的变量命名都按照规范的命名法,出现这种名字冲突的可能性是极小的。

  2.5. atl模板基类
  通过以上分析,我们发现,所有的atl组件类都需要实现一些相同的功能:

  l 保留一个指向其绑定c++对象的指针

  l 提供一个link2cppobj函数

  l 在构造函数中创建一个绑定c++类的对象

  为了减化编码,我们定义一个带参数的模板基类,实现上述公共功能,模板参数就是绑定的c++类。然后,所有的atl组件类都从模板基类中派生。现在的技术方案如下图所示:

 

 实现代码如下所示:

template

class ccpp2atltemplatebase :

{

protected:

// c++类指针

t* m_pcppobj;

// 标识继承该模板的atl对象是否由内部维护

bool m_binnermanage;

public:

/**********************************************************

模板的构造函数,实现如下功能:

1、new一个c++实现类对象

2、缺省情况下,atl对象由外部维护,将内部维护标识设为false

3、将c++类中对atl接口的反指指针设置为空

**********************************************************/

catlcpp2atltemplatebase()

{

m_pcppobj = new t;

m_binnermanage = false;

m_pcppobj->m_passociatlunk = null;

}


/**********************************************************

析构atl对象时,如果该atl对象是由外部创建的,

则显式的删除c++对象

如果atl对象由内部维护,那么什么事都不用做

**********************************************************/

virtual ~catlcpp2atltemplatebase()

{

if (!m_binnermanage) {

if (m_pcppobj)

delete m_pcppobj;

}

}


/**********************************************************

link2cppobj函数,负责绑定c++对象和atl接口

1、删除构造函数中new的c++对象,而使用外部传入的c++对象

2、将atl对象的内部维护标识设为true

3、设置c++基类中的接口指针成员

4、因为atl接口传送给外部使用,需要增加引用计数

**********************************************************/

virtual void link2cppobj(t* pobj, iunknown* punk)

{

assert(pobj != null);

assert(punk != null);


if (m_pcppobj)

delete m_pcppobj;

m_pcppobj = pobj;

m_binnermanage = true;

m_pcppobj->m_passociatlunk = punk;

m_pcppobj->m_passociatlunk->addref();

}

};

然后,每个atl类都从该模板类派生,如下代码片断所示:

class atl_no_vtable catlxx :

……,

// 添加atl模板基类

public ccpp2atltemplatebase

{

……

}

  3. c++参数类型的自动化包装
  在本文的技术方案中,c++类的public方法与atl组件接口中的方法一一对应;相应的,c++类中方法的参数类型也要转换为com规范所允许的数据类型。

  在基于com的自动化(automation)技术中,microsoft提供了一套自动化兼容的数据类型variant,定义如下:

  typedef struct farstruct tagvariant variant;

  typedef struct farstruct tagvariant variantarg;


  typedef struct tagvariant {

vartype vt;

unsigned short wreserved1;

unsigned short wreserved2;

unsigned short wreserved3;

union {

byte bval; // vt_ui1.

short ival; // vt_i2.

long lval; // vt_i4.

float fltval; // vt_r4.

double dblval; // vt_r8.

variant_bool boolval; // vt_bool.

scode scode; // vt_error.

cy cyval; // vt_cy.

date date; // vt_date.

bstr bstrval; // vt_bstr.

decimal far* pdecval; // vt_byref|vt_decimal.

iunknown far* punkval; // vt_unknown.

idispatch far* pdispval; // vt_dispatch.

safearray far* parray; // vt_array|*.

byte far* pbval; // vt_byref|vt_ui1.

short far* pival; // vt_byref|vt_i2.

long far* plval; // vt_byref|vt_i4.

float far* pfltval; // vt_byref|vt_r4.

double far* pdblval; // vt_byref|vt_r8.

variant_bool far* pboolval; // vt_byref|vt_bool.

scode far* pscode; // vt_byref|vt_error.

cy far* pcyval; // vt_byref|vt_cy.

date far* pdate; // vt_byref|vt_date.

bstr far* pbstrval; // vt_byref|vt_bstr.

iunknown far* far* ppunkval; // vt_byref|vt_unknown.

idispatch far* far* ppdispval; // vt_byref|vt_dispatch.

safearray far* far* pparray // vt_array|*.

variant far* pvarval; // vt_byref|vt_variant.

void far* byref; // generic byref.

char cval; // vt_i1.

unsigned short uival; // vt_ui2.

unsigned long ulval; // vt_ui4.

int intval; // vt_int.

unsigned int uintval; // vt_uint.

char far * pcval; // vt_byref|vt_i1.

unsigned short far * puival; // vt_byref|vt_ui2.

unsigned long far * pulval; // vt_byref|vt_ui4.

int far * pintval; // vt_byref|vt_int.

unsigned int far * puintval; // vt_byref|vt_uint.

};

};

  我们看到,所有简单数据类型都可以在variant中找到对应的定义,但是,在多数的基于c++的系统设计中,方法参数不会仅仅出现简单数据类型,类对象、对象引用、对象指针被频繁的作为参数来传递。

  以类对象、对象引用或对象指针形式存在的参数,我们称为复杂类型参数。在技术方案中,所有复杂类型参数在atl接口方法中一律对应接口指针,我们需要提供c++对象(或引用、指针)和atl接口指针之间的动态转换功能。下文就复杂类型作为传入、传出参数分别进行讨论。

  3.1. 复杂类型的传入参数
  atl接口方法获取一个接口指针参数后,如何将此接口指针转变为c++对象指针?对于atl对象,可以直接取得m_pcppobj变量,而接口指针却不能。所以,需要提供一种途径,从atl接口指针获取atl组件的m_pcppobj变量值。

  我们的设计是,为每个atl组件提供一个基接口icppobjseeker,实现对绑定c++对象指针(即m_pcppobj)的查询方法handlecppobj。任意atl接口都从该基接口派生,都可以调用handlecppobj方法。

  在前文就生命周期管理进行讨论时,曾提到这样一种情况:客户创建了一个组件,然后送交集合型组件管理。在集合型组件获取外部创建的组件的同时,需要:

  l 取得后者的c++对象指针。集合型组件对元素组件管理的实质是通过集合型c++对象对元素的c++对象进行管理,而集合型atl对象和元素atl对象之间并没有直接联系

  l 修改新加入元素组件的维护标识

  因此,我们为icppobjseeker接口添加postcppobj方法,用于实现以上功能。

  icppobjseeker接口idl定义如下所示,因为icppobjseeker接口和handlecppobj、postcppobj方法实际上都应用于内部,所以使用“hidden”属性对外隐藏:

  [

object,

uuid(1e9f7f79-936d-4680-9f8e-34a7dccff818),

dual,

hidden,

helpstring("icppobjseeker interface"),

pointer_default(unique)

  ]

interface icppobjseeker : idispatch

{

[id(1), helpstring("取得c++对象的指针"), hidden]

hresult handlecppobj([out, retval] long* pcppobj);

[id(2), helpstring("取得c++对象的指针,客户程序不再负责对c++对象生命周期的维护"), hidden]

hresult postcppobj([out, retval] long* pcppobj);

};

icppobjseeker接口的方法可以放在ccpp2atltemplatebase模板基类中统一实现:

template

class ccpp2atltemplatebase :

{

……

/**********************************************************

handlecppobj函数,由icppobjseeker接口定义,

负责取得atl接口中的c++对象指针

**********************************************************/

stdmethodimp handlecppobj(long *pcppobj)

{

afx_manage_state(afxgetstaticmodulestate())

*pcppobj = (long)m_pcppobj;

return s_ok;

}


/**********************************************************

postcppobj函数,由icppobjseeker接口定义,

负责取得atl接口中的c++对象指针,

同时标记对象为内部维护,客户不再负责对象的生命周期管理

**********************************************************/

stdmethodimp postcppobj(long *pcppobj)

{

afx_manage_state(afxgetstaticmodulestate())

*pcppobj = (long)m_pcppobj;

if (m_binnermanage == false) {

m_binnermanage = true;

m_pcppobj->m_passociatlunk = this;

m_pcppobj->m_passociatlunk->addref();

}

return s_ok;

}

};

  现在,所有的接口都不再直接从idispatch派生,而改从icppobjseeker派生,因此,idispatch的实现也应该在实现icppobjseeker接口的同一级或下级中提供。为了包容idispatch,我们将atl模板基类稍作改动:

template

class atl_no_vtable ccpp2atltemplatebase :

public idispatchimpl

{

……

};
  在从该模板类派生atl类时,将atl wizard自动生成的对idispatch接口的实现注释,而使用新定义的ccpp2atltemplatebase,如下代码片断所示:

class atl_no_vtable catlxx :

……,

// 将atl wizard生成的对idispatch接口的支持注释

// public idispatchimpl,

// 添加atl模板基类

public ccpp2atltemplatebase

{

……

}


  3.2. 复杂类型的传出参数
  从c++指针转换为接口指针基本上不存在困难,为方便使用,我们提供一个基于本技术方案的宏定义,如下代码所示:

/**********************************************************

从c++指针获取对应atl接口的宏

传入:c++指针,对应的atl类名,接口iid

传出:接口指针,执行状态hresult

**********************************************************/

#define cppobj_to_com_interface(pcppobj, catlclass, iid_idefine, ppinterface, hresult ) \

{ \

assert(pcppobj != null); \

if (pcppobj->m_passociatlunk != null) \

{ \

hresult = pcppobj->m_passociatlunk-> \

queryinterface(iid_idefine, (void **)ppinterface); \

atlassert(succeeded(hresult)); \

} \

else \

{ \

ccomobject* pcomobj; \

hresult = ccomobject::createinstance(&pcomobj); \

atlassert(succeeded(hresult)); \

hresult = pcomobj-> \

queryinterface(iid_idefine, (void **)ppinterface); \

atlassert(succeeded(hresult)); \

if (hresult == s_ok) \

pcomobj->link2cppobj(pcppobj, *ppinterface); \

}\

}


  4. 接口的继承与多态
  c++类的继承应用十分广泛,动态化后的组件应该保留原c++类之间的继承关系。在我们的技术方案中,c++类和接口一一对应,c++类的继承关系也应该体现在各个接口上,如下图所示:

  
  4.1. 支持继承的系列atl模板基类
  实现接口继承的实质是为派生atl类添加基接口,而为一个atl类添加接口的实质则是:

  l 修改idl文件,体现接口的继承关系

  l 在atl类中提供接口实现

  修改idl文件很简单,只需要更改派生接口的基接口即可。在atl类中添加基接口的实现倒颇费思量,我们的做法是:

  l 扩展atl模板基类的意义,每一个atl组件类都对应一个模板基类,都从该模板基类派生

  l 派生类的模板基类,从基类的模板基类中派生;ccpp2atltemplatebase是模板派生树的根节点,所有的模板都派生自ccpp2atltemplatebase

  l 所有的接口方法,都在对应的模板基类中实现

  atl派生类继承自它对应的模板基类,这个模板基类又继承自atl基类对应的模板基类,而在atl基类的模板基类中提供了基接口的实现。所以,atl派生类最终继承了基接口的实现。c++类、atl类、各模板基类的继承关系如下图所示:

  

  假定ibaseitf是基接口,iinherititf是派生接口。atl基类对应的模板基类定义如下:

/****************************************************************************

模板类catlbaseitf,提供了ibaseitf的实现,

用于将ibaseitf接口作为基接口共供其它接口继承

****************************************************************************/

template

class atl_no_vtable catlbaseitf : public ccpp2atltemplatebase

{

public:

// 基接口方法“basefunc”,在此模板类内实现

stdmethod(basefunc)()

{

m_pcppobj->basefunc();

return s_ok;

}

};

atl派生类对应的模板基类定义如下:

/****************************************************************************

模板类catlinherititf,继承了基接口ibaseitf方法的实现,

同时提供了iinherititf的实现,可以将iinherititf接口作为基接口共供其它接口继承

****************************************************************************/

template

class atl_no_vtable catlinherititf : public catlbaseitf

{

public:

// 派生接口方法“inheritfunc”,在此模板类内实现

stdmethod(inheritfunc)()

{

m_pcppobj->inheritfunc();

return s_ok;

}

};

更改iinherititf接口的idl定义:

[

object,

uuid(8f3902df-da55-4802-ab8a-958aff45b2f4),

dual,

helpstring("ibaseitf interface"),

pointer_default(unique)

]

// 基接口从icppobjseeker派生

interface ibaseitf : icppobjseeker

{

[id(1), helpstring("ibaseitf method")] hresult basefunc();

};

[

object,

uuid(afebd472-4bec-45ce-a5a2-e37537c4744a),

dual,

helpstring("iinherititf interface"),

pointer_default(unique)

]

// iinherititf接口从ibaseitf接口派生

interface iinherititf : ibaseitf

{

[id(11), helpstring("iinherititf method")] hresult inheritfunc();

};

最后,更改atl派生类的模板基类:

class atl_no_vtable catlinherit :

……,

public catlinherititf

{

……

};

  现在,通过iinherititf,我们可以使用ibaseitf的所有方法,实现了接口的继承。

  4.2. 接口的多态性
  在实现接口的继承后,要展现接口的多态性就很容易了,只需在atl派生类声明的接口映射表中添加基接口表项即可:

class atl_no_vtable catlinherit :

……,

public catlinherititf

{

……

begin_com_map(cinherititf)

com_interface_entry(iinherititf)

com_interface_entry(ibaseitf)

……

end_com_map()

……

};

  就象c++中基类指针所展现的多态性一样,一个“ibaseitf *”型指针可以完全操纵iinherititf接口,而不需要知道真正的接口类型。

  5. 总结
  至此,我们的技术方案全部介绍完毕。c++基类ccpp2atlobjbase、atl模板基类ccpp2atltempbase和基接口icppobjseeker是方案中的关键技术。ccpp2atlobjbase配合ccpp2atltempbase,完善了组件对象生命周期的管理机制;通过基接口icppobjseeker,我们可以从任意接口反向查询c++对象;ccpp2atltempbase提供了c++对象和atl组件的自由绑定功能,封装了idispatch接口的实现,而进一步定义的atl模板基类继承体系则极大的方便了接口的自由继承。

  在本文快结束的时候,我们不得不特别提到microsoft的“.net framework”。“.net”开发框架的推出,的确解决了com技术的许多困惑,也包括本技术方案所要解决的一些技术问题。然而“.net framework”是一个“改朝换代”的变化,要想一步将原来基于c++的系统(尤其是大型系统)完全移植到“.net”平台上是不可想象的,其工作量不亚于重新开发,所以microsoft特别推荐从com技术到“.net”平台的平滑移植。由此看来,本文提出的动态组件化的技术更显得可贵,它从工程化的角度,着眼于实际应用,解决了从面向对象的c++到基于组件的com技术的许多问题,既充分保护了原有系统的积累,又为这些系统搭上日益发展的“.net”快车提供了可能。


  参考文献
  《com原理与应用》,潘爱民 著,清华大学出版社

  《com本质论(essential com)》,don box 著,潘爱民 译,中国电力出版社

  《深入解析atl(atl internals)》,brent rector、chris sells 著,潘爱民、新语 译,中国电力出版社

  《设计模式-可复用面向对象软件的基础(design patterns-elements of reusable object-oriented software)》,erich gamma、richard helm、ralph johnson、john vlissides 著,李英军、马晓星、蔡敏、刘建中 等译

本文链接:http://www.qk112.com/lwfw/jsjlw/jisuanjiyingyong/244996.html

论文中心更多

发表指导
期刊知识
职称指导
论文百科
写作指导
论文指导
论文格式 论文题目 论文开题 参考文献 论文致谢 论文前言
教育论文
美术教育 小学教育 学前教育 高等教育 职业教育 体育教育 英语教育 数学教育 初等教育 音乐教育 幼儿园教育 中教教育 教育理论 教育管理 中等教育 教育教学 成人教育 艺术教育 影视教育 特殊教育 心理学教育 师范教育 语文教育 研究生论文 化学教育 图书馆论文 文教资料 其他教育
医学论文
医学护理 医学检验 药学论文 畜牧兽医 中医学 临床医学 外科学 内科学 生物制药 基础医学 预防卫生 肿瘤论文 儿科学论文 妇产科 遗传学 其他医学
经济论文
国际贸易 市场营销 财政金融 农业经济 工业经济 财务审计 产业经济 交通运输 房地产经济 微观经济学 政治经济学 宏观经济学 西方经济学 其他经济 发展战略论文 国际经济 行业经济 证券投资论文 保险经济论文
法学论文
民法 国际法 刑法 行政法 经济法 宪法 司法制度 法学理论 其他法学
计算机论文
计算机网络 软件技术 计算机应用 信息安全 信息管理 智能科技 应用电子技术 通讯论文
会计论文
预算会计 财务会计 成本会计 会计电算化 管理会计 国际会计 会计理论 会计控制 审计会计
文学论文
中国哲学 艺术理论 心理学 伦理学 新闻 美学 逻辑学 音乐舞蹈 喜剧表演 广告学 电视电影 哲学理论 世界哲学 文史论文 美术论文
管理论文
行政管理论文 工商管理论文 市场营销论文 企业管理论文 成本管理论文 人力资源论文 项目管理论文 旅游管理论文 电子商务管理论文 公共管理论文 质量管理论文 物流管理论文 经济管理论文 财务管理论文 管理学论文 秘书文秘 档案管理
社科论文
三农问题 环境保护 伦理道德 城镇建设 人口生育 资本主义 科技论文 社会论文 工程论文 环境科学