背景與前提
- 適用於 Windows 系統已建立「寬帶連線」(PPPoE)撥號項目。
- 基於 `rasapi32.dll`、`wininet.dll` 非托管 API,透過 P/Invoke 調用。
- 封裝為
RASDisplay 類,提供連線、斷線、狀態查詢、流量統計功能。
核心結構體定義
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct RASCONN
{
public int dwSize;
public IntPtr hrasconn;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 257)] public string szEntryName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 17)] public string szDeviceType;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 129)] public string szDeviceName;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct RASSTATS
{
public int dwSize;
public int dwBytesXmited; // 發送位元組
public int dwBytesRcved; // 接收位元組
public int dwFramesXmited;
public int dwFramesRcved;
public int dwCrcErr;
public int dwTimeoutErr;
public int dwConnectionDuration; // 連線持續時間(毫秒)
// ... 其他統計欄位
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct RASENTRYNAME
{
public int dwSize;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 257)] public string szEntryName;
}
P/Invoke 簽名
internal static class NativeMethods
{
[DllImport("rasapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern int RasEnumConnections(
ref RASCONN lprasconn, ref int lpcb, ref int lpcConnections);
[DllImport("rasapi32.dll", CharSet = CharSet.Auto)]
internal static extern uint RasGetConnectionStatistics(
IntPtr hRasConn, [In, Out] ref RASSTATS lpStatistics);
[DllImport("rasapi32.dll", CharSet = CharSet.Auto)]
internal static extern uint RasHangUp(IntPtr hrasconn);
[DllImport("rasapi32.dll", CharSet = CharSet.Auto)]
internal static extern uint RasEnumEntries(
string reserved, string lpszPhonebook,
[In, Out] RASENTRYNAME[] lprasentryname,
ref int lpcb, out int lpcEntries);
[DllImport("wininet.dll", CharSet = CharSet.Auto)]
internal static extern int InternetDial(
IntPtr hwnd, string lpszConnectoid,
uint dwFlags, ref int lpdwConnection, uint dwReserved);
}
高層封裝類:RASDisplay
public class RASDisplay
{
private RASCONN _currentConn;
private RASSTATS _stats;
private bool _connected;
public RASDisplay()
{
RefreshStatus();
}
/// 重新掃描當前撥號連線狀態
public void RefreshStatus()
{
_connected = false;
var conn = new RASCONN { dwSize = Marshal.SizeOf(typeof(RASCONN)) };
int cb = conn.dwSize, count = 0;
int ret = NativeMethods.RasEnumConnections(ref conn, ref cb, ref count);
if (ret != 0 || count == 0) return;
_currentConn = conn;
var stats = new RASSTATS { dwSize = Marshal.SizeOf(typeof(RASSTATS)) };
NativeMethods.RasGetConnectionStatistics(conn.hrasconn, ref stats);
_stats = stats;
_connected = true;
}
/// 撥號連線(entryName 為「網路連線」中顯示的名稱)
public int Connect(string entryName)
{
const uint INTERNET_AUTODIAL_UNATTENDED = 2;
int connHandle = 0;
return NativeMethods.InternetDial(IntPtr.Zero, entryName,
INTERNET_AUTODIAL_UNATTENDED, ref connHandle, 0);
}
/// 掛斷當前連線
public void Disconnect()
{
if (_connected)
NativeMethods.RasHangUp(_currentConn.hrasconn);
}
// --- 唯讀屬性 ---
public bool IsConnected => _connected;
public string EntryName => _connected ? _currentConn.szEntryName : "";
public string Duration => _connected ? FormatDuration(_stats.dwConnectionDuration) : "";
public long BytesSent => _connected ? _stats.dwBytesXmited : 0;
public long BytesReceived => _connected ? _stats.dwBytesRcved : 0;
public string[] AllEntries => GetAllEntryNames();
private string FormatDuration(int ms)
{
int s = ms / 1000;
int h = s / 3600; s %= 3600;
int m = s / 60; s %= 60;
return $"{h}h {m}m {s}s";
}
private string[] GetAllEntryNames()
{
int count = 1, size = Marshal.SizeOf(typeof(RASENTRYNAME));
var entries = new RASENTRYNAME[count];
entries[0].dwSize = size;
NativeMethods.RasEnumEntries(null, null, entries, ref size, out count);
if (count > entries.Length)
{
entries = new RASENTRYNAME[count];
for (int i = 0; i < count; i++) entries[i].dwSize = size;
NativeMethods.RasEnumEntries(null, null, entries, ref size, out count);
}
return entries.Take(count).Select(e => e.szEntryName).ToArray();
}
}
使用範例
var ras = new RASDisplay();
Console.WriteLine($"現有撥號項目: {string.Join(", ", ras.AllEntries)}");
if (!ras.IsConnected)
{
int ret = ras.Connect("ADSL"); // "ADSL" 為撥號連線名稱
Console.WriteLine(ret == 0 ? "撥號成功" : $"撥號失敗,錯誤碼: {ret}");
}
else
{
Console.WriteLine($"已連線: {ras.EntryName}, 時長: {ras.Duration}");
Console.WriteLine($"上傳: {ras.BytesSent / 1024} KB, 下載: {ras.BytesReceived / 1024} KB");
// ras.Disconnect(); // 斷線
}
注意事項
- 需以**管理員權限**運行,否則 `RasHangUp` 可能拒絕存取。
- 撥號項目名稱需與「網路連線」中顯示完全一致(區分大小寫)。
- `InternetDial` 為同步阻塞呼叫,UI 程式建議放入背景執行緒。
- 現代系統建議使用 `DotRas` 開源庫,提供更完整的托管封裝與異步支援。