特集

Visual C++ 2005

いままたC++が熱い!「C++/CLI」として大進化したVisual C++ 2005

株式会社ピーデー 川俣 晶
2005/08/31
Page1 Page2 Page3

重要度が高まるC++

 いま一部でプログラミング言語「C++」の重要度が高まっている。ここで勘違いをされると困るので念のために強調しておくが、これは「C++の重要度は高まるだろう」という未来予測を書いているわけではない。すでに一部では重要度は高まっている、という現在の状況について書いているのである。

 恐らく、このように書けば、そんなバカなと思う人も多いと思う。なぜなら、C++といえばすでに過去の言語であり、しかもJavaの誕生とともに、生産性の悪い失敗作のレッテルを張られて葬り去られたといっても過言ではないからだ。そして2005年のいま、すでにJavaすらもほころびが見える古い言語となっている。Windows環境であれば、明らかにJavaよりも生産性に優れるC#もあれば、大きく進化したVisual Basicもある。このような状況で、Javaを振り返るならともかく、それよりもさらに古いC++を振り返ることにどのような意味があるというのだろうか?

 その答えは、極めて単純化して一言で要約してしまえば「過去の資産の継承」ということができる。過去の資産とは、ソース・コード、ライブラリ、プログラマが持つ知識、経験などをいう。このような資産は非常に大きな価値を持つため、たいていは無視することができない。そして、技術の世代交代において、資産を継承できることは大きな強みになる。

 例えば、パソコンの世界でも、1980年代には相互に互換性のないアーキテクチャが乱立したにもかかわらず、最終的にIBM-PC互換機に収束し、その後の新世代のパソコンも基本的にそれに対する互換性を無視できないのは、やはり資産の継承が大きな価値を持つためだといえるだろう。

 プログラム言語でいえば、なぜ多くのVisual Basicプログラマが、C#へ移行することなくVisual Basicプログラマであり続けようとするのか。それは単に体質が保守的と断じるべきではなく、「過去の資産の継承」という価値を正しく守り続けているから、と見ることも可能だろう。何も資産を持たない若者や、新興企業であれば、このような価値にはさしたる意味を見いださないかもしれない。しかし、それなりの蓄積を抱え込んだ技術者や企業にとって、これは無視できない価値といえる。

■開発言語の系譜

 さて、「過去の資産の継承」について考えてみるために、ここでパソコンの主力開発言語の変遷を振り返ってみよう。非常に大ざっぱに要約してしまうと、これには主に2つの流れがあるといえる。1つは、お手軽さ重視のBASIC系の流れであり、もう1つは本格派のアセンブラ系の流れである。

 BASIC系の流れはVisual Basicを経てそのまま.NET Framework上のVisual Basic系言語へとつながる。アセンブラ系の流れは、より生産性を高めるために、高級なアセンブラと呼ばれるCを経て、さらにCの特徴を継承するC++へと進む。1990年代のWindowsプログラミングの状況を振り返ると、やはりお手軽系Visual Basicと、本格派系C++が、2大プログラム言語として君臨していたという印象が残る。

 お手軽系Visual Basicは、紆余(うよ)曲折はあれど、順当に進化を続けていった。これは、特に問題はないだろう。しかし、本格派系C++は大きな壁に突き当たってしまう。それは、C++にはいくつかの問題があったためだ。強力な標準ライブラリの欠如、マイクロソフトが提供するクラス・ライブラリの混乱(MFCとATLの並立など)、オブジェクト指向プログラミングというパラダイムの混迷、メモリリークが発生しやすい言語アーキテクチャ、などである。

 この壁を突き崩すようにタイムリーに出現したのがJavaである。Javaは確かにC++の抱えていた壁を突き崩してくれた。それ故に、これがブームになったことも当然のことだといえるだろう。しかし、Javaの輝かしい成果には、1つだけ重大な忘れ物があった。つまりそれが「過去の資産の継承」である。

 CからC++に移行したとき、C時代の資産を活用することはできた。例えば、Cで書かれたライブラリをそのまま使うことができた。しかし、C++からJavaに移行する際に、C++で書かれたライブラリはそのまま使うことができない。この点で、C#もJavaと同様である(しかも、C#はDelphiの進化形と位置付けられるというもっぱらの評判であったにもかかわらず、Delphiの資産もまったく継承できない)。

