3

I have an issue with my WinForms project which was created in VS2005 .NET Framework 2.0, which I just upgraded to VS2012 .NET Framework 4.5. In my project, I used a third-party DLL by DllImport and used its functions as I had all documentation for them.

The problem is one of the functions in the imported DLL which works fine in VS2005 .NET Framework 2.0 is not working in VS2012 .NET 4.5.

Following are my code snippets from my project:

[DllImport("W5EditLD.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "K5GetClassName")]
public static extern string GetClassName();//Dll import definition

public string _GetClassName()
{
    return GetClassName();//wrapper function to DLL import function 
}

string sClassName = _GetClassName();//where i call API via wrapper method,**

Above code snippet works fine in VS2005 .NET Framework 2.0 But when I upgraded my project to VS2012 .NET Framework 4.5 I have to do it in the following way:

[DllImport("W5EditLD.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "K5GetClassName")]

public static extern IntPtr GetClassName();//Dll import definition

public IntPtr _GetClassName()
{
    return GetClassName();//wrapper function to DLL import function 
}

IntPtr ptr = _GetClassName();//where i call API via wrapper method,    
string sClassName = System.Runtime.InteropServices.Marshal. PtrToStringAnsi(ptr);

Why this is? Is automatic string marshalling not supported in VS2012 .NET Framework 4.5?

ardila
  • 1,277
  • 1
  • 13
  • 24
Suraj Patil
  • 55
  • 2
  • 9
  • You say you "have" to do it in this different way. Why? Do you get an error message if you try to do it the same way? If so, what's the error message? If not, what's led you to conclude that you "have" to do it in a different way? – Damien_The_Unbeliever Aug 29 '13 at 09:54
  • Try to add CharSet definition to DllImport. BTW, you didn't write, how exactly is is "not working" - crash, incorrect result? – Alex F Aug 29 '13 at 10:01
  • @AlexFarber Without a CharSet definition, the default is ANSI. It's clear that the text that comes back is ANSI from the use of `PtrToStringAnsi`. – David Heffernan Aug 29 '13 at 10:02
  • @Damien_The_Unbeliever i did lots of workaround and finally come to do it other way. but the native dll code implemented by other company,according to documentation provided by them charset not mention by them and its working properly in VS2005 .net framework 2.0 it fails in VS2012 .net framework 4.5,fail means it cause exception when i called the function and i am not able to catch it. – Suraj Patil Aug 29 '13 at 11:53
  • @Suraj I think your question has been answered and you should choose an answer to accept – David Heffernan Aug 29 '13 at 19:44

2 Answers2

7

Consider your original p/invoke:

[DllImport(...)]
public static extern string GetClassName();

The marshaller's treatment of the return value is the key. This is marshalled as a C string, that is a pointer to a null-terminated array of characters. Because the data is coming from native to managed, and was not allocated in the managed code, the framework assumes that it is not responsible for deallocating it. The native code cannot deallocate it since it is no longer executing.

The policy therefore is that the p/invoke marshaller assumes that the character array was allocated on the shared COM heap. And so it calls CoTaskMemFree. I'm pretty sure that the array was not allocated on the shared COM heap. So your code was always broken. In older versions of .net the call to CoTaskMemFree happened to fail silently. In the latest versions it is failing with an error. I'm not sure whether the change is in the .net framework, or the underlying platform, but that matters little since the original code is broken everywhere.

Automatic string marshalling is supported in exactly the same way in .net 4.5 as in previous versions. But you have to do it right. If you want to use a string return value with default marshalling, allocate the character array on the COM heap with a call to CoTaskMemAlloc.

If the returned string is in fact statically allocated, and does not need deallocation you have two obvious choices:

  1. In the managed code, switch to using IntPtr and PtrToStringAnsi. This is easy enough to do for you because you move the call to PtrToStringAnsi inside your _GetClassName wrapper and present the same public interface as before.
  2. In the native code, go ahead and call CoTaskMemAlloc and then copy the static buffer into that heap allocated buffer.
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • OMG, this means that 90% of interop code in the world is written by wrong way. Is there some Microsoft documentation about this? For now, I found this: http://blogs.msdn.com/b/dsvc/archive/2009/06/22/troubleshooting-pinvoke-related-issues.aspx, bit this is only blog. – Alex F Aug 29 '13 at 10:23
  • @AlexFarber Perhaps there's some documentation somewhere, but I was taught this fact by Hans Passant. You can find millions of SO posts saying exactly what I say here. – David Heffernan Aug 29 '13 at 10:27
  • @David Heffernan- May what you said is right, but native code not implemented by us,its third party company so i can not do CoTaskMemAlloc and other stuff. i just want to know,what happening in VS2012 .net framework 4.5,on other side same code of mine working properly in VS2005 .net framework 2.0 properly in the sense when i call GetClassName function in VS2012 it cause some kind of exception my call stack directly jump into WindProc which i ovverridden and VS not giving me any kind of stacktrace,why this happens in VS2012 on other side in VS2005 the function not giving any error. – Suraj Patil Aug 29 '13 at 12:01
  • 2
    The code is not working properly in VS2005. It's failing silently, quite possibly with a memory leak if the string passed back to you is heap allocated. If you cannot change the native code, then your only option is to use `IntPtr` and `PtrToStringAnsi`. – David Heffernan Aug 29 '13 at 12:04
  • @DavidHeffernan Exactly. And if the calling convention is indeed cdecl, than the caller has to free the allocated memory, which wouldn´t be possible if it´s marshalled to string instead of IntPtr. Just my two cents... – Matze Aug 29 '13 at 13:27
  • 1
    The changing of `string` to `IntPtr` and using `Marshal.PtrToStringAnsi` in a wrapped function worked for me after we got a heap corruption after switching to .Net 4.5 and calling a vendor DLL. – Chris Benard Sep 30 '15 at 20:45
2

This doesn't have anything to do with the framework, everything to do with the Windows version. Your old 2.0 project probably ran on XP. We know that you've got a newer operating system now since 4.5 is not available for XP.

XP has a much more lenient heap manager, it just shrugs when the pinvoke marshaller calls CoTaskMemFree() to release the string and ignores the invalid pointer. Starting with Vista, it no longer shrugs and throws an AccessViolation so misbehaving programs are taken out of their misery. This no-nonsense attitude is one reason that Vista got such a bad name. It is now considered normal, after programmers had enough time to fix the bugs in their programs.

The workaround you found is the correct one, the marshaller isn't going to try to release the memory for an IntPtr. Do note that this will only actually come to a good end if the C code returns a const char* that doesn't need to be released. You have a permanent memory leak if that's not the case.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Thank you sir its helpful to me that you share very helpful information with me, surely it increase my knowledge base. – Suraj Patil Aug 29 '13 at 16:01