你有GIF的开发许可吗?PrecSubclassWindow 以及 MFC 中的 EOF

来源:岁月联盟 编辑:zhu 时间:2009-03-12

  本文示例源代码或素材下载

  本文涉及的问题解答:

  

  • 关于GIF的开发许可

     

      关于 CListBox 的疑问

      使用 CFile 时判断文件结束的问题

      dll的调试问题

      如何在程序中旋转图像?

  我在 2001年10月 的专栏中介绍了如何利用IPicture来显示GIF和JPEG图像,之后有很多读者发送电子邮件给我询问他们在显示GIF图像时是否应该支付专利权使用费。正如你们当中许多人所知道的(毫无疑问,还有很多人并不知道),在GIF格式中所使用的Lempel Ziv Welch(LZW)算法是Unisys公司的专利。如果在你自己的程序中读或写GIF图像,法律上可能会要求你应该从Unisys公司获得授权。

  为了获得更详细的信息,我查看了 http://www.unisys.com/unisys/lzw。不幸的是,即使反复浏览了几遍,我还是不能确定开发人员在何种情况下需要支付费用。假如你写一个GIF到BMP的转换程序,应该支付费用吗?(需要付费的可能性很大)。如果在“关于”对话框中显示一个GFI图像又如何呢?这个问题会由于你使用的商业产品可能已经获得了算法的授权而更复杂。为了弄明白些,我给Unisys相关人员发了封电子邮件。几天之后,我收到了一份Word文档,是关于我的申请所要填的标准问卷。当我提醒他们我只是问一下而已,并不是想填一份申请表时,得到的礼貌回答是:这个问题非常复杂,是否愿意跟他们的律师谈谈?

  那么微软呢?友善的 Redmondtonians(译注:指 Microsoft 公司的人 )对此有过说明吗?请看知识库文章Q193543:

  “在微软的产品中,微软已经从Unisys获得了使用.gif文件格式和其它由Unisys持有的美国和外国有关LZW技术专利的授权。

  但是,这些授权并不延伸给使用微软开发工具开发应用程序的第三方使用。第三方必须从Unisys获得书面合约形式的授权。欲了解更多信息......”

  不幸的是,最终的结果就是这样,我不能给你任何帮助。如果你正在写一个读写GIF的程序,你应该去Unisys网站来自己确定。Unisys工作人员会很 乐意给你他们律师的电话。很明显,整个关于GIF/LZW专利权的事情引起了很大的争议,其中一方是信仰软件开源的人。你可以去 http://lzw.info 对“LZW patent”作一个快速预览或者搜索相关的网站。

我有一个CListBox的子类。为了使用它的扩展功能,列表框必须是自绘的。我改写了 PreCreateWindow 虚函数来强制设置LBS_OWNERDRAWFIXED 样式。如果列表框是 由我自己动态创建的则运行正常。如果列表框作为对话框的一部分从资源加载,则怎么也不会调用 PreCreateWindow。这种情况下我怎样才能保证 LBS_OWNERDRAWFIXED 样式被设置呢?

  Frank Lagattuta

简短的回答是:使用 PreSubclassWindow。

  以下是详细的使用方法。PreCreateWindow 是一个由 MFC 调用的特殊的 CWnd 虚函数,你可能已经猜到了,该函数是在创建窗口前被调用。 也就是说,当你调用 CWnd::Create或CWnd::CreateEx 创建一个窗口时,MFC在 Windows 系统实际创建你的窗口前调用 CWnd::PreCreateWindow。

