Calling Everything from .NET

Plug-in and third party software discussion.
Post Reply
shovavnik
Posts: 7
Joined: Sat Sep 19, 2009 6:41 pm

Calling Everything from .NET

Post by shovavnik »

I'm trying to communicate with Everything from .NET but am having some trouble.

I based my code on the ipctest.c and .h files included with the SDK.

The problem is that the only result I'm getting from the SendMessage call is the value 0. I've also determined that the size of the query struct returned by Marshal.SizeOf() is 2 bytes smaller than that returned by sizeof() in c.

Here's the code I tried, along with the .NET struct definitions.

Any idea what I'm doing wrong?

Any help is greatly appreciated!

Code: Select all

        public bool SendSearch(string searchString)
        {
            Process everythingProcess = Process.GetProcessesByName("Everything").FirstOrDefault();
            if(everythingProcess == null)
            {
                return false;
            }

            int length = searchString.Length;

            int ipcQuerySize = Marshal.SizeOf(typeof(IpcQuery)) - Marshal.SizeOf(typeof(Char)) + length * Marshal.SizeOf(typeof(Char)) + Marshal.SizeOf(typeof(Char));
            Console.Out.WriteLine("Size: {0}", ipcQuerySize);

            IntPtr replyHandle = Process.GetCurrentProcess().MainWindowHandle;
            IpcQuery ipcQuery =
                new IpcQuery
                    {
                        MaxResults = int.MaxValue,// 0xFFFFFFFF,
                        Offset = 0,
                        ReplyCopyDataMessage = new IntPtr(0),
                        SearchFlags = 0,
                        ReplyHandle = replyHandle,
                        SearchString = searchString,
                    };

            IntPtr ipcQueryPointer = Marshal.AllocHGlobal(ipcQuerySize);
            Marshal.StructureToPtr(ipcQuery, ipcQueryPointer, false);

            CopyDataStruct copyDataStruct =
                new CopyDataStruct
                    {
                        dwData = COPY_DATA_UNICODE,
                        cbData = ipcQuerySize,
                        lpData = ipcQueryPointer,
                    };

            int cdsSize = Marshal.SizeOf(typeof(CopyDataStruct));
            IntPtr cdsPointer = Marshal.AllocHGlobal(cdsSize);
            Marshal.StructureToPtr(copyDataStruct, cdsPointer, false);

            IntPtr targetHandle = everythingProcess.MainWindowHandle;

            IntPtr reply = NativeMethods.SendMessage(targetHandle, replyHandle, cdsPointer);

            return reply.ToInt32() == 1;
        }
 

Code: Select all

	[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
	public struct IpcQuery
	{
		public IntPtr ReplyHandle;

		public IntPtr ReplyCopyDataMessage;

		public int SearchFlags;

		public int Offset;

		public int MaxResults;

		[MarshalAs(UnmanagedType.LPWStr)]
		public string SearchString;
	}

Code: Select all

	[StructLayout(LayoutKind.Sequential)]
	public struct CopyDataStruct
	{
		public int dwData;

		public int cbData;

		public IntPtr lpData;
	}

Code: Select all

		private const UInt32 WM_COPYDATA = 0x004A;

		[DllImport("user32.dll", CharSet = CharSet.Auto)]
		private static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

		public static IntPtr SendMessage(IntPtr targetHandle, IntPtr replyHandle, IntPtr copyDataStruct)
		{
			return SendMessage(targetHandle, WM_COPYDATA, replyHandle, copyDataStruct);
		}
David
Developer
Posts: 430
Joined: Tue Mar 17, 2009 1:42 am

Re: Calling Everything from .NET

Post by David »

SendMessage() should return 1 when the IPC query request is successful.

Check the Errorlog.txt file in your "Everything" installation folder for any errors messages.

You might be getting one of the following errors:

Code: Select all

The IPC query structure is too small.
bad query size in COPYDATA_IPC_QUERY cds->cbData >= sizeof(EVERYTHING_IPC_QUERY) - 1 + 1.

or

Code: Select all

The search string is not null terminated.
expected null terminator at the end of IPC query struct (search string) 
I've also determined that the size of the query struct returned by Marshal.SizeOf() is 2 bytes smaller than that returned by sizeof() in c.
There might be some alignment issues.
Please make sure your IPC query struct is packed to 1 byte.

To do this in C/C++:

Code: Select all

#pragma pack(1) 
I am not sure how to do this in .NET

Please make sure you are using the correct structure for the respected ascii or unicode EVERYTHING_IPC_COPYDATAQUERY message.

sizeof(EVERYTHING_IPC_QUERYA) should equal 21
sizeof(EVERYTHING_IPC_QUERYW) should equal 22
shovavnik
Posts: 7
Joined: Sat Sep 19, 2009 6:41 pm

Re: Calling Everything from .NET

Post by shovavnik »

Thanks for the pointers :).

