시스템을 개발하다 보면 종종 네트워크 드라이브를 액세스해야 할 때가 있습니다. 대개의 프로젝트에서 파일 업로드 기능을 구현할 때 로컬 하드 디스크나 SAN(Storage Area Network) 디스크와 같이 로컬 디스크와 동등한 저장소에 업로드 된 파일을 저장하기도 하지만 많은 경우, 별도의 스토리지 서버에 저장하는 경우가 많죠. 이때 스토리지가 NAS(Network Area Strorage)와 같은 윈도우의 파일 공유 메카니즘을 이용하는 경우, 프로그램은 네트워크 연결을 수행해야 합니다. 지난주에 제가 참여하는 프로젝트에서 요구된
프로그램적으로 이 네트워크 연결을 수행하는 방법에 대해 주절거려 볼까 합니다. 대부분 잘 아시겠지만... 혹 아직 모르시는 분들을 위해...
Working with Shared Folder...
폴더를 공유함으로써 네트워킹을 한다는 것이 초창기 NOS(Network Operating System)의 모토이자 지금도 많이 사용되는 통신 기법 중 하나이다. 폴더를 공유하거나 네트워크 드라이브를 만드는 것에 관련된 닷넷 환경의 API는 무엇인가?
필자가 아는 한 관련된 닷넷 네임스페이스나 클래스는 없다(있다면 알려주기 바람 -_-). 이상 블로그 끝~~ 텨텨텨
WNet API
대부분 그러하지만 닷넷에 관련 클래스가 없다면, 해결법은 Interop 밖에 없다. Win32 API에는 틀림없이 관련 API 가 있을 것이다. 하지만... MSDN을 막연히 디비볼려면 거의 죽음에 가깝다고 볼 수 있다. 짭밥과 통밥을 이용하여 MSDN을 노려보면 Networking & Directory Service 라는 최상위 레벨의 항목을 찾을 수 있고 그 하부에 Network Management 항목을 찾을 수 있다. 이 항목 아래에는 NetShell, SNMP, Network Management 등의 항목들이 있고 그 중 Windows Networking (WNet) 이란 녀석을 찾을 수 있다. 요 녀석이 Windows 3.1 시절부터 지금까지 내려오는 윈도우의 네트워크 드라이브에 관련된 WIN32 API 집합이 되겠다.
WNet API들은 파일 서버(파일 공유를 제공하는 컴퓨터, PC나 노트북일 수도 있겠지?)에서 제공하는 공유 폴더에 접근하기 위한 네트워크 연결에 대한 다양한 API들을 제공한다. 예를 들면, 네트워크 드라이브 연결시 나타나는 다음과 같은 다이얼로그 박스를 나타내는 API, 이러한 UI 없이 네트워크 연결을 수행해주는 API, 연결되어 있는 네트워크 자원을 열거해 주는 API 등등 유용한 것이 많다.

