C++で作成したDLLとC#／Javaとで文字列を授受する

# 目的

諸事情あってC++で作成したライブラリ(DLL)をC#, Javaでも使用したい。
またその処理の都合上、文字列を引数で授受したい。

# 方法

ソースコード全体は[GitHub](https://github.com/kurukuruz/demo-common-dll)に配置。

## 前提

- C++, C# のビルドにはVisual Studio 2019を使用
- Java は Open JDK 11 を使用
- 各ソースコードはShift-JISで記述

## 呼び出される関数

### 定義

今回呼び出される関数(C++で作成)の定義を示す。

```cpp: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`を指定する。

### 実装

デモとして以下のように関数を実装しておく。

```cpp: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#)

```csharp
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](https://github.com/java-native-access/jna)を利用する。

```xml: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>
```

```java: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)を含む文字列
```

# 参考

- [【C++/C#】C++で作成したDLLをC#で呼ぶ - Qiita](https://qiita.com/tera1707/items/81042abaa62dc97e26ae)
- [c# - C#でC++のDLLから文字列を受け取る - スタック・オーバーフロー](https://ja.stackoverflow.com/questions/30434/c%E3%81%A7c%E3%81%AEdll%E3%81%8B%E3%82%89%E6%96%87%E5%AD%97%E5%88%97%E3%82%92%E5%8F%97%E3%81%91%E5%8F%96%E3%82%8B)
- [JNA (Java Native Access) パターン集 - Qiita](https://qiita.com/everylittle/items/b888cbec643f14de5ea6)
- [jna 5.8.0 javadoc (net.java.dev.jna)](https://javadoc.io/doc/net.java.dev.jna/jna/latest/index.html)