Home Nach oben Multiling. Programme Remote Debugging

HOWTO: Multilinguale Programme mit VC für Dummies

Wer kennt das nicht? Da hat man ein Projekt, das sich im inländischen Markt verkauft und bewährt und auf einmal heißt es: Da haben wir einen Kunden in Ungarn, der will das jetzt auch. Was nun?

Der Ressource Editor von VS6 und VS.NET bringt alles mit sich, was man benötigt, um ein Projekt einfach auf mehrere Sprachen umzustellen.

Der erste Ansatz: alle Ressourcen in einer Datei halten und irgendwas mit SetThreadLocale herumspielen. Das funktioniert nur leidlich unter NT/W2K/XP und bringt noch ein paar andere organisatorische Probleme mit sich. Ab einer bestimmten Ressourcen Anzahl wird VS zu einer keimenden Kartoffel. Zudem wird das pflegen extrem unübersichtlich. Hatte man mit einer Sprache 30 Dialoge, hat man bei 3 Sprachen schon 90. Weiterhin kann man kaum programmtechnische Hilfe bei einem solchen Design nutzen.

Also wie macht man es am besten?

Das Ziel:
Aus meiner Erfahrung ist es am besten, eine EXE Datei nur mit den entsprechenden Basisressourcen zu versehen und alle Sprachen als zusätzliche Sprach-DLL's dazu zu laden. Die MFC unterstützt das durch den Befehl AfxSetResourceHandle. In reinem Win32 Applikationen kann dies genauso erreicht werden, man muss nur sehr genau zwischen hInstance und hResourceInstance unterscheiden.

Grundlage:
Jede Ressource wird immer mit einer bestimmten Sprache kompiliert. Wird eine Ressource einer Sprache zugeordnet, dann setzt der Ressource Editor zusätzlich Compile-Guards (#ifdef #endif) Blöcke, die es erlauben auch selektiv Ressourcen zu kompilieren.

Die Blöcke sehen alle etwa in dieser Form aus:

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)

Der Ansatz ist ganz einfach. Wird also die Variable AFX_RESOURCE_DLL nicht definiert, das ist der Normalfall in einem Projekt, dann werden die Ressourcen immer einbezogen. Wird die Variable AFX_RESOURCE_DLL definiert, dann wird KEINE Ressource kompiliert außer denen, deren Sprachversion angegeben wird, hier z.B. AFX_TARG_ENU.

Wir gehen immer von einer primären Sprache aus (bei uns Englisch). In dieser wird ein Projekt normal angelegt und gepflegt.

Vorbereitung des Projektes der EXE Datei

Alle sprachabhängigen Ressourcen erhalten die entsprechende Sprache zugewiesen (ich bleibe mal bei meinem Beispiel mit Englisch). Achtung! Auch Bitmaps können sprachabhängig sein. Man denke nur die an die Darstellung von Kursiv/Italic auf einem Schalter in einem Toolbar. Ist das nun ein schräges K(ursiv) oder I(talic)? Oder der Tageskalender (Blatt mit T). Also aufgepasst!

Wenn wir nun die RC Datei im Projekt Explorer markieren und auf die Eigenschaften des Ressource Compilers klicken, können wir hier nun Angaben machen was zu kompilieren ist. In den "preprocessor Definitions" geben wir nun einfach AFX_RESOURCE_DLL ein.

Würden wir nun unsere Projekt kompilieren so wären keine Ressourcen in der EXE Datei zu finden außer denen, die wir irgendwo in der .rc2 Datei oder in den zusätzlichen Ressourcen untergebracht haben. Diese müssen nun auch unbedingt Compile-Guards erhalten.

Wichtig ist nun, dass einige Ressourcen, wie zum Beispiel die Icons immer in der Exe Datei drin sein sollten, genauso wie z.B. die Versionsinformationen.

Ich ordne diesen Ressourcen und allen anderen sprachneutralen Ressourcen nun die Sprache "Neutral" zu. Zusätzlich erhält die RC Datei des EXE Projektes nun die "preprocessor Definitions" AFX_RESOURCE_DLL, AFX_TARG_NEU.

Nun müssen nur noch drei Zeilen Code geschrieben werden, die die entsprechende Sprach-DLL laden und das Ressource Handle speichern. In einer MFC Applikation ist nur der Befehl AfxSetResourceHandle notwendig.