At some point, I started looking for more helpful info from Everything, so I'd already found errorlog.txt and the -debug console. Unfortunately, they haven't been much help. Spy++ also isn't providing much assistance: it only shows a message was sent, but obviously nothing about how it's processed.
David wrote:Check the Errorlog.txt file in your "Everything" installation folder for any errors messages.
There are some messages in the errorlog.txt, but they're from earlier attempts. I've got:
1. expected null terminator at the end of IPC query struct (search string)
2. bad query size in COPYDATA_IPC_QUERYW 12 >= 22 - 2 + 2
I'm not sure what the code looked like when these messages were logged, but it's not logging new messages anymore and the code has been changed significantly since then.
David wrote:sizeof(EVERYTHING_IPC_QUERYW) should equal 22
I agree that there's an alignment issue. I'm not quite sure how to resolve it.

I'm pretty sure the problem is with the definition of the search_string buffer:

Code: Select all

WCHAR search_string[1];
sizeof(WCHAR[1]) is 2. But I can't find an equivalent type that can be marshaled to the WCHAR array with the same size. String, and IntPtr are 4, and I can't create a "meaningful" char array. And anyway, a single .NET Char is sized as 1, even though it's a unicode character.

Is it possible to add another similar struct (say, EVERYTHING_IPC_QUERYN) that can handle a 4-byte pointer to a unicode string?

I'm not sure, but I think this will help overcome the current hurdle.
David
Developer
Posts: 430
Joined: Tue Mar 17, 2009 1:42 am

Re: Calling Everything from .NET

Post by David »

Sorry, I am not familiar with the .NET syntax..

The search string MUST follow the IPC query struct.
Pointers are not allowed in the WM_COPYDATA messages.

Use the following IpcQuery structure

Code: Select all

public struct IpcQuery
{
     public IntPtr ReplyHandle;

      public IntPtr ReplyCopyDataMessage;

      public int SearchFlags;

      public int Offset;

      public int MaxResults;
}
Allocate a byte array using the size of the above struct plus the length of the search string plus the size of one character (the null terminator).

Code: Select all

// C++ code to allocate a new ipcQuery struct with room for the search string to follow.
IpcQuery *ipcQuery = (IpcQuery *)new unsigned char [sizeof(IpcQuery) + (searchString.Length + 1) * sizeof(TCHAR)];
Fill in the IpcQuery and copy the search string immediately after the IpcQuery struct.

Code: Select all

// C++ code to copy search string (including the null terminator) after the IpcQuery struct
CopyMemory(ipcQuery+1,searchString.cString,(searchString.Length+1) * sizeof(TCHAR));
The query struct in memory should look something like this:
Edit:The ASCII query struct in memory should look something like this:

Code: Select all

0x00000000: DWORD: 0x00000100 // ReplyWindowHandle
0x00000004: DWORD: 0x00000000 // ReplyCopyDataMessage
0x00000008: DWORD: 0x00000000 // SearchFlags
0x0000000C: DWORD: 0x00000000 // Offset
0x00000010: DWORD: 0xFFFFFFFF // MaxResults
// the search string follows the IpcQuery structure.
0x00000014: BYTE: 0x66 // f
0x00000015: BYTE: 0x6F // o
0x00000016: BYTE: 0x6F // o
0x00000017: BYTE: 0x00 // null terminator
shovavnik
Posts: 7
Joined: Sat Sep 19, 2009 6:41 pm

