vc學習(2005-01)

l          定义自己的api

 

 

#ifdef TRANSCLT_EXPORTS

#define CLT_API extern "C" __declspec(dllexport)

#else

#define CLT_API extern "C" __declspec(dllimport)

#endif

 

 

typedef void (*RECVCALLBACK)(PCMD_HEAD pCmd, UINT uHandle, PIP_ADDR pIP, DWORD dwParam = 0);

typedef void (*CONNCALLBACK)(UINT uHandle, PIP_ADDR pIP, DWORD dwParam = 0);

 

 

/* Interface of TransClt.dll */

class ITransClt

{

public:

        virtual BOOL Initialize(DWORD dwSvrIP,

                                                        WORD wSvrPort,

                                                        DWORD dwLocalIP = 0,

                                                        WORD wLocalPort = 0) = 0;

        virtual void Release() = 0;

 

        virtual void RegOnReceive(RECVCALLBACK OnReceive, DWORD dwParam = 0) = 0;

        virtual void RegOnConnect(CONNCALLBACK OnConnect, DWORD dwParam = 0) = 0;

        virtual void RegOnDisconnect(CONNCALLBACK OnDisconnect, DWORD dwParam = 0) = 0;

 

        virtual BOOL SendToServer(LPVOID pBuffer, UINT uSize) = 0;

};

 

/* API interface of TransClt.dll */

CLT_API LPVOID  TransCltCreateObject();

CLT_API DWORD TransCltGetHostIP(int nIPIndex);

 

 

#endif       // _YZ_TCPTRANS_CLIENT_MODULE_INTERFACE_INCLUDE_

 

l          调用有参数的Dll

 

typedef void (WINAPI * TESTDLL)(CString);

HINSTANCE hmod;

hmod = ::LoadLibrary ("ECPMail.dll");

AfxSetResourceHandle(hmod);

if(hmod==NULL)

{

        AfxMessageBox("Fail");

}

TESTDLL lpproc;

lpproc = (TESTDLL)GetProcAddress (hmod,"Show");

if(lpproc!=(TESTDLL)NULL)

{      CString str="sss";

   (*lpproc)(str);

}

FreeLibrary(hmod);

 

}

 

l          Dll的应用

m_pTransClt=(ITransClt*)TransCltCreateObject();

 

if( NULL == m_pTransClt )

                return FALSE;

if( FALSE == m_pTransClt->Initialize(inet_addr("192.168.0.137"), 45876) )

        {

                m_pTransClt->Release();

                m_pTransClt = NULL;

                return FALSE;

        }     

return TRUE;

 

l         modeless Dialog

 

 

For a modeless dialog box, you must provide your own public constructor in your dialog class. To create a modeless dialog box, call your public constructor and then call the dialog object’s Create member function to load the dialog resource. You can call Create either during or after the constructor call. If the dialog resource has the property WS_VISIBLE, the dialog box appears immediately. If not, you must call its ShowWindow member function.

 

