在需要对数据库进行操作时,OLE DB总是被认为是一种效率最高但最难的方法。但是以我最近使用OLE DB的经验看来,OLE DB的效率高则高矣,但却一点都不难。说它难恐怕主要是因为可参考的中文资料太少,为了帮助以后需要接触OLE DB的同行,我撰写了这篇文章。本文包含如下内容:

  1. OLE DB写数据库;
  2. OLE DB读数据库;
  3. OLE DB对二进制数据(text、ntext、image等)的处理。

首先来看看对SQL Server进行写操作的代码,有一定VC基础的读者应该可以很顺利地看懂。OLE DB写数据库,就是这么简单!

注:
1.以下代码中使用的模板类EAutoReleasePtr与ATL中的CComPtr类似,是一个在析构时自动调用Release的类。CComPtr的代码在ATLBASE.H中定义。
2.以下代码均在UNICODE环境下编译,因为执行的SQL语句必须是UNICODE的。设置工程为UNICODE的方法是:首先在project->settings->C/C++的属性页中的Preprocessor中,删除_MBCS写入UNICODE,_UNICODE。然后在link属性页中Category中选择output,在Entry-Point symbol 中添加wWinMainCRTStartup。

EAutoReleasePtr pIDBInitialize;
HRESULT hResult = ConnectDatabase( &pIDBInitialize, _T(“127.0.0.1”), _T(“sa”), _T(“password”) );
if( FAILED( hResult ) )
{
//失败,可能是因为数据库没有启动、用户名密码错等等
return;
}

EAutoReleasePtr pIOpenRowset;
hResult = CreateSession( pIDBInitialize, &pIOpenRowset );
if( FAILED( hResult ) )
{
//出错
return;
}

EAutoReleasePtr pICommand;
EAutoReleasePtr pICommandText;
hResult = CreateCommand( pIOpenRowset, &pICommand, &pICommandText );
if( FAILED( hResult ) )
{
//出错
return;
}

hResult = ExecuteSQL( pICommand, pICommandText, _T(“USE PBDATA”) );
if( FAILED( hResult ) )
{
//如果这里失败,那就是SQL语句执行失败。在此处,就是PBDATA还未创建
return;
}

// 创建表
ExecuteSQL( pICommand, pICommandText, _T(“CREATE TABLE 2005_1(Volume real NOT NULL,ID int NOT NULL IDENTITY)”) );

// 添加记录
ExecuteSQL( pICommand, pICommandText, _T(“INSERT INTO 2005_1 VALUES(100.0)”) );

//…

其中几个函数的代码如下:

HRESULT ConnectDatabase( IDBInitialize** ppIDBInitialize, LPCTSTR pszDataSource, LPCTSTR pszUserID, LPCTSTR pszPassword )
{
ASSERT( ppIDBInitialize != NULL && pszDataSource != NULL && pszUserID != NULL && pszPassword != NULL );

UINT uTimeout = 15U; // 连接数据库超时(秒)
TCHAR szInitStr[1024];
VERIFY( 1023 >= wsprintf( szInitStr, _T("Provider=SQLOLEDB;Data Source=%s;Initial Catalog=master;User Id=%s;Password=%s;Connect Timeout=%u"), pszDataSource, pszUserID, pszPassword, uTimeout ) );
//Initial Catalog=master指明连接成功后,"USE master"。EAutoReleasePtr<IDataInitialize> pIDataInitialize;
HRESULT hResult = ::CoCreateInstance( CLSID_MSDAINITIALIZE, NULL, CLSCTX_INPROC_SERVER,IID_IDataInitialize, ( void** )&pIDataInitialize );
if( FAILED( hResult ) )
{return hResult;
}EAutoReleasePtr<IDBInitialize> pIDBInitialize;
hResult = pIDataInitialize->GetDataSource( NULL, CLSCTX_INPROC_SERVER, ( LPCOLESTR )szInitStr,IID_IDBInitialize, ( IUnknown** )&pIDBInitialize );
if( FAILED( hResult ) )
{return hResult;
}
hResult = pIDBInitialize->Initialize( );
if( FAILED( hResult ) )
{return hResult;
}
* ppIDBInitialize = pIDBInitialize.Detach( );
return S_OK;

}

