[ C++で開発 ]

Visual C++雑多メモ

標準C関係

標準Cライブラリ

math.hのマクロ定義値がない

M_PIなどの#defineがデフォルトでは利用できない。<math.h>をインクルードする前に、_USE_MATH_DEFINESを定義しておく。

#define _USE_MATH_DEFINES
#include <math.h>

strcpy、sprinitfなどでコンパイル時に警告発生(unsafe)

VC++ 2005以上

現象

標準Cライブラリのstrcpy、sprintfなどを使用していると、コンパイル時に警告が発生します。バッファー・オーバーランの原因になるので、安全な別関数(strcpy_s、sprintf_sなど)の使用を推奨してきます。

MSDNの解説ページ[CRTのセキュリティ強化]

しかし・・・

対策

警告なので無視するのも一手ですが、警告が残ったままでは気持ちが悪いと感じるお行儀よいプログラミングをする人には「大迷惑」なので、コンパイル時にデファイン_CRT_SECURE_NO_WARNINGSを指定します。

または、素直にセキュリティを高める関数を使用するかです。

mkdir, closeなどPOSIX関数でコンパイル時に警告発生(unsafe)

VC++ 2005以上

現象

POSIXライブラリのopen, mkdirなどを使用していると、コンパイル時に警告が発生します。別関数(_open, _mkdirなど)の使用を推奨してきます。

要調査: C++標準におけるPOSIXライブラリの定義

MSDNの解説ページ[使用を推奨されていないCRT関数]

対策

コンパイル時にデファイン _CRT_NONSTDC_NO_DEPRECATEを定義する

標準C++関係

C++11規格

Visual C++ 10より、C++11規格の対応が進んでいます。

Windows SDK (Platform SDK)

Windows APIなどを使用するには、Windows SDKが必要です。前はPlatform SDKと呼ばれていました。Windows SDKには、Windows APIのヘッダーファイル、ライブラリファイル、その他サンプルコード、ヘルプドキュメント、ツール、コンパイラなどが含まれています。

Windows APIは、OSのバージョンアップに伴い変更されているので、開発対象OSに適合するWindows SDKを使用します。

Visual Studioにも製品化時点の最新のWindows SDKが内蔵されていますが、一部削除されているほか、更新はされないため、Windows SDKを入手して使用するのがよいようです。

Visual Studioのバージョンと内蔵するWindows SDKバージョン

 Visual Studio  Windows SDK  
 2008  6.0A  
 2010  7.0A  
 2012    

国際化(Unicode)対応

標準規格への取り組みに疑問を感じるVisual C++ですが、国際化対応はなぜか突っ走っている感があります。

Win32コンソールアプリケーションの雛形

Visual Studio 2010のVC++(10.0)でプロジェクトウィザード(種類:Win32コンソールアプリケーション)で作成されるmain関数は次のようになっています。

// Win32ConsoleApp.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"


int _tmain(int argc, _TCHAR* argv[])
{
        return 0;
}

ここで登場する_TCHAR_tmain については、コンパイル時にデファイン_UNICODEが定義されているか否かで、wchar_t、wmainになるか、char、mainになるかが決まります。wchar_tはC++標準でワイド文字を表す型として予約語となっています。これは、デファインでワイド文字版、非ワイド文字版(マルチバイト文字版)を切り替える汎用コーディングを実現するための仕組みです。tchar.hで定義されています(stdafx.hの中でtchar.hがインクルードされている)。

コンパイルオプションは次のように設定されています。_UNICODEがデファインされています。

/ZI /nologo /W3 /WX- /Od /Oy- /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /Gm
/EHsc /RTC1 /GS /fp:precise /Zc:wchar_t /Zc:forScope /Yu"StdAfx.h" /Fp"Debug\Win32ConsoleApp.pch" 
/Fa"Debug\" /Fo"Debug\" /Fd"Debug\vc100.pdb" /Gd /analyze- /errorReport:queue 

このように、Visual Studio (2005以降?)ではデフォルトの設定でワイド文字を標準で使う構成となっています。

なお、アンダースコアのないUNICODEもデファインされています。これは、Win32 APIで使用するものです。_UNICODEは、Cランタイムライブラリおよびtchar.hで使用しています。

