【问题标题】:AccessViolationException when calling C++/CLI wrapper from C#从 C# 调用 C++/CLI 包装器时出现 AccessViolationException
【发布时间】:2014-03-13 14:13:03
【问题描述】:

我正在尝试创建一个 C++/CLI 包装器,用于将类对象从非托管 C++ DLL 传递到托管 C# 代码(随后在网页上显示对象的内容)。我在非托管 C++ 代码中有这个函数:

ProbeState _cdecl ManagerAPI::getProbeState()
{
    ProbeState ps = psdao.getLastProbeStateByProbeId(1);
    return ps;
}

我在 C++/CLI 包装器中调用函数:

using namespace System;

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#include "../ManagerApp/ProbeState.h"

typedef ProbeState(*PSFunc)(void);

public ref class ManagerAPIWrapper
{
private:
    HINSTANCE managerApp;

public:
    ManagerAPIWrapper()
    {
        managerApp = LoadLibrary(L"ManagerApp.dll");
    }

    System::String^ testFunc()
    { 
        PSFunc psFunc = (PSFunc)GetProcAddress(managerApp, "?getProbeState@ManagerAPI@@QAA?AVProbeState@@XZ");

        ProbeState *ps = new ProbeState(psFunc());

        System::String ^s = gcnew System::String(ps->getName().c_str());

        delete ps;

        return s;
    }
};

最后我从我的 C# 控制器调用包装器:

ManagerAPIWrapper.ManagerAPIWrapper wrapper = new ManagerAPIWrapper.ManagerAPIWrapper();
ViewBag.DllMessage = wrapper.testFunc();

它总是在 ProbeState *ps = new ProbeState(psFunc());

行抛出异常

不过,奇怪的是,当我将 C++/CLI 包装器编译为控制台应用程序并添加了主函数时:

int _tmain(int argc, _TCHAR* argv[])
{
ManagerAPIWrapper::ManagerAPIWrapper wrapper;

System::Console::WriteLine(wrapper.testFunc());

getchar();

return 0;
}

这段代码运行良好,并打印出 C++ DLL 从数据库中检索到的状态名称。 C++/CLI 如何在控制台应用程序中工作并在从 C# 调用时抛出异常?

P.S.:包装器是使用 /clr 选项编译的。当我使用 /clr:pure 编译包装器时,异常与 C# 调用相同。这是否意味着当包装器在 C# 应用程序中编译并从 C# 应用程序调用时,它采用纯选项?

包装器旨在在 C++ 和 C# 之间转换数据,因此根据我的观点,它不应该在 C# 应用程序中使用更严格的选项进行编译。有没有办法告诉 C# 编译器这个程序集包含混合代码?

【问题讨论】:

  • 可能是_cdecl——如果是这种情况,在 C# 中添加一个编组属性应该可以解决它:stackoverflow.com/questions/5155180/…
  • 跨模块边界返回 C++ 对象是非常风险的。使用完全相同的编译器版本和完全相同的设置构建此 DLL 并且 CRT 是共享的(/MD 选项)是绝对关键的。 GetProcAddress 的恶作剧和崩溃强烈表明情况并非如此。您必须重建 DLL。
  • 那个 _cdecl 属性是我徒劳地努力让代码工作的结果......所以你认为我应该没有它吗?
  • 好的,我用显式的_cdecl 尝试了控制台输出,但它也因 AccesViolationEx 失败,所以这绝对是个坏主意。但是,进入 C# 代码的接口仍然不起作用。
  • 我做了一些调试,发现 LoadLibrary 调用失败并出现错误 126 - 未找到模块。但是问题来了:为什么调用在 DLL(由 C# 代码加载)中失败时可以与控制台应用程序一起使用?

标签: c# c++-cli wrapper


【解决方案1】:

好的,我终于通过了。在用try&fail方式寻找解决方案花费了很多小时后,我尝试先从C#代码直接调用非托管DLL中的函数,然后调用包装器的构造函数,该构造函数在LoadLibrary调用中成功。 C# 控制器中的代码现在如下所示:

 [DllImport("C:\\ManagerApp.dll", CharSet = CharSet.Unicode, 
      EntryPoint = "?initFunc@ManagerAPI@@QAEHXZ")]
    private static extern int initFunc();

    public ActionResult APITest()
    {
        ViewBag.Message = "API output test page.";

        if (initFunc() == 0)
        {
            ViewBag.Error = "Could not initialize the library.";

            return View();
        }

        ManagerAPIWrapper.ManagerAPIWrapper wrapper = new ManagerAPIWrapper.ManagerAPIWrapper();
        ViewBag.DllMessage = wrapper.testFunc();

        return View();
    }

我认为在非托管 DLL 上添加对包装 DLL 的依赖可能会有所帮助,因此无需调用 initFunc

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-07-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多