BOOL CWnd::CreateEx(...)
{
 CREATESTRUCT cs;
 ...
 // init cs
 if (!PreCreateWindow(cs)) {
  PostNcDestroy();
  return FALSE;
 }
 ...
 // create the window
 return TRUE;
}

  但是对话框不是这样创建的。当你调用 CDialog::DoModal 时,对话框才被创建,在这种情况下 MFC 使用::CreateDlgIndirect 来加载你的对话框模板,它大致与 ::DialogBox 功能相同。不管调用哪个函数,Windows 系统都会在内部创建控件,CWnd::CreateEx 将不会被调用。因此你自己控件的 PreCreateWindow 也不会被调用。

  但是当你将控件与某个 CWnd 派生类的对象实例关联时,比如你的特殊列表框。你得调用 SubclassDlgItem 来实现,它又会调用SubclassWindow。 此时 MFC 会调用另一个特殊的虚函数——PreSubclassWindow。正是你在这里,你有机会做一些事情(比如改变样式)。

void CMyListBox::PreSubclassWindow()
{
 // turn on owner-draw
 ModifyStyle(0,LBS_OWNERDRAWFIXED);
}

  事实上,PreSubclassWindow 是做这些工作的最佳场所,因为当你用 CreateEx 创建窗口时,MFC也会调用 PreSubclassWindow。换句话说,PreSubclassWindow 可以覆盖 了两种情况(不管是在对话框中创建还是单独创建)。因此,当你在 PreSubclassWindow 设置样式时,就不用再在 PreCreatWindow 中做同样的事了。

我正在程序中使用 CFile 类,我知道如何使用这个类来打开、写入和关闭文件,但不知道(这个很重要)如何在文件末尾(EOF)读文件。我处理了 CFileException::endOfFile,但得到一个无穷循环。

  Alessandro Chiodo

这样做好像有点古怪,不是吗?我的意思是,当你读到文件末尾时,希望得到一个 end-of-file 异常,对吗?一个 endOfFile 异常有什么好吗?正如所看到的,MFC在这种情况下并不使用 CFileException::endOfFile。为了检查文件尾,可以测试实际读到的字节数是否小于所希望的字节数。

CFile file;
file.Open();
char mybuf[BUFSIZ];
UINT nread;
do (nread=file.read(buf, BUFSIZ)) {
 ...
// process nread bytes
} while (nread==BUFSIZ);

  用这种方法好像有点奇怪,但我并不想规定这样做,只是说可以这样做。

我想调试一个由外部程序触发的 C 源程序。像下面这种情况:

  应用程序A -> 调用插件B-> 调用my.dll

  我没有应用程序A或插件的源码可以用,只有my.dll有源码。我想在应用程序A调用 my.dll 时调试它。应该怎么做呢?

  Chamika Gunathilaka

这个很简单。在 Visual Studio 中只要在 Project | Settings 中选择 Debug 标签。正常情况下,Visual Studio 会用你的可执行模块名字填充“executable for debug session”,但也可以输入任何你想要的程序名字。举个例子,你可以用 c:SomeDirAppA.exe。你甚至可以给程序指定一个不同的工作目录和参数。图 一展示了这些细节。这种技术通常用于调试 DLLs,名字空间扩展,COM对象以及其它由外部EXE模块调用的插件。

  图一 通过别的程序进行调试

我最近读了你的关于CPicture类的专栏文章。它太有用了,而且非常好用。我想知道我试图解决的两个问题是否也能够很容易地处理。我想显示JPG图像,但在某些情况下我想能旋转90度显示。 此外,我还希望能生成小的位图用于在列表中作为缩略图。

  Dan Byron

