【テクニカル・上級編】Windows API(P/Invoke)の活用:VB.NETからOSの標準機能(タスクバーのプログレス表示やウィンドウの点滅)を制御する実践テクニック – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

Windows API(P/Invoke)がもたらすWindows Formsの極限進化:タスクバー制御とメモリ最適化の全技術

レガシーシステムの近代化、あるいは現場の業務効率を極限まで高めるデスクトップアプリケーション開発において、VB.NET(Windows Forms)はいまだに強力な選択肢であり続けている。

だが、フレームワーク標準のコントロールだけでUIを構築しようとすれば、すぐに壁にぶ当たる。
「重い処理の進捗を、ユーザーに直感的に伝えたい」
「バックグラウンドで処理が完了した際、タスクバーのアイコンを点滅させて注意を促したい」

これらは、Windows Formsのデフォルト機能だけでは実現できない。OSの深層、すなわちShell32やUser32といったネイティブAPI(Win32 API)の領域へ踏み込む必要がある。

本稿では、VB.NETからP/Invoke(Platform Invoke)を駆使してタスクバーのプログレス表示やウィンドウの点滅を制御する実践的なテクニックと、ガベージコレクション(GC)の裏側まで意識した極限のメモリ最適化手法を詳説する。

—

1. P/Invokeの本質とアーキテクチャの理解

VB.NETから非マネージコード(ネイティブDLL)を呼び出す際、私たちはマネージド世界(CLR)からアンマネージド世界(OSカーネル周辺)へと橋を渡る。この境界を超えるオーバーヘッドを最小限に抑え、メモリリークやハンドル枯渇を防ぐための設計がシニアエンジニアには求められる。

特にCOMインターフェースを扱う場合、オブジェクトのライフサイクル管理はデリケートだ。安易なラッパーの乱用は、プロセスの不安定化を招く。

タスクバー制御の要:`ITaskbarList3` インターフェース

Windows 7以降、タスクバーの進捗表示(Taskbar Progress)やオーバーレイアイコンの制御は、シェルが提供するCOMインターフェース `ITaskbarList3` を通じて行う。

これをVB.NETから直接叩くための構造体を定義する。

—

2. 実装:タスクバープログレスとウィンドウ点滅の完全コード

以下のコードは、外部DLLのインポート、COMオブジェクトの適切なマーシャリング、そしてメモリのライフサイクル管理を網羅したプロダクションクオリティの実装である。

Imports System.Runtime.InteropServices
Imports System.Text

Public NotInheritable Class TaskbarManager
‘ インスタンス化させないための静的クラス設計
Private Sub New()
End Sub

Region “Win32 API & COM Declarations”

‘ タスクバーの進捗状態フラグ
Public Enum TaskbarProgressBarState
NoProgress = &H0
Indeterminate = &H1
Normal = &H2
ErrorState = &H4
Paused = &H8
End Enum

‘ ITaskbarList3 インターフェースの GUID



Private Interface ITaskbarList3
‘ ITaskbarList のメソッド (v1-v2)
Sub HrInit()
Sub AddTab(hwnd As IntPtr)
Sub DeleteTab(hwnd As IntPtr)
Sub ActivateTab(hwnd As IntPtr)
Sub SetActiveAlt(hwnd As IntPtr)

‘ ITaskbarList3 の独自メソッド
Sub SetProgressValue(hwnd As IntPtr, ullCompleted As ULong, ullTotal As ULong)
Sub SetProgressState(hwnd As IntPtr, state As TaskbarProgressBarState)
Sub RegisterTab(hwndAs As IntPtr, hwndMDI As IntPtr)
Sub UnregisterTab(hwnd As IntPtr)
Sub SetTabOrder(hwnd As IntPtr, hwndInsertBefore As IntPtr)
Sub SetTabActive(hwnd As IntPtr, hwndMDI As IntPtr, tbatFlags As UInteger)
Sub SetThumbnailTooltip(hwnd As IntPtr, pszTip As String)
Sub SetThumbnailClip(hwnd As IntPtr, ByRef prcClip As IntPtr)
End Interface

‘ CoCreateInstance 用の CLSID

Private Shared Function CoCreateInstance(
ByRef rclsid As Guid,
pUnkOuter As IntPtr,
dwClsContext As UInteger,
ByRef riid As Guid,
ByRef ppv As Object
) As Integer
End Function

‘ ウィンドウ点滅(FlashWindowEx)用の構造体とAPI

Public Structure FLASHWINFO
Public cbSize As UInteger
Public hwnd As IntPtr
Public dwFlags As UInteger
Public uCount As UInteger
Public dwTimeout As UInteger
End Structure

Public Const FLASHW_STOP As UInteger = &H0
Public Const FLASHW_ROW As UInteger = &H1 ‘ キャプションを点滅
Public Const FLASHW_TRAY As UInteger = &H2 ‘ タスクバーボタンを点滅
Public Const FLASHW_ALL As UInteger = FLASHW_ROW Or FLASHW_TRAY
Public Const FLASHW_TIMER As UInteger = &H4 The flash continuously until another FLASHW_STOP
Public Const FLASHW_TIMERNOFG As UInteger = &HC


Private Shared Function FlashWindowEx(ByRef pfwi As FLASHWINFO) As Boolean
End Function

Private Shared ReadOnly ClsIdTaskbarList As New Guid(“56fdf344-fd6d-11d0-958a-006097c9a090”)
Private Shared ReadOnly IidTaskbarList3 As New Guid(“ea1afb91-9e28-4b86-90e9-9e9f7a5eefaf”)

