1

I am currently working on migrating our company's x86 software to x64 and have encountered a snag. Our method from our unmanaged dll that returns a char* crashes with 0xc0000374 when the solution platform is switched to x64 and I'd like to know why.

C++ Native Code:

#include "stdafx.h"
#include <windows.h>
#include "windef.h"
#define VERSION "3.3.12"

extern "C"
{
  __declspec(dllexport) char* WINAPI GetMyVersion()
  {
    return (char*)VERSION;
  }
}

C# DllImport:

using System;
using System.Runtime.InteropServices;
using System.Security;

namespace InteropTest.Adapter
{
    [SuppressUnmanagedCodeSecurity]
    public unsafe class MyAdapter
    {
        private const string DLLName = "VersionNumber";

        private const string EntryPointName = "GetMyVersion";

        [DllImport(DLLName, EntryPoint = EntryPointName, CharSet = CharSet.Ansi)]
        internal static extern IntPtr GetMyVersionPtr();

        [DllImport(DLLName, EntryPoint = EntryPointName, CharSet = CharSet.Ansi)]
        internal static extern string GetMyVersionString();
    }
}

GetMyVersionString() only works on x86 platforms and crashes on x64 ones. However, I am able to get the IntPtr and to use the Marshal class to convert it to a managed string on both x86 and x64 like such:

using System.Runtime.InteropServices;
using System.Windows;
using InteropTest.Adapter;

namespace InteropTest
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            var ptr = MyAdapter.GetMyVersionPtr();
            var convertedString = Marshal.PtrToStringAnsi(ptr);

            // This crashes in x64 build
            var directString = MyAdapter.GetMyVersionString();

            VersionTextBlock.Text = convertedString;
        }
    }
}

I have also tried specifying the MarshalAs Attribute for the return value explicitly without success. It seems like the 64 bit char* is used as a 32 bit pointer if you do not explicitly use the Marshal class. Is this just a bug in .NET or did I do something wrong? From My point of view, converting with the Marshal class should be equivalent to using the MarshalAs Attribute.

EDIT:

Here is a sample project I've made to replicate the bug: https://github.com/chenhuang444/dotNet40InteropCrash

EDIT 2:

The program and exits with code 0xc0000374 without native debugging and only states "InteropTest.exe has triggered a breakpoint" if I have native debugging on, with a stack trace of:

    ntdll.dll!00007ffc0fdc4cfa()    Unknown
    ntdll.dll!00007ffc0fdcc806()    Unknown
    ntdll.dll!00007ffc0fdccad1()    Unknown
    ntdll.dll!00007ffc0fd69a55()    Unknown
    ntdll.dll!00007ffc0fd77347()    Unknown
    mscorlib.ni.dll!00007ffbd566759e()  Unknown
    [Managed to Native Transition]  
>   InteropTest.exe!InteropTest.MainWindow.OnLoaded(object sender, System.Windows.RoutedEventArgs e) Line 23    C#
    PresentationCore.dll!System.Windows.EventRoute.InvokeHandlersImpl(object source, System.Windows.RoutedEventArgs args, bool reRaised)    Unknown

EDIT 3:

The unmanaged C++ code is a project in our solution and the x64/x86 configurations are set up, similar to the example project linked above.

Chen Huang
  • 80
  • 6

1 Answers1

1

Well, your version is not a "string", it's indeed a pointer to raw memory. The .NET framework will try to deallocate this pointer if you tell it it's a string.

So, you have two options:

  • use an IntPtr like you do, which is fine.
  • allocate a real string that .NET can understand, using the COM allocator, so in your case, your C++ code could be replace by this:

.

__declspec(dllexport) char* WINAPI GetMyVersion() {
      char* p = (char*)CoTaskMemAlloc(7);
      CopyMemory(p, VERSION, 7);
      return p;
}

note: you can also use BSTRs.

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • So the CLR assumes that is a DllImported method returning a string needs to have the memeory freed using CoTaskMemFree and does this for me: https://stackoverflow.com/questions/370079/pinvoke-for-c-function-that-returns-char But the question of how our incorrect code works in the x86 platform still exists. – Chen Huang Oct 12 '18 at 00:29
  • @ChenHuang - it's not because it does not crash that it "works". Many bugs live forever w/o being ever noticed. – Simon Mourier Oct 12 '18 at 06:42