WNetUseConnection() function
우리가 필요한 것은 ASP.NET 코드나 기타 프로그램에서 사용자의 관여 없이 미리 지정된 서버의 공유 폴더에 접근하고 싶은 것이다. 이때 사용할 수 있는 API는 WNetAddConnection, WNetAddConnection2, WNetAddConnection3, WNetUseConnection 등이 있다. WNetAddConnection 류의 API는 반드시 D:, E: 류의 드라이브명 (API 문서에는 Local Name 이라 부르고 있다)을 명시해 주어야 하며 WNetUseConnection은 드라이브 명을 자동으로 설정하는 옵션을 가질 수 있다는 점이 상이할 뿐 비슷한 기능을 가진다.
고로 이 글에서는 WNetUserConnection을 사용하는 방법만 설명할 것이다. 이쯤되면 Interop을 좀 해본 독자라면 바로 구글에 가서 WNetUseConnection을 호출하는 닷넷 Interop 코드를 검색할 것이다. 그렇다. 이 Win32 API를 닷넷에서 호출할 수 있도록 P-Invoke(Platform Invoke) 코드만 제공하면 땡 인 것이다.
WNetUserConnection을 닷넷에서 호출할 수 있다면, 이 함수를 통해 파일 서버의 공유 폴더를 연결하고, 해당 공유 폴더에 단순한 파일 연산(읽기, 쓰기, 복사, 삭제 등등)을 수행하면 된다. 일단 연결되고 나면, \\ServerName\FolderName\File.ext 류의 UNC 이름을 직접 파일 경로로 줄 수도 있으며, 네트워크 드라이브 이름을 주었다면 Z:\\File.ext 의 파일 시스템 경로를 주어서 액세스할 수도 있다.
P-Invoke 코드 작성
WIN32 API가 있으면 뭐하나? WIN32 API를 전혀 모르는 사람(대부분이 여기에 해당할 것이다)이라면 그림의 떡인데... 하지만 닷넷은 P-Invoke란게 있지 않은가? 이를 통해 닷넷에서 WIN32 API를 호출하는 것은 어렵지 않다 !
이제 WNetUseConnection 을 닷넷에서 호출하기 위한 P-Invoke 관련 코드를 작성해 보자. 필요한 것은 WNetUseConnection 함수에 대한 P-Invoke 선언(extern 선언)이 필요하며, 이 함수가 매개변수로 취하는 구조체에 대한 닷넷 선언이 필요하다.
NETRESOURCE Structure
구글에서 Interop 코드를 찾았다면 상황 종료이지만, 직접 P-Invoke 코드를 만들어 보는 것도... 나쁘지 않을 듯하다. 앞서 언급한 API들은 NETRESOURCE 라는 구조체(structure)를 매개변수로 사용한다. 다른 정수형 매개변수나 문자열을 어케 해보겠는데 이렇게 구조체가 떡 나타나면 대략 골치가 아프기 마련이다. 다행이도 NETRESOURCE는 그다지 복잡한 구조체가 아니여서 어렵지 않게 선언을 할 수 있다. 다음은 필자의 NETRESOURCE 구조체 선언이다. (당근 C# 코드이다. VB.NET 개발자라면 요것을 수정해 보든가 구글에서 검색을 해보길...)
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
public struct NETRESOURCE
{
public uint dwScope;
public uint dwType;
public uint dwDisplayType;
public uint dwUsage;
public string lpLocalName;
public string lpRemoteName;
public string lpComment;
public string lpProvider;
}
ANSI, UNICODE 모두 사용 가능하도록 CharSet을 Auto로 주었으며, DWORD (double word, 즉 32비트 부호 없는 정수)를 위해 uint 타입을 사용했다는 점을 빼면 너무 심플해서 설명하기 난감할 정도다.
WNetUseConnection method Declaration
WNetUseConnection API에 대한 닷넷 메쏘드 선언은 extern 키워드와 DllImportAttribute 를 이용해 선언하면 된다.
[DllImport("mpr.dll", CharSet=CharSet.Auto)]
public static extern int WNetUseConnection(
IntPtr hwndOwner,
[MarshalAs(UnmanagedType.Struct)] ref NETRESOURCE lpNetResource,
string lpPassword,
string lpUserID,
uint dwFlags,
StringBuilder lpAccessName,
ref int lpBufferSize,
out uint lpResult);
각 매개변수에 필요한 값들은 MSDN을 참고하기 바란다. 이것까지 설명해 달라는 것은 대략 도둑넘 심보다.
Caller Code Example
이제 WNetUseConnection 메쏘드를 호출하는 코드만 작성하면 끝이다.
int capacity = 64;
uint resultFlags = 0;
uint flags = 0;
System.Text.StringBuilder sb = new System.Text.StringBuilder(capacity);
NETRESOURCE ns = new NETRESOURCE();
ns.dwType = 1; // 공유 디스크
ns.lpLocalName = null; // 로컬 드라이브 지정하지 않음
ns.lpRemoteName = @"\\fileserver\sharefoldername";
ns.lpProvider = null;
int result = WNetUseConnection(IntPtr.Zero, ref ns, "password", "userid", flags,
sb, ref capacity, out resultFlags);
위 코드는 공유 폴더에 대한 네트워크 연결을 만들며 로컬 드라이브 명을 명시하지 않았다. result 변수 값이 0 이면 오류가 없는 것이며, 0 이 아닌 값은 오류가 발생했음을 알리는 오류 코드이다. 공유 폴더 경로에 오류가 있는 경우, 1203 오류 코드가, 사용자/암호가 일치 하지 않다면 1326 오류 코드가 발생한다.
명시적으로 로컬 드라이브를 지정하고 싶다면 NETRESOURCE 구조체의 lpLocalName 필드에 Z:, Y: 등의 값을 설정할 수 있다. 이 경우, 이미 네트워크 드라이브 사용 중이라면 오류 코드 85 를 반환할 것이다. 만약, 사용 가능한 로컬 드라이브 (Z, Y, X, W, 등의 순서) 이름의 선택을 시스템에게 맞기고 싶다면 flags 값에 0x80 을 주면 시스템이 적절한 로컬 드라이브 이름을 선택해 줄 것이다. 그리고 그 드라이브 이름은 StringBuilder 를 통해 값이 반환된다. flags 가 0x80 이 아닌 값이 사용되면 StringBuilder는 일반적으로 공유 폴더의 UNC 이름을 반환한다. 따라서 capacity 값은 공유 폴더의 경로를 담을 수 있도록 충분히 주어야 한다. 그렇지 않으면 오류 코드 234를 반환할 것이다.
더 상세한 WNetUseConnection 함수에 대한 정보는 MSDN을 참고하기 바란다. 이만큼 설명한 것도 오버한 것이 아닌가 싶다... -_-
짧은 포스트는 없는가?
이번 포스트만은 짧게 할려고 했는데... 왤케 주저리 주저리 길게 써 대는지... 실은 네트워크 연결을 끊는 API 인 WNetCancelConnection 에 대해서도 몇마디 쓸려다가 꾹~~ 참았다. 함수가 그다지 어렵지 않을 뿐더러 P-Invoke 선언 역시 간단한 매개변수로서 선언이 그다지 어렵지 않기 때문이기도 하지만 길어지는 포스트의 압박에...