This answer explains how to get the Word.Application object from a hwnd, which means we can loop through all active Word processes and check if their Word.Application matches our own Word.Application object. This way, you don't need to do anything with the window caption.
Please note that you can only obtain the process of a Word.Application that is visible and has one or more documents open (the code opens a temporary empty document in the latter case):
using System;
using System.Linq;
using System.Text;
using Word = NetOffice.WordApi;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Diagnostics;
namespace WordHwnd
{
class Program
{
static void Main(string[] args)
{
using (var app = new Word.Application() { Visible = true })
{
Console.WriteLine(WordGetter.GetProcess(app).MainWindowHandle);
}
Console.ReadLine();
}
}
class WordGetter
{
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("00020400-0000-0000-C000-000000000046")]
private interface IDispatch
{
}
private const uint OBJID_NATIVEOM = 0xFFFFFFF0;
private static Guid IID_IDispatch = new Guid("{00020400-0000-0000-C000-000000000046}");
[DllImport("Oleacc.dll")]
private static extern int AccessibleObjectFromWindow(int hwnd, uint dwObjectID, byte[] riid, out IDispatch ptr);
private delegate bool EnumChildCallback(int hwnd, ref int lParam);
[DllImport("User32.dll")]
private static extern bool EnumChildWindows(int hWndParent, EnumChildCallback lpEnumFunc, ref int lParam);
[DllImport("User32.dll")]
private static extern int GetClassName(int hWnd, StringBuilder lpClassName, int nMaxCount);
private static bool Find_WwG(int hwndChild, ref int lParam)
{
if (GetClassName(hwndChild) == "_WwG")
{
lParam = hwndChild;
return false;
}
return true;
}
private static string GetClassName(int hwndChild)
{
var buf = new StringBuilder(128);
GetClassName(hwndChild, buf, 128);
return buf.ToString();
}
public static Process GetProcess(Word.Application app)
{
Word.Document tempDoc = null;
//This only works if there is a document open
if (app.Documents.Count == 0)
tempDoc = app.Documents.Add();
var processes = Process.GetProcessesByName("WINWORD");
var appsAndProcesses = processes
.Select(p => new { Process = p, App = WordGetter.GetWordApp(p) })
.Where(x => !Equals(x.App, null));
Process process = null;
foreach (var appAndProcess in appsAndProcesses)
{
if (appAndProcess.App == app)
{
process = appAndProcess.Process;
break;
}
else
{
appAndProcess.App.Dispose();
}
}
tempDoc?.Close(false);
return process;
}
public static Word.Application GetWordApp(Process process)
{
return GetWordApp(process.MainWindowHandle);
}
public static Word.Application GetWordApp(IntPtr hwnd)
{
return GetWordApp((int)hwnd);
}
public static Word.Application GetWordApp(int hwnd)
{
var wwG_Hwnd = 0;
var callback = new EnumChildCallback(Find_WwG);
EnumChildWindows(hwnd, callback, ref wwG_Hwnd);
if (wwG_Hwnd != 0)
{
IDispatch iDispatch;
var result = AccessibleObjectFromWindow(wwG_Hwnd, OBJID_NATIVEOM, IID_IDispatch.ToByteArray(), out iDispatch);
if (result >= 0)
{
var obj = iDispatch.GetType().InvokeMember("Application", BindingFlags.GetProperty, null, iDispatch, null);
return new Word.Application(null, obj);
}
return null;
}
return null;
}
}
}
I use NetOffice in this example, but you can easily alter it to work with the standard interop libraries by editing the using statement and doing Marshal.ReleaseComObject() instead of Word.Application.Dispose().