わかりました...今、あなたのコメント(以下)を使用して、私の答えがあなたにとってより役立つことを願っています。したがって、私はそれを私の新しい答えに完全に置き換えています:
まず第一に、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 オブジェクトを 1 つだけ持つことができなかった理由もわかりません。Print() が再度呼び出されるまでに使用されていませんか、それとも複数のスレッドを実行していますか? 複数のスレッドを実行している場合、何らかの同期メカニズムを介して Print() を呼び出し、バッファリングされたすべてのメッセージがPrint() をもう一度呼び出すとき.... 他のコードが CurrentLine を 0 に戻すのでしょうか、それとも 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++ を学習しようとしている場合は、まとまりのある概念をクラスとしてカプセル化することをお勧めします。これは、実装およびデバッグが完了すると、生データの不適切な使用に関連する他のコードで将来バグが発生する可能性を減らすのに役立ちます。以下のコードは、すべての静的データと静的メソッドを含む構造体として実装されています。これは、オブジェクト指向プログラミングでは少し珍しいことですが、この場合、これらの「オブジェクト」が複数必要になることはありません (実際、この場合、 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();
}