ワイド文字を使いたくない場合は、プロジェクト・プロパティの構成プロパティ>全般で、文字セット項を「Unicode文字セットを使用する」から「マルチバイト文字セットを使用する」に変更します。

どのスタイルを使用すべきか?

作成するプログラムが扱う文字がISO/IEC 646またはJIS X 0201(ASCII文字)であれば、「マルチバイト文字セットを使用する」に設定して、文字はすべてchar型で扱えばよいでしょう。

日本語を扱う場合は、扱う文字範囲を確認します。JIS X 0208(JIS第一水準、第二水準ほか約7000字)を範囲とするなら、「マルチバイト文字セットを使用する」でchar型でWindows-31J(Shift_JIS+拡張)エンコードで2バイト文字を2つのcharデータで表現する従来のプログラミングスタイルを踏襲することができます。

JIS X 0213(JIS第一水準、第二水準、第三水準、第四水準)を範囲とするか、ISO/IEC 10646(JIS X 0221、いわゆるUnicode)を範囲とするなら、マルチバイト文字セットとWindows-31Jでは対応できないので、「Unicode文字セットを使用する」で文字をwchar_tで扱います。

Windows Vista以降は、OSとして文字セットにJIS X 0213を採用しているので、「Unicode文字セットを使用する」にしないと、画面などから入力した文字を扱えないという問題が発生します。

tcharプログラミング

Visual C++の提供するTCHARの仕組みを使ったプログラミングについて別ページに記載しています。

エディタ

外部エディタとの連携(Emacs編)

VC++ 2005からはEmacsキーバインドが選択できるようになったのですが、それでもEmacsで編集したいときの連携を設定する方法です。

VC++ 2010では、Emacsキーバインドが標準で搭載されなくなってしまいました。別途拡張機能でダウンロード可能(ただしExpress版を除く)

参考のWebページ「Memoranda on Visual Studio 2005 Express Editions for Emacs Users」に詳しく設定方法が書いてありますので、ここはメモだけ。

[ツール]メニュー→[外部ツール...]で「外部ツール」ダイアログが表われるので、[追加]ボタンを押し、以下の設定を記述したのち[適用]ボタンを押します。

タイトル
Meadow
コマンド
C:\Meadow\Meadow2.bat
引数
+$(CurLine) $(ItemPath)
初期ディレクトリ
$(ItemDir)

設定後、[ツール]メニューにタイトルで設定した名称(Meadow)が表われるようになります。

Meadowで保存時に毎回ダイアログが出ないようにするには、[ツール]メニュー→[オプション]で表われるダイアログの左側ペインで[環境]→[ドキュメント]を選択し、右側ペインで[環境内で変更されていない場合に変更を自動的に読み込む(A)」にチェックを入れます。

注)コマンドではMeadow起動用に個人作成のバッチを指定していますが、直接Meadowの実行ファイルでもかまいません

参考

namespaceのインデント制御付加

大規模プログラムでは、階層化した名前空間(namespace)が不可欠ですが、VC++のエディタはnamespaceのインデントを抑止することができず、いたずらにインデントが深くなってしまいます。

namespace service {
    namespace weather {
        namespace sensor {
            class SensorMonitor : public Monitor {
            public:
                SensorMonitor();
                :
            };
        }
    }
}

インデントの設定ではこれを抑止することができず、これは結構困った問題です。

参考になる回避策について、以下URLに議論がありました。参考までに簡単に紹介します。

苦しい回避方法

見苦しさを伴う回避方法があるにはあります。

インデントレベルを1つだけに抑える
namespace service { namespace weather { namespace sensor {
    class SensorMonitor : public Monitor {
        :
    };
}}}

とっても許容したくはありませんが、回避策にはなります。それでもインデントが発生してしまいますが・・・。

空行を1つ入れる
namespace service
{;
namespace weather
{;
namespace sensor
{;
class SensorMonitor : public Monitor {
    :
};
}
}
}

トリッキーですが、インデントを0にすることができる回避策です。ソースコードにインデント制御のための無意味な記述が生じるのでとても許容したくはありませんが・・・

namespace宣言をマクロに置き換える

