簡體   English   中英

在 64 位 Windows 上將字符串從 C++ 返回到 C# 時,如何防止 AccessViolationException?

[英]How do I prevent AccessViolationException when returning a string from C++ to C# on 64-bit Windows?

我正在使用第三方專有的 DLL,我無法獲得其源代碼。 但是,我可以使用似乎是使用 SWIG 1.3.39 自動生成的包裝器代碼。 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.

根據我對供應商文檔的解釋,我已將解決方案中的所有內容編譯為 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之類的構造,但無濟於事。 如何解決和/或解決此問題?

正確的方法是讓您的托管代碼分配非托管代碼將寫入的緩沖區(字符串數據)。 假設由於某種原因這是不切實際的,您需要做的是以可以從托管代碼中釋放的方式分配字符串數據。

通常的方法是使用LocalAlloc分配 memory ,然后可以使用Marshal.FreeHGlobal從托管代碼中釋放。 這樣你就不再需要(kludgy 並且顯然沒有功能的) 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 類遺物; 如果那是代碼庫的 rest 的代表,那么,哇... :-/

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM