Unity (.NET Standard) プロジェクトでのWindows向けシリアルポートアクセス用ネイティブプラグインの開発

はじめに


Unityプロジェクトを.NET Standardで設定した場合、Windows環境でシリアルポートを使用する際にSystem.IO.Portsクラスが問題を引き起こします。この問題の根本的な原因は、Unityが.NET Frameworkに設定されている必要があるためです。具体的には以下のエラーが発生します: 

  1. NotSupportedException: System.IO.Ports is currently only supported on Windows.

ただし、プロジェクト全体を.NET Frameworkに変更すると、マルチプラットフォーム対応に影響が出るため、Windows専用の解決策が必要になります。

実装した解決方法


私たちは、この問題を解決するためにWindows向けのC++ネイティブプラグインを開発しました。これにより、Unityを.NET Standardのまま維持しつつ、Windowsでシリアルポートを安全に制御できるようになります。

Visual Studioでの設定

Unityで使用可能なDLLを作成するために、Visual Studioでは以下の設定が必要です:

  • プロジェクトテンプレートとして「Dynamic Link Library (DLL)」(動的リンクライブラリ)を使用
  • プラットフォームターゲットをプロジェクトに合わせてx64またはx86に設定
  • Unityから呼び出す関数を__declspec(dllexport)を使って明確に定義

主なメソッドの実装について

C++で実装したプラグインは、Windows APIを使用してシリアルポートを直接制御しています。主な関数は以下の通りです。

  • OpenSerialPort:シリアルポートを開くためにCreateFileAを使用します。
  1. __declspec(dllexport) bool OpenSerialPort(const char* portName)
  2. {
  3.     hSerial = CreateFileA(portName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
  4.     if (hSerial == INVALID_HANDLE_VALUE)
  5.     {
  6.         return false;
  7.     }
  8.     serialPorts[portName] = hSerial;
  9.     return true;
  10. }
  • WriteSerialPort:開いたシリアルポートにデータを書き込みます。WriteFileを使用します。
  1. __declspec(dllexport) bool WriteSerialPort(const char* data, DWORD dataLength)
  2. {
  3.     DWORD bytesWritten;
  4.     if (!WriteFile(hSerial, data, dataLength, &bytesWritten, NULL))
  5.     {
  6.         return false;
  7.     }
  8.     return true;
  9. }
  • GetAvailableSerialPorts:利用可能なシリアルポートを取得するために、QueryDosDeviceWを使用します。
  1. __declspec(dllexport) const wchar_t* GetAvailableSerialPorts()
  2. {
  3.     static std::wstring portsList;
  4.     portsList.clear();
  5.     wchar_t devices[65536];
  6.     DWORD charCount = QueryDosDeviceW(nullptr, devices, 65536);
  7.     if (charCount == 0) {
  8.         portsList = L"NONE";
  9.         return portsList.c_str();
  10.     }
  11.     wchar_t* currentDevice = devices;
  12.     while (*currentDevice != L'\0') {
  13.         std::wstring deviceName(currentDevice);
  14.         if (deviceName.find(L"COM") == 0) {
  15.             if (!portsList.empty()) portsList += L";";
  16.             portsList += deviceName;
  17.         }
  18.         currentDevice += wcslen(currentDevice) + 1;
  19.     }
  20.     if (portsList.empty()) {
  21.         portsList = L"NONE";
  22.     }
  23.     return portsList.c_str();
  24. }

UnityへのDLLの組み込み


生成したDLLをUnityプロジェクトに組み込む方法は以下の通りです:

1. VisualStudioでDLLのビルド:

Visual StudioでプロジェクトをReleaseモードでビルドします。

作成されたDLLは、プロジェクトフォルダ内のReleaseフォルダに保存されます(例:SerialPortPlugin.dll)。

2. UnityへのDLLの追加:

DLLファイルをコピーします。

Unityのプロジェクト内にAssets/Pluginsというフォルダを作成します。

Assets/Pluginsフォルダ内にDLLをペーストします。

3. DLLの設定確認:

Unity内でDLLファイルを選択し、ターゲットプラットフォームとアーキテクチャが正しく設定されていることを確認します。

4. UnityからのDLL利用方法:

Unityプロジェクト内でDLLを呼び出して利用するためには、[DllImport("SerialPortPlugin")]を用いて関数を宣言します。

Windows環境のみで動作するため、#if UNITY_STANDALONE_WINの使用が必須です。

以下は簡単なコード例です。

  1. using System.Runtime.InteropServices;
  2. using UnityEngine;
  3. public class SerialPortManager : MonoBehaviour
  4. {
  5. #if UNITY_STANDALONE_WIN
  6.     [DllImport("SerialPortPlugin")]
  7.     private static extern bool OpenSerialPort(string portName);
  8.     [DllImport("SerialPortPlugin")]
  9.     private static extern bool WriteSerialPort(string data, uint dataLength);
  10.     [DllImport("SerialPortPlugin")]
  11.     private static extern System.IntPtr GetAvailableSerialPorts();
  12.     void Start()
  13.     {
  14.         string availablePorts = Marshal.PtrToStringUni(GetAvailableSerialPorts());
  15.         Debug.Log($"利用可能なポート: {availablePorts}");
  16.         string[] ports = availablePorts.Split(';');
  17.         if (ports.Length > 0 && ports[0] != "NONE")
  18.         {
  19.             if (OpenSerialPort(ports[0]))
  20.             {
  21.                 Debug.Log($"ポート {ports[0]} を開きました。");
  22.                 string message = "Hello, Serial!";
  23.                 WriteSerialPort(message, (uint)message.Length);
  24.             }
  25.             else
  26.             {
  27.                 Debug.LogError($"ポート {ports[0]} のオープンに失敗しました。");
  28.             }
  29.         }
  30.         else
  31.         {
  32.             Debug.LogError("利用可能なポートがありません。");
  33.         }
  34.     }
  35. #endif
  36. }

まとめ


この方法により、Unity(.Net Standard)プロジェクトのマルチプラットフォーム対応を維持したまま、Windows環境でのシリアルポート利用を実現しています。 

上記ブログの内容に少しでも興味がありましたら、お気軽にご連絡ください。

弊社のエンジニアがフレンドリーに対応させていただきます。