language : Japanese | English

ぼっち大好き

トップ 貯蓄率とFIREまでの年数 二次関数と虚数i コピックメイキング 絶対音感 昔のソフトウェア Visual Studio C / C++ / MFC 備忘録 自作LLM 備忘録 日記 作者


Visual Studio C / C++ / MFC 備忘録

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内のブレークポイントでとまらなくなりました。

参照している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/Win32APIで書くか、C++/MFCで書くか。

ドッキング可能なツールバー・ダイアログバーや、ドキュメントビューアーキテクチャー、複数ページの印刷プレビューなどを使用する場合、C++/MFCを用いるのが簡便です(これらのコードを自力で書くのには途方もない時間を要します)。その他の場合、無料のVisual Studio Express Editionがあれば開発できることを考え、C/Win32APIで書くと良いようです。しかし2015年、MicrosoftがVisual Studio Community Editionをリリースし、個人開発やオープンソース開発等に限り、MFCを無料で提供することとなりました。最近のソフトはサブスクリプション版しかなく、お値段も高いので(円安でますます高くなっている)、個人開発やオープンソース開発は無料で使わせてくれるMicrosoftには感謝です。Windowsをサブスクにしたらダメですが。

最小の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);
}

実行結果

ANSI版とUnicode版の両方に対応したアプリケーションの書き方

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 ファイルから文字列を読み取り。

ANSI版とUnicode版の両方に対応したDLLの書き方

下記のようなマクロを記述しておくと、このライブラリを用いるプロジェクトで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;
}

MFCでローカルiniファイルを使用

レジストリを使うかローカル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;
}

GetPrivateProfileString / WritePrivateProfileStringの問題点

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をユーザーに解除させること、あるいはプログラム的に無効化することは可能ですが、セキュリティ上問題があります。

strcpy_s / wcscpy_sの問題点

使用領域をゼロで初期化して使っても、文字列の最後のヌル文字以降のゼロ初期化が不定値になるようです。したがって、これの続きにstrcatなどを使うと、たちまちバグとなります。既に自分でメモリを管理しているならば、strcpy / wcscpyのままにすることをお勧めします。

mbstowcs_s / wcstombs_sの問題点

上記の問題に加え、途中で変換不能文字があった場合の動作が未定義です。また、改行コード\r,\nの変換が正しくないようです。既に正常に動作しているのであれば、mbstowcs / wcstombsのままにすることをお勧めします。

fgets / fputs使用時の注意点

初歩的な問題ですが、rtモードでfopenしたテキストファイルからfgetsすると、その文字列には最後に"\r\n"や"\r"や"\n"がくっついてきます。そのため、必要文字列をファイルから正しく獲得するには、最後に引っ付いているこれらを'\0'で置き換えるのを忘れないように注意します。一方、fputsでは"\r\n"や"\r"や"\n"はつかないので自前でつける必要があります。

Unicode版プログラムでのUTF-16LE又はUTF-8のテキストファイルの読み書き

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に対応させる方法

CStdioFileはUnicodeに対応していないのでfopenとfcloseだけ手動でする必要があります。

FILE* pFile = _tfopen (strFileName, _T("rt, ccs=UTF-8"));
if (pFile == NULL) {
 /* エラー時の処理 */
}
CStdioFile theStdioFile (pFile);

...

fclose (pFile);

C/Win32アプリケーションの多言語化 - サテライトDLLとI18N(Internationalization)

サテライト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=English

Language=Japanese

Language=Japanese

Language=Chinese

Language=Chinese

C++/MFCアプリケーションの多言語化 - MFCのサテライトDLLとI18N(Internationalization)

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=English

Language=Japanese

Language=Japanese

Language=Chinese

Language=Chinese

番外編:ネットワークアルゴリズムの選定

リプライが必要な時点ですでにTCPを選択するべきです。自作のリプライ機構を実装してもTCPのパフォーマンスには適いません。

番外編:64bitWindowsにおけるDLLの置き場


(C)2000-2024 くず All rights reserved.