Windows で使用中の COM ポートを削除する方法

Posted Fri Jan 10 2014

組み込み系の装置を扱っていると,新しい装置を接続するたびに COM ポートが割り当てられポート番号が数十になってしまうことがあります.

今回,仕事で増えたポートを削除するツールを作って欲しいと言われ,色々と調査しました.そのときの覚書です.

デバイスマネージャから削除

コマンドラインから以下のコマンドを実行します.Windows 7 など UAC が効いている場合には,コマンドプロンプトを管理者権限で実行しないと表示されないので注意です.

$ set devmgr_show_nonpresent_devices=1
$ devmgmt.msc

[表示]->[非表示のデバイスの表示]で,使用されていないポートが半透明で表示されるので[Delete]キーで削除します.

レジストリから削除

Com ポートの使用情報は下記のレジストリで管理されています.

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\COM Name Arbiter\ComDB

ComDB は 32byte のバイナリ値で管理されており,ビットのフラグが立っているか立っていないかで,それぞれのポートが使用中かどうか判断しています.それぞれの割り当ては下記のように番号の大きいほうが上位ビットになっているようです.

0000  4D 05 00 00 00 00 00 00

Com   8  7  6  5  4  3  2  1
4D -> 0  1  0  0  1  1  0  1

Com  16 15 14 13 12 11 10  9
05 -> 0  0  0  0  0  1  0  1

この情報をクリアすれば,新デバイス接続時に若いポートから割り当てられるようになります.クリアするために下記のようなプログラムを書きました.

int BitPosToClearFlag(const int value)
{
  switch (value) {
  case 1: return 0x01;
  case 2: return 0x03;
  case 3: return 0x07;
  case 4: return 0x0f;
  case 5: return 0x1f;
  case 6: return 0x3f;
  case 7: return 0x7f;
  case 8: return 0xff;
  default: return 0xff;
  }
}

BOOL ClearComPort(const int com_number /* com_number 以降をクリアする */)
{
  TCHAR szSubKey[256] = _T("SYSTEM\\CurrentControlSet\\Control\\COM Name Arbiter");

  DWORD result, dwType;
  HKEY hOpendKey;

  BYTE pBuffer[32];
  DWORD nMaxLength = sizeof(pBuffer);
  if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, szSubKey, 0, KEY_READ | KEY_WRITE,
        &hOpendKey) == ERROR_SUCCESS) {
    result = RegQueryValueEx(hOpendKey, _T("ComDB"), 0,
        &dwType, (LPBYTE)pBuffer, &nMaxLength);
    if (result != ERROR_SUCCESS)
      return;

    // バイナリのどの場所からクリアするか
    int start_index = com_number / 8;
    // 開始位置のビットクリアのためのフラグ
    int clear_bit = BitToClearFlag((com_number + 7) % 8);

    for (int i = start_index; i < nMaxLength; i++) {
      if (i == start_index)
        pBuffer[i] &= clear_bit;
      else
        pBuffer[i] = 0;
    }

    result = RegSetValueEx(hOpendKey, _T("ComDB"), 0, dwType,
        pBuffer, nMaxLength);

    RegCloseKey(hOpendKey);

    if (result != ERROR_SUCCESS)
      return FALSE;
  }
  return TRUE;
}

ただし,既に接続したことがあるデバイスはレジストリに情報が残っており前回接続時のポート番号が引き継がれてしまいます.例えば FTDI のチップの場合には下記のレジストリに情報が残っています.

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\FTDIBUS
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USB\VID_0403&PID_6001

この情報をクリアするために,RegDeleteKey によるレジストリの削除などを試みたのですが,権限がないのか,削除に失敗してしまいます.そこで今回はこの情報を削除するために,次節の DevCon というツールを利用しました.

DevCon

DevCon は WinDDK に含まれるデバッグ用のツールでデバイスマネージャーと同等の機能を持つコマンドラインユーティリティらしいです.このツールを利用すると,レジストリから残りの情報も削除できました.今回は FTDI の情報だけを削除すればよかったので,下記の ftdiclean というバッチを利用させていただきました.

ftdiclean:DevCon を使用して FTDI のデバイス情報をクリアするバッチ.:

https://github.com/mss/ftdiclean

DevCon Reference:

http://msdn.microsoft.com/en-us/library/ff544707(v=vs.85).aspx

日本語解説記事:

http://blogs.msdn.com/b/jpwdkblog/archive/2009/06/16/devcon-setupdi-api-devcon.aspx

下記の資料には,新しいデバイスを接続した時にポート番号がインクリメントしないようにする方法が載っています.
http://liionbms.com/pdf/DisableFTDI_Enumeration.pdf

下記の資料には FTDI のチップに対する対策が載っています.
http://www.ftdichip.com/Support/Documents/AppNotes/AN123How%20COM%20PortsAre%20Allocated%20on%20DriverInstallation.pdf