misc.log

日常茶飯事とお仕事と

WindowsAPI/WNetAddConnection2とWNetCancelConnection2の動作

1台のPC上で動く複数のアプリによる共有フォルダアクセスについてメモ。OSはWindows XP

共有フォルダに対してネットワークドライブを割り当てられるWNetAddConnection2と、割り当てを解除(切断)するWNetCancelConnection2の動作について確認しました。コードの信頼性等については保障できませんので、もし参考にされる奇特な方がいらっしゃいましたら各自確認願います(Visual Studio 2008/VB.NET利用)。

確認はすべて「1台のパソコンからもう1台の共有フォルダを見る」という方針で行っています。

結論

  • 別ユーザー名義で動くプログラムであれば、ネットワークドライブ名が重複していても全く問題無く、独立して接続状態を操作できる。
  • 同一ユーザー名義で動くプログラムの場合、1プログラム、複数プログラムを問わず、ネットワークドライブ割り当てに関する制約が発生する。
    • 1共有フォルダへの複数リモートアカウントでの接続はできない(あとから接続した方がエラーになる)。
    • (当然のことながら)同一のネットワークドライブ名での接続はできない(あとから接続した方がエラーになる)。

接続

  • 1プログラムが、同一のリモートアカウントで同一の共有フォルダに同一ドライブ名で接続。
    • 「(Error 85) ローカル デバイス名は既に使用されています。」が発生。
  • 1プログラムが、同一のリモートアカウントで同一の共有フォルダに別のドライブ名で接続。
    • 問題無く割り当て完了(たとえば、共有フォルダAにドライブWとZを割り当て)。
  • 1プログラムが、複数のリモートアカウントで同一の共有フォルダに別のドライブ名で接続。
    • 「(Error 1219) 同じユーザーによる、サーバーまたは共有リソースへの複数のユーザー名での複数の接続は許可されません。サーバーまたは共有リソースへの以前の接続をすべて切断してから、再試行してください。」が発生。エラーが戻るまで数十秒かかる。
  • 2プログラムが、同一のリモートアカウントで同一の共有フォルダに同一ドライブ名で接続。
    • 「(Error 85) ローカル デバイス名は既に使用されています。」が発生。
  • 2プログラムが、同一のリモートアカウントで同一の共有フォルダに別のドライブ名で接続。
    • 問題無く割り当て完了(たとえば、共有フォルダAにドライブWとZを割り当て)。
  • 2プログラムが、複数のリモートアカウントで同一の共有フォルダに別のドライブ名で接続。
    • 「(Error 1219) 同じユーザーによる、サーバーまたは共有リソースへの複数のユーザー名での複数の接続は許可されません。サーバーまたは共有リソースへの以前の接続をすべて切断してから、再試行してください。」が発生。エラーが戻るまで数十秒かかる。
  • 同一PCの別アカウントで動く2プログラム*1が、同一の共有フォルダに接続。
    • ドライブ名、アカウントの如何に関わらず、問題無く割り当て完了。

切断

  • 切断については、同一ユーザーによる1つ以上のプログラムでの接続が、「同一リモートアカウントで、ネットワークドライブ名を変えた場合だけ重複接続可能」であることから、どこかを切断すると予期しない別の接続切れるというケースは無い。
  • ただ、MSDNにある「切断先としてドライブ名ではなくリモートリソース名(\\192.168.0.10\Test1 など)を指定した場合、そこへの全接続が切断されるというケースはあるのかもしれない……が、実際上記のような文字列を指定して切断した場合、「(Error 2250) このネットワーク接続はありません。」が発生して切断できなかった。

作って覚える Visual Basic 2017 デスクトップアプリ入門

作って覚える Visual Basic 2017 デスクトップアプリ入門

サンプルとして使ったコード(抜粋)

WNetAddConnection2に渡すデータ構造用の構造体宣言は下記。

''' <summary>
''' WNetAddConnection2用の構造体定義
''' </summary>
''' <remarks></remarks>
Public Structure NETRESOURCE
    Public dwScope As Integer
    Public dwType As Integer
    Public dwDisplayType As Integer
    Public dwUsage As Integer
    Public LocalName As String
    Public RemoteName As String
    Public Comment As String
    Public Provider As String
End Structure

接続処理の呼び出しは下記。

    Declare Function WNetAddConnection2 Lib "mpr.dll" Alias "WNetAddConnection2A" (ByRef netResource As NETRESOURCE, ByVal password As String, ByVal Username As String, ByVal Flag As Integer) As Integer
    Declare Function WNetCancelConnection2 Lib "mpr.dll" Alias "WNetCancelConnection2A" (ByVal lpName As String, ByVal dwFlags As Long, ByVal fForce As Long) As Integer

    'WNetAddConnection2用の定数定義
    Private Const RESOURCE_CONNECTED As Long = &H1
    Private Const RESOURCETYPE_ANY As Long = &H0
    Private Const RESOURCEDISPLAYTYPE_SHARE As Long = &H3
    Private Const CONNECT_UPDATE_PROFILE As Long = &H1  'これはWNetCancelConnection2と共用


    ''' <summary>
    ''' ネットワークドライブとしてリソースに接続する
    ''' </summary>
    ''' <param name="RemoteName">リモートリソースをサーバー名からのURIで指定。</param>
    ''' <param name="LocalName">ローカルで割り当てるドライブ名を指定("Z:"など)。</param>
    ''' <param name="username">接続に用いるユーザー名。</param>
    ''' <param name="password">接続に用いるパスワード。</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function ConnectDrive(ByVal RemoteName As String, ByVal LocalName As String, _
                                 Optional ByVal username As String = Nothing, _
                                 Optional ByVal password As String = Nothing)

        Dim myResource As NETRESOURCE

        Try
            '接続
            Dim returnValue As Integer
            myResource.dwScope = 2
            myResource.dwType = 1
            myResource.dwDisplayType = RESOURCEDISPLAYTYPE_SHARE
            myResource.LocalName = LocalName
            myResource.RemoteName = RemoteName
            myResource.dwUsage = Nothing
            myResource.Comment = Nothing
            myResource.Provider = Nothing

            returnValue = WNetAddConnection2(myResource, password, username, 0)

            '結果判定
            If returnValue <> 0 Then
                Dim errorMsg = New System.ComponentModel.Win32Exception(returnValue).Message
                Dim resultMsg As String = RemoteName & "に接続できません。 エラーメッセージ: " & vbNewLine & vbNewLine & "(Error " & returnValue & ") " & errorMsg
                Throw New System.ApplicationException(resultMsg)
            End If

            Return returnValue

        Catch ex As System.ApplicationException
            'ApplicationExceptionの場合→スルー
            Throw

        Catch ex As Exception
            'エラー発生時
            Dim errorMsg As String = RemoteName & "に接続できません。 エラーメッセージ: " & vbNewLine & vbNewLine & ex.Message
            Throw New System.ApplicationException(errorMsg, ex)
        End Try

    End Function

切断処理は下記。

    ''' <summary>
    ''' ネットワークドライブを切断する。
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function DisconnectDrive(ByVal targetName As String)

        Try
            '切断
            Dim returnValue As Integer
            returnValue = WNetCancelConnection2(targetName, CONNECT_UPDATE_PROFILE, -1)

            '結果判定
            If returnValue <> 0 Then
                Dim errorMsg = New System.ComponentModel.Win32Exception(returnValue).Message
                Dim resultMsg = targetName & " を切断できません。 エラーメッセージ: " & vbNewLine & vbNewLine & "(Error " & returnValue & ") " & errorMsg
                Throw New System.ApplicationException(resultMsg)
            End If

            Return returnValue

        Catch ex As System.ApplicationException
            'ApplicationExceptionの場合→スルー
            Throw

        Catch ex As Exception
            'エラー発生時
            Dim errorMsg As String = targetName & " を切断できません。 エラーメッセージ: " & vbNewLine & vbNewLine & ex.Message
            Throw New System.ApplicationException(errorMsg, ex)
        End Try

    End Function

*1:具体的には、XP上に作った2つのユーザーで、ユーザーを切り替えながらそれぞれプログラムを起動。