영어로 읽기

다음을 통해 공유


C++ Q&A

Singleton Class Private Constructor, C# Singleton Class, and More

Paul DiLascia

Code download available at:CQA0302.exe(40 KB)

QI have a C++ singleton class, and I have done the preliminaries of making the constructor private. I wrote a static function that returns a reference to the one and only object, restricting all means of creating the object except the following:

// This should not compile CSingleton temp = CSingleton::GetObjectInstance();

How can I prevent this line from compiling?

QI have a C++ singleton class, and I have done the preliminaries of making the constructor private. I wrote a static function that returns a reference to the one and only object, restricting all means of creating the object except the following:

// This should not compile CSingleton temp = CSingleton::GetObjectInstance();

How can I prevent this line from compiling?

Nilesh Prakash Padbidri

AWhen the C++ compiler encounters the statement

CSingleton temp = CSingleton::GetInstance();

it looks for a copy constructor, which is a constructor with the signature CSingleton(const CSingleton&). If you don't provide a copy constructor, C++ will provide one for you. The default copy constructor does a simple flat copy of the bytes from one object to the other. If you want something else, you'll have to implement your own copy constructor. In this case, to disallow copying, you can make it private.

class CSingleton { private: // private copy constructor CSingleton(const CSingleton& obj) { ASSERT(FALSE); } // should never happen };

AWhen the C++ compiler encounters the statement

CSingleton temp = CSingleton::GetInstance();

it looks for a copy constructor, which is a constructor with the signature CSingleton(const CSingleton&). If you don't provide a copy constructor, C++ will provide one for you. The default copy constructor does a simple flat copy of the bytes from one object to the other. If you want something else, you'll have to implement your own copy constructor. In this case, to disallow copying, you can make it private.

class CSingleton { private: // private copy constructor CSingleton(const CSingleton& obj) { ASSERT(FALSE); } // should never happen };

If you do this, you'll also have to implement the default (no-argument) constructor, if you haven't already, because C++ only generates a default constructor if no others are defined. Without the default constructor, you won't be able to instantiate the singleton. You can make the default constructor protected or private, depending on whether you want to let other classes derive from CSingleton. Figure 1 shows the details. So the short answer to your question is: implement a private copy constructor.

Figure 1 Singleton.cpp

// This program illustrates how to write a singleton class (a class that // can have only one instance) in C++. The trick is to make the default // constructor, copy constructor and assignment operator all private. A // static function GetInstance returns the one and only object instance. // // If you attempt to compile this program, it will generate errors. // (See main function below.) // class CSingleton { public: static CSingleton& GetInstance() { static CSingleton theInstance; // one and only instance return theInstance; } protected: // need default ctor for GetInstance. // ctor is protected, not private in case you want to derive. CSingleton() { } private: CSingleton(const CSingleton& o) { } CSingleton& operator=(const CSingleton& o) { } }; main() { // These lines will not compile: CSingleton x = CSingleton::GetInstance(); // error: private // copy ctor! CSingleton y = CSingleton::GetInstance(); // error: private // copy ctor! x = y; // error: private // assignment! }

QI've been programming in C++ for a long time, and I'm familiar with a number of patterns like the singleton (a global object with a private constructor). Now I'm starting a project in C# using Microsoft® .NET and I'd like to know the best way to create a singleton class in C#.

QI've been programming in C++ for a long time, and I'm familiar with a number of patterns like the singleton (a global object with a private constructor). Now I'm starting a project in C# using Microsoft® .NET and I'd like to know the best way to create a singleton class in C#.

Marianne Simone

ASingleton classes are even easier in C# than they are in C++ because the .NET Framework has the notion of singletons built in. Here's the C# Singleton pattern distilled:

sealed class Singleton { private Singleton() { } public static readonly Singleton TheInstance = new Singleton(); }

ASingleton classes are even easier in C# than they are in C++ because the .NET Framework has the notion of singletons built in. Here's the C# Singleton pattern distilled:

sealed class Singleton { private Singleton() { } public static readonly Singleton TheInstance = new Singleton(); }

As in C++, you can use a private constructor to prevent programmers from creating instances of Singleton. To prohibit inheritance, declare your class sealed. (In C++ you can do this by making all constructors private.) Instead of a static object inside a static GetInstance function, as shown in Figure 1, C# lets you create a read-only static property (in this case, Singleton.TheInstance) initialized to a new instance of the class. This accomplishes the same thing as the C++ pattern with less typing: the framework won't actually create and initialize TheInstance until someone attempts to use it, which you can do like so:

Singleton s = Singleton.TheInstance;

The framework even takes care of synchronization problems that can arise in multithreaded situations. Figure 2 shows a C# Singleton you can actually compile and run. For more on singletons, see the article titled "Exploring the Singleton Design Pattern" in the MSDN® library.

Figure 2 Singleton.cs

// Singleton — list top-level visible windows // using System; sealed class Singleton { private Singleton() { } public static readonly Singleton TheInstance = new Singleton(); public void SayHello() { Console.WriteLine("hello,world"); } } class MyApp { // global command-line switches [STAThread] // main entry point static int Main(string[] args) { // Singleton s = new Singleton(); // error! Singleton s = Singleton.TheInstance; s.SayHello(); return 0; } }

QI read your column in the August 2002 issue of MSDN Magazine and found it very helpful. My problem is similar to your delegates example. I pass a C# delegate into unmanaged C++, and C++ makes the callback whenever it needs to. However, one of the members in the signature of the callback is an int array, and when it does get back into C#-land, C# thinks the array is of length one and only contains the first member of the array, no matter how big the array is. I've looked through many books and Web sites, but not many give help on interop issues between C# and unmanaged C++. I would be grateful if you could give me some ideas.

QI read your column in the August 2002 issue of MSDN Magazine and found it very helpful. My problem is similar to your delegates example. I pass a C# delegate into unmanaged C++, and C++ makes the callback whenever it needs to. However, one of the members in the signature of the callback is an int array, and when it does get back into C#-land, C# thinks the array is of length one and only contains the first member of the array, no matter how big the array is. I've looked through many books and Web sites, but not many give help on interop issues between C# and unmanaged C++. I would be grateful if you could give me some ideas.

Vince Kwok

AWhile Microsoft .NET makes mixing managed and unmanaged code as painless as possible, the finer details of interop can sometimes be intimidating and elusive. But the designers were actually quite thorough in this area, so when it comes to interop, it's usually safe to assume that if you can't make things work, it's because you haven't yet divined the magic voodoo. Life can be even more confusing when callbacks are involved because of the added indirection. Even certified C++ gurus sometimes have to scratch their heads to recall the proper typedef syntax for callbacks. Never fear, I'm here to help.

AWhile Microsoft .NET makes mixing managed and unmanaged code as painless as possible, the finer details of interop can sometimes be intimidating and elusive. But the designers were actually quite thorough in this area, so when it comes to interop, it's usually safe to assume that if you can't make things work, it's because you haven't yet divined the magic voodoo. Life can be even more confusing when callbacks are involved because of the added indirection. Even certified C++ gurus sometimes have to scratch their heads to recall the proper typedef syntax for callbacks. Never fear, I'm here to help.

I wrote a little program called TestArray that shows how to pass int arrays between managed and unmanaged code in a variety of situations. TestArray actually comprises two pieces: an unmanaged DLL (MyLib.dll) written in C++ and a .NET-centric console application written in C#. Figure 3 shows the code and makefile; Figure 4 shows its results. TestArray runs three tests. The first passes a .NET Array of ints from C# to an unmanaged C function, ArrayTest in MyLib.dll:

extern "C" __declspec( dllexport ) int ArrayTest(int ar[], int count) { ••• // printf to print the array return count; }

Figure 4 Running TestArray

Figure 3 TestArray

MyLib.cpp

// To build: // // cl /LD MyLib.cpp // #include <stdio.h> // This is normally defined in windows.h: #define CALLBACK __stdcall // callback function takes array of ints and length typedef void (CALLBACK* ARRAYCB)(int ar[], int len); // fwd ref static void PrintArray(int* ar, int len); /////////////////// // Simple array test: unmanaged C function receives array, length and // displays it on console. // extern "C" __declspec( dllexport ) void TestArray(int ar[], int len) { // display array on stdout printf(" [c++] inside TestArray: ar="); PrintArray(ar, len); } /////////////////// // Callback test: unmanaged C function receives a callback that takes an // array and count. // extern "C" __declspec( dllexport ) void TestArrayCallback(ARRAYCB cbfn) { // create an array int len = 10; int* parray = new int[len]; for (int i=0; i<len; i++) { parray[i] = len-i; } // pass it to callback printf(" [c++] inside TestArrayCallback, cbfn=%p, calling it now...\n", cbfn); (*cbfn)(parray, len); } /////////////////// // Modify array test: unmanaged C function receives an array, modifies // it, and returns to .NET caller. The array must be passed as a C# ref, // so it's declared here as a ptr-to-array. The length is also passed as // ref so it's a ptr-to-int. The function replaces each item in the // array with its value squared. Its function doesn't alter the size of // the array, but it could. // extern "C" __declspec( dllexport ) void TestModifyArray(int** pArray, int* pLen) { int len = *pLen; int* ar = *pArray; printf(" [c++] inside TestModifyArray, *pArray="); PrintArray(ar, len); printf(" [c++] squaring elements...\n"); // modify array: square each element for (int i=0; i<len; i++) { ar[i] *= ar[i]; } } ////////////////// // Helper fn to print array on stdout. // static void PrintArray(int* ar, int len) { printf("["); for (int i=0; i<len; i++) { if (i>0) printf(","); printf("%d", ar[i]); } printf("], len=%d\n", len); }

TestArray.cs

// This program illustrates how to pass integer arrays between C# and // C++ in various ways, and in particular how to pass an array from C++ // to a C# callback without having the array length collapse to 1. The // trick is to use MarshalAs with SizeParamIndex to tell the framework // which callback parameter contains the array size. // // To build: // // csc TestArray.cs // using System; using System.Runtime.InteropServices; ////////////////// // This class wraps the unmanaged C++ functions in MyLib.dll. // class LibWrap { [DllImport("MyLib.dll")] public static extern void TestArray(int[] ar, int len); // callback delegate declaration. SizeParamIndex = 1 tells .NET that // the length of the array is in the 1st (2nd from 0-offset) param. // public delegate void ArrayCB( [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] int[] ar, int len); [DllImport("MyLib.dll")] public static extern void TestArrayCallback(ArrayCB cb); [DllImport("MyLib.dll")] public static extern void TestModifyArray( ref IntPtr array, ref int len ); } ////////////////// // App class with Main entry // class MyApp { // callback function simply displays array contents private static void MyCallbackFn(int[] ar, int len) { Console.Write(" [c# ] inside MyCallbackFn with array="); WriteArray(ar); } // main entry point [STAThread] static int Main(string[] args) { // create array int len = 7; int[] array = new int[len]; for (int i=0; i<len; i++) { array[i] = i+1; } // Run simple array test. Console.WriteLine("TEST 1 — Pass array from .NET to C++"); Console.Write(" [c# ] calling TestArray with array="); WriteArray(array); LibWrap.TestArray(array, len); // Run callback test Console.WriteLine("\nTEST 2 — Pass array callback from .NET to C++:"); Console.WriteLine(" [c# ] calling TestArrayCallback..."); LibWrap.ArrayCB mycb = new LibWrap.ArrayCB(MyCallbackFn); LibWrap.TestArrayCallback(mycb); // Run modify array test. This requires copying the array to an // InPtr buffer allocated with Marshal.AllocCoTaskMem. // Console.WriteLine("\nTEST 3 — Pass modifiable array from .NET to C++"); IntPtr buf = Marshal.AllocCoTaskMem(Marshal.SizeOf(array[0]) * array.Length); Marshal.Copy(array, 0, buf, array.Length ); Console.Write(" [c# ] calling TestModifyArray with array="); WriteArray(array); LibWrap.TestModifyArray(ref buf, ref len); // call DLL array = new int[len]; // new array w/returned length Marshal.Copy(buf, array, 0, len); // copy buffer to array Marshal.FreeCoTaskMem(buf); // free buffer Console.Write(" [c# ] TestModifyArray returns array="); WriteArray(array); return 0; } // helper to print array on console static private void WriteArray(int[] ar) { Console.Write("["); for (int i=0; i<ar.Length; i++) { if (i>0) Console.Write(","); Console.Write(ar[i]); } Console.WriteLine("], len={0}", ar.Length); }

makefile

################################################################ # MSDN Magazine — February 2003 # If this code works, it was written by Paul DiLascia. # If not, I don't know who wrote it. # Compiles with Visual Studio .NET on Windows XP. Tab size=3. # # To build, type "NMAKE" in a console window. Make sure your LIB and # INCLUDE variables are set up as per the Visual Studio vsvars32.bat. # all: MyLib.dll TestArray.exe MyLib.dll: MyLib.cpp cl /LD MyLib.cpp TestArray.exe: MyLib.dll TestArray.cs csc TestArray.cs

TestArray takes an ordinary C array of ints and its length and displays them on stdout. Extern "C" is required to suppress C++ name-mangling and __declspec(dllexport) exports the function. This is C 101; MyLib.dll could have been written 10 years ago.

To make TestArray known to C#, you have to wrap it the .NET interop way, like this:

using System.Runtime.InteropServices; class LibWrap { [DllImport("MyLib.dll")] public static extern void TestArray(int[] ar, int len); }

Now you can call TestArray with a .NET Array and the framework will automagically marshal it properly:

int[] array = // create, initialize LibWrap.TestArray(array, array.Length);

The interop services in the common language runtime (CLR) convert the .NET Array into a C array of ints. Remember, in .NET an array is not a block of memory stuffed with ints one after another, as in C. In .NET, System.Array is a full-blown class with rank, length, and lower bound, as well as data. To pass the length to C++, you simply pass Array.Length as a separate parameter.

But what about going the other way? That is, from unmanaged to managed code. In particular, how can you pass an array to a callback method? The second test shows how. This is where life gets a little tricky because if you declare your callback parameter as int[], you'll discover, as did Vince, that the array arrives with a length of 1 every time. C-style arrays don't have their lengths built in, whereas .NET Arrays do. So how do you tell .NET the array length when going from C++ to C#? The answer lies in the magic MarshalAs attribute and special field SizeParamIndex:

class LibWrap { public delegate void ArrayCB( [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] int[] ar, int len); }

In general, MarshalAs lets you tell .NET how to marshal parameters when you don't like the default. Here, marshaling as LPArray (C-style array) is fine, but you need SizeParamIndex set to 1 to tell .NET that the length is the second parameter (the zero-based index is 1). If you have a fixed-length array, you can use SizeConst to specify a fixed size—for example, SizeConst = 50 if the array always has 50 elements. You only need SizeParamIndex in the delegate declaration, not the actual callback:

// don't need MarshalAs here private static void MyCallbackFn(int[] ar, int len) { // ... ar.Length is same as len }

The C++ side of things is straightforward. The only challenge is remembering how to declare a pointer-to-function in C. Here it is:

// ptr-to-fn typedef void (*ARRAYCB)(int ar[], int len);

So now the C function that runs the callback test looks like this:

extern "C" __declspec( dllexport ) void TestArrayCallback(ARRAYCB cbfn) { int len = // length of array int* array = // create, initialize (*cbfn)(array, len); // call it }

Now if you write the following lines everything works fine, courtesy of .NET:

LibWrap.ArrayCB mycb = new LibWrap.ArrayCB(MyCallbackFn); LibWrap.TestArrayCallback(mycb);

When TestArrayCallback calls cbfn, the interop services convert the C-style array into a .NET Array upon arrival, with Array.Length set to whatever you pass as the len parameter. Amazing! To see for yourself, download the code, compile, and go (for full source code, see the link at the top of this article). So the short answer to your question is: you must declare the array parameter of your delegate with MarshalAs and SizeParamIndex equal to the index of the parameter that holds the size.

I've answered Vince's question, but there's one more situation I should mention briefly before saying bye-bye for today. The first test shows how to pass an array from C# to C++, and the second test shows how to pass an array the other way through a callback. Curious minds might wonder if you can pass an array from managed to unmanaged code in a way that lets the unmanaged code modify the array directly. In fact, you can, and that's what the third test in my sample program does.

You might think all you have to do is declare and pass the array as a reference (ref), which makes sense but, alas, doesn't work. If you try, you'll discover the array goes from C# to C++ just fine, but always comes back with the length set to 1. Sigh. So then you might think to try the SizeParamIndex trick again, but this time the compiler complains, muttering something like, "can't use SizeParamIndex with reference array..." Double sigh.

So how can you pass a modifiable array from managed to unmanaged code? TestArray shows the way: you must pass the array as an IntPtr. This requires manually copying the array to and from a buffer that you previously allocate with Marshal.AllocCoTaskMem. It's ugly—but hey, it works! Figure 3 shows the gory details.

Like I said at the outset, the .NET interop services have everything you need to marshal all kinds of parameters any way you like. For more details see "Default Marshaling Behavior" and in particular, "Default Marshaling for Arrays" in the .NET documentation. And make sure to bring along your universal nerdspeak de-crypto translator!

Minor correction: In my recent article, ".NET GUI Bliss," which appeared in the November 2002 issue, I stated that "XUL was developed by the Java language folks for Mozilla." While there are no doubt many Java folks who've contributed to XUL, XUL is, properly speaking, a Mozilla effort. Sorry for the confusion.

Send your questions and comments for Paul to  cppqa@microsoft.com.

Paul DiLasciais a freelance writer, consultant, and Web/UI designer-at-large. He is the author of Windows++: Writing Reusable Windows Code in C++ (Addison-Wesley, 1992). Paul can be reached at askpd@pobox.com or https://www.dilascia.com.