Re: Calling Everything from .NET

Post by shovavnik »

Making progress... I realize c# isn't your forte, and I really appreciate your help!

I'm now creating a byte array built almost like the one you described, except for unicode. Here's what it looks like:

Code: Select all

        0xC00B3F00 [3221962496]
        0x00000000 [0]
        0x00000000 [0]
        0x00000000 [0]
        0xFFFFFFFF [4294967295]
        0x66 [102] // "f"
        0x00 [0]
        0x6F [111] // "o"
        0x00 [0]
        0x6F [111] // "o"
        0x00 [0]
        0x00 [0]
        0x00 [0]
This still doesn't work. Note that I'm trying to get it to work with unicode, but when I serialize it as ascii (single byte characters), it still doesn't work. In both cases, neither the debug console nor the errorlog.txt file report anything. Playing around with the ipctest.c, I have managed to get progress and errors reported, so I know Everything is working right (pun not intended!).

Anyway, I'm trying out a lot of different options, but the main difficulty is that it's all guesswork. I do have a couple questions that might help.

1. Is this structure for unicode searches (with double byte characters) valid or am I still missing something?

2. Does Everything's debug console report every attempt to communicate with it via IPC? Are there exceptions? It would help if it would report attempts, even if their structure is completely invalid (not minor things like missing null terminators...).

3. Does the search string have a maximum size? This will help with the serialization and marshaling code.

Thanks!
David
Developer
Posts: 430
Joined: Tue Mar 17, 2009 1:42 am

Re: Calling Everything from .NET

Post by David »

1. Is this structure for unicode searches (with double byte characters) valid or am I still missing something?
The unicode part is correct, however..
0xC00B3F00 [3221962496]
This looks like a pointer to a CWnd
It should be a HWND handle.

Try using ReplyWindow.m_hWnd instead of ReplyWindow.

Is your window setup up correctly to receive results via the WM_COPYDATA message?
2. Does Everything's debug console report every attempt to communicate with it via IPC? Are there exceptions? It would help if it would report attempts, even if their structure is completely invalid (not minor things like missing null terminators...).
"Everything" should report failure or success in the debug console.

The following messages can occur:

Code: Select all

ERROR: expected null terminator at the end of IPC query struct (search string)
ERROR: bad query size in COPYDATA_IPC_QUERY cds->cbData >= sizeof(EVERYTHING_IPC_QUERYA) - 1 + 1
SUCCESS: IPC: Client disconnected.
However, you will get SUCCESS: IPC: Client disconnected even if the ReplyWindow you send in your WM_COPYDATA is invalid.
3. Does the search string have a maximum size? This will help with the serialization and marshaling code.
The are no limits on the search string size.

SendMessage should handle serialization and marshaling for WM_COPYDATA.

The following link maybe of some help:
http://www.codeproject.com/KB/threads/I ... cator.aspx
shovavnik
Posts: 7
Joined: Sat Sep 19, 2009 6:41 pm

Re: Calling Everything from .NET

Post by shovavnik »

I did some more exploratory work today. I'm sure I'm missing something simple. Your comments have been very helpful!

I have very little experience with MFC and COM development, and this foray into unknown territory is an interesting challenge. Can you recommend a tool or two that might help trap the messages being sent, so I can better understand what I'm doing wrong? A pointer in right direction will be greatly appreciated.
This looks like a pointer to a CWnd
It should be a HWND handle.

Try using ReplyWindow.m_hWnd instead of ReplyWindow.
I'm not quite sure what the difference between a CWnd and an HWND handle is. I used the value returned by System.Diagnostics.Process.MainWindowHandle, which is described as "Gets the window handle of the main window of the associated process". I thought this fit the bill, and is also the value used in examples I found elsewhere on the web (including the link you referred me to). There is also a System.Diagnostics.Process.Handle, described as "Returns the associated process' native handle".

Do you know which value maps to the required hwnd?