好吧,我很高兴我的类对你有帮助。我猜你拍完马屁之后,希望我能告诉你答案,对吗?告诉我,为什么你想要旋转图像?你就不能告诉你的用户叫他们歪着头看?他们 是不是希望你能做所有事情?

  好啦,好啦。你并不想让你的客户扭到脖子而且也想完善类的功能,让我们来解决这个问题。旋转图像的一个方法是自己绘制像素。也就是,在x,y空间中用一个矩阵变换来达到旋转的效果。

  cos A -sin A
  
  sin A cos A

  这个变换(如果你还记得这些高中三角学或是大学线性代数所学过的知识点的话)的意思是如果有一个点的坐标为(x,y),旋转后的坐标为(x*cos(A) y*sin(A), -x*sin(A) y*cos(A)),这里的A用弧度表示(2π 弧度 = 360角度)。所以只要加载位图,把它选入设备上下文,然后像上面所说的调用 GetPixel 和 SetPixel 进行所有像素的绘制,最后神奇般地你旋转了图像。旋转90度,180度或-90度似乎不是很难,因为 sin 和 cos 计算的结果是 /-1或0。

  不过,我知道你在想什么。“额滴个神啊……请别叫我这么做!一定还有更好的办法!”你猜对了。使用 GDI 让你不用再郁闷旋转的事了。你在阅读MSDN杂志方面已经落后了很多年(我知道时间过得很快),GDI 是GDI图形库的增强版,可以通过C 使用。GDI 内建在Windows XP和Microsoft .NET中,但是为 Windows 98,Windows NT 和 Windows 2000 提供了可以重新分发的版本。( 详情请查看http://www.microsoft.com/msdownload/ platformsdk/sdkupdate/psdkredist.htm.)

  GDI 是一个 C API。它使用 C 类和方法。为了使用 GDI ,你应该 #include <gdiplus.h>,在工程中链接 gdiplus.lib,这些都是最新 Windows SDK 的一部分。GDI 的知识远远超过 本专栏文章所涉及的内容。然而,我在2001年10月专栏文章 ImgView 程序的基础上重新写了一个新程序,ImgView2,来展示如何使用 GDI 旋转图像(如图 二)。

  图二 在ImgView中旋转

  10月份的专栏提供了一个CPicture类,它基于 IPicture(用于处理图像的COM接口)。在ImgView2中,我使用 GDI 中的 Image 类重写了CPicture。所有给出的 Image 与 IPicture 转换相对比较简单,都已经在类中封装好了。一旦我在 CPicture 中使用 Image 代替IPicture,所有其它的类如 CPictureView 和 CPictureCtrl 都不会受影响。当然还 是会有一些小麻烦。

  首先,你要初始化和释放GDI库,这不能算是麻烦,仅仅是因为 GDI 需要这样做。最好在程序中的 InitInstance 和 ExitInstance 做这些事:

class CMyApp : public CWinApp {
protected:
 GdiplusStartupInput m_gdiplusStartupInput;
 ULONG_PTR m_gdiplusToken;
 ...
};
BOOL CMyApp::InitInstance()
{
 VERIFY(GdiplusStartup(&m_gdiplusToken,
  &m_gdiplusStartupInput, NULL)==Ok);
 ...
}
int CMyApp::ExitInstance()
{
 GdiplusShutdown(m_gdiplusToken);
 return CWinApp::ExitInstance();
}

  CMyApp::m_gdiplusToken 是一个很有用的成员变量,从 GdiplusStartup 中获得赋值,然后传递给 GdiplusShutdown;m_gdiplusStartupInput 是一个结构变量,用于保存 GDI 启动参数,其默认的构造函数会自动给结构体赋以默认值,这也再一次证明了 C 比 C 要好。

  一旦加载了 GDI ,你就能使用它了。旧的 CPicture 保存了一个指向 IPicture 的指针,新版的 CPicture 则保存了一个指向 Image 的指针。跟以前一样, 有多个重载的 Load 函数可用不同的方式加载图像。以下的例子展示了新的 CPicture 用路径名加载图像。

BOOL CPicture::Load(LPCTSTR pszPathName)
{
 Free();
 USES_CONVERSION;
 m_pImage = Image::FromFile(A2W(pszPathName),
  m_bUseEmbeddedColorManagement);
 return m_pImage->GetLastStatus()==Ok;
}

  这里重要的事情是 GDI 使用宽字符,因此你需要用 USES_CONVERSION 和 A2W。旧版 CPicture 提供的 Load 函数可以从 CFile、CArchive、资源ID或流加载图像。所有这些 Load 函数最终都 通过 CPicture::Load(IStream*)加载图像,也就是从流加载图像。但是当我开始用 Image 代替 IPicture,使用 GDI 函数从流中加载图像时,什么也不会发生。太糟糕了。问题出在 MFC 的 CArchiveStream 类上,它在 CArchive 上实现了一个流(查看上个月的专栏)。

  不知什么原因(有可能 CArchiveStream 没能正确地实现所有 IStream 功能),Image::FromStream 不能在基于 CArchiveStream 的流上正常工作。最糟的是,函数执行正常,但稍后你显示或删除 Image 时却会失败。

  针对这个问题,我使用 CreateStreamOnHGlobal 重写了 CPiture::Load(UINT nID)。这个方便的 API 函数可以在一块全局内存上创建一个流。

// 分配全局内存,在上面创建流
HGLOBAL m_hMem = GlobalAlloc(GMEM_FIXED, len);
BYTE* pmem = (BYTE*)GlobalLock(m_hMem);
memcpy(pmem,lpRsrc,len);
IStream* pstm;
CreateStreamOnHGlobal(m_hMem,FALSE,&pstm);

  这里的 lpRsrc 已经指向了内存中的图像资源。因此加载图像资源最基本的方法就是,把它拷贝到全局内存里,在全局内存中创建一个流,然后使用 Image::FromStream 创建一个图像。具体实现细节请下载源代码。

  CPicture 可以在对象被销毁或者加载另一个图像时自动释放分配的全局内存。至于从 CFile 或 CArchive 加载,我并不是真的很需要它们。

  加载图像已经谈了很多。为了能够显示,你还得使用另一个 GDI 类,Graphics——相当于老的 GDI 中设备上下文(HDC或CDC)的作用和它的一个方法,DrawImage。

BOOL CPicture::Render(CDC* pDC, CRect rc) const
{
 ...
 Graphics graphics(pDC->m_hDC);
 graphics.DrawImage(m_pImage,
  rc.left, rc.top, rc.Width(), rc.Height());
}

  详情请参考源代码中的 CPicture 实现细节。用 Image 代替 IPicture 使得绘制变得简单。Image::GetWidth 和 Image::GetHeight 用于获取以像素为单位的宽和高,你能用来代替从 IPicture 获得的以 HIMETRIC 单位的相关尺寸。一般来说,GDI 用于编程相当容易。以下例子展示如何旋转。

void CPicture::Rotate(RotateFlipType rft)
{
 if (m_pImage) {
  m_pImage->RotateFlip(rft);
 }
}

  以上代码将图像顺时针旋转了90度。RotateFlipType 所有可能的取值如下:

// enum types for Image::RotateFlip
//
enum RotateFlipType
{
  RotateNoneFlipNone = 0,
  Rotate90FlipNone  = 1,
  Rotate180FlipNone = 2,
  Rotate270FlipNone = 3,
  RotateNoneFlipX  = 4,
  Rotate90FlipX   = 5,
  Rotate180FlipX   = 6,
  Rotate270FlipX   = 7,
  RotateNoneFlipY  = Rotate180FlipX,
  Rotate90FlipY   = Rotate270FlipX,
  Rotate180FlipY   = RotateNoneFlipX,
  Rotate270FlipY   = Rotate90FlipX,
  RotateNoneFlipXY  = Rotate180FlipNone,
  Rotate90FlipXY   = Rotate270FlipNone,
  Rotate180FlipXY  = RotateNoneFlipNone,
  Rotate270FlipXY  = Rotate90FlipNone
};

  GDI 还有其它函数用于拉伸和裁剪图像。甚至还有 Image::GetThumbnailImage 函数用于解决关于缩略图的问题,这个我把它留着作为你的课外练习。

 

  我鼓励你去使用 GDI 。一些程序员并不喜欢它,因为它有点慢,但是文档上已经给出了提高性能的提示。比如说,有一个CachedBitmap类用于在设备优化模式下保存位图。你可能不会将 GDI 用于大量的图形处理,像视频游戏或高端的图像编辑器(这些你会使用 DirectX)。但是对于日常的图形处理任务,GDI 比 GDI 有了很大的提高。

  再见!