Nun haben wir eine Rumpf EXE. Da die Ressourcen aber hier noch im Projekt sind, funktioniert der Classwizard und alle anderen VS internen Features wie gewohnt.

Vorbereitung für die Sprach DLL der primären Sprache

Nun hier legen wir einfach ein Projekt für eine normale DLL an. In den Linker Optionen sollte /NOENTRY gesetzt werden. Dann wird auch kein DllMain Einsprungpunkt und kein Code mit kompiliert.

Wir legen hier eine leere RC Datei an. Bei den Ressource includes (rechter Mausklick auf die RC-Datei im Resourceeditor) wird nun einfach die RC Datei der EXE Datei noch einmal included. Evtl. (je nach Lage der Datei) sollte man bei den RC-Compiler Optionen noch den Pfad des Hauptprojektes als "Additional include directories" angeben.

Nun werden noch die "preprocessor Definitions" auf AFX_RESOURCE_DLL, AFX_TARG_ENU und AFX_TARG_NEU (Englisch USA und Neutral). Nicht vergessen auch die neutralen Ressourcen hier noch einmal einzubinden! Denn auch diese werden evtl. im Programm benötigt.

Vorbereitung für die Sprach DLL der sekundären Sprachen:

Auch hier legen wir einfach ein Projekt für eine normale DLL an. In den Linker Optionen sollte wieder /NOENTRY gesetzt werden.

Wir kopieren die RC Datei der primären Sprache in dieses neue Projekt. Wir geben die resource.h Datei des Hauptprojektes bei den Ressource "Readonly includes" mit an. Die Sprach DLL erhält eine eigene resource.h Datei, die im Allgemeinen jedoch unbenutzt bleibt.

Auch hier (je nach Lage der Datei) sollte man bei den RC-Compiler Optionen noch den Pfad des Hauptprojektes als "Additional include directories" angeben, damit die Bitmaps nicht noch einmal kopiert werden müssen, oder man legt die rc Dateien der Sprach DLL einfach in das selbe Verzeichnis, in dem auch die Ressourcendatei der Erstsprache liegen.

Nun werden noch die "preprocessor Definitions" auf AFX_RESOURCE_DLL, AFX_TARG_HUN und AFX_TARG_NEU (Ungarisch und Neutral) gesetzt. Auch die neutralen Ressourcen werden hier wieder eingebunden!

Achtung nicht vergessen die MFC Sprachressourcen in den "Ressource includes" korrekt einzubinden und auszutauschen.

Wir setzen RC-WinTrans ein. In diesem Fall wird keine neue RC Datei sondern eine Datei mit der Endung rci erzeugt. Diese wird dann wieder nur als Subinclude in eine leere RC Datei included. Nachteil: Ladbar ist diese Datei nicht mehr in den Resourceeditor. Das will aber auch keiner, denn das Übersetzungsutility erzeugt immer aus der Erstsprachen Ressource die neue Zielsprachen! Wichtig ist hier der Trick mit den Projekteinstellungen der "Additional include directories" für die RC-Datei.

Leider hat VSS ein paar Probleme (überflüssige Meldungen, Warnungen) wenn man den res Ordner zwischen mehreren Projekten teilt. Aber es ist auszuhalten. Man muss nur darauf achten immer nur Dateien in einem Projekt (Sprache) auszuchecken und zu bearbeiten.

An was man noch denken sollte:

Eine Versionskontrolle, die auch prüft, ob EXE und DLL zusammenpassen ist in meinen Augen unerlässlich. In meinen Augen kann man sich die Mühe sparen, eine Umschaltung auf andere Sprachen während des laufenden Programms zu implementieren. Es gibt einfach zu viele bereits existierende Fenster in der MFC und Klassen, die alle neu geladen werden müssen. Allen voran die CDocTemplates, die in der MDI Version auch Menüs vorhalten. Gleichfalls müssten nicht modale Dialoge zerstört und neu geladen werden. Ich habe mir das gespart. In den meisten Fällen ist die Entscheidung für eine Sprache einmal getroffen und wird nicht mehr geändert. Bei der Wahl von externen Libraries sollte man gleichfalls unbedingt auf entsprechende Implementierung der Mehrsprachigkeit achten. Wird dieselbe Technik verwendet erleichtert dies einiges. Ich setze auch die BCG-Library ein, die auch auf derselben Technik basiert.

