C#プログラミング備忘録

Visual Studio C/C++のDLLの作成方法

C#

この記事ではVisual StudioでのC/C++のDLLの作り方を説明しています。方法はいくつかあると思いますが、ウィザードを使うと割と簡単に作成できるので興味のある方は試してみて下さい。

事前準備

DLLを作成する前にC#のコンソールアプリケーションを作成しておいてください。確認のため最後にC#からDLL内の関数を呼び出します。今回は「CSharpStudy2」というアプリケーション名にしました。このアプリケーションと同じソリューション内にDLLのプロジェクトも含めます。今回の開発環境は以下のとおりです。

オペレーティングシステム Windows10 x64

Visual Studio Microsoft Visual Studio Community 2019 Version 16.11.2

DLLの作成

DLL用のプロジェクトを追加

ソリューションエクスプローラーにて以下の手順でプロジェクトを追加します。

  1. ソリューションを右クリック
  2. 追加
  3. 新しいプロジェクト

「新しいプロジェクトを追加」画面が表示されます。ここでプログラム言語とオペレーティングシステムによって絞り込みができるので、それぞれC++とWindowsを選択します。表示される項目の中から「Windows デスクトップ ウィザード」を選択します。

「次へ」ボタンを押下します。「新しいプロジェクトを構成します」画面が表示されるので「プロジェクト名」と「場所」を入力します。通常プロジェクト名はDLLのファイル名になるので適切な名前を入力してください。ここでは「MyLib」としておきます。

「作成」ボタンを押下します。「Windows デスクトップ プロジェクト」画面が表示されるので以下の図のとおり設定してください。

「OK」ボタンを押下するとプロジェクトが追加されます。今回は「MyLib」という名前のプロジェクトになります。

32ビットDLL用の設定

ここでは32ビットのDLLを作成することにします。DLLのプロジェクトを選択してツールバーから「構成マネージャー」を開きます。

「構成マネージャー」では以下の図のとおり設定してください。「アクティブ ソリューション構成」にて「Debug」と「Release」の両方とも同じ設定にします。今回は「MyLib」プロジェクトのプラットフォームで「Win32」を選択します。

ソースコードの整形

ウィザードによってソースコードの雛形が作成されます。ここではヘッダーファイル「MyLib.h」とソースファイル「MyLib.cpp」を整形していきます。それぞれ余分なコメントやクラス定義は削除して「fnMyLib」関数だけを残します。

MyLib.h

ヘッダーファイルを以下のとおりにします。ここでのポイントは extern “C” です。extern “C” については補足の「名前マングリングについて」で説明しています。簡単に言うとCリンケージでコンパイルするように指示するためのものです。

#ifdef MYLIB_EXPORTS
#define MYLIB_API __declspec(dllexport)
#else
#define MYLIB_API __declspec(dllimport)
#endif

#ifdef  __cplusplus
extern "C" {
#endif

// 関数定義
MYLIB_API int fnMyLib(void);

#ifdef  __cplusplus
}
#endif

ソースファイルは以下のとおりとします。

MyLib.cpp
// MyLib.cpp : DLL 用にエクスポートされる関数を定義します。
//

#include "pch.h"
#include "framework.h"
#include "MyLib.h"

// これは、エクスポートされた関数の例です。
MYLIB_API int fnMyLib(void)
{
    return 256;
}

DLLファイルのコピーの設定

ビルドしたDLLファイルをアプリケーションの実行ファイルと同じフォルダにコピーをするために以下のとおり設定します。DLLのプロジェクト(今回はMyLib)を選択してメニューバーにて「プロジェクト」の「MyLib のプロパティ」を押下します。以下の画面が表示されます。ここでは 「Debug」と「Release」の両方とも同じ設定にするため左上の「構成」から「すべての構成」を選択しておきます。

「ビルド イベント」の「ビルド後のイベント」にて以下のコマンドを入力します。ここは環境に応じて適宜修正してください。

robocopy "$(SolutionDir)$(Configuration)" "$(SolutionDir)$(SolutionName)\bin\$(Configuration)" *.dll /xo /E /COPY:DT
IF %ERRORLEVEL% LSS 8 EXIT 0

このコマンドの詳細については下記サイトを参考にさせて頂きました。ありがとうございます。

C#からの呼び出し

C#のアプリケーションのソースコードは以下のとおりです。

