1

I am currently working on a project with really short deadline, so I don't have much time to understand everything. Also, I am not an expert in C++ development and memory management.

So, what I am trying to do is to create a DLL in with both C and C++ code. Then, I would like to call this DLL in a C# code. Currently, the communication between C++ and C# is OK. The problem comes up when I try to transfer a string from the DLL to the C# code. The error is this one :

System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
   at Microsoft.Win32.Win32Native.CoTaskMemFree(IntPtr ptr)
   at System.StubHelpers.CSTRMarshaler.ClearNative(IntPtr pNative)
   at NMSPRecognitionWrapper.Program.GetResultsExt()
   at NMSPRecognitionWrapper.Program.<Main>b__0() in <my dir>\Program.cs:line 54
   at NMSPRecognitionWrapper.Program.StartRecognitionExt()
   at NMSPRecognitionWrapper.Program.Main(String[] args) in <my dir>\Program.cs:line 60

Also, I can give you some piece of code below (really simplified !). Actually, the C++ expose two methods : StartRecognition() launch operations to get some data from microphone, then process them and store the results. GetResults() return an instance of the results previously stored. The WrapperCallback() allows the C# part to be called when a Result is able for processing. The C# part, when the Callback is called, will ask to get the results using the GetResults() method.

I know the architecture may seem really inappropriate in this presentation, but I don't want to explain the whole project to validate the model, please be sure everything is correct.

To finish, the problem is when the C# callback call the GetResults() method. Trying to access to the resultsForCS seems to be impossible from the C#.

C++ part - header

// NMSPRecognitionLib.h

#pragma once
#include <iostream>

using namespace std;

extern "C" __declspec(dllexport) char* GetResults();
extern "C" static void DoWork();
extern "C" __declspec(dllexport) void StartRecognition();

C++ part - sources

#include "stdafx.h"
#include "NMSPRecognitionLib.h"

static char * resultsForCS;

static SUCCESS ProcessResult(NMSPCONNECTION_OBJECTS *pNmspConnectionObjects, LH_OBJECT hResult)
{
    [...]
    char* szResult;
    [...]

    resultsForCS = szResult;

    DoWork();

    [...]
    return Success;

    error:
        return Failure;
} /* End of ProcessResult */


extern "C" __declspec(dllexport) char* GetResults()
{
    return resultsForCS;
}

extern "C"
{
    typedef void (*callback_function)();
    callback_function gCBF;

    __declspec(dllexport) void WrapperCallback(callback_function callback) {
        gCBF = callback;
    }

    static void DoWork() {
        gCBF();
    }
}

extern "C" __declspec(dllexport) void StartRecognition()
{
    char* argv[] = { "path", "params" };
    entryPoint(2, argv);
}

C# part

class Program
{
    [DllImport("NMSPRecognitionLib.dll", EntryPoint = "GetResults")]
    [return: MarshalAs(UnmanagedType.LPStr)]
    public static extern string GetResultsExt();

    public delegate void message_callback_delegate();

    [DllImport("NMSPRecognitionLib.dll", EntryPoint = "WrapperCallback")]
    public static extern void WrapperCallbackExt(message_callback_delegate callback);

    [DllImport("NMSPRecognitionLib.dll", EntryPoint = "StartRecognition")]
    public static extern void StartRecognitionExt();

    static void Main(string[] args)
    {
        WrapperCallbackExt(
            delegate()
            {
                Console.WriteLine(GetResultsExt());
            }
        );

        StartRecognitionExt();

        Console.WriteLine("\nPress any key to finish... ");
        var nothing = Console.ReadLine();
    }
}

I understand that the problem comes because I am using a pointer to store the results (char *), but I actually don't know how to do this in another way. The szResults type is char * too and I can't change this !

2
  • The first thing I notice is your CallingConvention does not match. The default CallingConvention for C# is StdCall while the default CallingConvention is Cdecl. You are going to need to figure out how to resolve the char pointer yourself since the correct way to do that is well documented. This question deserves some more research on your part, the CallingConvention, is likely only one problem you have. You should simple use a reference to a char[] instead of a string array. Commented May 23, 2013 at 20:01
  • Thank you very much for this precision ! I will keep this idea in mind. Actually, with the accepted answer, my code is now working. I KNOW it's not the best solution, but it's a working one... I don't have much time, so I will keep this solution for now and come back to your idea later. Anyway, thank you for the information. Commented May 24, 2013 at 7:18

3 Answers 3

7

Yes, the return type is the problem. The pinvoke marshaller must do something to release the memory that was allocated for the string. The contract is that memory allocations that need to be released by the caller must be allocated from the COM heap. CoTaskMemAlloc() in native code, also exposed in .NET as Marshal.AllocCoTaskMem().

This rarely comes to a good end, most native code allocates with malloc() or ::operator new, allocating from a heap that's created by the C runtime library. The wrong heap. So inevitably the CoTaskMemFree() call will fail. Ignored silently in Windows XP and earlier, a kaboom on Vista and up.

You must stop the pinvoke marshaller from trying to release the memory. Do so by declaring the return value as IntPtr. And use Marshal.PtrToStringAnsi() to recover the string.

You still have a Big Problem, the kind of problem that bedevils any native code that tries to use this function as well. You still have a string buffer that needs to be released. You cannot do that from C#, you can't pinvoke the correct version of free() or ::operator delete. A memory leak is inevitable. The only thing you can hope for is that the native code takes care of it, somehow. If it doesn't then you must use C++/CLI to interop with it. With the additional requirement that the native code needs to be rebuilt with the same compiler so that it uses the same shared CRT. Code that's difficult to use correctly from native code is also hard to pinvoke. That's a design flaw, always allow the caller to pass a buffer to be filled in so there's never a question who owns the memory.

Sign up to request clarification or add additional context in comments.

1 Comment

Your response is really clear and worked perfectly ! Thank you.
4

Looking at:

at Microsoft.Win32.Win32Native.CoTaskMemFree(IntPtr ptr)
at System.StubHelpers.CSTRMarshaler.ClearNative(IntPtr pNative)
at NMSPRecognitionWrapper.Program.GetResultsExt()

I can see that your callback is called, but the runtime tries to free some memory. I think it assumes your pointer would be to com memory. Try converting the string yourself, it is easy!

[DllImport("NMSPRecognitionLib.dll", EntryPoint = "GetResults")]
public static extern IntPtr GetResultsExt();

[...]

string result = Marshal.PtrToStringAnsi(GetResultsExt())

No 100% guarantee, but worth a try.

Comments

-1

I have found that it is usually easier to write a wrapper in C++/CLI around the C++ native code. A C++/CLI class can directly call and use native C++, but is accessible from C# (and any .Net language). In my experience, using DLLImport as you do leads to hard to debug and find errors.

5 Comments

I am not the downvoter, but I think it's because your answer do not help me to resolve my problem ;)
I downvoted because "you can always implement it in this other language" does not answer the question. It's a alternative yes but not a solution to the OP's immediate problem.
He already uses C++ and C#, so it's not another language. It provides a simpler and less error-prone way to solve this and similar problems in the future.
Hmm, no it is not simple to wrap C++ code. It is impossible. Only C code, or C++ code that was made to look like C and doesn't use classes, is easy to wrap. Using C++/CLI is a fine solution.
@Ramhound YMMV - I have made much better experiences with C++/CLI wrapping than directly using pinvoke. Especially when it comes to finding problems and errors.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.