詳細は参考URLを参照、これも許容したくはありません。

ユーザーマクロでインデントを修正

詳細は参考URLを参照、これならソースコードを汚染せずににインデントを制御できそうです。マクロについては、Express版では利用できないようです。

DLLの作成

DLL公開ヘッダーに定義するシンボル公開用修飾子

Visual C++では、DLLを定義する際に、DLLが公開する関数・クラスを定義したヘッダーファイルに特別な修飾を付ける必要があります。

DLL自体をビルドするときは、ヘッダーファイルで公開する関数・クラスを宣言・定義する際に__declspec(dllexport)を付けます。

DLLビルド用ヘッダー定義

void __declspec(dllexport) myFunc();

class __declspec(dllexport) MyClass {};

DLLを使用する側をビルドするときは、DLLが提供するヘッダーファイルで公開する関数・クラスを宣言する際に、_declspec(dllimport)を付けます。

DLL提供用ヘッダー定義

void __declspec(dllimport) myFunc();

class __declspec(dllimport) MyClass {};

ヘッダーファイルを2重(dllimport用とdllexport用)に作成することは作業効率・保守効率上好ましくないので、1つのヘッダーファイルで条件コンパイルによって切り替えます。

#ifdef _USRDLL
#  ifdef MYDLL_EXPORT
#    define EXPORT __declspec(dllexport)
#  else
#    define EXPORT __declspec(dllimport)
#  endif // MYDLL_EXPORT
#endif // _USRDLL

void EXPORT myFunc();

class EXPORT MyClass {};

VC++ではDLLプロジェクトを作成すると、_USRDLLというプリプロセッサシンボルを定義するのでこれを利用してDLLを使用するときはこれを定義します(呼び出し側も定義する必要があります)。こうすると、同じヘッダーファイルをDLLではなく利用することができます。

次にDLL自体をビルドするプロジェクトではMYDLL_EXPORTを定義します。

VC++ 9のプロジェクトウィザードでシンボルのエクスポートを選ぶと生成される制御

VC++プロジェクトファイル

VC++のプロジェクトファイルのフォーマットに関するメモです。数が増えてくるとGUI上で1つ1つちまちまと同じ修正をしてまわるのは苦痛なので、ツールで一括変換する際にフォーマットを知っておくと便利です。

vcprojファイルのフォーマット

XMLフォーマットです。

インクルードパス、プリプロセッサ定義、ライブラリパス、ライブラリを修正したいというのが通常なので、それぞれのXML上の定義を見てみます。

<?xml version="1.0" encoding="shift_jis"?>
<VisualStudioProject ProjectType="Visual C++" Version="7.10" ...>
    <Configurations>
        <Configuration Name="Debug|Win32" ...>
            <Tool Name="VCCLCompilerTool"
                  AdditionalIncludeDirectories="..\include;&quot;$(ACE_ROOT)&quot;"
                  PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE"
                  ...
            />
            <Tool Name="VCLinkerTool"
                  AdditionalDependencies="TAO_CosNamingd.lib TAOd.lib ACEd.lib"
                  AdditionalLibraryDirectories="&quot;$(ACE_ROOT)\lib&quot;"
                  ...
            />
            ...
        </Configuration>
    </Configurations>
</VisualStudioProject>

基本はGUIで入力したとおりですが、環境変数を使用している場合、””で囲まれ、さらに”がXMLの&quot;で表現されます。

Cランタイム

CランタイムライブラリおよびC++ライブラリや、MFC、ATL、OpenMPなどがあります。

ランタイムには、スタティックリンク・ライブラリとダイナミックリンク・ライブラリがあります。ダイナミックリンク・ライブラリの場合、プログラム実行時にランタイムのDLLファイルが必要になります。

ランタイムをダイナミックリンクするのはDLLのインストールが必要になり、手間がかかるのでスタティックリンクがいいように思えます。しかし、スタティックリンクの場合には特有の問題があります。

指針

単一のEXEファイルだけで動作するプログラムを作成する場合、ランタイムをスタティックリンクすることでランタイムの再頒布は不要となる。

EXEファイルの他にDLLファイルを作成する場合、ランタイムはダイナミックリンクする。ただしランタイムの再頒布が必要となる。

