将 std::vector 转换为数组然后 p/调用它会在编组期间导致 mscorlib.dll 中的访问冲突异常

Posted

技术标签:

【中文标题】将 std::vector 转换为数组然后 p/调用它会在编组期间导致 mscorlib.dll 中的访问冲突异常【英文标题】:Converting std::vector to array an then p/Invoking it causes a Access Violation Exception in mscorlib.dll during marshalling 【发布时间】:2017-09-13 03:27:45 【问题描述】:

在 C++ 中,我有以下来自 3rd-party 代码的结构:

typedef struct NodeInfoTag

    long lResult;
    int bComplete;
    char *pszNodeAddr;
    char *pszParentAddr;
    RTS_WCHAR *pwszNodeName;
    RTS_WCHAR *pwszDeviceName;
    RTS_WCHAR *pwszVendorName;
    unsigned long ulTargetType; 
    unsigned long ulTargetId;
    unsigned long ulTargetVersion;
    unsigned short wMaxChannels;
NodeInfotyp;

以及对 RTS_WCHAR 的定义:

#   ifndef RTS_WCHAR_DEFINED
#       define RTS_WCHAR_DEFINED
        typedef wchar_t RTS_WCHAR;  /* wide character value */
#   endif

(所以它基本上是一个 wchar_t)

然后我有自己的名为CScanNetworkCallback 的类,它扩展了CPLCHandlerCallback 类,一个来自同一供应商的类:

.h 文件:

class CScanNetworkCallback : public CPLCHandlerCallback

    public:
        bool bScanComplete;
        NodeInfotyp* pNodeInfo;
        NodeInfotyp* pNodeInfoList;
        std::vector<NodeInfotyp> vList;
        CScanNetworkCallback();
        virtual ~CScanNetworkCallback(void);

        virtual long Notify(CPLCHandler *pPlcHandler, CallbackAddInfoTag CallbackAdditionalInfo);
;

实施遵循他们自己的指导方针,并加入了一些我自己的东西:

CScanNetworkCallback::CScanNetworkCallback(void) : CPLCHandlerCallback()

    bScanComplete = false;


CScanNetworkCallback::~CScanNetworkCallback()

    delete pNodeInfo;
    delete pNodeInfoList;


long CScanNetworkCallback::Notify(CPLCHandler *pPlcHandler, CallbackAddInfoTag CallbackAdditionalInfo)

    if (pPlcHandler != NULL)
    
        if (CallbackAdditionalInfo.ulType == PLCH_SCAN_NETWORK_CALLBACK)
        
            pNodeInfo = CallbackAdditionalInfo.AddInf.pNodeInfo;
            if (pNodeInfo->lResult == RESULT_OK)
            
                vList.push_back(*pNodeInfo);
                bScanComplete = false;
            
            else 
            
                pNodeInfoList = &vList[0]; //New pointer points to the vector elements, which will be used as an array later on

                // I have also tried copying it, to the same result:
                //std::copy(vList.begin(), vList.end(), pNodeInfoList);

                bScanComplete = true;
            
        
    
    return RESULT_OK;

所以基本上,上面类中的Notify方法每次在网络中找到一个“节点”时都会被调用,将节点的信息分配给pNodeInfo(请忽略节点是什么,它与ATM无关) .由于在扫描过程中它被调用到网络中的每个节点并且我必须将此信息发送到 C++,所以除了使用 std::vector 存储每个回调信息供以后使用之外,我找不到任何其他方法,因为我不知道编译时会有多少个节点。在找到所有节点后调用else 部分。为了理解 C# 代码,我必须描述一些其他的 p/Invoked C++ 方法的实现:

PROASADLL __declspec(dllexport) void scanNetwork()
    pScanHandler->ScanNetwork(NULL, &scanNetworkCallback);

对象scanNetworkCallback 是静态的。 pScanHandler 是指向来自第 3 方供应商的另一个类的指针,它的 ScanNetwork 方法在单独的线程上运行。在内部(我只知道由于这个 API 指南,我没有它的源代码),只要在网络中找到一个节点,它就会调用 Notify 方法,或者类似的东西

最后:

PROASADLL __declspec(dllexport) NodeInfotyp* getScanResult(int* piSize) 
    *piSize = scanNetworkCallback.vList.size();
    return scanNetworkCallback.pNodeInfoList;

