在C 中我具有以下从第三方代码的结构:
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#代码有意义,我必须描述其他一些被调用的C 方法的实现:
PROASADLL __declspec(dllexport) void scanNetwork(){
pScanHandler->ScanNetwork(NULL, &scanNetworkCallback);
}
对象scanNetworkCallback
是静态的。pScanHandler
是第三方供应商的另一个类的指针,其ScanNetwork
方法在单独的线程上运行。在内部(而且我只知道由于此API指南,我没有其源代码(,每当网络中找到一个节点时,它就调用Notify
方法,或者是该效果的某些内容
最后:
PROASADLL __declspec(dllexport) NodeInfotyp* getScanResult(int* piSize) {
*piSize = scanNetworkCallback.vList.size();
return scanNetworkCallback.pNodeInfoList;
}
返回指向所有节点信息的指针,并将其作为OUT参数中的数量。现在让我们看一下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 = &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;
}
没有骰子。我在所涉及的任何变量中不使用new
或malloc
,甚至将PnodeInfolist(数组(从类成员更改为全局变量。另外,正如我被告知,我正在使用move
,可以用来解决所有权问题。还有其他技巧吗?
所有权不是幼稚的C 类型系统的一部分,因此,当您删除您不拥有或不放弃的指针时,您将不会遇到错误。
但是,在语义上某些值,指示器和数据块归某些类型或值所有。
在这种情况下,向量拥有其内存块。没有办法要求它或放弃所有权。
致电.data()
Onky为您提供指针,它确实提供指针语义所有权。
您将.data()
的返回值存储在成员变量中。稍后您在该成员变量上调用delete
。这向我表明,成员变量应该拥有其数据。因此,您可以删除(作为向量和指针都认为他们拥有的数据所指向的数据(,并且您的编译器为您崩溃了。
您需要考虑到与您合作的每一块内存的所有权以及所有权考虑。一种方法是永远不要直接调用新的,malloc或删除或免费,并始终使用内存管理类型,例如向量和唯一的PTR。避免坚持原始指针,因为从类型中不清楚其所有权语义。