好的...现在有了您的评论(如下),我的答案有望对您更有用,因此我将完全用我的新答案替换它:
首先,您的 PaintArray[] 缓冲区是指针数组,而不是 PrintLines 对象数组。您访问它们的方式(使用 -> 运算符)也将它们视为指针,因此它可以编译。但是您永远不会分配真正的 PrintLines 对象来分配给缓冲区,因此当您在调试器中中断 vsprintf() 调用时会发现 NULL 。
在我看来,实际上没有必要动态分配这些 PrintLines 对象,因此您可以将 PaintArray 定义为 PrintLine 对象的数组并完成它(即分配):
PrintLines PaintArray[MAX_PRINT_LINES]; // note this is an array of objects, not pointers
...但是任何时候访问它们时,都需要使用点运算符,而不是箭头运算符。我不知道您是否/为什么需要在“if (CurrentLine >= MAX_PRINT_LINES)”中将这些对象归零,而且我不确定 InvalidateRect() 调用对您有什么作用。我也不确定为什么你不能只拥有一个你不断重复使用的全局 PrintLines 对象;再次调用 Print() 时不会使用它,还是您正在运行多个线程?如果您正在运行多个线程,那么我怀疑您的“if (CurrentLine >= MAX_PRINT_LINES)”内容的正确性,除非您通过某种同步机制以某种方式调用 Print() 并且知道所有缓冲的消息都已完成发送您再次调用 Print() 的时间.... 其他代码是否会将 CurrentLine 设置回零,或者 Print() 是 CurrentLine 变量的唯一用户吗?如果 Print() 是它的唯一用户,那么 (1),您可以将其设为局部静态变量而不是全局变量,并且 (2),我认为您有一些真正的正确性问题:要么您真的不需要这些东西的数组,否则当 CurrentLine 回到 0 时,您不应该将它们全部清除。
一个结构良好的非平凡 C++ 程序将使其组件与公共和私有(如果它在继承层次结构中,则受保护)接口清楚地隔离。这是通过类完成的。类是与“方法”(函数)相关联的数据结构。当您将代码分解成小的、有凝聚力的单元,并将整个项目构建为协同工作的对象集合时,事情就会非常顺利,而不是将您的项目构建为一个单一的混乱代码。来自 asm 背景,这对你来说将是一个巨大的飞跃。我知道,我也开始用 asm 编程……为 Intel 8051,然后是 8088,然后是 Motorola 68K 和 PowerPC 850/860,并加入了一点 Sparc。从 asm 到 C 的步骤是最小的,如果你只是在 C++ 中以过程风格编程,那也不是一个大的飞跃,但如果你希望成为一名有市场的程序员,你真的需要向面向对象编程迈进。现在有一些 OO 狂热者会提倡以严格的 OO 风格进行编程,但是也有很多项目将它们的大部分组件实现为对象,但它们的主要监督/控制代码以仅使用对象的过程代码实现......如果你能做到这一点,那可能是一个非常好的开始。
在这种情况下,我编写了您的代码版本,将您的缓冲区封装在一个环形缓冲区类中。这假设您实际需要的只是看似永无止境的 PrintLines 对象的缓冲区(只要消费者跟上,环形缓冲区就不会填满,就永远不会结束)。如果您正在尝试学习 C++,我建议您开始将内聚概念封装为类,一旦实现和调试,有助于减少与不正确使用原始数据相关的其他代码中未来出现错误的可能性。下面的代码被实现为一个包含所有静态数据和静态方法的结构,这对于 OO 编程来说有点不寻常,但在这种情况下,你永远不需要这些“对象”中的一个(实际上在这种情况下你不会甚至有一个 PaintBuffers“对象”,你只有类 & 一堆静态数据和方法)。有些人会主张在这种情况下使用“单例”模式,但这超出了 OO 的边缘,你并不真正需要它。这使您更接近 OO 思维,并且更容易(几乎)直接从 asm 代码访问。我希望它对你有用。
#include <stdio.h>
#include <stdarg.h>
// ================================== This would go in a .h file.....
struct PrintLines // My own personal stand-in for whatever a "PrintLines" object is
{
int rgb;
int stringlen;
char string[400];
};
class PaintBuffers // encapsulates a circular buffer of PrintLines objects
{
public: // Public data: Anyone can have direct access to this stuff....
static const unsigned int maxPrintLines = 4; // formerly #define MAX_PRINT_LINES
private: // Private data: Only this class's methods can access this stuff....
static PrintLines PaintArray[maxPrintLines]; // note these are real objects, not pointers
static unsigned int producerIdx; // for data coming into this class
static unsigned int consumerIdx; // for data going out of this class
public: // Public methods: Anyone can call these methods....
static int numUsedBuffers() { return (producerIdx-consumerIdx) % maxPrintLines; }
// Side note, but important: The % above gives us what we want only if the
// lhs (left-hand-side) is positive. One way to ensure that is by simply
// treating the terms as unsigned; even though subtracting a larger
// number from a smaller "wraps around" to a very large number, after
// the % operation we still get what we want, so there's no need to
// compute the absolute value of a signed subtraction if we just make
// them unsigned (or cast them as unsigned) in the first place.
static int numFreeBuffers() { return maxPrintLines - numUsedBuffers(); }
// Producer calls this: Get the next 'write' buffer (to write into it)
static PrintLines* getWriteBuf()
{
if (numFreeBuffers() > 1) // The >1 implements the "always keep one slot open"
{ // solution to the full/empty ambiguity problem, thus
// there will ALWAYS be at least one unused buffer.
// There are alternative solutions that allow use of
// that one last buffer, but none which results in
// more efficient code.
PrintLines* ret = &PaintArray[producerIdx];
producerIdx = (producerIdx+1) % maxPrintLines;
// ...Note that if maxPrintLines is a power-of-2 (smart programmers only make
// circular buffers that are sized as powers-of-2), the compiler will
// automatically turn that % operation into an equivalent & for efficiency.
return ret;
}
else
{
return NULL; // Tell the caller there's no more buffer space.
}
}
// Consumer calls this: Get the next 'read' buffer (to read data from it)
static PrintLines* getReadBuf()
{
if (numUsedBuffers() > 0)
{
PrintLines* ret = &PaintArray[consumerIdx];
consumerIdx = (consumerIdx+1) % maxPrintLines;
return ret;
}
else
{
return NULL; // Tell the caller there's no data available.
}
}
};
// Because you can't (easily) call a C++ name-mangled function from assembly,
// I'll define a "C"-linkage interface to the PaintBuffers class below. Once
// your whole ASM project is ported to C++, you can blow the ASM interface away.
extern "C" int PaintBuffers_numUsedBuffers();
extern "C" int PaintBuffers_numFreeBuffers();
extern "C" PrintLines* PaintBuffers_getWriteBuf();
extern "C" PrintLines* PaintBuffers_getReadBuf();
// ================================== This would go in a .cpp file.....
// In the .h file, we declared that there are such functions (somewhere), now
// we need to actually define them....
extern "C" int PaintBuffers_numUsedBuffers() { return PaintBuffers::numUsedBuffers(); }
extern "C" int PaintBuffers_numFreeBuffers() { return PaintBuffers::numFreeBuffers(); }
extern "C" PrintLines* PaintBuffers_getWriteBuf() { return PaintBuffers::getWriteBuf(); }
extern "C" PrintLines* PaintBuffers_getReadBuf() { return PaintBuffers::getReadBuf(); }
// In the .h file, we declared that there are such variables (somewhere), now
// we need to actually define them....
PrintLines PaintBuffers::PaintArray[PaintBuffers::maxPrintLines];
unsigned int PaintBuffers::producerIdx=0;
unsigned int PaintBuffers::consumerIdx=0;
// Note that all of the PaintBuffers class's methods were defined inline in the
// class itself. You could also just declare them there (in the class definition),
// and define them here in a .cpp file.
void Print(/*HWND hWnd,*/ int rgb, const char* string, ...)
{
PrintLines* PaintObject = PaintBuffers::getWriteBuf();
if (!PaintObject) // Is it NULL?
{ // What should we do if there is no more buffer space???
return; // I guess just do nothing... Lost message.
}
// TODO: Is this needed somehow?.... InvalidateRect(hWnd, NULL, TRUE);
// MSG msg;
va_list argList;
va_start(argList, string);
PaintObject->stringlen = vsnprintf(PaintObject->string, sizeof(PaintObject->string)-1, string, argList);
va_end (argList);
PaintObject->rgb = rgb;
// msg.hwnd = hWnd;
// msg.message = WM_PAINT;
// DispatchMessage(&msg);
}
void Consume() // ...my stand-in for whatever your consumer is (still in ASM?)
{
PrintLines* PaintObject = PaintBuffers::getReadBuf();
if (PaintObject) // Was it non-NULL?
{
printf("Consume(): Got \"%s\"\n", PaintObject->string);
}
else // This is only here to show that we did get NULL.....
{
printf("Consume(): Got NULL! (no buffers with data in them)\n");
}
}
int main()
{
Consume();
Consume();
Print(0x11111111, "The %dst message.", 1);
Print(0x11111111, "The %dnd message.", 2);
Consume();
Consume();
Print(0x11111111, "The %drd message.", 3);
Print(0x11111111, "The %dth message.", 4);
Consume();
Print(0x11111111, "The %dth message.", 5);
Consume();
Consume();
Consume();
Consume();
Consume();
Print(0x11111111, "The %dth message.", 6);
Print(0x11111111, "The %dth message.", 7);
Print(0x11111111, "The %dth message.", 8);
Print(0x11111111, "The %dth message.", 9); // ...will be lost (no more buffer space)
Print(0x11111111, "The %dth message.", 10); // ...will be lost (no more buffer space)
Print(0x11111111, "The %dth message.", 11); // ...will be lost (no more buffer space)
Consume();
Consume();
Consume();
Consume();
Consume();
Consume();
Consume();
}