複数ファイルのリンク方法 (分割コンパイルとリンク)

今回の内容は厳密に言うと、Windows プログラミングに限った話ではありません。

普通の「 C 言語の話題」です。

ですが、C 言語の基礎を勉強したばかりの人が、本格的な Windows プログラミングを 始めようと思ったときに、つまづきやすいところでもあります。

そういうわけで、コードを複数のファイルに分けて、それからひとつの EXE ファイル をビルドする方法について説明したいと思います。

複数ファイルのリンクとは?

通常、プログラムは、機能的に複数の部分に分かれます。

例えば、ひとつのプログラムが機能的に、次の三つに分かれるとしましょう:

  1. 「メインのプログラム」(このプログラム特有の機能)
  2. 「ログの出力」 (ログを出力する一般的な機能)
  3. 「設定ファイルの読み込み」(設定ファイルを読み込む一般的な機能)

1、2、3それぞれのソースコード、ヘッダファイルの名前を次のようにします。

  1. prog1.cpp, prog1.h
  2. log.cpp, log.h
  3. conf.cpp, conf.h

それぞれをコンパイルして出来上がる中間ファイル、オブジェクトファイルは以下になります。

  1. prog1.obj
  2. log.obj
  3. conf.obj

この三つです。

実行可能ファイル prog1.exe をこの三つから作成する場合は、この三つの オブジェクトファイルをリンクすればよいのです。

もし外部のライブラリを利用している場合は、さらにライブラリファイルもリンクします。

さて、そこで他のプログラムを作ることにします。上記、2「ログの出力」、 3「設定ファイルの読み込み」は共通機能として、使いまわすことにします。

するとプログラム2を構成するのは次のファイルです。

  1. prog2.cpp, prog2.h
  2. log.cpp, log.h
  3. conf.cpp, conf.h

そして、オブジェクトファイルは次の三つです。

  1. prog2.obj
  2. log.obj
  3. conf.obj

お気づきだと思いますが、log.obj と conf.obj の二つは、プログラム1を 作ったときと全く同じファイルになります。従って、プログラム2を作るときは この二つはコンパイルしなおす必要がありません。

コンパイルする必要があるのは、prog2.cpp のみです。prog2.cpp から prog2.obj を作成して、それを log.obj と conf.obj にリンクすれば、 実行可能ファイル prog2.exe を作ることができます。

複数の obj ファイルから実行可能ファイルを作成しよう!

それでは、実際に試してみましょう。

ファイルの準備

まずはファイルの準備です。たくさんあるので頑張ってください!

conf.h は次の内容です。

#pragma once

void PrintWindowsVersion();

conf.cpp

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

void PrintWindowsVersion() {

        DWORD dwVersion = GetVersion();

        DWORD dwMajorVersion = (DWORD)(LOBYTE(LOWORD(dwVersion)));
        DWORD dwMinorVersion = (DWORD)(HIBYTE(LOWORD(dwVersion)));

        printf("Windows %d.%d\n", dwMajorVersion, dwMinorVersion );

}

conf.cpp では Windows のバージョンを取得する簡単な方法を示しています。

log.h

#pragma once

void PrintLog( char* lpszLog );

log.cpp

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

LONG g_lLine = 0;

void PrintLog( char* lpszLog ) {

        LONG l = InterlockedIncrement( &g_lLine );

        printf( "[%05u: %s]\n", l, lpszLog );

}

InterlockedIncrement はスレッドセーフに変数をインクリメントする関数です。

prog1.h

#pragma once

void Foo();

prog1.cpp

#include <stdio.h>
#include "prog1.h"
#include "conf.h"
#include "log.h"

int main( int argc, char* argv ) {

        Foo();

        return 0;
}


void Foo() {

        PrintLog ( "Entering Foo" );

        PrintWindowsVersion ();

        PrintLog ( "Exit Foo" );

}

prog2.h

#pragma once

void Bar();

prog2.cpp

#include <stdio.h>
#include "prog2.h"
#include "conf.h"
#include "log.h"

int main( int argc, char* argv ) {

        Bar();

        return 0;
}


void Bar() {

        PrintLog ( "Entering Bar" );

        PrintWindowsVersion ();

        PrintLog ( "Exit Bar" );

}

コンパイル

次のコマンドを実行してください。cl のパスが通る Visual Studio の Developer Command Prompt で実行するのを忘れないようにしてください。

> cl /c conf.cpp
> cl /c log.cpp
> cl /c prog1.cpp
> cl /c prog2.cpp

この結果次のファイルが出来上がります。

conf.obj
log.obj
prog1.obj
prog2.obj

実行可能ファイルの作成

prog1.exe, prog2.exe はそれぞれ次のコマンドで作ります。

> link conf.obj log.obj prog1.obj /OUT:prog1.exe
> link conf.obj log.obj prog2.obj /OUT:prog2.exe

ここでは conf.obj, log.obj が両方のプログラムの作成で使われていることに注意してください。

prog1.exe 及び prog2.exe をそれぞれ実行してみてください。 以下のような情報が表示されれば OK です。

> prog1
[00001: Entering Foo]
Windows 6.2
[00002: Exit Foo]

> prog2
[00001: Entering Bar]
Windows 6.2
[00002: Exit Bar]

確かに prog1.exe と prog2.exe は実行可能ですね。

このように、複数のソースファイルをそれぞれバラバラにコンパイルして、必要に応じてオブジェクトファイルをリンクして使うことができます。

ちなみに、必要なソースファイルを処理 (コンパイルなど) して、リンクする一連の作業を「プログラムをビルドする」という風に呼びます。