On Windows, how can I find window handles for a given process ID using Perl?

Let’s say you want to send some messages to the window of a Windows process, but all you have is the process id. How do you find the window handle for that process?

According to this post on Stackoverflow, you need to use the EnumWindows function to go through each Window, look up its process id using GetWindowThreadProcessId function, and compare it to the process id whose window handle you are seeking.


GetWindowThreadProcessId takes two parameters: A window handle and a pointer to a location where the function can store the process id. It returns the thread id.

To call this function, we are going to use the trust Win32::API module:

Win32::API->Import(qw(User32 GetWindowThreadProcessId NP N));


The first parameter of EnumWindows is a callback we supply. The function calls our callback for each window it finds. The second parameter is a value that will be passed to our callback function. We can import EnumWindows into our namespace simply by using the following statement:

Win32::API->Import(qw(User32 EnumWindows KN I));

EnumWindowsProc callback function

The callback function EnumWindows needs takes two parameters: The first is the window handle, and the second is the value we provided to EnumWindows as its second parameter.

We will use that second parameter to supply the process id for which we are trying to locate the window handle. Within the body of the callback, we’ll find the process id for the given window handle using GetWindowThreadProcessId, and compare it to the one passed in to our callback. If the two are equal, then we save the window handle and continue enumerating.

To specify the callback function, we use Win32::API::Callback.

Putting everything together, we have the following quick script that will give us the window handles for a process id specified on the command line:

#!/usr/bin/env perl

use strict; use warnings;

use Carp qw( croak );
use Win32::API;
use Win32::API::Callback;

Win32::API->Import(qw(User32 GetWindowThreadProcessId NP N));
Win32::API->Import(qw(User32 EnumWindows KN I));

croak "Need pid\n" unless @ARGV;

for my $hwnd (find_hwnd_for_pid(@ARGV)) {
    printf "%X\n", $hwnd;

sub find_hwnd_for_pid {
    my ($wanted_pid) = @_;
    my @wanted_hwnd;

        Win32::API::Callback->new(sub {
            my ($hwnd, $wanted_pid) = @_;

            my $pid = 0xffffffff;
            my $tid = GetWindowThreadProcessId($hwnd, $pid);

            $pid = unpack 'L', $pid;

            if ($wanted_pid == $pid) {
                push @wanted_hwnd, $hwnd;
            return 1;
        }, 'NN', 'I'),

    return @wanted_hwnd;

Trying this out on my system, I got five handle values for a GVim window one of which WinSpy++ identified as the main window. Obviously, one would then choose which one to interact with based on other criteria. For things like console windows and notepad, I always got a single handle back.