■C++の進化

 さて、ここで昔話を横に置いて、いま現在のことを考えてみよう。1990年代の主力開発言語の1つであったC++によって書かれたソース・コードやライブラリは決して少なくないだろう。そして、それらを活用するプログラム開発のニーズも少なくないはずだ。

 では、そのような開発を目の前にしたとき、どのように対処すればよいのだろうか。C++のコードをJavaやC#ですべて書き換えるべきだろうか? 予算や時間が潤沢にあればそれもよいだろう。しかし、それらは常にあるとは限らない。むしろ、たいていの場合は「ない」と思った方がよいだろう。では、昔ながらのC++開発に舞い戻るべきだろうか。それはうれしい話ではない。それはいろいろな問題を抱え込んでいることが明らかだからだ。

 しかし、C++も立ち止まっていたわけではない。C++も進化を続けている。「C++マネージ拡張」(Managed Extensions for C++)を経て「C++/CLI」へと続く新しいC++は、「過去の資産の継承」と、かつてC++が抱えていた問題の解消を同時に成し遂げている。

 実は、問題さえ解決できれば、C++で悪いことは何もないのである。そして、JavaやC#にはできない「過去の資産の継承」が可能であれば、まさに最強。これによって、本格派C++系の流れをがっちりと受け止める決定打となるプログラミング言語に、C++自身が再浮上してきたのではないかと感じるわけである。

 このように感じるのは、頭で考えたアイデアに対してではない。実際に自分で手を動かす作業を通じて感じるのである。筆者が現在開発しているソフトウェアは、1990年代前半にC++で記述されたプログラムを現在的にアレンジして復活させるものなのだが、まさにC++こそがそのための最善の選択となっている。もし、C++を使わないとすれば、予算と時間の都合でボツになっていたはずの企画である。

 さて、この記事では、まずVisual Studio .NET 2003のC++マネージ拡張を紹介し、それに足りない部分があることを示し、それがC++/CLIという新しい言語と、それを実装した統合開発環境であるVisual C++ 2005(Visual Studio 2005の一部となる)によって満たされているという順に話を進めていくことにしよう。。本稿の趣旨はC++マネージ拡張の進化形「C++/CLI」の解説だが、その比較対象、前提知識として、まずはC++マネージ拡張を紹介する。

 なお、この記事は、C++マネージ拡張やC++/CLIを知らない方々に、そのさわりを紹介する内容であり、これらをすでにご存じの方々には退屈となる可能性が高いことをお断りしておく。

本当はすごいC++マネージ拡張

 現行の開発環境であるVisual Studio .NET 2003で使用できるC++マネージ拡張は、それだけで実はすごい力を発揮してくれる。もし、C++マネージ拡張のことを、C++の文法で書くC#という程度の認識の方がいれば、ぜひともそれを改めていただきたい。

■ネイティブ・コードのprintf関数を呼び出す

 まず、小さなプログラムを使って、どのあたりがすごいのかを見ていこう。

 C++マネージ拡張は、ほとんどの点で、従来のC++の上位互換となっている。例えば、以下のようなよくあるCの“Hello World”プログラムを記述してもそのまま実行できる。

#include "stdafx.h"
#include <stdio.h>

int _tmain()
{
  printf("Hello World!\n");
  return 0;
}
よくあるCの“Hello World”プログラム
Visual Studio .NET 2003にて、[Visual C++]の[コンソールアプリケーション(.NET)]プロジェクトを作成して入力する。なおソース・コード中の「_tmain」は、main関数とwmain関数(=ワイド文字バージョンのmain関数)を、コンパイル設定(プリプロセッサの定義)に合わせて適切に選択するためのマクロである。

 これを実行すると以下のようになる。

 これのいったい何がすごいのか分からないという読者もいると思う。また、.NET Frameworkの知識のある読者の中には、何が起きているのかよく理解できないという方もいるかもしれない。

 実はこのソース・コードは、マネージ・コードで書かれたmain関数(ソース・コードでは「_tmain」)から、ネイティブ・コード(アンマネージ・コード)で書かれたprintf関数(旧来のC言語標準ライブラリのAPI)を呼び出しているのである。つまり、新規に書いた部分は新しいテクノロジで、呼び出す先は古いテクノロジで実現されているということである。まさに「過去の資産の継承」を印象付ける事例なのである。

■“Hello World”プログラムの中身

 具体的な内容は.NET用の逆アセンブラ「ildasm.exe」を使うことで確認することができる(以下の説明が難しすぎると思った場合はこの部分を読み飛ばしてもらっても構わない)。

 まず、main関数は以下のように逆アセンブルされ、確かにMSILで記述されていることが分かる。つまり、マネージ・コードである。