HRESULT CreateSession( IDBInitialize* pIDBInitialize, IOpenRowset** ppIOpenRowset )
{
ASSERT( pIDBInitialize != NULL && ppIOpenRowset != NULL );
EAutoReleasePtr pSession;
HRESULT hResult = pIDBInitialize->QueryInterface( IID_IDBCreateSession, ( void** )&pSession );
if( FAILED( hResult ) )
{
return hResult;
}
EAutoReleasePtr pIOpenRowset;
hResult = pSession->CreateSession( NULL, IID_IOpenRowset, ( IUnknown** )&pIOpenRowset );
if( FAILED( hResult ) )
{
return hResult;
}
* ppIOpenRowset = pIOpenRowset.Detach( );
return S_OK;
}

HRESULT CreateCommand( IOpenRowset* pIOpenRowset, ICommand** ppICommand, ICommandText** ppICommandText )
{
ASSERT( pIOpenRowset != NULL && ppICommand != NULL && ppICommandText != NULL );
HRESULT hResult;
EAutoReleasePtr pICommand;
{
EAutoReleasePtr pICreateCommand;
hResult = pIOpenRowset->QueryInterface( IID_IDBCreateCommand, ( void** )&pICreateCommand );
if( FAILED( hResult ) )
{
return hResult;
}

    hResult = pICreateCommand->CreateCommand( NULL, IID_ICommand, (IUnknown**)&pICommand );if( FAILED( hResult ) ){return hResult;}
}
EAutoReleasePtr<ICommandText> pICommandText;
hResult = pICommand->QueryInterface( &pICommandText );
if( FAILED( hResult ) )
{return hResult;
}
* ppICommand = pICommand.Detach( );
* ppICommandText = pICommandText.Detach( );
return S_OK;

}

HRESULT ExecuteSQL( ICommand* pICommand, ICommandText* pICommandText, LPCTSTR pszCommand, LONG* plRowsAffected )
{
ASSERT( pICommand != NULL && pICommandText != NULL && pszCommand != NULL && pszCommand[0] != 0 );

HRESULT hResult = pICommandText->SetCommandText( DBGUID_DBSQL, ( LPCOLESTR )pszCommand );
if( FAILED( hResult ) )
{return hResult;
}
LONG lAffected;
hResult = pICommand->Execute( NULL, IID_NULL, NULL, plRowsAffected == NULL ? &lAffected : plRowsAffected, ( IUnknown** )NULL );
return hResult;

}

以上就是写数据库的全部代码了,是不是很简单呢?下面再来读的。

// 先用与上面代码中一样的步骤获取pICommand,pICommandText。此处省略

HRESULT hResult = pICommandText->SetCommandText( DBGUID_DBSQL, ( LPCOLESTR )_T(“SELECT Volume FROM 2005_1 WHERE ID = @@IDENTITY”) ); //取我们刚刚添加的那一条记录
if( FAILED( hResult ) )
{
return;
}

LONG lAffected;
EAutoReleasePtr pIRowset;
hResult = pICommand->Execute( NULL, IID_IRowset, NULL, &lAffected, ( IUnknown** )&pIRowset );

if( FAILED( hResult ) )
{
return;
}

EAutoReleasePtr pIAccessor;
hResult = pIRowset->QueryInterface( IID_IAccessor, ( void** )&pIAccessor );

if( FAILED( hResult ) )
{
return;
}

// 一个根据表中各字段的数值类型而定义的结构,用于存储返回的各字段的值
struct CLoadLastFromDB
{
DBSTATUS dwdsVolume;
DWORD dwLenVolume;
float fVolume;
};

// 此处我们只查询了一个字段。如果要查询多个字段,CLoadLastFromDB中要添加相应的字段定义,下面的dbBinding也要相应扩充。dbBinding[].iOrdinal要分别指向各个字段,dbBinding[].wType要根据字段类型赋合适的值。

DBBINDING dbBinding[1];
dbBinding[0].iOrdinal = 1; // Volume 字段的位置,从 1 开始
dbBinding[0].obValue = offsetof( CLoadLastFromDB, fVolume );
dbBinding[0].obLength = offsetof( CLoadLastFromDB, dwLenVolume );
dbBinding[0].obStatus = offsetof( CLoadLastFromDB, dwdsVolume );
dbBinding[0].pTypeInfo = NULL;
dbBinding[0].pObject = NULL;
dbBinding[0].pBindExt = NULL;
dbBinding[0].dwPart = DBPART_VALUE | DBPART_STATUS | DBPART_LENGTH;
dbBinding[0].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
dbBinding[0].eParamIO = DBPARAMIO_NOTPARAM;
dbBinding[0].cbMaxLen = 0;
dbBinding[0].dwFlags = 0;
dbBinding[0].wType = DBTYPE_R4; // float就是DBTYPE_R4,int就是DBTYPE_I4。参见MSDN
dbBinding[0].bPrecision = 0;
dbBinding[0].bScale = 0;