l          关于Dll的使用

 

        typedef LPVOID (WINAPI * TESTDLL)();

        HINSTANCE hmod;

        hmod = ::LoadLibrary ("ECPMail.dll");

        AfxSetResourceHandle(hmod);//十分重要

        if(hmod==NULL)

        {

                AfxMessageBox("Fail");

        }

        TESTDLL CreatMailObject;

        CreatMailObject = (TESTDLL)GetProcAddress (hmod,"ECPMailCreateObject");

       

        if(CreatMailObject!=(TESTDLL)NULL)

        {     

 

                IECPMail* m_pECPMail=NULL;

                m_pECPMail=(IECPMail*)CreatMailObject();

 

                m_pECPMail->Initialize(m_dwSvrIP ,45877, m_dwBindIP,45876,AfxGetMainWnd());

                       

                /*

                if( FALSE == m_pECPMail->Initialize(m_dwSvrIP ,45877, m_dwBindIP,45876))

                                {

                                        m_pECPMail->Release();

                                        m_pECPMail = NULL;

                                       

                                }     

                }

        else AfxMessageBox("load Function Faild");

        FreeLibrary(hmod);

 

 

 

 

l          设定一个事件的超时响应

        if(WAIT_TIMEOUT == WaitForSingleObject(g_eventQueryDNUsed,TIMEOUT_WAIT_SVR))

        {

                dwError = ERR_WAIT_SVR_TIMEOUT;

        }

        else

        {

                dwError = g_dwRetQueryDNUsed;

                g_dwRetQueryDNUsed = 0;

                g_eventQueryDNUsed.ResetEvent();

        }

l          如何在静态函数中调用本身类的动态函数

 

第一步:

static void OnReceive(PCMD_HEAD pCmd, UINT uHandle, PIP_ADDR pIP, DWORD dwParam);

DWORD dwParam-------指向本身类的指针

 

 

 

void MailManage::OnReceive(PCMD_HEAD pCmd, UINT uHandle, PIP_ADDR pIP, DWORD dwParam)

{

        if( 0 != IsBadWritePtr(pCmd->pBuf, pCmd->nBufSize) )

                return;

        // CWnd* pWnd = AfxGetMainWnd();

        MailManage *pManage = (MailManage *)dwParam;

        switch(pCmd->dwCmd)

                {

                        case CMD_MAIL_UPDATE_RET:

                        SetEvent(pManage->g_hEventUpdateMailList);          

                                break;

                       

                        default:

                                break;

                }

}

 

 

        WriteDebugInfo(DBG_THREAD,"/n DistributeRecvData Begin Thread Count = %d MAX_THREAD = %d /n",pThis->m_dwDistribeThreadCount,pThis->m_dwWorkThread);

        CString strDebug = "";

        strDebug.Format("/n DistributeRecvData() Begin! dwThreadCount = %d ThreadID = 0x%x",pThis->m_dwDistribeThreadCount,GetCurrentThreadId());

        OutputDebugString(strDebug);

 

 

 

 

SetParentWnd(CWnd *pParent)

 

AfxGetMainWnd()

 

数据库编程--dao

 

char sql[ 100 ];

sprintf( sql, "DeviceID = '%s'", m_DeviceID );

rs.FindFirst( ( LPCTSTR )sql );

 

关于DAO数据库编程的几点经验

作者:广东南海昭信科技有限公司 king_koo

 

 

前言

本文是作者在DAO数据库编程中积累的经验,希望对使用DAO进行开发的朋友有所帮助。

 

一、如何在新建时没选数据库支持的程序中加入数据库支持

 

以对话框DAO-Access为例:

1.1用类向导新建类.Name:"mydb", Base Class:"DaoRecordset",选择正确的数据源和表.

:vc6无法直接对access2000进行支持,要用的话先转换为97版才行。

 

1.2mydb头文件加入#include"afxdao.h".在对话框类头文件加入#include"mydb.h".

 

1.3测试:在对话框类加入一按钮,在其响应函数内加入如下代码:

mydb db;

db.Open();

MessageBox(db.m_answerA);

db.Close();

这里假设我的数据库表里有answerA字段.

 

二、如何在数据库没有静态绑定其它控件的程序手工加入静态绑定.

 

仍以上面为例.先把先前的按钮及其消息处理函数删掉.加入一EDIT控件.

 

2.1 声明:在对话框头文件的AFX_DATA内加入:mydb* rec;如下:

//{{AFX_DATA(CAaaDlg)

      enum { IDD = IDD_AAA_DIALOG };

      mydb* rec;

      // NOTE: the ClassWizard will add data members here

//}}AFX_DATA

 

2.2 绑定:在类向导为edit控件添成员变量m_amswerA.(在向导中选择)

 

2.3 初始化:在对话框构造函数内加入

rec=new mydb;

rec->Open();

     

2.4 销毁:响应对话框WM_CLOSE消息,加入消息处理代码:

rec->Close();

delete rec;

     

2.5 测试:编译运行,就可以看到EDIT框里出现了数据库的内容.唯一不足的是不能自动更新显示.需手工UpdateData(0);

 

三、如何确保上述程序拷到别人机里仍可运行.(数据库也拷在同一目录)

 

把刚才mydbGetDefaultDBName()函数内容改为:

char str[255];

GetCurrentDirectory(255,str);

strcat(str,"//my.mdb");

return _T(str);

 

后记

虽然DAO逐渐被ADO所取代,但仍旧有许多网友通过DAO来学习VC下的数据库编程,以上是自己摸索所得的经验,希望对初学者有所帮助,有不妥之处请各位大虾指正!

 

10.8 DAO

 

10.8.1 什么是DAO

 

  DAO(Database Access Object)使用Microsoft Jet数据库引擎来访问数据库。Microsoft Jet为象AccessVisual Basic这样的产品提供了数据引擎。

 

  与ODBC一样,DAO提供了一组API供编程使用。MFC也提供了一组DAO类,封装了底层的API,从而大大简化了程序的开发。利用MFCDAO类,用户可以编写独立于DBMS的应用程序。

 

  DAO是从Visual C++4.0版开始引入的。一般地讲,DAO类提供了比ODBC类更广泛的支持。一方面,只要有ODBC驱动程序,使用Microsoft JetDAO就可以访问ODBC数据源。另一方面,由于DAO是基于Microsoft Jet引擎的,因而在访问Access数据库(*.MDB文件)时具有很好的性能。

 

10.8.2 DAOODBC的相似之处

 

DAO类与ODBC类相比具有很多相似之处,这主要有下面几点:

 

二者都支持对各种ODBC数据源的访问。虽然二者使用的数据引擎不同,但都可以满足用户编写独立于DBMS的应用程序的要求。

 

DAO提供了与ODBC功能相似的MFC类。例如,DAOCDaoDatabase类对应ODBCCDatabase类,CDaoRecordset对应CRecordsetCDaoRecordView对应CRecordViewCDaoException对应CDBException。这些对应的类功能相似,它们的大部分成员函数都是相同的。

 

AppWizardClassWizard对使用DAOODBC对象的应用程序提供了类似的支持。

 

  由于DAOODBC类的许多方面都比较相似,因此只要用户掌握了ODBC,就很容易学会使用DAO。实际上,用户可以很轻松地把数据库应用程序从ODBC移植到DAO

 

  Visual C++随盘提供了一个名为DaoEnrol的例子,该例实际上是Enroll的一个DAO版本。读者可以打开DaoEnrol工程看一看,它的源代码与Enroll的极为相似。读者可以按照建立Enroll的步骤来建立DaoEnrol,其中只有若干个地方有差别,这主要有以下几点:

 

选取的数据源不同。在用AppWizard创建DaoEnrol时,以及在用ClassWizard创建CDaoRecordset类的派生类时,在Database Options对话框中应该选择DAO而不是ODBC。而且DAO的数据源是通过选择一个.MDB文件来指定的,即点击“...”按钮后在文件对话框中选择要访问的.MDB文件。

 

记录集的缺省类型不同。ODBC记录集的缺省类型是快照(Snapshot),而DAO则是动态集(Dynaset)

 

参数化的方式不同。DAO记录集的m_strFilterm_strSort中的参数不是“?”号,而是一个有意义的参数名。例如,在下面的过滤器中有一个名为CourseIDParam的参数。

m_pSet->m_strFilter ="CourseID = CourseIDParam";

DoFieldExchange函数中,有下面两行:

pFX->SetFieldType(CDaoFieldExchange::param);

DFX_Text(pFX, _T("CourseIDParam"), m_strCourseIDParam);

DFX函数的第二个参数也是CourseIDParam

 

处理异常的方式不同。例如,在删除记录时,对异常的处理如下所示:

 

try

 

{

 

m_pSet->Delete();

 

}

 

catch(CDaoException* e)

 

{

 

AfxMessageBox(e->

 

m_pErrorInfo->m_strDescription);

 

e->Delete();

 

}

 

  除了上述差别外,AppWizardClassWizard也隐藏了一些细微的不同之处,例如,DAO记录集是使用是DFX数据交换机制(DAO record field exchange)而不是RFX,在DAO记录集的DoFieldExchange中使用的是DFX函数而不是RFX函数。

 

10.8.3 DAO的特色

 

  DAO可以通过ODBC驱动程序访问ODBC数据源。但DAO是基于Microsoft Jet引擎的,通过该引擎,DAO可以直接访问AccessFoxProdBASEParadoxExcelLotus WK等数据库。CDaoDatabase类可以直接与这些数据库进行连接,而不必在ODBC管理器中注册DSN。例如,下面的代码用来打开一个FoxPro数据库:

 

CDaoDatabase daoDb;

 

daoDb.Open( “”,FALSE,FALSE,"FoxPro 2.5;DATABASE=c://zyf");

 

CDaoDatabase::Open函数用来连接某个数据库,该函数的声明为:

 

virtual void Open( LPCTSTR lpszName, BOOL bExclusive = FALSE, BOOL bReadOnly = FALSE, LPCTSTR lpszConnect = _T("") );

throw( CDaoException, CMemoryException );

 

 

 

  参数bExclusive如果为TRUE,则函数以独占方式打开数据库,否则就用共享方式。如果bReadOnlyTRUE,那么就以只读方式打开数据库。如果要打开一个Access数据库,则可以在lpszName参数中指定MDB文件名。如果要访问非Access数据库,则应使该参数为“”,并在lpszConnect中说明一个连接字符串。连接字符串的形式一般为 数据库类型;DATABASE=路径(文件)”,例如 “dBASE III;DATABASE=c://MYDIR”

 

  Open函数也可以打开一个ODBC数据源,但这需要相应的ODBC驱动程序,并需要在ODBC管理器中注册DSN。此时lpszConnect的形式为 “ODBC;DSN=MyDataSource”。显然,用DAO访问象FoxPro这样的数据库时,直接打开比把它当作ODBC数据源打开要省事。

 

  支持DDLDAO对数据库编程良好支持的一个重要体现。DDL(Data Definition Language)SQL术语中叫做“数据定义语言”,它用来完成生成、修改和删除数据库结构的操作。ODBC类只支持DML(Data Manipulation Language,数据操作语言),不支持DDL,所以用ODBC类只能完成数据的操作,不能涉及数据库的结构。要执行DDL操作,只有通过ODBC API。而DAO类同时提供了对DMLDDL的支持,这意味着程序可以使用DAO类方便的创建数据库及修改数据库的结构。

 

  与ODBC相比,DAO提供了一些新类来加强其功能,这些新类包括:

 

CDaoTableDef类提供了对表的结构的定义。调用CDaoTableDef::Open可以获得表的结构定义。调用CDaoTableDef::Create可以创建一张新表,调用CDaoTableDef:: CreateField可为表添加字段,调用CDaoTableDef::CreateIndex可以为表添加索引。调用CDaoTableDef::Append可以把新创建的表保存到数据库中。

 

CDaoQueryDef类代表一个查询定义(Query definition),该定义可以被存储到数据库中。

 

CDaoWorkspace提供了数据工作区(Workspace)。一个工作区可以包含几个数据库,工作区可以对所属的数据库进行全体或单独的事务处理,工作区也负责数据库的安全性。如果需要,程序可以打开多个工作区。

 

  DAO的另一个重要特色在于它对Access数据库提供了强大的支持。由于DAO是基于Microsoft Jet引擎的,所以DAO肯定要在Access数据库上多作一些文章。例如,调用CDaoDatabase::Create可以直接建立一个MDB文件,代码如下所示:

m_db.Create(“C://MYDIR//MYDB.MDB”);

 

利用AppWizardClassWizard,用户可以方便地开发出性能优良的基于DAOAccess数据库应用程序。

 

10.8.4 ODBC还是DAO

 

由于DAO可以访问ODBC数据源,下面几条可以作为DAO替代ODBC的理由:

 

在某些情况下可以获得更好的性能,特别是在访问Microsoft Jet.MDB)数据库时。

 

ODBC兼容

 

DAO允许数据有效检查

 

DAO允许用户说明表与表之间的关系

 

  当然,DAO的出现并不意味着ODBC已经过时了。如果用户的工作必须严格限于ODBC数据源,尤其是在开发Client/Server结构的应用程序时,用ODBC有较好的性能。

 

直接通过DAO读写Access文件

作者:徐景周

下载示例源代码 http://www.vckbase.com/code/downcode.asp?id=1643

 

直接利用DAO来创建、读写Access文件,总的说来,对比上篇《直接通过ODBC读、写Excel文件》来讲,要简单一些。在下面的示例中,我们将用到两种方法:SQLDAO类函数来混合实现它们,这样做的目地,我想可以使大家更加方便灵活的运用它们来完成你想要做的东西。在示例程序中默认指定创建数据库名为:Demo.mdb,内部表名为:DemoTable,写入两个字段:名字和年龄,采用和上一篇读写Excel类似的操作,你也可以根据自己需要来动态改变它们。示例程序运行界面如下所示:

 

 

 

下面让我们来简要看看它的实现步骤:

1. 首先,应确保包含进了afxdao.h头文件,可以在StdAfx.h文件中包含它,如下:

 

 

#include <afxdao.h>                        //加入DAO数据库支持

 

2. 声明DAO库及其记录集变量,可在你的实现文件中加入下面代码: CDaoDatabase db;                                       //数据库

CDaoRecordset RecSet(&db);         //记录集

 

3. 接着,先让我们来实现它的创建及写入操作void CRWAccessDlg::OnWriteAccess()

{

        //获取主程序所在路径,存在sPath

        CString sPath;

        GetModuleFileName(NULL,sPath.GetBufferSetLength (MAX_PATH+1),MAX_PATH);

        sPath.ReleaseBuffer ();

        int nPos;

        nPos=sPath.ReverseFind (''//'');

        sPath=sPath.Left (nPos);

 

        //默认创建数据名:Demo.mdb,内部表名:DemoTable,表内有二个字段:姓名、年龄

        CString lpszFile = sPath + "//Demo.mdb";

       

        CFileFind  fFind;

        BOOL bSuccess;

        bSuccess=fFind.FindFile(lpszFile);

 

        fFind.Close ();

    //是否已有创建好的Demo.mdb文件,没有则创建它

        if(!bSuccess)

        {

                db.Create(lpszFile);

 

                CString SqlCmd = "CREATE TABLE DemoTable(Name VARCHAR(20),Age VARCHAR(3));";

                db.Execute(SqlCmd);

       

                //打开已创建的数据表

                RecSet.Open(AFX_DAO_USE_DEFAULT_TYPE,

                        "SELECT * FROM DemoTable", 0);

                //加入第一个记录,用SQL语句

                db.Execute("INSERT INTO DemoTable (Name,Age) VALUES (''徐景周'',26)");

               

                //加入第二个记录,用DAO涵数

                RecSet.AddNew();

                RecSet.SetFieldValue("Name","徐志慧");

                RecSet.SetFieldValue("Age","21");

                RecSet.Update();

               

                //加入第三个记录,用DAO涵数

                RecSet.AddNew();

                RecSet.SetFieldValue("Name","郭徽");

                RecSet.SetFieldValue("Age","27");

                RecSet.Update();

               

                //关闭记录集及库

                RecSet.Close();

                db.Close();

 

                AfxMessageBox("Access文件写入成功!");

        }

        else

                AfxMessageBox("Demo.mdb数据库已经创建!");

       

}

 

4. 最后,让我们来实现它的读取操作。void CRWAccessDlg::OnReadAccess()

{

        COleVariant var;              // 字段类型

        var.ChangeType(VT_BSTR, NULL);

        CString strName,strAge,strFile;

 

        //清空列表框

        m_AccessList.ResetContent();

 

        //获取主程序所在路径,存在sPath

        CString sPath;

        GetModuleFileName(NULL,sPath.GetBufferSetLength (MAX_PATH+1),MAX_PATH);

        sPath.ReleaseBuffer ();

    int nPos;

        nPos=sPath.ReverseFind (''//'');

        sPath=sPath.Left (nPos);

 

        strFile = sPath + "//demo.mdb";

        db.Open(strFile);              // 打开已创建的demo数据库及DamoTable

        RecSet.Open(AFX_DAO_USE_DEFAULT_TYPE,"SELECT * FROM DemoTable",NULL);

 

        while(!RecSet.IsEOF())    // 有没有到表结尾

        {

                RecSet.GetFieldValue("Name",var);

                strName = (LPCSTR)var.pbstrVal;

                RecSet.GetFieldValue("Age",var);

                strAge = (LPCSTR)var.pbstrVal;

                m_AccessList.AddString( strName + " --> "+strAge );

 

                RecSet.MoveNext();

        }

 

        //关闭记录集及库

        RecSet.Close();

        db.Close();

}

 

以上部分代码的具体实现的细节问题,可在下载实例代码后,仔细查看源码既可(内有详细注释)

 

如果在DAO程序中用的是SQL方法,abc123的位置用%s,然后用逗号m_DeviceID,例如:strSQl=("Select * from 表名 Where 字段名='%s'",m_strname)

m_pDaoDatabase->Excute(strSQL);

m_pDaoDatabase是数据库指针。

 

 

m_pMainWnd->SetWindowText("Receiver");

 

 

CString str="Receiver";

 

CWnd *pWnd=CWnd::FindWindow(NULL,str);

 

if(pWnd)

 

pWnd->SendMessage(WM_COMM,0,0);

 

Handle

 

HandlePathName好像关系不大,有几种函数可以找Handle

 

HWND FindWindow(

  LPCTSTR lpClassName,  // class name

  LPCTSTR lpWindowName  // window name

);

 

BOOL EnumWindows(

  WNDENUMPROC lpEnumFunc,  // callback function

  LPARAM lParam            // application-defined value

);

 

函数原型:HWND GetWindowHWND hWndUNIT nCmd);

 

    参数:

 

    hWnd:窗口句柄。要获得的窗口句柄是依据nCmd参数值相对于这个窗口的句柄。

 

    nCmd:说明指定窗口与要获得句柄的窗口之间的关系。该参数值可以是下列之一:

 

    GW_CHILD:如果指定窗口是父窗口,则获得的是在Z序顶端的子窗口的句柄,否则为NULL。函数仅检查指定父窗口的子窗口,不检查继承窗口。

 

    GW_ENABLEDPOUP:(WindowsNT 5.0)返回的句柄标识了属于指定窗口的处于使能状态弹出式窗口(检索使用第一个由GW_HWNDNEXT 查找到的满足前述条件的窗口);如果无使能窗口,则获得的句柄与指定窗口相同。

 

    GW_HWNDFIRST:返回的句柄标识了在Z序最高端的相同类型的窗口。如果指定窗口是最高端窗口,则该句柄标识了在Z序最高端的最高端窗口;如果指定窗口是顶层窗口,则该句柄标识了在z序最高端的顶层窗口:如果指定窗口是子窗口,则句柄标识了在Z序最高端的同属窗口。

 

    GW_HWNDLAST:返回的句柄标识了在z序最低端的相同类型的窗口。如果指定窗口是最高端窗口,则该柄标识了在z序最低端的最高端窗口:如果指定窗口是顶层窗口,则该句柄标识了在z序最低端的顶层窗口;如果指定窗口是子窗口,则句柄标识了在Z序最低端的同属窗口。

 

    GW_HWNDNEXT:返回的句柄标识了在Z序中指定窗口下的相同类型的窗口。如果指定窗口是最高端窗口,则该句柄标识了在指定窗口下的最高端窗口:如果指定窗口是顶层窗口,则该句柄标识了在指定窗口下的顶层窗口;如果指定窗口是子窗口,则句柄标识了在指定窗口下的同属窗口。

 

    GW HWNDPREV:返回的句柄标识了在Z序中指定窗口上的相同类型的窗口。如果指定窗口是最高端窗口,则该句柄标识了在指定窗口上的最高端窗口;如果指定窗口是顶层窗口,则该句柄标识了在指定窗口上的顶层窗口;如果指定窗口是子窗口,则句柄标识了在指定窗口上的同属窗口。

 

    GW_OWNER:返回的句柄标识了指定窗口的所有者窗口(如果存在)。

 

    返回值:如果函数成功,返回值为窗口句柄;如果与指定窗口有特定关系的窗口不存在,则返回值为NULL

 

    若想获得更多错误信息,请调用GetLastError函数。

 

    备注:在循环体中调用函数EnumChildWindow比调用GetWindow函数可靠。调用GetWindow函数实现该任务的应用程序可能会陷入死循环或退回一个已被销毁的窗口句柄。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值