ランタイムをスタティックリンクする場合の制約・注意点

プログラムが、実行ファイル(EXEファイル)単独で成り立つ場合、ランタイムをスタティックリンクしても問題ありません。しかし、プログラムが複数の実行ファイル(EXEファイルおよびDLLファイル)から成り立つ場合、ランタイムをスタティックリンクすると問題が生じます。

+-------------+
| EXEファイル |
|             |
+-------------+
|ランタイム   |
+-------------+
+-------------+  +-------------+        +-------------+
| EXEファイル |  | DLLファイル |        | DLLファイル |
+-------------+  +-------------+ ・・・ +-------------+
|ランタイム   |  | ランタイム  |        | ランタイム  |
+-------------+  +-------------+        +-------------+
注)Visual C++ではスタティックリンクしたランタイムがこのように複数ロードされることを想定していない

ランタイムがプロセス(メモリ)中に2つあるということは、例えばstaticなデータが1つではなく複数あることになります。通常staticな領域はプロセス中に1つであることを想定してプログラミングするので、この状況では予期せぬ誤動作をすることになります。

ランタイムで扱うデータとして、ファイルハンドル、環境変数、ロケール、ヒープメモリ管理などがあります。これらCランタイムオブジェクトを、DLL境界をまたいで授受すると予期せぬ振る舞いが生じることがあります。

DLLファイル(1)でnew/malloc等でメモリを動的に割り当て、そのポインタをDLLファイル(2)で破棄すると、同様に予期せぬ振る舞いが発生し、この場合はメモリアクセス違反やヒープの破損などプログラム異常につながることがあります。

+-------------+  +-------------+        +-------------+
| EXEファイル |  | DLLファイル |        | DLLファイル |
+------+------+  +-------+-----+ ・・・ +------+------+
       |                 |                     |
       +-------------+   |     +---------------+
                     |   |     |
                   +-------------+
                   | ランタイム  |
                   +-------------+

ランタイムのDLLファイルのインストール

Visual C++ がインストールされていないマシン上でランタイムをダイナミックリンクする設定でビルドしたプログラムを実行するには、Visual C++のランタイムDLLファイルをインストールしておく必要があります。

インストールにはいくつか方法があります。

Visual C++再頒布可能パッケージ

Microsoftから、Visual C++の各バージョン(サービスパック)毎に再頒布可能パッケージが提供されています。

Visual C++のバージョン(サービスパックがある場合、サービスパックレベルも)が、プログラムをビルドしたVisual C++と一致している必要があります。

Windowsアップデートによる更新で、再頒布可能パッケージがさらに更新されることがあり、同じバージョンの再頒布可能パッケージを入れても動かないということがあったようです。以下が一例です。

Microsoft Visual C++ 2005 Service Pack 1 再頒布可能パッケージ ATL のセキュリティ更新プログラム
デバッグ版DLLは、再頒布可能パッケージには含まれない

これらは、Releaseビルド用のDLLのみで、Debugビルド用のDLLは含まれていません。Debugビルド用のDLLは、再頒布が可能ではありません。

Visual C++に含まれる再頒布用ランタイムDLLファイル

Visual C++ 10(Visual Studio 2010)の場合、次の場所に再頒布用DLLファイルが置かれています。

%VCINSTALLDIR%\redist
    +--- Debug_NonRedist
    +--- ia64
    +--- x64
    +--- x86

ここのx86の下には次のディレクトリ・ファイルが存在します。

Microsoft.VC100.ATL
   +--- atl100.dll
Microsoft.VC100.CRT
   +--- msvcp100.dll
   +--- msvcr100.dll
Microsoft.VC100.MFC
   +--- mfc100.dll
   +--- mfc100u.dll
   +--- mfcm10.dll
   +--- mfcm100u.dll
Microsoft.VC100.MFCLOC
   +--- mfc100jpn.dll ほか
Microsoft.VC100.OPENMP
   +--- vcomp100.dll

デバッグビルドしたDLLファイルは、Debug_NonRedistディレクトリにありますが、ライセンス上再頒布はできず、デバッグとテスト目的で開発サイト内限定で他のコンピュータにコピーできるとあります。

情報源

Microsoft発信情報