JavaからC#のDLLを呼び出す

◆哀丁・四方山話 第11話

pinch
JavaからC#のDLLを呼び出す

ジャンル・キーワード

  • Windows 10
  • Visual Studio 2019
  • JNR(JNI)
  • C#

結果は・・・・解決(^o^)

 

何が起こったの?

3つの異なる世界

Windows上で動作する多くのプログラムは、Microsoftが提供している.NET Frameworkの上で稼働します。このようなプログラムの事を「マネージドコード」と呼びます。

一方で旧来の言語(VB6など)や、Microsoft系ではない言語(Cなど)で作成されたプログラムは、「.NETではない世界」で動作します。これを「アンマネージドコード(ネイティブコード)」と呼びます。

私が日頃利用しているPROCESSINGも、Java上(Java VM)で稼働するプログラムですので、.NETではない世界で動作していますね。Javaの場合は、マネージドでもネイティブでもない世界(Java VMの中)で動作します。

このようにWindowsの世界には、マネージドコード、アンマネージドコード、それ以外(Javaの世界)という3つの異なる世界が広がっているのです。

(画像URL:illust-AC 様:ダニエルさん、acworksさん)

 

3つの世界は仲が悪い

Javaは歴史がある言語で、強力なライブラリーや拡張機能が豊富に用意されていますが、ときにOSやプラットホーム固有の機能を生かしたプログラムが作りたくなる事もあります。

そんなときWindowsであれば、Microsoftが用意しているAPI(Win32API)を利用するか、.NETフレームワークが用意している命令を利用して、OS固有の機能を呼び出す事になります。

ですがアンマネージドコードで作られたプログラムからは、マネージドコードで作られたプログラムを利用することができません。

またJavaはMicrosoftではない団体が管理している言語であるため、マネージドコードだけでなく、アンマネージドコード(Win32APIで作られた部品)も呼び出すことができないのです。


図にすると、こんな関係になります。そうです。3つの世界はあまり仲が良くなかったんですね・・・。

 

仲良くしようよ

これではあまりにも不便なので、JNI(Java Native Interface)という仕組みが考え出されました。

JNIを使うと、Javaからアンマネージドなプログラムを呼び出すことが可能となります。実際にはJNIは少し使いにくい面があるため、JNIを改良したJNA(Java Native Access)を使うことがよくあります。


こんなイメージですね。はい。JNA(JNI)は、Javaとアンマネージドコードの2つの世界の橋渡しをする人なんですね。いいヤツなんです(笑)。

(画像URL:illust-AC 様:ダニエルさん、acworksさん)

 

それでも困った事がある

しかし、やっぱりJavaからは、.NETワールドにあるマネージドコードは利用することができません。JNAとはいえ、完璧ではないんですね。

こんな時、どーしても.NETワールドのプログラムを呼び出したい場合はどうするのかというと・・・

代表的な方法としては、Visual C++を使ってCLI(.NET Frameworkの共通言語基盤)を利用した「アンマネージドな世界とマネージドな世界を橋渡しする部品」を自分たちで作成し、その部品経由でマネージドコードを利用することになります。


こんな感じです。

C++ CLIで作成された橋渡し用の部品(ラッパー関数)は、JNAからも呼び出す事が可能です。

(画像URL:illust-AC 様:ダニエルさん、acworksさん)

これでようやく、3つの世界をなんとか繋ぐことができました。

ところが、このC++/CLIを「自作しなければいけない」というところが曲者で、はっきり言って初心者向きではありません。いやプロでも面倒くさいです(汗)。

でも、いままではここが精一杯でした。

 

救世主現る

ながーい前フリが終わって、ようやく本題に入ります。

このC++/CLIの橋渡し用部品を用意しなくても、JNAからマネージドコードの部品を呼び出す画期的な方法を見つけました。それが「DllExport」というC#のライブラリーです。