using System;
using System.Runtime.InteropServices;

namespace CSharpStudy2
{
    class Program
    {
        static void Main(string[] args)
        {
            int ret = NativeMethod.fnMyLib();

            Console.WriteLine(ret);

            if (IntPtr.Size == 4)
            {
                Console.WriteLine("32ビットプロセスで動作しています。");
            } 
            else if (IntPtr.Size == 8)
            {
                Console.WriteLine("64ビットプロセスで動作しています。");
            }

            Console.ReadLine();
        }
    }

    static class NativeMethod
    {
        [DllImport("MyLib.dll")]
        public static extern int fnMyLib();
    }
}

※32ビットと64ビットのどちらのプロセスで動作しているかを確認するためにIntPtr.Sizeプロパティを参照する方法があります。このプロパティの値が4の場合は32ビットプロセス、8の場合は64ビットプロセスとなります。

32ビットプロセスの動作設定

今回は32ビットのDLLを作成したので32ビットのプロセスで動作させます。アプリケーションのプロジェクトを右クリックしてプロパティを押下します。以下の画面が表示されます。

32ビットプロセスの設定

対象プラットフォームが「Any CPU」なので32ビットWindows上で実行された場合は32ビットのネイティブコード(x86)に変換され、 64ビットWindows上で実行された場合は64ビットのネイティブコード(x64)に変換されます。ただしデフォルトの設定では上図のとおり「32 ビットを選ぶ」にチェックが入っているため64ビットWindows上でも32ビットのプロセスとして動作します。

VC++ランタイムの設定

ここまででほぼ終わりですが、このままではDLLを作成した開発環境にインストールされているVC++のランタイムが存在しない環境では実行時エラーが発生します。DLLをビルドする際にランタイムライブラリを静的リンクするか動的リンクするかを選択することができます。それぞれのメリットとデメリットは以下のとおりです。

メリットデメリット


リンク
開発環境にインストールされているランタイムが存在しない環境でも実行できるDLLのファイルサイズが大きくなる




DLLのファイルサイズが小さくなる開発環境にインストールされているランタイムが存在しない環境では実行できない

ここでは静的リンクの設定をすることにします。DLLのプロジェクトを選択してメニューから「プロジェクト」->「MyLib のプロパティ」を押下します。「構成」から「Debug」と「Release」が選択できるので両方設定します。それぞれ「構成プロパティ」->「C/C++」->「コード生成」を選択します。ここに表示されている「ランタイム ライブラリ」を設定します。

Debugでは「マルチスレッド デバッグ(/MTd)」を選択します。

Releaseでは 「マルチスレッド(/MT)」を選択します。

他の選択肢を含めて詳細は以下を参照してください。

C ランタイム (CRT) と C++ 標準ライブラリ .lib ファイル
リンク先として使用できる Microsoft C ランタイムおよび C++ 標準ライブラリの .lib ファイルの一覧と、それらに関連付けられているコンパイラオプションとプリプロセッサディレクティブ。

実行結果

DLLおよびアプリケーションをビルドして実行するとコンソールウィンドウに以下の結果が表示されます。

256
32ビットプロセスで動作しています。

C#から色々なCの関数を呼び出す方法については以下を参照して下さい。

>> C#からC言語の関数(DLL)を呼び出す

補足

名前マングリングについて

DLLのヘッダーファイル「MyLib.h」で fnMyLib 関数に extern “C” を付けているのはC++ではなくCリンケージでコンパイルするためです。C++には名前空間や関数名のオーバーロードの機能があります。 extern “C” を付けずにC++コンパイラでコンパイルすると出力されるシンボル名は fnMyLib ではなくなってしまうため実行時に以下のリンクエラーが発生します。例えば関数名のオーバーロードをした場合、C++コンパイラでコンパイルするとそれぞれの関数を識別できるように一意のシンボル名が必要となるため元の関数名とは異なるシンボル名が付けられます。これが名前マングリング(name mangling)です。

64ビットプロセスでの実行

今回は32ビットのDLLを32ビットのプロセスで動作させたため問題ありませんでした。しかし、上に出てきた「32ビットプロセスの設定」画面で 「32 ビットを選ぶ」のチェックを外すと64ビットWindowsでは64ビットのプロセスとして動作するため以下のとおり実行時エラーとなってしまいます。

以上です。

コメント