multithreaded로 컴파일되는 것을 multithreaded dll 로 바꾸어도 컴파일이나 링크 모두 잘됩니다.

 

multithreaded dll을 multithreaded 로 바꾸면 에러가 나는경우가 생기는데...

fatal error C1189: #error :  Please use the /MD switch for _AFXDLL builds

_AFXDLL 을 정의해놓고 세팅은 왜 이따위로 했는냐 이런 의미인거 같은데... 정확하게 원리가 어떻게 되는것인지

 

/MD, /MT의 컴파일 옵션의 차이점은 컴파일 시 .obj 파일에 어떤 c runtime library를 삽입 하느냐의 차이입니다.

 

/MD compile option (multithreaded dll)

    c runtime library(MSVCPRT.LIB)를 컴파일 하는 .cpp파일의 .obj 파일에 삽입하여,

    external symbols와 MSVCP71.dll을 통하여 동적 연결(dinamic link)할 수 있게 하고,

    MSVCPRT.LIB는 MSVCP71.dll을 사용할 수 있도록 구성됩니다.

 

/MT compile option (multithreaded)

    c runtime library(LIBCPMT.LIB)를 컴파일 하는 .cpp파일의 .obj 파일에 삽입하여,

    external symbols와 정적 연결(static link)를 하게 합니다.

 

MFC의 APP wizard를 통하여 Project를 생성 시,

MFC를 static library를 사용하게 만들 경우 /MT compile option이 붙고

shared dll 모드로 만들 경우 /MD compile option이 붙는걸 확인했습니다.

Posted by '김용환'
,

CString Management

c++ 2006. 1. 7. 06:08

 

CString Management

Home
Back To Tips Page

CStrings are a useful data type. They greatly simplify a lot of operations in MFC, making it much more convenient to do string manipulation. However, there are some special techniques to using CStrings, particularly hard for people coming from a pure-C background to learn. This essay discusses some of these techniques.

Much of what you need to do is pretty straightforward. This is not a complete tutorial on CStrings, but captures the most common basic questions. 

String Concatenation

One of the very convenient features of CString is the ability to concatenate two strings. For example if we have

CString gray("Gray");
CString cat("Cat");
CString graycat = gray + cat;

is a lot nicer than having to do something like:

char gray[] = "Gray";
char cat[] = "Cat";
char * graycat = malloc(strlen(gray) + strlen(cat) + 1);
strcpy(graycat, gray);
strcat(graycat, cat);

Note that the above code is not "Unicode-aware", that is, it only works in compilations of ANSI applications.  The correct Unicode-aware representation would be

CString gray(_T("Gray"));
CString cat(_T("Cat"));
CString graycat = gray + cat;

Formatting (including integer-to-CString)

Rather than using sprintf or wsprintf, you can do formatting for a CString by using the Format method:

CString s;
s.Format(_T("The total is %d"), total);

The advantage here is that you don't have to worry about whether or not the buffer is large enough to hold the formatted data; this is handled for you by the formatting routines.

Use of formatting is the most common way of converting from non-string data types to a CString, for example, converting an integer to a CString:

CString s;
s.Format(_T("%d"), total);

I always use the _T( ) macro because I design my programs to be at least Unicode-aware, but that's a topic for some other essay. The purpose of _T( ) is to compile a string for an 8-bit-character application as:

#define _T(x) x // non-Unicode version

whereas for a Unicode application it is defined as

#define _T(x) L##x // Unicode version

so in Unicode the effect is as if I had written

s.Format(L"%d", total);

If you ever think you might ever possibly use Unicode, start coding in a Unicode-aware fashion. For example, never, ever use sizeof( ) to get the size of a character buffer, because it will be off by a factor of 2 in a Unicode application. We cover Unicode in some detail in Win32 Programming. When I need a size, I have a macro called DIM, which is defined in a file dim.h that I include everywhere:

#define DIM(x) ( sizeof((x)) / sizeof((x)[0]) )

This is not only useful for dealing with Unicode buffers whose size is fixed at compile time, but any compile-time defined table.

class Whatever { ... };
Whatever data[] = {
   { ... },
    ...
   { ... },
};

for(int i = 0; i < DIM(data); i++) // scan the table looking for a match 

Beware of those API calls that want genuine byte counts; using a character count will not work.

TCHAR data[20];
lstrcpyn(data, longstring, sizeof(data) - 1); // WRONG!
lstrcpyn(data, longstring, DIM(data) - 1); // RIGHT
WriteFile(f, data, DIM(data), &bytesWritten, NULL); // WRONG!
WriteFile(f, data, sizeof(data), &bytesWritten, NULL); // RIGHT

This is because lstrcpyn wants a character count, but WriteFile wants a byte count. Also note that this always writes out the entire contents of data. If you only want to write out the actual length of the data, you would think you might do

WriteFile(f, data, lstrlen(data), &bytesWritten, NULL); // WRONG

but that will not work in a Unicode application. Instead, you must do

WriteFile(f, data, lstrlen(data) * sizeof(TCHAR), &bytesWritten, NULL); // RIGHT

because WriteFile wants a byte count. (For those of you who might be tempted to say "but that means I'll always be multiplying by 1 for ordinary applications, and that is inefficient", you need to understand what compilers actually do. No real C or C++ compiler would actually compile a multiply instruction inline; the multiply-by-one is simply discarded by the compiler as being a silly thing to do. And if you think when you use Unicode that you'll have to pay the cost of multiplying by 2, remember that this is just a bit-shift left by 1 bit, which the compiler is also happy to do instead of the multiplication).

Using _T does not create a Unicode application. It creates a Unicode-aware application. When you compile in the default 8-bit mode, you get a "normal" 8-bit program; when you compile in Unicode mode, you get a Unicode (16-bit-character) application. Note that a CString in a Unicode application is a string that holds 16-bit characters.

Converting a CString to an integer

The simplest way to convert a CString to an integer value is to use one of the standard string-to-integer conversion routines.

While generally you will suspect that _atoi is a good choice, it is rarely the right choice. If you play to be Unicode-ready, you should call the function _ttoi, which compiles into _atoi in ANSI code and _wtoi in Unicode code. You can also consider using _tcstoul (for unsigned conversion to any radix, such as 2, 8, 10 or 16) or _tcstol (for signed conversion to any radix). For example, here are some examples:

CString hex = _T("FAB");
CString decimal = _T("4011");
ASSERT(_tcstoul(hex, 0, 16) == _ttoi(decimal));

Converting between char * and CString

This is the most common set of questions beginners have on the CString data type. Due largely to serious C++ magic, you can largely ignore many of the problems. Things just "work right". The problems come about when you don't understand the basic mechanisms and then don't understand why something that seems obvious doesn't work.

For example, having noticed the above example you might wonder why you can't write

CString graycat = "Gray" + "Cat";

or

CString graycat("Gray" + "Cat");

In fact the compiler will complain bitterly about these attempts. Why? Because the + operator is defined as an overloaded operator on various combinations of the CString and LPCTSTR data types, but not between two LPCTSTR data types, which are underlying data types. You can't overload C++ operators on base types like int and char, or char *. What will work is 

CString graycat = CString("Gray") + CString("Cat");

or even

CString graycat = CString("Gray") + "Cat";

If you study these, you will see that the + always applies to at least one CString and one LPCSTR.

Note that it is always better to write Unicode-aware code, e.g.,

CString graycat = CString(_T("Gray")) + _T("Cat");

and so on. This makes your code immediately portable.

char * to CString

So you have a char *, or a string. How do you create a CString. Here are some examples:

char * p = "This is a test"

or, in Unicode-aware applications

TCHAR * p = _T("This is a test")

or

LPTSTR p = _T("This is a test");

you can write any of the following:

CString s = "This is a test";     // 8-bit only
CString s = _T("This is a test"); // Unicode-aware
CString s("This is a test");      // 8-bit only
CString s(_T("This is a test"));  // Unicode-aware
CString s = p;
CString s(p);

Any of these readily convert the constant string or the pointer to a CString value. Note that the characters assigned are always copied into the CString so that you can do something like

TCHAR * p = _T("Gray");
CString s(p);
p = _T("Cat");
s += p;

and be sure that the resulting string is "GrayCat".

There are several other methods for CString constructors, but we will not consider most of these here; you can read about them on your own.

Actually, it is a bit subtler than I show. For example

CString s = "This is a test"; 

is sloppy programming, but actually will compile correctly for Unicode. What it does is invoke the MultiByteToWideChar operation of the CString constructor to convert, at run-time, the 8-bit character string to a 16-bit Unicode character string. However, this can still be useful if the char * pointer refers, for example, to 8-bit data that just came in over the network.

CString to char * I: Casting to LPCTSTR

This is a slightly harder transition to find out about, and there is lots of confusion about the "right" way to do it. There are quite a few right ways, and probably an equal number of wrong ways.

The first thing you have to understand about a CString is that it is a special C++ object which contains three values: a pointer to a buffer, a count of the valid characters in the buffer, and a buffer length. The count of the number of characters can be any size from 0 up to the maximum length of the buffer minus one (for the NUL byte). The character count and buffer length are cleverly hidden.

Unless you do some special things, you know nothing about the size of the buffer that is associated with the CString. Therefore, if you can get the address of the buffer, you cannot change its contents. You cannot shorten the contents, and you absolutely must not lengthen the contents. This leads to some at-first-glance odd workarounds.

The operator LPCTSTR (or more specifically, the operator const TCHAR *), is overloaded for CString. The definition of the operator is to return the address of the buffer. Thus, if you need a string pointer to the CString you can do something like

CString s("GrayCat");
LPCTSTR p =  s;

and it works correctly. This is because of the rules about how casting is done in C; when a cast is required, C++ rules allow the cast to be selected. For example, you could define (float) as a cast on a complex number (a pair of floats) and define it to return only the first float (called the "real part") of the complex number so you could say

Complex c(1.2f, 4.8f);
float realpart = c;

and expect to see, if the (float) operator is defined properly, that the value of realpart is now 1.2.

This works for you in all kinds of places. For example, any function that takes an LPCTSTR parameter will force this coercion, so that you can have a function (perhaps in a DLL you bought):

BOOL DoSomethingCool(LPCTSTR s);

and call it as follows

CString file("c:\\myfiles\\coolstuff")
BOOL result = DoSomethingCool(file);

This works correctly because the DoSomethingCool function has specified that it wants an LPCTSTR and therefore the LPCTSTR operator is applied to the argument, which in MFC means that the address of the string is returned.

But what if you want to format it?

CString graycat("GrayCat");
CString s;
s.Format("Mew! I love %s", graycat);

Note that because the value appears in the variable-argument list (the list designated by "..." in the specification of the function) that there is no implicit coercion operator. What are you going to get?

Well, surprise, you actually get the string

"Mew! I love GrayCat"

because the MFC implementers carefully designed the CString data type so that an expression of type CString evaluates to the pointer to the string, so in the absence of any casting, such as in a Format or sprintf, you will still get the correct behavior. The additional data that describes a CString actually lives in the addresses below the nominal CString address.

What you can't do is modify the string. For example, you might try to do something like replace the "." by a "," (don't do it this way, you should use the National Language Support features for decimal conversions if you care about internationalization, but this makes a simple example):

CString v("1.00");  // currency amount, 2 decimal places
LPCTSTR p = v;
p[lstrlen(p) - 3] = ',';

If you try to do this, the compiler will complain that you are assigning to a constant string. This is the correct message. It would also complain if you tried

strcat(p, "each");

because strcat wants an LPTSTR as its first argument and you gave it an LPCTSTR

Don't try to defeat these error messages. You will get yourself into trouble!