HACCESSOR hAccessor = DB_NULL_HACCESSOR;
DBBINDSTATUS dbs[1];
hResult = pIAccessor->CreateAccessor( DBACCESSOR_ROWDATA, 1, dbBinding, sizeof( CLoadLastDataFromDB ), &hAccessor, dbs );

if( FAILED( hResult ) )
{
return;
}

ASSERT( dbs[0] == DBBINDSTATUS_OK );
ULONG uRowsObtained = 0;
HROW hRows[1]; // 这里我们只查询了最新的那一条记录
HROW* phRows = hRows;
CLoadLastFromDB rmd;
hResult = pIRowset->GetNextRows( NULL, 0, 1, &uRowsObtained, &phRows );
if( SUCCEEDED( hResult ) && uRowsObtained != 0U )
{
hResult = pIRowset->GetData( phRows[0], hAccessor, &rmd );
if( FAILED( hResult ) )
{
ASSERT( FALSE );
}
ASSERT( rmd.dwdsVolume == DBSTATUS_S_OK );
// rmd.fVolume 就是我们要取的值
}

pIRowset->ReleaseRows( uRowsObtained, phRows, NULL, NULL, NULL );
pIAccessor->ReleaseAccessor( hAccessor, NULL );
pIAccessor.Release( );
pIRowset.Release( );

读操作也完成了,是不是仍然很简单呢?下面我们再来看看最麻烦的二进制数据(text、ntext、image等)的读写。要实现BLOB数据的读写,我们需要一个辅助的类,定义如下:

class CSequentialStream : public ISequentialStream // BLOB 数据访问类
{
public:
CSequentialStream( );
virtual ~CSequentialStream( );
virtual BOOL Seek( ULONG uPosition );
virtual BOOL Clear( );
virtual ULONG GetLength( ) { return m_uBufferUsed; };
virtual operator void* const( ) { return m_pBuffer; };
STDMETHODIMP_( ULONG ) AddRef( ) { return ++ m_uRefCount; };
STDMETHODIMP_( ULONG ) Release( ) { ASSERT( m_uRefCount != 0U ); – m_uRefCount; if( m_uRefCount == 0U ) { delete this; } return m_uRefCount; };
STDMETHODIMP QueryInterface( REFIID riid, LPVOID* ppv );
STDMETHODIMP Read( void __RPC_FAR* pv, ULONG cb, ULONG __RPC_FAR* pcbRead );
STDMETHODIMP Write( const void __RPC_FAR* pv, ULONG cb, ULONG __RPC_FAR* pcbWritten );
void ResetPosition( ) { m_uPosition = 0U; };
HRESULT PreAllocBuffer( ULONG uSize );

private:
ULONG m_uRefCount; // reference count
void* m_pBuffer; // buffer
ULONG m_uBufferUsed; // buffer used
ULONG m_uBufferSize; // buffer size
ULONG m_uPosition; // current index position in the buffer
};

实现如下:

CSequentialStream::CSequentialStream( ) : m_uRefCount( 0U ), m_pBuffer( NULL ), m_uBufferUsed( 0U ), m_uBufferSize( 0U ), m_uPosition( 0U )
{
AddRef( );
}

CSequentialStream::~CSequentialStream( )
{
Clear( );
}

HRESULT CSequentialStream::QueryInterface( REFIID riid, void** ppv )
{
if( riid == IID_IUnknown || riid == IID_ISequentialStream )
{
* ppv = this;
( ( IUnknown* )*ppv )->AddRef( );
return S_OK;
}
* ppv = NULL;
return E_NOINTERFACE;
}

BOOL CSequentialStream::Seek( ULONG uPosition )
{
ASSERT( uPosition < m_uBufferUsed );
m_uPosition = uPosition;
return TRUE;
}

BOOL CSequentialStream::Clear( )
{
m_uBufferUsed = 0U;
m_uBufferSize = 0U;
m_uPosition = 0U;
( m_pBuffer != NULL ? CoTaskMemFree( m_pBuffer ) : 0 );
m_pBuffer = NULL;
return TRUE;
}

