求助:C# 调用C++的dll的回调函数的结构体数组的问题,为什么只得到数组的一条记录。

DLL接口:
struct CardEvent
{
DWORD CardNo; //卡号
INT32 EvtYear; //年
INT32 EvtMonth; //月
INT32 EvtDay; //日
INT32 EvtHour; //小时
INT32 EvtMin; //分钟
INT32 EvtSec; //秒
INT32 EvtDoor; 门序号
INT32 EvtType; 事件类别
};
回调函数:hDev为收到事件的设备,pEvt:事件信息数组, nEvtCnt 事件个数
typedef VOID (CALLBACK* lpOnRecvCardEvent)(HANDLE hDev, CardEvent* pEvt, INT32 nEvtCnt);

VOID ACSSetupCardEventCallback (lpOnRecvCardEvent lpCall); :设定系统接收到卡事件后的回调函数.lpCall:回调函数地址。返回:无。
C# 定义及调用:
public delegate void CardEvent(IntPtr hDev, strcNACSCardEvent[] phld, Int32 nEvtCnt);
[DllImport("Comm.dll")] //设定系统接收到卡事件后的回调函数
private extern static void ACSSetupCardEventCallback(CardEvent lpCall);

public struct CardEvent
{
public uint CardNo; //卡号
public Int32 EvtYear; //年
public Int32 EvtMonth; //月
public Int32 EvtDay; //日
public Int32 EvtHour; //小时
public Int32 EvtMin; //分钟
public Int32 EvtSec; //秒
public Int32 EvtDoor; //门序号
public Int32 EvtType;
}
public static void themain(IntPtr hDev, CardEvent[] phld1, Int32 nEvtCnt)
{
for (int i = 0; i < nEvtCnt; i++)
{
System.Console.Write("事件数:" + nEvtCnt);
System.Console.WriteLine();
}
for (int i = 0; i < phld1.Length; i++)
{
string time = phld1[i].EvtYear + "-" + phld1[i].EvtMonth + "-" + phld1[i].EvtDay + " " + phld1[i].EvtHour + ":" + phld1[i].EvtMin + ":" + phld1[i].EvtSec;
System.Console.Write("时间:" + time);
System.Console.WriteLine();
}
}

static void Main(string[] args)
{
...........//前面与设备的连接
lCardEvent myhuidiao = new CardEvent(themain);
ACSSetupCardEventCallback(myhuidiao);
}

运行后,返回的事件个数nEvtCnt 是正确的(有多少显示为多少),但返回结构体数组则只有第一条记录。不知道为什么?求高手解答,高分感谢啊。

关键是 lpOnRecvCardEvent 这个函数的第二个参数是个 C++ 指针。
楼主在 C# 中声明这个导出函数时,使用了并不对应的 CardEvent[] C# 数组类型。
正确的做法是: 在 C# 声明 lpOnRecvCardEvent 时,第二个参数应该是 IntPtr ,不是数组。
由于楼主的这个 VC DLL 可能是自己写的,我没办法给出正确代码。
我提供一份模拟代码做参考:
----------------------------------------------------------------------------------------------------
VC DLL:
StructMod.h

#pragma once

#include <Windows.h>

#ifndef STRUCTMOD_EXPORT
#define STRUCTMOD_EXPORT extern "C" __declspec(dllimport)
#endif

typedef struct _tagCardEvent {
DWORD CardNo;
INT32 EvtYear;
} CardEvent, *PCardEvent;

STRUCTMOD_EXPORT CardEvent;
STRUCTMOD_EXPORT PCardEvent;

typedef void CALLBACK PFNTEST(PCardEvent pce, INT32 nEvt);

STRUCTMOD_EXPORT void Caller(PFNTEST pfnTest);

StructMod.cpp

#define STRUCTMOD_EXPORT extern "C" __declspec(dllexport)
#include "StructMod.h"
#include <tchar.h>

void Caller(PFNTEST pfnTest) {
CardEvent ce[2] = { { 2, 2011 }, { 3, 2005 } };
if (pfnTest) (*pfnTest)(ce, _countof(ce));
}

--------------------------------------------------------------------------------------------------------------------
C# 调用:

using System;
using System.Text;
using System.Runtime.InteropServices;

