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
Related
The prototype of GetCurrentDirectory
DWORD GetCurrentDirectory(
[in] DWORD nBufferLength,
[out] LPTSTR lpBuffer
);
DWORD is unsigned long, LPTSTR is a pointer to wchar buffer in Unicode environment. It can be called from C++
#define MAX_BUFFER_LENGTH 256
int main() {
TCHAR buffer[MAX_BUFFER_LENGTH];
GetCurrentDirectory(MAX_BUFFER_LENGTH, buffer);
return 0;
}
I tried to encapsulate this win32 function in C#, but failed.
[DllImport("kernel32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
public static extern uint GetCurrentDirectory(uint nBufferLength, out StringBuilder lpBuffer);
You simply need to remove out on the StringBuilder parameter:
[DllImport("kernel32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
public static extern uint GetCurrentDirectory(uint nBufferLength, StringBuilder lpBuffer);
And then pre-allocate the buffer when calling the function:
const int MAX_PATH = 260;
var buffer = new StringBuilder(MAX_PATH);
var len = GetCurrentDirectory(buffer.Capacity, buffer);
var path = buffer.ToString(0, len);
That being said, you can just use System.IO.Directory.GetCurrentDirectory() instead:
var path = Directory.GetCurrentDirectory();
I am calling into native code from managed code and I am having a bit of trouble figuring out how to properly marshal my code. In C, I have the following:
struct cHandle {
unsigned long handleLo;
unsigned long handleHi;
}
struct cBuffer {
unsigned long bufferSize;
unsigned long bufferType;
__field_bcount(bufferSize) void *bufferPtr;
}
struct cBufferDesc {
unsigned long bufferVersion;
unsigned long bufferCount;
_field_ecount(bufferCount) cBuffer *buffers;
}
uint __stdcall CMethod(
__in_opt cHandle* handle1,
__in_opt cHandle* handle2,
__in_opt wchar_t* wstr,
__in unsigned long long1,
__in unsigned long resevered1, // Reserved, always 0
__in unsigned long long2,
__in_opt cBufferDesc* inputBufferPtr,
__in unsigned long reserved2, // Reserved, always 0
__inout_opt cHandle* outHandle,
__inout_opt cBufferDesc* outputBufferPtr,
__out unsigned long * outLong,
__out_opt TimeStampStruct* timeStamp);
The behaviour of CMethod is as follows. outputBufferPtr will always output a value. If inputBufferPtr is NULL, handle2 should also be null and CMethod should follow different logic to give an initial output buffer, and if not CMethod should calculate the output buffer based on the data in the input buffer. I am having trouble getting my initial call to work. Additionally, I don't care about the timestamp, so I will not detail that struct, or make a C# equivalent. I have tried the following marshalling in C#:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Handle {
private IntPtr HandleLo;
private IntPtr HandleHi;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Buffer {
public uint Size; // Possibly unknown
public uint Type; // Always set.
public IntPtr Buffer; // Possibly unknown
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct BufferDesc {
public uint Count; // Always 1 for my purposes
public uint Version; // Always set
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
public Buffer[] BufferArray; // Will always be a size 1 array.
}
// Used for calling when we have an existing input buffer
[DllImport("mylib.dll", ExactSpelling = "true", CharSet = CharSet.Unicode, SetLastError = true)]
uint CMethod(
[In] ref Handle handle1,
[In] ref Handle handle2,
[In] IntPtr wstr,
[In] uint long1, // C# uint == C ulong
[In] uint reserved1,
[In] uint long2,
[In] ref BufferDesc inputBufferPtr,
[In] uint reserved2,
[In, Out] ref Handle outHandle,
[In, Out] ref BufferDesc outputBufferPtr,
[Out] out IntPtr outLong,
[Out] out IntPtr timestamp);
// Used for calling when we do not have an existing input buffer
// Here IntPtr.Zero will be passed in for handle2 and inputBufferPtr
[DllImport("mylib.dll", ExactSpelling = "true", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern uint CMethod(
[In] ref Handle handle1,
[In] IntPtr handle2,
[In] IntPtr wstr,
[In] uint long1, // C# uint == C ulong
[In] uint reserved1,
[In] uint long2,
[In] IntPtr inputBufferPtr,
[In] uint reserved2,
[In, Out] ref Handle outHandle,
[In, Out] ref BufferDesc outputBufferPtr,
[Out] out IntPtr outLong,
[Out] out IntPtr timestamp);
public static void WrapperMethod(
ref Handle handle1,
ref Handle handle2,
string wstr,
byte[] inputBuffer,
ref Handle outHandle,
out byte[] outputBuffer)
{
BufferDesc inputBufferDesc;
BufferDesc outputBufferDesc;
outputBufferDesc.Count = 1;
outputBufferDesc.Version = 0; // Real data not shown
outputBufferDesc.BufferArray = new Buffer[0];
outputBufferDesc.BufferArray[0].Count = 0;
outputBufferDesc.BufferArray[0].Type = 2; // Real data not shown
outputBufferDesc.BufferArray[0].Buffer = IntPtr.Zero;
IntPtr wstrPtr = Marshal.StringToCoTaskMemUni(wstr);
IntPtr ignoredOutLong;
IntPtr ignoredTimestamp;
if (null != inputBuffer)
{
inputBufferDesc.Count = 1;
inputBufferDesc.Version = 0; // Real data not shown
inputBufferDesc.BufferArray = new Buffer[1];
inputBufferDesc.BufferArray[0].Size = inputBuffer.Length;
inputBufferDesc.BufferArray[0].Type = 2; // Real data not shown
inputBufferDesc.BufferArray[0].Buffer = GCHandle.Alloc(inputBuffer, GCHandleType.Pinned).AddrOfPinnedObject();
CMethod(
ref handle1,
ref handle2,
wstrPtr,
0, // Real data not shown
0,
0, // Real data not shown
ref inputBufferDesc,
0,
ref outHandle,
ref outputBufferDesc,
out ignoreOutLong,
out ignoreTimestamp);
}
else
{ ///////////////////////////////////////////////////////////////////////
// This is the call I am taking and also where the code is crashing. //
CMethod( //
ref handle1, //
IntPtr.Zero, //
wstrPtr, //
0, // Real data not shown //
0, //
0, // Real data not shown //
IntPtr.Zero, //
0, //
ref outHandle, //
ref outputBufferDesc, //
out ignoreOutLong, //
out ignoreTimestamp); //
///////////////////////////////////////////////////////////////////////
}
// Do Cleanup. Not reached at this point.
}
The error that I am getting is that I am trying to access read or write protected memory. If there is anything you can see which is obviously wrong with how I am marshalling or if I am pinning wrong, or just not pinning where I should be please, or if you can see any other issues let me know.
The issue was with my output buffer. I wasn't assigning an empty array of size one to outputBufferDesc.Buffers, and the native code tried to write to memory that wasn't allocated for that purpose. I also couldn't marshal it as a byvalue array. Instead my struct looks like this:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct BufferDesc
{
public uint Version;
public uint Count;
public IntPtr Buffers;
}
And I pin an empty SecurityBuffer array of size 1 and give the address to Buffers.
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.
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());
}
}