【问题标题】:Heap corruption when attempting to print尝试打印时堆损坏
【发布时间】:2015-10-10 21:03:18
【问题描述】:

我一直在开发一个原生 Unity 插件,它允许用户在 Windows 上的默认打印机上打印一些东西(目前是文本)。

我的 (EDIT: OLD) 代码如下,用于打印文本:

bool PrintText(const char* pText, int pTextWidth, int pTextHeight, const char* pPrinterName) {
        LPCSTR szDriver = (LPCSTR)"WINSPOOL";
        TCHAR   szPrinter[256];
        DWORD   cchBuffer = 255;
        HDC     hdcPrint = NULL;
        HDC     hdcPrintImg = NULL;
        HANDLE  hPrinter = NULL;
        PRINTER_INFO_2  *pPrinterData;
        BYTE    pdBuffer[16384];
        BOOL    bReturn = FALSE;
        LPCSTR  documentFilename = "PrintTest";
        LPCSTR  documentText = (LPCSTR)pText;
        DWORD   cbBuf = sizeof(pdBuffer);
        DWORD   cbNeeded = 0;
        pPrinterData = (PRINTER_INFO_2 *)&pdBuffer[0];

        bReturn = GetDefaultPrinter(szPrinter, &cchBuffer);

        if (bReturn) {
            bReturn = OpenPrinter((LPSTR)pPrinterName, &hPrinter, NULL);
        }

        if (bReturn) {
            bReturn = GetPrinter(hPrinter, 2, &pdBuffer[0], cbBuf, &cbNeeded);
            ClosePrinter(hPrinter);
        }

        if (bReturn) {
            hdcPrint = CreateDC(szDriver, (LPSTR)pPrinterName, pPrinterData->pPortName, NULL);
        }

        if (hdcPrint) {
            Escape(hdcPrint, STARTDOC, 8, documentFilename, NULL);
            TextOut(hdcPrint, pTextWidth, pTextHeight, documentText, strlen((const char*)documentText));
            Escape(hdcPrint, NEWFRAME, 0, NULL, NULL);
            Escape(hdcPrint, ENDDOC, 0, NULL, NULL);

            DeleteDC(hdcPrint);
        }
        return bReturn;
    }

这会将文档发送到打印后台处理程序并成功打印,但是,我在 VS 中触发了一个断点,说堆已损坏。

我对 C++ 和非托管语言非常陌生,所以任何指针(哈!)将不胜感激 :)

编辑: 一位工作人员帮助查明了问题。 将文本打印到打印机的最少代码:

bool PrintText(char* inputText, int positionX, int positionY, char* printerName)
{
    HDC     printerDeviceContext = NULL;
    HANDLE  printerHandle = NULL;
    BOOL    bReturn = FALSE;
    LPCSTR  documentFilename = "PrintTest";
    LPCSTR  documentText = (LPCSTR)inputText;
    DWORD   buffer;
    DWORD   bytesRequired;

    bReturn = OpenPrinter((LPSTR)printerName, &printerHandle, NULL);

    GetPrinter(printerHandle, 2, NULL, 0, &buffer);
    BYTE* printerBuffer = new BYTE[buffer]; //allocate buffer
    bReturn = GetPrinter(printerHandle, 2, printerBuffer, buffer, &bytesRequired);

    ClosePrinter(printerHandle);

    printerDeviceContext = CreateDC(NULL, printerName, NULL, NULL);

    if (printerDeviceContext)
    {
        Escape(printerDeviceContext, STARTDOC, 8, documentFilename, NULL);
        TextOut(printerDeviceContext, positionX, positionY, documentText, strlen((char*)documentText));
        Escape(printerDeviceContext, NEWFRAME, 0, NULL, NULL);
        Escape(printerDeviceContext, ENDDOC, 0, NULL, NULL);

        DeleteDC(printerDeviceContext);
    }

    delete[] printerBuffer; //free buffer

    return bReturn;
}

【问题讨论】:

  • 混合char 和TCHAR 是自找麻烦。放弃两者,并在整个过程中使用wchar_t,以及Windows API 的显式Unicode 版本(例如GetDefaultPrinterW)。
  • 有关堆损坏的消息会在内存管理器发现它们时显示出来,而这些消息不一定在它们引起的位置附近。
  • @IInspectable,我同意混合使用 char 和 TCHAR 是一个坏主意,但硬编码 Unicode API 也是如此。只需制作 TCHAR 的所有内容,它就会在 32 位和 64 位版本中编译并完美运行。
  • @CareyGregory:基于什么原理,硬编码您将使用的唯一 API(即 Unicode)是一个“坏主意”?相反,它会从您的测试矩阵中删除一个参数。这与 32/64 位完全无关。
  • @IInspectable 我没有看到 OP 说他永远不想支持 ASCII,所以当免费提供为 ASCII 或 Unicode 构建的能力时​​,为什么要将自己锁定在仅限 Unicode 的构建中?如果您真的不打算使用其中一个,那么它可能会从测试矩阵中消失。同时,您正在使用所有“正常”的 Win32 函数名称,并保留将来改变主意的能力。 (显然我的意思是说 ASCII 和 Unicode,而不是我之前的评论中的 32 位和 64 位。)

标签: c++ winapi memory-management printing gdi


【解决方案1】:

我实际上没有看到任何可能导致堆损坏的直接问题。

但是,上面的代码中还有一些其他问题:

  1. CreateDC的第三个参数应该为NULL。
  2. 不应将 CreateDC 的第二个参数强制转换为 LPSTR!!! (这不是问题,因为该方法无论如何都需要 LPCSTR,但仍然:仅在极少数情况下不正确编写库的情况下才需要丢弃 const ......使用它总是有未定义行为的风险)。
  3. 常规:仅在必要时进行强制转换。 (在大多数情况下,在您的代码中,实际上并非如此)
  4. 永远不要在堆栈上分配 16k 内存 (BYTE pdBuffer[16384]):基本上你应该调用 GetPrinter() 两次:一次不提供缓冲区。此调用将失败,但它会返回实际需要的缓冲区大小。然后为该大小分配一个缓冲区并将其提供给第二次调用。

编辑: 4 实际上应该看起来像这样:

GetPrinter(hPrinter, 2, NULL, 0, &cbNeeded);
BYTE* pBuffer = new BYTE[cbNeeded]; //allocate buffer
bReturn = GetPrinter(hPrinter, 2, pBuffer, cbNeeded, &cbActual);

// do something with pBuffer

delete[] pBuffer; //free buffer

编辑 2: 基本上有三件事会出错:

  • 你尝试释放已经被释放的内存

    delete pObject; //some code delete pObject;

  • 你尝试使用已经被释放的内存

    delete pObject; pObject->use();

  • 你在某处的堆上发生了缓冲区溢出

    pBuffer = new BYTES[3] memcpy(pBuffer, pSomeMemory, 100); //copy 100 bytes into a 3 byte buffer delete[] pBuffer;

实际错误出现在代码的其他部分的可能性非常高。

【讨论】:

  • 请原谅我的无知,但对于第 4 点。我将如何重组我的代码来做到这一点?我是否只是将所有 NULL 传递给第一个 GetPrinter() 调用?以及如何声明并将大小传递给我的pdBuffer?
  • 感谢您的解释。我仍然在 OpenPrinter 调用周围遇到堆损坏中断(有时会在此之前或之后稍微发生)。仍然不完全确定发生了什么。我的类型好吗?
猜你喜欢
  • 2015-10-06
  • 1970-01-01
  • 1970-01-01
  • 2015-08-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多