目的
諸事情あってC++で作成したライブラリ(DLL)をC#, Javaでも使用したい。
またその処理の都合上、文字列を引数で授受したい。
方法
ソースコード全体はGitHubに配置。
前提
- C++, C# のビルドにはVisual Studio 2019を使用
- Java は Open JDK 11 を使用
- 各ソースコードはShift-JISで記述
呼び出される関数
定義
今回呼び出される関数(C++で作成)の定義を示す。
my_common_lib.h
#define MY_COMMON_FUNC_DECL_KWD __declspec(dllexport)
extern "C" MY_COMMON_FUNC_DECL_KWD bool __stdcall my_func_a(
const char* in_str,
char* out_str
);
仕様上のポイントは以下の通り。
- 入力引数として文字列
- 出力引数として文字列
- 戻り値は論理値
実装上のポイントは以下の通り。
-
extern "C"
として関数名がマングリングされるのを防ぐ。 -
__stdcall
を指定する。
実装
デモとして以下のように関数を実装しておく。
my_common_lib.cpp
#include "my_common_lib.h"
#include <iostream>
bool my_func_a(const char* in_str, char* out_str) {
std::cout << "in_str: " << in_str << std::endl;
strcpy(out_str, "日本語(とASCII)を含む文字列");
return (strlen(in_str) < 5);
}
呼び出す側(C#)
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace DemoApp {
class Program {
static void Main(string[] args) {
StringBuilder outBuf = new StringBuilder(256);
NativeMethods.my_func_a("日本語とEnglish", outBuf);
Console.WriteLine(outBuf.ToString());
}
}
static class NativeMethods {
[DllImport("..\\..\\common-dll\\common-dll\\dest\\my_common_lib.dll", CallingConvention = CallingConvention.StdCall)]
public extern static bool my_func_a(string in_str, StringBuilder out_str);
}
}
実装のポイントは以下の通り。
-
関数と同名のstaticメソッドに
DllImport
属性を付与してDLL関数の受け皿とする-
dllName
にファイルパスを指定すれば任意のDLLファイルをロードできる - 呼び出し規約はDLL関数に合わせておく(今回は
StdCall
) - 入力引数の文字列は
string
を割り当てる - 出力引数の文字列は
StringBuilder
を割り当てる
-
- 使用する際は単にstaticメソッドとして呼び出せばよい。
実行結果は以下のようになる(1行目はDLL関数内での出力)。
in_str: C#から文字列
result: False
out_str: 日本語(とASCII)を含む文字列
呼び出す側(Java)
Java Native Accessを利用する。
pom.xml
<!-- https://mvnrepository.com/artifact/net.java.dev.jna/jna -->
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.8.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/net.java.dev.jna/jna-platform -->
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
<version>5.8.0</version>
</dependency>
App.java
import com.sun.jna.Library;
import com.sun.jna.Native;
public class App
{
public static void main( String[] args )
{
byte[] buf = new byte[256];
boolean result = NativeMethods.INSTANCE.my_func_a("Javaから文字列", buf);
System.out.println("result: " + result);
System.out.println("out_str: " + Native.toString(buf));
}
public interface NativeMethods extends Library {
NativeMethods INSTANCE = Native.load("..\\..\\common-dll\\dest\\my_common_lib.dll", NativeMethods.class);
public boolean my_func_a( String in_str, byte[] out_str );
}
}
実装のポイントは以下の通り。
-
com.sun.jna.Library
インターフェースを継承したインターフェースを作成する-
関数と同名のメソッドを作成する
- 入力引数の文字列は
String
を割り当てる -
出力引数の文字列は
byte[]
を割り当てる→呼び出し後に文字列に変換する
- 入力引数の文字列は
- メンバにインスタンスの実体を定義する(※)
-
name
にファイルパスを指定すれば任意のDLLファイルをロードできる
-
-
関数と同名のメソッドを作成する
- 使用する際はインターフェースのメンバを介してメソッドを実行する。
Native.toString
メソッドを利用し、出力引数のバイト列を文字列に変換する
※JNA 4.xで使用されていたloadLibrary
は非推奨とされ、5.xではload
を使用することに注意(使い方は同じ)。
実行結果は以下のようになる(1行目はDLL関数内での出力)。
in_str: Javaから文字列
result: false
out_str: 日本語(とASCII)を含む文字列