特集
Visual C++ 2005

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

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

Visual C++ 2005とC++/CLIという解決

 新しいプログラム言語であるC++/CLIは、C++マネージ拡張の進化形と位置付けることができる。しかし、単なる進化形ではない。現在、ヨーロッパの工業標準化団体であるECMAによって標準言語として制定作業中である。もちろん、標準を名乗る以上、C++/CLIが対象とする実行環境はWindowsでも.NET Frameworkでもなく、国際規格である「ISO/IEC 23271:2005, Common Language Infrastructure(CLI)」である。

 C++/CLIが、Visual Basic .NETと同じように「C++/.NET」というネーミングではない理由はここにある。対象としている実行環境がCLIなので、C++/CLIという名前になるのである。つまり、CLIの実装あるところどこでもC++/CLIは利用可能となるわけで、Windowsという小さな枠で規定されるC++マネージ拡張とはスケールが違うのである。

 Windows以外のOSで、C++/CLIを使うことも、いずれは特別なことではなくなるだろう。それはさておき、.NET Frameworkは「CLI+独自拡張の実装」と考えることができるので、もちろんC++/CLIを.NET Framework上で使うことは問題ない。C ++/CLIは、Visual Studio 2005の一部となるVisual C++ 2005から利用可能になる(なおVisual C++ 2005においても、引き続きC++マネージ拡張の利用が可能である)。Visual Studio 2005はすでにベータ版を入手でき、それによってC++/CLIを体験することができる。

 さて、C++マネージ拡張とC++/CLIは別言語といってよいほどに言語仕様が変わっている。もちろん、どちらもC++との互換性を継承しているので、相違点はC++に対する拡張部分に多い。

 ソース・コードの雰囲気がどのように変わったかを見るために、先ほどのファイル・コピーのサンプル・プログラムを、C++/CLIで書き直したものを以下に紹介しよう。

#include "stdafx.h"

using namespace System;
using namespace System::IO;

int main(array<System::String ^> ^args)
{
  FileStream inputStream(args[0], FileMode::Open);
  FileStream outputStream(args[1], FileMode::Create);
  for(;;)
  {
    array<Byte> ^ ar = gcnew array<Byte>(1024);
    int size = inputStream.Read(ar, 0, ar->Length);
    if ( size == 0 ) break;
    outputStream.Write(ar, 0, size);
  }
  return 0;
}
ファイルのコピーを行うサンプル・プログラム(C++/CLI版)
Visual C++ 2005 Express Beta 2にて、[Visual C++]の[CLRコンソールアプリケーション]プロジェクトを作成して入力する。

 まず驚かされるのは、ソース・コードの短さである。C++マネージ拡張より短いどころか、C#よりも短い。この短さは、usingステートメント相当の機能が言語にビルトインされていることによって達成されている。

 ローカル変数として宣言されたマネージ・オブジェクト(上記の例ではinputStreamやoutputStreamなど)は、そのスコープから抜け出す時点で、Disposeメソッドが呼び出される(メモリの回収はガベージ・コレクションまで遅延される)。これにより、Disposeパターンを使用したリソース回収を記述する手間が大幅に少なくなり、C#よりも短く記述できているのである。ちなみに、Disposeメソッドはデストラクタの構文で記述するようになっていて、いちいち明示的にIDisposeインターフェイスを実装するという宣言も不要となっている。

 arrayというジェネリックを使用した配列の宣言にも注目してみよう。また、その後に続く「^」記号は、マネージ・オブジェクトの参照を示す記号である。アンマネージなポインタを示す「*」記号とは異なる記号が割り当てられており、これらは区別して扱われる。

array<Byte> ^ ar = gcnew array<Byte>(1024);

 それから、配列を確保するために、「__gc new」ではなく、「gcnew」という短いキーワードが使われていることに注目しよう。もはや、2つのアンダースコアを前置きする必要はないし、すっきりと1つのキーワードに収まってくれたのだ。

 ちなみに、C++/CLIでは以下のようにfor eachステートメントも利用できる。

#include "stdafx.h"

using namespace System;
using namespace System::Collections::Generic;

int main(array<System::String ^> ^args)
{
  List<String^> list;
  list.Add("abc");
  list.Add("def");
  list.Add("ghi");

  for each( String ^ s in list )
  {
    Console::WriteLine(s);
  }
  return 0;
}
C++/CLIではfor eachステートメントも利用可能となっている

 思わず、本当にこれがC++か? と思ってしまうようなソース・コードだが、これがC++/CLIのスタイルである。そして、これはC#やJavaとはまた異なる雰囲気を持っていて、ほかのどのプログラム言語とも違うC++/CLIならではの世界を作り出している。

