10

I am using a third-party, proprietary DLL for which the source code is not available to me. Wrapper code that appears to have been auto-generated using SWIG 1.3.39 is, however, available to me. The wrapper code consists of a C++ file that compiles (using some headers that describe the DLL) to a DLL and of a C# project that makes PInvoke calls to the C++ wrapper DLL.

Per my interpretation of the vendor's documentation, I have compiled everything in the solution as either x86 or x64, depending on the target platform. The vendor provides both 32-bit and 64-bit versions of the proprietary DLL, and I have ensured that I use the correct one for the given build. My machine is 32-bit. Testing the x86 version of my application on my machine, in either release or debug builds, seems to work fine. On 64-bit, however, the application works in Debug mode but fails with System.AccessViolationException in Release mode.

I have read this nice blog entry that seems to describe the Debug vs. Release problem well, as well as this question and answer that gave rise to the blog post. However, I am unsure how to troubleshoot the problem in this case.

The AccessViolationException seems to occur the first time a string of any real length is returned (or attempted to be returned) from the C++ wrapper. Here is the offending C# code:

// In one file of the C# wrapper:
public string GetKey()
{
    // swigCPtr is a HandleRef to an object already created
    string ret = csWrapperPINVOKE.mdMUHybrid_GetKey(swigCPtr);
    return ret;
}

// In the csWrapperPINVOKE class in another file in the C# wrapper:
[DllImport("csWrapper.dll", EntryPoint="CSharp_mdMUHybrid_GetKey")]
public static extern StringBuilder mdMUHybrid_GetKey(HandleRef jarg1);

And the troublesome C++ code from the C++ wrapper:

SWIGEXPORT char * SWIGSTDCALL CSharp_mdMUHybrid_GetKey(void * jarg1) {
  char * jresult ;
  mdMUHybrid *arg1 = (mdMUHybrid *) 0 ;
  char *result = 0 ;

  arg1 = (mdMUHybrid *)jarg1; 
  result = (char *)(arg1)->GetKey();
  jresult = SWIG_csharp_string_callback((const char *)result); 
  return jresult;
}

SWIGEXPORT had already been defined as __declspec(dllexport). In debugging, I discovered that SWIG_csharp_string_callback, defined as:

/* Callback for returning strings to C# without leaking memory */
typedef char * (SWIGSTDCALL* SWIG_CSharpStringHelperCallback)(const char *);
static SWIG_CSharpStringHelperCallback SWIG_csharp_string_callback = NULL;

was being set to the delegate (in the C# wrapper):

static string CreateString(string cString) {
  return cString;
}

I have tried messing with this code to use constructs such as Marshal.PtrToStringAut to no avail. How do I troubleshoot and/or fix this problem?

Community
  • 1
  • 1
Andrew
  • 14,325
  • 4
  • 43
  • 64
  • I ran into the exact same problem and this post really fixed it. However , I want to know how did u find the offending code. Because when I run my project on release mode I was not getting any exception. The "Access Violation" only comes into picture when I browse to release folder and execute the exe directly from there ! – Simsons Jun 13 '12 at 05:55
  • @Subhen It's been some time, and I have since changed employers, so I no longer have access to this code. If I recall, I was running this on a Windows Server instance, and I was able to use the remote debugger somehow. I could be mistaken on that, but I know it took me a day or two to find where this was breaking. Logging can also help, of course, if you can record where exactly you are in the code when you get the exception. – Andrew Jun 13 '12 at 16:04

1 Answers1

4

The proper way to do this is to have your managed code allocate the buffer to which the unmanaged code will be writing (the string data). Assuming that's impractical for some reason, what you need to do is allocate the string data in a way that can be deallocated from managed code.

The usual approach is to allocate the memory with LocalAlloc, which can then be deallocated from managed code using Marshal.FreeHGlobal. This way you no longer need the (kludgy and obviously non-functional) SWIG_csharp_string_callback and CreateString.

C++ code:

SWIGEXPORT HLOCAL SWIGSTDCALL CSharp_mdMUHybrid_GetKey(mdMUHybrid* jarg1)
{
    char const* const str = jarg1->GetKey();
    std::size_t const len = std::strlen(str);
    HLOCAL const result = ::LocalAlloc(LPTR, len + 1u);
    if (result)
        std::strncpy(static_cast<char*>(result), str, len);
    return result;
}

C# code:

// In one file of the C# wrapper:
public string GetKey()
{
    return csWrapperPINVOKE.mdMUHybrid_GetKey(swigCPtr);
}

// ...

public static class csWrapperPINVOKE
{
    // ...

    [DllImport("csWrapper.dll")]
    private static extern IntPtr CSharp_mdMUHybrid_GetKey(HandleRef jarg1);

    public static string mdMUHybrid_GetKey(HandleRef jarg1)
    {
        var ptr = CSharp_mdMUHybrid_GetKey(jarg1);
        try
        {
            return Marshal.PtrToStringAnsi(ptr);
        }
        finally
        {
            Marshal.FreeHGlobal(ptr);
        }
    }
}

As an aside, that tiny snippet of C++ code you showed is a hideous C-with-classes relic; if that's representative of the rest of the codebase, then just, wow... :-/

ildjarn
  • 62,044
  • 9
  • 127
  • 211
  • Well, in all fairness, the code _does_ seem to be autogenerated (and vendor-supplied, not internal, for which we can be thankful). Thanks for your answer; I'll give it a try tomorrow if I can. – Andrew May 03 '11 at 04:01
  • @ildjarm This worked great. Thank you! To inform the Google crowd, I did make a couple adjustments (using `strncpy_s` just to get rid of the warning for example) and putting most of this into a separate function so I could use it with all functions that (used to) return a `char*`. On the C# side, I did the same kind of thing. In my non-existent spare time, I should consider contributing to swig, since I see from another project that at least version 2.0.1 produces similar code. Thanks again! – Andrew May 03 '11 at 19:41
  • The function CSharp_mdMUHybrid_GetKey is really not efficient, this extra layer should be avoided if possible, otherwise, the Interop with C++ is just too expensive. I don't have experience with SWIG, but can you call jarg1->GetKey() from C# using SWIG via P/Invoke? – xInterop Apr 20 '13 at 07:02