「DllExport」を導入すると、以下のような制約条件は付きますが、C++/CLIで橋渡し用の部品を作成するのに比べて、圧倒的に少ない手数でJavaからC#で作られたマネージドなDLLを呼び出すことが可能となります。

  • 呼び出される側(C#)のソースコードに若干手を加える必要がある
  • 呼び出されるC#側の関数は、static 関数である事
  • DllExportで公開指定された関数から、DllExportで公開指定された別関数は呼び出せない
  • DllからDllを呼び出すのが難しい

詳しく書くと、上図のような関係になります。

図で Exportと書かれた関数が、DllExportにより公開指定された関数です。これをC++/CLIの橋渡し部品を用意する事なく、呼び出せるようになります。

(画像URL:illust-AC 様:ダニエルさん、acworksさん)

ただし・・・、(私の技術力が足りないせいなのですが)呼び出し元となるDllから、別のDllにある部品を呼び出す事に苦戦しました。図でいうと、緑の点線の箇所(関数A->関数D、関数C->関数Eなど)です。

呼び出す方法はあるのですが、普段利用しない方法で呼び出す事しかできませんでした・・・orz。それでもよければ、この記事の一番下の方で紹介していますので参考としてください。

 

結論

DllExportは大変便利です。DllExportは、VisualStudioからNuGet経由でインストールする事が可能です。

以下に、最も基本的な導入方法と利用例を掲載しておきます。

1.C#でマネージドなDLLを作成する

以下のように、.NET FrameworkのDLLを作成します。


プロジェクトを作成したら、試験的に以下のようなコードを作成してください。namespaceとclass名は、あなたのプロジェクトに合わせて変更してくださいね。

ポイントは、アンマネージド側に公開する関数に[DllExport]というアノテーションを付けることです。

あとは注意書きでも書いたように、公開する関数は static にしてください。

この時点では[DllExport]部分に赤い波線が出てコンパイルできませんが、気にしないでください。

 

2.NuGetでDllExportをインストールする

プロジェクト->NuGetパッケージの管理を選択します。


参照で検索窓にDllExportと入力し、見つけたDllExportパッケージをインストールします。


インストール途中で、以下のようなダイアログボックスが開きます。


作成するC#のDLLのbit数を選択(32bitならx86、64bitならx64、両方なら(x86+64))し、Installedにチェックをつけ、Applyを押下します。

注意点はbit数の選択です。

ここで仮に64bitを選択したら、呼び出し側(Java側)も64bitで作成する必要があります。JDKやPROCESSING、EclipseやIntelliJ IDEAなどのIDEのビット数もすべて64bitで統一してください。

64bitと32bitの混在はできない事に注意してください。

インストールが終わると、以下のようなダイアログボックスが開くので「すべて再読み込み」を選択します。


先程まで表示されていた[DllExport]部分の赤い波線が消えているはずです。ビルドして、DLLを作成してください。

 

3.Java側を作成する

Java側をIntelliJ IDEAで作成する例を紹介します。

まずはIDEで新規プロジェクトを作成します。JNAパッケージをインストールしたかったので私はgradleを使いましたが、ここはみなさんの好みで変えてください。

JNAはmvnrepository から入手可能です。2020/03月現在、Ver5.5.0が最新です。またJava Native Access Platformも入手しておいてください。

gradleを使っている人は、新規プロジェクトのbuild.gradleに以下の記述を追加してください。

続いて以下のようにクラスを記述します。

インタフェースの名前は、自由につけてもらって構いません。インタフェース部に、C#側で公開した関数を定義します。

main()の中でJNAにDLLを読み込ませ、インタフェースに定義した関数を呼び出しています。

ポイントは

と書かれた箇所です。

JNAはJavaのシステムエンコードを見て動作するようですが、(なぜか)shift_jis以外の場合、C#側とやり取りする文字列が化けてしまいます。なので強制的に、JNAが利用する文字コードを shift_jisにしています。

実行すると、「ほげ」と表示された小さなメッセージBOXが表示されます。

またIntelliJ IDEAのコンソールに
のようなメッセージが表示されて、みごとにJava->C#のDLLを呼び出せたことがわかります。

やったね!

 

わかった事

DllExportを利用すると、C++/CLIでブリッジ用のDLLを用意する事なく、簡単にC#のDLLを利用する事ができそうです。

ここでは簡単な例のみを紹介しましたが、がんばればかなり複雑な情報の交換もできるようです。必要に迫られている方は以下の参考サイト様などをご覧になると、幸せになれるかもしれません。

 

おまけ:DLLから別のDLLを呼び出す

そもそもは・・・DLL(1段目)から別のDLL(2段目)を呼びだす事自体、結構難易度が高い作業となります。

1段目のDLLは呼び出せても、2段目のDLLが見つからないなどの問題によく出くわします(汗)。特にDllExportを利用した場合は、2段目のDLLが見えずに苦戦しました。

以下に紹介する方法が「正解」なのかどうかはわかりませんが、これしか方法を思いつきませんでした(汗)。

それは、2段目のDLLを1段目のDLLから動的リンクで呼び出す方法です。

参考までに、私が試行錯誤した方法を記載しておきます。誰かの参考になれば幸いです。

まず2段目のDLLを作成する

検証するために、2段目のDLLを以下のように作成し、ビルドしてDLLを作成します。

ExportStaticFuncCはstaticな公開関数、PublicFuncDは通常の公開関数です。2段目のDLLはJavaから直接呼び出さないため、DllExportは利用していません。

 

1段目の DLLから動的リンクで呼び出す

1段目のDLLを、以下のように改造します。

2段目のDLLに含まれているstatic関数(ExportStaticFuncC)とPublic関数(PublicFuncD)を呼び出す処理を追加しています。

static関数は2段目のDLLに含まれているものを直接呼び出すことが可能です(スタティックですからね!)が、Public関数の方はインスタンスを生成してから呼び出す必要があります。

 

Java側も修正する

それでは呼び出すJava側のモジュールも修正しましょう。以下のようにします。

CallStaticSecondFuncとCallPublicSecondFuncの呼び出し部分を追加しました。

実行すると


というメッセージBOXが表示され、無事にJava->1段目のDLL->2段目のDLLと、順番に呼び出せたことがわかります。

 


参考にさせて頂いたサイト様など

●JNAについて

●DllExportについて

●その他


哀丁・四方山話一覧 へ戻る
(画像URL:illust-AC 様:wayo さん、acworks さん Free icons 様)