【C#】C を C# から使いたかったので DLL 化した話

最近、ある友人にC言語を教えているのですが、ふと思ったことがあります。

C言語ってC#から読み込めるのかな?」と。

1. DLLファイルの作成

C言語C++言語はDLL(ダイナミックリンクライブラリ)ファイルにすれば読み込めるとのことです。まずはDLLファイルを作っていきます。

1.1 Visual Studio で2つのプロジェクトを作成する

呼び出すほうは C#, 呼び出されるほうは C なので、プロジェクトを2つに分ける必要があります。この段階ですごい今更なのですが、なぜ Visual Studio が「ソリューション」と「プロジェクト」で使い分けているのかがようやく分かりました(笑)

まずはC#のプロジェクトですね。シンプルにコンソールアプリ(.NET Core) でいきます。

f:id:takunology:20191230163755p:plain

できたらソリューションエクスプローラーから新規プロジェクトを追加します。
私はC++の空のプロジェクトを選びました。

f:id:takunology:20191230164341j:plain

f:id:takunology:20191230164349p:plain

これでソリューションに 2つのプロジェクトが作成されました。

f:id:takunology:20191230164539p:plain

1.2 ソースコードを書く

後は普通にC言語を書いていけばいいのですが、その前に約束事を記述していきます。
まずはヘッダーファイルを書きます。

f:id:takunology:20191230170244p:plain

#pragma once //自動生成されてる
extern "C" __declspec (dllexport) int main();

これでDLLにするための関数を定義できました。上記では main 関数がDLL化されたとき、C# から C のmain関数を呼び出せるようになります。
あとはソースファイルに c++ ファイルを追加して

f:id:takunology:20191230172045p:plain

普通にC言語を書いていきます。C++も書けますが、それは別の記事で紹介します。

#include<stdio.h>
#include"dlldefine.h" //先ほど定義したヘッダファイル

int main() {
    printf("この文字はC言語で表示しています!\n");
    return 0;
}

1.3 プロパティを変更する

このままビルドしても実行可能形式(.exe)が生成されてしまうので、DLLを生成するように変更します。
C++のプロジェクトを右クリックして、"プロパティ"をクリックするとプロパティページが表示されます。
構成の種類を "ダイナミック ライブラリ(.dll)" に変更します。

f:id:takunology:20191230172722j:plain

これでビルドしてみます。

f:id:takunology:20191230172908p:plain

どうやら ソリューション内の Debug というディレクトリに保存されているみたいですね。

f:id:takunology:20191230173148p:plain

これでDLL化が終わりました!

2. C#からDLLを使う

2.1 C#から呼び出すコードを書く

C#プロジェクトの Program.cs を次のように記述します。

using System.Runtime.InteropServices;

namespace DLLApp
{
    class Program
    {
        [DllImport("DLLfile.dll")]
        static extern int main(); //DLL内で定義された関数

        static void Main(string[] args)
        {
            main(); //C言語のmain関数を実行する 
        }
    }
}

DLLImportでDLLファイルを指定する際には、先ほど生成されたDLLファイル名を記述します。
さて、これで準備はできたように思えますが、このまま実行すると例外が発生します。

f:id:takunology:20191230183055p:plain

どうやらDLLファイルが見つからないようです。なぜでしょうか?

2.2 例外が起こる原因

答えはプロジェクトによって、ビルドの出力先ディレクトリが異なるからです。

C/C++の場合: DLLApp\Debug
C#の場合: DLLApp\DLLApp\bin\Debug\netcoreapp3.0

C#のほうが階層が深く、そのディレクトリ内でDLLを参照しようとするために例外が発生してしまうのです。
解決する方法はいくつかあります。

この中で最も分かりやすいのは 1番目ですね。2番目と3番目はどちらもビルドしてみないと正確なパスが分かりません。4番目は配布されている場合は良いかもしれませんが、同じソリューションで開発している場合、DLLを変更するたびに毎回コピペする必要があり非効率です。

2.3 出力先ディレクトリの統一

各プロジェクトでプロパティを設定します。 共通のビルドパスとして DLLApp\Build\ に設定します。 まずはC#からです。

f:id:takunology:20191230185708j:plain

ただし、.NET Core 3.0 フレームワークで開発すると netcoreapp3.0 というディレクトリが自動で生成される ので、これを考慮しないといけません。

次に、DLLの出力先を変更します。先ほどの考慮を踏まえて、設定するパスとしては DLLApp\Build\netcoreapp3.0\ となります。(ディレクトリを指定するので、最後に \ を付けないと警告が出ます。)

f:id:takunology:20191230192627j:plain

これでDLLの出力先とアプリの出力先のパスが統一されました。あとはビルドするだけですね。

3 ビルドと実行

Visual Stufio には複数のプロジェクトを一気にビルドする方法があります。これがバッチビルドです。"ビルド" タブに "バッチビルド" があります。
色々と項目がありますが、それぞれのプロジェクトで Debug を選択して ビルドのチェックボックスにチェックを入れました。(使用しているOSによってはアプリが64ビット版で動作するので、それに合わせて x64を選択します。)これで "ビルド" をクリックするとチェックを入れた項目がビルドされます。

f:id:takunology:20191230194532p:plain

f:id:takunology:20191230194613p:plain

同じディレクトリにビルドされていますね

f:id:takunology:20191230193849p:plain

後は実行して動くか確かめてみましょう!

f:id:takunology:20191230194718p:plain

無事に動いていますね。C#からではなく、Cから文字を表示しています!

おわりに

CのファイルをDLL化することで、C#からCを使うことができました。
複雑なプログラムをDLL化すれば、利用する側としては引数と関数名さえ分かっていれば簡単に利用することができるのがメリットだと思います!

C#マジ愛してる。

今回作ったプログラム

参考にどうぞ。

github.com

参考にしたサイト

ありがとうございます。

qiita.com