Visual C++の誰も教えてくれない常識(無料!?)
目次
Visual C++の使い方のポイント
こんにちは、こたかです。
Visual C++でWindowsアプリを作る方法について。
Visual C++ の参考書などを読むと、使い方については書いてありますが、
Visual C++ にはポイントがあります。
main関数を作ってprintfを呼べるようになったら、この記事のポイントを確認してみてください。
コンソールアプリとWindowアプリがある
Visual C++ の開始関数は、main関数とWinMain関数があります。
コンソールアプリではmain関数、WindowアプリではWinMainとなります。
WinMainとmainでは引数も異なります。main関数は説明もいらないと思います。argcとargvですね。
WinMainにはHINSTANCEがあります。この引数の値は、アプリケーションが終了するまで他の変数などに保存しますが、
実は、GetModuleHandleでも取得できたりします。
コンソールアプリでもWindowアプリでも、同じことができますが、
コンソールアプリには、黒い画面へ入出力するためのハンドルがあります。
Windowアプリにはこのハンドルがありません。
VCランタイムには4種類ある
VCランタイムには、
マルチスレッドスタティック
マルチスレッドDLL
マルチスレッドスタティックデバッグ
マルチスレッドDLLデバッグ
があります。
こたかは、「マルチスレッドスタティック」しか使いません。
「デバッグ」はランタイム内のデバッグもできるのですが、VCライブラリのデバッグはやりません。
起動時のリンク切れを起こすのが「DLL」版ランタイムです。
ユーザー環境において、VCランタイム無しで起動されることばかりなので、使うのをやめました。
MFCを使わない
こたかはMFCを使いません。代わりにSTLを使います。std::wstringなどです。
MFCは一見便利ですが、MFCで開発してしまうとMFCを知っている開発者でないと開発ができなくなります。
ライブラリーにバグがあると、対処できなくなります。
実行モジュールのサイズが大きくなります。
EXEとDLLでオブジェクトを渡せない
EXEとDLLとで、VCランタイムバージョンが異なると、std::wstringのオブジェクトを渡せなくなります。
これは、VCランタイムのバージョンによって、std::wstringの構造が異なり、名前こそ同じでも、別なオブジェクトなのでキャストできないためです。
例えば構造体、structのメンバーで、std::wstringのメンバー、ポインターがあると、
その構造体はビルドしたときのランタイムのバージョンに縛られます。
ランタイムを更新したい場合は、再度ビルドする必要があります。
どうしても文字列を渡したい場合はLPSTRのアドレスで渡すことはできます。
DLL側でmallocしたメモリを渡すのもダメです。exe側のfreeでバッファを削除できません。
多くは動作しますが、一度エラーが出始めると、バージョンが違うことによるエラーなので、
新しいコンパイラーとライブラリでDLLをリビルドする必要が出てきます。
DLL側で作ったオブジェクトをDLL側で削除することはできます。
例えば、DLL内でmallocしたメモリをハンドル(void*)としてEXEに渡し、使い終わったらDLL側にハンドルを渡してfreeすることはできます。
この場合、ランタイムの影響が無く、どのWindowsでも動作することができます。
DLLで作ったオブジェクトはDLL側で削除する。同じくEXEで作ったオブジェクトもEXE側で削除する。
文字列はLPSTRとLPWSTRは渡すことができるが、確保したメモリのサイズは変更できない。
int,float,doubleは問題なく渡すことができる。
struct自体も問題なく渡すことができる。メンバー変数については、std::stringなどは含めると渡すことができない。
ランタイム齟齬のエラーは、VisualStudio2003、2005、2012などの変更をしたときに発生することがあります。
この現象は、EXEとDLLを毎回フルビルドすることが前提の製品では無視することができます。
この場合、DLLとEXE間でオブジェクトの受け渡しを行っても問題になりません。
開発プロジェクトの体制の都合で、単に並行開発するチームごとにモジュールを分けた場合は問題になりにくいです。
もう一つ、DLLの動作のポイントですが、
DLLはダイナミック・リンク・ライブラリなのです。動的にロードとアンロードできる実装が正しい状態です。
多くは、EXEの終了までアンロードされないので、実装に問題があっても動作します。
プラグインなど、EXEが終了しないで、DLLが何度もロード、アンロードする設計では、DLLの実装の正しさが重要になってきます。
DLLで確保したリソースはDLLのアンロードで破棄されます。この動作のため、DLLリソースの参照が残っていて、アンロードされると、参照先が破棄されてしまい、無効なアドレスを参照した状態になります。この状態で参照先にアクセスすると、アクセスバイオレーションエラーになります。
「DLLをアンロードするとエラーが出るんだけどな?」という事態になるわけです。
DLL関数の呼び出し規約
「呼び出し規約」は、VC++でアプリを作る場合に、最初に知るべき超重要常識なのに、なぜか省略されます。
EXE起動したら、最初にKernel32.dllのLoadLibrary呼んでるでしょ!これstdcallですから。
WinMainだって「WINAPI WinMain」でしょ?WINAPIって、これもstdcallですから。
#define WINAPI __stdcall
学校の先生でさえ教えてくれないので、困ったものです。
試験にも出ないので、現場ではじめて壮大なミスになります。
Windows APIはすべて stdcallです。cdeclではありません。
なのでDLL関数をEXEへ公開するときにすべてstdcallにするべきです。
VBなどから呼び出す場合も、stdcallなのです。これだけです。
VC++の関数は何も指示をしないとcdeclになります。なのでエクスポートするDLL関数はすべてstdcall (WINAPIでも可、この場合もstdcallを宣言しています)を明示的に指定する必要があります。
おそらく、cdeclのままエクスポートすると、VC++からは呼べるのに、VBやExcelから呼べない状況になります。
引数が無い場合は呼べることがあります。引数を持つDLL関数をエクスポートして初めて、問題に気が付き、ソースを書き直すことになるのです。
このようなことは最初に知っておくべきです。
最強のWindowsAPI参考書はMSDNライブラリ(無料)
MSDNライブラリ「MSDN Library for Visual Studio 2008 SP1 (2008年12月更新版)」
https://www.microsoft.com/ja-jp/download/details.aspx?id=20955
このDVDをインストールするとヘルプが使えるようになりますが、「プラットフォームSDK」にあるWindows APIがほとんど利用できます。
ヘッダーファイル、Libraryファイル、DLL、がセットで記載されているので、
自力でヘッダーとLibraryを指定すれば、すぐに呼び出せてしまうのです。
もちろん、VisualStudioをインストールした直後に、既にLibraryとヘッダーはインストールされているわけです。
呼び出し方が書かれています。
膨大なWindowsのコアのAPIをほぼすべて網羅しています。
kernel32.lib / User32.lib / GDI32.lib / gdiplus.lib
GDIはGDIとGDIPlusがあるのでPNG画像やJPEG画像などの画処理機能はGDIPlus側が良いです。
キーワードで検索した後、ツールバーの「目次と同期」を押してみましょう。

目次が同期して、おなじライブラリの関数群を逆引きできます。
この機能がとても強力で、連携する関数を芋づる式に列挙できてしまいます。
この情報も、誰も教えてくれない、重要常識です。
ほとんどの参考書はMSDNライブラリの前には無力でしょう。
「そんなものMSDNライブラリ見れば誰でも分かるでしょ?」という内容で多くの参考書ができています。
初級の参考書になれてきたら、次はMSDNライブラリも見てください。余計な出費が抑えられるかもしれません。


まとめ
- WindowsアプリにはコンソールアプリとWindowアプリがある
- VCランタイムには4つの種類があり、「マルチスレッドスタティック」がおすすめ
- EXE/DLL間でのオブジェクトは渡さないことが基本。フルビルド前提の場合は、問題が起きにくい
- VCのデフォルトではcdeclになるのでDLLエクスポート関数では明示的に宣言が必要。開発前に先に決めておくこと。
- MSDNライブラリにはWindowsAPIの基本がすべて載っている。
ではまた、次の記事でお会いしましょう。