Unterschiede zwischen MFC42 und MFC70

Die MFC42 und MFC70 DLL-Versionen verhalten sich unterschiedlich was ihre Sprachressourcen betrifft. Hier muss manuell eingegriffen werden und die entsprechende Sprachressource eingeladen werden. Die MFC42 lädt immer die MFC42LOCL.DLL, die MFC70.DLL lädt immer die MFC70xxx.DLL passend zur Systemsprache! D.h. bei der MFC42 muss der Benutzer sich für eine Sprache entscheiden und kopiert eine der mitgelieferten Sprach DLL's (MFC42DEU.DLL oder MFC42ENU.DLL( nach MFC42LOC.DLL).

Noch ein wichtiger Unterschied, mit dem man sich bei der MFC70 gerne ins Knie schießt! Die MFC70 lädt automatisch bei Durchführung von CWinApp::InitInstance eine entsprechende existierende Sprach DLL der Applikation, wenn diese im gleichen Verzeichnis vorhanden ist. Bei der Programmierung des Beispiels hatte ich das schon wieder vergessen und ich wunderte, mich warum ich nur die Deutsche Version immer geladen bekam. Grund war einfach, dass ich CWinApp::InitInstance immer erst nach meinem Code ausgeführt habe …

Ähnliches gilt, wenn man externe Libraries wie die BCG-Controlbar-Library einsetzt, dort gibt es entsprechende Sprach DLL's, die nachgeladen werden können.

Fazit

Vorteile:

Nachteile:

Das Beispielprojekt

Das Beispiel wurde mit VS.NET und der MFC 7.0 erzeugt und behandelt all das, was hier eben schon geschrieben wurde. Eine Versionsprüfung wurde nicht implementiert. Die entsprechende Sprachumschaltung der Applikation findet sich in InitInstance. Das Projekt wurde danach in ein VC++ 6.0 Projekt mit der MFC 4.2 überführt. Interessanter Weise ist kein Unterschied in der Programmierung notwendig, was ich ursprünglich befürchtete und mich um ein VC++ 6.0 Beispielprojekt herum gedrückt habe.

Wichtig ist jedoch anzumerken, dass beim MFC 4.2 Projekt die Sprach DLL's MFC42DEU.DL und MFC42ENU.DLL im entsprechenden Verzeichnis liegen sollten, in dem auch die MFC42.DLL liegt. Dies ist normalerweise nicht der Fall, meistens findet man nur eine MFC42LOC.DLL (s.o.). Ich habe deshalb beide DLL's noch einmal in ein separates Verzeichnis in das Demo Projekt gepackt.

Eine kleine Feinheit für die Sprach RC Dateien sei hier noch angemerkt: Die rc Dateien der Sprach DLL's liegen nicht in den Unterverzeichnissen, sondern im selben Verzeichnis wie die "Erstsprachen Ressource". Dadurch hat man auch keine Probleme, die sonstigen Ressourcen im RES Verzeichnis zu teilen.

Die beiden Projekte unterscheiden sich nur in den Anmerkungen und dass das VC++ 6.0 Projekt über kein Manifest verfügt. Das Manifest habe ich entfernt weil es "bekanntlich" Probleme beim Zeichnen der Rebars unter XP mit der MFC 4.2 gibt, wenn ein Manifest für die Common Control 6.0 eingesetzt wird.

Thats it.


Sollte ich etwas vergessen haben, dann schreibt es mir. Für grammatikalische und typographische Fehler bin ich bekannt und bitte dies zu entschuldigen.

Sachdienliche Kritik ist jederzeit willkommen.

Für die netten Fußtritte, die mich bewegten dieses HOWTO zu schreiben bedanke ich mich bei meiner netten newsgroup microsoft.public.de.vc!

Mein Dank gilt Dieter Smode für die hilfreiche Beseitigung grammatikalischer, inhaltlicher und typografischer Ungereimtheiten und auch Reto Felix, der das VC++ 6.0 Projekt erstellt hat und auch einen Fehler in der ersten Version korrigierte.

Version 2.2 vom 21.03.2003

Download Beispielprogramm für VS.NET 2002 und VC 6.0

Copyright © 2003 by Martin Richter, Germany [MVP]