首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >作为函数指针的C++/CLI委托(System.AccessViolationException)

作为函数指针的C++/CLI委托(System.AccessViolationException)
EN

Stack Overflow用户
提问于 2011-12-30 10:44:19
回答 2查看 10.5K关注 0票数 20

我一直在尝试使用C++/CLI委托(因为我正在尝试创建一个.NET参考库),并且我遇到了以下问题。

我在C++中定义了一个委托,然后在C#中创建了一个委托的实例,然后通过一个函数指针通过非托管C++调用该委托的实例。这一切都和预期的一样。

用于说明这一点的代码(首先是我的C#)

using System;

namespace TestProgram
{
    class Program
    {
        static void Main(string[] args)
        {
            Library.Test.MessageDelegate messageDelegate = new Library.Test.MessageDelegate(Message);
            Library.Test test = new Library.Test(messageDelegate);
            test.WriteMessage();
            Console.Read();
        }

        static void Message()
        {
            Console.WriteLine(1024);
        }
    }
}

接下来是我的托管c++文件(Managed.cpp)

#include "Unmanaged.hpp"
#include <string>

namespace Library
{
    using namespace System;
    using namespace System::Runtime::InteropServices;

    public ref class Test
    {
    public:
        delegate void MessageDelegate();
    internal:
        MessageDelegate^ Message;
        void* delegatePointer;

    public:
        Test(MessageDelegate^ messageDelegate)
        {
            delegatePointer = (void*)Marshal::GetFunctionPointerForDelegate(messageDelegate).ToPointer();
        }

        void WriteMessage()
        {
            Unmanaged::WriteMessage(delegatePointer);
        }
    };
}

和我的非托管C++文件(Unmanaged.cpp)

#include "Unmanaged.hpp"

namespace Unmanaged
{
    typedef void (*WriteMessageType)();
    WriteMessageType WriteMessageFunc;

    void WriteMessage(void* Function)
    {
        WriteMessageType WriteMessageFunc = (WriteMessageType)(Function);
        WriteMessageFunc();
    }
}

这段代码完全按照预期工作,输出为"1024",因为方法()由指向委托方法的函数指针调用。

当使用带参数的委托应用相同的方法时,我的问题出现了,即:

delegate void MessageDelegate(int number);

我的代码现在如下(C#):

using System;

namespace AddProgram
{
    class Program
    {
        static void Main(string[] args)
        {
            Library.Test.MessageDelegate messageDelegate = new Library.Test.MessageDelegate(Message);
            Library.Test test = new Library.Test(messageDelegate);
            test.WriteMessage(1024);
            Console.Read();
        }

        static void Message(int number)
        {
            Console.WriteLine(number);
        }
    }
}

我的托管C++文件:

#include "Unmanaged.hpp"
#include <string>

namespace Library
{
    using namespace System;
    using namespace System::Runtime::InteropServices;

    public ref class Test
    {
    public:
        delegate void MessageDelegate(int number);
    internal:
        MessageDelegate^ Message;
        void* delegatePointer;

    public:
        Test(MessageDelegate^ messageDelegate)
        {
            delegatePointer = (void*)Marshal::GetFunctionPointerForDelegate(messageDelegate).ToPointer();
        }

        void WriteMessage(int number)
        {
            Unmanaged::WriteMessage(delegatePointer, number);
        }
    };
}

和我的非托管C++文件:

#include "Unmanaged.hpp"

namespace Unmanaged
{
    typedef void (*WriteMessageType)(int number);
    WriteMessageType WriteMessageFunc;

    void WriteMessage(void* Function, int number)
    {
        WriteMessageType WriteMessageFunc = (WriteMessageType)(Function);
        WriteMessageFunc(number);
    }
}

当我执行该程序时,我得到以下错误:

在非托管库Test.dll中出现'System.AccessViolationException‘类型的未经处理的异常

附加信息:已尝试读取或写入受保护的内存。这通常表示其他内存已损坏。

顺便说一句,控制台窗口确实显示1024,但随后是一个随机的int (~1000000),然后我得到了错误。

我可以开始想象我得到这个错误的一些原因,但我不确定,并且很难找到。如果有人能告诉我为什么会出现这个错误,以及我可以做些什么来修复它,我将不胜感激。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2011-12-30 19:52:00

 void WriteMessage(void* Function, int number)

将函数指针作为void*传递是一个非常糟糕的想法。它可以防止编译器检查你是否做错了什么。虽然编译器在这种特定情况下无法检测到,但还是有一些问题。委托作为使用__stdcall调用约定的函数指针进行封送处理,而实际的函数指针使用__cdecl调用约定(本机代码的默认约定)。这会导致堆栈在调用时变得不平衡。

您可以通过将[UnmanagedFunctionPointer] attribute应用于委托声明来修复它,指定CallingConvention::Cdecl。

票数 13
EN

Stack Overflow用户

发布于 2011-12-30 10:59:44

从委托创建的函数指针对垃圾回收器是不可见的,并且在可达性分析期间不会被计算在内。

来自the documentation

必须手动阻止垃圾回收器从托管代码回收委托。垃圾回收器不跟踪对非托管代码的引用sic。

如果委托被收集,函数指针就会悬空,您的程序将表现不佳。访问冲突是更有可能的结果之一,但不是唯一的可能性。如果用于包含本机/托管跳床的内存被重用于其他一些数据,CPU可能会尝试将其解释为指令,这可能意味着任何事情。

解决方案是保持委托的可访问性,例如,通过C++/CLI gcroot类,它是.NET GCHandle的一个薄包装器。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/8675638

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档