1

I am calling an un-managed and very simple C++ function (located in JNIDiskInfoDll.dll) from a C# managed one as follows:

C++:

#include "stdafx.h"
#include "AtaSmart.h"

#include <iostream>
#include <string.h>

extern "C" __declspec(dllexport) char* __cdecl getSerial(LPTSTR inCStrIn)
{
    return "abcdefg";
}

C#:

using System;
using System.Runtime.InteropServices;

namespace HardInfoRetriever
{
    class DiskInfoRetreiver
    {

        [DllImport("C:\\Users\\User1\\Documents\\Visual Studio 2017\\Projects\\HardInfoRetriever\\Debug\\JNIDiskInfoDll.dll",
            EntryPoint = "getSerial", CallingConvention = CallingConvention.Cdecl,
            BestFitMapping = false, ThrowOnUnmappableChar = true, CharSet = CharSet.Ansi)]

        public static extern String getSerial([MarshalAs(UnmanagedType.LPTStr)]String _driveletter_);
        public static String getSerialNumber(String driveletter)
        {
            try
            {
                return getSerial(driveletter);
            }
            catch (Exception e)
            {
                throw e;
            }
        }
    }
}

My problem is that after running the application I get two successive errors saying projectName.exe has triggered a breakpoint and Unhandled exception at 0x77110E23 (ntdll.dll) in projectName.exe: 0xC0000374: A heap has been corrupted (parameters: 0x7712E930).. Knowing that although I'm getting these errors, the function is still returning the desired output.

Please note that the getSerial C function has the LPTSTR inCStrIn parameter since I was using it before I did remove the whole code (keeping only return "abcdefg";) where the error persists.

I don't know what could be the problem here. I tried to change the Charset in the DllImport into Unidcode, but still getting same errors. Any help please?

1201ProgramAlarm
  • 32,384
  • 7
  • 42
  • 56
peter bence
  • 782
  • 3
  • 14
  • 34
  • 1
    Don't return pointers. The sure way to get this to work is for the caller to provide the buffer, and the function copies the string to the buffer. That's how all Windows API functions handle strings. – PaulMcKenzie May 21 '19 at 16:01
  • @paulMckenzie thanks for reply, do you have any external link that gives an example of this, or could you post an answer on this if possible? – peter bence May 21 '19 at 16:09
  • 2
    Look at the GetWindowText API function as an example. – PaulMcKenzie May 21 '19 at 16:14
  • Your `getSerial` function returns pointer to some address on the stack which is initialized with "abcdefg" (in C++ you have to specify explicitly by `new` operator your intention to create an object in heap). Obviously stack value doesn't outlive the function where it's set, so in calling code you're getting pointer to some irrelevant memory area. – Dmytro Mukalov May 21 '19 at 16:38
  • 1
    This function is very hard to use reliably in a C++ program, that doesn't get better when you pinvoke it. At issue is who is responsible for releasing the storage for the string. A C++ programmer would not know unless he reads a manual or he looks at the source code. The marshaller can do neither and assumes the function is well-behaved. It calls the equivalent of Marshal.FreeCoTaskMem(). Kaboom, it isn't allocated in that heap. As-is you'd need to change the return type to IntPtr and use Marshal.PtrToStringAnsi(). Hopefully it stays a literal in the C++ code. – Hans Passant May 21 '19 at 16:42

1 Answers1

0

Thanks to the comment of @PaulMcKnezie saying:

Don't return pointers. The sure way to get this to work is for the caller to provide the buffer, and the function copies the string to the buffer.

So I used the same concept as the same method mentioned here that is about

Returning a String With a BSTR * Parameter.

Finally, below is the final working version of my code:

C++:

extern "C" __declspec(dllexport) HRESULT __cdecl getSerial(LPTSTR inCStrIn, BSTR* inCStrOut)
{
    *inCStrOut= SysAllocString(L"abcdefg"); 
    return S_OK;
}

C#:

using System;
using System.Runtime.InteropServices;

namespace HardInfoRetriever
{
    class DiskInfoRetreiver
    {

        [DllImport("C:\\Users\\User1\\Documents\\Visual Studio 2017\\Projects\\HardInfoRetriever\\Debug\\JNIDiskInfoDll.dll",
            EntryPoint = "getSerial", CallingConvention = CallingConvention.Cdecl,
            BestFitMapping = false, ThrowOnUnmappableChar = true, CharSet = CharSet.Ansi)]
        //[return: MarshalAs(UnmanagedType.LPTStr)]
        public static extern int getSerial([MarshalAs(UnmanagedType.LPTStr)] string _driveletter_, [MarshalAs(UnmanagedType.BStr)] out string serial);
        public static String getSerialNumber(string letter)
        {
            try
            {
                string serial;
                int result =  getSerial(letter, out serial);
                if (result == 0)
                {
                    return serial;
                }
                return null;
            }
            catch (Exception e)
            {
                throw e;
            }
        }
    }
}
peter bence
  • 782
  • 3
  • 14
  • 34