TN059: Verwenden von MFC MBCS/Unicode-Umwandlungsmakros

Hinweis

Der folgende technische Hinweis wurde seit dem ersten Erscheinen in der Onlinedokumentation nicht aktualisiert. Daher können einige Verfahren und Themen veraltet oder falsch sein. Um aktuelle Informationen zu erhalten, wird empfohlen, das gewünschte Thema im Index der Onlinedokumentation zu suchen.

In diesem Hinweis wird beschrieben, wie Sie die Makros für die MBCS/Unicode-Konvertierung verwenden, die in AFXPRIV.H definiert sind. Diese Makros sind am nützlichsten, wenn Ihre Anwendung direkt mit der OLE-API umgeht oder aus irgendeinem Grund häufig zwischen Unicode und MBCS konvertiert werden muss.

Überblick

In MFC 3.x wurde eine spezielle DLL (MFCANS32.DLL) verwendet, um beim Aufrufen von OLE-Schnittstellen automatisch zwischen Unicode und MBCS zu konvertieren. Diese DLL war eine fast transparente Ebene, die OLE-Anwendungen so geschrieben werden konnte, als ob die OLE-APIs und -Schnittstellen MBCS waren, obwohl sie immer Unicode (außer auf dem Macintosh) sind. Obwohl diese Ebene praktisch war und anwendungen schnell von Win16 zu Win32 portiert werden konnte (MFC, Microsoft Word, Microsoft Excel und VBA, sind nur einige der Microsoft-Anwendungen, die diese Technologie verwendet haben), es hatte manchmal einen erheblichen Leistungstreffer. Aus diesem Grund verwendet MFC 4.x diese DLL nicht und spricht stattdessen direkt mit den Unicode OLE-Schnittstellen. Dazu muss MFC bei der Implementierung einer OLE-Schnittstelle in MBCS in Unicode konvertiert werden und muss häufig bei der Implementierung einer OLE-Schnittstelle in MBCS konvertiert werden. Um dies effizient und einfach zu verarbeiten, wurden eine Reihe von Makros erstellt, um diese Konvertierung zu vereinfachen.

Eine der größten Hindernisse beim Erstellen einer solchen Gruppe von Makros ist die Speicherzuweisung. Da die Zeichenfolgen nicht direkt konvertiert werden können, müssen neue Speicher für die konvertierten Ergebnisse zugewiesen werden. Dies könnte mit Code wie folgt geschehen:

// we want to convert an MBCS string in lpszA
int nLen = MultiByteToWideChar(CP_ACP,
    0,
    lpszA, -1,
    NULL,
    NULL);

LPWSTR lpszW = new WCHAR[nLen];
MultiByteToWideChar(CP_ACP,
    0,
    lpszA, -1,
    lpszW,
    nLen);

// use it to call OLE here
pI->SomeFunctionThatNeedsUnicode(lpszW);

// free the string
delete[] lpszW;

Dieser Ansatz ist eine Reihe von Problemen. Das Standard Problem besteht darin, dass es viele Code zum Schreiben, Testen und Debuggen gibt. Etwas, das ein einfacher Funktionsaufruf war, ist jetzt viel komplexer. Darüber hinaus gibt es einen erheblichen Laufzeitaufwand. Der Speicher muss für den Heap zugewiesen und bei jeder Konvertierung freigegeben werden. Schließlich müsste der obige Code für Unicode- und Macintosh-Builds angemessen #ifdefs hinzugefügt werden (was diese Konvertierung nicht erfordert).

Die Lösung, die wir erstellt haben, besteht darin, einige Makros zu erstellen, die 1) den Unterschied zwischen den verschiedenen Plattformen maskieren und 2) ein effizientes Speicherzuweisungsschema verwenden, und 3) sind einfach in den vorhandenen Quellcode einzufügen. Nachfolgend sehen Sie ein Beispiel für eine der Definitionen:

#define A2W(lpa) (\
((LPCSTR)lpa == NULL) NULL : (\
    _convert = (strnlen(lpa)+1),\
    AfxA2WHelper((LPWSTR) alloca(_convert*2),
    lpa,
    _convert)\)\)

Die Verwendung dieses Makros anstelle des obigen Codes ist wesentlich einfacher:

// use it to call OLE here
USES_CONVERSION;
pI->SomeFunctionThatNeedsUnicode(T2OLE(lpszA));

Es gibt zusätzliche Aufrufe, bei denen die Konvertierung erforderlich ist, die Verwendung der Makros ist jedoch einfach und effektiv.

Die Implementierung jedes Makros verwendet die _alloca()-Funktion, um Speicher aus dem Stapel anstelle des Heaps zuzuweisen. Das Zuordnen des Speichers aus dem Stapel ist viel schneller als das Zuordnen des Speichers auf dem Heap, und der Speicher wird automatisch freigegeben, wenn die Funktion beendet wird. Darüber hinaus vermeiden die Makros das Aufrufen MultiByteToWideChar (oder WideCharToMultiByte) mehrmals. Dies geschieht durch die Zuordnung von etwas mehr Arbeitsspeicher als erforderlich. Wir wissen, dass ein MBC in höchstens ein WCHAR konvertiert und für jede WCHAR maximal zwei MBC-Bytes vorhanden sind. Durch die Zuordnung von etwas mehr als erforderlich, aber immer genug, um die Konvertierung zu behandeln, wird der zweite Aufruf der Konvertierungsfunktion vermieden. Der Aufruf der Hilfsfunktion AfxA2Whelper reduziert die Anzahl der Argument-Pushs, die ausgeführt werden müssen, um die Konvertierung auszuführen (dies führt zu einem kleineren Code, als wenn er direkt aufgerufen wird MultiByteToWideChar ).

Damit die Makros Platz zum Speichern der temporären Länge haben, müssen Sie eine lokale Variable deklarieren, die _convert genannt wird, die dies in jeder Funktion ausführt, die die Konvertierungsmakros verwendet. Dies geschieht durch Aufrufen des USES_CONVERSION Makros wie oben im Beispiel gezeigt.

Es gibt sowohl generische Konvertierungsmakros als auch OLE-spezifische Makros. Diese beiden verschiedenen Makrosätze werden weiter unten erläutert. Alle Makros befinden sich in AFXPRIV.H.

Generische Konvertierungsmakros

Die generischen Konvertierungsmakros bilden den zugrunde liegenden Mechanismus. Das Makrobeispiel und die Implementierung, das im vorherigen Abschnitt A2W gezeigt wird, ist ein solches generisches Makro. Sie bezieht sich nicht speziell auf OLE. Der Satz generischer Makros ist unten aufgeführt:

A2CW      (LPCSTR) -> (LPCWSTR)
A2W      (LPCSTR) -> (LPWSTR)
W2CA      (LPCWSTR) -> (LPCSTR)
W2A      (LPCWSTR) -> (LPSTR)

Neben der Textkonvertierung gibt es auch Makros und Hilfsfunktionen zum Konvertieren TEXTMETRIC, , DEVMODE, BSTRund OLE zugeordneten Zeichenfolgen. Diese Makros liegen außerhalb des Rahmens dieser Diskussion - siehe AFXPRIV. H für weitere Informationen zu diesen Makros.

OLE-Konvertierungsmakros

Die OLE-Konvertierungsmakros wurden speziell für die Behandlung von Funktionen entwickelt, die OLESTR-Zeichen erwarten. Wenn Sie die OLE-Header untersuchen, werden viele Verweise auf LPCOLESTR und OLECHAR angezeigt. Diese Typen werden verwendet, um auf den Typ von Zeichen zu verweisen, die in OLE-Schnittstellen auf eine Weise verwendet werden, die nicht spezifisch für die Plattform ist. OLECHAR ist in Win16- und Macintosh-Plattformen und WCHAR in Win32 zugeordnetchar.

Um die Anzahl der #ifdef Direktiven im MFC-Code auf ein Minimum zu beschränken, verfügen wir für jede Konvertierung, in der OLE-Zeichenfolgen beteiligt sind, ein ähnliches Makro. Die folgenden Makros sind die am häufigsten verwendeten Makros:

T2COLE   (LPCTSTR) -> (LPCOLESTR)
T2OLE   (LPCTSTR) -> (LPOLESTR)
OLE2CT   (LPCOLESTR) -> (LPCTSTR)
OLE2T   (LPCOLESTR) -> (LPCSTR)

Auch hier gibt es ähnliche Makros für textmetric, DEVMODE, BSTR und OLE zugeordnete Zeichenfolgen. Verweisen Sie auf AFXPRIV. H für weitere Informationen.

Weitere Überlegungen

Verwenden Sie die Makros nicht in einer engen Schleife. Sie möchten z. B. nicht die folgende Art von Code schreiben:

void BadIterateCode(LPCTSTR lpsz)
{
    USES_CONVERSION;
    for (int ii = 0; ii <10000; ii++)
    pI->SomeMethod(ii, T2COLE(lpsz));

}

Der obige Code kann dazu führen, megabyte Arbeitsspeicher auf dem Stapel zu zuordnen, je nachdem, was der Inhalt der Zeichenfolge lpsz ist! Außerdem dauert es Zeit, um die Zeichenfolge für jede Iteration der Schleife zu konvertieren. Verschieben Sie stattdessen solche Konstantenkonvertierungen aus der Schleife:

void MuchBetterIterateCode(LPCTSTR lpsz)
{
    USES_CONVERSION;
    LPCOLESTR lpszT = T2COLE(lpsz);

    for (int ii = 0; ii <10000; ii++)
    pI->SomeMethod(ii, lpszT);

}

Wenn die Zeichenfolge nicht konstant ist, kapseln Sie den Methodenaufruf in eine Funktion. Dadurch kann der Konvertierungspuffer jedes Mal freigegeben werden. Beispiel:

void CallSomeMethod(int ii, LPCTSTR lpsz)
{
    USES_CONVERSION;
    pI->SomeMethod(ii, T2COLE(lpsz));

}

void MuchBetterIterateCode2(LPCTSTR* lpszArray)
{
    for (int ii = 0; ii <10000; ii++)
    CallSomeMethod(ii, lpszArray[ii]);

}

Geben Sie niemals das Ergebnis eines der Makros zurück, es sei denn, der Rückgabewert impliziert, eine Kopie der Daten vor der Rückgabe zu erstellen. Dieser Code ist beispielsweise schlecht:

LPTSTR BadConvert(ISomeInterface* pI)
{
    USES_CONVERSION;
    LPOLESTR lpsz = NULL;
    pI->GetFileName(&lpsz);

LPTSTR lpszT = OLE2T(lpsz);

    CoMemFree(lpsz);

return lpszT; // bad! returning alloca memory
}

Der obige Code könnte behoben werden, indem der Rückgabewert in etwas geändert wird, das den Wert kopiert:

CString BetterConvert(ISomeInterface* pI)
{
    USES_CONVERSION;
    LPOLESTR lpsz = NULL;
    pI->GetFileName(&lpsz);

LPTSTR lpszT = OLE2T(lpsz);

    CoMemFree(lpsz);

return lpszT; // CString makes copy
}

Die Makros sind einfach zu verwenden und einfach in Ihren Code einzufügen, aber wie Sie aus den obigen Einschränkungen erkennen können, müssen Sie bei der Verwendung vorsichtig sein.

Siehe auch

Technische Hinweise – nach Nummern geordnet
Technische Hinweise – nach Kategorien geordnet