建立網路共享資料夾需呼叫 NetShareAdd,但該函數在 **Windows 9x 位於 `svrapi.dll`**,**Windows NT/2000/XP 位於 `netapi32.dll`**,參數結構截然不同。本文採用 **`LoadLibrary` + `GetProcAddress` 動態載入** 策略,單一執行檔同時支援 9x 與 NT 系平台,並提供完整錯誤碼處理與權限提升代碼。
| 平台 | DLL | 層級 | 結構 | 關鍵欄位 |
|---|---|---|---|---|
| Win9x | svrapi.dll | 50 | share_info_50 | ansi 字串、密碼欄位分讀寫/唯讀 |
| WinNT/2k+ | netapi32.dll | 2 | SHARE_INFO_2 | Unicode (LPWST)、權限位元組、最大連線數 |
// 型別定義
typedef DWORD (WINAPI *PFN_NETSHAREADD_9X)(LPCSTR, short, LPCSTR, unsigned short);
typedef DWORD (WINAPI *PFN_NETSHAREADD_NT)(LPWSTR, DWORD, LPBYTE, LPDWORD);
PFN_NETSHAREADD_9X g_pNetShareAdd9x = nullptr;
PFN_NETSHAREADD_NT g_pNetShareAddNt = nullptr;
HMODULE g_hShareDll = nullptr;
bool g_bIsNT = false;
// 初始化:偵測平台並載入 DLL
bool InitShareApi() {
OSVERSIONINFO vi = {sizeof(vi)};
GetVersionEx(&vi);
g_bIsNT = (vi.dwPlatformId == VER_PLATFORM_WIN32_NT);
g_hShareDll = LoadLibrary(g_bIsNT ? L"netapi32.dll" : L"svrapi.dll");
if (!g_hShareDll) return false;
if (g_bIsNT) {
g_pNetShareAddNt = (PFN_NETSHAREADD_NT)GetProcAddress(g_hShareDll, "NetShareAdd");
return g_pNetShareAddNt != nullptr;
} else {
g_pNetShareAdd9x = (PFN_NETSHAREADD_9X)GetProcAddress(g_hShareDll, "NetShareAdd");
return g_pNetShareAdd9x != nullptr;
}
}
// 統一介面
AnsiString CreateShare(const AnsiString& Path, const AnsiString& ShareName,
const AnsiString& Password, const AnsiString& Remark)
{
if (!InitShareApi()) return "載入 DLL 失敗";
if (!g_bIsNT) { // -------- Win9x --------
share_info_50 si50 = {0};
strncpy(si50.shi50_netname, ShareName.c_str(), LM20_NNLEN);
si50.shi50_type = STYPE_DISKTREE;
si50.shi50_flags = SHI50F_FULL;
si50.shi50_remark = Remark.c_str();
si50.shi50_path = Path.c_str();
strncpy(si50.shi50_rw_password, Password.c_str(), SHPWLEN);
strncpy(si50.shi50_ro_password, Password.c_str(), SHPWLEN);
DWORD err = g_pNetShareAdd9x(nullptr, 50, (LPCSTR)&si50, sizeof(si50));
FreeLibrary(g_hShareDll);
return (err == NERR_Success) ? "共建立成功" : "錯誤碼: " + IntToStr(err);
}
else { // -------- WinNT/2k/XP --------
// 提升權限:SeSecurityPrivilege / SeCreatePermanentPrivilege
HANDLE hToken; TOKEN_PRIVILEGES tp;
OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
LookupPrivilegeValue(nullptr, SE_SECURITY_NAME, &tp.Privileges[0].Luid);
tp.PrivilegeCount = 1; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken, FALSE, &tp, 0, nullptr, 0);
SHARE_INFO_2 si2 = {0};
si2.shi2_netname = WideString(ShareName).c_bstr();
si2.shi2_remark = WideString(Remark).c_bstr();
si2.shi2_path = WideString(Path).c_bstr();
si2.shi2_passwd = WideString(Password).c_bstr(); // 空字串 = 無密碼
si2.shi2_type = STYPE_DISKTREE;
si2.shi2_permissions = ACCESS_ALL; // 0 = 全部權限
si2.shi2_max_uses = (DWORD)-1; // 無限制
si2.shi2_current_uses = 0;
DWORD paramErr = 0;
DWORD err = g_pNetShareAddNt(nullptr, 2, (LPBYTE)&si2, ¶mErr);
FreeLibrary(g_hShareDll);
switch (err) {
case NERR_Success: return "建立成功";
case ERROR_ACCESS_DENIED: return "拒絕存取 (需管理員)";
case NERR_DuplicateShare: return "共享名稱已存在";
case NERR_UnknownDevDir: return "路徑不存在";
default: return "錯誤碼: " + IntToStr(err);
}
}
}
| 錯誤碼 | 常見原因 | 解決方式 |
|---|---|---|
| ERROR_ACCESS_DENIED (5) | 非管理員 / 無 SeSecurityPrivilege | 以管理員身分執行、啟用 UAC 虛擬化、加入 manifest requireAdministrator |
| NERR_DuplicateShare (2118) | 同名共享已存在 | 先呼叫 NetShareDel 刪除、或改用新名稱 |
| NERR_UnknownDevDir (2116) | 路徑不存在或非本機磁碟 | 確認路徑存在、為 NTFS、非映射磁碟機 |
| ERROR_INVALID_PARAMETER (87) | 結構填寫錯誤 (Unicode/ANSI 混用) | 嚴格區分 9x 用 ANSI、NT 用 Unicode (WideString/BSTR) |
NetShareDel,9x 層級 0,NT 層級 1。NetShareEnum 可取得現有共享清單,避免重複建立。NetShareAdd 層級 502/1005 (支援 SMB 簽名、加密、CA 憑證),或透過 WMI `Win32_Share.Create`。透過 `LoadLibrary`/`GetProcAddress` 動態綁定 `NetShareAdd`,配合平台專用結構體與權限提升,即可在單一執行檔中實現 Windows 9x 至現代 Windows 10/11 的跨版本共享資料夾建立功能。此技術對維護舊系統相容性、撰寫系統管理工具極具實務價值。