The reason is that the buffer has a count, which is inaccessible to you (it's in that hidden area that sits below the CString address), and if you change the string, you won't see the change reflected in the character count for the buffer. Furthermore, if the string happens to be just about as long as the buffer physical limit (more on this later), an attempt to extend the string will overwrite whatever is beyond the buffer, which is memory you have no right to write (right?) and you'll damage memory you don't own. Sure recipe for a dead application.

CString to char * II: Using GetBuffer

A special method is available for a CString if you need to modify it. This is the operation GetBuffer. What this does is return to you a pointer to the buffer which is considered writeable. If you are only going to change characters or shorten the string, you are now free to do so:

CString s(_T("File.ext"));
LPTSTR p = s.GetBuffer();
LPTSTR dot = strchr(p, '.'); // OK, should have used s.Find...
if(p != NULL)
    *p = _T('\0');
s.ReleaseBuffer();

This is the first and simplest use of GetBuffer. You don't supply an argument, so the default of 0 is used, which means "give me a pointer to the string; I promise to not extend the string". When you call ReleaseBuffer, the actual length of the string is recomputed and stored in the CString. Within the scope of a GetBuffer/ReleaseBuffer sequene, and I emphasize this: You Must Not, Ever, Use Any Method Of CString on the CString whose buffer you have! The reason for this is that the integrity of the CString object is not guaranteed until the ReleaseBuffer is called. Study the code below:

CString s(...);
LPTSTR p = s.GetBuffer();
//... lots of things happen via the pointer p
int n = s.GetLength(); // BAD!!!!! PROBABLY WILL GIVE WRONG ANSWER!!!
s.TrimRight();         // BAD!!!!! NO GUARANTEE IT WILL WORK!!!!
s.ReleaseBuffer();     // Things are now OK
int m = s.GetLength(); // This is guaranteed to be correct
s.TrimRight();         // Will work correctly

Suppose you want to actually extend the string. In this case you must know how large the string will get. This is just like declaring

char buffer[1024];

knowing that 1024 is more than enough space for anything you are going to do. The equivalent in the CString world is

LPTSTR p = s.GetBuffer(1024);

This call gives you not only a pointer to the buffer, but guarantees that the buffer will be (at least) 1024 characters in length. (Note I said "characters", not "bytes", because CString is Unicode-aware implicitly).

Also, note that if you have a pointer to a const string, the string value itself is stored in read-only memory; an attempt to store into it, even if you've done GetBuffer, you have a pointer to read-only memory, so an attempt to store into the string will fail with an access error. I haven't verified this for CString, but I've seen ordinary C programmers make this error frequently.

A common "bad idiom" left over from C programmers is to allocate a buffer of fixed size, do a sprintf into it, and assign it to a CString:

char buffer[256];
sprintf(buffer, "%......", args, ...); // ... means "lots of stuff here"
CString s = buffer;

while the better form is to do

CString s;
s.Format(_T("%...."), args, ...);

Note that this always works; if your string happens to end up longer than 256 bytes you don't clobber the stack!

Another common error is to be clever and realize that a fixed size won't work, so the programmer allocates bytes dynamically. This is even sillier:

int len = lstrlen(parm1) + 13 + lstrlen(parm2) + 10 + 100;
char * buffer = new char[len];
sprintf(buffer, "%s is equal to %s, valid data", parm1, parm2);
CString s = buffer;
....
delete [] buffer;

Where it can be easily written as

CString s;
s.Format(_T("%s is equal to %s, valid data"), parm1, parm2);

Note that the sprintf examples are not Unicode-ready (although you could use tsprintf and put _T() around the formatting string, but the basic idea is still that you are doing far more work than is necessary, and it is error-prone.

CString to char * III: Interfacing to a control

A very common operation is to pass a CString value in to a control, for example, a CTreeCtrl. While MFC provides a number of convenient overloads for the operation, but in the most general situation you use the "raw" form of the update, and therefore you need to store a pointer to a string in the TVITEM which is included within the TVINSERTITEMSTRUCT:

TVINSERTITEMSTRUCT tvi;
CString s;
// ... assign something to s
tvi.item.pszText = s; // Compiler yells at you here
// ... other stuff
HTREEITEM ti = c_MyTree.InsertItem(&tvi);

Now why did the compiler complain? It looks like a perfectly good assignment! But in fact if you look at the structure, you will see that the member is declared in the TVITEM structure as shown below:

LPTSTR pszText;
int cchTextMax;

Therefore, the assignment is not assigning to an LPCTSTR and the compiler has no idea how to cast the right hand side of the assignment to an LPTSTR.

OK, you say, I can deal with that, and you write

tvi.item.pszText = (LPCTSTR)s; // compiler still complains!

What the compiler is now complaining about is that you are attempting to assign an LPCTSTR to an LPTSTR, an operation which is forbidden by the rules of C and C++. You may not use this technique to accidentally alias a constant pointer to a non-constant alias so you can violate the assumptions of constancy. If you could, you could potentially confuse the optimizer, which trusts what you tell it when deciding how to optimize your program. For example, if you do

const int i = ...;
//... do lots of stuff
     ... = a[i];  // usage 1
// ... lots more stuff
     ... = a[i];  // usage 2

Then the compiler can trust that, because you said const, that the value of i at "usage1" and "usage2" is the same value, and it can even precompute the address of a[i] at usage1 and keep the value around for later use at usage2, rather than computing it each time. If you were able to write

const int i = ...;
int * p = &i;
//... do lots of stuff
     ... = a[i];  // usage 1
// ... lots more stuff
     (*p)++;      // mess over compiler's assumption
// ... and other stuff
     ... = a[i];  // usage 2

The the compiler would believe in the constancy of i, and consequently the constancy of the location of a[i], and the place where the indirection is done destroys that assumption. Thus, the program would exhibit one behavior when compiled in debug mode (no optimizations) and another behavior when compiled in release mode (full optimization). This Is Not Good. Therefore, the attempt to assign the pointer to i to a modifiable reference is diagnosed by the compiler as being bogus. This is why the (LPCTSTR) cast won't really help.

Why not just declare the member as an LPCTSTR? Because the structure is used both for reading and writing to the control. When you are writing to the control, the text pointer is actually treated as an LPCTSTR but when you are reading from the control you need a writeable string. The structure cannot distinguish its use for input from its use for output.

Therefore, you will often find in my code something that looks like

tvi.item.pszText = (LPTSTR)(LPCTSTR)s;

This casts the CString to an LPCTSTR, thus giving me that address of the string, which I then force to be an LPTSTR so I can assign it. Note that this is valid only if you are using the value as data to a Set or Insert style method! You cannot do this when you are trying to retrieve data!

You need a slightly different method when you are trying to retrieve data, such as the value stored in a control. For example, for a CTreeCtrl using the GetItem method. Here, I want to get the text of the item. I know that the text is no more than MY_LIMIT in size. Therefore, I can write something like

TVITEM tvi;
// ... assorted initialization of other fields of tvi
tvi.pszText = s.GetBuffer(MY_LIMIT);
tvi.cchTextMax = MY_LIMIT;
c_MyTree.GetItem(&tvi);
s.ReleaseBuffer();

Note that the code above works for any type of Set method also, but is not needed because for a Set-type method (including Insert) you are not writing the string. But when you are writing the CString you need to make sure the buffer is writeable. That's what the GetBuffer does. Again, note that once you have done the GetBuffer call, you must not do anything else to the CString until the ReleaseBuffer call.

CString to BSTR

When programming with ActiveX, you will sometimes need a value represented as a type BSTR. A BSTR is a counted string, a wide-character (Unicode) string on Intel platforms and can contain embedded NUL characters. 

You can convert at CString to a BSTR by calling the CString method AllocSysString:

CString s;
s = ... ; // whatever
BSTR b = s.AllocSysString();

 The pointer b points to a newly-allocated BSTR object which is a copy of the CString, including the terminal NUL character. This may now be passed to whatever interface you are calling that requires a BSTR. Normally, a BSTR is disposed of by the component receiving it. If you should need to dispose of a BSTR, you must use the call

::SysFreeString(b);

to free the string.

The story is that the decision of how to represent strings sent to ActiveX controls resulted in some serious turf wars within Microsoft. The Visual Basic people won, and the string type BSTR (acronym for "Basic String") was the result.

BSTR to CString

 Since a BSTR is a counted Unicode string, you can use standard conversions to make an 8-bit CString. Actually, this is built-in; there are special constructors for converting ANSI strings to Unicode and vice-versa. You can also get BSTRs as results in a VARIANT type, which is a type returned by various COM and Automation calls.

For example, if you do, in an ANSI application,

BSTR b;
b = ...; // whatever
CString s(b == NULL ? L"" : b)

works just fine for a single-string BSTR, because there is a special constructor that takes an LPCWSTR (which is what a BSTR is) and converts it to an ANSI string. The special test is required because a BSTR could be NULL, and the constructors Don't Play Well with NULL inputs (thanks to Brian Ross for pointing this out!). This also only works for a BSTR that contains only a single string terminated with a NUL; you have to do more work to convert strings that contain multiple NUL characters. Note that embedded NUL characters generally don't work well in CStrings and generally should be avoided.

Remember, according to the rules of C/C++, if you have an LPWSTR it will match a parameter type of LPCWSTR (it doesn't work the other way!).

In UNICODE mode, this is just the constructor

CString::CString(LPCTSTR);

As indicated above, in ANSI mode there is a special constructor for

CString::CString(LPCWSTR); 

this calls an internal function to convert the Unicode string to an ANSI string. (In Unicode mode there is a special constructor that takes an LPCSTR, a pointer to an 8-bit ANSI string, and widens it to a Unicode string!). Again, note the limitation imposed by the need to test for a BSTR value which is NULL.

There is an additional problem as pointed out above: BSTRs can contain embedded NUL characters; CString constructors can only handle single NUL characters in a string. This means that CStrings will compute the wrong length for a string which contains embedded NUL bytes. You need to handle this yourself. If you look at the constructors in strcore.cpp, you will see that they all do an lstrlen or equivalent to compute the length. 

Note that the conversion from Unicode to ANSI uses the ::WideCharToMultiByte conversion with specific arguments that you may not like. If you want a different conversion than the default, you have to write your own.

If you are compiling as UNICODE, then it is a simple assignment:

CString convert(BSTR b)
   {
    if(b == NULL)
        return CString(_T(""));
    CString s(b); // in UNICODE mode
    return s;
   }

If you are in ANSI mode, you need to convert the string in a more complex fashion. This will accomplish it. Note that this code uses the same argument values to ::WideCharToMultiByte that the implicit constructor for CString uses, so you would use this technique only if you wanted to change these parameters to do the conversion in some other fashion, for example, specifying a different default character, a different set of flags, etc.

CString convert(BSTR b)
   {
    CString s;
    if(b == NULL)
       return s; // empty for NULL BSTR
#ifdef UNICODE
    s = b;
#else
    LPSTR p = s.GetBuffer(SysStringLen(b) + 1); 
    ::WideCharToMultiByte(CP_ACP,            // ANSI Code Page
                          0,                 // no flags
                          b,                 // source widechar string
                          -1,                // assume NUL-terminated
                          p,                 // target buffer
                          SysStringLen(b)+1, // target buffer length
                          NULL,              // use system default char
                          NULL);             // don't care if default used
    s.ReleaseBuffer();
#endif
    return s;
   }

Note that I do not worry about what happens if the BSTR contains Unicode characters that do not map to the 8-bit character set, because I specify NULL as the last two parameters. This is the sort of thing you might want to change.

VARIANT to CString  

Actually, I've never done this; I don't work in COM/OLE/ActiveX where this is an issue. But I saw a posting by Robert Quirk on the microsoft.public.vc.mfc newsgroup on how to do this, and it seemed silly not to include it in this essay, so here it is, with a bit more explanation and elaboration. Any errors relative to what he wrote are my fault.

A VARIANT is a generic parameter/return type in COM programming. You can write methods that return a type VARIANT, and which type the function returns may (and often does) depend on the input parameters to your method (for example, in Automation, depending on which method you call, IDispatch::Invoke may return (via one of its parameters) a VARIANT which holds a BYTE, a WORD, an float, a double, a date, a BSTR, and about three dozen other types (see the specifications of the VARIANT structure in the MSDN). In the example below, it is assumed that the type is known to be a variant of type BSTR, which means that the value is found in the string referenced by bstrVal.  This takes advantage of the fact that there is a constructor which, in an ANSI application, will convert a value referenced by an LPCWCHAR to a CString (see BSTR-to-CString). In Unicode mode, this turns out to be the normal CString constructor. See the caveats about the default ::WideCharToMultibyte conversion and whether or not you find these acceptable (mostly, you will).

VARIANT vaData;

vaData = m_com.YourMethodHere();
ASSERT(vaData.vt == VT_BSTR);

CString strData(vaData.bstrVal);

Note that you could also make a more generic conversion routine that looked at the vt field. In this case, you might consider something like:

CString VariantToString(VARIANT * va)
   {
    CString s;
    switch(va->vt)
      { /* vt */
       case VT_BSTR:
          return CString(vaData->bstrVal);
       case VT_BSTR | VT_BYREF:
          return CString(*vaData->pbstrVal);
       case VT_I4:
          s.Format(_T("%d"), va->lVal);
          return s;
       case VT_I4 | VT_BYREF:
          s.Format(_T("%d"), *va->plVal);
       case VT_R8:
          s.Format(_T("%f"), va->dblVal);
          return s;
       ... remaining cases left as an Exercise For The Reader
       default:
          ASSERT(FALSE); // unknown VARIANT type (this ASSERT is optional)
          return CString("");
      } /* vt */
   }

Loading STRINGTABLE values

If you want to create a program that is easily ported to other languages, you must not include native-language strings in your source code. (For these examples, I'll use English, since that is my native language (aber Ich kann ein bischen Deutsch sprechen). So it is very bad practice to write

CString s = "There is an error";

Instead, you should put all your language-specific strings (except, perhaps, debug strings, which are never in a product deliverable). This means that is fine to write

s.Format(_T("%d - %s"), code, text);

in your program; that literal string is not language-sensitive. However, you must be very careful to not use strings like

// fmt is "Error in %s file %s"
// readorwrite is "reading" or "writing"
s.Format(fmt, readorwrite, filename); 

I speak of this from experience. In my first internationalized application I made this error, and in spite of the fact that I know German, and that German word order places the verb at the end of a sentence, I had done this. Our German distributor complained bitterly that he had to come up with truly weird error messages in German to get the format codes to do the right thing. It is much better (and what I do now) to have two strings, one for reading and one for writing, and load the appropriate one, making them string parameter-insensitive, that is, instead of loading the strings "reading" or "writing", load the whole format:

// fmt is "Error in reading file %s"
//          "Error in writing file %s"
s.Format(fmt, filename);

Note that if you have more than one substitution, you should make sure that if the word order of the substitutions does not matter, for example, subject-object, subject-verb, or verb-object, in English.

For now, I won't talk about FormatMessage, which actually is better than sprintf/Format, but is poorly integrated into the CString class. It solves this by naming the parameters by their position in the parameter list and allows you to rearrange them in the output string. 

So how do we accomplish all this? By storing the string values in the resource known as the STRINGTABLE in the resource segment. To do this, you must first create the string, using the Visual Studio resource editor. A string is given a string ID, typically starting IDS_. So you have a message, you create the string and call it IDS_READING_FILE and another called IDS_WRITING_FILE. They appear in your .rc file as

STRINGTABLE
    IDS_READING_FILE "Reading file %s"
    IDS_WRITING_FILE "Writing file %s"
END

Note: these resources are always stored as Unicode strings, no matter what your program is compiled as. They are even Unicode strings on Win9x platforms, which otherwise have no real grasp of Unicode (but they do for resources!). Then you go to where you had stored the strings 

// previous code
   CString fmt;
      if(...)
        fmt = "Reading file %s";
     else
       fmt = "Writing file %s";
  ...
    // much later
  CString s;
  s.Format(fmt, filename);

and instead do

// revised code
    CString fmt;
        if(...)
           fmt.LoadString(IDS_READING_FILE);
        else
           fmt.LoadString(DS_WRITING_FILE);
    ...
      // much later
    CString s;
    s.Format(fmt, filename);

Now your code can be moved to any language. The LoadString method takes a string ID and retrieves the STRINGTABLE  value it represents, and assigns that value to the CString

There is a clever feature of the CString constructor that simplifies the use of STRINGTABLE entries. It is not explicitly documented in the CString::CString specification, but is obscurely shown in the example usage of the constructor! (Why this couldn't be part of the formal documentation and has to be shown in an example escapes me!). The feature is that if you cast a STRINGTABLE ID to an LPCTSTR it will implicitly do a LoadString. Thus the following two examples of creating a string value produce the same effect, and the ASSERT will not trigger in debug mode compilations:

CString s;
s.LoadString(IDS_WHATEVER);
CString t( (LPCTSTR)IDS_WHATEVER);
ASSERT(s == t);

Now, you may say, how can this possibly work? How can it tell a valid pointer from a STRINGTABLE ID? Simple: all string IDs are in the range 1..65535. This means that the high-order bits of the pointer will be 0. Sounds good, but what if I have valid data in a low address? Well, the answer is, you can't. The lower 64K of your address space will never, ever, exist. Any attempt to access a value in the address range 0x00000000 through 0x0000FFFF (0..65535) will always and forever give an access fault. These addresses are never, ever valid addresses. Thus a value in that range (other than 0) must necessarily represent a STRINGTABLE ID.

I tend to use the MAKEINTRESOURCE macro to do the casting. I think it makes the code clearer regarding what is going on. It is a standard macro which doesn't have much applicability otherwise in MFC. You may have noted that many methods take either a UINT or an LPCTSTR as parameters, using C++ overloading. This gets us around the ugliness of pure C where the "overloaded" methods (which aren't really overloaded in C) required explicit casts. This is also useful in assigning resource names to various other structures.

CString s;
s.LoadString(IDS_WHATEVER);
CString t( MAKEINTRESOURCE(IDS_WHATEVER));
ASSERT(s == t);

Just to give you an idea: I practice what I preach here. You will rarely if ever find a literal string in my program, other than the occasional debug output messages, and, of course, any language-independent string.

CStrings and temporary objects  

Here's a little problem that came up on the microsoft.public.vc.mfc newsgroup a while ago. I'll simplify it a bit. The basic problem was the programmer wanted to write a string to the Registry. So he wrote:

I am trying to set a registry value using RegSetValueEx() and it is the value that I am having trouble with. If I declare a variable of char[] it works fine. However, I am trying to convert from a CString and I get garbage. "ÝÝÝÝ...ÝÝÝÝÝÝ" to be exact. I have tried GetBuffer, typecasting to char*, LPCSTR. The return of GetBuffer (from debug) is the correct string but when I assign it to a char* (or LPCSTR) it is garbage. Following is a piece of my code:

char* szName = GetName().GetBuffer(20);
RegSetValueEx(hKey, "Name", 0, REG_SZ, 
                    (CONST BYTE *) szName,
                    strlen (szName + 1));


The Name string is less then 20 chars long, so I don't think the GetBuffer parameter is to blame.

It is very frustrating and any help is appreciated.

Dear Frustrated,

You have been done in by a fairly subtle error, caused by trying to be a bit too clever. What happened was that you fell victim to knowing too much. The correct code is shown below:

CString Name = GetName();
RegSetValueEx(hKey, _T("Name"), 0, REG_SZ, 
                    (CONST BYTE *) (LPCTSTR)Name,
                    (Name.GetLength() + 1) * sizeof(TCHAR));

Here's why my code works and yours didn't. When your function GetName returned a CString, it returned a "temporary object". See the C++ Reference manual §12.2. 

In some circumstances it may be necessary or convenient for the compiler to generate a temporary object. Such introduction of temporaries is implementation dependent. When a compiler introduces a temporary object of a class that has a constructor it must ensure that a construct is called for the temporary object. Similarly, the destructor must be called for a temporary object of a class where a destructor is declared. 

The compiler must ensure that a temporary object is destroyed. The exact point of destruction is implementation dependent....This destruction must take place before exit from the scope in which the temporary is created.

Most compilers implement the implicit destructor for a temporary at the next program sequencing point following its creation, that is, for all practical purposes, the next semicolon. Hence the CString existed when the GetBuffer call was made, but was destroyed following the semicolon. (As an aside, there was no reason to provide an argument to GetBuffer, and the code as written is incorrect since there is no ReleaseBuffer performed). So what GetBuffer returned was a pointer to storage for the text of the CString. When the destructor was called at the semicolon, the basic CString object was freed, along with the storage that had been allocated to it. The MFC debug storage allocator then rewrites this freed storage with 0xDD, which is the symbol "Ý". By the time you do the write to the Registry, the string contents have been destroyed.

There is no particular reason to need to cast the result to a char * immediately. Storing it as a CString means that a copy of the result is made, so after the temporary CString is destroyed, the string still exists in the variable's CString. The casting at the time of the Registry call is sufficient to get the value of a string which already exists.

In addition, my code is Unicode-ready. The Registry call wants a byte count. Note also that the call lstrlen(Name+1) returns a value that is too small by 2 for an ANSI string, since it doesn't start until the second character of the string. What you meant to write was lstrlen(Name) + 1 (OK, I admit it, I've made the same error!). However, in Unicode, where all characters are two bytes long, we need to cope with this. The Microsoft documentation is surprisingly silent on this point: is the value given for REG_SZ values a byte count or a character count? I'm assuming that their specification of "byte count" means exactly that, and you have to compensate.

CString Efficiency

One problem of CString is that it hides certain inefficiencies from you. On the other hand, it also means that it can implement certain efficiencies. You may be tempted to say of the following code

CString s = SomeCString1;
s += SomeCString2;
s += SomeCString3;
s += ",";
s += SomeCString4;

that it is horribly inefficient compared to, say

char s[1024];
lstrcpy(s, SomeString1);
lstrcat(s, SomeString2);
lstrcat(s, SomeString 3);
lstrcat(s, ",");
lstrcat(s, SomeString4);

After all, you might think, first it allocates a buffer to hold SomeCString1, then copies SomeCString1 to it, then detects it is doing a concatenate, allocates a new buffer large enough to hold the current string plus SomeCString2, copies the contents to the buffer and concatenates the SomeCString2 to it, then discards the first buffer and replaces the pointer with a pointer to the new buffer, then repeats this for each of the strings, being horribly inefficient with all those copies.

The truth is, it probably never copies the source strings (the left side of the +=) for most cases.

In VC++ 6.0, in Release mode, all CString buffers are allocated in predefined quanta. These are defined as 64, 128, 256, and 512 bytes. This means that unless the strings are very long, the creation of the concatenated string is an optimized version of a strcat operation (since it knows the location of the end of the string it doesn't have to search for it, as strcat would; it just does a memcpy to the correct place) plus a recomputation of the length of the string. So it is about as efficient as the clumsier pure-C code, and one whole lot easier to write. And maintain. And understand. 

Those of you who aren't sure this is what is really happening, look in the source code for CString, strcore.cpp, in the mfc\src subdirectory of your vc98 installation. Look for the method ConcatInPlace which is called from all the += operators.

Aha! So CString isn't really "efficient!" For example, if I create

CString cat("Mew!");

then I don't get a nice, tidy little buffer 5 bytes long (4 data bytes plus the terminal NUL). Instead the system wastes all that space by giving me 64 bytes and wasting 59 of them.

If this is how you think, be prepared to reeducate yourself. Somewhere in your career somebody taught you that you always had to use as little space as possible, and this was a Good Thing.

This is incorrect. It ignores some seriously important aspects of reality. 

If you are used to programming embedded applications with 16K EPROMs, you have a particular mindset for doing such allocation. For that application domain, this is healthy. But for writing Windows applications on 500MHz, 256MB machines, it actually works against you, and creates programs that perform far worse than what you would think of as "less efficient" code.

For example, size of strings is thought to be a first-order effect. It is Good to make this small, and Bad to make it large. Nonsense. The effect of precise allocation is that after a few hours of the program running, the heap is cluttered up with little tiny pieces of storage which are useless for anything, but they increase the storage footprint of your application, increase paging traffic, can actually slow down the storage allocator to unacceptable performance levels, and eventually allow your application to grow to consume all of available memory. Storage fragmentation, a second-order or third-order effect, actually dominates system performance. Eventually, it compromises reliability, which is completely unacceptable.

Note that in Debug mode compilations, the allocation is always exact. This helps shake out bugs.

Assume your application is going to run for months at a time. For example, I bring up VC++, Word, PowerPoint, FrontPage, Outlook Express, Forté Agent, Internet Explorer, and a few other applications, and essentially never close them. I've edited using PowerPoint for days on end (on the other hand, if you've had the misfortune to have to use something like Adobe FrameMaker, you begin to appreciate reliability; I've rarely been able to use this application without it  crashing four to six times a day! And always because it has run out of space, usually by filling up my entire massive swap space!) Precise allocation is one of the misfeatures that will compromise reliability and lead to application crashes.

By making CStrings be multiples of some quantum, the memory allocator will end up cluttered with chunks of memory which are almost always immediately reusable for another CString, so the fragmentation is minimized, allocator performance is enhanced, application footprint remains almost as small as possible, and you can run for weeks or months without problem.

Aside: Many years ago, at CMU, we were writing an interactive system. Some studies of the storage allocator showed that it had a tendency to fragment memory badly. Jim Mitchell, now at Sun Microsystems, created a storage allocator that maintained running statistics about allocation size, such as the mean and standard deviation of all allocations. If a chunk of storage would be split into a size that was smaller than the mean minus one s than the prevailing allocation, he didn't split it at all, thus avoiding cluttering up the allocator with pieces too small to be usable. He actually used floating point inside an allocator! His observation was that the long-term saving in instructions by not having to ignore unusable small storage chunks far and away exceeded the additional cost of doing a few floating point operations on an allocation operation. He was right.

Never, ever think about "optimization" in terms of small-and-fast analyzed on a per-line-of-code basis. Optimization should mean small-and-fast analyzed at the complete application level (if you like New Age buzzwords, think of this as the holistic approach to program optimization, a whole lot better than the per-line basis we teach new programmers). At the complete application level, minimum-chunk string allocation is about the worst method you could possibly use.

If you think optimization is something you do at the code-line level, think again. Optimization at this level rarely matters. Read my essay on Optimization: Your Worst Enemy for some thought-provoking ideas on this topic.

Note that the += operator is special-cased; if you were to write:

CString s = SomeCString1 + SomeCString2 + SomeCString3 + "," + SomeCString4;

then each application of the + operator causes a new string to be created and a copy to be done (although it is an optimized version, since the length of the string is known and the inefficiencies of strcat do not come into play).

Unicode and CStrings

There are two different issues in dealing with Unicode and CStrings.  In VS6, the CString takes on the data type of the TCHAR type; that is, in an "ANSI" application a CString holds only 8-bit characters and in a Unicode app a CString holds only Unicode (16-bit) characters.  If you have to "mix and match" you are out of luck. In some cases, this forces you to compile as a Unicode app, use Unicode everywhere, and "down-convert" the Unicode characters to 8-bit characters before sending them out, and "up-convert" incoming character streams to Unicode.

This isn't always a Bad Thing; for example, if the incoming characters were in UTF-7 or UTF-8 encoding, then Unicode is actually the "natural" representation of those characters, and only the transport layer is concerned about the transmittal as 8-bit characters.

In VS.NET, however, we have more degrees of flexibility. In addition to the CString data type, which follows the same rules as VS6, there are two new data types, CStringA and CStringW. A CStringA string always holds 8-bit characters independent of the compilation mode of the program, and a CStringW always holds Unicode characters, independent of the compilation mode of the program.

One of the questions that arises often is "How do I..." and followed by a question of how to handle 8-bit characters in a Unicode app or Unicode characters in an 8-bit app.

In VS6, this is not easy.  You will have to explicitly convert the strings to the form of your app.  I have found vastly fewer problems if I build the apps as Unicode apps and up-convert any 8-bit strings on input and down-convert if 8-bit output is required.  This allows me to keep everything internally in a single, uniform representation.  If you have coded your app "Unicode-aware" from the start, you can simply set the UNICODE/_UNICODE options and your app will come out Unicode-ready; then all you do is modify the 8-bit input and output locations to do the conversion. But if you require the app remain 8-bit, then the "not easy" part comes into play.  I have found that instead of using TCHAR * arrays which are explicitly allocated and freed, I prefer to use CArray, e.g., typedef CArray<WCHAR, WCHAR> CWCharArray, which gives me the advantages of CString in terms of allocation and deallocation but is still, frankly, a real pain to use the rest of the time if you need string operations; you will end up using the wcs... operations far too often.  But doing this I find fewer storage leaks occur, because the CArray is properly destroyed when its containing object is destroyed.

If you are forced to do wcsspy or wcscat, you should rethink what you are doing to use the strsafe.h library and use StringCchCopy or StringCchCat.  These are macros, like CreateFile, so the underlying functions have names like StringCchCopyA and StringCchCopyW which you can call explicitly.  You need a recent release of the Platform SDK to get these functions and their libraries.

You should also look into the ATL macros for character conversions.  In some cases, where the default codepage is all that is needed, these will suffice.  However, it seems more common, at least in the code I write, to need explicit UTF-8 conversions.

In VS.NET, it is easier; what the table below is going to show is some ways of doing conversions between input/output representations and internal representations.  I am going to write the table in terms of CStringA and CStringW.  For VS6, this will require knowing which mode you have compiled in, and select an alternate representation for the "other" format.  For example, if you are compiling an ANSI app, then CString represents 8-bit strings, and CWCharArray (see the earlier paragraph in this section) represents 16-bit strings.  If you are compiling Unicode app, then CString represents 16-bit strings and CByteArray represents 8-bit strings.  This is one reason that I don't try to build apps in VS6 that handle both 8-bit and 16-bit representations but could be compiled in either mode.  I just make them Unicode apps from the start, and that way I only have one conversion issue to deal with, and strings are easier to use.

Converting To ê Converting From è 8-bit ACP byte stream 8-bit non-ACP byte stream
CStringA
LPCSTR acp = ...;
CStringA s = acp;

or

LPCSTR acp;
CStringA s(acp);
LPCSTR acp = ...;
CStringA s = acp;

or

LPCSTR acp;
CStringA s(acp);
CStringW
LPCSTR acp = ...;
or
CStringA acp = ...;

then

int n = ::MultiByteToWideChar(CP_ACP, 0, acp, -1, NULL, 0);
CStringW s;
LPWSTR p = s.GetBuffer(n);
::MultiByteToWideChar(CP_ACP, 0, acp, -1, p, n);
s.ReleaseBuffer();
LPCSTR acp = ...;

or

CStringA acp = ...;

then

int n = ::MultiByteToWideChar(CP_OF_YOUR_CHOICE, 0, acp, -1, NULL, 0);
CString W s;
LPWSTR p = s.GetBuffer(n);
::MultiByteToWideChar(CP_ACP, 0, acp, -1, p, n);
s.ReleaseBuffer();
CString (VS6, ANSI mode)
LPCSTR acp = ...;
CString s = acp;

or

LPCSTR acp = ...;
CString s(acp);
LPCSTR acp = ...;
CString s = acp;

or

LPCSTR acp = ...;
CString s(acp);
CString (VS6, Unicode mode)
LPCSTR acp = ...;
int n = ::MultiByteToWideChar(CP_ACP, 0, acp, -1, NULL, 0);
CString s;
LPWSTR p = s.GetBuffer(n);
::MultiByteToWideChar(CP_ACP, 0, acp, -1, p, n);
s.ReleaseBuffer();
or
CArray<char, char> acp;
// e.g. 
// acp.SetSize(n);
// ReadFile(h, acp.GetBuffer(), n, NULL);

then

CString s(acp.GetData());

or

CString s;
int n = ::MultiByteToWideChar(CP_ACP, 0, acp.GetBuffer(), -1, NULL, 0);
LPWSTR p = s.GetBuffer(n);
::MultiByteToWideChar(CP_ACP, 0, acp.GetData(), -1, p, n);
s.ReleaseBuffer();
LPCSTR acp = ...;
int n = ::MultiByteToWideChar(CP_OF_YOUR_CHOICE, 0, acp, -1, NULL, 0);
CString W s;
LPWSTR p = s.GetBuffer(n);
::MultiByteToWideChar(CP_ACP, 0, acp, -1, p, n);
s.ReleaseBuffer();

or

CArray<char, char> acp;
// e.g. 
// acp.SetSize(n);
// ReadFile(h, acp.GetBuffer(), n, NULL);

then

CString s(acp.GetData());

or

CString s;
int n = ::MultiByteToWideChar(CP_ACP, 0, acp.GetData(), -1, NULL, 0);
LPWSTR p = s.GetBuffer(n);
::MultiByteToWideChar(CP_ACP, 0, acp.GetData(), -1, p, n);
s.ReleaseBuffer();

 

Converting To ê Converting From è Unicode character stream
CStringA (CP_ACP)
CStringW stream = ...;
CStringA s(stream);

or

LPCWSTR stream = ...;
CStringA s(stream);

or

LPCWSTR stream = ...;
int n = ::WideCharToMultiByte(CP_ACP, 0, stream, -1, NULL, 0, NULL, NULL);
CStringA s;
LPSTR p = s.GetBuffer(n);
::WideCharToMultiByte(CP_ACP, 0, stream, -1, p, n, NULL, NULL);
s.ReleaseBuffer();

or

CStringW stream = ...;
int n = ::WideCharToMultiByte(CP_ACP, 0, stream, -1, NULL, 0, NULL, NULL);
CStringA s;
LPSTR p = s.GetBuffer(n);
::WideCharToMultiByte(CP_ACP, 0, stream, -1, p, n, NULL, NULL);
s.ReleaseBuffer();
CStringA (non-CP_ACP)
LPCWSTR stream = ...;
int n = ::WideCharToMultiByte(CP_OF_YOUR_CHOICE, 0, stream, -1, NULL, 0, NULL, NULL);
CStringA s;
LPSTR p = s.GetBuffer(n);
::WideCharToMultiByte(CP_OF_YOUR_CHOICE, 0, stream, -1, p, n, NULL, NULL);
s.ReleaseBuffer();

or

CStringW stream = ...;
int n = ::WideCharToMultiByte(CP_OF_YOUR_CHOICE, 0, stream, -1, NULL, 0, NULL, NULL);
CStringA s;
LPSTR p = s.GetBuffer(n);
::WideCharToMultiByte(CP_OF_YOUR_CHOICE, 0, stream, -1, p, n, NULL, NULL);
s.ReleaseBuffer();

 

CStringW
LPCWSTR stream = ...;
CStringW s(stream)

or

LPCWSTR stream = ...;
CStringW s = stream;
CString (VS6, ANSI mode)
LPCWSTR stream = ...;
int n = ::WideCharToMultiByte(CP_ACP, 0, stream, -1, NULL, 0, NULL, NULL);
CStringA s;
LPSTR p = s.GetBuffer(n);
::WideCharToMultiByte(CP_ACP, 0, stream, -1, p, n, NULL, NULL);
s.ReleaseBuffer();

or

CArray<WCHAR, WCHAR> stream;
// e.g.,
// stream.SetSize(n);
// ReadFile(h, stream.GetData(), n * sizeof(WCHAR), NULL);
int n = ::WideCharToMultiByte(CP_ACP, 0, stream.GetData(), -1, NULL, 0, NULL, NULL);
CString s;
LPSTR p = s.GetBuffer(n);
::WideCharToMultiByte(CP_ACP, 0, stream.GetData(), -1, p, n, NULL, NULL);
s.ReleaseBuffer();
CString (VS6, Unicode mode)
LPCWSTR stream = ...;
CString s(stream);

or

LPCWSTR stream = ...;
CString s;
s = stream;

or

CString stream;
// e.g.
// ReadFile(h, stream.GetBuffer(n), n * sizeof(WCHAR), NULL);
// stream.ReleaseBuffer();

 

Converting To ê Converting From è CStringA CStringW
8-bit characters ACP
CStringA s = ...;
LPCSTR p = (LPCSTR)s;
CStringW stream;
CStringA s(stream);

or

CStringW stream;
CStringA s;
int n = ::WideCharToMultiByte(CP_ACP, 0, stream, -1, NULL, 0, NULL, NULL);
CStringA s;
LPCSTR p = s.GetBuffer();
::WideCharToMultiByte(CP_ACP, 0, stream, -1, p, n, NULL, NULL);
s.ReleaseBuffer();
8-bit characters non-ACP
CStringA s = ...;
LPCSTR p = (LPCSTR)s;
CStringW stream;
CStringA s;
int n = ::WideCharToMultiByte(CP_OF_YOUR_CHOICE, 0, stream, -1, NULL, 0, NULL, NULL);
CStringA s;
LPCSTR p = s.GetBuffer();
::WideCharToMultiByte(CP_OF_YOUR_CHOICE, 0, stream, -1, p, n, NULL, NULL);
s.ReleaseBuffer();
Unicode Characters
CStringA s = ...;
CStringW stream(s);

or

CStringA s = ...;
int n = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)s, -1, NULL, 0);
CArray<WCHAR, WCHAR> stream;
stream.SetSize(n);
MultiByteToWideChar(CP_ACP, 0, (LPCTSTR)s, -1, stream.GetData(), 0);
CStringW stream;
...(LPCWSTR)stream...;

UTF-7 and UTF-8 in CStrings

In the above examples, the CP_OF_YOUR_CHOICE could be reasonable CP_UTF7 or CP_UTF8. In this case, life gets more than a little strange.  You are now working in the domain of Multi-byte Character Sets (MBCS).  The significance here is that you no longer have a 1:1 mapping between characters in the string and characters of the character set.  In MBCS, it might take two, three, or four characters to represent a single glyph.  You cannot predict in advance how many characters are used. 

In UTF-8, the most common representation used for Unicode-as-8-bit-sequences, the high-order bit is set on the first and subsequent characters of a multicharacter sequence. You cannot iterate across a string picking up "characters" because you might get the interior representation of a character sequence. Instead, you must iterate using the MBCS support.

Character value UTF-16 UTF-8 Byte 1 UTF-8 Byte 2 UTF-8 Byte 3 UTF-8 Byte 4
000000000x6x5x4x3x2x1x0 000000000x6x5x4x3x2x1x0 0x6x5x4x3x2x1x0      
00000y4y3y2y1y0x5x4x3x2x1x0 00000y4y3y2y1y0x5x4x3x2x1x0 110y4y3y2y1y0 10x5x4x3x2x1x0    
z3z2z1z0y5y4y3y2y1y0x5x4x3x2x1x0 z3z2z1z0y5y4y3y2y1y0x5x4x3x2x1x0 1110z3z2z1z0 10y5y4y3y2y1y0 10x5x4x3x2x1x0  
u4u3u2u1u0z3z2z1z0y5y4y3y2y1y0x5x4x3x2x1x0 110110w3w2w1w0z3z2z1z0y5y4 +
110111
y3y2y1y0x5x4x3x2x1x0
11110u4u3u2* 10u1u0z3z2z1z0 10y5y4y3y2y1y0 10x5x4x3x2x1x0
  *Note that u4u3u2u1u0 == w3w2w1w0 + 1.  Consult The Unicode Standard, Section 3.7, "Surrogates"

If you are simply assigning strings and manipulating them as entire entities, then UTF-8 and other MBCS encodings pose no problem.  However, these strings cannot be examined a character-at-a-time without using the special MBCS functions to "advance to next character", and they cannot be passed as arguments to kernel functions that expect LPC[T]STR values; instead you will have to convert them to Unicode and use that string as the argument.  Otherwise, filenames and other things that use string names will end up with erroneous representations. At the moment, I have declared that MBCS is outside the scope of this essay.

The ATL String Support Macros

There are two forms of ATL String support; those conversions supported by VS6, and those supported by VS.NET.  To get these, you must include the ATL support header in your compilation, most commonly in stdafx.h:

#include <afxpriv.h>

The functions are summarized as currenttype2newtype.  Thus A2W converts an ANSI string to a Unicode string; T2A converts whatever the current string type is to ANSI. These all allocate a new buffer for the string, which is why I did not use them in the section about Unicode-ANSI conversions.  Nonetheless, they are often easiest to use if performance is not a critical issue.  Note that conversions down to ANSI will use the current codepage selected for the running thread to do the conversion.

This table is not intended to be comprehensive; it s just the most commonly-used conversions.  Consult the MSDN for the complete set of rules. As usual, T is interpreted as A in ANSI applications and W in Unicode applications. The qualifier C indicates a const result.

A2T A2W A2CT A2CW
W2A W2T W2CA W2CT
T2A T2W T2CA T2CW

In VS6, the USES_CONVERSION macro must be called in any function that uses these to avoid compiler warnings.  This is not needed in VS.NET.  Consult also Microsoft's Technical Note TN059: Using MFC MBCS/Unicode Conversion Macros.

For example, in VS6, to convert a CString to an LPCWSTR, you could do

USES_CONVERSION
CString s = ...;
LPCWSTR p = T2W(s);
SomeAPIThatWantsW(p, ...);

The string that is created will be freed up when the function exits

Summary

These are just some of the techniques for using CString. I use these every day in my programming. CString is not a terribly difficult class to deal with, but generally the MFC materials do not make all of this apparent, leaving you to figure it out on your own.

 

http://www.flounder.com/cstring.htm#char%20*%20to%20CString

 

Posted by '김용환'
,
char dest[20];
  char *sour = "Ciba";
  strcpy(dest, sour);

  cout<<dest<<endl;
  요건 된다.

  그렇다면
  char *dest = 0;
  char *sour = "Ciba";
  strcpy(dest, sour);

  cout<<dest<<endl;
  요건 될까?
  
  dest에 저장해야할 공간이 있어야 한다.
  결과는 에러이다. dest는 char 형의 포인터이지 sour을 담을 공간이 없는 것이다.

  이경우 포인터를 사용하고 싶다면
  char *dest=0;
  dest = new char[strlen(sour)];
  strcpy(dest,sour);
  이렇게 저장할 공간을 할당해야 한다.    


  여기까지는 기본이다.
  그럼 욘니 헷갈리는 윈도우 문자형들
  1. LPSTR
   이것의 정의는
   #define char* LPSTR
  
  그렇다면 위의 코드를 변경하면
  LPSTR dest  = 0;
  LPSTR sour  = "Ciba";
  dest = new char[strlen(sour)];
  strcpy(dest, sour);
  이렇게 하면 된다.
  어떤가 코드가 좀 깔끔하게 보이지?
  
2. LPTSTR
  이것의 msdn api정의를 보믄
  LPTSTR An LPWSTR if UNICODE is defined, an LPSTR otherwise
  요렇게 되있다..
  유니코드가 정의되어 있으믄      => LPTSTR 은 LPWSTR로
  유니코드가 정의되어 있지 않으믄 => LPTSTR 은 LPSTR로
  여기서 유니코드란게 몬가믄
  msdn 에서
  Unicode and Character Sets
  Microsoft Windows provides support for the many different written languages of the   international marketplace through Unicode and traditional character sets. Unicode is a   worldwide character-encoding standard that uses 16-bit code values to represent all the   characters used in modern computing, including technical symbols and special characters     used in publishing. Traditional character sets are the previous character-encoding   standardssuch as the Windows ANSI character setthat use 8-bit code values or combinations   of 8-bit values to represent the characters used in a specific language or geographical   region.

  This overview describes the character set functions and explains how to use them in your   applications.


   출처:     http://msdn.microsoft.com/library/default.asp?url=/library/en-us/intl/unicode_6bqr.asp
  
   해석을 하믄(성재으 해석을 믿지 말장!!)
   유니코드와 문자 집합
   마이구라 소프트 윈도우즈는 유니코드와 전통적은 문자 집합을 통해 세계적인 시장들에서
   여러가지로 쓰여진 언어들을 지원한당(세계적인 시장의 의미는 아프리카 가튼 열쇠적
   시장은 신경쓰지 않는다는 의미것지) 유니코드는 기술적인 기호나 출판에 사용되는 특정
   문자들을 포함하는, 현대 컴퓨팅에서 사용되는 모든 문자들을 표현하기하기 위해 16비트 코드
   값을 사용하는 확장된 문자 엔코딩 기준이당. 전통적인 문자 집힙들은 8비트 코드값을
   사용하거나 특정 언어나 지역에서 사용되는 문자를 표현하기위한 8비트 값의 조합을 이용하는
   윈도우 에이쓰벌 문자 집합인 구식의 문자 엔코딩이당.

   다시 돌아와서
   즉 유니코드는 국제화를 위해서 8비트 안시의 한계를 극복하기 위해 16비트로 확장하여
   한글, 한자, 가다가나등을 표현할 수 있게 엠에쑤의 시츄에이션이라 할 수 있것다.
   만약에 프로그램을 이웃나라로 수출한다면 유니코드는 필수이다.
   그렇지만 현재 프로그램에서는 옵션이다. 걍 있으면 좋은거란 예기당.
   즉 LPTSTR이 LPWSTR로 바뀌든 LPSTR로 바뀌든 지금 프로그램에서는 큰 문제는 아니다.

   코드또한 이케 바뀐다.

   LPTSTR dest  = 0;
   LPTSTR sour  = "Ciba";
   dest = new char[strlen(sour)];
   strcpy(dest, sour);

   cout<<dest<<endl;
Posted by '김용환'
,

dual-core chip이란 무엇인가?

 

dual-core를 위해서는 두 개의 processor 코어를 물리적으로
하나의 chip에 장착시킬 수 있는 chip 디자인과 제작 능력이 필요하다.

이는 종종 “하나의 chip 위에서의 Symmetric Multi Processor (SMP)” , dual processor, dual CPU 혹은 2-way chip으로도 언급되었다.

일반적으로 dual-core는 하나의 chip 위에 두 개의 processor 코어를 올리는 것으로 인식되어진다.

 

Note:

HP는 하나의 dual-core chip을 하나의 processor로 본다.
따라서 HP는 8개 코어(4개의 dual-core chip) system을 네 개 processor
또는 4-way 구성이라 부른다. HP는 문서상에서 processor와 dual-core chip을 혼용해서 쓴다.
따라서 HP System을 비교할 때에는 용어가 혼동될 수 있기 때문에 주의해야 한다.

 

Note:

SUN은 두 개의 thread를 돌릴 수 있는 하나의 processor를 dual-core chip으로 본다.
2005년 4월 21일 발표에 따르면 SUN은 또한 “four processor”(네 개의 dual-core chip)
V40z server를 일반적인 네 개 processor (네 개의 single-core chip)를 장착한 server와 구분한다.

 

IBM POWER5™ processor 기반의 server benchmark의 결과는 n-way의 용어로 표현되었는데
여기서 n은 processor chip이 아닌 processor cores의 수를 의미한다.
그러므로 dual-core IBM POWER5™ processor 기반 server에서 8-way는
네 개의 IBM POWER5™ processor chip와 같다. (p5-575의 8-way는 예외적이다)

Posted by '김용환'
,

'c or linux' 카테고리의 다른 글

전처리기 ##, #  (0) 2006.02.08
[펌] char* LPSTR LPWSTR 등에 관하여  (0) 2006.01.05
[펌] C언어 연산자 우선 순위  (0) 2005.11.15
[펌] Visual Studio 6.0의 라이브러리 링크  (0) 2005.11.05
c programming FAQs  (0) 2005.06.23
Posted by '김용환'
,
Posted by '김용환'
,

super.paint() 이용

java core 2005. 12. 12. 23:27

ProgramBrief 클래스는 Container를 상속받은 Scene을 상속받은 클래스로서, UI화면을 보여주는 창이다.

add된 컴포넌트와 paint메소드를 오버라이드하여 부분을 잘 엮으면, 기본 컴포넌트와 UI화면에 specific하게 보여줄 수 있다.

 

public class ProgramBrief extends Scene {

    Label label = new Label();

 

    public ProgramBrief() {

        add(label, 0);

    }

 

    public void paint(Grahics g) {

        super.paint(g); // 실제 add된 컴포넌트가 paint되는 부분.

        g.setColor(Color.black);

        g.fillRect(0, 0, getSize().width, getSize().height);

         //=> add된 컴포넌트가 그려진다.

        // 이럴 때는 super.paint(g) 콜을 화면에 그려지고 난 뒤로 이동시켜야한다.

        // g.setColor(Color.black);

        // g.fillRect(0, 0, getSize().width, getSize().height);

        // super.paint(g); // 실제 add된 컴포넌트가 paint되는 부분.

        // => 이렇게 하면, 바탕을 검게한후, 컴포넌트를 칠하게 된다..

   }

}

Posted by '김용환'
,

j2me 의 General File Connection 프레임웍에 사용예..

 

출처 : http://www.devx.com/wireless/Article/21871/0/page/1 

 

 

rom the earliest days of J2ME, network connectivity has been a central focus of the design. So much so that a generic architecture, referred to as the Generic Connection Framework (GCF), was created to standardize not only how applications create network connections but the associated connection interfaces device manufacturers must implement. The GCF is a fundamental part of J2ME and is available to all application profiles including Personal Profile and the Mobile Information Device Profile (MIDP).
Figure 1. The J2ME Stack: The configuration layer forms the basis for the J2ME stack and defines the characteristics of the J2ME.



In order to make the GCF available to all J2ME application profiles, the GCF is implemented in the configuration layer. The configuration layer is a set of J2ME libraries that form the basis of any J2ME architecture. Packages such as java.lang, java.util, java.io, and so forth can be found in the configuration layer. Figure 1 shows how the configuration layer relates to the profile layer in J2ME
 
 
 
 
J2ME Configurations
Currently, there are two J2ME configurations that support networking: the Connected Limited Device Configuration (CLDC) and the Connected Device Configuration (CDC). The CLDC is designed for more limited devices that require a smaller API footprint. MIDP is based on the CLDC. The CDC is designed for devices with more power and API footprint but are still too small to support standard Java. The Personal Profile is based on the CDC.

 

In order to enhance the upward compatibility of J2ME applications from the CLDC range of devices to the CDC range of devices, the CLDC and CDC have a strict superset-subset relationship. Figure 2 shows the relationship between the CLDC, CDC, and J2SE. Note that the CLDC and CDC contain APIs that are unique to J2ME and not part of standard Java. Libraries that are unique to J2ME exist in the javax.microedition.* packages.

Figure 2. Concentric Circles. The CDC and CLDC have a strict subset-superset relationship. Note also that J2ME configurations contain APIs that extend beyond what standard Java (J2SE) provides.

 

Configurations and Connectivity
The GCF is implemented in the CLDC, thereby making it available to any J2ME application since, by definition, the CDC must include everything defined by the CDLC. This has the effect of standardizing network connectivity throughout the J2ME environment, regardless of the profile or the device—a big advantage for J2ME developers.

 

Creating Network Connections using the GCF
Before discussing the architecture any further, I want to look at some code. At the heart of the GCF is the Connector class and a set of interfaces that are defined in the javax.microedition.io package. The Connector class is a factory that creates connection instances. The interfaces define the types of connections supported. How the implementer actually provides the network connectivity is not of concern, to either the GCF or the application developer, as long as the connection returned supports the behaviors defined by the GCF interface. An example of an HTTP connection is shown below.


HttpConnection c = (HttpConnection)
Connector.open("http://www.gearworks.com",  
  Connector.READ_WRITE, true);
The Connector class figures out what type of connection to create by parsing the connection string ("http://www.gearworks.com" in this case). The connection string is composed of three parts: the scheme, target, and parameters. The scheme is required but the latter two are optional. All connection strings follow the pattern noted below.

{scheme}: [{target}] [{parameters}]
In the HTTP example the scheme is "http" and the target is "www.gearworks.com." There are no parameters.

Although the connection string looks a lot like a URL, it can be used to create many different types of connections, not just HTTP connections. Table 1 shows examples of different types of connections that may be created using the GCF. Note however, the actual support for a specific connection is subject to the J2ME profile requirements and what a vendor chooses to implement. If the connection is not supported by the J2ME implementation a ConnectionNotFoundException is thrown.

HTTP http://www.host.com: 8080
Socket socket://host.com:80
Socket Listener socket://:1234
Datagram Sender datagram://host.com:9001
Datagram Listener datagram://: 9001
File file:/myfile.txt
Comm Port comm:com0;baudrate=19200;parity=odd

Table 1. Examples of different types of connections that may be created using the GCF. Support for the actual connection type, however, is implementation dependent.

 

 

Digging into Datagrams
It is quite possible for someone to have worked with Java for some time without ever needing to understand or use datagrams. For mobile devices, however, datagrams offer some advantages in that they are rather lightweight when compared to TCP-based connections such as sockets. User Datagram Protocol, UDP, is one of the more common datagram protocols. However, because most datagram protocols follow the same basic principals as to their usage, the GCF is able to support datagrams generically.
 
Datagrams are based on a connection-less paradigm, which means that a conversation is not established between two systems. Instead, datagrams are blindly transmitted across a network connection. To transmit a datagram from one system to another, an application creates a datagram and sends it to the intended target. This is a rather simple process in J2ME. However, there is a caveat to using datagrams in that they offer no guarantee or acknowledgement as to whether the datagram actually reached the intended recipient. As a result, a datagram implementation must either not care if the recipient actually receives the data 100 percent of the time, or an acknowledgement/handshake must be implemented manually by the application.

 

Using Datagrams
The following example shows how to create a datagram and send it to a specific IP address.


try	{
  DatagramConnection dgc = (DatagramConnection)
    Connector.open("datagram://localhost:9001");
  try {
    byte[] payload = "Test Message".getBytes();
    Datagram datagram = 
      dgc.newDatagram(payload, payload.length);
    dgc.send(datagram);
  } finally {
      dgc.close();
  }
} catch (IOException x) {
  x.printStackTrace();
}
In this example, a MIDlet creates a Datagram containing the text "Hello from a Datagram" and sends it to an application listening for datagrams on the local machine on port 9001. This example will execute just fine even if there is no application running to receive the datagram, thus demonstrating the connection-less nature of datagrams. The sender has no indication that the datagram was consumed by the target system.

Next I show how an application might receive the datagram sent by the previous code snippet.


try {
  DatagramConnection dgc = (DatagramConnection) 
    Connector.open("datagram://:9001");
  try {
    int size = 100;
    Datagram datagram = dgc.newDatagram(size);
    dgc.receive(datagram);
    System.out.println(
      new String(datagram.getData()).trim());
  } finally {
      dgc.close();
  }
} catch (IOException x){
  x.printStackTrace();
}
In the receive example, a datagram connection is established on port 9001. A datagram is created with a size of 100 bytes; if a received datagram contains more than 100 bytes, any data beyond the 100-byte mark is ignored. Once the datagram is created the receive() method is called, putting the thread in a wait state until a datagram is received. When a datagram is received, the payload is extracted from the datagram container and printed to the console.

In order to run the send and the receive examples, two instances of a MIDlet can be run from the Wireless Toolkit, one to perform the receive and one to perform the send.

 

Socket To Me
Another type of connection commonly available on J2ME devices is the TCP-based socket connection. Sockets are different than datagrams because they use a connection-based paradigm to transmit data. This means that both a sender and a receiver must be running and establish a communication channel for data to be exchanged. To use a real-world analogy, a socket connection is like calling a friend on the telephone. If the friend does not answer, a conversation cannot take place. Datagrams on the other hand are more like sending a letter to a friend, where a note is placed into an envelope, addressed, and mailed.

The following code demonstrates how to set up a listener to monitor a port for an inbound socket connection.


try
{
  ServerSocketConnection ssc = (ServerSocketConnection) 
  Connector.open("socket://:9002");
  StreamConnection sc = null;
  InputStream is = null;
  try{
    sc = ssc.acceptAndOpen();
    is = sc.openInputStream();
    int ch = 0;
    StringBuffer sb = new StringBuffer();
    while ((ch = is.read()) != -1){
      sb.append((char)ch);
    }
    System.out.println(sb.toString());
  } finally{
      ssc.close();
      sc.close();
      is.close();
  }
} catch (IOException x) {
    x.printStackTrace();
}
In this example a ServerSocketConnection is opened on port 9002. This type of connection is used for sole purpose of listening for an inbound socket connection. The code goes into a wait state when the acceptAndOpen() method is called. When a socket connection is established, the acceptAndOpen() method returns with an instance of a SocketConnection. Opening an input stream on this connection allows data to be read from the transmission.

The next example demonstrates the code required by the client to initiate the socket connection.


try{
  SocketConnection sc = (SocketConnection) 
    Connector.open("socket://localhost:9002");
  OutputStream os = null;
  try{
    os = sc.openOutputStream();
    byte[] data = "Hello from a socket!".getBytes();
    os.write(data);
  } finally{
      sc.close();
      os.close();
  }
} catch (IOException x){
	x.printStackTrace();
}
In this example a SocketConnection is established on port 9002 of the local machine. When using sockets, this is the point on the server side that the acceptAndOpen() method returns. If the connection is successfully opened, the OutputStream is obtained and a message is written to the stream. Note that because sockets are connection based, if there is no server listening for an incoming socket connection an exception will be thrown.
 

Reading Web Content
The last example will cover reading data using the MIDP HttpConnection. Note that this connection interface is not part of the CLDC or CDC, but is defined rather in the MIDP and Personal Profiles themselves. The behavior of HttpConnection is one that combines an InputStream and an OutputStream into a single connection. A single HttpConnection may open and use exactly one OutputStream and exactly one InputStream. The order in which the streams are used is important as well. The OutputStream, if used, must be used before the InputStream. Once the streams have been used the connection should be closed and a new HttpConnection should be opened to continue communications if necessary. This follows the HTTP request-response paradigm.

The HttpConnection is a bit more tricky to use than the socket or datagram connections because there is a lot that happens behind the scenes. There are three states to an HttpConnection:

  • Setup
  • Connected
  • Closed
The setup state is the first state encountered after a connection is opened. While in this state, connection parameters can be set such as the request method (GET, POST or HEAD) using the setRequestMethod() method or any header properties using the setRequestProperty() method.

The transition from setup to connected is triggered by any methods that cause data to be sent to the server. The following is a list of methods that cause this transition.

  • openInputStream
  • openDataInputStream
  • getLength
  • getType
  • getEncoding
  • getHeaderField
  • getResponseCode
  • getResponseMessage
  • getHeaderFieldInt
  • getHeaderFieldDate
  • getExpiration
  • getDate
  • getLastModified
  • getHeaderField
  • getHeaderFieldKey
Once the connection transitions to the connected state, any calls to setRequestMethod() and setRequestProperty() will throw an IOException. The state transition from setup to connected reflects the underlying handshake of the HttpConnection as headers are sent to the server and the connection prepares to send data. The following example demonstrates reading Web content from an HttpConnection.

HttpConnection c = null;
InputStream is = null;
StringBuffer sb = new StringBuffer();
try {
  c = (HttpConnection)Connector.open(
     "http://www.gearworks.com”, 
     Connector.READ_WRITE, true);
  c.setRequestMethod(HttpConnection.GET); //default
  is = c.openInputStream(); // transition to connected!
  int ch = 0;
  for(int ccnt=0; ccnt < 150; ccnt++) { // get the title.
    ch = is.read();
    if (ch == -1){
      break;
    }
    sb.append((char)ch);
  }
}
catch (IOException x){
	x.printStackTrace();
}
finally{
     try     {
       is.close();
          c.close();
     } catch (IOException x){
          x.printStackTrace();
     }
}
System.out.println(sb.toString());
In this example, the server at www.gearworks.com is contacted. Because this is an HttpConnection and no port is specified, port 80 is used by default. The request method is set to GET (note GET is the default and is explicitly set here only for the example). 
 
 
 
Knowing When to Use Which Protocol
As I've just explained, the GCF makes a number of networking options available to applications, but there are a few things you need to know in order to decide which to use when. In the case of MIDP, the specification only requires support for HTTP, although many devices support datagrams and sockets as well, because these protocols are needed to implement HTTP. However, before making commitments to datagrams or sockets it is a good idea to make sure they are supported on the platforms you are targeting. Because HTTP is guaranteed to be supported by MIDP this is usually the best protocol choice due to portability. HTTP moves through firewalls easily since it generally operates over port 80. However, of the three protocols discussed, HTTP incurs the most overhead, which may drive up the user's cost of using the application on the network.

Datagrams, on the other hand, tend to have far less overhead and can be easier on the end-user's budget. The caveat, as discussed previously, is that the application must take on the task of ensuring that all data arrived at the other end and that the order of that data received is correct.

Finally, sockets fall somewhere in the middle of HTTP and datagrams in that data sent over a socket is managed by the protocol, but requires fewer network resources than HTTP. This means that the application does not have to concern itself with making sure data is received on the other end. Sockets are generally a lighter-weight option over HTTP since the application controls when the end points communicate. HTTP, on the other hand, must make several trips between the end points to exchange header information as well as the data.

The GCF provides a lot of functionality and standardizes how connections are established throughout the J2ME architecture. The GCF is extensible as well, allowing manufacturers to support their own interfaces and connection types. In some cases a manufacturer may use the GCF to support streaming multimedia content with a special connection or for establishing connections to a GPS receiver. Having this kind of flexibility prevents J2ME from being constrained to a common set of protocols while maintaining consistency throughout the architecture.

Page 4 of 4
Posted by '김용환'
,

 

 

10. GCF 프로그래밍(1)

 

 

 

 

GCF(Generic Connection Framework)

J2ME는 기존의 풍부한 환경에서의 프로그래밍과 다르다. 보다 다양하고 제한된 환경에서의 프로그래밍을 고려해야 한다. 이런 환경에서의 입출력과 네트워크에 대한 문제를 고려한 스펙이 GCF이다.

GCF는 network 연결을 실행하는 무선기기들이 쉽게 실행될 수 있게 한다. 그것은 CLDC와 CDC 부분으로 되어있고, javax.microedition.io package안에 존재한다.

광범위한 기기들에게 호환이 될 수 있게 네트워크 와 I/O를 추상화 하여 Interface로 제공함.

 

<J2ME 환경>

1. CPU : 수백 MHZ

2. 메모리 : 1~10M

3. 하드디스크 : 유(수십 MB) or 무

4. 네트워크 속도 : 144kbps

 

이런 네워크환경에서 기존 자바와의 큰 차이점은

java.io.net를 사용할 수 없다는 점,

메모리 제약,

TCP/IP, WAP, iMode, IrDA, Bluetooth라는 새로운 통신방법에 대한 지원,

파일 시스템이 존재하지 않는 입출력 메커니즘을 하나로 통합할 필요성이 존재하기 때문에 새로운 개념의 입출력과 네트워크를 정의한 GCF를 소개.

 

<GCF 설계 목표>

: 서로 다른 형태의 입출력 형태를 일관성있게 지원.

: 서로 다른 형태의 프로토콜을 일관성 있게 지원.

: 어플리케이션 이식성 향상.

: 표준 자바 클래스 라이브러리와 상위 호환

: 더 작은 메모리 풋프린트를 가짐

 

javax.macroedition.io 패키지에 모든 클래스 포함.

 

 

위의 그림을 보면, TCP/IP, WAP, iMode, IrDA, Bluetooth등 7개의 인터페이스를 추상화 하였다.

1. Connection Interface

: GCF에서 가장 기본이 되는 최상위 인터페이스로서 단지 열리고 닫힐 수만 있다.

2. StreamConnectionNotifier Interface

: 연결이 설정할 때까지 기다리기 위해 사용되는 연결형 Connection이다.

3. DatagramConnection Interface

: UDP/IP 통신을 위한 데이터그램 종단을 표현하기 위한 연결형 Connection를 생성한다.

4. InputConnection, OutputConnection Interface

: 데이터를 읽고 쓰기 위한 장치를 지정하는 인터페이스.

5. StreamConnection Interface

: InputConnection, OutputConnection Interface에서 파생되어 Connection 인터페이스를 실제로 구현하는 클래스를 위한 논리적인 출발점을 제공.

6. ContentConnection

: StreamConnection에서 MIME 데이터의 입력과 출력을 담당하는 3개의 메소드를 추가 파생.

7. HttpConnection

: ContentConnectiion으로부터 파생, Hand set 등의 프로파일인 MIDP 스펙에 정의. 이것은 HTTP 1.1 프로토콜을 지원하기 위한 메소드와  상수를 포함하고 있다. 이 인터페이스는 모든 MIDP 실제 장치에 구현시 구현해야한다. 따라서, HttpConnection을 사용할 경우 MIDP를 구현한 모든 장치에서 사용할수 있는 장점이 있다.

Posted by '김용환'
,

DataSource의 복제를 통한 다중 플레이어 생성방법


안녕하세요. 지난번 강좌까지 실험 잘 하셨나요?
지난 번 까지의 소스만으로도 간단한 영상 음성 기록 프로그램을 만들수도 있고, 캡쳐 카드가 있다면, 비디오 카메라의 영상 출력을 캡쳐 카드의 입력단으로 연결하고, 프로그램을 약간 수정한다면, 컴퓨터로 비디오 카메라의 영상을 저장하는 프로그램을 만드는 것도 그리 어렵지 않다고 생각됩니다. 자. 한번 해보세요. 그리고 소스를 공개할 용의가 있는 분들은 함께 JMF를 시작하는 다른 분들에게도 정보를 나누어 주세요..어차피 기술은 공유되어야 더욱 더 발전됩니다. 혼자만 알고 있는 기술. 과연 그것이 자신만의 독보적인 핵심기술일까요.. 쩝.. 후후. 같이 열심히 하자는 이야기 입니다.


[알림]
여러분도 아시겠지만, 마이크로 소프트웨어 잡지의 2,3,4월호에도 JMF를 이용한 방송국 프로그램에 대한 강좌가 있더군요. 저도 읽어보았고 소스분석도 다 마쳤습니다. 일단은 목적이 인터넷 방송 선국장치를 통한 간략한 방송 프로그램의 작성이구요.. 네에.. 처음 JMF를 시작하시는 분들은 꼭 읽어보세요.. 자료도 별로 없을때 이렇게 연재를 시작한것은 정말 우리같은 개발자에게 도움이 됩니다. 아쉽다면 영상부분의 몇몇 설명을 그냥 건너뛴 점이구요.. 하여간 관심있는 분들 꼭 읽어보세요. 나중에 저도 시간나면 마이크로 소프트웨어의 JMF 강좌를 재 구성해서 괜찮은 프로그램 하나 만들어 올리도록 하죠.. 음.. 시간이 될라나 모르겠지만...


자. 이번 시간에는 Sun의 JMF 공식 홈페이지에 있는 Solution 예제중에서 우리에게 꽤나 필요한 프로그램인 Clone.java에 대해 알아보려고 합니다.
일단 먼저 소스 프로그램을 다운 받으시고, 실행해 보세요.. 

주어진 문제
DataSource로 부터 Source Clone을 생성하고, 미디어의 플레이백을 위해서 서로다른 Player를 생성한다. 

해결책
아래에 간단한 샘플 애플리케이션이 있습니다. 이것은 플레이백을 위한 입력 미디어의 URL을 실행인자로 받아들이고, 플레이백을 위해 얼마나 많은 복사본을 생성할지의 갯수를 받아들입니다. 실제 프로그램은 아래의 단계를 따라 수행됩니다. 

  1. 주어진 입력 URL을 통해서 DataSource를 생성하기 
  2. Manager.createCloneableDataSource를 이용해서 원본 DataSource로부터 cloneable DataSource를 생성하기 
  3. cloneable DataSource로부터 clone 생성하기 
  4. cloneable DataSource 와 각각 생성된 clones에 대하여 playback를 위한 player를 만들기 
 
  • 필요조건
  • 관련 클래스들 
  • 프로그램 구동 방법
  • 소스 코드
필요조건
Platform: JDK 1.1.6 or later
JMF API: 2.0 or later
Implementation: AJ, WPP, SPP *

* AJ = All Java, WPP = Windows Performance Pack, SPP = Solaris Performance Pack

관련클래스
  • javax.media.Player
  • javax.media.Manager
  • javax.media.protocol.SourceCloneable
프로그램 구동 방법
java Clone <URL> <# of copies>


Example:
java Clone file:/c:/temp/foo.mov 2
Source Code

위의 예제는 Sun의 JMF 홈페이지에서 보실 수 있습니다. 예제의 이름은 Clone.java인데, 아주 간단한 프로그램이지만, 실제로 전 이 프로그램에서 제공된 방법을 통해서 많은 부분 도움을 받고 있습니다. 처음 JMF 할때 도대체 데이타 복제를 어떻게 해야하나.. 여러개의 플레이어를 동시에 재생하려면 어떻게 해야하나하는 의문들이 들었었는데... 바로 이 소스 코드를 보고 해결할 수 있었습니다. 그래서 예제들도 무심히 지나치지 마시고, 반드시 숙독해서 자신의 프로그램에 바로바로 적용할 수 있도록 하는 것도 좋은 방법이라고 생각됩니다.

// Clone.java

// 요구되는 패키지들의 임포트부분 
import java.awt.*;
import javax.media.*;
import javax.media.control.TrackControl;
import javax.media.format.*;
import javax.media.protocol.*;

import java.net.*;
import java.io.*;

/**
* cloneable DataSource를 이용해서 실제 dataSource를 복제하고 그 결과로 playback을 수행하는 
* 애플리 케이션 입니다. 
* 이 프로그램은 Sun에서 제공된 예제 입니다.  
*/


// 데이타 소스의 복제를 위해 필요한 기능을 수행하는 클래스를 하나 만들었습니다.
// 이 클래스 내부에서 복제하고자 하는 데이타 소스를 인자로 넘겨받아서 Player를 생성하고 
// 생성된 Player에 대해 이벤트 처리를 하는 부분입니다.
// 주의 할것은, 단순히 player만 생성한후에 이벤트 처리와 함께 player의 states 처리를 제대로 하지 않으면
// 프로그램 구동후 얼마안가서 바로 해석하기도 힘든 에러가 발생한다는 점입니다. 
// 잊지 마세요.. 이벤트 처리 및 Player의 states 처리!!!

public class Clone extends Frame implements ControllerListener {

    Player p;        // Playback을 위한 Player

    Object waitSync = new Object();
    boolean stateTransitionOK = true;


    /**
    * 데이타소스가 주어지면, player를 생성하고, 생성된 player를 이용해서 
    * playback을 하기위해서 사용합니다. 
    */

    public boolean open(DataSource ds) {

   
    // 데이타소스의 타입이 어떠한 것인지를 먼저 알아봅니다. DataSource의 getContentType()를 이용합니다.
        // 이때 리턴되는 값은 데이타 소스의 미디어 타입에 대한 String 입니다.
        // 예를 들면 create player for: RAW 이와 같은 형태입니다.

        System.err.println("create player for: " + ds.getContentType());

        // player를 만들기 위해서 Manager의 createPlayer를 호출합니다.
        // 이때 넘겨주는 인자가 바로 데이타 소스 입니다. 
        try {
            p = Manager.createPlayer(ds);

        } catch (Exception e) {
            System.err.println("Failed to create a player from the given DataSource: " + e);
            return false;
        }

        // player의 생성이 성공했다면 player에 대한 이벤트 처리를 등록하여야 합니다.
        p.addControllerListener(this);


        // player를 Realize 단계까지 이동하도록 만들어 주어야 합니다.
        // 여기서 주의할것은 player를 생성한후 바로 start를 호출하지 말고 
        // 현재 player의 상태를 체크해서 각 상태에 알맞도록 처리를 해주어야 한다는 것입니다. 
        // player의 prefetch를 호출함으로서 player에게 prefetched 상태로 이동하도록 명령합니다.

        p.prefetch();

        // player가 원하는 상태에 도달되도록 기다립니다.
        if (!waitForState(p.Prefetched)) {
            System.err.println("Failed to realize the player.");
            return false;
        }

        // Realize 상태까지 완료되었다면 이제 우리는 이 부분에서 
        // VisualComponent와 ControlPanelComponent를 추가하여 사용자에게 시각적인 효과를 
        // 나타낼 수 있습니다. 
        // 이 부분에서는 일단 레이아웃을 변경했구요..
        setLayout(new BorderLayout());

        // visual component를 얻기위해서 선언했구요
        Component cc;

        // controlPanelComponent를 얻기 위해서 선언했습니다. 
        Component vc;
        if ((vc = p.getVisualComponent()) != null) {
            add("Center", vc);
        }

        if ((cc = p.getControlPanelComponent()) != null) {
            add("South", cc);
        }

        // 자. 여기까지 해서, 시각적인 컴포넌트의 등록까지 마쳤습니다.
        // 이제 남은것은 player를 start 시키는 일 입니다. 

        p.start();

        // 화면에 보이도록...
        setVisible(true);

        return true;
    }

    public void addNotify() {
        super.addNotify();
        pack();
    }

    /**
    * Player로 하여금 주어진 상태로 이동할때까지 Blocking을 합니다. 
    * 만약 player의 상태변화가실패하면 false가 리턴됩니다. 
    * 아래에 구현된 while에 의한 방법이 그래도 가장 안전한 방법이네요...
    */

    boolean waitForState(int state) {
        synchronized (waitSync) {
            try {
                while (p.getState() < state && stateTransitionOK)
                waitSync.wait();
            } catch (Exception e) {}
        }
        return stateTransitionOK;
    }


    /**
    * Controller에 대한 이벤트 리스터 부분입니다. 
    * 이 부분들은 예전부터 강좌에서 많이 설명드렸으니 어렵지 않죠??
    */
    public void controllerUpdate(ControllerEvent evt) {

        if (evt instanceof ConfigureCompleteEvent ||
            evt instanceof RealizeCompleteEvent ||
            evt instanceof PrefetchCompleteEvent) {
            synchronized (waitSync) {
                stateTransitionOK = true;
                waitSync.notifyAll();
            }
        } else if (evt instanceof ResourceUnavailableEvent) {
            synchronized (waitSync) {
                stateTransitionOK = false;
                waitSync.notifyAll();
            }
        } else if (evt instanceof EndOfMediaEvent) {
            p.close();
            //System.exit(0);
        } else if (evt instanceof SizeChangeEvent) {
        }
    }



    /**
    * Main program 부분입니다. 애플리케이션이니까 당근이죠..
    * main문의 인자로 플레이백을 위해 필요한 미디어 파일의 이름과 몇개의 데이타 소스를 
    * 복제할것인가에 대한 갯수를 넘겨주어야 합니다.
    */

    public static void main(String [] args) {

        if (args.length == 0) {
            prUsage();
            System.exit(0);
        }

        MediaLocator ml;
        int copies = 1;

        // 파일이름을 스트링형태로 받아서 MediaLocator를 생성합니다.
        if ((ml = new MediaLocator(args[0])) == null) {
            System.err.println("Cannot build media locator from: " + args[0]);
            prUsage();
            System.exit(0);
        }

        if (args.length > 1) {
        try {
            copies = new Integer(args[1]).intValue();
        } catch (NumberFormatException e) {
            System.err.println("An invalid # of copies is specified: " + args[1]);
            System.err.println("Will default to 1.");
            copies = 1;
        }
    }

    DataSource ds = null;

    // MediaLocator를 통해서 원하는 미디어 파일의 위치를 알아내었죠?
    // 이제 그렇게 알아낸 위치로부터 데이타 소스를 생성해 내야 합니다.
    // 바로 , Manager의 createDataSource를 호출합니다.
    // 물론 이때 넘겨주는 인자는 MediaLocator가 되겠지요..
    // 이 부분에 한 작업이 바로 원본 데이타소스를 만드는 과정이었구요..

    try {
        ds = Manager.createDataSource(ml);
    } catch (Exception e) {
        System.err.println("Cannot create DataSource from: " + ml);
        System.exit(0);
    }

    // 자. 이부분이 바로 데이타소스 복제의 핵심입니다.
    // Manager의 createCloneableDataSource를 이용해서 원본 데이터를 넘겨주면 
    // 원본 데이타소스에 대한 복제본 데이타 소스가 리턴되어 나옵니다.
    // 이 리턴된 복제본 데이타 소스를 이용해서 우리가 원하는 player를 만들고 playback을 하기위해서 
    // 위에서 만든 Clone 클래스로 복제본 데이타 소스를 넘겨주는 것입니다. 

    ds = Manager.createCloneableDataSource(ds);    // 정말 주의!! 이부분 확인하세요.. 아래에 정리했습니다. 

    if (ds == null) {
        System.err.println("Cannot clone the given DataSource");
        System.exit(0);
    }

    Clone clone = new Clone();


    // 복제본 데이타 소스 클래스는 위에서 만들었고, 복제된 데이타 소스를 넘겨줍니다.
   
// 이렇게 해서 player가 생성되도록 해야겠죠..
    
    if (!clone.open(ds))
        System.exit(0);

    // player를 여려개 만들려고 하는경우
    // 아래와 같이 루프를 돌려서 새롭게 player를 만들고 이벤트 처리를 위해서 
    // 원하는 갯수만큼 새롭게 Clone 클래스를 만들어 주어야 합니다. 

    for (int i = 1; i < copies; i++) {
        clone = new Clone();
        if (!clone.open(((SourceCloneable)ds).createClone()))
            System.exit(0);
        }
    }


    static void prUsage() {
        System.err.println("Usage: java Clone <url> <# of copies>");
    }
}

///////////////////// program End

SourceCloneable은 interface 로서, cloneable되어지는 경우에 반드시 implements되어야 합니다. 
cloneable DataSource를 생성하기 위해서는 Manager.createCloneableDataSource 를 이용해야 합니다.

 DataSource createClone()
          이 함수는 동일한 데이타 스트림의 복사본을 제공하기 위해 원본 DataSource의 clone을 생성하도록 해줍니다.
 

Method Detail

createClone

public DataSource createClone()
Create a clone of the original DataSource that provides a copy of the same data streams. The clones generated may or may not have the same properties of the original DataSource depending on the implementation. Therefore, they should be checked against the properties required for the application.
For example, the original DataSource may be a "pull" DataSource (PullDataSource or PullBufferDataSource). But the resulted clone may be the equivalent "push" DataSource. In that case, the resulting "push" DataSource will push data at the same rate at which the original DataSource is being pulled.
Returns:
a clone of the DataSource, or null if a clone could not be created.


createCloneableDataSource

public static DataSource createCloneableDataSource(DataSource source)
Creates a cloneable DataSource. The returned DataSource implements the SourceCloneable interface and enables the creation of clones by the createClone method.

If the input DataSource implements SourceCloneable, it will be returned right away as the result. Otherwise, a "proxy" DataSource is created. It implements the SourceCloneable interface and can be used to generate other clones.

[전 처음에 아랫부분 무시했다가 엄청 혼났습니다. 주의하세요.. ]
When createCloneableDataSource is called on a DataSource,
the returned DataSource should be used in place of the original DataSource.
Any attempt to use the original DataSource may generate unpredictable results.

The resulted cloneable DataSource can be used to generate clones.

The clones generated may or may not has the same properties of the original DataSource depending on the implementation. Therefore, they should be checked against the properties required for the application. If the original DataSource is not SourceCloneable and a "proxy" DataSource is resulted, the clones generated from this "proxy" DataSource is of type PushDataSource or PushBufferDataSource depending on the type of the original DataSource. In this case, each clone pushes data at the same rate that the original DataSource is pulled or pushed.
Parameters:
source - the DataSource to be cloned
Returns:
a cloneable DataSource for the given source

[정리]
그다지 어려운 소스 프로그래은 아닙니다만, 정말 중요한 내용이 많이 들어있었습니다. 그래서 특별히 이번소스를 분석해서 강좌에 넣게 되었구요. 이 부분에서 주의할 것은 원본 데이타 소스에 대해서 복제본을 만들기 위해서 Manager.createCloneableDataSource를 호출하였고, 리턴된 값이 바로 복제본 데이타 소스가 되는 것입니다. 이렇게 복제된 데이타 소스를 가지고, 원하는 갯수만큼의 player를 만들고 이벤트 처리를 하게 된것이지요.  현재 이 프로그램의 필요성이 없을런지는 몰라도..  아참. 바로 이전강좌에서도 제가 이 clone 클래스를 이용했거든요.. 기억이 나시나요? 한번 이전 강좌를 다시 읽어봐주세요..

또 하나 주의할 점은 Manager.createCloneableDataSource를 통해서 복제 가능한 데이타 소스를 생성했다면, 더 이상 원본 데이타 소스를 이용해서는 안되고, 원본 데이타 소스가 이용되는 부분에는 복제 가능한데이타 소스로 전부 바꾸어져야 한다는 것입니다. 실제로 제가 이것때문에 처음에 프로그램이 이유없이..(??) 다운되고, 시스템도 재부팅되고.. 하여간 정말 고생많았지요.. 언제나 도움말을 확실히 읽고 넘어가자구요.. 결국 도움말에 나와있더라구요.. 쩝...

 

[ 질문에 대한 답변]

아래 질문에 대한 답변은 함께 공유하면 좋을듯 싶어서... 올립니다. 지난 번에 어느분이 매일로 문의하신 내용인데요..

문의 : 제가 프로그램 돌릴때는 아래와 같은 에러 메시지만 나오고 프로그램이 중단됩니다. 예제는 Sun에서 다운로드 받은것도 있구요. 훈님의 강좌예제중에서 캡쳐해서 저장하는 예제도 아래와 같은 에러가 납니다.

Unable to realize com.sun.media.MediaEngine@62ac60a3 coudn't realize processor


답변: 사실 처음 프로그래밍할때 이런 에러가 나오면 정말 난감합니다. 도대체 이렇게 에러 메시지가 나오면 어디에서 찾아 보아야 하나요.. 쩝.. 그렇죠? 그런데 잘 보니까 MediaEngine 이라는 부분이 있네요.. 그리고 processor가 realize가 안되었다는 이야기이구요.. 사실 이것만 봐서는 어떤곳이 에러인지 정말 찾기가 어렵습니다. 그래서 프로그램 실행할때 생성되는 jmf.log 파일을 보내달라고 했습니다. 그래서 확인한 사항인데... 보내준 jmf.log 파일을 보니까... 주루루룩 설명이 있다가 아래와 같은 메시지가 있더군요..

## Connect to multiplexer
## Output content type : AVI

XX Failed to find a multiplexer for the specified output
XX Failed to realize : com.sun.media.MediaEngine@17c05de7
XX Cannot connect the multiplexer


자. 보셨나요.. 이곳에서도 MediaEngine 이라고 나옵니다. 그리고 그 밑에 processor가 realize되지 않는다고 나오지요. 그럼 그 윗부분을 보세요..  Connect to multiplexer부분에서 우리가 지정한 타입이 AVI 입니다. 즉, 예제 프로그램에서 AVI로 저장할 타입을 지정했는데.. 프로그램을 구동시키는 시스템에서는 특정타입을 AVI 변환해주는 Multiplexer를 찾을수 없다는 이야기입니다. 결국은 , 이러한 문제의 근본 원인은 우리가 프로그램에서 지정해준 압축 포맷이 프로세서의 CODEC에 의해서 인식되지 못한다는 점이지요.. 많은 분들이 윈도우 환경에서 AVI로 저장할때 이런 문제점을 많이 본다고 합니다. 이런 경우 결국은 프로그래머가 스스로 Codec을 만들던가( 좀 힘들겠죠?) 아니면 다른 파일포맷 (예를들어 .mov , .mpg)등으로 저장하도록 변경해야 합니다.
결국은 구동하는 시스템에 processor가 인식하는 multiplexer가 제대로 있는가의 문제입니다.

이부분에 대한 설명은 제 강좌의 JMFRegistry 부분에서도 설명이 되어 있습니다. 참조해보세요.


위의 그림에서 보는바와 같이 일단 원본 데이타소스인 A로부터 복제가능한 데이터소스인 B를 만들고 나면, A는 더이상 사용할 수가 없고, A가 필요한 자리에는 모두 B로 대치되어야 합니다. 이렇게 복제가능한 데이타 소스인 B가 만들어지면, 이 B를 가지고 player를 하나 만듭니다. 그 후에 2개의 player를 더 만들고 싶은경우, 원본 데이타 소스인 A를 이용하는 것이 아니라, 복제 가능한 데이타 소스인 B로부터 createClone 함수를 이용하여 계속해서 복제를 해서 사용해야 합니다.  주의하세요...

'java UI' 카테고리의 다른 글

quartz 를 이용하기  (0) 2008.01.22
File Connection Optiobal Package  (0) 2006.02.28
jtable tutorial  (0) 2005.02.16
The Eclipse runtime options  (0) 2005.02.15
www.eclipse-plugins.info 의 순위  (0) 2005.02.12
Posted by '김용환'
,