安全注意事项:GDI+

本主题提供有关使用 Windows GDI+ 编程的安全注意事项的信息。 本主题并未提供你需要了解的关于安全问题的所有信息 - 此技术领域从本主题开始,主题中的内容仅供参考。

验证构造函数是否成功

许多 GDI+ 类都提供了 Image::GetLastStatus 方法,你可以调用该方法来确定在对象上调用的方法是否成功。 还可以调用 Image::GetLastStatus 来确定构造函数是否成功。

以下示例演示如何构造 Image 对象并调用 Image::GetLastStatus 方法来确定构造函数是否成功。 OkInvalidParameter 值是 Status 枚举的元素。

Image myImage(L"Climber.jpg");
Status st = myImage.GetLastStatus();

if(Ok == st)
   // The constructor was successful. Use myImage.
else if(InvalidParameter == st)
   // The constructor failed because of an invalid parameter.
else
   // Compare st to other elements of the Status 
   // enumeration or do general error processing.

分配缓冲区

多个 GDI+ 方法在调用方分配的缓冲区中返回数字或字符数据。 对于上述每个方法,都有一个配套方法,可提供所需缓冲区的大小。 例如,GraphicsPath::GetPathPoints 方法返回 Point 对象的数组。 在调用 GraphicsPath::GetPathPoints 之前,必须分配足够大的缓冲区来保存该数组。 可以通过调用 GraphicsPath 对象的 GraphicsPath::GetPointCount 方法来确定所需缓冲区的大小。

以下示例演示如何确定 GraphicsPath 对象中的点的数量,分配一个足够大的缓冲区来容纳这些点,然后调用 GraphicsPath::GetPathPoints 来填充缓冲区。 在代码调用 GraphicsPath::GetPathPoints 之前,它通过确保缓冲区指针不为 NULL 来验证缓冲区分配是否成功。

GraphicsPath path;
path.AddEllipse(10, 10, 200, 100);

INT count = path.GetPointCount();          // get the size
Point* pointArray = new Point[count];      // allocate the buffer

if(pointArray)  // Check for successful allocation.
{
   path.GetPathPoints(pointArray, count);  // get the data
   ...                                     // use pointArray
   delete[] pointArray;                    // release the buffer
   pointArray = NULL;
}

前面的示例使用 new 运算符分配缓冲区。 new 运算符很方便,因为缓冲区填充了已知数量的 Point 对象。 在某些情况下,GDI+ 在缓冲区中写入的内容比 GDI+ 对象数组多。 有时,有时缓冲区会填充 GDI+ 对象数组以及这些对象的成员指向的其他数据。 例如,Image::GetAllPropertyItems 方法返回 PropertyItem 对象的数组,每个对象对应存储在映像中的属性项(元数据片段)。 但 Image::GetAllPropertyItems 返回的不仅仅是 PropertyItem 对象的数组;它为数组添加了额外的数据。

在调用 Image::GetAllPropertyItems 之前,必须分配一个足够大的缓冲区,以容纳 PropertyItem 对象数组以及其他数据。 可以调用 Image::GetPropertySize 方法来确定所需缓冲区的总大小。

以下示例演示如何创建 Image 对象,然后调用 Image 对象的 Image::GetAllPropertyItems 方法,以检索存储在映像中的所有属性项(元数据)。 代码根据 Image::GetPropertySize 方法返回的大小值分配缓冲区。 Image::GetPropertySize 还返回一个计数值,该计数值给出了映像中的属性项数量。 请注意,代码不会将缓冲区大小计算为 count*sizeof(PropertyItem)。 以这种方式计算的缓冲区太小了。

UINT count = 0;
UINT size = 0;
Image myImage(L"FakePhoto.jpg");
myImage.GetPropertySize(&size, &count);

// GetAllPropertyItems returns an array of PropertyItem objects
// along with additional data. Allocate a buffer large enough to 
// receive the array and the additional data.
PropertyItem* propBuffer =(PropertyItem*)malloc(size);

if(propBuffer)
{
   myImage.GetAllPropertyItems(size, count, propBuffer);
   ...
   free(propBuffer);
   propBuffer = NULL;
}

错误检查

GDI+ 文档中的大多数代码示例都不显示错误检查。 完整的错误检查会使代码示例变得更长,并可能掩盖示例所说明的要点。 不应将文档中的示例直接粘贴到生产代码中;相反,应通过添加自己的错误检查来增强示例。

以下示例显示了使用 GDI+ 实现错误检查的一种方法。 每次构造 GDI+ 对象时,代码都会检查构造函数是否成功。 该检查对于依赖于读取文件的 Image 构造函数尤其重要。 如果所有四个 GDI+ 对象(GraphicsGraphicsPathImageTextureBrush)都已成功构造,则代码将调用这些对象的方法。 检查每个方法调用是否成功;如果失败,则跳过其余的方法调用。

Status GdipExample(HDC hdc)
{
   Status status = GenericError;
   INT count = 0;
   Point* points = NULL;

   Graphics graphics(hdc);
   status = graphics.GetLastStatus();
   if(Ok != status)
      return status;

   GraphicsPath path;
   status = path.GetLastStatus();
   if(Ok != status)
      return status;

   Image image(L"MyTexture.bmp");
   status = image.GetLastStatus();
   if(Ok != status)
      return status;

   TextureBrush brush(&image);
   status = brush.GetLastStatus();
   if(Ok != status)
      return status;

   status = path.AddEllipse(10, 10, 200, 100);

   if(Ok == status)
   {
      status = path.AddBezier(40, 130, 200, 130, 200, 200, 60, 200);
   }
  
   if(Ok == status)
   {
      count = path.GetPointCount();
      status = path.GetLastStatus();
   }

   if(Ok == status)
   {
      points = new Point[count];

      if(NULL == points)
         status = OutOfMemory;
   }

   if(Ok == status)
   {
      status = path.GetPathPoints(points, count);  
   }
  
   if(Ok == status)
   {
      status = graphics.FillPath(&brush, &path);
   }
   
   if(Ok == status)
   {
      for(int j = 0; j < count; ++j)
      {
         status = graphics.FillEllipse(
         &brush, points[j].X - 5, points[j].Y - 5, 10, 10);
      }
   }

   if(points)
   {
      delete[] points;
      points = NULL;
   }

   return status;
}

线程同步

多个线程可以访问单个 GDI+ 对象。 但是,GDI+ 不提供任何自动同步机制。 因此,如果应用程序中的两个线程有一个指向同一 GDI+ 对象的指针,那么你有责任同步对该对象的访问。

如果一个线程试图调用一个方法,而另一个线程正在对同一个对象执行一个方法,则一些 GDI+ 方法将返回 ObjectBusy。 不要尝试根据 ObjectBusy 返回值同步对对象的访问。 相反,每次访问对象的成员或调用对象的方法时,请将调用放在关键部分内,或使用其他标准同步技术。

安全开发人员文档

安全操作方法资源

Microsoft Security Response Center(Microsoft 安全响应中心)