既存ライブラリ活用の事例

 最後に、C++/CLIを用いて過去の資産を継承するサンプル・プログラムを紹介しよう。しかも、かなりこってりと脂っこい難物である。

 題材は、スクリーンセーバー・ライブラリである。スクリーンセーバー・ライブラリは、Windows 3.1の時代より提供されているスタティックリンク・ライブラリで、その名のとおり、スクリーンセーバーを作成するためのライブラリである。

 このライブラリが特にこってりと脂っこいと呼ぶに値する理由は、その構造の特異性にある。このライブラリは、何とWinMain関数(Windowsアプリケーションにおけるmain関数)を持っているのである。そして、ウィンドウクラスを登録し、ウィンドウを生成するという手順までが、すべてライブラリ自身の中に含まれている。

 ユーザー・プログラム側ではいくつかの関数を実装するのだが、それらはライブラリ内から呼び出される形となる。つまり、驚くなかれ、通常はユーザー・プログラムがライブラリを呼び出して使うにもかかわらず、このライブラリはその構造が逆転してしまうのである。実にトリッキーであり、安易な互換レイヤでは容易に破たんしてしまう難物である。ところが、Visual C++ 2005とC++/CLIはこの難物にもすんなりと対応するのである。

 ここでは、すでにスクリーンセーバー・ライブラリを使用したWin32のネイティブ・アプリケーションが存在するという前提で、それを.NET Frameworkのクラス・ライブラリで描画するように修正するというシナリオを想定してみよう。

■Win32ネイティブ版スクリーンセーバー

 まず、.NET Frameworkのことなど何も知らないピュアなWin32スクリーンセーバーを作成してみよう。以下のコードはあくまでサンプルであるため、実用スクリーンセーバーとしては十分ではないことはお断りしておく。

 まず、Visual Studio .NET 2003にて、[Visual C++]の[Win32 Windowsアプリケーション]プロジェクトを作成する。その際、内容は空にしておく。そして、以下のような内容を持つ.cppファイルをプロジェクトに追加する。その後、プロジェクトのプロパティにて、リンカの追加の依存ファイルとして「scrnsave.lib comctl32.lib」を書き加える。これをビルドして、コマンドラインに「/s」を付けて起動するとスクリーンセーバーとして動作する。

#include <windows.h>
#include <stdlib.h>
#include <scrnsave.h>

extern "C" {
  char szAppName[40];
}

extern HINSTANCE  hMainInstance;
extern HWND  hMainWindow;
char szName[TITLEBARNAMELEN];
char szIsPassword[22];
char szIniFile[MAXFILELEN];
char szScreenSaver[22];
char szPassword[16];
char szDifferentPW[BUFFLEN];
char szChangePW[30];
char szBadOldPW[BUFFLEN];
char szHelpFile[MAXFILELEN];
char szNoHelpMemory[BUFFLEN];

UINT_PTR wTimer;
WORD wElapse = 1000;
int xText=0, yText=0;
char text[] = "Hello!";

#define ID_TIMER 1

LONG CALLBACK ScreenSaverProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  switch (msg)
  {
    case WM_CREATE:
      {
        wTimer = SetTimer(hWnd, ID_TIMER, wElapse, NULL);
      }
      break;

    case WM_TIMER:
      {
        HDC hdc = GetDC(hWnd);
        // 注: マルチモニタには対応していない
        xText = rand() % GetDeviceCaps(hdc,HORZRES);
        yText = rand() % GetDeviceCaps(hdc,VERTRES);
        ReleaseDC(hWnd,hdc);
        InvalidateRect(hWnd,NULL,TRUE);
      }
      break;

    case WM_DESTROY:
      if( wTimer ) KillTimer(hWnd, ID_TIMER);
      break;

    case WM_PAINT:
      {
        PAINTSTRUCT ps;
        HDC hDC = BeginPaint(hWnd, &ps);
        HFONT hFont = CreateFont(160, 0, 0, 0, FW_NORMAL, FALSE,
            FALSE, FALSE, DEFAULT_CHARSET, 0, 0, 0, 0, NULL);
        HGDIOBJ oldFont = SelectObject( hDC, hFont );
        SetTextColor( hDC, 0xff8080 );
        SetBkColor( hDC, 0x000000 );
        TextOut(hDC, xText, yText, text, lstrlen(text) );
        SelectObject( hDC, oldFont );
        DeleteObject( hFont );
        EndPaint(hWnd, &ps);
      }
      break;

    case WM_ERASEBKGND:
      {
        RECT rc;
        GetClientRect(hWnd,&rc);
        FillRect((HDC)wParam, &rc,
            (HBRUSH)GetStockObject(BLACK_BRUSH));
      }
      return 0L;

    default:
      break;
  }

  return DefScreenSaverProc(hWnd, msg, wParam, lParam);
}

BOOL WINAPI RegisterDialogClasses (HANDLE hInst)
{
  return TRUE;
}

BOOL CALLBACK ScreenSaverConfigureDialog(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
  return FALSE;
}
Win32ネイティブ版スクリーンセーバー