HRESULT CSequentialStream::PreAllocBuffer( ULONG uSize )
{
if( m_uBufferSize < uSize )
{
m_uBufferSize = uSize;
m_pBuffer = CoTaskMemRealloc( m_pBuffer, m_uBufferSize );
if( m_pBuffer == NULL )
{
Clear( );
return STG_E_INSUFFICIENTMEMORY;
}
}
return S_OK;
}

HRESULT CSequentialStream::Read( void* pv, ULONG cb, ULONG* pcbRead )
{
( pcbRead != NULL ? ( * pcbRead = 0U ) : 0 );
if( pv == NULL ) { return STG_E_INVALIDPOINTER; }
if( cb == 0U ) { return S_OK; }

ASSERT( m_uPosition <= m_uBufferUsed );
ULONG uBytesLeft = m_uBufferUsed - m_uPosition;if( uBytesLeft == 0U ) { return S_FALSE; } //no more bytesULONG uBytesRead = ( cb > uBytesLeft ? uBytesLeft : cb );
memcpy( pv, ( BYTE* )m_pBuffer + m_uPosition, uBytesRead );
m_uPosition += uBytesRead;( pcbRead != NULL ? ( * pcbRead = uBytesRead ) : 0 );
return ( cb != uBytesRead ? S_FALSE : S_OK );

}

HRESULT CSequentialStream::Write( const void* pv, ULONG cb, ULONG* pcbWritten )
{
if( pv == NULL ) { return STG_E_INVALIDPOINTER; }
( pcbWritten != NULL ? ( * pcbWritten = 0U ) : 0 );
if( cb == 0U ){ return S_OK; }

ASSERT( m_uPosition <= m_uBufferUsed );
if( m_uBufferSize < m_uPosition + cb )
{m_uBufferSize = m_uPosition + cb;m_pBuffer = CoTaskMemRealloc( m_pBuffer, m_uBufferSize );if( m_pBuffer == NULL ){Clear( );return STG_E_INSUFFICIENTMEMORY;}
}
m_uBufferUsed = m_uPosition + cb;
memcpy( ( BYTE* )m_pBuffer + m_uPosition, pv, cb );
m_uPosition += cb;
( pcbWritten != NULL ? ( * pcbWritten = cb ) : 0 );
return S_OK;

}

下面我们开始往一个包含ntext字段的表中添加记录。假设这个表(News)的结构为:ID int NOT NULL IDENTITY、Title nchar(80)、 Contents ntext。

// 先将记录添加进去,ntext字段留空。我们稍后再更新ntext的内容。
HRESULT hResult = ExecuteSQL( pICommand, pICommandText, _T(“INSERT INTO News VALUES(’‘TEST’’,’’’’)”) );

DBPROP dbProp;
dbPropSet.guidPropertySet = DBPROPSET_ROWSET;
dbPropSet.cProperties = 1;
dbPropSet.rgProperties = &dbProp;

DBPROPSET dbPropSet;
dbPropSet.rgProperties[0].dwPropertyID = DBPROP_UPDATABILITY;
dbPropSet.rgProperties[0].dwOptions = DBPROPOPTIONS_REQUIRED;
dbPropSet.rgProperties[0].dwStatus = DBPROPSTATUS_OK;
dbPropSet.rgProperties[0].colid = DB_NULLID;
dbPropSet.rgProperties[0].vValue.vt = VT_I4;
V_I4( &dbPropSet.rgProperties[0].vValue ) = DBPROPVAL_UP_CHANGE;

EAutoReleasePtr pICommandProperties;
hResult = pICommandText->QueryInterface( IID_ICommandProperties, ( void** )&pICommandProperties );

// 设置 Rowset 属性为“可以更新某字段的值”
hResult = pICommandProperties->SetProperties( 1, &dbPropSet );

hResult = pICommandText->SetCommandText( DBGUID_DBSQL, ( LPCOLESTR )L"SELECT Contents FROM News WHERE ID = @@IDENTITY" );

LONG lAffected;
EAutoReleasePtr pIRowsetChange;
hResult = pICommand->Execute( NULL, IID_IRowsetChange, NULL, &lAffected, ( IUnknown** )&pIRowsetChange );

EAutoReleasePtr pIAccessor;
hResult = pIRowsetChange->QueryInterface( IID_IAccessor, ( void** )&pIAccessor );

struct BLOBDATA
{
DBSTATUS dwStatus;
DWORD dwLength;
ISequentialStream* pISeqStream;
};