namespace IntPtrTest
{
class Program
{
static void Main(string[] args)
{
TestD t = new TestD(Test);
Caller(t);
}

[StructLayout(LayoutKind.Sequential)]
public struct CardEvent
{
public uint CardNo;
public Int32 EvtYear;
}

delegate void TestD(IntPtr p, Int32 nEvt);
[DllImport("StructMod", EntryPoint = "Caller", CallingConvention = CallingConvention.Cdecl)]
private static extern void Caller(TestD t);

public static unsafe void Test(IntPtr p, Int32 nEvt)
{

Console.WriteLine("nEvt = " + nEvt.ToString() + Environment.NewLine);
for (int n = 0; n < nEvt; n++)
{
p = p + n * sizeof(CardEvent);
CardEvent ce = (CardEvent)Marshal.PtrToStructure(p, typeof(CardEvent));
Console.WriteLine("[{0}]", n);
Console.WriteLine("CardNo - " + ce.CardNo.ToString());
Console.WriteLine("EvtYear - " + ce.EvtYear.ToString());
}
}
}
}追问

感谢silvergingko!分给您了,谢谢。
我取3条记录 前面2条没问题,后面一条就乱了。就这个地方改了下
p =(IntPtr)( (int)p + n * sizeof(CardEvent));
不是到有问题么?
还有
我想再问下,如何释放掉那些内存呢? 我发现回调函数调用几次后内存就会加大?

追答

楼主说的这个 DLL 我不熟悉。里面的函数我也不了解。
一般情况下,一个 DLL 在一个函数的内部动态分配了内存的话,这个 DLL 会记录该内存的使用情况的。该 DLL 可以通过两种方式来实现内存的释放。
一、当该 DLL 在卸载时,DLL 自己释放。
二、另外再提供一个对应的函数,供客户程序调用来释放前面一个函数分配的内存。如 API : HeapAlloc,总会另外再提供 HeapFree 来让我们能自主释放内存。

楼主看下这个 DLL 的导出函数列表,里面是否有对应的内存释放函数,自行调用就可以了。还有就是内存的增长跟 DLL 没有关系,完全是代码有 BUG 所致。就如同楼主对于 DLL 提供的指针类型,楼主强行将它转成了 Array ,VC 的 struct 中没有 Length 这一数据成员,在 C# 中,强行使用了 Length 属性,结果是将排列整齐 CardEvent 数组(VC,不是C#数组)中任意的一个数据当做了 Length 属性,被 C# 读取出来。结果可想而知。
-----------------------------------------------------------------------------------------------
如果还不正确。楼主就要再研究下 DLL 这两个被调用的函数究竟是如何工作的。譬如传进来的参数究竟代表了什么,回调函数究竟该如何使用这些参数。C# 通不过的话,建议楼主还是用 VC 实现程序想要实现的功能。如果 VC 还是不行,那么就可以肯定要么是楼主对 DLL 导出函数理解不正确,产生了错误的编程逻辑;要么就是 DLL 自身有 BUG。这也是为什么我建议楼主先用 VC 的原因。先排除对 C# 语言不熟造成的编程干扰。只有确信 DLL 没有 BUG ,楼主的编程逻辑是正确的,然后再通过 C# 来实现对 DLL 的调用。
根据经验,一般内存不断增长,肯定是存在了 BUG 。内存的释放,我估计不是由于 DLL 函数引起的,而是楼主其他地方有问题。楼主阅读一下主调函数的使用文档,是否记载了该函数会动态分配内存,是的话,正规文档肯定会记载释放内存的方法说明。

温馨提示:答案为网友推荐,仅供参考
第1个回答  2011-05-05
以前写过类似的,可以私下交流么?
应该在函数中加个参数,用来记录读取的数据位置。每次调用回调函数的时候就会取到不同的数据追问

用这样可以读到所有值
public static unsafe void themain(IntPtr hDev, CardEvent* phld, Int32 nEvtCnt)
{
}
但怎样清除这些内存呢?我发现回调函数调用几次后内存就会加大?
求解啊。给分啊》。。

追答

小伙子说话啦??

追问

是啊,还在加班 怎么联系您?

追答

是这样的,你没次回调都会全部读取!

追问

是的 我是需要全部读取。怎么回收内存呢?

追答

按你的方法的话那你只有没次在回调后,把得到的数据保存,然后再释放内存。

相似回答