从 C++ 返回字符串时如何防止 AccessViolationException在 64 位 Windows 上转换为 C#?

发布于 2024-11-04 06:48:03 字数 2198 浏览 3 评论 0原文

我使用的是第三方专有 DLL,我无法获得其源代码。不过,我可以使用似乎是使用 SWIG 1.3.39 自动生成的包装器代码。包装器代码由一个编译(使用一些描述 DLL 的头文件)为 DLL 的 C++ 文件和一个对 C++ 包装器 DLL 进行 PInvoke 调用的 C# 项目组成。

根据我对供应商文档的解释,我已将解决方案中的所有内容编译为 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);

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;
}

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;

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

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

我尝试修改此代码以使用诸如 Marshal.PtrToStringAut< /code> 无济于事。我该如何排查和/或解决这个问题?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

醉殇 2024-11-11 06:48:04

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

通常的方法是使用 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;
}

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);
        }
    }
}

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

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... :-/

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文