But it seems that when the IpcQuery structure is serialized, the handle is serialized with the wrong endianess, which is probably what you noticed. I've resolved that issue (hopefully).
Is your window setup up correctly to receive results via the WM_COPYDATA message?
Not yet. I haven't gotten there yet. I'm focusing right now on sending the message correctly.
However, you will get SUCCESS: IPC: Client disconnected even if the ReplyWindow you send in your WM_COPYDATA is invalid.
I haven't been so lucky :) But this is encouraging. If I understand it correctly, it means that SendMessage's return value does not depend on whether I can receive the results. Or am I wrong?
SendMessage should handle serialization and marshaling for WM_COPYDATA.
I don't follow. SendMessage receives a pointer to a CopyDataStruct object. I may not have to serialize it (it's just three 4-byte data members), but I certainly have to allocate unmanaged memory in order to be able to pass the reference. The example you referred to also marshals the CopyDataStruct object.

Another difference that may be significant is that serializing more than one piece of data (as in the example).

Now that I think about it, I realize I may not need to marshal the internal IpcQuery object. It might be enough to serialize the IpcQuery object and the search string and then marshal only the byte array.

I'll continue experimenting over the next few days.
David
Developer
Posts: 430
Joined: Tue Mar 17, 2009 1:42 am

Re: Calling Everything from .NET

Post by David »

I used the value returned by System.Diagnostics.Process.MainWindowHandle, which is described as "Gets the window handle of the main window of the associated process". I thought this fit the bill
Yes, this should be the handle we are after, it is probably using the wrong endianess like you suggested.
I haven't been so lucky :) But this is encouraging. If I understand it correctly, it means that SendMessage's return value does not depend on whether I can receive the results. Or am I wrong?
SendMessage returns non-zero if the IPC query was successful.
SendMessage will return zero if the IPC query failed.

If you are not getting IPC messages in the debug console you could be sending the IPC query message to the wrong window?
Make sure you send it to the EVERYTHING_TASKBAR_NOTIFICATION window.

You can use MS Spy++ to check if the EVERYTHING_TASKBAR_NOTIFICATION window is processing the IPC request and to check if you window is receiving the results.
shovavnik
Posts: 7
Joined: Sat Sep 19, 2009 6:41 pm

Re: Calling Everything from .NET

Post by shovavnik »

If you are not getting IPC messages in the debug console you could be sending the IPC query message to the wrong window?
Make sure you send it to the EVERYTHING_TASKBAR_NOTIFICATION window.

You can use MS Spy++ to check if the EVERYTHING_TASKBAR_NOTIFICATION window is processing the IPC request and to check if you window is receiving the results.
It definitely looks like I'm sending it to the wrong window. I'm getting a handle to Everything's main window, which does not seem to be the taskbar notification window.

I noticed that in Spy++, the window's name is lacking the final "N". Not sure if that's significant, but it doesn't seem to be a coincidence that it's cut off at 30 characters.

Next chance I have, I'll find out how to get a handle to that window using .NET (if possible) or the Windows API.
David
Developer
Posts: 430
Joined: Tue Mar 17, 2009 1:42 am

Re: Calling Everything from .NET

Post by David »

everything_hwnd = FindWindow("EVERYTHING_TASKBAR_NOTIFICATION",0);

See http://www.tek-tips.com/viewthread.cfm?qid=304765 for more information.
shovavnik
Posts: 7
Joined: Sat Sep 19, 2009 6:41 pm

Re: Calling Everything from .NET

Post by shovavnik »

Thanks for the pointer. I'll get back to this in a few days, after the holiday.
shovavnik
Posts: 7
Joined: Sat Sep 19, 2009 6:41 pm

Re: Calling Everything from .NET

Post by shovavnik »

I'm finally making some progress. I thought I'd keep you posted...

I'm able to send messages to Everything and the messages are registering in the debug console. There's still something wrong with the way I'm sending strings, so the result counts are still incorrect.

I've also started setting up the results thread. I'm getting the DEVICE_CHANGE messages instead of the COPY_DATA messages for some reason, but there's definitely progress on this end as well.

Right now I'm trying to better understand how Everything handles the search string as well as the internal structure of the results list. So I'm basically facing more integration issues (marshaling and serializing).

I'll let you know if I need more help. I've still got a few ideas to try out.

I'm thinking that if I actually get this thing working, I'll publish (at least) the integration code so others can benefit from and improve upon it.
Post Reply