// 有关DBOBJECT、DBBINDING的设置,建议参考MSDN,很容易懂。
DBOBJECT dbObj;
dbObj.dwFlags = STGM_READ;
dbObj.iid = IID_ISequentialStream;

DBBINDING dbBinding;
dbBinding.iOrdinal = 1; // BLOB 字段的位置,从 1 开始
dbBinding.obValue = offsetof( BLOBDATA, pISeqStream );
dbBinding.obLength = offsetof( BLOBDATA, dwLength );
dbBinding.obStatus = offsetof( BLOBDATA, dwStatus );
dbBinding.pTypeInfo = NULL;
dbBinding.pObject = &dbObj;
dbBinding.pBindExt = NULL;
dbBinding.dwPart = DBPART_VALUE | DBPART_STATUS | DBPART_LENGTH;
dbBinding.dwMemOwner = DBMEMOWNER_CLIENTOWNED;
dbBinding.eParamIO = DBPARAMIO_NOTPARAM;
dbBinding.cbMaxLen = 0;
dbBinding.dwFlags = 0;
dbBinding.wType = DBTYPE_IUNKNOWN;
dbBinding.bPrecision = 0;
dbBinding.bScale = 0;

HACCESSOR hAccessor = DB_NULL_HACCESSOR;
DBBINDSTATUS dbs;
hResult = pIAccessor->CreateAccessor( DBACCESSOR_ROWDATA, 1, &dbBinding, sizeof( BLOBDATA ), &hAccessor, &dbs );

EAutoReleasePtr pIRowset;
hResult = pIRowsetChange->QueryInterface( IID_IRowset, ( void** )&pIRowset );

ULONG uRowsObtained = 0;
HROW* phRows = NULL;
hResult = pIRowset->GetNextRows( NULL, 0, 1, &uRowsObtained, &phRows );

CSequentialStream* pss = new CSequentialStream;
pss->PreAllocBuffer( 1024 ); // 预先分配好内存,并读入数据
pss->Write( pszSomebuffer, 512, NULL ); // pss->Write可以连续调用
pss->Write( pszSomebuffer+512, 512, NULL );
pss->ResetPosition( );

BLOBDATA bd;
bd.pISeqStream = ( ISequentialStream* )pss;
bd.dwStatus = DBSTATUS_S_OK;
bd.dwLength = pss->GetLength( );

// 将 BLOB 数据写入到数据库
hResult = pIRowsetChange->SetData( phRows[0], hAccessor, &bd );

pIAccessor->ReleaseAccessor( hAccessor, NULL );
pIRowset->ReleaseRows( uRowsObtained, phRows, NULL, NULL, NULL );

// pss was released by pIRowsetChange->SetData.

这样,我们就完成了一条记录的添加。读取BLOB字段的代码跟上面的完全类似,只要把
hResult = pIRowset->GetNextRows( NULL, 0, 1, &uRowsObtained, &phRows );
后面的那些改成下面的代码即可。

BLOBDATA bd;
hResult = pIRowset->GetData( phRows[0], hAccessor, &bd );
if( bd.dwStatus == DBSTATUS_S_ISNULL )
{
// 此字段为空
}
else if( bd.dwStatus != DBSTATUS_S_OK || bd.pISeqStream == NULL )
{
// 失败
}
else
{
// 从系统分配的 ISequentialStream 接口读入 BLOB 数据
BYTE szReadBuffer[1024];
for( ULONG uRead = 0U; ; )
{
if( FAILED( bd.pISeqStream->Read( szReadBuffer, 1024, &uRead ) ) )
{
break;
}
//szReadBuffer中就包含了BLOB字段的数据
if( uRead != 1024 )
{
break;
}
}
bd.pISeqStream->Release( );
}
pIAccessor->ReleaseAccessor( hAccessor, NULL );
pIRowset->ReleaseRows( uRowsObtained, phRows, NULL, NULL, NULL );