返回指向所有节点信息的指针和作为输出参数的输入量。现在让我们看一下C#代码:

    public static List<NodeInfoTag> AsaScanNetworkAsync()
    
        Console.WriteLine("SCANNING NETWORK");
        scanNetwork(); // C++ Method

        while (!isScanComplete()) // Holds the C# thread until the scan is complete
            Thread.Sleep(50);

        int size = 0;
        IntPtr pointer = getScanResult(out size); // works fine, I get some IntPtr and the correct size
        List<NodeInfoTag> list = Marshaller.MarshalPointerToList<NodeInfoTag>(pointer, size); // PROBLEM!!!

        // Continue doing stuff
    

这是NodeInfoTag 类,以匹配 C++ NodeInfotyp 结构:

[StructLayout(LayoutKind.Sequential)]
public class NodeInfoTag

    public int Result;
    public int Complete;
    [MarshalAs(UnmanagedType.LPStr)] //char*
    public string NodeAddress; 
    [MarshalAs(UnmanagedType.LPStr)] //char*
    public string ParentAddress;
    [MarshalAs(UnmanagedType.LPWStr)] //wchar_t
    public string VendorName;
    public uint TargetType;
    public uint TargetId;
    public uint TargetVersion;
    public short MaxChannels;

这就是我的内存访问冲突:

internal class Marshaller

    public static List<T> MarshalPointerToList<T>(IntPtr pointer, int size)
    
        if (size == 0)
            return null;

        List<T> list = new List<T>();

        var symbolSize = Marshal.SizeOf(typeof(T));
        for (int i = 0; i < size; i++)
        
            var current = (T)Marshal.PtrToStructure(pointer, typeof(T));
            list.Add(current);
            pointer = new IntPtr(pointer.ToInt32() + symbolSize);
        
        return list;
    

该错误专门发生在应该进行封送处理时,在var current = (T)Marshal.PtrToStructure(pointer, typeof(T)); 行。这段 C# 代码过去可以正常工作,但 C++ 部分很糟糕、复杂且容易出错,所以我决定让事情变得更简单,但我终生无法弄清楚为什么我会得到这个异常我确保所有 C++ 资源都可用于 C#,因为出于测试目的,我不会删除 C++ 中的任何内容,并且我只在类中使用具有全局范围的变量,这些变量分配给静态内存。那么,我错过了什么?

编辑:我删除了pNodeInfoList = &amp;vList[0];并重写getScanResult如下:

static NodeInfotyp pNodeInfoList;

//(...)

PROASADLL __declspec(dllexport) NodeInfotyp* getScanResult(int* piSize) 
    *piSize = scanNetworkCallback.vList.size();
    std::move(scanNetworkCallback.vList.begin(), 
scanNetworkCallback.vList.end(), &pNodeInfoList);
    return &pNodeInfoList;

没有骰子。我没有在涉及的任何变量中使用newmalloc,甚至将pNodeInfoList(数组)从类成员更改为全局变量。另外,我正在使用move,正如我被告知的那样,可以用来解决所有权问题。还有其他提示吗?

【问题讨论】:

因为那需要很长时间... 这个库已经开发了一年多了,总的来说,它非常复杂,我不可能为了消除一些错误而重写整个东西。或者,就像在这种情况下发生的那样,我只是想让一种方法更可靠。 【参考方案1】:

所有权不是幼稚 C++ 类型系统的一部分,因此当您删除不属于您的指针或在不放弃的情况下转移所有权时不会出错。

但是,语义上某些值、指针和数据块属于某些类型或值。

在这种情况下,向量拥有它的内存块。没有办法要求它或让它放弃所有权。

调用.data()onky 会为您提供一个指针,它赋予该指针语义所有权。

您将.data() 的返回值存储在成员变量中。您稍后在该成员变量上调用 delete。这向我表明成员变量应该拥有它的数据。所以你双重删除(因为向量和指针都认为它们拥有指向的数据),你的编译器会为你崩溃程序。

您需要重写您的代码,同时考虑到您正在使用的每个内存块的寿命和所有权。一种方法是永远不要直接调用 new、malloc 或 delete 或 free,并始终使用内存管理类型,如 vector 和 unique ptr。避免持久化原始指针,因为它们的所有权语义从类型中不清楚。

【讨论】:

@S.O.我不知道您的编辑与我的 cmets 有什么关系。新代码似乎正在复制随机未初始化的内存。

以上是关于将 std::vector 转换为数组然后 p/调用它会在编组期间导致 mscorlib.dll 中的访问冲突异常的主要内容,如果未能解决你的问题,请参考以下文章

将 std::vector<int> 从原始内存转换为数组[重复]

将 char* 转换为 std::vector

如何将 std::vector<std::string> 转换为 C api 的 char*[] [重复]

std::vector<std::string> 到 char* 数组

将 arma::cx_mat 转换为数组数组

如何将 xarray 转换为 std::vector?