I have a WPF application that needs to interface with another application.
This application has about 20 custom Windows Messages (WM_USER+50...WM_USER+70).
Summary of what I'm trying to accomplish:
WPF Application -> SendMessage -> ThirdParty application
The problem I have is that all of the Messages are CUSTOM messages. Therefore I have to implement my own data marshaling.
See http://msdn.microsoft.com/en-us/library/windows/desktop/ms644950(v=vs.85).aspx
It seems that the process I need to go through is:
Grab the process and open it for all access.
User32.GetWindowThreadProcessId(windowHandle, out pId);
// Open the process with all access
someprocess = OpenProcess((0x1F0FFF), false, (int)pId);
Allocate a buffer in the process:
IntPtr buffer = VirtualAllocEx( hProcess, IntPtr.Zero, 1024, 0x1000, 0x04 );
Fill up some sort of struct that will be written to the buffer created in #2?
Copy #3 to the remote buffer is #2? WriteProcessMemory??
Send the custom message ( SendMessage(windowhandle, customMsg, 0, buffer from #2?)
Read the struct back in from the remote process buffer into a local buffer
Marshal this data to a managed type. (This is a C# .Net application)
I could really use some insight. I haven't had much luck thus far. I think the part that I'm most stuck on is what type of struct to send to the WriteProcessMemory?
WM_COPYDATA is definitely the easiest way to do this. WM_COPYDATA lets you send two distinct items of data to another process - a DWORD value, and an arbitrarily-sized chunk of data. So for your implementation you would probably do something like this:
COPYDATASTRUCT cds;
cds.dwData = WM_USER + 50; // the "message" you want to send
cds.cbData = sizeof(MyDataForMessage50); // the size of the chunk of data
cds.lpData = lpMessage50Data; // a pointer to the chunk of data
SendMessage(hwndTarget, WM_COPYDATA, reinterpret_cast<WPARAM>(hwndSender),
reinterpret_cast<LPARAM>(&cds));
Note that hwndTarget is the target window in the other process, and hwndSender is a window in the sending process. The target window receives the same parameters and so can use wParam to learn who sent the message, and can therefore send a reply if needed.
In the WndProc at the receiving end:
if (uMsg == WM_COPYDATA)
{
HWND hwndSender = reinterpret_cast<HWND>(wParam);
LPCOPYDATASTRUCT pcds = reinterpret_cast<LPCOPYDATASTRUCT>(lParam);
DWORD dwCustomMsg = pcds->dwData;
LPVOID pCustomData = pcds->lpData;
DWORD dwCustomDataSize = pcds->cbData;
// do something with the custom message
// return TRUE to indicate message received
return TRUE;
}
Also note the important note in the docs for WM_COPYDATA:
The receiving application should consider the data read-only. The
lParam parameter is valid only during the processing of the message.
The receiving application should not free the memory referenced by
lParam. If the receiving application must access the data after
SendMessage returns, it must copy the data into a local buffer
Sample code for Sending message:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp2
{
class Program
{
public const int WM_COPYDATA = 0x4A;
public const UInt32 WM_COMMAND = 0x0111;
public const UInt32 IDM_MENU_SECUREDISCONNECT = 305;
public const UInt32 PROCESS_QUERY_LIMITED_INFORMATION = 0x1000;
[StructLayout(LayoutKind.Sequential)]
public struct COPYDATASTRUCT
{
public IntPtr dwData;
public int cbData;
[MarshalAs(UnmanagedType.LPWStr)]
public string lpData;
}
[DllImport("User32.dll", EntryPoint = "SendMessage", SetLastError = true)]
public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, ref COPYDATASTRUCT lParam);
private const string MARKER = "MARKER\n";
static void Main(string[] args)
{
IntPtr destWnd = (IntPtr)int.Parse(args[0]);
string packedargs = DAZZLE_MARKER + String.Join("\n", args[1]);
/
byte[] sarr = System.Text.Encoding.Unicode.GetBytes(packedargs);
int len = sarr.Length;
COPYDATASTRUCT CopyDataStruct;
CopyDataStruct.dwData = (IntPtr)100;
CopyDataStruct.cbData = (len + 1) * 2;
CopyDataStruct.lpData = packedargs;
int result = SendMessage(destWnd, WM_COPYDATA, 0, ref CopyDataStruct);
}
}
}
Related
I'm working on a plug and play version POS for .NET Service Object. I have a FTDI device that has 2 devices on it. One is a MSR and the other is a RFID. For a quick catch up POS for .NET supports Plug in play if you give it a Hardware ID. The FTDI device's hardware id is QUADPORT\QUAD_SERIAL_INTERFACE. So on my Service Object (SO) I start it with this
[HardwareId("QUADPORT\\QUAD_SERIAL_INTERFACE")]
[ServiceObject(DeviceType.Msr, "FTDI Device", "MSR FTDI Device", 1, 8)]
public class FTDIDevice : Msr
{
...
}
In the open method I can query the DevicePath property and it is the correct DevicePath. As a quick test to make sure it was indeed talking to the serial port through the device path I opened a FileStream to this Device Path, then tried to open both of the Virtual Com Ports and sure enough one of them had "Access Denied". Awesome! My next test I did was to write to the RFID device since I know most all of the commands and how to format the byte array to communicate with the RFID device. In fact here is the code I use.
public override void Open()
{
string str = this.DevicePath;
handle_read = Kernel32.CreateExistingRWFile(str);
FS_read = new FileStream(handle_read, FileAccess.ReadWrite, 9);
Console.WriteLine("File Stream Created, Writing Open Sequence");
byte[] open = { 0xAA, 0xBB, 0x04, 0xFB, 0x00, 0x00, 0x03, 0xF8 };
FS_read.Write(open, 0, open.Length);
//byte[] receive = new byte[11];
//var something = FS_read.Read(receive, 0, receive.Length);
//Console.WriteLine(BitConverter.ToString(receive, 0, something));
//Close the FileStream and the SafeFileHandle
close();
}
private SafeFileHandle handle_read;
private FileStream FS_read;
The first time I ran this code I selected the MSR (there are 2 devices with the exact name both in device manager and in the microsoft test app, so I was guessing anyway) and sending that command gave me no response from the MSR (for obvious reasons, its not the RFID Device). According to the MSDN site the Read command blocks until at least one byte is read from my stream. That is a problem since the MSR does not respond to RFID commands. So essentially my program is deadlocked. I force closed the program then fired it up again and selected the RFID and the Read method did indeed return with a successful response from the RFID.
So my question is is how can I query the Stream to see if there was any response from my device? I've tried using the CanRead property (always returns true). I've tried using ReadByte to see if it would return -1 like the document said it is supposed to, but it too hangs. I tried using the Length property but it throws an exception saying that the Stream does not support seeking. It feels like I'm out of options, hence why I'm asking here. Any suggestions?
EDIT
Hans wants to see my how I created my handle
internal class Kernel32
{
internal static SafeFileHandle CreateExistingRWFile(string devicePath)
{
return CreateFile(devicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);
}
internal const short FILE_ATTRIBUTE_NORMAL = 0x80;
internal const short INVALID_HANDLE_VALUE = -1;
internal const uint GENERIC_READ = 0x80000000;
internal const uint GENERIC_WRITE = 0x40000000;
internal const uint FILE_SHARE_READ = 0x00000001;
internal const uint FILE_SHARE_WRITE = 0x00000002;
internal const uint CREATE_NEW = 1;
internal const uint CREATE_ALWAYS = 2;
internal const uint OPEN_EXISTING = 3;
[DllImport("kernel32.dll", SetLastError = true)]
private static extern SafeFileHandle CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool ReadFile(SafeFileHandle hFile, byte[] lpBuffer, uint nNumberOfBytesToRead, ref uint lpNumberOfBytesRead, IntPtr lpOverlapped);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool WriteFile(SafeFileHandle hFile, byte[] lpBuffer, uint nNumberOfBytesToWrite, ref uint lpNumberOfBytesWritten, IntPtr lpOverlapped);
}
I was working with a BeginRead when I saw Hans suggest something, so far it EndRead always returns 0 :/
here is the code for that.
byte[] receive = new byte[11];
IAsyncResult iAR = FS_read.BeginRead(receive, 0, receive.Length, ASyncFileCallBack, receive);
byte[] wildGuess = (byte[])iAR.AsyncState;
Console.WriteLine("Guessing:{0}", BitConverter.ToString(wildGuess));
}
private void ASyncFileCallBack(IAsyncResult result)
{
byte[] buffer = (byte[])result.AsyncState;
int length = FS_read.EndRead(result);
if (length > 0)
Console.WriteLine("Response:{0}", BitConverter.ToString(buffer, 0, length));
else
Console.WriteLine("No Resposne");
}
The guessing always just spits out 11 0's almost immediatly. Shortly after "No Response" is put up on the Console.
I have an application that will form a packet and send the packet data to an external program to send. I have everything working, but my only method I know that doesn't require the window to be the foremost is PostMessage. However, it seems to always lose 0-2 characters at the beginning of the message. Is there a way I can make a check to prevent the loss? I've tried looping GetLastError() and re-sending it if it's 0, but it doesn't help any. Here's the code I've gotten so far:
public void SendPacket(string packet)
{
//Get window name
IntPtr hWnd = Window.FindWindow(null, "???????????");
//Get the first edit box handle
IntPtr edithWnd = Window.FindWindowEx(hWnd, IntPtr.Zero, "TEdit", "");
//Get the handle for the send button
IntPtr buttonhWnd = Window.FindWindowEx(hWnd, IntPtr.Zero, "TButton", "SEND");
//Iterate twice to get the edit box I need
edithWnd = Window.FindWindowEx(hWnd, edithWnd, "TEdit", "");
edithWnd = Window.FindWindowEx(hWnd, edithWnd, "TEdit", "");
foreach (Char c in packet)
{
SendCheck(c, edithWnd);
}
//Press button
TextSend.PostMessageA(buttonhWnd, 0x00F5, 0, 0);
//Clear the edit box
TextSend.SendMessage(edithWnd, 0x000C, IntPtr.Zero, "");
}
public void SendCheck(char c, IntPtr handle)
{
//Send the character
TextSend.PostMessageA(handle, 0x102, c, 1);
//If error code is 0 (failure), resend that character
if (TextSend.GetLastError() == 0)
SendCheck(c, handle);
return;
}
And here are the definitions in TextSend class:
[DllImport("Kernel32.dll")]
public static extern int GetLastError();
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true)]
public static extern bool SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, string s);
The fact that you're finding a TEdit and a TButton makes me think that the target application was written in Delphi. If so, depending on the version of Delphi it may or may not be a Unicode application. You're calling PostMessageA instead of PostMessageW which means it's sending a single-byte Ansi char instead of a 16-bit Unicode char from the c# application.
Do you have source to the target application? Stuffing data in an edit box and clicking a button seems a bit fragile. If you can modify the target application there are certainly other options available than to send one character at a time.
I posted this question a few days ago, and I have some follow up doubts about marshaling an IntPtr to a struct.
The thing goes like this:
As stated in the question I am referencing, I make calls to asynchronous methods on a native Dll. These methods communicate their completion with Windows Messages. I receive the Windows Message correctly now and, within it, an lParam property (of type IntPrt).
According to the documentation I am following, this lParam points to the struct that has the results of the execution of the method. As a particular example, one of the structures I am trying to fill is defined as follows:
Original C signature:
typedef struct _wfs_result {
ULONG RequestID;
USHORT hService;
TIMESTAMP tsTimestamp; /*Win32 SYSTEMTIME structure according to documentation*/
LONG hResult;
union {
DWORD dwCommandCode;
DWORD dwEventID;
} u;
LPVOID lpBuffer;
} WFSRESULT, *LPWFSRESULT;
My C# definition:
[StructLayout(LayoutKind.Sequential), Serializable]
public struct Timestamp
{
public ushort wYear;
public ushort wMonth;
public ushort wDayOfWeek;
public ushort wDay;
public ushort wHour;
public ushort wMinute;
public ushort wSecond;
public ushort wMilliseconds;
}
[StructLayout(LayoutKind.Explicit), Serializable]
public struct WFSResult
{
[FieldOffset(0), MarshalAs(UnmanagedType.U4)]
public uint RequestID;
[FieldOffset(4), MarshalAs(UnmanagedType.U2)]
public ushort hService;
[FieldOffset(6), MarshalAs(UnmanagedType.Struct, SizeConst = 16)]
public Timestamp tsTimestamp;
[FieldOffset(22), MarshalAs(UnmanagedType.U4)]
public int hResult;
[FieldOffset(26), MarshalAs(UnmanagedType.U4)]
public UInt32 dwCommandCode;
[FieldOffset(26), MarshalAs(UnmanagedType.U4)]
public UInt32 dwEventID;
[FieldOffset(30), MarshalAs(UnmanagedType.U4)]
public Int32 lpBuffer;
}
Now the fun part: the native Dll I am calling belongs to an independent process, FWMAIN32.EXE, which is running in the same machine (single instance). I believe the Window Message that I receive, which is application specific (above WM_USER), returns an LParam that is not really pointing to the struct I am expecting, and that the struct resides somewhere in the memory space of the FWMAIN32.EXE process.
Initially, I tried to just Marshal.PtrToStructure (with little hope actually) and the struct got filled with garbage data. I also tried with GetLParam with same outcome. Finally, I tried to go across process boundaries with the ReadProcessMemory API, as explained in these posts:
C# p/invoke, Reading data from an Owner Drawn List Box
http://www.codeproject.com/KB/trace/minememoryreader.aspx
I get the exception code 299 (ERROR_PARTIAL_COPY: Only part of a ReadProcessMemory or WriteProcessMemory request was completed.)
And additionally the byte[] I get from using ReadProcessMemory is: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
My code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.ComponentModel;
using System.Runtime.InteropServices;
namespace XFSInteropMidleware
{
public class CrossBoundaryManager
{
[DllImport("kernel32")]
static extern IntPtr OpenProcess(UInt32 dwDesiredAccess, Int32 bInheritHandle, UInt32 dwProcessId);
[DllImport("kernel32")]
static extern Int32 ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [In, Out] byte[] lpBuffer, UInt32 dwSize, out IntPtr lpNumberOfBytesRead);
[DllImport("kernel32")]
static extern Int32 CloseHandle(IntPtr hObject);
[DllImport("kernel32")]
static extern int GetLastError();
private const string nativeProcessName = "FWMAIN32";
private IntPtr hProcess = IntPtr.Zero;
const uint PROCESS_ALL_ACCESS = (uint)(0x000F0000L | 0x00100000L | 0xFFF);
static int dwSize = 34; //The size of the struct I want to fill
byte[] lpBuffer = new byte[dwSize];
public void OpenProcess()
{
Process[] ProcessesByName = Process.GetProcessesByName(nativeProcessName);
hProcess = CrossBoundaryManager.OpenProcess(CrossBoundaryManager.PROCESS_ALL_ACCESS, 1, (uint)ProcessesByName[0].Id);
}
public byte[] ReadMemory(IntPtr lParam, ref int lastError)
{
try
{
IntPtr ptrBytesReaded;
OpenProcess();
Int32 result = CrossBoundaryManager.ReadProcessMemory(hProcess, lParam, lpBuffer, (uint)lpBuffer.Length, out ptrBytesReaded);
return lpBuffer;
}
finally
{
int processLastError = GetLastError();
if (processLastError != 0)
{
lastError = processLastError;
}
if (hProcess != IntPtr.Zero)
CloseHandle(hProcess);
}
}
public void CloseProcessHandle()
{
int iRetValue;
iRetValue = CrossBoundaryManager.CloseHandle(hProcess);
if (iRetValue == 0)
throw new Exception("CloseHandle failed");
}
}
}
And I use it like this:
protected override void WndProc(ref Message m)
{
StringBuilder sb = new StringBuilder();
switch (m.Msg)
{
case OPEN_SESSION_COMPLETE:
GCHandle openCompleteResultGCH = GCHandle.Alloc(m.LParam); //So the GC does not eat the pointer before I can use it
CrossBoundaryManager manager = new CrossBoundaryManager();
int lastError = 0;
byte[] result = manager.ReadMemory(m.LParam, ref lastError);
if (lastError != 0)
{
txtState.Text = "Last error: " + lastError.ToString();
}
StringBuilder byteResult = new StringBuilder();
for (int i = 0; i < result.Length; i++)
{
byteResult.Append(result[i].ToString() + " ");
}
sb.AppendLine("Memory Read Result: " + byteResult.ToString());
sb.AppendLine("Request ID: " + BitConverter.ToInt32(result, 0).ToString());
txtResult.Text += sb.ToString();
manager.CloseProcessHandle();
break;
}
base.WndProc(ref m);
}
Is it correct to go across process boundaries in this case?
Is it correct to use lParam as the base address for ReadProcessMemory?
Is the CLR turning lParam to something I cannot use?
Why I am getting the 299 exception?
I correctly get the process ID of FWMAIN32.EXE, but how can I be sure the lParam is pointing inside its memory space?
Should I consider the use of "unsafe"? Could anyone recommend that approach?
Are there any other ways to custom marshal the struct?
Too many questions on a single post, I know, but I think they all point to resolving this issue. Thank you all for your help in advance, and sorry I had to make it so long.
I guess I have to take this one myself. So, as stated in the comments above, removing the
GCHandle openCompleteResultGCH = GCHandle.Alloc(m.LParam);
line did the trick. I understood that when a pointer in manage context is pointing to a struct in unmanaged context, the GC would collect it as the pointer really had nothing in its address. It is in fact the other way around. When, in managed context, we hold an object or struct that is being pointed from an unmanaged context, the GC could collect it because no pointer in the managed context is pointing to it, thus the need to pin it in order to keep the GC at distance.
So, in the end, there was no need go across process boundaries in this case. I removed the call to the Kernell32 methods, as the CLR handles the marshalling quiet well and Marshal.PtrToStructure was all I needed.
Credit goes to Jim and David who pointed me in the right direction.
I have two mdi applications, both of which retrieve their data from the same database.
The two applications need to be able to send messages to each other to keep in synch.
The messages being passed back and forth only contain a string telling the recieving application which piece of data in the database it should be looking at (a job number, and some additional related info).
Both applications have a message handler, instantiated when each program starts up.
When a message is sent from the VB6 app to the c# app it sees the message, and acts appropriately, but when I send the same type of message from the c# app to the VB6 app, it seems to see the message event, but when unpacking it, only sees part of the data, and then ignores the message.
I'm thinking I may be formatting something wrong on the c# end.
Here is the method that sends the message:
namespace InterProcessMessaging
{
[StructLayout(LayoutKind.Sequential)]
public struct COPYDATASTRUCT
{
public IntPtr dwData;//a pointer to a number use this to identify your message
public IntPtr lpData;//a pointer to the address of the data
public IntPtr cbData;//a pointer to the number of bytes to be transferred
}
public class clsMessaging : System.Windows.Forms.NativeWindow, IDisposable
{
//API function to send async. message to target application
[DllImport("user32.dll", CharSet = CharSet.Ansi)]
public static extern IntPtr SendMessageA(IntPtr hwnd, Int32 wMsg, Int32 wParam, COPYDATASTRUCT lParam);
public void SendMessageToVB6(string sendMsg, string WindowsAppTitle)
{
try
{
IntPtr hwndTarget = FindWindow(null, WindowsAppTitle);
IntPtr pDWData = Marshal.AllocHGlobal(sizeof(Int32));//a pointer to a number used this to identify your message
Marshal.StructureToPtr(3, pDWData, true);//place the value 3 at this location
IntPtr pLPData = Marshal.StringToHGlobalAnsi(sendMsg.Trim());//a pointer to the address of the data
IntPtr pCBData = Marshal.AllocHGlobal(sizeof(Int32));//a pointer to the number of bytes to be transferred
Marshal.StructureToPtr(sendMsg.Trim().Length+1, pCBData, true);//place the size of the string at this location
COPYDATASTRUCT cds;//a structure containing the three pointers above
cds.dwData = pDWData;//a pointer to a number used this to identify your message (3)
cds.lpData = pLPData;//a pointer to the address of the data
cds.cbData = pCBData;//a pointer to the number of bytes to be transferred
if (!System.IntPtr.Zero.Equals(hwndTarget))
{
SendMessageA(hwndTarget, 74, 0, cds);
}
}
catch (Exception ex)
{
Debug.Print(ex.InnerException.ToString());
}
}
}
}
I would recommend to look into Named Pipes. In .NET you can use System.IO.Pipes for this purpose. In VB6 you can easily implement it with Win32API. Named Pipes is better way to make IPC than windows messaging. Also IPC via SendMessage has limitations on Vista and Win7.
You got that pretty wrong. Only COPYDATASTRUCT.lpData is a pointer. dwData indicates the message number. You pick your own, use 0 if you have only one. cbData is the size of the pointed-to data.
More problems, you are leaking the memory. The amount of memory you allocate doesn't match the size you pass. The string conversion is lossy and might not produce as many bytes as string.Length(). FindWindow is notoriously unreliable. Use a socket or a named pipe for this so you don't have to guess a name, WCF is best.
COPYDATASTRUCT cds;
cds.dwData = (IntPtr)3;
cds.lpData = Marshal.StringToHGlobalUni(sendMsg);
cds.cbData = 2 * (sendMsg.Length + 1);
SendMessageA(hwndTarget, 74, 0, cds);
Marshal.FreeHGlobal(cds.lpData);
This works:
public struct COPYDATASTRUCT
{
public IntPtr dwData;
public UInt32 cbData;
[MarshalAs(UnmanagedType.LPStr)]
public string lpData;
}
[DllImport("User32.dll", EntryPoint = "SendMessage")]
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, ref COPYDATASTRUCT lParam);
IntPtr result;
byte[] sarr = System.Text.Encoding.Default.GetBytes(sendMsg);
int len = sarr.Length;
COPYDATASTRUCT cds;
cds.dwData = (IntPtr)3;
cds.lpData = sendMsg;
cds.cbData = (UInt32)len + 1;
result = SendMessage(hwndTarget, WM_COPYDATA, 0, ref cds);
Credit for this solution goes to Jim Kemp...who is not yet a member.
Thanks Jim!
I based the solution off of the example I found here:
http://boycook.wordpress.com/2008/07/29/c-win32-messaging-with-sendmessage-and-wm_copydata/
I've been searching around, and I haven't found how I would do this from C#.
I was wanting to make it so I could tell Google Chrome to go Forward, Back, Open New Tab, Close Tab, Open New Window, and Close Window from my C# application.
I did something similar with WinAmp using
[DllImport("user32", EntryPoint = "SendMessageA")]
private static extern int SendMessage(int Hwnd, int wMsg, int wParam, int lParam);
and a a few others. But I don't know what message to send or how to find what window to pass it to, or anything.
So could someone show me how I would send those 6 commands to Chrome from C#? thanks
EDIT:
Ok, I'm getting voted down, so maybe I wasn't clear enough, or people are assuming I didn't try to figure this out on my own.
First off, I'm not very good with the whole DllImport stuff. I'm still learning how it all works.
I found how to do the same idea in winamp a few years ago, and I was looking at my code. I made it so I could skip a song, go back, play, pause, and stop winamp from my C# code. I started by importing:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr FindWindow([MarshalAs(UnmanagedType.LPTStr)] string lpClassName, [MarshalAs(UnmanagedType.LPTStr)] string lpWindowName);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern int SendMessageA(IntPtr hwnd, int wMsg, int wParam, uint lParam);
[DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
public static extern int GetWindowText(IntPtr hwnd, string lpString, int cch);
[DllImport("user32", EntryPoint = "FindWindowExA")]
private static extern int FindWindowEx(int hWnd1, int hWnd2, string lpsz1, string lpsz2);
[DllImport("user32", EntryPoint = "SendMessageA")]
private static extern int SendMessage(int Hwnd, int wMsg, int wParam, int lParam);
Then the code I found to use this used these constants for the messages I send.
const int WM_COMMAND = 0x111;
const int WA_NOTHING = 0;
const int WA_PREVTRACK = 40044;
const int WA_PLAY = 40045;
const int WA_PAUSE = 40046;
const int WA_STOP = 40047;
const int WA_NEXTTRACK = 40048;
const int WA_VOLUMEUP = 40058;
const int WA_VOLUMEDOWN = 40059;
const int WINAMP_FFWD5S = 40060;
const int WINAMP_REW5S = 40061;
I would get the hwnd (the program to send the message to) by:
IntPtr hwnd = FindWindow(m_windowName, null);
then I would send a message to that program:
SendMessageA(hwnd, WM_COMMAND, WA_STOP, WA_NOTHING);
I assume that I would do something very similar to this for Google Chrome. but I don't know what some of those values should be, and I googled around trying to find the answer, but I couldn't, which is why I asked here. So my question is how do I get the values for:
m_windowName and WM_COMMAND
and then, the values for the different commands, forward, back, new tab, close tab, new window, close window?
Start your research at http://dev.chromium.org/developers
EDIT: Sending a message to a window is only half of the work. The window has to respond to that message and act accordingly. If that window doesn't know about a message or doesn't care at all you have no chance to control it by sending window messages.
You're looking at an implementation detail on how you remote controlled Winamp. Sending messages is just one way to do it and it's the way the Winamp developers chose. Those messages you're using are user defined messages that have a specific meaning only to Winamp.
What you have to do in the first step is to find out if Chromium supports some kind of remote controlling and what those mechanisms are.
You can get the window name easily using Visual Studio's Spy++ and pressing CTRL+F, then finding chrome. I tried it and got
"Chrome_VistaFrame" for the out window. The actual window with the webpage in is "Chrome_RenderWidgetHostHWND".
As far as WM_COMMAND goes - you'll need to experiment. You'll obviously want to send button clicks (WM_MOUSEDOWN of the top off my head). As the back,forward buttons aren't their own windows, you'll need to figure out how to do this with simulating a mouse click at a certain x,y position so chrome knows what you're doing. Or you could send the keyboard shortcut equivalent for back/forward and so on.
An example I wrote a while ago does this with trillian and winamp: sending messages to windows via c# and winapi
There's also tools out there to macro out this kind of thing already, using a scripting language - autoit is one I've used: autoit.com
Ok, here's what I've got so far... I kinda know what I need to do, but it's just a matter of doing it now...
Here's the window from Spy++, I locked onto the Chrome_RenderWidgetHostHWND and clicked the Back button on my keyboard. Here's what I got:
So here's my assumptions, and I've been playing with this forever now, I just can't figure out the values.
IntPtr hWnd = FindWindow("Chrome_RenderWidgetHostHWND", null);
SendMessage(hWnd, WM_KEYDOWN, VK_BROWSER_BACK, 0);
SendMessage(hWnd, WM_KEYUP, VK_BROWSER_BACK, 0);
Now, I just don't know what I should make the WM_KEYDOWN/UP values or the VK_BROWSER_BACK/FORWARD values...
I tried this:
const int WM_KEYDOWN = 0x100;
const int WM_KEYUP = 0x101;
const int VK_BROWSER_BACK = 0x6A;
const int VK_BROWSER_FORWARD = 0x69;
The latter two values I got from the image I just showed, the ScanCodes for those two keys. I don't know if I did it right though. The former two values I got after searching google for the WM_KEYDOWN value, and someone used &H100 and &H101 for the two values. I've tried several other random ideas I've seen floating around. I just can't figure this out.
Oh, and here's the SendMessage method
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern int SendMessage(IntPtr hwnd, int wMsg, int wParam, uint lParam);
This is a great site for interop constants:
pinvoke
Another way of finding the values is to search koders.com, using C# as the language, for WM_KEYDOWN or the constant you're after:
Koders.com search
&H values look like that's from VB(6). pinvoke and koders both return results for VK_BROWSER_FORWARD,
private const UInt32 WM_KEYDOWN = 0x0100;
private const UInt32 WM_KEYUP = 0x0101;
public const ushort VK_BROWSER_BACK = 0xA6;
public const ushort VK_BROWSER_FORWARD = 0xA7;
public const ushort VK_BROWSER_REFRESH = 0xA8;
public const ushort VK_BROWSER_STOP = 0xA9;
public const ushort VK_BROWSER_SEARCH = 0xAA;
public const ushort VK_BROWSER_FAVORITES = 0xAB;
public const ushort VK_BROWSER_HOME = 0xAC;
(It's funny how many wrong defintions of VK constants are floating about, considering VK_* are 1 byte 0-255 values, and people have made them uints).
Looks slightly different from your consts. I think the function you're after is SendInput (but I haven't tried it) as it's a virtual key.
[DllImport("User32.dll")]
private static extern uint SendInput(uint numberOfInputs, [MarshalAs(UnmanagedType.LPArray, SizeConst = 1)] KEYBOARD_INPUT[] input, int structSize);
Explanation about the parameters:
Parameters
nInputs- Number of structures in the pInputs array.
pInputs - Pointer to an array of INPUT structures. Each structure represents an event to be inserted into the keyboard or mouse input stream.
cbSize - Specifies the size, in bytes, of an INPUT structure. If cbSize is not the size of an INPUT structure, the function fails.
This needs a KEYBOARD_INPUT type:
[StructLayout(LayoutKind.Sequential)]
public struct KEYBOARD_INPUT
{
public uint type;
public ushort vk;
public ushort scanCode;
public uint flags;
public uint time;
public uint extrainfo;
public uint padding1;
public uint padding2;
}
And finally a sample, which I haven't tested if it works:
/*
typedef struct tagKEYBDINPUT {
WORD wVk;
WORD wScan;
DWORD dwFlags;
DWORD time;
ULONG_PTR dwExtraInfo;
} KEYBDINPUT, *PKEYBDINPUT;
*/
public static void sendKey(int scanCode, bool press)
{
KEYBOARD_INPUT[] input = new KEYBOARD_INPUT[1];
input[0] = new KEYBOARD_INPUT();
input[0].type = INPUT_KEYBOARD;
input[0].vk = VK_BROWSER_BACK;
uint result = SendInput(1, input, Marshal.SizeOf(input[0]));
}
Also you'll need to focus the Chrome window using SetForegroundWindow