■Win32ネイティブ・コードとC++/CLIの混在

 さて、このスクリーンセーバーの描画処理をGDIではなく、Visual C++ 2005とC++/CLIによって、.NET Frameworkのクラスを用いて描画させるには、どうすればよいのだろうか。

 残念ながら標準セットのVisual C++ 2005 Express Beta 2にはWin32関係のファイルが入っていないので、ここではVisual Studio 2005 Team System Beta 2を用いて試してみた。

 まず上記プロジェクトのソリューションをVisual Studio 2005で読み込ませ変換させる。そして、マネージ・コードを利用するために、プロジェクトのプロパティにある[構成プロパティ]で以下の変更を行う。

  • [全般の共通言語ランタイムサポート]の値を「共通言語ランタイムサポート (/clr)」に設定

  • [C/++、コード生成のランタイムライブラリ]の値を「マルチスレッドデバッグDLL (/MDd)」に設定(デバッグ・ビルドの場合)

 次に、メインのソース・コードの#includeを記述した行の後に、利用する.NET Frameworkのクラス・ライブラリを参照するための以下のコードを書き加える。

#using <mscorlib.dll>
#using <System.dll>
#using <System.Drawing.dll>
#using <System.Windows.Forms.dll>
using namespace System;
using namespace System::Drawing;
using namespace System::Windows::Forms;

 そして、いよいよ本番である。ScreenSaverProc関数の「case WM_PAINT:」以下のコードの中で、BeginPaint関数の呼び出しとEndPain関数の呼び出しの中間にあるコード、つまりGDIを利用して描画を行う部分を、以下の.NET Frameworkクラス・ライブラリを利用するコードにごっそりと置き換える(注:置き換えるコードの機能は完全に同じというわけではない)。

Graphics ^ g = Graphics::FromHdc((IntPtr)hDC);
try
{
  Font font("MS ゴシック",160);
  SolidBrush brush( Color::FromArgb(255,128,128,255) );
  PointF position( (float)xText, (float)yText );
  g->DrawString( "Hello",  %font,  %brush, position );
}
__finally
{
  g->Dispose();
}
.NET Frameworkクラス・ライブラリを利用して描画を行うC++/CLIのコード
C++/CLIでは、Win32ネイティブなコードにおいても、このような.NET Frameworkクラス・ライブラリの利用が可能となる。

 この後ビルドし、コマンドラインパラメータに「/s」を付けて実行すると、スクリーンセーバーとして機能するはずだ。

 WinMain関数を自前で持ち、ユーザー・プログラムの関数を呼び出すような脂っこいライブラリを使ったプログラムではあるが、それがきれいにマネージ・コードのアプリケーションとして実行できているのは、実に驚くべき技術といえる。もちろん、ネイティブ・コードで記述されたライブラリはネイティブ・コードのまま取り込まれ、マネージ・コードとの間は自動的にマーシャリングが挿入されているのである。

 これほどの機能が実現されたVisual C++ 2005とC++/CLIを見ていると、確かに過去の資産を継承するためにC++を使う価値があると納得させられる。

■古いプログラムに新しい息吹を! そして新しいプログラムには時を超えて続く魂を!

 Windows黄金時代のC++資産を受け継ぐということは、どういうことだろうか。Web/イントラネットの時代を経たいま、そのような古い資産を持ち出す価値がどこにあるのだろうか。その答えは、Web/イントラネットの時代を是としない考え方と連動しているように思われる。

 Web/イントラネットの時代とは、つまり管理コスト低減という美名の陰でユーザー・インターフェイスが弱体化した時代であったといえる。良質なWindowsアプリケーションが提供していた、極限まで分かりやすく、効率化されたユーザー・インターフェイスと同等のものを、Webアプリケーションは提供し得ない。そして、それは現場の利用者には不便を強いることになる。管理者にはよくとも利用者にはよくないのである。それが、Web/イントラネットの時代の持つ壁である。

 この壁を打ち破るための1つの方策こそが、Windows黄金時代の逆襲ではないだろうか。もちろん、これは時計の針を逆に戻すという意味ではない。マネージ・コードを支配するC++/CLIは、ClickOnceのような新しいネットワーク技術と連携して、新しい未来を切り開くために使われるべきものだと思う。

 そして、継承性のある技術は、新規にプログラムを記述する場合にも有効である。より多くの場面で使われるプログラム言語は、それだけ利用ノウハウなども蓄積されやすく、それは新規のプログラム開発にも活用できる。そして、新しいプログラムであっても、いつでも過去の膨大な蓄積を参照できることは、生産性の向上につながる。

 時代遅れであり過ぎる言語であっても、過去の蓄積を参照できるというだけで延命することがあるが、C++/CLIはむしろ新しさを吹き込まれた言語である。新しさと過去の蓄積への参照を兼ね備えた言語は、ある意味で最強といってよいのではないだろうか?End of Article

 

 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 記事ランキング

本日 月間