VC++ OLE DB 读写数据库相关推荐

  1. ssms 连接 ssis_SSIS连接管理器:OLE DB,ODBC,ADO.NET

    ssms 连接 ssis In this article, I will first give an overview of the OLE DB, ODBC, and ADO.NET SSIS co ...

  2. 使用OLE DB方式连接常用数据库的连接字符串的设置

    使用OLE DB方式连接常用数据库的连接字符串的设置 SQL Server 使用 OLE DB 所设置的连接字符串: 标准连接方式 Provider=sqloledb;Data Source=data ...

  3. SQL SERVER访问Access数据库,出现错误:无法初始化链接服务器 (null) 的 OLE DB 访问接口 Microsoft.Jet.OLEDB.4.0 的数据源对象

    SQL SERVER访问Access数据库,出现错误: 无法初始化链接服务器 "(null)" 的 OLE DB 访问接口 "Microsoft.Jet.OLEDB.4. ...

  4. oracle ole db 安装,Microsoft OLE DB Provider for Oracle(数据库引擎)

    Microsoft OLE DB Provider for Oracle(数据库引擎) 06/15/2011 本文内容 Microsoft OLE DB Provider for Oracle 允许对 ...

  5. mysql的ole db 访问接口msdasql的数据源对象_数据库访问接口(ODBC,OLEDB,ADO)

    目录 数据库访问接口发展历史 ODBC历史 ODBC(Open Database Connectivity,开放数据库互连).要了解ODBC是什么,先了解一下数据库连接的相关知识.在最开始连接数据库时 ...

  6. excel oledb mysql_通用Excel设置外部数据源引入Access数据库数据时,提示:“尚未注册 OLE DB 访问接口 Microsoft.Ace.Oledb.12.0”...

    在通用Excel中开发各种ERP.CRM.OA.进销存.内控管理.项目管理.工程施工管理.客户关系管理.绩效考核管理.销售管理.采购管理.仓库管理.行政管理.财务管理.人事管理.生产管理.考勤管理等系 ...

  7. SQL SERVER访问Access数据库,出现错误:无法初始化链接服务器 (null) 的 OLE DB 访问接口 Microsoft.Jet.OLEDB.4.0 的数据源对象...

    SQL SERVER访问Access数据库,出现错误: 无法初始化链接服务器 "(null)" 的 OLE DB 访问接口 "Microsoft.Jet.OLEDB.4. ...

  8. 关于windows2008r2下access数据库网站报Microsoft OLE DB Provider for ODBC Drivers 错误 '80004005'

    今天配置站点时报: "Microsoft OLE DB Provider for ODBC Drivers 错误 '80004005' [Microsoft][ODBC 驱动程序管理器] 未 ...

  9. VC用ADO访问数据库全攻略  作者 相生昌

    VC用ADO访问数据库全攻略 作者 相生昌 <script language=VBScript> document.title="VC用ADO访问数据库全攻略 "&am ...

最新文章

  1. 理解一条语句:SELECT difference(sum(value)) FROM mq_enqueue WHERE channel =~ /ActiveMQ_TEST/ AND $tim...
  2. [FAQ]VC读取数据源列表和驱动程序列表
  3. 二开微信表情包小程序去授权版+网站后端
  4. 手机游戏中的社交互动与任务剧情
  5. Day10(--JSP与JSP内置对象)
  6. linux安装matlab(亲测有用)
  7. Qt 3D Opengl学习
  8. 12月第1周网络安全报告:境内95.8万主机感染病毒
  9. 苹果试玩换个新id行不_苹果试玩换ID可以重复任务吗
  10. 联想笔记本系统还原要如何操作
  11. esp32(ROS2foxy)之飞龙在天turtlesim最快能多快???
  12. 前端部署的灵魂拷问,你能回答出几个?【部署必看篇】
  13. [保研直硕直博经验分享-2019] (上海交大计算机系夏令营+九推)
  14. 怎么仿制html文件,简单仿制HTML网页
  15. python 日志中最亮的仔,是喜欢的花里胡哨吖...
  16. 关于浏览器主页被劫持
  17. pyqt pyside2 QLabel 显示图片问题
  18. C语言编程练习,扫雷游戏
  19. 调用Nodejs版网易云API时,遇到code:-462报错解决方案
  20. Ubantu14编译7.1.2Android系统

热门文章

  1. 大前端学习--两个多月来的收获与进步 学习总结
  2. 转岗前景分析:人工智能、大数据开发是未来高薪的趋势?
  3. 防疫学生证,跟手机说再见
  4. 500 lines web craw 读书笔记
  5. babel-loader
  6. 基于javaweb项目ssm食品管理系统设计与实现(论文+程序设计源码+数据库文件)
  7. python是什么和c++是什么区别_Python和C++编程有什么区别?
  8. 第二周预习:异常类,常用类,容器
  9. 怎样为用户写“招标书”
  10. 金三银四?铜三铁四才对吧......