I am being told by an exception that's being thrown in the last line, that I'm trying to read/write on protected memory. What am I doing wrong here? Thanks
int count = (int)WinApi.SendMessage(_chatHwnd, WinApi.LB_GETCOUNT, 0, 0);
Debug.WriteLine("count=" + count);
StringBuilder sb = new StringBuilder(count * 20);
for (int i = _lastReadPosition; i < count; i++) {
int len = (int)WinApi.SendMessage(_chatHwnd, WinApi.LB_GETTEXTLEN, i, 0);
IntPtr text = Marshal.AllocHGlobal(len);
byte[] itemText = new byte[len];
WinApi.SendMessage(_chatHwnd, WinApi.LB_GETTEXT, i, text.ToInt32());
Marshal.Copy(text, itemText, 0, len);
string s = System.Text.Encoding.UTF8.GetString(itemText);
sb.Append(s);
}
Debug.WriteLine("analise"); <- EXCEPTION THROWN HERE
From the msdn:
LB_GETTEXTLEN
The return value is the length of the string, in TCHARs, excluding the terminating null character. Under certain conditions, this value may actually be greater than the length of the text. For more information, see the following Remarks section.
LB_GETTEXT
A pointer to the buffer that will receive the string; it is type LPTSTR which is subsequently cast to an LPARAM. The buffer must have sufficient space for the string and a terminating null character. An LB_GETTEXTLEN message can be sent before the LB_GETTEXT message to retrieve the length, in TCHARs, of the string.
You need to provide space for one additional null TCHAR. However I see several other problems in your code:
Your system is WinNT? Then lb_gettextlen returns length in TCHAR and on NT systems one TCHAR is two bytes long
I see AllocHGlobal, but I do not see FreeHGlobal. Memory leak?
Why you convert byte array to string using UTF8 encoding? You need to use Unicode.
Your SendMessage interface potentially dangerous, because it does not expect x64 pointers.
Update: In general your code must look like this:
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam,
[MarshalAs(UnmanagedType.LPTStr)] StringBuilder lParam);
private void Form1_Shown(object sender, EventArgs e)
{
int count = (int)SendMessage(_chatHwnd, WinApi.LB_GETCOUNT, IntPtr.Zero, IntPtr.Zero);
StringBuilder sb = new StringBuilder(count * 20);
for (int i = _lastReadPosition; i < count; i++)
{
int len = (int)SendMessage(_chatHwnd, WinApi.LB_GETTEXTLEN, (IntPtr)i, IntPtr.Zero);
StringBuilder LineBuilder = new StringBuilder(len + 1);
SendMessage(_chatHwnd, WinApi.LB_GETTEXT, (IntPtr)i, LineBuilder);
sb.Append(LineBuilder.ToString());
}
}
Related
I am trying to do login in the phone. I am developing in c# and the library is in C++. The function "lineDevSpecific" returns the value "-2147483595", but it must to be positive.
[DllImport("Tapi32.dll", SetLastError = true)]
unsafe private static extern int lineDevSpecific(IntPtr hLine, IntPtr lpParams);
[StructLayout(LayoutKind.Sequential)]
public struct UserRec
{
//[MarshalAs(UnmanagedType.I4)]
public int dwMode=8;
public int dwParam1=201;
public int dwParam2=0;
}
unsafe static void Iniciar()
{
string vline = "Ip Office Phone: 201";
IntPtr hline = System.Runtime.InteropServices.Marshal.StringToHGlobalAnsi(vline);
var sizeUserRec = Marshal.SizeOf(typeof(UserRec));
var userRec = Marshal.AllocHGlobal(sizeUserRec);
int result=lineDevSpecific(hline, userRec);
var x = (UserRec)Marshal.PtrToStructure(userRec, typeof(UserRec));
Marshal.FreeHGlobal(userRec);
}
Version 2:
I have modified the initial post adding the lineInitializeEx method and lineOpen.
These methods returns a positive value, after this lineDevSpecific continues returning the same value.
[DllImport("Tapi32.dll", CharSet = CharSet.Auto)]
unsafe private static extern long lineInitializeEx(ref uint lphLineApp, uint hInstance, uint lpfnCallback, uint lpszFriendlyAppName, ref uint lpdwNumDevs, ref uint lpdwAPIVersion, ref uint lpLineInitializeExParams);
[DllImport("Tapi32.dll", SetLastError = true)]
internal static extern long lineOpen(ref uint hLineApp, uint dwDeviceID, uint lphLine, uint dwAPIVersion, uint dwExtVersion, uint dwCallbackInstance, uint dwPrivileges, uint dwMediaModes, ref uint lpCallParams);
[DllImport("Tapi32.dll", CharSet = CharSet.Auto)]
unsafe private static extern int lineDevSpecific(uint hLine, IntPtr lpParams);
uint lineApp = 0;
uint numdevs = 0;
uint apiversion = 0;
uint exparams = 0;
uint lpcallparams = 0;
string sParams = "";
long lSize = 0;
long inicio = lineInitializeEx(ref lineApp, 0, 0, 0, ref numdevs, ref apiversion, ref exparams);
if (inicio > 0)
{
long open = lineOpen(ref lineApp, 0, 0, 0, 0, 0, 0, 0, ref lpcallparams);
//string vline = "Ip Office Phone: 201";
//IntPtr hline = System.Runtime.InteropServices.Marshal.StringToHGlobalAnsi(vline);
var sizeUserRec = Marshal.SizeOf(typeof(UserRec));
var userRec = Marshal.AllocHGlobal(sizeUserRec);
int result = lineDevSpecific(lineApp, userRec);
var x = (UserRec)Marshal.PtrToStructure(userRec, typeof(UserRec));
Marshal.FreeHGlobal(userRec);
}
Solution: I have called to Julmar Atapi.
string extension = "201";
char[] c = new char[extension.Length + 2];
c[0] = (char)0x08; //login character
int i = 1;
foreach (char s in extension)
{
c[i] = s;
i++;
}
c[i] = (char)0x00; //null terminator
CurrentAddress.DeviceSpecific(Encoding.ASCII.GetBytes(c));
That is a LINEERR_ constant, see MSDN LINEERR_ Constants page
These use a hexadecimal unsigned "0x8000 00xx" style, turning them negative if you look at them as a signed int.
So this one is 0x8000 0035 LINEERR_INVALPOINTER
Your hline is wrong, this is a handle to a line not a text in a pointer. You need to get it from a lineOpen
Update for version 2
You are mixing up hLineApp and hLine. From lineOpen MSDN:
hLineApp: Handle to the application's registration with TAPI.
lphLine: Pointer to an HLINE handle that is then loaded with the handle representing the opened line device. Use this handle to identify the device when invoking other functions on the open line device.
I am trying to retrieve information from extern desktop aplication in Windows.
I know how extract the text from Textboxes (class "Edit") but I don't know how extract the values from controls with class name "ThunderRT6ListBox" and "ThunderRT6ComboBox". How can I do that?
I have this code to extract the text from the textbox:
public static class ModApi
{
[DllImport("user32.dll", EntryPoint = "FindWindowA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll", EntryPoint = "SendMessageTimeout", SetLastError = true, CharSet = CharSet.Auto)]
public static extern uint SendMessageTimeoutText(IntPtr hWnd, int Msg, int countOfChars, StringBuilder text, uint flags, uint uTImeoutj, uint result);
[DllImport("user32.dll", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
static internal extern bool EnumChildWindows(IntPtr hWndParent, funcCallBackChild funcCallBack, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, StringBuilder lParam);
const int LB_GETCOUNT = 0x018B;
const int LB_GETTEXT = 0x0189;
public static string GetText(IntPtr hwnd)
{
var text = new StringBuilder(1024);
if (SendMessageTimeoutText(hwnd, 0xd, 1024, text, 0x2, 1000, 0) != 0)
{
return text.ToString();
}
return "";
}
}
public foo()
{
IntPtr value = new IntPtr(0x019C086A); //ID locate using Spy++
String caption = ModApi.GetText(value);
}
UPDATE 1:
The way to read from ListBox:
public static List<string> GetListBoxContents(IntPtr listBoxHwnd)
{
int cnt = (int)SendMessage(listBoxHwnd, LB_GETCOUNT, IntPtr.Zero, null);
List<string> listBoxContent = new List<string>();
for (int i = 0; i < cnt; i++)
{
StringBuilder sb = new StringBuilder(256);
IntPtr getText = SendMessage(listBoxHwnd, LB_GETTEXT, (IntPtr)i, sb);
listBoxContent.Add(sb.ToString());
}
return listBoxContent;
}
UPDATE 2:
The way to read from ComboBox:
public static List<string> GetComboBoxContents(IntPtr cbBoxHwnd)
{
int cnt = (int)SendMessage(cbBoxHwnd, CB_GETCOUNT, IntPtr.Zero, null);
List<string> listBoxContent = new List<string>();
for (int i = 0; i < cnt; i++)
{
//int txtLength = SendMessage(cbBoxHwnd, CB_GETLBTEXTLEN, i, 0);
StringBuilder sb = new StringBuilder(256);
IntPtr getText = SendMessage(cbBoxHwnd, CB_GETLBTEXT, (IntPtr)i, sb);
listBoxContent.Add(sb.ToString());
}
return listBoxContent;
}
You're dealing with a VB6 application from eons ago. "Thunder" was the internal name for the VB product/project (trivial side note).
You're closer than you realize. If you have the HWND to the control, and I think you do:
Call SendMessage with that HWND and the message LB_GETCOUNT to get the number of items in the list.
For each index, call SendMessage with LB_GETTEXTLEN and the current item index to get the length of the text, then allocate a buffer accordingly.
Call SendMessage again, this time using LB_GETTEXT message, same item index (zero-based), and the reference to your buffer, and that should get you each item's text.
You might consider one more declaration/alias to SendMessage that just returns an int, which should make some of these calls simpler.
If I get a chance, I will clean this up a bit later with a more specific code example (or at least pseudocode), but I get the impression you're very much on the right track already and may only need this basic description to get the rest of the way.
Good luck!
I am trying to obtain a value from a registry key contained in an offline hive. The code would compile but I am getting 'System.AccessViolationException' or the program just closes.
I think the program is trying to read of write to memory that is not allocated. However I have tried to allocate memory for myValue using stringbuilder.
When i set pcbData to any value lower than 66 i get a return value of 234, which means that the specified buffer size is not large enough to hold the data.
OROpenHive seems be working as I am getting a return value of 0.
Syntax for ORGetValue at http://msdn.microsoft.com/en-us/library/ee210767(v=vs.85).aspx
DWORD ORGetValue(
_In_ ORHKEY Handle,
_In_opt_ PCWSTR lpSubKey,
_In_opt_ PCWSTR lpValue,
_Out_opt_ PDWORD pdwType,
_Out_opt_ PVOID pvData,
_Inout_opt_ PDWORD pcbData
);
Here is my code:
[DllImport("offreg.dll", CharSet = CharSet.Auto, EntryPoint = "ORGetValue", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
public static extern uint ORGetValue(
IntPtr Handle,
string lpSubKey,
string lpValue,
out uint pdwType,
out StringBuilder pvData,
ref uint pcbData);
[DllImport("offreg.dll", CharSet = CharSet.Auto)]
public static extern uint OROpenHive(
String lpHivePath,
out IntPtr phkResult);
private void button2_Click(object sender, EventArgs e)
{
IntPtr myHive;
String filepath = #"C:\Users\JON\Desktop\NTUSER.DAT";
StringBuilder myValue = new StringBuilder("", 256);
uint pdwtype;
uint pcbdata = 66;
uint ret2 = OROpenHive(filepath, out myHive);
this.textBox1.Text = ret2.ToString();
uint ret3 = ORGetValue(myHive, "Environment", "TEMP", out pdwtype, out myValue, ref pcbdata);
this.textBox1.Text = ret3.ToString();
}
You are taking the result wrong. pcbdata should be passed in the size of the buffer, and it will contain the actual number of characters read after the function returns.
Winapi functions don't know what size are the buffers, so even if you are allocating 256 bytes, you are telling ORGetValue you have allocated only 66... if you are reading a key which needs more than 66 bytes, it will return 234 (or ERROR_MORE_DATA).
You'd typically do something like:
StringBuilder myValue = new StringBuilder("", 256);
uint pcbdata = myValue.Capacity;
//...
uint ret3 = ORGetValue(myHive, "Environment", "TEMP", out pdwtype, out myValue, ref pcbdata);
this.textBox1.Text = "Read " + pcbdata.ToString() + " bytes - returned: " + ret3.ToString();
If your key is 66 bytes, then this should read: Read 66 bytes - returned: 0
I'm trying to use the RtlGetCompressionWorkSpaceSize and RtlCompressBuffer functions in a C# project.
Here is what I have so far:
class Program
{
const uint COMPRESSION_FORMAT_LZNT1 = 2;
const uint COMPRESSION_ENGINE_MAXIMUM = 0x100;
[DllImport("ntdll.dll")]
static extern uint RtlGetCompressionWorkSpaceSize(uint CompressionFormat, out uint pNeededBufferSize, out uint Unknown);
[DllImport("ntdll.dll")]
static extern uint RtlCompressBuffer(uint CompressionFormat, byte[] SourceBuffer, uint SourceBufferLength, out byte[] DestinationBuffer,
uint DestinationBufferLength, uint Unknown, out uint pDestinationSize, IntPtr WorkspaceBuffer);
static void Main(string[] args)
{
uint dwSize = 0;
uint dwRet = 0;
uint ret = RtlGetCompressionWorkSpaceSize(COMPRESSION_FORMAT_LZNT1 | COMPRESSION_ENGINE_MAXIMUM, out dwSize, out dwRet);
IntPtr pMem = Marshal.AllocHGlobal((int)dwSize);
byte[] buffer = new byte[1024];
byte[] outBuf = new byte[1024];
uint destSize = 0;
ret = RtlCompressBuffer(COMPRESSION_FORMAT_LZNT1 | COMPRESSION_ENGINE_MAXIMUM, buffer, 1024, out outBuf, 1024, 0, out destSize, pMem);
Console.Write(ret.ToString());
Console.Read();
}
}
RtlGetCompressionWorkSpaceSize works since it returns 0 (NT success code) but when I call RtlCompressBuffer I get a Memory Access Violation error.
EDIT: With help from David's answer I've fixed the issue and the correct code is below.
const ushort COMPRESSION_FORMAT_LZNT1 = 2;
const ushort COMPRESSION_ENGINE_MAXIMUM = 0x100;
[DllImport("ntdll.dll")]
static extern uint RtlGetCompressionWorkSpaceSize(ushort CompressionFormat, out uint pNeededBufferSize, out uint Unknown);
[DllImport("ntdll.dll")]
static extern uint RtlCompressBuffer(ushort CompressionFormat, byte[] SourceBuffer, int SourceBufferLength, byte[] DestinationBuffer,
int DestinationBufferLength, uint Unknown, out int pDestinationSize, IntPtr WorkspaceBuffer);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern IntPtr LocalAlloc(int uFlags, IntPtr sizetdwBytes);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr LocalFree(IntPtr hMem);
internal static byte[] Compress(byte[] buffer)
{
var outBuf = new byte[buffer.Length * 6];
uint dwSize = 0, dwRet = 0;
uint ret = RtlGetCompressionWorkSpaceSize(COMPRESSION_FORMAT_LZNT1 | COMPRESSION_ENGINE_MAXIMUM, out dwSize, out dwRet);
if (ret != 0)
{
return null;
}
int dstSize = 0;
IntPtr hWork = LocalAlloc(0, new IntPtr(dwSize));
ret = RtlCompressBuffer(COMPRESSION_FORMAT_LZNT1 | COMPRESSION_ENGINE_MAXIMUM, buffer,
buffer.Length, outBuf, outBuf.Length, 0, out dstSize, hWork);
if (ret != 0)
{
return null;
}
LocalFree(hWork);
Array.Resize(ref outBuf, dstSize);
return outBuf;
}
You are very nearly there. The problem is this part of your P/invoke for RtlCompressBuffer:
out byte[] DestinationBuffer
The default marshalling for byte[] is for the array contents to marshalled in both directions, from managed to unmanaged, and then back again when the function returns. The C definition of RtlCompressBuffer is annotated with __out but that means that the array contents are __out rather than the pointer being __out.
Change your P/invoke to
byte[] DestinationBuffer
and similarly in the call to RtlCompressBuffer change out outBuf to outBuf and you should be good to go.
Be warned that your code as it stands will return an status code of STATUS_BUFFER_ALL_ZEROS so don't be tricked into thinking that this non-zero return value indicates failure.
One final point, the first parameter to both P/invokes, CompressionFormat, should be declared as ushort.
I'm trying to port some C++ code to C#, and one of the things that I need to do is use PostMessage to pass a byte array to another process' window. I'm trying to get the source code to the other program so I can see exactly what it's expecting, but in the meantime, here's what the original C++ code looks like:
unsigned long result[5] = {0};
//Put some data in the array
unsigned int res = result[0];
Text winName = "window name";
HWND hWnd = FindWindow(winName.getConstPtr(), NULL);
BOOL result = PostMessage(hWnd, WM_COMMAND, 10, res);
And here's what I have now:
[DllImport("User32.dll", SetLastError = true, EntryPoint = "FindWindow")]
public static extern IntPtr FindWindow(String lpClassName, String lpWindowName);
[DllImport("User32.dll", SetLastError = true, EntryPoint = "SendMessage")]
public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, ref COPYDATASTRUCT lParam);
[StructLayout(LayoutKind.Sequential)]
public struct COPYDATASTRUCT
{
public int dwData;
public int cbData;
[MarshalAs(UnmanagedType.ByValArray, SizeConst=32)]
public byte[] lpData;
}
public const int WM_COPYDATA = 0x4A;
public static int sendWindowsByteMessage(IntPtr hWnd, int wParam, byte[] data)
{
int result = 0;
if (hWnd != IntPtr.Zero)
{
int len = data.Length;
COPYDATASTRUCT cds;
cds.dwData = wParam;
cds.lpData = data;
cds.cbData = len;
result = SendMessage(hWnd, WM_COPYDATA, wParam, ref cds);
}
return result;
}
//*****//
IntPtr hWnd = MessageHelper.FindWindow(null, windowName);
if (hWnd != IntPtr.Zero)
{
int result = MessageHelper.sendWindowsByteMessage(hWnd, wParam, lParam);
if (result == 0)
{
int errCode = Marshal.GetLastWin32Error();
}
}
Note that I had to switch from using PostMessage in C++ to SendMessage in C#.
So what happens now is that I'm getting both result and errCode to be 0, which I believe means that the message was not processed - and indeed looking at the other application, I'm not seeing the expected response. I have verified that hWnd != IntPtr.Zero, so I think that the message is being posted to the correct window, but the message data is wrong. Any ideas what I'm doing wrong?
Update
I'm still not having any luck after trying the suggestions in the comments. Here's what I've currently got:
[DllImport("User32.dll", SetLastError = true)]
public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
[StructLayout(LayoutKind.Sequential)]
public struct COPYDATASTRUCT
{
public IntPtr dwData;
public int cbData;
public IntPtr lpData;
}
public struct BYTEARRDATA
{
public byte[] data;
}
public static IntPtr IntPtrAlloc<T>(T param)
{
IntPtr retval = Marshal.AllocHGlobal(Marshal.SizeOf(param));
Marshal.StructureToPtr(param, retval, false);
return (retval);
}
public static void IntPtrFree(IntPtr preAllocated)
{
//Ignores errors if preAllocated is IntPtr.Zero!
if (IntPtr.Zero != preAllocated)
{
Marshal.FreeHGlobal(preAllocated);
preAllocated = IntPtr.Zero;
}
}
BYTEARRDATA d;
d.data = data;
IntPtr buffer = IntPtrAlloc(d);
COPYDATASTRUCT cds;
cds.dwData = new IntPtr(wParam);
cds.lpData = buffer;
cds.cbData = Marshal.SizeOf(d);
IntPtr copyDataBuff = IntPtrAlloc(cds);
IntPtr r = SendMessage(hWnd, WM_COPYDATA, IntPtr.Zero, copyDataBuff);
if (r != IntPtr.Zero)
{
result = r.ToInt32();
}
IntPtrFree(copyDataBuff);
copyDataBuff = IntPtr.Zero;
IntPtrFree(buffer);
buffer = IntPtr.Zero;
This is a 64 bit process trying to contact a 32 bit process, so there may be something there, but I'm not sure what.
The problem is that COPYDATASTRUCT is supposed to contain a pointer as the last member, and you're passing the entire array.
Take a look at the example on pinvoke.net: http://www.pinvoke.net/default.aspx/Structures/COPYDATASTRUCT.html
More info after comments:
Given these definitions:
const int WM_COPYDATA = 0x004A;
[StructLayout(LayoutKind.Sequential)]
struct COPYDATASTRUCT
{
public IntPtr dwData;
public int cbData;
public IntPtr lpData;
}
[DllImport("User32.dll", SetLastError = true, EntryPoint = "FindWindow")]
public static extern IntPtr FindWindow(String lpClassName, String lpWindowName);
[DllImport("User32.dll", SetLastError = true, EntryPoint = "SendMessage")]
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, ref COPYDATASTRUCT lParam);
I can create two .NET programs to test WM_COPYDATA. Here's the window procedure for the receiver:
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_COPYDATA:
label3.Text = "WM_COPYDATA received!";
COPYDATASTRUCT cds = (COPYDATASTRUCT)Marshal.PtrToStructure(m.LParam, typeof(COPYDATASTRUCT));
byte[] buff = new byte[cds.cbData];
Marshal.Copy(cds.lpData, buff, 0, cds.cbData);
string msg = Encoding.ASCII.GetString(buff, 0, cds.cbData);
label4.Text = msg;
m.Result = (IntPtr)1234;
return;
}
base.WndProc(ref m);
}
And the code that calls it using SendMessage:
Console.WriteLine("{0} bit process.", (IntPtr.Size == 4) ? "32" : "64");
Console.Write("Press ENTER to run test.");
Console.ReadLine();
IntPtr hwnd = FindWindow(null, "JimsForm");
Console.WriteLine("hwnd = {0:X}", hwnd.ToInt64());
var cds = new COPYDATASTRUCT();
byte[] buff = Encoding.ASCII.GetBytes(TestMessage);
cds.dwData = (IntPtr)42;
cds.lpData = Marshal.AllocHGlobal(buff.Length);
Marshal.Copy(buff, 0, cds.lpData, buff.Length);
cds.cbData = buff.Length;
var ret = SendMessage(hwnd, WM_COPYDATA, 0, ref cds);
Console.WriteLine("Return value is {0}", ret);
Marshal.FreeHGlobal(cds.lpData);
This works as expected when both the sender and receiver are 32 bit processes and when they're 64 bit processes. It will not work if the two processes' "bitness" does not match.
There are several reasons why this won't work for 32/64 or 64/32. Imagine that your 64 bit program wants to send this message to a 32 bit program. The lParam value passed by the 64 bit program is going to be 8 bytes long. But the 32 bit program only sees 4 bytes of it. So that program won't know where to get the data from!
Even if that worked, the size of the COPYDATASTRUCT structure is different. In 32 bit programs, it contains two pointers and a DWORD, for a total size of 12 bytes. In 64 bit programs, COPYDATASTRUCT is 20 bytes long: two pointers at 8 bytes each, and a 4-byte length value.
You have similar problems going the other way.
I seriously doubt that you'll get WM_COPYDATA to work for 32/64 or for 64/32.
This will work on 32bit sender to 64bit receiver, 64bit sender to 32bit receiver. Also work from 32 to 32, and 64 to 64. You don't even need to declare COPYDATASTRUCT. Very simple:
const int WM_COPYDATA = 0x004A;
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(String lpClassName, String lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
static IntPtr SendMessage(IntPtr hWnd, byte[] array, int startIndex, int length)
{
IntPtr ptr = Marshal.AllocHGlobal(IntPtr.Size * 3 + length);
Marshal.WriteIntPtr(ptr, 0, IntPtr.Zero);
Marshal.WriteIntPtr(ptr, IntPtr.Size, (IntPtr)length);
IntPtr dataPtr = new IntPtr(ptr.ToInt64() + IntPtr.Size * 3);
Marshal.WriteIntPtr(ptr, IntPtr.Size * 2, dataPtr);
Marshal.Copy(array, startIndex, dataPtr, length);
IntPtr result = SendMessage(hWnd, WM_COPYDATA, IntPtr.Zero, ptr);
Marshal.FreeHGlobal(ptr);
return result;
}
private void button1_Click(object sender, EventArgs e)
{
IntPtr hWnd = FindWindow(null, "Target Window Tittle");
byte[] data = System.Text.Encoding.ASCII.GetBytes("this is the sample text");
SendMessage(hWnd, data, 0, data.Length);
}
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_COPYDATA:
byte[] b = new Byte[Marshal.ReadInt32(m.LParam, IntPtr.Size)];
IntPtr dataPtr = Marshal.ReadIntPtr(m.LParam, IntPtr.Size * 2);
Marshal.Copy(dataPtr, b, 0, b.Length);
string str = System.Text.Encoding.ASCII.GetString(b);
MessageBox.Show(str);
// m.Result = put result here;
return;
}
base.WndProc(ref m);
}
Could it be a 32 versus 64 bit problem?
Try setting COPYDATASTRUCT's dwData member to an IntPtr instead of an int.
See this thread for a related problem:
http://www.vistax64.com/net-general/156538-apparent-marshalling-related-problem-x64-but-works-x86.html
See the original definition of COPYDATASTRUCT:
http://msdn.microsoft.com/en-us/library/ms649010(VS.85).aspx
Here's the meaning of ULONG_PTR on x64:
http://msdn.microsoft.com/en-us/library/aa384255(VS.85).aspx
To store a 64-bit pointer value, use ULONG_PTR. A ULONG_PTR value is 32 bits when compiled with a 32-bit compiler and 64 bits when compiled with a 64-bit compiler.
In your IntPtrAlloc function, what's the SizeOf(param) giving you? I think it's going to be the size of a reference to an array, not the size of the array content. And so Windows will copy a .NET array reference into the other process, which is completely meaningless.
Pin the array, and use Marshal.UnsafeAddrOfPinnedArrayElement to get the proper value of lpData.