A heap has been corrupted. C# dllimport, delphi PChar return value
Asked Answered
A

1

5

I have dll imported. All the other parts work, but the string return value of imported method gives this:

Unhandled exception at 0x7748EA5F (ntdll.dll) in ***.exe: 0xC0000374: A heap has been corrupted (parameters: 0x774C4270).

It still returns the string, but I'm worried that this causes some other errors later on, that are hard to debug. From what I have tested, it feels like it can be anything, that is causing this.

This is my importing code:

[DllImport("kernel32.dll")]
public static extern IntPtr LoadLibrary(string dllToLoad);
[DllImport("kernel32.dll")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);

[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Ansi)]
private delegate String GetStringDelegate(int handle, int index);

private static GetStringDelegate getString { get; set; }

var addressOfGetString = NativeMethods.GetProcAddress(_handle, "GetString");
getString = (GetStringDelegate)Marshal.GetDelegateForFunctionPointer(addressOfGetString, typeof(GetStringDelegate));

usage

getString(Handle, 1);

This works, but it causes the error. While debugging, just pressing "continue" will allow it to process it and show the results. Result is correct.

This is how it is done in delphi dll

function GetString(Hnd,Index : Integer) : PChar; stdcall;
begin
 Result:=TControl(Hnd).Stack.GetString(Index);
end;

I have same kind of code for integers, doubles, bools and everything else in the dll works, without errors. So I think it creates somekind of overflow or wrong size of memory allocation.

Note: If I create console application, it just fails, without breaking on error, if I run console without debugger ( ctrl+f5 ), it works, still without error. Heap error is generated when I call this from forms application.

TL;DR; This code works, but it shows the heap error, while returning ints, bools etc works perfectly.

Accouchement answered 20/5, 2015 at 12:11 Comment(5)
PCHAR is Unicode in recent versions of Delphy, so [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Ansi)] should be [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]Piwowar
@Piwowar Unless, as is often the case, the user is still on D2007 or earlierJessabell
@DavidHeffernan I've written "in recent versions"Piwowar
If i change it to unicode, the string is some chinese letters.Accouchement
@Accouchement That would indicate that the text really is ANSI and CharSet.Ansi is appropriate for this DLLJessabell
J
10

When you return a string as a function return value of a p/invoke function the marshaller takes responsibility for freeing that memory. It assumes the memory was allocated on the COM heap, e.g. with CoTaskMemAlloc. Your string does not meet that requirement.

  • You could change the Delphi code to allocate the memory that way.
  • You could return IntPtr and use Marshal.PtrToStringAnsi to manually marshal. The question then remains as to whether the memory needs to be freed, and if so how.
  • You could return a COM BSTR, but that only works with Delphi as an out parameter and not a function return value. See Why can a WideString not be used as a function return value for interop?
  • You could ask the caller to allocate memory and have the callee populate it.

I cannot see all the way into your code to be sure, but I would not be surprised if you were returning PChar(s) where s was a local variable. That would mean that you would be returning the address of freed memory.

The bottom line here is that passing strings (or indeed arrays or other dynamic structures) from callee to caller is much more complex than passing simple value types. You are going to need to re-consider how you do this.

Jessabell answered 20/5, 2015 at 12:18 Comment(9)
I see.. I am not the author of the dll, but I will point him to this answer. Thanks.Accouchement
Apparently, it was the framework 4.5 that was causing the problem. Your notes were good and after discussing with the author, it was noted that it has worked on other .Net app before, so I had to take other direction. Had to look out of the box. Changing to .net 4 helped.. Thanks anywaysAccouchement
No, that's all wrong. Your code is broken on all versions of .net, right back to version 1. You've just been getting away with it. It's just chance that it breaks the latest framework. I don't understand why you would ask the question if you don't want to understand the problem. Do you not believe me when I tell you that the marshaller is calling CoTaskMemFree?!Jessabell
Ok? Well, maybe I rushed bit fast to a conclusion, but it seemed like obvious solution. But the author of dll, said that dll will clean the referenced string after I'm creating the call to do so ( by starting another request ), thus string reference should be valid and I should be able to copy it ( and I am able to do, it just happens to come with this error ) and then allow dll to clear its references. What I don't understand is, why 4.5 is throwing that. No other version does it.Accouchement
Things change. That's how it goes. You were just unlucky before in that your broken code managed not to result in errors. If the string is statically allocated and cleaned up later then you need to use my second bullet point. Replace the return type with IntPtr and pass that pointer to Marshal.PtrToStringAnsi and it's all good. I think you would do well to stay with the problem and not brush it under the carpet.Jessabell
Looking at your profile and your answers quality, there is no situation in the world, were I could question your words. Problem is more in the fact, that I'm bit lost here now. But this looks like great opportunity to learn something, so I will test that now, thanks.Accouchement
Thanks for kicking me a bit there! Your solution to change the return type from string to IntPtr and then Marshal.PtrToStringAnsi that return value worked.Accouchement
I'm glad you stayed with this. And happy that switching to IntPtr worked out. Do feel free to question my words, it's always wise to be sceptical. By the same token, question yourself, and in this case you must have known in your bones that switching .net framework did not feel right! ;-)Jessabell
Thanks for the IntPtr/Marshal.PtrToStringAnsi tip. The perfect solution.Erminiaerminie

© 2022 - 2024 — McMap. All rights reserved.