在64位Windows上将字符串从C++返回到C#时,如何防止AccessViolationException?

And*_*rew 9 c++ pinvoke swig .net-4.0 access-violation

我正在使用第三方专有DLL,我无法获得源代码.但是,使用SWIG 1.3.39自动生成的包装代码可供我使用.包装器代码包含一个C++文件,该文件编译(使用一些描述DLL的头文件)到DLL和一个C#项目,它使PInvoke调用C++包装器DLL.

根据我对供应商文档的解释,我已将解决方案中的所有内容编译为x86或x64,具体取决于目标平台.供应商提供专有DLL的32位和64位版本,并且我确保我使用正确的版本给定构建.我的机器是32位的.在我的机器上,在发布版本或调试版本中测试我的应用程序的x86版本似乎工作正常.但是,在64位上,应用程序在调试模式下工作,但在发布模式下因System.AccessViolationException而失败.

我已经阅读了这篇很好的博客文章,似乎很好地描述了调试与发布问题,以及引发博客文章的这个问题和答案.但是,我不确定如何在这种情况下解决问题.

AccessViolationException似乎是在第一次从C++包装器返回(或尝试返回)任何实际长度的字符串时发生的.这是违规的C#代码:

// 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);
Run Code Online (Sandbox Code Playgroud)

来自C++包装器的麻烦的C++代码:

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;
}
Run Code Online (Sandbox Code Playgroud)

SWIGEXPORT已被定义为__declspec(dllexport).在调试中,我发现SWIG_csharp_string_callback,定义为:

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

被设置为委托(在C#包装器中):

static string CreateString(string cString) {
  return cString;
}
Run Code Online (Sandbox Code Playgroud)

我已经尝试搞乱这段代码使用构造,如Marshal.PtrToStringAut无济于事.如何解决和/或解决此问题?

ild*_*arn 4

执行此操作的正确方法是让托管代码分配非托管代码将写入的缓冲区(字符串数据)。假设由于某种原因这是不切实际的,您需要做的是以可以从托管代码中释放的方式分配字符串数据。

通常的方法是使用 分配内存LocalAlloc,然后可以使用 来从托管代码中释放内存Marshal.FreeHGlobal。这样你就不再需要(笨拙且明显不起作用的)SWIG_csharp_string_callbackCreateString

C++代码

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;
}
Run Code Online (Sandbox Code Playgroud)

C# 代码

// 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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

顺便说一句,您展示的那一小段 C++ 代码是一个可怕的 C-with-classes 遗迹;如果这代表了代码库的其余部分,那么,哇...:-/