在窗口中显示一幅 JPG 图象
来源:互联网 发布:淘宝天下小二邀请活动 编辑:程序博客网 时间:2024/05/21 14:58
转自:http://www.cnblogs.com/miaohw/archive/2011/08/20.html
本实例是《杨老师之Blog——COM组件设计与应用(四)》中的实例三,本人实现后并加以注释。
void CShowJPGView::OnDraw(CDC* pDC)
{
CShowJPGDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
// TODO: 在此处为本机数据添加绘制代码
::CoInitialize(NULL); //初始化COM
HRESULT hr;
CFile file;
file.Open("D:\\test.jpg",CFile::modeRead | CFile::shareDenyNone); //读入文件内容
DWORD dwSize = file.GetLength(); //获取图片文件的大小,用来分配全局内存
HGLOBAL hMem = ::GlobalAlloc(GMEM_MOVEABLE,dwSize); //给图片分配全局内存
LPVOID lpBuf = ::GlobalLock(hMem); //锁定内存
file.Read(lpBuf,dwSize); //读取图片到全局内存当中
file.Close();
::GlobalUnlock(hMem); //解锁内存
IStream *pStream = NULL; //创建一个IStream接口指针,用来保存图片流
IPicture *pPicture = NULL; //创建一个IPicture接口指针,表示图片对象
//由HGLOBAL得到IStream,参数TRUE表示释放IStream的同时,释放内存
hr = ::CreateStreamOnHGlobal(hMem,TRUE,&pStream); //用全局内存初始化IStream接口指针
ASSERT(SUCCEEDED(hr));
hr = ::OleLoadPicture(pStream,dwSize,TRUE,IID_IPicture,(LPVOID*)&pPicture); //用OleLoadPicture获得IPicture接口指针
ASSERT(hr == S_OK);
//得到IPicture COM接口对象后,你就可以进行获得图片信息、显示图片等操作
long nWidth,nHeight; //宽高,MM_HIMETRIC模式,单位是0.01毫米
pPicture->get_Width(&nWidth); //宽
pPicture->get_Height(&nHeight); //高
//原始大小显示
CSize sz(nWidth,nHeight);
pDC->HIMETRICtoDP(&sz); //转换MM_HIMETRIC模式单位为MM_TEXT像素单位
pPicture->Render(pDC->m_hDC,0,0,sz.cx,sz.cy,0,nHeight,nWidth,-nHeight,NULL); //在指定的DC上绘出图片
//按窗口尺寸显示
// CRect rect;
// GetClientRect(&rect);
// pPicture->Render(pDC->m_hDC,0,0,rect.Width(),rect.Height(),0,nHeight,nWidth,-nHeight,NULL);
if(pPicture)
pPicture->Release(); //释放IPicture指针
if(pStream)
pStream->Release(); //释放IStream指针,同时释放了hMem
::CoUninitialize();
}
运行结果:
原始大小显示:
按窗口尺寸显示:
注释1:
CFile:: Open
virtual BOOL Open( LPCTSTR lpszFileName, UINT nOpenFlags, CFileException* pError = NULL );
返回值:
如果成功打开,则返回非零值,否则为0。pError参数仅在返回0时才有意义。
参数:
lpszFileName:待打开文件的路径,路径可为绝对、相对或网络名(UNC)。
nOpenFlags:一个定义了文件的共享和访问模式的UINT。它指定了打开文件后的动作,可以用OR(|)操作符将选项组合起来,至少应有一个访问权限和一个共享选项,modeCreate和modeNoInherit模式是可选的。可参阅CFile 构造函数中模式选项的列表。
具体如下:
CFile::modeCreate 让构造器创建一个新文件,如果那个文件已经存在,把那个文件的长度重设为
CFile::modeNoTruncate 可以同modeCreate. 一起用,如果要创建的文件已经存在,并不把它长度设置为0,因而这个文件获取或者作为一个新建文件或者作为一个已存在文件打开。这个功能往往很好用,比如说,当你需要打开一个设置文件,但是你并不清楚这个文件是否已经存在。
CFile::modeRead 打开文件仅仅供读
CFile::modeReadWrite 打开文件供读写
CFile::modeWrite 打开文件只供写
CFile::modeNoInherit 阻止这个文件被子进程继承
CFile::shareDenyNone 打开这个文件同时允许其它进程读写这个文件。如果文件被其它进程以incompatibility模式打开,这是create操作会失败。
CFile::shareDenyRead 打开文件拒绝其它任何进程读这个文件。如果文件被其它进程用compatibility模式或者是读方式打开,create操作失败。
CFile::shareDenyWrite 打开文件拒绝其它任何进程写这个文件。如果文件被其它进程用compatibility模式或者是写方式打开,create操作失败。
CFile::shareExclusive 以独占方式打开这个文件,不允许其它进程读写这个文件。 Construction fails if the file has been opened in any other mode for read or write access, even by the current process.
CFile::shareCompat 这个标志在32位的MFC中无效。 This flag maps to CFile::shareExclusive when used in CFile::Open.
CFile::typeText 设置成对回车换行对有特殊处理的文本模式(仅用在派生类中)
CFile::typeBinary 设置二进制模式(仅用在派生类中)
pError:指向一个存在的文件异常对象,获取失败操作的状态。
注释2:
调用GlobalAlloc函数分配一块内存,该函数会返回分配的内存句柄。
调用GlobalLock函数锁定内存块,该函数接受一个内存句柄作为参数,然后返回一个指向被锁定的内存块的指针。 您可以用该指针来读写内存。
调用GlobalUnlock函数来解锁先前被锁定的内存,该函数使得指向内存块的指针无效。
调用GlobalFree函数来释放内存块。您必须传给该函数一个内存句柄。
GlobalAlloc
该函数从堆中分配一定数目的字节数。Win32内存管理器并不提供相互分开的局部和全局堆。提供这个函数只是为了与16位的Windows相兼容。
HGLOBAL GlobalAlloc(
UINT uFlags, // 分配属性(方式)
SIZE_T dwBytes // 分配的字节数
);
返回值:
若函数调用成功,则返回一个新分配的内存对象的句柄。
若函数调用失败,则返回 NULL。可调用 GetLastError 以获得更多错误信息。
参数:
uFlags:指定如何分配内存,若指定为0,则是默认的GMEM_FIXED.这个值可以是下面其中一个或几个位标识(那些指明不兼容的组合除外)
具体如下:
GHND GMEM_MOVEABLE 和 GMEM_ZEROINIT的组合。
GMEM_FIXED 分配固定的内存,返回值是一个指针。
GMEM_MOVEABLE 分配可移动的内存,在Win32中内存块在物理内存中是不可移动的,但在缺省堆中可以。返回值是该内存对象的句柄,可使用函数 GlobalLock 将该句柄转换为一个指针。这个标识不能与 GMEM_FIXED 组合使用。
GMEM_ZEROINIT 将所申请内存初始化为0。
GPTR GMEM_FIXED和GMEM_ZEROINIT组合。
GMEM_DDESHARE、GMEM_DISCARDABLE、GMEM_LOWER、GMEM_NOCOMPACT、GMEM_NODISCARD、GMEM_NOT_BANKED、GMEM_NOTIFY、GMEM_SHARE 均被忽略,这些标识只是为与 16 位 Windows 相兼容而提供的。
dwBytes:指定要申请的字节数。若该参数为 0 且参数 uFlags 指定为 GMEM_MOVEABLE 则该函数返回一个内存对象的句柄,该内存对象被标识为discarded(可抛弃的)。
注解:
如果堆内没有足够的空间满足请求,函数将返回 NULL。因为NULL是用于标明错误的,所以不会分配虚拟0地址。
因此很容易检测出是否在使用一个NULL指针。
使用此函数分配内存可以保证8字节的边界。所有的内存均在执行访问时创建;不需要特别的函数来动态执行所产生的代码。
若函数调用成功,将至少分配所需内存。若实际分配量超过所需,则内存仍然能够充分利用之。可用函数 GlobalSize 来确定实际所分配的字节数。
可使用 GlobalFree 来释放内存。
GlobalLock
锁定内存中指定的内存块,并返回一个地址值,令其指向内存块的起始处。除非用 GlobalUnlock 函数将内存块解锁,否则地址会一直保持有效。Windows 为每个内存对象都维持着一个锁定计数。对这个函数的每次调用都应有一个对应的 GlobalUnlock 调用。
LPVOID GlobalLock(
HGLOBAL hMem // handle to global memory object
);
一般情况下我们在编程的时候,给应用程序分配的内存都是可以移动的或者是可以丢弃的,这样能使有限的内存资源充分利用,所以,在某一个时候我们分配的那块内存的地址是不确定的,因为他是可以移动的,所以得先锁定那块内存块,这儿应用程序需要调用API函数GlobalLock函数来锁定句柄。如下: lpMem=GlobalLock(hMem); 这样应用程序才能存取这块内存。
参数:
hMem:全局内存对象的句柄。这个句柄是通过GlobalAlloc或GlobalReAlloc来得到的
返回值:
调用成功,返回指向该对象的第一个字节的指针
调用失败,返回NULL,可以用GetLastError来获得出错信息
注意:
调用过GlobalLock锁定一块内存区后,一定要调用GlobalUnlock来解锁。
GlobalUnlock
GlobalUnlock函数解除锁定的内存块,使指向该内存块的指针无效,GlobalLock锁定的内存,一定要用GlobalUnlock解锁。
BOOL GlobalUnlock(
HGLOBAL hMem // handle to global memory object
);
参数:
hMem:全局内存对象的句柄
返回值:
非零值,指定的内存对象仍处于被锁定状态
0,函数执行出错,可以用GetLastError来获得出错信息,如果返回NO_ERROR,则表示内存对象已经解锁了
注意:
这个函数实际上是将内存对象的锁定计数器减一,如果计数器不为0,则表示执行过多个GlobalLock函数来对这个内存对象加锁,需要对应数目的GlobalUnlock函数来解锁。
如果通过GetLastError函数返回错误码为ERROR_NOT_LOCKED,则表示未加锁或已经解锁。
注释3:
CreateStreamOnHGlobal
CreateStreamOnHGlobal函数从指定内存创建流对象。
WINOLEAPI CreateStreamOnHGlobal(
HGLOBAL hGlobal, //Memory handle for the stream object
BOOL fDeleteOnRelease, //Whether to free memory when the
// object is released
LPSTREAM *ppstm //Address of output variable that
// receives the IStream interface pointer
);
参数:
hGlobal:由GlobalAlloc函数分配的内存句柄。
fDeleteOnRelease:该参数指明上一个参数制定的内存在该对象被释放后是否也自动释放。如果该参数设定为FALSE,那么调用者必须显示的释放hGlobal。如果该参数设置为TRUE,则hGlobal最终会自动释放。
ppstm:IStream指针的地址,该指针在该函数执行后指向新创建的流对象。该参数不能未NULL。 如果函数创建流对象成功则返回S_OK。
注释4:
OleLoadPicture
用API OleLoadPicture来加载JPG、GIF格式的图片(注:不支持PNG格式,另外GIF只能加载第一帧,且不支持透明)
OleLoadPicture 函数实际上创建了一个IPicture类型的COM接口对象,然后我们可以通过这个COM接口来操作图片(实际上你也可以用API OleCreatePictureIndirect来加载图片,不过相比而言OleLoadPicture函数简化了基于流的IPicture对象的创 建)。
posted @ 2011-08-20 22:45 Mr-Victor 阅读(325) 评论(0) 编辑
【转载】COM组件设计与应用(四)——简单调用组件
本文摘自:http://blog.vckbase.com/teacheryang/archive/2005/07/04/9135.html
一、前言
在 VCKBASE 的顶力支持下,在各位网友回帖的鼓励下,我才能顺利完成系列论文的前三回。书到本回,我们终于开始写代码啦。写点啥那?恩,有了!咱们先从如何调用现成的简单的组件开始吧,同时也顺便介绍一些相关的知识。
二、组件的启动和释放
在第三回中,大家用“小本本”记录了一个原则:COM 组件是运行在分布式环境中的 。于是,如何启动组件立刻就遇到了严重的问题,大家看这段代码:
p = new 对象; p->对象函数(); delete p;
这样的代码再熟悉不过了,在本地进程中运行是不会有问题的。但是你想想,如果这个对象是在“地球另一边”的计算机上,结果会如何?嘿嘿,C++ 在设计 new 的时候,可没有考虑远程的实现呀(计算机语言当然不会,也没必要去设计)。因此启动组件、调用接口的功能,当然就由 COM 系统来实现了。
图一 组件调用机制
由上图可以看出,当调用组件的时候,其实是依靠代理(运行在本地)和存根(运行在远端)之间的通讯完成的。具体来说,当客户程序通过 CoCreateInstance() 函数启动组件,则代理接管该调用,它和存根通讯,存根则它所在的本地(相对于客户程序来说就是远程了)执行 new 操作加载对象。对于初学者,你可以不用理它,代理和存根对我们来说是透明的。只要大约知道是怎么一回事就一切OK了。
问题又来了,这个远程的对象什么时候消灭呢?在第二回介绍接口概念的时候,当时我们特意忽略了两个函数,就是IUnknown::AddRef()和IUnknown::Release(),从函数名就能猜到了,一个是对内部引用记数器(Ref)加1,一个是释放(减1),当记数器减为0的时候,就是释放的机会啦。看起来很复杂,没办法,因为这是在介绍原理。其实在我们写程序的时候到比较简单,请大家遵守几个原则:
1、启动组件得到一个接口指针(Interface)后,不要调用AddRef()。因为系统知道你得到了一个指针,所以它已经帮你调用了AddRef()函数;
2、通过QueryInterface()得到另一个接口指针后,不要调用AddRef()。因为......和上面的道理一样;
3、当你把接口指针赋值给(保存到)另一个变量中的时候,请调用AddRef();
4、当不需要再使用接口指针的时候,务必执行Release()释放;
5、当使用智能指针的时候,可以省略指针的维护工作;(注1)
三、内存分配和释放
自从学习了C语言,老师就教导我们说:对于动态内存的申请和释放,一定要遵守“谁申请,谁释放”的原则。在此原则的指导下,不仅是我、不仅是你,就连特级大师都设计了这样怪怪的函数:
CListBox::GetText(int,LPTSTR)取得列表窗中子项目的标题。需要调用两个函数,先取得长度,然后分配内存,再实际取得标题内容。真烦!
说实在的,不但函数调用者感觉别扭,就连函数设计者心情也不会爽的,而这一切都是为了满足所谓“谁申请,谁释放”的原则。 解决这个问题最好的方式就是:函数内部根据实际需要动态申请内存,而调用者负责释放。这虽然违背了上述原则,但 COM 从方便性和效率出发,确实是这么设计的。
C语言C++语言Windows 平台COMIMalloc 接口BSTR申请malloc()newGlobalAlloc()CoTaskMemAlloc()Alloc()SysAllocString()重新申请realloc() GlobalReAlloc()CoTaskRealloc()Realloc()SysReAllocString()释放free()deleteGlobalFree()CoTaskMemFree()Free()SysFreeString() 以上这些函数必须要按类型配合使用(比如:new 申请的内存,则必须用 delete 释放)。在 COM 内部,当然你可以随便使用任何类型的内存分配释放函数,但组件如果需要与客户进行内存的交互,则必须使用上表中的后三类函数族。
1、BSTR 内存在上回书中,已经有比较丰富的介绍了,不再重复;
2、CoTaskXXX()函数族,其本质上就是调用C语言的函数(malloc...);
3、IMalloc 接口又是对 CoTaskXXX() 函数族的一个包装。包装后,同时增强了一些功能,比如:IMalloc::GetSize()可以取得尺寸,使用 IMallocSpy 可以监视内存的使用;
四、参数传递方向
在C语言的函数声明中,尤其当参数为指针的时候,你是看不出它传递方向的。比如:
void fun(char * p1, int * p2); 请问,p1、p2 哪个是入参?哪个是出参?甚或都是入参或都是出参?由于牵扯到内存分配和释放等问题,COM 需要明确标注参数方向。以后我们写程序,就类似下面的样子:
HRESULT Add([in] long n1, [in] long n2, [out] long *pnSum); // IDL文件(注2) STDMETHOD(Add)(/*[in]*/ long n1, /*[in]*/ long n2, /*[out]*/ long *pnSum); // .h文件
如果参数是动态分配的内存指针,那么遵守如下的规定:
方向申请人释放人提示[in]调用者调用者组件接收指针后,不能重新分配内存[out]组件调用者组件返回指针后,调用者“爱咋咋地”(注3)[in,out]调用者调用者组件可以重新分配内存
五、示例程序
示例一、由 CLSID 得到 ProgID。(程序以 word 为例子。如果运行不正确,嘿嘿,你没有安装 word 吧?)
::CoInitialize( NULL );HRESULT hr;// {000209FF-0000-0000-C000-000000000046} = word.application.9CLSID clsid = {0x209ff,0,0,{0xc0,0,0,0,0,0,0,0x46}};LPOLESTR lpwProgID = NULL;hr = ::ProgIDFromCLSID( clsid, &lpwProgID );if ( SUCCEEDED(hr) ){::MessageBoxW( NULL, lpwProgID, L"ProgID", MB_OK );IMalloc * pMalloc = NULL;hr = ::CoGetMalloc( 1, &pMalloc ); // 取得 IMallocif ( SUCCEEDED(hr) ){pMalloc->Free( lpwProgID ); // 释放ProgID内存pMalloc->Release(); // 释放IMalloc}}::CoUninitialize();
示例二、如何使用“浏览文件夹”选择对话窗。
CString BrowseFolder(HWND hWnd, LPCTSTR lpTitle){ // 调用 SHBrowseForFolder 取得目录(文件夹)名称 // 参数 hWnd: 父窗口句柄 // 参数 lpTitle: 窗口标题 char szPath[MAX_PATH]={0}; BROWSEINFO m_bi; m_bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_STATUSTEXT; m_bi.hwndOwner = hWnd; m_bi.pidlRoot = NULL; m_bi.lpszTitle = lpTitle; m_bi.lpfn = NULL; m_bi.lParam = NULL; m_bi.pszDisplayName = szPath; LPITEMIDLIST pidl = ::SHBrowseForFolder( &m_bi ); if ( pidl ) { if( !::SHGetPathFromIDList ( pidl, szPath ) ) szPath[0]=0; IMalloc * pMalloc = NULL; if ( SUCCEEDED ( ::SHGetMalloc( &pMalloc ) ) ) // 取得IMalloc分配器接口 { pMalloc->Free( pidl ); // 释放内存 pMalloc->Release(); // 释放接口 } } return szPath;}
示例三、在窗口中显示一幅 JPG 图象。
void CxxxView::OnDraw(CDC* pDC){::CoInitialize(NULL); // COM 初始化HRESULT hr;CFile file;file.Open( "c:\\aa.jpg", CFile::modeRead | CFile::shareDenyNone ); // 读入文件内容DWORD dwSize = file.GetLength();HGLOBAL hMem = ::GlobalAlloc( GMEM_MOVEABLE, dwSize );LPVOID lpBuf = ::GlobalLock( hMem );file.ReadHuge( lpBuf, dwSize );file.Close();::GlobalUnlock( hMem );IStream * pStream = NULL;IPicture * pPicture = NULL;// 由 HGLOBAL 得到 IStream,参数 TRUE 表示释放 IStream 的同时,释放内存hr = ::CreateStreamOnHGlobal( hMem, TRUE, &pStream );ASSERT ( SUCCEEDED(hr) );hr = ::OleLoadPicture( pStream, dwSize, TRUE, IID_IPicture, ( LPVOID * )&pPicture );ASSERT(hr==S_OK);long nWidth,nHeight; // 宽高,MM_HIMETRIC 模式,单位是0.01毫米pPicture->get_Width( &nWidth ); // 宽pPicture->get_Height( &nHeight ); // 高////////原大显示//////CSize sz( nWidth, nHeight );pDC->HIMETRICtoDP( &sz ); // 转换 MM_HIMETRIC 模式单位为 MM_TEXT 像素单位pPicture->Render(pDC->m_hDC,0,0,sz.cx,sz.cy,0,nHeight,nWidth,-nHeight,NULL);////////按窗口尺寸显示//////////CRect rect;GetClientRect(&rect);//pPicture->Render(pDC->m_hDC,0,0,rect.Width(),rect.Height(),//0,nHeight,nWidth,-nHeight,NULL);if ( pPicture ) pPicture->Release();// 释放 IPicture 指针if ( pStream ) pStream->Release(); // 释放 IStream 指针,同时释放了 hMem::CoUninitialize();}
示例四、在桌面建立快捷方式
在阅读代码之前,先看一下关于“快捷方式”组件的结构示意图。
图二、快捷方式组件的接口结构示意图
从结构图中可以看出,“快捷方式”组件(CLSID_ShellLink),有3个(其实不止)接口,每个接口完成一组相关功能的函数。IShellLink 接口(IID_IShellLink)提供快捷方式的参数读写功能(见图三),IPersistFile 接口(IID_IPersistFile)提供快捷方式持续性文件的读写功能。对象的持续性(注5),是一个非常常用,并且功能强大的接口家族。但今天,我们只要了解其中两函数,就可以了:IPersistFile::Save()和IPersistFile:Load()。(注6)
图三、快捷方式中的各种属性
#include < atlconv.h >void CreateShortcut(LPCTSTR lpszExe, LPCTSTR lpszLnk){// 建立块捷方式// 参数 lpszExe: EXE 文件全路径名// 参数 lpszLnk: 快捷方式文件全路径名::CoInitialize( NULL );IShellLink * psl = NULL;IPersistFile * ppf = NULL;HRESULT hr = ::CoCreateInstance( // 启动组件CLSID_ShellLink, // 快捷方式 CLSIDNULL, // 聚合用(注4)CLSCTX_INPROC_SERVER, // 进程内(Shell32.dll)服务IID_IShellLink, // IShellLink 的 IID(LPVOID *)&psl ); // 得到接口指针if ( SUCCEEDED(hr) ){psl->SetPath( lpszExe ); // 全路径程序名//psl->SetArguments(); // 命令行参数//psl->SetDescription(); // 备注//psl->SetHotkey(); // 快捷键//psl->SetIconLocation(); // 图标//psl->SetShowCmd(); // 窗口尺寸// 根据 EXE 的文件名,得到目录名TCHAR szWorkPath[ MAX_PATH ];::lstrcpy( szWorkPath, lpszExe );LPTSTR lp = szWorkPath;while( *lp ) lp++;while( ''\\'' != *lp ) lp--;*lp=0;// 设置 EXE 程序的默认工作目录psl->SetWorkingDirectory( szWorkPath );hr = psl->QueryInterface( // 查找持续性文件接口指针IID_IPersistFile, // 持续性接口 IID(LPVOID *)&ppf ); // 得到接口指针if ( SUCCEEDED(hr) ){USES_CONVERSION; // 转换为 UNICODE 字符串ppf->Save( T2COLE( lpszLnk ), TRUE ); // 保存}}if ( ppf )ppf->Release();if ( psl )psl->Release();::CoUninitialize();}void OnXXX(){CreateShortcut(_T("c:\\winnt\\notepad.exe"), // 记事本程序。注意,你的系统是否也是这个目录?_T("c:\\Documents and Settings\\Administrator\\桌面\\我的记事本.lnk"));// 桌面上建立快捷方式(lnk)文件的全路径名。注意,你的系统是否也是这个目录?// 如果用程序实现寻找桌面的路径,则可以查注册表// HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders}
七、小结
本回介绍的内容比较实用。大家不要只抄袭代码,而一定要理解它。结合 MSDN 的说明去思索代码、理解其含义。好了,想方设法把代码忘掉!三天后(如过你还没有忘记,那就再过三天),你在不参考示例代码,但可以随便翻阅 MSDN 的情况下,自己能独立地再次完成这四个例程,那么恭喜你,你已经入门了:0) 从下回开始,我们要用 ATL 做 COM 的开发工作啦,您老人家准备好了吗?
作业,留作业啦......
1、你已经学会如何建立快捷方式了,那么你知道怎么读取它的属性吗?(如果写不出这个程序,那么你就不用继续学习了。因为......动点脑筋呀!我还没有见过象你这么笨的学生呢!)
2、示例程序三中使用了 IPicture 接口显示一个 JPG 图象。那么你现在去完成一个功能,把 JPG 文件转换为 BMP 文件。
注1:智能指针的概念和用法,后续介绍。
注2:IDL 文件,下回就要介绍啦。
注3:东北话,想干什么都可以,反正我不管啦。
注4:聚合,也许在第30回中介绍吧:-)
注5:持续性,IPersistXXXXXX是一个非常强大的接口家族,后续介绍。
注6:想知道 IShellLink、IPersistFile接口的所有函数吗?别愣着,快去看MSDN呀......
posted @ 2011-08-20 20:59 Mr-Victor 阅读(68) 评论(0) 编辑
【转载】COM组件设计与应用(三)——数据类型
本文摘自:http://blog.vckbase.com/teacheryang/archive/2005/06/27/8885.html
一、前言
上回书介绍了GUID、CLSID、IID和接口的概念。本回的重点是介绍 COM 中的数据类型。咋还不介绍组件程序的设计步骤呀?咳......别着急,别着急!孔子曰:“饭要一口一口地吃”;老子语:“心急吃不了热豆腐”,孙子云:“走一步看一步吧” ...... 先掌握必要的知识,将来写起程序来才会得心应手也:-)
走入正题之前,请大家牢牢记住一条原则:COM 组件是运行在分布式环境中的。比如,你写了一个组件程序(DLL或EXE),那么使用者可能是在本机的某个进程内加载组件(INPROC_SERVER);也可能是从另一个进程中调用组件的进程(LOCAL_SERVER);也可能是在这台计算机上调用地球那边计算机上的组件(REMOTE_SERVER)。所以在理解和设计的时候,要时时刻刻想起这句话。快!拿出小本本,记下来!
二、HRESULT 函数返回值
每个人在做程序设计的时候,都有他们各自的哲学思想。拿函数返回值来说,就有好多种形式。
浮点数值
计算正玄值BOOL DeleteFile(LPCTSTR)布尔值
文件删除是否成功。如失败,需要GetLastError()才能取得失败原因void * malloc(size_t)内存指针
内存申请,如果失败,返回空指针 NULLLONG RegDeleteKey(HKEY,LPCTSTR)整数
删除注册表项。0表示成功,非0失败,同时这个值就反映了失败的原因UINT DragQueryFile(HDROP,UINT,LPTSTR,UINT)
整数
取得拖放文件信息。以不同的参数调用,则返回不同的含义:一会儿表示文件个数,一会儿表示文件名长度,一会儿表示字符长度...... ......
...
...... ......如此纷繁复杂的返回值,如此含义多变的返回值,使得大家在学习和使用的过程中,增加了额外的困难。好了,COM 的设计规范终于对他们进行了统一。组件API及接口指针中,除了IUnknown::AddRef()和IUnknown::Release()两个函数外,其它所有的函数,都以 HRESULT 作为返回值。大家想象一个组件的接口函数比如叫Add(),完成2个整数的加法运算,在C语言中,我们可以如下定义:
long Add( long n1, long n2 ) { return n1 + n2; }
还记得刚才我们说的原则吗?COM 组件是运行在分布式环境中的。也就是说,这个函数可能运行在“地球另一边”的计算机上,既然运行在那么遥远的地方,就有可能出现服务器关机、网络掉线、运行超时、对方不在服务区......等异常。于是,这个加法函数,除了需要返回运算结果以外,还应该返回一个值------函数是否被正常执行了。
HRESULT Add( long n1, long n2, long *pSum ) { *pSum = n1 + n2; return S_OK; }
如果函数正常执行,则返回 S_OK,同时真正的函数运行结果则通过参数指针返回。如果遇到了异常情况,则COM系统经过判断,会返回相应的错误值。常见的返回值有:
HRESULT值含义S_OK0x00000000成功S_FALSE0x00000001函数成功执行完成,但返回时出现错误E_INVALIDARG0x80070057参数有错误E_OUTOFMEMORY0x8007000E内存申请错误E_UNEXPECTED0x8000FFFF未知的异常E_NOTIMPL0x80004001未实现功能E_FAIL0x80004005没有详细说明的错误。一般需要取得 Rich Error 错误信息(注1)E_POINTER0x80004003无效的指针E_HANDLE0x80070006无效的句柄E_ABORT0x80004004终止操作E_ACCESSDENIED0x80070005访问被拒绝E_NOINTERFACE0x80004002不支持接口
图一、HRESULT 的结构
HRESULT 其实是一个双字节的值,其最高位(bit)如果是0表示成功,1表示错误。具体参见 MSDN 之"Structure of COM Error Codes"说明。我们在程序中如果需要判断返回值,则可以使用比较运算符号;switch开关语句;也可以使用VC提供的宏:
HRESULT hr = 调用组件函数; if( SUCCEEDED( hr ) ){...} // 如果成功 ...... if( FAILED( hr ) ){...} // 如果失败 ......
三、UNICODE
计算机发明后,为了在计算机中表示字符,人们制定了一种编码,叫ASCII码。ASCII码由一个字节中的7位(bit)表示,范围是0x00 - 0x7F 共128个字符。他们以为这128个数字就足够表示abcd....ABCD....1234 这些字符了。
咳......说英语的人就是“笨”!后来他们突然发现,如果需要按照表格方式打印这些字符的时候,缺少了“制表符”。于是又扩展了ASCII的定义,使用一个字节的全部8位(bit)来表示字符了,这就叫扩展ASCII码。范围是0x00 - 0xFF 共256个字符。
咳......说中文的人就是聪明!中国人利用连续2个扩展ASCII码的扩展区域(0xA0以后)来表示一个汉字,该方法的标准叫GB-2312。后来,日文、韩文、阿拉伯文、台湾繁体(BIG-5)......都使用类似的方法扩展了本地字符集的定义,现在统一称为 MBCS 字符集(多字节字符集)。这个方法是有缺陷的,因为各个国家地区定义的字符集有交集,因此使用GB-2312的软件,就不能在BIG-5的环境下运行(显示乱码),反之亦然。
咳......说英语的人终于变“聪明”一些了。为了把全世界人民所有的所有的文字符号都统一进行编码,于是制定了UNICODE标准字符集。UNICODE 使用2个字节表示一个字符(unsigned shor int、WCHAR、_wchar_t、OLECHAR)。这下终于好啦,全世界任何一个地区的软件,可以不用修改地就能在另一个地区运行了。虽然我用 IE 浏览日本网站,显示出我不认识的日文文字,但至少不会是乱码了。UNICODE 的范围是 0x0000 - 0xFFFF 共6万多个字符,其中光汉字就占用了4万多个。嘿嘿,中国人赚大发了:0)
在程序中使用各种字符集的方法:
const char * p = "Hello"; // 使用 ASCII 字符集 const char * p = "你好"; // 使用 MBCS 字符集,由于 MBCS 完全兼容 ASCII,多数情况下,我们并不严格区分他们 LPCSTR p = "Hello,你好"; // 意义同上 const WCHAR * p = L"Hello,你好"; // 使用 UNICODE 字符集 LPCOLESTR p = L"Hello,你好"; // 意义同上 // 如果预定义了_UNICODE,则表示使用UNICODE字符集;如果定义了_MBCS,则表示使用 MBCS const TCHAR * p = _T("Hello,你好"); LPCTSTR p = _T("Hello,你好"); // 意义同上
在上面的例子中,T是非常有意思的一个符号(TCHAR、LPCTSTR、LPTSTR、_T()、_TEXT()...),它表示使用一种中间类型,既不明确表示使用 MBCS,也不明确表示使用 UNICODE。那到底使用哪种字符集那?嘿嘿......编译的时候决定吧。设置条件编译的方式是:VC6中,"Project\Settings...\C/C++卡片 Preprocessor definitions" 中添加或修改 _MBCS、_UNICODE;VC.NET中,"项目\属性\配置属性\常规\字符集"然后用组合窗进行选择。使用 T 类型,是非常好的习惯,严重推荐!
四、BSTR
COM 中除了使用一些简单标准的数据类型外(注2),字符串类型需要特别重点地说明一下。还记得原则吗?COM 组件是运行在分布式环境中的。通俗地说,你不能直接把一个内存指针直接作为参数传递给COM函数。你想想,系统需要把这块内存的内容传递到“地球另一 边”的计算机上,因此,我至少需要知道你这块内存的尺寸吧?不然让我如何传递呀?传递多少字节呀?!而字符串又是非常常用的一种类型,因此 COM 设计者引入了 BASIC 中字符串类型的表示方式---BSTR。BSTR 其实是一个指针类型,它的内存结构是:(输入程序片段 BSTR p = ::SysAllocString(L"Hello,你好");断点执行,然后观察p的内存)
图二、BSTR 内存结构
BSTR 是一个指向 UNICODE 字符串的指针,且 BSTR 向前的4个字节中,使用DWORD保存着这个字符串的字节长度( 没有含字符串的结束符)。因此系统就能够正确处理并传送这个字符串到“地球另一 边”了。特别需要注意的是,由于BSTR的指针就是指向 UNICODE 串,因此 BSTR 和 LPOLESTR 可以在一定程度上混用,但一定要注意:
有函数 fun(LPCOLESTR lp),则你调用 BSTR p=...; fun(p); 正确
有函数 fun(const BSTR bstr),则你调用 LPCOLESTR p=...; fun(p); 错误!!!
有关 BSTR 的处理函数:
CString 函数
说明
AllocSysString()从 CString 得到 BSTRSetSysString()重新申请 BSTR 指针,并复制到 CString 中CComBSTR 函数
ATL 的 BSTR 包装类。在 atlbase.h 中定义
Append()、AppendBSTR()、AppendBytes()、ArrayToBSTR()、BSTRToArray()、AssignBSTR()、Attach()、Detach()、Copy()、CopyTo()、Empty()、Length()、ByteLength()、ReadFromStream()、WriteToStream()、LoadString()、ToLower()、ToUpper()运算符重载:!,!=,==,<,>,&,+=,+,=,BSTR太多了,但从函数名称不能看出其基本功能。详细资料,查看MSDN 吧。另外,左侧函数,有很多是 ATL 7.0 提供的,VC6.0 下所带的 ATL 3.0 不支持。
由于我们将来主要用 ATL 开发组件程序,因此使用 ATL 的 CComBSTR 为主。VC也提供了其它的包装类 _bstr_t。
五、各种字符串类型之间的转换
1、函数 WideCharToMultiByte(),转换 UNICODE 到 MBCS。使用范例:
LPCOLESTR lpw = L"Hello,你好"; size_t wLen = wcslen( lpw ) + 1; // 宽字符字符长度,+1表示包含字符串结束符 int aLen=WideCharToMultiByte( // 第一次调用,计算所需 MBCS 字符串字节长度CP_ACP,0,lpw, // 宽字符串指针wLen, // 字符长度NULL,0, // 参数0表示计算转换后的字符空间NULL,NULL); LPSTR lpa = new char [aLen]; WideCharToMultiByte(CP_ACP,0,lpw,wLen,lpa, // 转换后的字符串指针aLen, // 给出空间大小NULL,NULL); // 此时,lpa 中保存着转换后的 MBCS 字符串 ... ... ... ... delete [] lpa;
2、函数 MultiByteToWideChar(),转换 MBCS 到 UNICODE。使用范例:
LPCSTR lpa = "Hello,你好"; size_t aLen = strlen( lpa ) + 1; int wLen = MultiByteToWideChar(CP_ACP,0,lpa,aLen,NULL,0); LPOLESTR lpw = new WCHAR [wLen]; MultiByteToWideChar(CP_ACP,0,lpa,aLen,lpw,wLen); ... ... ... ... delete [] lpw;
3、使用 ATL 提供的转换宏。
上表中的宏函数,其实非常容易记忆:
使用范例:
#include <atlconv.h> void fun() { USES_CONVERSION; // 只需要调用一次,就可以在函数中进行多次转换 LPCTSTR lp = OLE2CT( L"Hello,你好") ); ... ... ... ... // 不用显式释放 lp 的内存,因为 // 由于 ATL 转换宏使用栈作为临时空间,函数结束后会自动释放栈空间。 }
使用 ATL 转换宏,由于不用释放临时空间,所以使用起来非常方便。但是考虑到栈空间的尺寸(VC 默认2M),使用时要注意几点:
1、只适合于进行短字符串的转换;
2、不要试图在一个次数比较多的循环体内进行转换;
3、不要试图对字符型文件内容进行转换,因为文件尺寸一般情况下是比较大的;
4、对情况 2 和 3,要使用 MultiByteToWideChar() 和 WideCharToMultiByte();
六、VARIANT
C++、BASIC、Java、Pascal、Script......计算机语言多种多样,而它们各自又都有自己的数据类型,COM 产生目的,其中之一就是要跨语言(注3)。而 VARIANT 数据类型就具有跨语言的特性,同时它可以表示(存储)任意类型的数据。从C语言的角度来讲,VARIANT 其实是一个结构,结构中用一个域(vt)表示------该变量到底表示的是什么类型数据,同时真正的数据则存贮在 union 空间中。结构的定义太长了(虽然长,但其实很简单)大家去看 MSDN 的描述吧,这里给出如何使用的简单示例:
学生:我想用 VARIANT 表示一个4字节长的整数,如何做?
老师:VARIANT v; v.vt=VT_I4; v.lVal=100;
学生:我想用 VARIANT 表示布尔值“真”,如何做?
老师:VARIANT v; v.vt=VT_BOOL; v.boolVal=VARIANT_TRUE;
学生:这么麻烦?我能不能 v.boolVal=true; 这样写?
老师:不可以!因为
所以如果你 v.boolVal=true 这样赋值,那么将来 if(VARIANT_TRUE==v.boolVal) 的时候会出问题(-1 != 1)。但是你注意观察,任何布尔类型的“假”都是0,因此作为一个好习惯,在做布尔判断的时候,不要和“真值”相比较,而要与“假值”做比较。
学生:谢谢老师,你太牛了。我对老师的敬仰如滔滔江水,连绵不绝......
学生:我想用 VARIANT 保存字符串,如何做?
老师:VARIANT v; v.vt=VT_BSTR; v.bstrVal=SysAllocString(L"Hello,你好");
学生:哦......我明白了。可是这么操作真够麻烦的,有没有简单一些的方法?
老师:有呀,你可以使用现成的包装类 CComVariant、COleVariant、_variant_t。比如上面三个问题就可以这样书写:CComVariant v1(100),v2(true),v3("Hello,你好"); 简单了吧?!(注4)
学生:老师,我再问最后一个问题,我如何用 VARIANT 保存一个数组?
老师:这个问题很复杂,我现在不能告诉你,我现在告诉你怕你印象不深......(注5)
学生:~!@#$%^&*()......晕!
七、小结
以上所介绍的内容,是基本功,必须熟练掌握。先到这里吧,休息一会儿......更多精彩内容,敬请关注《COM 组件设计与应用(四)》
注1:在后续的 ISupportErrorInfo 接口中介绍。
注2:常见的数据类型,请参考 IDL 文件的说明。(别着急,还没写那......嘿嘿)
注3:跨语言就是各种语言中都能使用COM组件。但啥时候能跨平台呢?
注4:CComVariant/COlevariant/_variant_t 请参看 MSDN。
注5:关于安全数组 SafeArray 的使用,在后续的文章中讨论。
posted @ 2011-08-20 20:53 Mr-Victor 阅读(60) 评论(0) 编辑
【转载】COM组件设计与应用(二)——GUID和接口
本文摘自:http://blog.vckbase.com/teacheryang/archive/2005/06/27/8884.html
一、前言
书接上回,话说在 doc(Word) 复合文件中,已经解决了保存 xls(Excel) 数据的问题了。那么,接下来又要解决另一个问题:当 WORD 程序读取复合文件,遇到了 xls 数据的时候,它该如何启动 Excel 呢?启动后,又如何让 Excel 自己去读入、解析、显示 xls 数据呢?
二、CLSID 概念
有一个非常简单的解决方案,那就是在对象数据的前面,保存有处理这个数据的程序名。(见下图左上)
图一、CLSID 的概念
这的确是一个简单的方法,但同时问题也很严重。在“张三”的计算机上,Excel 的路径是:"c:\office\Excel.exe",如果把这个 doc 文件复制到“李四”的计算机上使用,而“李四”的 Excel 的路径是:
"d:\Program files\Microsoft Office\Office\Excel.exe",完蛋了:-(
于是,微软想出了一个解决方案,那就是不使用直接的路径表示方法,而使用一个叫 CLSID(注1)的方式间接描述这些对象数据的处理程序路径。CLSID 其实就是一个号码,或者说是一个16字节的数。观察注册表(上图),在HKCR\CLSID\{......}主键下,LocalServer32(DLL组件使用InprocServer32) 中保存着程序路径名称。CLSID 的结构定义如下:
typedef struct _GUID {
DWORD Data1; // 随机数
WORD Data2; // 和时间相关
WORD Data3; // 和时间相关
BYTE Data4[8]; // 和网卡MAC相关
} GUID;
typedef GUID CLSID; // 组件ID
typedef GUID IID; // 接口ID
#define REFCLSID const CLSID &
// 常见的声明和赋值方法
CLSID CLSID_Excel = {0x00024500,0x0000,0x0000,{0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}};
struct __declspec(uuid("00024500-0000-0000-C000-000000000046")) CLSID_Excel;
class DECLSPEC_UUID("00024500-0000-0000-C000-000000000046") CLSID_Excel;
// 注册表中的表示方法
{00024500-0000-0000-C000-000000000046}
用一个号码间接表示程序名,的确是个 Good idea,实现了组件位置的透明性,并方便地扩展出 DCOM(远程组件)。但,但,但,但.....CLSID 有16个字节共128位二进制数,干吗用这么长的数字呀?遥想当年......我还在上幼儿园的时候,人们设计了 socket,用 TCP/IP 协议进行网络通讯。每个参与通讯的计算机都有一个4字节的 IP 表示编号地址,范围是 0,0,0,0 ~ 255,255,255,255 共42亿个地址。可是没想到啊,没想到,自从 Internet 选择了TCP/IP 协议后,42亿个地址就不够全世界的劳动人民分配啦。除了劳动人民,还有冰箱、彩电、电饭锅、手机、手提电脑......这些都需要连网呀。在办公室通过网络开启电饭锅给我焖饭,下班回家后就能吃现成的啦,多幸福呀?!(注:在我们家老婆是领导,所以是我做饭。咳......)
由于前车之鉴,微软这次设计 CLSID/IID 就使用了GUID概念的16个字节,这下好啦,全世界60亿人口,每个人每秒钟分配10亿个号码,那么需要分配1800亿年。反正等到地球没有了都不会使用完的:-)
三、产生 CLSID
1、如果使用开发环境编写组件程序,则IDE会自动帮你产生 CLSID;
2、你可以手工写 CLSID,但千万不要和人家已经生成的 CLSID 重复呀,所以严重地不推荐;(可是微软的CLSID都是手工写的,这叫“只许州官放火,不许百姓点灯”) ;
3、程序中,可以用函数 CoCreateGuid() 产生 CLSID;
4、使用工具产生 GUID(注2);
vc6.0版本运行:"vc目录\Common\Tools\GuidGen.exe"程序(你可以参照上回文章中介绍的方法,把这个工具程序加到开发环境中,方便调用)。vc.net版本,在菜单“工具\创建GUID”中,就可以执行了。
四、ProgID 概念
每一个COM组件都需要指定一个 CLSID,并且不能重名。它之所以使用16个字节,就是要从概率上保证重复是“不可能”的。但是,(世界上就怕“但是”二字)微软为了使用方便,也支持另一个字符串名称方式,叫 ProgID(注3)。见上图注册表的ProgID 子键内容(注4)。由于 CLSID 和 ProgID 其实是一个概念的两个不同的表示形式,所以我们在程序中可以随便使用任何一种。(有些人就是讨厌,说话不算数。明明 GUID 的目的就是禁止重复,但居然又允许使用 ProgID?!ProgID 是一个字符串的名字,重复的可能性就太大了呀。赶明儿我也写个程序,我打算这个程序的 ProgID 叫“Excel.Application”,嘿嘿)下面介绍一下 CLSID 和 ProgID 之间的转换方法和相关的函数:
五、接口(Interface)的来历
到此,我们已经知道了 CLSID 或 ProgID 唯一地表示一个组件服务程序,那么根据这些ID,就可以加载运行组件,并为客户端程序提供服务了。(启动组件程序的方法,会陆续介绍)。接下来先讨论如何调用组件提供的函数?-----接口。
作为客户端程序员,它希望或者说他要求:我的程序只写一次,然后不做任何修改就可以调用任意一个组件。举例来说:
你可以在 Word 中嵌入 Excel,也可以嵌入 Picture,也可以嵌入任何第三方发表的 ActiveX 文档......也就是说,连 Word 自己都不知道使用它的人将会在 doc 里面插入什么东东;
你可以在 HTML 文件中插入一个 ActiveX,也可以插入一个程序脚本Script,......你自己写的插件也可以插入到 IE 环境中。为了完成你的功能, 你绝对也不会去让微软修改IE吧?!
这个要求实在有点难度,Office 开发停滞了。说来话巧,一天老O(Office 项目的总工程师)和小B(VB 项目的总工程师)一起喝酒,老O向小B倾诉了他的烦恼:
老O:怎么能让我写的程序C,可以调用其它人写的程序S中的函数?(C表示客户程序,S表示提供服务的程序)
小B:你是不是喝糊涂了?让S作成 DLL,你去 LoadLibrary()、GetProcAddress()、...FreeLibrary()?!
老O:废话!要是这么简单就好了。问题是,连我都不知道这个S程序是干什么的?能干什么?我怎么调用呀?
小B:哦......这个比较高级,但我现在不能告诉你,因为我怕你印象不深。
老O:~!·#¥%……—*......
小B:是这样的,在VB中,我们制定了一个标准,这个标准允许任何一个VB开发者,把他自己写的某个功能的小程序放在VB的工具栏上,这样就好象他扩展了 VB 的功能一样。
老O:哦?就是那个叫什么 VBX 的滥玩意儿?
小B:我呸......别看 VBX 这个东西不起眼儿,的确我也没看上它。但你猜怎么着?现在有成千上万的 VB 程序爱好者把他们写的各式各样功能的 VBX 小程序,放到网上,让大家共享那。
老O:哦~~~,那你们的这个 VBX 标准是什么?
小B:嘿嘿......其实特简单,就是在 VBX 中必须实现7个函数,这7个函数名称和功能必须是:初始化、释放、显示、消息处理......,而至于它内部想干什么,我也管不着。我只是在需要的时候调用我需要的这7个函数。
老O:哦~~~,这样呀......对了,我现有个急事,我先走了。88,你付帐吧......
小B:喂!喂喂...... 走这么急干什么,钱包都掉了:-)
老O虽然丢了钱包,仍然兴奋地冲回办公室,他开始了思考......
1、我的程序C,要能调用任何人写的程序B。那么B必须要按照我事先的要求,提供我需要的函数F1(),F2(),F3(),K1(),K2()。
2、BASIC 是解释执行,因此它的函数不用考虑书写顺序,只要给出函数名,解释器就能找到。但我使用的是 C++呀......
3、C++编译后的代码中没有函数名,只有函数地址,因此我必须改进为用VTAB(虚函数表)表示函数入口:
图二、VTAB 的结构
4、还不够好,需要改进一下,因为所有的函数地址都放在一个表中会不灵活、不好修改、不易扩展。恩,有了!按照函数功能的类型进行分类:
图三、多个 VTAB 的结构
5、问题又来了,现在有2个 VTAB 虚函数表,那么怎么能够从一个表找到另一个表那?恩又有办法了,我要求你必须要实现一个函数,并且这个函数地址必须放在所有表的开头(表中的第一个函数指针),这个函数就叫 QueryInterface()吧,完成从一个表查找到另一个表的功能:(除了QueryInterface()函数,顺便也完成另外两个函数,叫 AddRef() 和 Release()。这两个函数的功能以后再说)
图四、COM 接口结构
6、为了以后描述方便,不再使用上图(图四)的方法了,而使用图五这样简洁的样式:
图五、COM 接口结构的简洁图示
六、接口(Interface)概念
1、函数是通过 VTAB 虚函数表提供其地址, 从另一个角度来看,不管用什么语言开发,编译器产生的代码都能生成这个表。这样就实现了组件的“二进制特性”轻松实现了组件的跨语言要求。
2、假设有一个指针型变量保存着 VTAB 的首地址,则这个变量就叫“接口指针”(注6), 变量命名的时候,习惯上加上"I"开头。另外为了区分不同的接口,每个接口 也都要有一个名字,该名字就和 CLSID 一样,使用 GUID 方式,叫 IID。
3、接口一经发表,就不能再修改了。不然就会出现向前兼容的问题。这个性质叫“接口不变性”。
4、组件中必须有3个函数,QueryInterface、AddRef、Release,它们3个函数也组成一个接口,叫"IUnknown"。(注7)
5、任何接口,其实都包含了 IUnknown 接口。随着你接触到更多的接口就会了更体会解到接口的另一个性质“继承性”。
6、在任何接口上,调用表中的第一个函数,其实就是调用 QueryInterface()函数,就得到你想要的另外一个接口指针。这个性质叫“接口的传递性”
7、C/C++语言中需要事先对函数声明,那么就 会要求组件也必须提供C语言的头文件。不行!为了能使COM具有跨语言的能力,决定不再为任何语言提供对应的函数接口声明,而是独立地提供一个叫类型库(TLB)的声明。每个语言的IDE环境自己去根据TLB生成自己语言需要的包装。这个性质叫“接口声明的独立性”(注8)
七、客户程序与组件之间的协商调用
回到我们的上一个话题,Word中嵌入一个组件,那么Word是如何协商使用这个组件的那?下面是容器和组件之间的一个模拟对话过程:
CoCreateInstance()生成对象,执行构造函数,执行初始化动作。2你有IUnknown接口吗?有,给你!3恩,太好了,那么你有IPersistStorage接口吗?(注9)
IUnknown::QueryInterface(IID_IPersistStorage...)没有!4真差劲,连这个都没有。那你有IPersistStreamInit接口吗?(注10)
IUnknown::QueryInterface(IID_IPersistStreamInit...)哈,这个有,给!5好,好,这还差不多。你现在给我初始化吧。
IPersistStreamInit::InitNew()OK,初始化完成了。6完成了?好!现在你读数据去吧。
IPersistStreamInit::Load()读完啦。我根据数据,已经在窗口中显示出来了。7好,现在咱们各自处理用户的鼠标、键盘消息吧............8哎呀!用户要保存退出程序了。你的数据被用户修改了吗?
IPersistStreamInit::IsDirty()改了,用户已经修改啦。9那好,那么用户修改后,你的数据需要多大的存储空间呀?
IPersistStreamInit::GetSizeMax()恩,我算算呀......好了,总共需要500KB。10晕,你这么个小玩意居然占用这么大空间?!......好了,你可以存了。
IPersistStreamInit::Save()谢谢,我已经存好了。11恩。拜拜了您那。(注11)
IPersistStreamInit::Release();IUnknown::Release()执行析构函数,删除对象。12我自己也该退出了......
PostQuitMessage()
容器(或者说客户端)就是这样和组件进行对话,协商调用的。如果组件甲实现了 IA 接口,那么容器就会使用它,如果组件乙没有提供 IA 接口,但是它提供了 IB 接口,那么容器就会调用 IB 接口的函数......如此,容器程序根本就不需要知道组件到底是干什么的,组件到底是用什么语言开发的,组件的磁盘位置到底在哪里,它都可以正常运行。太奇妙了!太精彩了!怎一个“爽”字了得!
八、小结
第二回中,介绍了两个非常重要的概念:CLSID 和 Interface。由于全篇都是概念描述而没有示例程序相配合,可能读者的理解还不太深入、不彻底。别着急,我们马上就要进入到组件程序设计阶段了,到那个时候,你根据具体的程序代码,再回过头来再次阅读本回文章,没读懂?哦......再读!慢慢地您老人家就懂了:-)
留作业啦......
1、IDispatch 接口的 IID 是多少?(哎~~~ 笨笨,在源程序中,用鼠标右键执行Go to definition 呀)
2、IPicture 接口有几个函数?功能是什么?(别玩了!你多大了?想不想在程序中显示 JPG 图像呀,看 MSDN 去)
想知道为什么COM函数总是返回 HRESULT 吗?想知道如何使用 BSTR、VARIANT 吗?想知道 COM 中应该如何使用内存吗?想知道如何使用 UNICODE 吗?......恩~~~,我现在不能告诉你,我现在告诉你,怕你印象不深!且听下回分解......
注1:CLSID = Class ID 上回书已经介绍了把CLSID写入复合文件的函数:WriteClassStg()、IStorage::SetClass()。
注2:GUID 全局唯一标示符,CLSID/IID 其实是借用了GUID的概念。
注3:ProgID = Program ID,等价于 CLSID, 是用字符串表示的。
注4:注册表子键 ProgID 和 VersionIndependentProgID 分别表示真正的 ProgID 和版本无关的 ProgID。比如在我计算机上安装的 Excel,它的 ProgID = "Excel.Application.9",而 VersionIndependentProgID = "Excel.Application"。
注5:COM 组件的内存管理,见后续的文章。
注6:Interface = 接口,以前微软不叫它接口,而叫协议Protocol。其实我 到认为这个词更贴切一些。
注7:IUnknown 这个名字起的好,居然叫“我不知道”:-),它的 IID 叫 IID_IUnknown,如果用注册表样式表示,那么它的值是{00000000-0000-0000-C000-000000000046}。
注8:TLB是由一个描述接口的文件 IDL 经过编译产生的。IDL 的说明,见后续的文章吧。
注9:IPersistStorage 是用复合文件的存储(Storage)功能来保存/读取数据用的一个接口。
注10:IPersistStreamInit 是用复合文件的流(Stream)功能来保存/读取数据用的一个接口。
注11:拜拜了您那 = 英语北京话,再见。
posted @ 2011-08-20 20:46 Mr-Victor 阅读(110) 评论(0) 编辑
【转载】COM组件设计与应用(一)——起源及复合文件
本文摘自:http://blog.vckbase.com/teacheryang/archive/2005/06/27/8883.html
一、前言
公元一九九五年某个夜黑风高的晚上,我的一位老师跟我说:“小杨呀,以后写程序就和搭积木一样啦。你赶快学习一些OLE的技术吧......”,当时我心里就寻思 :“开什么玩笑?搭积木方式写程序?再过100年吧......”,但作为一名听话的好学生,我开始在书店里“踅摸”(注1)有关OLE的书籍(注2)。功夫不负有心人,终于买到了我的第一本COM书《OLE2 高级编程技术》,这本800多页的大布头花费了我1/5的月工资呀......于是开始日夜耕读.....
功夫不负有心人,我坚持读完了全部著作,感想是:这本书,在说什么呐?
功夫不负有心人,我又读完了一遍大布头,感想是:咳~~~,没懂!
功夫不负有心人,我再,我再,我再读 ... 感想是:哦~~~,读懂了一点点啦,哈哈哈。
...... ......
功夫不负有心人,我终于,我终于懂了。
800页的书对现在的我来说,其实也就10几页有用。到这时候才体会出什么叫“书越读越薄”的道理了。到后来,能买到的书也多了,上网也更方便更便宜了......
为了让VCKBASE上的朋友,不再经历我曾经的痛苦、不再重蹈我“无头苍蝇”般探索的艰辛、为了VCKBASE的蓬勃发展、为了中国软件事业的腾飞(糟糕,吹的太也高了)......我打算节约一些在 BBS 上赚分的时间,写个系列论文,就叫“COM组件设计与应用”吧。今天是第一部分——起源。
二、文件的存储
传说350年前,牛顿被苹果砸到了头,于是发现了万有引力。但到了二十一世纪的现在,任何一个技术的发明和发展,已经不再依靠圣人灵光的一闪。技术的进步转而是被社会的需求、商业的利益、竞争的压力、行业的渗透等推动的。微软在Windows平台上的组件技术也不例外,它的发明,有其必然因素。什么是这个因素那?答案是——文件的存储。
打开记事本程序,输入了一篇文章后,保存。——这样的文件叫“非结构化文件”;
打开电子表格程序,输入一个班的学生姓名和考试成绩,保存。——这样的文件叫“标准结构化文件”;
在我们写的程序中,需要把特定的数据按照一定的结构和顺序写到文件中保存。——这样的文件叫“自定义结构化文件”;(比如 *.bmp 文件)
以上三种类型的文件,大家都见的多了。那么文件存储就依靠上述的方式能满足所有的应用需求吗?恩~~~,至少从计算机发明后的50多年来,一直是够用的了。嘿嘿,下面看看商业利益的推动作用,对文件 的存储形式产生了什么变化吧。30岁以上的朋友,我估计以前都使用过以下几个著名的软件:WordStar(独霸DOS下的英文编辑软件),WPS(裘伯君写的中文编辑软件,据说当年的市场占有率高达90%,各种计算机培训班的必修课程),LOTUS-123(莲花公司出品的电子表格软件)......
微软在成功地推出 Windows 3.1 后,开始垂涎桌面办公自动化软件领域。微软的 OFFICE 开发部门,各小组分别独立地开发了 WORD 和 EXCEL 等软件,并采用“自定义结构”方式,对文件进行存储。在激烈的市场竞争下,为了打败竞争对手,微软自然地产生了一个念头------如果我能在 WORD 程序中嵌入 EXCEL,那么用户在购买了我 WORD 软件的情况下,不就没有必要再买 LOTUS-123 了吗?!“恶毒”(中国微软的同志们看到了这个词,不要激动,我是加了引号的呀)的计划产生后,他们开始了实施工作,这就是 COM 的前身 OLE 的起源(注3)。但立刻就遇到了一个严重的技术问题:需要把 WORD 产生的 DOC 文件和 EXCEL 产生的 XLS 文件保存在一起。
方案
优点
缺点
建立一个子目录,把 DOC、XLS 存储在这同一个子目录中。数据隔离性好,WORD 不用了解 EXCEL 的存储结构;容易扩展。结构太松散,容易造成数据的损坏或丢失。不易携带。修改文件存储结构,在DOC结构基础上扩展出包容 XLS 的结构。结构紧密,容易携带和统一管理。WORD 的开发人员需要通晓 EXCEL 的存储格式;缺少扩展性,总不能新加一个类型就扩展一下结构吧?!
以上两个方案,都有严重的缺陷,怎么解决那?如果能有一个新方案,能够合并前两个方案的优点,消灭缺点,该多好呀......微软是作磁盘操作系统起家的,于是很自然地他们提出了一个非常完美的设计方案,那就是把磁盘文件的管理方式移植到文件中了------复合文件,俗称“文件中的文件系统”。连微软当年都没有想到,就这么一个简单的想法,居然最后就演变出了 COM 组件程序设计的方法。可以说,复合文件是 COM 的基石。下图是磁盘文件组织方式与复合文件组织方式的类比图:
图一、左侧表示一个磁盘下的文件组织方式,右侧表示一个复合文件内部的数据组织方式。
三、复合文件的特点
1、复合文件的内部是使用指针构造的一棵树进行管理的。编写程序的时候要注意,由于使用的是单向指针,因此当做定位操作的时候,向后定位比向前定位要快;
2、复合文件中的“流对象”,是真正保存数据的空间。它的存储单位为512字节。也就是说,即使你在流中只保存了一个字节的数据,它也要占据512字节的文件空间。啊~~~,这也太浪费了呀?不浪费!因为文件保存在磁盘上,即使一个字节也还要占用一个“簇”的空间那;
3、不同的进程,或同一个进程的不同线程可以同时访问一个复合文件的不同部分而互不干扰;
4、大家都有这样的体会,当需要往一个文件中插入一个字节的话,需要对整个文件进行操作,非常烦琐并且效率低下。而复合文件则提供了非常方便的“增量访问”能力;
5、当频繁地删除文件,复制文件后,磁盘空间会变的很零碎,需要使用磁盘整理工具进行重新整合。和磁盘管理非常相似,复合文件也会产生这个问题,在适当的时候也需要整理,但比较简单,只要调用一个函数就可以完成了。
四、浏览复合文件
VC6.0 附带了一个工具软件“复合文件浏览器”,文件名是“vc目录\Common\Tools\DFView.exe”。为了方便使用该程序,可以把它加到工具(tools)菜单中。方法是:Tools\Customize...\Tools卡片中增加新的项目。运行 DFView.exe,就可以打开一个复合文件进行观察了(注4)。但奇怪的是,在 Microsoft Visual Studio .NET 2003 中,我反而找不到这个工具程序了,汗!不过这恰好提供给大家一个练习的机会,在你阅读完本篇文章并掌握了编程方法后,自己写一个“复合文件浏览编辑器”程序,又练手了,还有实用的价值。
五、复合文件函数
复合文件的函数和磁盘目录文件的操作非常类似。所有这些函数,被分为3种类型:WIN API 全局函数,存储 IStorage 接口函数,流 IStream 接口函数。什么是接口?什么是接口函数?以后的文章中再陆续介绍,这里大家只要把“接口”看成是完成一组相关操作功能的函数集合就可以了。
WIN API 函数
功能说明
StgCreateDocfile()建立一个复合文件,得到根存储对象StgOpenStorage()打开一个复合文件,得到根存储对象StgIsStorageFile()判断一个文件是否是复合文件
IStorage 函数
功能说明
CreateStorage()在当前存储中建立新存储,得到子存储对象CreateStream()在当前存储中建立新流,得到流对象OpenStorage()打开子存储,得到子存储对象OpenStream()打开流,得到流对象CopyTo()复制存储下的所有对象到目标存储中,该函数可以实现“整理文件,释放碎片空间”的功能MoveElementTo()移动对象到目标存储中DestoryElement()删除对象RenameElement()重命名对象EnumElements()枚举当前存储中所有的对象SetElementTimes()修改对象的时间SetClass()在当前存储中建立一个特殊的流对象,用来保存CLSID(注5)Stat()取得当前存储中的系统信息Release()关闭存储对象IStream 函数
功能说明
Read()从流中读取数据Write()向流中写入数据Seek()定位读写位置SetSize()设置流尺寸。如果预先知道大小,那么先调用这个函数,可以提高性能CopyTo()复制流数据到另一个流对象中Stat()取得当前流中的系统信息Clone()克隆一个流对象,方便程序中的不同模块操作同一个流对象Release()关闭流对象 WIN API 补充函数功能说明WriteClassStg()写CLSID到存储中,同IStorage::SetClass()ReadClassStg()读出WriteClassStg()写入的CLSID,相当于简化调用IStorage::Stat()WriteClassStm()写CLSID到流的开始位置ReadClassStm()读出WriteClassStm()写入的CLSIDWriteFmtUserTypeStg()写入用户指定的剪贴板格式和名称到存储中ReadFmtUserTypeStg()读出WriteFmtUserTypeStg()写入的信息。方便应用程序快速判断是否是它需要的格式数据。CreateStreamOnHGlobal()内存句柄 HGLOBAL 转换为流对象GetHGlobalFromStream()取得CreateStreamOnHGlobal()调用中使用的内存句柄 为了让大家快速地浏览和掌握基本方法,上面所列表的函数并不是全部,我省略了“事务”函数和未实现函数部分。更全面的介绍,请阅读 MSDN。
下面程序片段,演示了一些基本函数功能和调用方法。
示例一:建立一个复合文件,并在其下建立一个子存储,在该子存储中再建立一个流,写入数据。
void SampleCreateDoc()
{
::CoInitialize(NULL); // COM 初始化
// 如果是MFC程序,可以使用AfxOleInit()替代
HRESULT hr; // 函数执行返回值
IStorage *pStg = NULL; // 根存储接口指针
IStorage *pSub = NULL; // 子存储接口指针
IStream *pStm = NULL; // 流接口指针
hr = ::StgCreateDocfile( // 建立复合文件
L"c:\\a.stg", // 文件名称
STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE, // 打开方式
0, // 保留参数
&pStg); // 取得根存储接口指针
ASSERT( SUCCEEDED(hr) ); // 为了突出重点,简化程序结构,所以使用了断言。
// 在实际的程序中则要使用条件判断和异常处理
hr = pStg->CreateStorage( // 建立子存储
L"SubStg", // 子存储名称
STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,
0,0,
&pSub); // 取得子存储接口指针
ASSERT( SUCCEEDED(hr) );
hr = pSub->CreateStream( // 建立流
L"Stm", // 流名称
STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,
0,0,
&pStm); // 取得流接口指针
ASSERT( SUCCEEDED(hr) );
hr = pStm->Write( // 向流中写入数据
"Hello", // 数据地址
5, // 字节长度(注意,没有写入字符串结尾的\0)
NULL); // 不需要得到实际写入的字节长度
ASSERT( SUCCEEDED(hr) );
if( pStm ) pStm->Release(); // 释放流指针
if( pSub ) pSub->Release(); // 释放子存储指针
if( pStg ) pStg->Release(); // 释放根存储指针
::CoUninitialize() // COM 释放
// 如果使用 AfxOleInit(),则不调用该函数
}
图二、运行示例程序一后,使用 DFView.exe 打开观察复合文件的效果图
示例二:打开一个复合文件,枚举其根存储下的所有对象。
#include // ANSI、MBCS、UNICODE 转换
void SampleEnum()
{
// 假设你已经做过 COM 初始化了
LPCTSTR lpFileName = _T( "c:\\a.stg" );
HRESULT hr;
IStorage *pStg = NULL;
USES_CONVERSION; // (注6)
LPCOLESTR lpwFileName = T2COLE( lpFileName ); // 转换T类型为宽字符
hr = ::StgIsStorageFile( lpwFileName ); // 是复合文件吗?
if( FAILED(hr) )
return;
hr = ::StgOpenStorage( // 打开复合文件
lpwFileName, // 文件名称
NULL,
STGM_READ | STGM_SHARE_DENY_WRITE,
0,
0,
&pStg); // 得到根存储接口指针
IEnumSTATSTG *pEnum=NULL; // 枚举器
hr = pStg->EnumElements( 0, NULL, 0, &pEnum );
ASSERT( SUCCEEDED(hr) );
STATSTG statstg;
while( NOERROR == pEnum->Next( 1, &statstg, NULL) )
{
// statstg.type 保存着对象类型 STGTY_STREAM 或 STGTY_STORAGE
// statstg.pwcsName 保存着对象名称
// ...... 还有时间,长度等很多信息。请查看 MSDN
::CoTaskMemFree( statstg.pwcsName ); // 释放名称所使用的内存(注6)
}
if( pEnum ) pEnum->Release();
if( pStg ) pStg->Release();
}
六、小结
复合文件,结构化存储,是微软组件思想的起源,在此基础上继续发展出了持续性、命名、ActiveX、对象嵌入、现场激活......一系列的新技术、新概念。因此理解和掌握 复合文件是非常重要的,即使在你的程序中并没有全面使用组件技术,复合文件技术也是可以单独被应用的。祝大家学习快乐,为社会主义软件事业而奋斗:-)
留作业啦......
作业1:写个小应用程序,从 MSWORD 的 doc 文件中,提取出附加信息(作者、公司......)。
作业2:写个全功能的“复合文件浏览编辑器”。
注1:踅摸(xuemo),动词,北方方言,寻找搜索的意思。
注2:问:为什么不上网查资料学习?
答:开什么国际玩笑!在那遥远的1995年代,我的500块工资,不吃不喝正好够上100小时的Internet网。
注3:OLE,对象的连接与嵌入。
注4:可以用 DFView.exe 打开 MSWORD 的 DOC 文件进行复合文件的浏览。但是该程序并没有实现国际化,不能打开中文文件名的复合文件,因此需要改名后才能浏览。
注5:CLSID,在后续的文章中介绍。
注6:关于 COM 中内存使用的问题,在后续的文章中介绍。
posted @ 2011-08-20 19:55 Mr-Victor 阅读(123) 评论(0) 编辑
通过ODBC API实现对数据库的访问
使用ODBC所需要的文件
1、sql.h : 包含基本的ODBC API的定义。
2、sqlext.h :包含扩展的ODBC的定义。
3、odbc32.lib :库文件。
例如:
#include <sqlext.h>
#include <sql.h>
#include <odbcinst.h>
#pragma comment(lib, "odbccp32.lib")
#pragma comment(lib, "odbc32.lib")
一、配置ODBC数据源
配置ODBC数据源可以通过手动配置 和 程序自动配置 两种方式来实现。
第一种:手动配置(不作说明)
第二种:程序自动配置
SQLRETURN retcode;
retcode = SQLConfigDataSource(NULL,ODBC_ADD_DSN,"Microsoft Access Driver (*.mdb, *.accdb\0","DSN=Victor\0DBQ=D:\\MyDataBase\\Victor.accdb\0DEFAULTDIR=D:\\MyDataBase\0");
if(!retcode)
{
AfxMessageBox("配置ODBC数据源失败!");
return;
}
ODBC API提供了动态创建数据源的函数SQLConfig DataSource。该函数的原型如下:
BOOL SQLConfigDataSource(HWND hwndParent, WORD fRequest, LPCSTR lpszDriver, LPCSTR lpszAttributes);
参数说明如下:
1、参数hwndParent用于指定父窗口句柄,在不需要创建数据源对话框时,可以将该参数指定为NULL。
2、参数fRequest用于指定函数的操作内容,取值如下:
ODBC_ADD_DSN: 加入一个新的用户数据源;
ODBC_CONFIG_DSN:修改一个存在的用户数据源;
ODBC_REMOVE_DSN:除一个存在的用户数据源;
ODBC_ADD_SYS_DSN:增加一个新的系统数据源;
ODBC_CONFIG_SYS_DSN:配置或者修改一个存在的系统数据源;
ODBC_REMOVE_SYS_DSN:删除一个存在的系统数据源;
ODBC_REMOVE_DEFAULT_DSN:删除省缺的数据源说明部分。
3、参数lpszDriver用于指定ODBC数据源的驱动
例如,为了指定Access数据源,该参数应赋以字符串"Microsoft Access Driver (*.mdb, *.accdb)\0";对SQL SERVER数据源,则应赋以字符串"SQL Server"。
4、参数lpszAttributes用于指定ODBC数据源属性。例如:
4.1 对Access数据源:
"DSN=Victor\0DBQ=D:\\MyDataBase\\Victor.accdb\0DEFAULTDIR=D:\\MyDataBase\0"
说明:该字符串指定数据源名称(DNS)为Victor;数据库文件(DBQ)为D:\\MyDataBase\\Victor.accdb;缺省数据库文件路径(DEFAULTDIR)为D:\\MyDataBase。
4.2 对SQL SERVER数据源:
"DSN=Victor\0SERVER=MYET\0 DATABASE=Image"
说明:该字符串指定数据源名称(DSN)为MYIMAGE;SQLSERVER 数据库服务器名(SERVER)为MYET;数据库名称(DATABASE)为Image。
二、分配ODBC环境并设置ODBC环境属性
对于任何ODBC应用程序来说,第一步的工作是装载驱动程序管理器,然后初始化ODBC环境,分配环境句柄。ODBC驱动程序管理器使用该环境句柄跟踪每一个ODBC连接及其状态。
//分配环境句柄
SQLHENV henv;
retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
if(!(retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO))
{
AfxMessageBox("分配环境句柄失败!");
return;
}
执行该调用语句后,驱动程序分配一个结构,该结构中存放环境信息,然后返回对应于该环境的环境句柄。
然后ODBC应用程序必须调用SQLSetEnvAttr函数,这个函数注册将要使用的ODBC API版本。
//设置ODBC环境属性
retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3,0);
if(!(retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO))
{
SQLFreeHandle(SQL_HANDLE_ENV, henv);
AfxMessageBox("设置ODBC环境属性失败!");
return;
}
三、分配连接句柄
分配环境句柄后,在建立至数据源的连接之前,必须分配一个连接句柄,每一个到数据源的连接对应于一个连接句柄。
//分配连接句柄
SQLHDBC hdbc;
retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
if(!(retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO))
{
SQLFreeHandle(SQL_HANDLE_ENV, henv);
AfxMessageBox("分配连接句柄失败!");
return;
}
四、连接数据源
当连接句柄分配完成后,可以设置连接属性,所有的连接属性都有缺省值,但是可以通过调用函数SQLSetConnectAttr()来设置连接属性。用函数SQLGetConnectAttr(0获取这些连接属性。
//设置最长登录时间为5s
SQLSetConnectAttr(hdbc, SQL_LOGIN_TIMEOUT, (SQLPOINTER)5, 0);
完成对属性的设置之后,就可以建立到数据源的连接了。对于不同的程序和用户接口,可以用不同的函数建立连接:SQLConnect、SQLDriverConnect、SQLBrowseConnect.
SQLConnect()提供了最为直接的程序控制方式,只要提供数据源名称、用户ID和口令就可以进行连接了。
//连接到数据源
retcode = SQLConnect(hdbc, (SQLCHAR*)"Victor", SQL_NTS, NULL , 0, NULL, 0);
if(!(retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO))
{
SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
SQLFreeHandle(SQL_HANDLE_ENV, henv);
AfxMessageBox("连接数据源失败!");
return;
}
五、分配语句句柄
最后一段ODBC初始化代码是调用SQLAllocHandle函数创建一个语句句柄。该语句句柄用于处理SQL请求。
//分配语句句柄
SQLHSTMT hstmt;
retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
if(!(retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO))
{
SQLDisconnect(hdbc);
SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
SQLFreeHandle(SQL_HANDLE_ENV, henv);
AfxMessageBox("分配语句句柄失败!");
return;
}
posted @ 2011-08-20 18:24 Mr-Victor 阅读(351) 评论(0) 编辑
金山词霸查词API
接口:
http://dict-co.iciba.com/api/dictionary.php?w=$userSearch
说明:
$userSearch 为用户要查的词或短句,请使用url转码。
返回数据的格式为xml
以词good为例
输入:http://dict-co.iciba.com/api/dictionary.php?w=good
输出:
<?xml version="1.0" encoding="UTF-8"?>
<dict num="219" id="219" name="219">
<key>good</key>
<ps>ɡʊd</ps>
<pron>http://res.iciba.com/resource/amp3/7/5/755f85c2723bb39381c7379a604160d8.mp3</pron>
<pos>n.</pos>
<acceptation>好, 好事, 慷慨的行为, 好处, 利益</acceptation>
<pos>adj.</pos>
<acceptation>优良的, 上等的, 虔诚的, 愉快的, 慈善的, 好心的, 有益的</acceptation>
<sent>
<orig>Good - bye! happy landing !</orig>
<pron>http://res.iciba.com/resource/phrase_mp3/0/2/0247f3dd84906223785fddb18353bafe.mp3</pron>
<trans>再见!一路平安(坐飞机轮船用)</trans>
</sent>
<sent>
<orig>Good - humoured mockery seems to be a proper rod but great caution and skill are necessary in the use of it or you may happen to catch a tartar.</orig>
<pron>http://res.iciba.com/resource/phrase_mp3/a/f/af4d426552127de5e0989f12574864ef.mp3</pron>
<trans>善意的嘲笑看起来是一根合适的棍棒,但运用时必须非常谨慎,并注意技巧,否则你可能骑虎难下。</trans>
</sent>
<sent>
<orig>Good - bye, I 'll contact you again.</orig>
<pron>http://res.iciba.com/resource/phrase_mp3/3/a/3a9eb45fa45f2ea725176aaa5d3646ac.mp3</pron>
<trans>再见,我会再同您联系。</trans>
</sent>
<sent>
<orig>Good - bye and thank you for all you have done for me.</orig>
<pron>http://res.iciba.com/resource/phrase_mp3/7/9/79edb6793892f9199772154d56d17360.mp3</pron>
<trans>再见了,谢谢你为我做的一切。</trans>
</sent>
<sent>
<orig>Good - bye, Xiao Lin.</orig>
<pron>http://res.iciba.com/resource/phrase_mp3/8/3/83f344600d15cbf1ed5cb005db32f994.mp3</pron>
<trans>再见,小林。</trans>
</sent></dict>
Xml标签说明:
返回xml以<dict>开始以</dict>结束
Key 用户查询内容
Ps 音标
Pron 发音
Pos 词性
Acceptation 解释
Sent 短句
Orig 短句内容
Trans 翻译
注意:
多个词性会有多个解释
当前版本的短句会返回5条记录
posted @ 2011-08-20 18:22 Mr-Victor 阅读(395) 评论(0) 编辑
CListCtrl使用技巧大全(VC)
以下未经说明,listctrl默认view 风格为report
--------------------------------------------------------------------------------
1. CListCtrl 风格
LVS_ICON: 为每个item显示大图标
LVS_SMALLICON: 为每个item显示小图标
LVS_LIST: 显示一列带有小图标的item
LVS_REPORT: 显示item详细资料
直观的理解:windows资源管理器,“查看”标签下的“大图标,小图标,列表,详细资料”
--------------------------------------------------------------------------------
2. 设置listctrl 风格及扩展风格
LONG lStyle;
lStyle = GetWindowLong(m_list.m_hWnd, GWL_STYLE);//获取当前窗口style
lStyle &= ~LVS_TYPEMASK; //清除显示方式位
lStyle |= LVS_REPORT; //设置style
SetWindowLong(m_list.m_hWnd, GWL_STYLE, lStyle);//设置style
DWORD dwStyle = m_list.GetExtendedStyle();
dwStyle |= LVS_EX_FULLROWSELECT;//选中某行使整行高亮(只适用与report风格的listctrl)
dwStyle |= LVS_EX_GRIDLINES;//网格线(只适用与report风格的listctrl)
dwStyle |= LVS_EX_CHECKBOXES;//item前生成checkbox控件
m_list.SetExtendedStyle(dwStyle); //设置扩展风格
注1:listview的style请查阅msdn
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wceshellui5/html/wce50lrflistviewstyles.asp
注2:DWORD SetExtendedStyle(DWORD dwNewStyle);
函数功能:设置CListCtrl的扩展样式
dwNewStyle指定的扩展样式(部分):
LVS_EX_GRIDLINES //绘制表格
LVS_EX_SUBITEMIMAGES//子项目图标列表
LVS_EX_CHECKBOXES //带复选框
LVS_EX_TRACKSELECT //自动换行
LVS_EX_HEADERDRAGDROP//报表头可以拖拽
LVS_EX_FULLROWSELECT //选择整行
LVS_EX_ONECLICKACTIVATE//单击激活
LVS_EX_TWOCLICKACTIVATE//双击激活
LVS_EX_FLATSB//扁平滚动条
LVS_EX_REGIONAL
LVS_EX_INFOTIP//将提示信息窗口的消息通知父窗口
LVS_EX_UNDERLINEHOT
LVS_EX_UNDERLINECOLD
LVS_EX_MULTIWORKAREAS//多工作区
--------------------------------------------------------------------------------
3. 插入数据
m_list.InsertColumn( 0, "ID", LVCFMT_LEFT, 40 );//插入列
m_list.InsertColumn( 1, "NAME", LVCFMT_LEFT, 50 );
int nRow = m_list.InsertItem(0, “11”);//插入行
m_list.SetItemText(nRow, 1, “jacky”);//设置数据
--------------------------------------------------------------------------------
4. 一直选中item
选中style中的Show selection always,或者在上面第2点中设置LVS_SHOWSELALWAYS
--------------------------------------------------------------------------------
5. 选中和取消选中一行
int nIndex = 0;
//选中
m_list.SetItemState(nIndex, LVIS_SELECTED|LVIS_FOCUSED, LVIS_SELECTED|LVIS_FOCUSED);
//取消选中
m_list.SetItemState(nIndex, 0, LVIS_SELECTED|LVIS_FOCUSED);
--------------------------------------------------------------------------------
6. 得到listctrl中所有行的checkbox的状态
m_list.SetExtendedStyle(LVS_EX_CHECKBOXES);
CString str;
for(int i=0; i<m_list.GetItemCount(); i++)
{
if( m_list.GetItemState(i, LVIS_SELECTED) == LVIS_SELECTED || m_list.GetCheck(i))
{
str.Format(_T("第%d行的checkbox为选中状态"), i);
AfxMessageBox(str);
}
}
--------------------------------------------------------------------------------
7. 得到listctrl中所有选中行的序号
方法一:
CString str;
for(int i=0; i<m_list.GetItemCount(); i++)
{
if( m_list.GetItemState(i, LVIS_SELECTED) == LVIS_SELECTED )
{
str.Format(_T("选中了第%d行"), i);
AfxMessageBox(str);
}
}
方法二:
POSITION pos = m_list.GetFirstSelectedItemPosition();
if (pos == NULL)
TRACE0("No items were selected!\n");
else
{
while (pos)
{
int nItem = m_list.GetNextSelectedItem(pos);
TRACE1("Item %d was selected!\n", nItem);
// you could do your own processing on nItem here
}
}
--------------------------------------------------------------------------------
8. 得到item的信息
TCHAR szBuf[1024];
LVITEM lvi;
lvi.iItem = nItemIndex;
lvi.iSubItem = 0;
lvi.mask = LVIF_TEXT;
lvi.pszText = szBuf;
lvi.cchTextMax = 1024;
m_list.GetItem(&lvi);
关于得到设置item的状态,还可以参考msdn文章
Q173242: Use Masks to Set/Get Item States in CListCtrl
http://support.microsoft.com/kb/173242/en-us
--------------------------------------------------------------------------------
9. 得到listctrl的所有列的header字符串内容
LVCOLUMN lvcol;
char str[256];
int nColNum;
CString strColumnName[4];//假如有4列
nColNum = 0;
lvcol.mask = LVCF_TEXT;
lvcol.pszText = str;
lvcol.cchTextMax = 256;
while(m_list.GetColumn(nColNum, &lvcol))
{
strColumnName[nColNum] = lvcol.pszText;
nColNum++;
}
--------------------------------------------------------------------------------
10. 使listctrl中一项可见,即滚动滚动条
m_list.EnsureVisible(i, FALSE);
--------------------------------------------------------------------------------
11. 得到listctrl列数
int nHeadNum = m_list.GetHeaderCtrl()->GetItemCount();
--------------------------------------------------------------------------------
12. 删除所有列
方法一:
while (m_list.DeleteColumn (0))
因为你删除了第一列后,后面的列会依次向上移动。
方法二:
int nColumns = 4;
for (int i=nColumns-1; i>=0; i--)
m_list.DeleteColumn (i);
--------------------------------------------------------------------------------
13. 得到单击的listctrl的行列号
添加listctrl控件的NM_CLICK消息相应函数
void CXXXDlg::OnClickList1(NMHDR* pNMHDR, LRESULT* pResult)
{
// 方法一:
DWORD dwPos = GetMessagePos();
CPoint point( LOWORD(dwPos), HIWORD(dwPos) );
m_list.ScreenToClient(&point);
LVHITTESTINFO lvinfo;
lvinfo.pt = point;
lvinfo.flags = LVHT_ABOVE;
int nItem = m_list.SubItemHitTest(&lvinfo);
if(nItem != -1)
{
CString strtemp;
strtemp.Format("单击的是第%d行第%d列", lvinfo.iItem, lvinfo.iSubItem);
AfxMessageBox(strtemp);
}
// 方法二:
NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
if(pNMListView->iItem != -1)
{
CString strtemp;
strtemp.Format("单击的是第%d行第%d列",
pNMListView->iItem, pNMListView->iSubItem);
AfxMessageBox(strtemp);
}
*pResult = 0;
}
--------------------------------------------------------------------------------
14. 判断是否点击在listctrl的checkbox上
添加listctrl控件的NM_CLICK消息相应函数
void CXXXDlg::OnClickList1(NMHDR* pNMHDR, LRESULT* pResult)
{
DWORD dwPos = GetMessagePos();
CPoint point( LOWORD(dwPos), HIWORD(dwPos) );
m_list.ScreenToClient(&point);
LVHITTESTINFO lvinfo;
lvinfo.pt = point;
lvinfo.flags = LVHT_ABOVE;
UINT nFlag;
int nItem = m_list.HitTest(point, &nFlag);
//判断是否点在checkbox上
if(nFlag == LVHT_ONITEMSTATEICON)
{
AfxMessageBox("点在listctrl的checkbox上");
}
*pResult = 0;
}
--------------------------------------------------------------------------------
15. 右键点击listctrl的item弹出菜单
添加listctrl控件的NM_RCLICK消息相应函数
void CXXXDlg::OnRclickList1(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
if(pNMListView->iItem != -1)
{
DWORD dwPos = GetMessagePos();
CPoint point( LOWORD(dwPos), HIWORD(dwPos) );
CMenu menu;
VERIFY( menu.LoadMenu( IDR_MENU1 ) );
CMenu* popup = menu.GetSubMenu(0);
ASSERT( popup != NULL );
popup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this );
}
*pResult = 0;
}
--------------------------------------------------------------------------------
16. item切换焦点时(包括用键盘和鼠标切换item时),状态的一些变化顺序
添加listctrl控件的LVN_ITEMCHANGED消息相应函数
void CXXXDlg::OnItemchangedList1(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
// TODO: Add your control notification handler code here
CString sTemp;
if((pNMListView->uOldState & LVIS_FOCUSED) == LVIS_FOCUSED && (pNMListView->uNewState & LVIS_FOCUSED) == 0)
{
sTemp.Format("%d losted focus",pNMListView->iItem);
}
else if((pNMListView->uOldState & LVIS_FOCUSED) == 0 && (pNMListView->uNewState & LVIS_FOCUSED) == LVIS_FOCUSED)
{
sTemp.Format("%d got focus",pNMListView->iItem);
}
if((pNMListView->uOldState & LVIS_SELECTED) == LVIS_SELECTED && (pNMListView->uNewState & LVIS_SELECTED) == 0)
{
sTemp.Format("%d losted selected",pNMListView->iItem);
}
else if((pNMListView->uOldState & LVIS_SELECTED) == 0 && (pNMListView->uNewState & LVIS_SELECTED) == LVIS_SELECTED)
{
sTemp.Format("%d got selected",pNMListView->iItem);
}
*pResult = 0;
}
--------------------------------------------------------------------------------
17. 得到另一个进程里的listctrl控件的item内容
http://www.codeproject.com/threads/int64_memsteal.asp
--------------------------------------------------------------------------------
18. 选中listview中的item
Q131284: How To Select a Listview Item Programmatically
http://support.microsoft.com/kb/131284/en-us
--------------------------------------------------------------------------------
19. 如何在CListView中使用CListCtrl的派生类
http://www.codeguru.com/cpp/controls/listview/introduction/article.php/c919/
--------------------------------------------------------------------------------
20. listctrl的subitem添加图标
m_list.SetExtendedStyle(LVS_EX_SUBITEMIMAGES);
m_list.SetItem(..); //具体参数请参考msdn
--------------------------------------------------------------------------------
21. 在CListCtrl显示文件,并根据文件类型来显示图标
网上找到的代码,share
BOOL CTest6Dlg::OnInitDialog()
{
CDialog::OnInitDialog();
HIMAGELIST himlSmall;
HIMAGELIST himlLarge;
SHFILEINFO sfi;
char cSysDir[MAX_PATH];
CString strBuf;
memset(cSysDir, 0, MAX_PATH);
GetWindowsDirectory(cSysDir, MAX_PATH);
strBuf = cSysDir;
sprintf(cSysDir, "%s", strBuf.Left(strBuf.Find("\\")+1));
himlSmall = (HIMAGELIST)SHGetFileInfo((LPCSTR)cSysDir, 0, &sfi, sizeof(SHFILEINFO), SHGFI_SYSICONINDEX | SHGFI_SMALLICON );
himlLarge = (HIMAGELIST)SHGetFileInfo((LPCSTR)cSysDir, 0, &sfi, sizeof(SHFILEINFO), SHGFI_SYSICONINDEX | SHGFI_LARGEICON);
if (himlSmall && himlLarge)
{
::SendMessage(m_list.m_hWnd, LVM_SETIMAGELIST, (WPARAM)LVSIL_SMALL, (LPARAM)himlSmall);
::SendMessage(m_list.m_hWnd, LVM_SETIMAGELIST, (WPARAM)LVSIL_NORMAL, (LPARAM)himlLarge);
}
return TRUE; // return TRUE unless you set the focus to a control
}
void CTest6Dlg::AddFiles(LPCTSTR lpszFileName, BOOL bAddToDocument)
{
int nIcon = GetIconIndex(lpszFileName, FALSE, FALSE);
CString strSize;
CFileFind filefind;
// get file size
if (filefind.FindFile(lpszFileName))
{
filefind.FindNextFile();
strSize.Format("%d", filefind.GetLength());
}
else
strSize = "0";
// split path and filename
CString strFileName = lpszFileName;
CString strPath;
int nPos = strFileName.ReverseFind('\\');
if (nPos != -1)
{
strPath = strFileName.Left(nPos);
strFileName = strFileName.Mid(nPos + 1);
}
// insert to list
int nItem = m_list.GetItemCount();
m_list.InsertItem(nItem, strFileName, nIcon);
m_list.SetItemText(nItem, 1, strSize);
m_list.SetItemText(nItem, 2, strFileName.Right(3));
m_list.SetItemText(nItem, 3, strPath);
}
int CTest6Dlg::GetIconIndex(LPCTSTR lpszPath, BOOL bIsDir, BOOL bSelected)
{
SHFILEINFO sfi;
memset(&sfi, 0, sizeof(sfi));
if (bIsDir)
{
SHGetFileInfo(lpszPath, FILE_ATTRIBUTE_DIRECTORY, &sfi, sizeof(sfi), SHGFI_SMALLICON | SHGFI_SYSICONINDEX | SHGFI_USEFILEATTRIBUTES |(bSelected ? SHGFI_OPENICON : 0));
return sfi.iIcon;
}
else
{
SHGetFileInfo(lpszPath, FILE_ATTRIBUTE_NORMAL, &sfi, sizeof(sfi), SHGFI_SMALLICON | SHGFI_SYSICONINDEX | SHGFI_USEFILEATTRIBUTES | (bSelected ? SHGFI_OPENICON : 0));
return sfi.iIcon;
}
return -1;
}
--------------------------------------------------------------------------------
22. listctrl内容进行大数据量更新时,避免闪烁
m_list.SetRedraw(FALSE);
//更新内容
m_list.SetRedraw(TRUE);
m_list.Invalidate();
m_list.UpdateWindow();
或者参考
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclib/html/_mfc_cwnd.3a3a.setredraw.asp
--------------------------------------------------------------------------------
23. listctrl排序
Q250614:How To Sort Items in a CListCtrl in Report View
http://support.microsoft.com/kb/250614/en-us
--------------------------------------------------------------------------------
24. 在listctrl中选中某个item时动态改变其icon或bitmap
Q141834: How to change the icon or the bitmap of a CListCtrl item in Visual C++
http://support.microsoft.com/kb/141834/en-us
--------------------------------------------------------------------------------
25. 在添加item后,再InsertColumn()后导致整列数据移动的问题
Q151897: CListCtrl::InsertColumn() Causes Column Data to Shift
http://support.microsoft.com/kb/151897/en-us
--------------------------------------------------------------------------------
26. 关于listctrl第一列始终居左的问题
解决办法:把第一列当一个虚列,从第二列开始插入列及数据,最后删除第一列。
具体解释参阅 http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/commctls/listview/structures/lvcolumn.asp
--------------------------------------------------------------------------------
27. 锁定column header的拖动
http://msdn.microsoft.com/msdnmag/issues/03/06/CQA/
--------------------------------------------------------------------------------
28. 如何隐藏clistctrl的列
把需隐藏的列的宽度设为0,然后检测当该列为隐藏列时,用上面第27点的锁定column 的拖动来实现
--------------------------------------------------------------------------------
29. listctrl进行大数据量操作时,使用virtual list
http://www.codeguru.com/cpp/controls/listview/advanced/article.php/c4151/
http://www.codeproject.com/listctrl/virtuallist.asp
--------------------------------------------------------------------------------
30. 关于item只能显示259个字符的问题
解决办法:需要在item上放一个edit。
--------------------------------------------------------------------------------
31. 响应在listctrl的column header上的鼠标右键单击
Q125694: How To Find Out Which Listview Column Was Right-Clicked
http://support.microsoft.com/kb/125694/en-us
--------------------------------------------------------------------------------
32. 类似于windows资源管理器的listview
Q234310: How to implement a ListView control that is similar to Windows Explorer by using DirLV.exe
http://support.microsoft.com/kb/234310/en-us
--------------------------------------------------------------------------------
33. 在ListCtrl中OnTimer只响应两次的问题
Q200054:
PRB: OnTimer() Is Not Called Repeatedly for a List Control
http://support.microsoft.com/kb/200054/en-us
--------------------------------------------------------------------------------
34. 以下为一些为实现各种自定义功能的listctrl派生类
(1) 拖放
http://www.codeproject.com/listctrl/dragtest.asp
在CListCtrl和CTreeCtrl间拖放
http://support.microsoft.com/kb/148738/en-us
(2) 多功能listctrl
支持subitem可编辑,图标,radiobutton,checkbox,字符串改变颜色的类
http://www.codeproject.com/listctrl/quicklist.asp
支持排序,subitem可编辑,subitem图标,subitem改变颜色的类
http://www.codeproject.com/listctrl/ReportControl.asp
(3) subitem中显示超链接
http://www.codeproject.com/listctrl/CListCtrlLink.asp
(4) subitem的tooltip提示
http://www.codeproject.com/listctrl/ctooltiplistctrl.asp
(5) subitem中显示进度条
http://www.codeproject.com/listctrl/ProgressListControl.asp
http://www.codeproject.com/listctrl/napster.asp
http://www.codeguru.com/Cpp/controls/listview/article.php/c4187/
(6) 动态改变subitem的颜色和背景色
http://www.codeproject.com/listctrl/highlightlistctrl.asp
http://www.codeguru.com/Cpp/controls/listbox/colorlistboxes/article.php/c4757/
(7) 类vb属性对话框
http://www.codeproject.com/listctrl/propertylistctrl.asp
http://www.codeguru.com/Cpp/controls/listview/propertylists/article.php/c995/
http://www.codeguru.com/Cpp/controls/listview/propertylists/article.php/c1041/
(8) 选中subitem(只高亮选中的item)
http://www.codeproject.com/listctrl/SubItemSel.asp
http://www.codeproject.com/listctrl/ListSubItSel.asp
(9) 改变行高
http://www.codeproject.com/listctrl/changerowheight.asp
(10) 改变行颜色
http://www.codeproject.com/listctrl/coloredlistctrl.asp
(11) 可编辑subitem的listctrl
http://www.codeproject.com/listctrl/nirs2000.asp
http://www.codeproject.com/listctrl/editing_subitems_in_listcontrol.asp
(12) subitem可编辑,插入combobox,改变行颜色,subitem的tooltip提示
http://www.codeproject.com/listctrl/reusablelistcontrol.asp
(13) header 中允许多行字符串
http://www.codeproject.com/listctrl/headerctrlex.asp
(14) 插入combobox
http://www.codeguru.com/Cpp/controls/listview/editingitemsandsubitem/article.php/c979/
(15) 添加背景图片
http://www.codeguru.com/Cpp/controls/listview/backgroundcolorandimage/article.php/c4173/
http://www.codeguru.com/Cpp/controls/listview/backgroundcolorandimage/article.php/c983/
http://www.vchelp.net/vchelp/archive.asp?type_id=9&class_id=1&cata_id=1&article_id=1088&search_term=
(16) 自适应宽度的listctrl
http://www.codeproject.com/useritems/AutosizeListCtrl.asp
(17) 改变ListCtrl高亮时的颜色(默认为蓝色)
处理 NM_CUSTOMDRAW
http://www.codeproject.com/listctrl/lvcustomdraw.asp
--------------------------------------------------------------------------------
PS:自己程序实现的全选/反选实例
--------------------------------------------------------------------------------
全选
int count = m_ctrlQQFriendList.GetItemCount();
for(int i=0;i<count;i++)
{
m_ctrlQQFriendList.SetCheck(i,TRUE);
}
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
反选
int count = m_ctrlQQFriendList.GetItemCount();
for(int i=0;i<count;i++)
{
m_ctrlQQFriendList.SetCheck(i,!m_ctrlQQFriendList.GetCheck(i));
}
--------------------------------------------------------------------------------
posted @ 2011-08-20 18:20 Mr-Victor 阅读(530) 评论(0) 编辑
嵌入式——环境搭建
以下列环境为例:
主机系统为Windows XP ,在虚拟机下安装了Red Hat Enterprise Linux 5 (Server版)
一、交叉编译环境的搭建
以“博创”公司提供的“魔法师”套件为例,具体用到的开发板为UP-Magic6410,交叉编译器为UP-Magic6410型开发板配套的4.3.1-eabi-armv6-up-cup6410.tar.gz,系统为虚拟机里的Linux系统。具体搭建过程如下:
1、在宿主机的/usr/local/目录下(目录可自定义)建立arm 目录存放交叉编译器
#mkdir /usr/local/arm
2、解压交叉编译器包至/usr/local/arm/目录下
#tar xzvf 4.3.1-eabi-armv6-UP-CUP6410.tar.gz –C /usr/local/arm/
解压后便会产生/usr/local/arm/4.3.1-eabi-armv6/目录
3、配置环境变量
# vi /etc/profile
在里面添加如下内容:
#UP-Magic6410II
PATH=$PATH:$HOME/bin:/usr/local/arm/4.3.1-eabi-armv6/usr/bin/
LD_LIBRARY_PATH=/usr/local/arm/4.3.1-eabi-armv6/gmp/lib:/usr/local/arm/4.3.1-eabi-armv6/mpfr/lib
4、运行source /etc/profile命令重新加载profile文件或者注销或者重启系统来使以上设置生效。
二、Windows XP 和 Linux目录的共享
因为在开发中需要Windows 和虚拟机里的Linux进行数据的交互,所以需要涉及到Windows和Linux的目录的共享,将Windows下的目录挂载到Linux下,具体步骤如下:
1、Windows下的设置
将Windows下要挂载的目录设置成共享属性,关闭Windows下的防火墙。
2、虚拟机的设置
虚拟机上的网络类型应该设置成桥接方式,这样设置的效果是Windows、虚拟机里的Linux系统将处在同一个网段内,更易于操作。但是有个缺点就是需要在联网的情况下才能实现,比如将网线连接在交换机上或者开发板上,将网线连接在开发板上时,开发板里的Linux系统也与Windows系统处在同一个网段。
3、Linux下的设置
网络设置好后,运行ping命令,查看Linux下能否ping通Windows。若能ping通,说明网络配置正确,那么运行以下命令将Windows的目录挂载到Linux下。
# mount -t cifs -o username=administrator%miao0403.com //172.31.4.127/Linux_Code /home/MHW/Code/
其中:username=后面接Windows登录用户名;
“%”符号后面接Windows登录密码;
172.31.4.127是Windows的IP地址;
Linux_Code是Windows的共享目录;
/home/MHW/Code/说明将Linux_Code挂载到Linux下的Code目录下
三、宿主机Linux系统和开发板Linux系统目录的共享
在开发中需要编译好的可执行文件拷贝到开发板上执行,就需要设置宿主机Linux系统和开发板Linux系统目录的共享,具体步骤如下:
1、宿主机Linux系统下NFS的配置
运行如下命令打开配置文件
# vi /etc/exports
在exports文件中添加如下内容:
/home/MHW/ARM_Board/NFS_6410 172.31.4.*(rw,sync,no_root_squash)
其中:/home/MHW/ARM_Board/NFS_6410 是允许其他计算机访问的目录
172.31.4.* 表示被允许访问该目录的客户端IP地址
rw 表示可读可写
sync 表示同步写磁盘
no_root_squash 表示客户端root用户对该目录具备写权限
运行如下命令打开hosts文件
# vi /etc/hosts
在hosts文件最后一行添加开发板的IP地址,比如:
172.31.4.99 172.31.4.99
2、重新启动portmap和nfs,命令如下:
# /etc/init.d/portmap restart
# /etc/init.d/nfs restart
3、将宿主机Linux下的共享目录挂载到开发板Linux目录下
运行如下命令设置开发板的IP地址:
ifconfig eth0 172.31.4.99
注意:IP地址必须设置成添加到hosts文件中的IP地址
运行一下命令挂载宿主机的目录:
mount –o nolock,rsize=4096,wsize=4096 172.31.4.100:/home/MHW/ARM_Board/NFS_6410 /mnt/nfs
其中:172.31.4.100是宿主机的IP地址
/home/MHW/ARM_Board/NFS_6410 是宿主机Linux的共享目录
/mnt/nfs 是要挂载宿主机共享目录的开发板的目录
posted @ 2011-08-20 17:59 Mr-Victor 阅读(184) 评论(0) 编辑
UTF8和UNICODE之间的转换(VC)
CString CXXXDlg::UTF8Convert(CString &str, int sourceCodepage, int targetCodepage)
{
int len=str.GetLength();
int unicodeLen=MultiByteToWideChar(sourceCodepage,0,str,-1,NULL,0);
wchar_t * pUnicode;
pUnicode=new wchar_t[unicodeLen+1];
memset(pUnicode,0,(unicodeLen+1)*sizeof(wchar_t));
MultiByteToWideChar(sourceCodepage,0,str,-1,(LPWSTR)pUnicode,unicodeLen);
BYTE * pTargetData=NULL;
int targetLen=WideCharToMultiByte(targetCodepage,0,(LPWSTR)pUnicode,-1,(char *)pTargetData,0,NULL,NULL);
pTargetData=new BYTE[targetLen+1];
memset(pTargetData,0,targetLen+1);
WideCharToMultiByte(targetCodepage,0,(LPWSTR)pUnicode,-1,(char *)pTargetData,targetLen,NULL,NULL);
CString rt;
rt.Format("%s",pTargetData);
delete pUnicode;
delete pTargetData;
return rt;
}
例如:
UTF8转UNICODE
m_strUnicode = UTF8Convert(m_strUTF8,CP_UTF8,CP_ACP);
UNICODE转UTF8
m_strUTF8 = UTF8Convert(m_strUnicode,CP_ACP,CP_UTF8);
posted @ 2011-08-20 17:54 Mr-Victor 阅读(836) 评论(0) 编辑
用自定义消息在线程间通信(VC)
在多线程的程序中,有时需要在子线程中使用UpdateData()函数对主窗口进行更新,但是在子线程里调用UpdateData()函数是行不通的,这就需要使用自定义消息来进行线程间的通信,在此记录怎么使用自定义消息。
在Visual C++中提供了WM_USER,比它大的整数可以由用户使用,比它小的整数由系统保留。
自定义消息处理的过程如下:
1、声明一个全局的常量,代码如下:
const WM_xxx = WM_USER + N;
其中,WM_xxx表示自定义的消息名,N是正整数。
2、在对话框的头文件中DECLARE_MESSAGE_MAP()的前面添加下列语句:
//{{AFX_MSG(CXXXDlg)
...
afx_msg LONG OnXXXX(WPARAM wParam, LPARAM lParam); //消息处理函数的声明,OnXXXX可自行命名
...
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
3、切换到对话框类的实现文件中,在BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之间添加消息映射代码:
BEGIN_MESSAGE_MAP(CXXXDlg, CDialog)
//{{AFX_MSG_MAP(CQQDlg)
...
ON_MESSAGE(WM_xxx,OnXXXX) //WM_xxx就是第1点中声明的全局常量,OnXXXX就是第2点声明的消息处理函数
...
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
4、在对话框类的实现文件中添加消息处理函数的实现代码,大致如下:
LONG CXXXDlg::OnXXXX(WPARAM wParam, LPARAM lParam)
{
switch(wParam)
{
case 情况1:
{
UpdateData(lParam);
break;
}
case 情况2:
{
//消息处理
break;
}
.....
default:
break;
}
}
5、现在就可以向主线程发送消息了,Windows提供了两个发送消息的函数:SendMessage和PostMessage。
(1) SendMessage:该函数只有等消息被处理完以后才可以返回。其函数原型为:
LRESULT SendMessage(HWND hWnd, UINT message, WPARAM wParam=0, LPARAM lParam=0);
hWnd:其窗口程序接收消息的窗口的句柄。
message:指定要发送的消息。
wParam:指定额外消息依赖的信息。
lParam:指定额外消息依赖的信息。
LRESULT:表示一个窗口过程返回值的类型。
(2) PostMessage:该函数将消息发送后就可以直接返回。其函数原型为:
BOOL PostMessage(HWND hWnd, UINT message, WPARAM wParam=0, LPARAM lParam=0);
hWnd:其窗口程序接收消息的窗口的句柄。
message:指定要发送的消息。
wParam:指定额外消息依赖的信息。
lParam:指定额外消息依赖的信息。
注:上面的WPARAM和LPARAM参数表示一个32位的值,将作为参数传递给响应Windows消息的函数。
例如:
PostMessage((HWND)(pQQDlg->GetSafeHwnd()), WM_xxx, 情况1, FALSE);
posted @ 2011-08-20 17:52 Mr-Victor 阅读(184) 评论(0) 编辑
凯撒加密/解密算法(VC)
/***********加密CString并写入文件(加法加密)************/
CFile fileWrite;
CString m_strEncryption = _T("需要加密的信息");
int count = m_strEncryption.GetLength();
BYTE* m_byteRead = new BYTE[count];
BYTE* m_byteWrite = new BYTE[count*2];
m_byteRead = (BYTE*)m_strEncryption.GetBuffer(count);
for (int i = 0, k = 0, j = 1; i < count; i++, k = k + 2, j = j + 2)
{
m_byteWrite[k] = (byte)((m_byteRead[i] + 参数(密钥)) % 128 + 128);
m_byteWrite[j] = (byte)((m_byteRead[i] + 参数(密钥)) / 128);
}
if(fileWrite.Open(输出路径, CFile::modeCreate | CFile::modeWrite))
{
fileWrite.Write(m_byteWrite,count*2);
fileWrite.Write("\r\n",2);
}
fileWrite.Close();
m_strEncryption.ReleaseBuffer();
delete[] m_byteWrite;
PS:解密及其他方式的加密解密可见《凯撒加密/解密算法(C#)》,将其稍作修改就可适用于VC。
posted @ 2011-08-20 17:48 Mr-Victor 阅读(280) 评论(0) 编辑
凯撒加密/解密算法(C#)
/***************加密***************/
public void Encryption()
{
FileStream fs1 = new FileStream(加密源文件的存放路径, FileMode.Open, FileAccess.Read);
FileStream fs2 = new FileStream(加密后文件的存放路径, FileMode.Create);
BinaryReader br1 = new BinaryReader(fs1);
BinaryWriter br2 = new BinaryWriter(fs2);
byte[] bys1 = br1.ReadBytes((int)fs1.Length);
byte[] bys2 = new byte[(int)(bys1.Length) * 2];
//加法加密
if ()
{
for (int i = 0, k = 0, j = 1; i < bys1.Length; i++, k = k + 2, j = j + 2)
{
bys2[k] = (byte)((Convert.ToInt16(bys1[i]) + Convert.ToInt16(参数(密钥))) % 128 + 128);
bys2[j] = (byte)((Convert.ToInt16(bys1[i]) + Convert.ToInt16(参数(密钥))) / 128);
}
br2.Write(bys2);
}
//乘法加密
else
{
for (int i = 0, k = 0, j = 1; i < bys1.Length; i++, k = k + 2, j = j + 2)
{
bys2[k] = (byte)((Convert.ToInt16(bys1[i]) * Convert.ToInt16(参数(密钥))) % 128 + 128);
bys2[j] = (byte)((Convert.ToInt16(bys1[i]) * Convert.ToInt16(参数(密钥))) / 128);
}
br2.Write(bys2);
}
br2.Close();
br1.Close();
}
/***************解密***************/
public void Decryption()
{
FileStream fs1 = new FileStream(解密源文件的存放路径, FileMode.Open, FileAccess.Read);
FileStream fs2 = new FileStream(解密后文件的存放路径, FileMode.Create);
BinaryReader br1 = new BinaryReader(fs1);
BinaryWriter br2 = new BinaryWriter(fs2);
byte[] bys1 = br1.ReadBytes((int)fs1.Length);
byte[] bys2 = new byte[(int)(bys1.Length) / 2];
//加法解密
if ()
{
for (int i = 0, j = 1, k = 0; i < bys1.Length; i = i + 2, j = j + 2, k++)
{
bys2[k] = (byte)(((Convert.ToInt16(bys1[i]) - 128) + (Convert.ToInt16(bys1[j]) * 128)) - Convert.ToInt16(参数(密钥)));
}
br2.Write(bys2);
}
//乘法解密
else
{
for (int i = 0,j=1,k=0; i < bys1.Length; i=i+2,j=j+2,k++)
{
bys2[k] = (byte)(((Convert.ToInt16(bys1[i]) - 128) + (Convert.ToInt16(bys1[j]) * 128)) / Convert.ToInt16(参数(密钥)));
}
br2.Write(bys2);
}
br2.Close();
br1.Close();
}
posted @ 2011-08-20 17:43 Mr-Victor 阅读(372) 评论(0) 编辑
Access2010 - 数据类型
Access允许十种数据类型:文本、备注、数值、日期/时间、货币、自动编号、是/否、OLE对象、超级链接、附件、查询向导 。
文本(Text):这种类型允许最大255个字符或数字,Access默认的大小是50个字符,而且系统只保存输入到字段中的字符,而不保存文本字段中未用位置上的空字符。可以设置“字段大小”属性控制可输入的最大字符长度。
备注(Memo):这种类型用来保存长度较长的文本及数字,它允许字段能够存储长达64000个字符的内容。但Access不能对备注字段进行排序或索引,却可以对文本字段进行排序和索引。在备注字段中虽然可以搜索文本,但却不如在有索引的文本字段中搜索得快。
数字(Number):这种字段类型可以用来存储进行算术计算的数字数据,用户还可以设置“字段大小”属性定义一个特定的数字类型,任何指定为数字数据类型的字型可以设置成“字节”、“整数”、“长整数”、“单精度数”、“双精度数”、“同步复制ID”、“小数”五种类型。在Access中通常默认为“双精度数”。
日期/时间(Data/Time):这种类型是用来存储日期、时间或日期时间一起的,每个日期/时间字段需要8个字节来存储空间。
货币(Currency):这种类型是数字数据类型的特殊类型,等价于具有双精度属性的数字字段类型。向货币字段输入数据时,不必键入人民币符号和千位处的逗号,Access会自动显示人民币符号和逗号,并添加两位小数到货币字段。当小数部分多于两位时,Access会对数据进行四舍五入。精确度为小数点左方15位数及右方4位数。
自动编号(AutoNumber):这种类型较为特殊,每次向表格添加新记录时,Access会自动插入唯一顺序或者随机编号,即在自动编号字段中指定某一数值。自动编号一旦被指定,就会永久地与记录连接。如果删除了表格中含有自动编号字段的一个记录后,Access并不会为表格自动编号字段重新编号。当添加某一记录时,Access不再使用已被删除的自动编号字段的数值,而是重新按递增的规律重新赋值。
是/否(Yes/No):这种字段是针对于某一字段中只包含两个不同的可选值而设立的字段,通过是/否数据类型的格式特性,用户可以对是/否字段进行选择。
OLE对象(OLE Object):这个字段是指字段允许单独地“链接”或“嵌入”OLE对象。添加数据到OLE对象字段时,可以链接或嵌入Access表中的OLE对象是指在其他使用OLE协议程序创建的对象,例如WORD文档、EXCEL电子表格、图像、声音或其他二进制数据。OLE对象字段最大可为1GB,它主要受磁盘空间限制。
超级链接(Hyperlink):这个字段主要是用来保存超级链接的,包含作为超级链接地址的文本或以文本形式存储的字符与数字的组合。当单击一个超级链接时,WEB浏览器或Access将根据超级链接地址到达指定的目标。超级链接最多可包含三部分:一是在字段或控件中显示的文本;二是到文件或页面的路径;三是在文件或页面中的地址。在这个字段或控件中插入超级链接地址最简单的方法就是在“插入”菜单中单击“超级链接”命令。
附件(?):可允许向Access数据库附加外部文件的特殊字段(Access2007新增)。
查阅向导(Lookup Wizard):这个字段类型为用户提供了一个建立字段内容的列表,可以在列表中选择所列内容作为添入字段的内容。
Access数据类型大全
文本:char(n)、string(n)、text(n) 、varchar(n) 其中n表示字段大小
备注:note、memo
数字:
字节:byte
长整型:long、int、integer
整型:short、smallint
单精度型:single、real
双精度型:double、float
日期/时间:date、datetime、time、timestamp
货币:currency、money
自动编号:counter(m,n)、autoincrement(m,n) m为初始值,n为步长值,如果不写默认值是(1,1)
是/否:bit、yesno
OLE对象:image
二进制:binary
posted @ 2011-08-20 17:41 Mr-Victor 阅读(1699) 评论(0) 编辑
Access2010中如何运行SQL执行SQL语句
1、打开一个Access数据库文件
2、点击“创建”标签中的“查询设计”,会弹出一个“显示表”的对话框,点击“关闭”将其关闭
3、这时会有一个名为“查询*”的窗口,还不能输入SQL语句
4、点击左上角的“SQL视图”,这时就可在查询窗中输入SQL语句了
5、SQL语句编辑完成后,点击左上角的红色感叹号即可执行SQL语句
posted @ 2011-08-20 17:37 Mr-Victor 阅读(1550) 评论(0) 编辑
由CLSID得到ProgID 以及 由ProgID得到CLSID
以下代码出自《杨老师之Blog——COM组件设计与应用(四)》。
地址:http://blog.vckbase.com/teacheryang/archive/2005/07/04/9135.html
一、由CLSID得到ProgID(以Word为例子)
void CGetProgIDFromCLSIDDlg::OnBnClickedButton1()
{
::CoInitialize(NULL);
HRESULT hr;
CLSID clsid = {0x209ff,0,0,{0xc0,0,0,0,0,0,0,0x46}};
LPOLESTR lpwProgID = NULL;
hr = ::ProgIDFromCLSID(clsid, &lpwProgID);
if(SUCCEEDED(hr))
{
::MessageBoxW(NULL, lpwProgID, L"ProgID", MB_OK);
IMalloc *pMalloc = NULL;
hr = ::CoGetMalloc(1, &pMalloc); //取得IMalloc
if(SUCCEEDED(hr))
{
pMalloc->Free(lpwProgID); //释放ProgID内存
pMalloc->Release(); //释放IMalloc
}
}
::CoUninitialize();
}
运行结果:
二、由ProgID得到CLSID(以Excel为例子)
void CGetProgIDFromCLSIDDlg::OnBnClickedButton2()
{
::CoInitialize(NULL); //COM初始化
//如果是MFC程序,可以使用AfxOleInit()替代
HRESULT hr;
USES_CONVERSION; // 只需要调用一次,就可以在函数中进行多次转换
LPCOLESTR lpwProgID = T2COLE(_T("Excel.Application.14"));
CLSID clsid;
hr = ::CLSIDFromProgID(lpwProgID,&clsid);
CString str;
str.Format("%08X-%04X-%04x-%02X%02X-%02X%02X%02X%02X%02X%02X" ,
clsid.Data1,clsid.Data2,clsid.Data3,
clsid.Data4[0],clsid.Data4[1],clsid.Data4[2],
clsid.Data4[3],clsid.Data4[4],clsid.Data4[5],
clsid.Data4[6],clsid.Data4[7]);
if(SUCCEEDED(hr))
{
::MessageBox(NULL, (LPCSTR)str, _T("CLSID"), MB_OK);
}
::CoUninitialize();
}
运行结果:
posted @ 2011-08-20 17:21 Mr-Victor 阅读(277) 评论(0) 编辑
如何使用“浏览文件夹”选择对话窗
本文摘自:http://www.cnblogs.com/wangliang651/archive/2006/07/07/445473.html(稍作修改)
有时我们需要获得某个文件夹的路径,而不是文件的路径,这时需要用API函数SHBrowseForFolder来实现。
函数原型为:
LPITEMIDLIST SHBrowseForFolder(LPBROWSEINFO lpbi);
其中LPBROWSEINFO为BROWSEINFO结构的指针。
Visual C++(VC)中,BROWSEINFO结构中包含有用户选中目录的重要信息,其结构如下:
typedef struct_browseinfo
{
HWND hwndOwner;
LPCITEMIDLIST pidlRoot;
LPSTR pszDisplayName;
LPCSTR lpszTitle;
UINT ulFlags;
BFFCALLBACK lpfn;
LPARAM lParam;
int iImage;
}BROWSEINFO,*PBROWSEINFO,*LPBROWSEINFO;
成员变量:
hwndOwner:浏览文件夹对话框的父窗体句柄。
pidlRoot:ITEMIDLIST结构的地址,包含浏览时的初始根目录,而且只有被指定的目录和其子目录才显示在浏览文件夹对话框中。该成员变量可以是NULL,在此时桌面目录将被使用。
pszDisplayName:用来保存用户选中的目录字符串的内存地址。该缓冲区的大小缺省是定义的MAX_PATH常量宏。
lpszTitle:该浏览文件夹对话框对话框的显示文本,用来提示该浏览文件夹对话框的功能、作用和目的。
ulFlags:该标志位描述了对话框的选项。它可以为0,也可以是以下常量的任意组合:
BIF_BROWSEFORCOMPUTER:返回计算机名。除非用户选中浏览器中的一个计算机名,否则该对话框中的“OK”按钮为灰色。
BIF_BROWSEFORPRINTER:返回打印机名。除非选中一个打印机名,否则“OK”按钮为灰色。
BIF_BROWSEINCLUDEFILES:浏览器将显示目录,同时也显示文件。
BIF_DONTGOBELOWDOMAIN:在树形视窗中,不包含域名底下的网络目录结构。
BIF_EDITBOX:浏览对话框中包含一个编辑框,在该编辑框中用户可以输入选中项的名字。
BIF_RETURNFSANCESTORS:返回文件系统的一个节点。仅仅当选中的是有意义的节点时,“OK”按钮才可以使用。
BIF_RETURNONLYFSDIRS:仅仅返回文件系统的目录。例如:在浏览文件夹对话框中,当选中任意一个目录时,该“OK”按钮可用,而当选中“我的电脑”或“网上邻居”等非有意义的节点时,“OK”按钮为灰色。
BIF_STATUSTEXT:在对话框中包含一个状态区域。通过给对话框发送消息使回调函数设置状态文本。
BIF_VALIDATE:当没有BIF_EDITBOX标志位时,该标志位被忽略。如果用户在编辑框中输入的名字非法,浏览对话框将发送BFFM_VALIDATEFAILED消息给回调函数。
lpfn:应用程序定义的浏览对话框回调函数的地址。当对话框中的事件发生时,该对话框将调用回调函数。该参数可用为NULL。
lParam:对话框传递给回调函数的一个参数指针。
iImage:与选中目录相关的图像。该图像将被指定为系统图像列表中的索引值。
调用例子如下:
void CXXXDlg::OnBnClickedButton3()
{
CString sFolderPath; //用来存储路径
char szPath[MAX_PATH] = {0}; //用来得到你选择的活页夹路径,相当于提供一个缓冲区
BROWSEINFO m_bi;
m_bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_STATUSTEXT | BIF_EDITBOX | BIF_NEWDIALOGSTYLE;
m_bi.hwndOwner = GetSafeHwnd(); //获得父窗口句柄
m_bi.pidlRoot = NULL;
m_bi.lpszTitle = "Select Folder";
m_bi.lpfn = NULL;
m_bi.lParam = NULL;
m_bi.pszDisplayName = szPath; //此参数如为NULL则不能显示对话框
LPITEMIDLIST pidl = ::SHBrowseForFolder(&m_bi); //调用显示对话框
//弹出文件夹浏览目录,并选取目录
if(pidl)
{
//取得文件夹路径到szPath里
if(!::SHGetPathFromIDList(pidl,szPath))
szPath[0] = 0;
//将路径保存到一个CString对象里
sFolderPath = szPath;
IMalloc * pMalloc = NULL;
if(SUCCEEDED(::SHGetMalloc(&pMalloc))) //取得IMalloc分配器接口
{
pMalloc->Free(pidl); //释放内存
pMalloc->Release(); //释放接口
}
}
MessageBox(sFolderPath);
}
- 在窗口中显示一幅 JPG 图象
- 在窗口中显示一幅 JPG 图象
- 在窗口中显示一幅 JPG 图象
- PC Camera开发日志(十四)---- 用IPicture在窗口中显示一幅 JPG 图象
- vc怎样在picture control中显示jpg,jif,bmp格式图象
- picture control中显示jpg,jif,bmp格式图象
- picture control中显示jpg,jif,bmp格式图象
- 显示jpg和gif图象
- VC中打开一幅jpg图片并且显示在对话框上的方法和代码
- 在 WebForm 的 DadaGrid 中显示图象
- 在 WebForm 的 DadaGrid 中显示图象
- VC:在窗口中显示一幅位图
- wince下显示流式jpg图象
- 在对话框中显示jpg图片
- 在wtl中显示jpg,png
- 资源中的JPG在CListCtrl中显示
- 在控件中显示JPG图片
- 如何在MFC窗口显示BMP和JPG图片
- ORA-12516 解决方法
- 学生表 课程表 成绩表 教师表 50个常用sql语句
- C++统计字符串中的小字符串个数
- Windows Server 2012正式版安装初体验
- WinIO驱动级模拟键盘 C#
- 在窗口中显示一幅 JPG 图象
- IOS之数据持久化
- 网络子系统大杂烩三
- Android c/c++层打印log信息和java层打印log信息
- Oracle存储过程异常处理总结
- 10大iOS开发者最喜爱的类库
- oracle字段拼接
- 选手的分
- Struts2 学习笔记 4