windows: add GetProcAddressByOrdinal

The current GetProcAddress implementation only resolves functions in
DLLs by name. Add GetProcAddressByOrdinal that allows resolving
functions by ordinal number, using the same GetProcAddress call from
kernel32.dll in the background.

This is particularly useful for some functions (e.g. IsOS from
shlwapi.dll in some older versions of Windows) that cannot be found by
name.

Fixes golang/go#16507

Change-Id: Ib5fba7568c365a0aa2491c1261876b3a3929ec3d
Reviewed-on: https://go-review.googlesource.com/70690
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
Run-TryBot: Alex Brainman <alex.brainman@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/windows/syscall_test.go b/windows/syscall_test.go
index 62588b9..d7009e4 100644
--- a/windows/syscall_test.go
+++ b/windows/syscall_test.go
@@ -7,6 +7,7 @@
 package windows_test
 
 import (
+	"syscall"
 	"testing"
 
 	"golang.org/x/sys/windows"
@@ -31,3 +32,22 @@
 	// make sure TESTENV gets set to "", not deleted
 	testSetGetenv(t, "TESTENV", "")
 }
+
+func TestGetProcAddressByOrdinal(t *testing.T) {
+	// Attempt calling shlwapi.dll:IsOS, resolving it by ordinal, as
+	// suggested in
+	// https://msdn.microsoft.com/en-us/library/windows/desktop/bb773795.aspx
+	h, err := windows.LoadLibrary("shlwapi.dll")
+	if err != nil {
+		t.Fatalf("Failed to load shlwapi.dll: %s", err)
+	}
+	procIsOS, err := windows.GetProcAddressByOrdinal(h, 437)
+	if err != nil {
+		t.Fatalf("Could not find shlwapi.dll:IsOS by ordinal: %s", err)
+	}
+	const OS_NT = 1
+	r, _, _ := syscall.Syscall(procIsOS, 1, OS_NT, 0, 0)
+	if r == 0 {
+		t.Error("shlwapi.dll:IsOS(OS_NT) returned 0, expected non-zero value")
+	}
+}
diff --git a/windows/syscall_windows.go b/windows/syscall_windows.go
index 9b5ed54..acd06e3 100644
--- a/windows/syscall_windows.go
+++ b/windows/syscall_windows.go
@@ -202,6 +202,21 @@
 
 // syscall interface implementation for other packages
 
+// GetProcAddressByOrdinal retrieves the address of the exported
+// function from module by ordinal.
+func GetProcAddressByOrdinal(module Handle, ordinal uintptr) (proc uintptr, err error) {
+	r0, _, e1 := syscall.Syscall(procGetProcAddress.Addr(), 2, uintptr(module), ordinal, 0)
+	proc = uintptr(r0)
+	if proc == 0 {
+		if e1 != 0 {
+			err = errnoErr(e1)
+		} else {
+			err = syscall.EINVAL
+		}
+	}
+	return
+}
+
 func Exit(code int) { ExitProcess(uint32(code)) }
 
 func makeInheritSa() *SecurityAttributes {