.method public static int32 modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl)
    main() cil managed
{
  .vtentry 1 : 1
  // コード サイズ     15 (0xf)
  .maxstack  1
  IL_0000:  ldsflda  valuetype $ArrayType$0x5c1e545c modopt([Microsoft.VisualC]Microsoft.VisualC.IsConstModifier) '?A0x83128050.unnamed-global-0'
  IL_0005:  call     vararg int32 modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl) printf(int8 modopt([Microsoft.VisualC]Microsoft.VisualC.NoSignSpecifiedModifier) modopt([Microsoft.VisualC]Microsoft.VisualC.IsConstModifier)*)
  IL_000a:  pop
  IL_000b:  ldc.i4.0
  IL_000c:  br.s     IL_000e
  IL_000e:  ret
} // end of method 'Global Functions'::main
ildasm.exeによるmain関数の逆アセンブル出力

 ではprintf関数はどうなっているのかというと、以下のような結果が得られる。コメントに示されているとおり、これは埋め込まれたネイティブなコードである。

.method public static pinvokeimpl(/* No map */)
    vararg int32 modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl)
    printf(int8 modopt([Microsoft.VisualC]Microsoft.VisualC.NoSignSpecifiedModifier) modopt([Microsoft.VisualC]Microsoft.VisualC.IsConstModifier)* A_0) native unmanaged preservesig
{
  .custom instance void [mscorlib]System.Security.SuppressUnmanagedCodeSecurityAttribute::.ctor() = ( 01 00 00 00 )
  // Embedded native code
  //  ネイティブ メソッドの逆アセンブリはサポートされていません。
  //  Managed TargetRVA = 0x19740
} // end of method 'Global Functions'::printf
ildasm.exeによるprintf関数の逆アセンブル出力

 このようにC++マネージ拡張では、マネージ・コードとネイティブ・コードを1つのアセンブリに含ませることも容易であるし、C時代から継承されてきたライブラリをマネージ・コードから呼び出すことも容易なのである。

■C標準ライブラリとWin32 APIと.NET Frameworkクラス・ライブラリを呼び出す

 それだけではない。実はWin32のネイティブDLLの呼び出し、例えばWin32 APIの呼び出しも従来のC++と同様に実現できる。「従来のC++と同様」ということは、ヘッダ・ファイルである「windows.h」をインクルードし、インポート・ライブラリをリンクするだけでWin32 APIが呼び出せることを意味する。

 C#やVisual Basicでは、DllImport属性やDeclare文を必要としたが、そのような特殊な構文はC++マネージ拡張では必要とされない。何も書かなくとも、自動的に「P/Invoke」(マネージ・コードからアンマネージ・コードを呼び出すためのインターフェイス)が挿入されるのである。

 もちろん、.NET Frameworkのクラス・ライブラリも呼び出すことができる。以下に、1つの関数内から、C標準ライブラリ、Win32 API、.NET Frameworkクラス・ライブラリを呼び出す例を紹介しよう。

#include "stdafx.h"
#include <windows.h>
#include <stdio.h>

#using <mscorlib.dll>

using namespace System;

int _tmain()
{
  DWORD dummy;

  // C標準ライブラリ
  printf("Hello World!\n");

  // Win32 API
  WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),
    "Hello World!\n",13,&dummy,NULL);

  // .NET Frameworkクラス・ライブラリ
  Console::WriteLine(S"Hello World!");

  return 0;
}
3種類の呼び出し手順を混在させて利用するC++マネージ拡張のコード
Visual Studio.NET 2003にて、[Visual C++]の[コンソールアプリケーション(.NET)]プロジェクトを作成して入力する。C++マネージ拡張では、1つの関数内から、C標準ライブラリ、Win32 API、.NET Frameworkクラス・ライブラリを呼び出すことができる。

 これを実行すると以下のようになる。

 3種類の呼び出しの結果は同じに見えるが、技術的にはまったく異なっている。この3つが平然と共存しているソース・コードは、ある意味で驚きの対象といえるだろう。これらが、いかに異なるテクノロジによって実現されているか、その詳細を知れば知るほど、驚きは大きいものになるのではないだろうか。技術的な面白さという意味では、C++マネージ拡張はC#やVisual Basicをはるかにしのいでいるかもしれない。

 

 INDEX
  [特集] Visual C++ 2005
  いままたC++が熱い!「C++/CLI」として大進化したVisual C++ 2005
  1. 本当はすごいC++マネージ拡張
    2. C++マネージ拡張の物足りない部分
    3. Visual C++ 2005とC++/CLIという解決
 


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間