End Region

Region “Public Methods”

”’

”’ タスクバーのプログレスバーを設定します。
”’

Public Shared Sub SetProgress(hwnd As IntPtr, current As ULong, total As ULong, state As TaskbarProgressBarState)
Dim taskbarList As ITaskbarList3 = Nothing
Try
Dim obj As Object = Nothing
Dim hr As Integer = CoCreateInstance(ClsIdTaskbarList, IntPtr.Zero, 1, IidTaskbarList3, obj)

If hr = 0 AndAlso obj IsNot Nothing Then
taskbarList = TryCast(obj, ITaskbarList3)
If taskbarList IsNot Nothing Then
taskbarList.SetProgressState(hwnd, state)
If state = TaskbarProgressBarState.Normal OrElse state = TaskbarProgressBarState.ErrorState OrElse state = TaskbarProgressBarState.Paused Then
taskbarList.SetProgressValue(hwnd, current, total)
End If
End If
End If
Catch ex As Exception
‘ ログ出力等のフォールバック処理
System.Diagnostics.Debug.WriteLine(ex.Message)
Finally
‘ COM オブジェクトの明示的解放
If taskbarList IsNot Nothing Then
Marshal.ReleaseComObject(taskbarList)
End If
End Try
End Sub

”’

”’ 指定されたウィンドウのタスクバーを点滅させます(注意喚起)。
”’

Public Shared Sub FlashWindow(hwnd As IntPtr, Optional count As UInteger = 3)
Dim fwi As New FLASHWINFO()
fwi.cbSize = CUInt(Marshal.SizeOf(fwi))
fwi.hwnd = hwnd
fwi.dwFlags = FLASHW_TRAY Or FLASHW_TIMERNOFG
fwi.uCount = count
fwi.dwTimeout = 0

FlashWindowEx(fwi)
End Sub

End Region

End Class

—

3. シニアエンジニアが知るべきメモリ最適化とリソース管理の極意

上記のコードにおいて、最も注目すべきは `Finally` ブロックにおける `Marshal.ReleaseComObject(taskbarList)` の存在だ。

なぜマネージド言語で明示的な解放が必要なのか?

VB.NET(.NET Framework / .NET Core / .NET 5+)はガベージコレクタ(GC)がメモリを自動管理する。しかし、COMオブジェクトはマネージドヒープの外(アンマネージド領域)に実体が存在する。
GCは「マネージドのラッパーオブジェクト」しか回収しないため、裏で動いているCOMコンポーネントへの参照カウントが残ったままになり、プロセス内にゾンビのようなリソースが蓄積していく。これが「COMオブジェクトに起因するメモリリーク」の正体である。

特に、非同期ループやタイマーイベント内で頻繁にタスクバーの進捗を更新する設計にする場合、毎回の呼び出しでCOMインスタンスを生成・破棄していると、参照カウントのリークやGCのプレッシャー(Gen 2への昇格多発)によるカクつき(UIスレッドの停止)を引き起こす。

極限の最適化:インスタンスのキャッシュ

パフォーマンスを極限まで高める本番環境では、`ITaskbarList3` のインスタンスをフォームのライフサイクル中(またはアプリケーションスコープ)で1度だけ生成・保持し、使い回すべきである。

‘ クラスレベルでの保持例
Private _taskbarList As ITaskbarList3

Private Sub InitializeTaskbar()
Dim obj As Object = Nothing
‘ 初期化時に一度だけバインド
If CoCreateInstance(ClsIdTaskbarList, IntPtr.Zero, 1, IidTaskbarList3, obj) = 0 Then
_taskbarList = TryCast(obj, ITaskbarList3)
_taskbarList?.HrInit()
End If
End Sub

‘ フォーム終了時やアプリケーション終了時に必ず解放
Private Sub CleanupTaskbar()
If _taskbarList IsNot Nothing Then
Marshal.ReleaseComObject(_taskbarList)
_taskbarList = Nothing
End If
End Sub

—

4. レガシー環境・システム間連携における実戦的注意点

1. スレッドセーフティの担保
Windows FormsのUIスレッド以外からタスクバーやウィンドウハンドル(`Handle`)を操作すると、クロススレッド例外や予期せぬCOMExceptionが発生する。必ず `Invoke` または `BeginInvoke` を経由してUIスレッドからAPIを叩くこと。
2. OSバージョンの差異(フォールバック)
`ITaskbarList3` はWindows 7以降でなければ動作しない。もしレガシーなWindows Server 2008(Vistaベース)やそれ以前の環境が混在する極限のエンタープライズ環境では、OSのメジャーマイナーバージョンを判定し、機能が存在しない場合は例外を握りつぶす(あるいは何もしない)防御的プログラミングが必須となる。

If Environment.OSVersion.Version.Major >= 6 Then
‘ 安全にタスクバー制御を実行
End If

—

5. 結言

Windows Formsは「古い技術」とやゆされることがある。だが、それは使い手の技量がフレームワークの標準機能に縛られている場合に限った話だ。

P/Invokeを自在に操り、OSの心臓部であるWin32 APIやCOMとダイレクトに対話することで、VB.NETによるデスクトップアプリケーションは、モダンなWeb技術や最新のUIフレームワークに勝るとも劣らない、圧倒的なレスポンスと洗練されたユーザー体験を実現できる。

アーキテクトとしての誇りを持ち、メモリの隅々まで支配下に置いた堅牢なコードを書き上げてほしい。

タイトルとURLをコピーしました