language : Japanese | English
トップ 貯蓄率とFIREまでの年数 二次関数と虚数i 絵 コピックメイキング 曲 絶対音感 昔のソフトウェア Visual Studio C / C++ / MFC 備忘録 自作LLM 備忘録 日記 作者
RPGやMIDIシーケンサーを作ったときの備忘録になります。基本事項は本にして出していますので、そちらを参照してくださいませ。
ファイルの文字コードをShift-JIS(コードページ932)にしていると、ファイルの同一性が確認できず、ブレークポイントで止まらないバグがあります。これは、ファイルの文字コードをUTF-8(コードページ65001シグネチャ付)で保存すると解決します。もっとも、本当に古いヴァージョンのソースコードを参照することもあるから、古いソースコードはすべてzip圧縮して直接使えないようにしておくなどの措置をしておくのが安全です。
しかし、問題もあります。UTF-8のテキストファイルには先頭に3バイトのBOM{0xEF,0xBB,0xBF}が付いてますが、エディタやコンパイラによってはこのBOM付きUTF-8テキストファイルを正しく認識できないのです。例えば、Microsoft製リソースコンパイラはUTF-8のリソースファイル(*.rc)をコンパイルできません。ではBOMなしのUTF-8にすれば良いのではという案がありますが、日本語Windows上では、BOMのないテキストファイルは、Shift-JISとみなされるのが普通です。中には自動判別してくれるソフトもありますが、世界中に文字コードセットは30種類以上あるので、特にテキストが短い場合には誤判定も多くあてになりません。したがって、UTF-8にしておくのは*.c, *.cpp, *.hぐらいが良さそうです。
参照しているdllと同じフォルダ内に、pdbファイルが必要です。ところが2024年1月ごろ、VisualStudioをアップデートすると、pdbファイルなどの中間ファイルが、dllとは別のディレクトリに出力されるように、勝手に変更されたようです。dllと同じフォルダに設定してあっても、勝手にプロジェクトの設定がかわっています。これを手動で元に戻し、常にdllと同じフォルダにpdbファイル等を出力する必要があります。直す場所は、プロジェクトのプロパティ→構成プロパティ→全般→中間ディレクトリ→"$(ShortProjectName)\(Platform)\(Configuration)\"の"$(ShortProjectName\"を削除します。Debug-Win32,Debug-x64,Release-Win32,Release-x64の4つとも直してください。
また、ツール-オプション-デバッグ-「マイコードのみを有効にする」も念のためOFFにしておいた方がよいもしれません。マイコードの定義は謎ですが。
ドッキング可能なツールバー・ダイアログバーや、ドキュメントビューアーキテクチャー、複数ページの印刷プレビューなどを使用する場合、C++/MFCを用いるのが簡便です(これらのコードを自力で書くのには途方もない時間を要します)。その他の場合、無料のVisual Studio Express Editionがあれば開発できることを考え、C/Win32APIで書くと良いようです。しかし2015年、MicrosoftがVisual Studio Community Editionをリリースし、個人開発やオープンソース開発等に限り、MFCを無料で提供することとなりました。最近のソフトはサブスクリプション版しかなく、お値段も高いので(円安でますます高くなっている)、個人開発やオープンソース開発は無料で使わせてくれるMicrosoftには感謝です。Windowsをサブスクにしたらダメですが。
C言語によるWindowsアプリケーションの書き方を解説していると、それだけで1冊の本が書けてしまうので、ここでは、最小のWindowsアプリケーションを紹介します。このプログラムは、メッセージボックスを表示するだけで終わりです。
#include <tchar.h> #include <windows.h> int WINAPI _tWinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { return MessageBox (NULL, _T("こんにちは、世界よ。"), _T("例題"), MB_ICONEXCLAMATION | MB_OK); }
実行結果
WindowsXP以降では、すべての文字列はUnicodeのUTF-16LEで内部処理していますので、ご使用のOSが日本語Windowsだとしても、IMEパッドを使ってラテン文字・漢字・ハングル文字・キリル文字を含むすべての文字を使えるようになっています(ファイル名を含む)。例えば「メモ帳」は全言語対応です。
WindowsアプリケーションにはANSI版とUnicode版の2種類があります。ANSI版では内部的に文字はすべて1バイト単位で扱われ、その解釈・表示方法は使用している文字コードセットにより異なるため、文字化けの心配があります。一方、Unicode版ではすべて2バイト単位(正確にはsizeof(wchar_t)バイト単位)で扱われ、その解釈・表示方法は全国共通であるため、文字化けの心配はありません(その文字を表示できるフォントである限り)。Windows2000以前はANSI版のアプリケーションが主流でした。現在では、日本語で動けば英語で動くのだから問題ないという時代は既に終わり、多くのソフトが日本語・英語・中国語・その他の言語に対応しています。そのため、ANSI版のプログラムはUnicode版に移行する必要があります。
移行期間や互換性検証の問題もあるので、ひとつのソースコードからANSI版のexeファイルとUnicode版のexeファイルが両方出来上がれば便利です。そのためには、tchar.hで定義されているマクロを活用します。例えば、TCHARは、_UNICODEが定義されている場合はwchar_tに、そうでない場合はcharに展開されます。ソースコード中にANSIもしくはUnicodeのどちらかに依存するコードが入ってはいけません。やむを得ない場合は、その部分だけ#ifdef _UNICODE #else #endifで分けます。なお、MFCのCString型はこのマクロ機構が既に組み込まれているので、どちらの場合でも気にせず使えます(リテラル文字列使用時は_T("")にする必要あり)。
Visual Studio 2005以降では、デフォルトでUnicode版のexeファイルができるようになっています。それ以前の古いVisual StudioではデフォルトでANSI版のexeファイルができるようになっています。このページでは、ANSI版とUnicode版の両方に対応するため、必ずTCHARで定義されているマクロを用い、_UNICODEの定義があるかないかで出力するexeファイルの種類を切り替えらえるようにしています。Visual Studioを使用している場合は、プロジェクト -> (プロジェクト名)のプロパティ... -> 構成プロパティ -> 全般 -> 文字セットで、「設定なし」がANSI版、「Unicode文字セットを使用する」がUnicode版となります。
tchar.hで定義されている、ANSIとUnicode両対応マクロの一例
マクロ | _UNICODEが未定義の場合 | _UNICODEが定義されている場合 | 意味 |
_tmain | main | wmain | コンソールアプリケーションのメイン関数。 |
_tWinMain | WinMain | wWinMain | Windowsアプリケーションのメイン関数。 |
TCHAR | char | wchar_t | 文字の最小単位。ANSIでは1~2バイト/文字で、テーブルは文字コードセット(Latin-1, Shift-JIS, 簡体字中国語, 韓国語, 繁体字中国語など)により異なるので文字化けの元になる。Unicodeではsizeof (wchar_t)バイト/文字で、テーブルは世界共通である。 |
LPTSTR | char* | wchar_t* | 文字列へのポインタ。 |
LPCTSTR | const char* | const wchar_t* | 定数文字列へのポインタ。 |
_T("") | "" | L"" | リテラル文字列。Lがないものはchar型扱い、Lがあるものはwchar_t型扱い。 |
_tprintf | printf | _wprintf | コンソール画面に文字列出力。文字列の指定方法がconst char*型かconst wchar_t*型かで異なる。 |
_tfopen | fopen | _wfopen | ファイルを開く。ファイル名とモードの指定方法がconst char*型かconst wchar_t*型かで異なる。 |
_tcslen | strlen | wcslen | ゼロ終了文字列の長さ測定。strlenは1バイト単位、wcslenはsizeof (wchar_t)バイト単位。 |
_tcsncmp | strncmp | wcsncmp | 文字列の比較。 |
_tcsncpy | strncpy | wcsncpy | 文字列のコピー。 |
_sntprintf | _snprintf | _snwprintf | 文字列のフォーマット。 |
_ttoi | atoi | _wtoi | 文字列を整数(int型)に変換。 |
_ttof 又は _tstof | atof | _wtof | 文字列を浮動小数(double型)に変換。 |
_fputts | fputs | fputws | ファイルに文字列書き込み。 |
_fgetts | fgets | fgetws | ファイルから文字列を読み取り。 |
_ftprintf | fprintf | fwprintf | ファイルに文字列を書き込み。 |
_ftscanf | fscanf | fwscanf | ファイルから文字列を読み取り。 |
下記のようなマクロを記述しておくと、このライブラリを用いるプロジェクトでUNICODEが定義されていればGetHelloWorldWが採用され、そうでなければGetHelloWorldAが採用されますが、どちらの場合でもGetHelloWorldに見えるため、ライブラリユーザーは非UNICODEとUNICODEでコードを分けなくて済みます。なお、このようにライブラリ内で関数を分ける必要があるのは、引数又は戻り値に文字列や文字列へのポインタを含む場合のみです。
TestDLL.def
;LIBRARY TestDLL EXPORTS GetHelloWorldA GetHelloWorldW
TestDLL.h
#ifndef _TESTDLL_H_ #define _TESTDLL_H_ char* __stdcall GetHelloWorldA (char* pBuf, long lLen); wchar_t* __stdcall GetHelloWorldW (wchar_t* pBuf, long lLen); #ifdef UNICODE #define GetHelloWorld GetHelloWorldW #else #define GetHelloWorld GetHelloWorldA #endif #endif
TestDLL.c
#include <stdlib.h> #include <string.h> #include <wchar.h> #include "TestDLL.h" char* __stdcall GetHelloWorldA (char* pBuf, long lLen) { memset (pBuf, 0, lLen); strncpy (pBuf, "HelloWorld", lLen - 1); return pBuf; } wchar_t* __stdcall GetHelloWorldW (wchar_t* pBuf, long lLen) { memset (pBuf, 0, lLen * sizeof(wchar_t)); wcsncpy (pBuf, L"HelloWorld", lLen - 1); return pBuf; }
レジストリを使うかローカルiniファイルを使うかについては、各プロジェクトの判断によりますが、ここでは、MFCでローカルiniファイルを用いる方法を述べます。まず、InitInstanceあたりでCWinApp::m_pszProfileNameをdeleteし、ここにiniファイル名(フルパス付き)の文字列領域を再度newして文字列を設定します。すると、以降のLoadStdProfileSettingsやGetProfileStringやWriteProfileStringのターゲットは指定iniファイルに固定されます。
BOOL CExampleApp::InitInstance () { // (中略) // EXEファイルのフルパス名を取得 TCHAR szPath[_MAX_PATH + 1]; TCHAR szDrive[_MAX_DRIVE + 1]; TCHAR szDir[_MAX_DIR + 1]; TCHAR szFileName[_MAX_FNAME + 1]; TCHAR szExt[_MAX_EXT + 1]; memset (szPath, 0, sizeof (szPath)); memset (szDrive, 0, sizeof (szDrive)); memset (szDir, 0, sizeof (szDir)); memset (szFName, 0, sizeof (szFName)); memset (szExt, 0, sizeof (szExt)); ::GetModuleFileName (this->m_hInstance, szPath, _MAX_PATH); _tsplitpath (szPath, szDrive, szDir, szFileName, szExt); m_strExeFilePath.Format (_T("%s%s"), szDrive, szDir); if (m_strExeFilePath.Right (11) == _T("\\.x64\\Debug\\")) { m_strExeFilePath = m_strExeFilePath.Left (m_strExeFilePath.GetLength () - 10); } else if (m_strExeFilePath.Right (13) == _T("\\.x64\\Release\\")) { m_strExeFilePath = m_strExeFilePath.Left (m_strExeFilePath.GetLength () - 12); } else if (m_strExeFilePath.Right (7) == _T("\\Debug\\")) { m_strExeFilePath = m_strExeFilePath.Left (m_strExeFilePath.GetLength () - 6); } else if (m_strExeFilePath.Right (9) == _T("\\Release\\")) { m_strExeFilePath = m_strExeFilePath.Left (m_strExeFilePath.GetLength () - 8); } // INIファイルのフルパス名の変更 if (m_pszProfileName) { delete ((void*)m_pszProfileName); m_pszProfileName = NULL; } m_pszProfileName = new TCHAR[MAX_PATH + 1]; if (m_pszProfileName == NULL) { AfxMessageBox (_T("メモリ不足です。"), MB_ICONSTOP); return FALSE; } memset ((void*)m_pszProfileName, 0, sizeof (TCHAR) * (MAX_PATH + 1)); _sntprintf ((TCHAR*)m_pszProfileName, MAX_PATH, _T("%s%s"), m_strExeFilePath, _T("Example.ini")); // (中略) return TRUE; }
Win32APIに用意されている所定のiniファイルに設定値を読み書きするこれらの関数は非常に便利ですが、後部に付随している半角スペースを無視するという問題点があるため、半角スペースで終わる文字列を記録すると、たちまちバグとなります。半角スペースで終わる文字列を格納する可能性がある場合は、GetPrivateProfileStringとWritePrivateProfileStringは自作関数を作る必要があります。オープンソースのIniDataライブラリを用いるのも良いでしょう。
また、BOMのないiniファイルは、日本ではShift-JISとして扱われるため、Unicode文字に対応しておりません。UTF-8のiniファイルは、ラテン文字・漢字・ハングル文字・キリル文字のGetPrivateProfileString/WritePrivateProfileStringができません。BOM付きのUTF-16LEではうまくいくようです。したがって、iniファイルはBOM付UTF-16LEで統一しておくのが最も無難です。
VirtualStoreの問題にも気をつける必要があります。WindowsVista以降、Windowsのユーザーアカウント制御(UAC)機能により、c:\program files, c:\program files(x86), c:\windowsの中には、ファイルに書き込みをすることはできません。iniファイルばかりでなくゲームのセーブデータなど全ファイルについて、書き込みモードのfopenは、virtualstoreフォルダ上で行われます。UACをユーザーに解除させること、あるいはプログラム的に無効化することは可能ですが、セキュリティ上問題があります。
使用領域をゼロで初期化して使っても、文字列の最後のヌル文字以降のゼロ初期化が不定値になるようです。したがって、これの続きにstrcatなどを使うと、たちまちバグとなります。既に自分でメモリを管理しているならば、strcpy / wcscpyのままにすることをお勧めします。
上記の問題に加え、途中で変換不能文字があった場合の動作が未定義です。また、改行コード\r,\nの変換が正しくないようです。既に正常に動作しているのであれば、mbstowcs / wcstombsのままにすることをお勧めします。
初歩的な問題ですが、rtモードでfopenしたテキストファイルからfgetsすると、その文字列には最後に"\r\n"や"\r"や"\n"がくっついてきます。そのため、必要文字列をファイルから正しく獲得するには、最後に引っ付いているこれらを'\0'で置き換えるのを忘れないように注意します。一方、fputsでは"\r\n"や"\r"や"\n"はつかないので自前でつける必要があります。
UTF-16LEのテキストファイルをwchar_t配列に読み書きするには、fopen時に、modeの引数のところを、"rt, ccs=UTF-16LE" もしくは "wt, ccs=UTF-16LE" と指定します。UTF-8のテキストファイルをwchar_t配列に読み書きするには、fopen時に、modeの引数のところを、"rt, ccs=UTF-8" もしくは "wt, ccs=UTF-8" と指定します。mode引数にccsを何も指定しないとANSI仕様で読み書きするので、wchar_t配列への読み書きはうまくいきません。
CStdioFileはUnicodeに対応していないのでfopenとfcloseだけ手動でする必要があります。
FILE* pFile = _tfopen (strFileName, _T("rt, ccs=UTF-8")); if (pFile == NULL) { /* エラー時の処理 */ } CStdioFile theStdioFile (pFile); ... fclose (pFile);
サテライトDLLとは、リソースのみを含むDLLです。プログラムを日本語・英語・中国語などに対応させるため、言語に依存したリソース(ビットマップ、アイコン、カーソル、ツールバー、メニュー、アクセラレーター、ダイアログ、文字列など)は専用のDLLに分離して記述します。言語別にサテライトDLLを用意し(例:ExampleJpn.dll, ExampleEnu.dll, ExampleChs.dll)、アプリケーションの初期化時に、所定言語のサテライトDLLを読み込みます。実行時にはサテライトDLLから文字列などを読み込みます。アプリケーションの終了時にはそのサテライトDLLを解放します。resource.hで各言語間のリソースIDを共通にしておけば、基本的にソースコードは言語に影響しません。
次の例は、上記の最小のWindowsアプリケーション(Hello World)を国際化した例です。プロジェクトは4つ必要です。ひとつは、Example.exe製作用で、Example.cとresource.hを使用。ひとつは、ExampleEnu.dll製作用で、ExampleEnu.rcとresource.hを使用。ひとつは、ExampleJpn.dll製作用で、ExampleJpn.rcとresource.hを使用。ひとつは、ExampleChs制作用で、ExampleChs.rcとresource.hを使用。resource.hは全プロジェクト共通なので、#includeでは相対パスを指定しておいてください。サテライトDLLでは、プロジェクトのプロパティで、全般→構成の種類を「ダイナミックリンクライブラリ(DLL)」にするとともに、リンカ→詳細→エントリポイントなしを「はい(/NOENTRY)」に設定してください。デバッグ版とリリース版の両方設定するのを忘れないでください。ただし、サテライトDLLは、プリプロセッサ(例:ENU, JPN, CHS)とコンパイラスイッチ(例:#ifdef ENU, #ifdef JPN, #ifdef CHS)を用いて、うまくひとつのプロジェクトにまとめることができます。
reosurce.h (UTF-8, BOM付, 1行目はBOMを分離するため空行かコメントとする)
#define IDS_APPNAME 0x2001 #define IDS_MESSAGE 0x2002
ExampleEnu.rc (UTF-16LE, BOM付)
#include <windows.h> #include "..\\resource.h" STRINGTABLE DISCARDABLE { IDS_APPNAME "Example" IDS_MESSAGE "Hello World !" }
ExampleJpn.rc (UTF-16LE, BOM付)
#include <windows.h> #include "..\\resource.h" STRINGTABLE DISCARDABLE { IDS_APPNAME "例題" IDS_MESSAGE "こんにちは、世界よ。" }
ExampleChs.rc (UTF-16LE, BOM付)
#include <windows.h> #include "..\\resource.h" STRINGTABLE DISCARDABLE { IDS_APPNAME "例题" IDS_MESSAGE "您好,世界。" }
Example.c (UTF-8, BOM付)
#include <stdio.h> #include <tchar.h> #include <windows.h> #include "resource.h" HINSTANCE g_hInstance; HANDLE g_hResource; TCHAR g_szExeFilePath[256]; TCHAR g_szIniFileName[256]; TCHAR g_szLanguage[256]; TCHAR g_szAppName[256]; TCHAR g_szMessage[256]; /* EXEファイルのあるパスを設定 */ void InitExeFilePath () { TCHAR szPath[_MAX_PATH + 1]; TCHAR szDrive[_MAX_DRIVE + 1]; TCHAR szDir[_MAX_DIR + 1]; TCHAR szFName[_MAX_FNAME + 1]; TCHAR szExt[_MAX_EXT + 1]; long lLen = 0; memset (szPath, 0, sizeof (szPath)); memset (szDrive, 0, sizeof (szDrive)); memset (szDir, 0, sizeof (szDir)); memset (szFName, 0, sizeof (szFName)); memset (szExt, 0, sizeof (szExt)); /* EXEファイル名をフルパスで取得 */ GetModuleFileName (g_hInstance, szPath, _MAX_PATH); /* EXEファイルのあるディレクトリ名を設定 */ _tsplitpath (szPath, szDrive, szDir, szFName, szExt); _sntprintf (g_szExeFilePath, 255, _T("%s%s"), szDrive, szDir); lLen = _tcslen (g_szExeFilePath); if (lLen >= 11 && _tcsncmp (&g_szExeFilePath[lLen - 11], _T("\\.x64\\Debug\\"), 11) == 0) { g_szExeFilePath[lLen - 11] = _T('\0'); } else if (lLen >= 13 && _tcsncmp (&g_szExeFilePath[lLen - 13], _T("\\.x64\\Release\\"), 13) == 0) { g_szExeFilePath[lLen - 13] = _T('\0'); } else if (lLen >= 7 && _tcsncmp (&g_szExeFilePath[lLen - 7], _T("\\Debug\\"), 7) == 0) { g_szExeFilePath[lLen - 7] = _T('\0'); } else if (lLen >= 9 && _tcsncmp (&g_szExeFilePath[lLen - 9], _T("\\Release\\"), 9) == 0) { g_szExeFilePath[lLen - 9] = _T('\0'); } } /* リソースDLLを読み込む */ BOOL LoadResourceDLL () { if (_tcscmp (g_szLanguage, _T("English")) == 0) { g_hResource = LoadLibrary (_T("ExampleEnu.dll")); if (g_hResource == NULL) { MessageBox (NULL, _T("ExampleEnu.dll Load Failed!"), _T("Example"), MB_ICONSTOP); return FALSE; } } else if (_tcscmp (g_szLanguage, _T("Chinese")) == 0) { g_hResource = LoadLibrary (_T("ExampleChs.dll")); if (g_hResource == NULL) { MessageBox (NULL, _T("ExampleChs.dll Load Failed!"), _T("Example"), MB_ICONSTOP); return FALSE; } } else { g_hResource = LoadLibrary (_T("ExampleJpn.dll")); if (g_hResource == NULL) { MessageBox (NULL, _T("ExampleJpn.dll Load Failed!"), _T("Example"), MB_ICONSTOP); return FALSE; } } return TRUE; } /* リソースDLLを解放する */ void FreeResourceDLL () { if (g_hResource) { FreeLibrary (g_hResource); } g_hResource = NULL; } /* メイン関数 */ int WINAPI _tWinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { g_hInstance = hInstance; /* exeファイルのあるパスを調べる */ InitExeFilePath (); /* iniファイルから言語設定を読み込む */ _sntprintf (g_szIniFileName, 255, _T("%s\\%s"), g_szExeFilePath, _T("Example.ini")); GetPrivateProfileString (_T("Language"), _T("Language"), _T("Japanese"), g_szLanguage, 255, g_szIniFileName); /* 言語別サテライトDLLの読み込み */ if (LoadResourceDLL () == FALSE) { return 0; } /* 本論 */ LoadString (g_hResource, IDS_APPNAME, g_szAppName, 255); LoadString (g_hResource, IDS_MESSAGE, g_szMessage, 255); MessageBox (NULL, g_szMessage, g_szAppName, MB_ICONEXCLAMATION | MB_OK); /* 言語別サテライトDLLの解放 */ FreeResourceDLL (); return 1; }
Example.ini (UTF-16LE, BOM付, 1行目はBOMを分離するため空行とする)
[Language] Language=Chinese
実行結果
Language=English
Language=Japanese
Language=Chinese
MFCを用いた場合、上述の対応に加え、MFCで定義しているリソース(例:"1から127までの数字を入力してください。")も日本語・英語・中国語などに対応させる必要があります。MFC内部では、mfcXXloc.dllという名前のdllを既定のサテライトDLLとして読み込むため、ひとつのパッケージにつきひとつの言語しか対応できません。解決方法として、MFCに付属の言語別サテライトDLLをすべて用意しておき、アプリケーションの初期化時に、リソースハンドルにターゲットのサテライトDLLをLoadLibraryし、すでに割り当てられているAfxGetMuduleState()->m_appLangDLLをFreeLibraryし、そのメンバ変数に先ほどのリソースハンドルを代入します。MFC以外の部分のサテライトDLLは、C/Win32APIの場合と同様な処理ですが、そのリソースハンドルをAfxSetResourceHandleする必要があります。アプリケーションの終了時にはそのリソースハンドルをFreeLibraryします。
// リソースDLLの読み込み BOOL CExampleApp::LoadResourceDLL () { if (m_strLanguage == _T("English")) { m_hMFCResourceDLL = ::LoadLibrary (_T("MFC90Enu.dll")); if (m_hMFCResourceDLL == NULL) { ::MessageBox (NULL, _T("MFC90Enu.dll Load failed!"), _T("Example"), MB_ICONEXCLAMATION); return FALSE; } m_hResourceDLL = ::LoadLibrary (_T("ExampleEnu.dll")); if (m_hResourceDLL == NULL) { ::MessageBox (NULL, _T("ExampleEnu.dll Load failed!"), _T("Example"), MB_ICONEXCLAMATION); return FALSE; } } else if (m_strLanguage == _T("Chinese")) { m_hMFCResourceDLL = ::LoadLibrary (_T("MFC90Chs.dll")); if (m_hMFCResourceDLL == NULL) { ::MessageBox (NULL, _T("MFC90Enu.dll Load failed!"), _T("Example"), MB_ICONEXCLAMATION); return FALSE; } m_hResourceDLL = ::LoadLibrary (_T("ExampleChs.dll")); if (m_hResourceDLL == NULL) { ::MessageBox (NULL, _T("ExampleChs.dll Load failed!"), _T("Example"), MB_ICONEXCLAMATION); return FALSE; } } else { m_hMFCResourceDLL = ::LoadLibrary (_T("MFC90Jpn.dll")); if (m_hMFCResourceDLL == NULL) { ::MessageBox (NULL, _T("MFC90Jpn.dll Load failed!"), _T("Example"), MB_ICONEXCLAMATION); return FALSE; } m_hResourceDLL = ::LoadLibrary (_T("ExampleJpn.dll")); if (m_hResourceDLL == NULL) { ::MessageBox (NULL, _T("ExampleJpn.dll Load failed!"), _T("Example"), MB_ICONEXCLAMATION); return FALSE; } } // MFCの言語別リソースDLLはDLLMainで勝手に設定されてしまうので、 // いったん解放してから指定したDLLを設定しなおす。 AFX_MODULE_STATE* pState = AfxGetModuleState (); if (pState->m_appLangDLL) { ::FreeLibrary (pState->m_appLangDLL); pState->m_appLangDLL = NULL; } pState->m_appLangDLL = m_hMFCResourceDLL; // Exampleの言語別リソースDLLを設定する AfxSetResourceHandle (m_hResourceDLL); return TRUE; } // リソースDLLの開放 void CExampleApp::FreeResourceDLL () { if (m_hResourceDLL) { ::FreeLibrary (m_hResourceDLL); } m_hResource = NULL; }
実行結果
Language=English
Language=Japanese
Language=Chinese
リプライが必要な時点ですでにTCPを選択するべきです。自作のリプライ機構を実装してもTCPのパフォーマンスには適いません。
(C)2000-2024 くず All rights reserved.