C#调用非托管dll运行时随机报System.AccessViolationException
我写的C#程序有几个接口会频繁调用vc写的非托管dll,这个动态库之前就在用应该没什么大问题而且程序在运行时也比较正常,但是当我在运行一段时间后,发现程序卡死了(系统呈现死机状态,屏幕保护时按键无法激活显示器),在我写demo测试接口时发现测试程序运行一段时间后程序报错误,错误信息如下:
See the end of this message for details on invoking
just-in-time (JIT) debugging instead of this dialog box.
************** Exception Text **************
System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
at Common.API.CLIENTStart.Invoke(CHANNELR& _pCHANNEL)
at Common.Monomer.Channel.Connect(IntPtr _hWnd)
at test_Cruse.Connect()
at test_Cruse.tmrCurse_Tick(Object sender, EventArgs e)
at System.Windows.Forms.Timer.OnTick(EventArgs e)
at System.Windows.Forms.Timer.TimerNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
我最初的考虑此问题的可能引发的原因是,系统用调用非托管dll时所传入的结构体中有以string声明的成员,在转入vcdll时需要转换,而且整个结构体在传输时不会被锁定,在调用dll接口时此结构体的内存发生了移动,所以我把string换成了定长的byte[] 但是demo依然会引发System.AccessViolationException异常,不知道有没有人遇到过!
最奇怪的是可能调用此接口50次都不会发生问题,也可能20-30次就会发生此异常,但是一开始运行程序的时候是没问题的。 --------------------编程问答-------------------- 补充一下CLIENTStart接口我传递的是结构体指针,使用ref CHANNELR的方式
我查询了一下如果使用引用类型封送的字段都是blittable类型如byte、int等时,封送拆收器将他锁定在托管内存中,而整个结构体中需要char数组,我一开始使用string时引发异常我担心是string不是blittable类型的问题,托管内存没有被锁定,所以我把string改为了byte[],因为只包含blittable的一维数组也被当做blittable类型,想排除此问题 --------------------编程问答-------------------- 不会,可以帮顶不 --------------------编程问答-------------------- 单纯由blittable类型构成的结构和数组是blittable的,但嵌套数组的结构不会是blittable的,在这里使用String和Byte[]没太大的区别
但是应该和这点没有关系,锁定只是作为一种优化的方式,不锁定,则会复制,与锁定相比只是开销较大而已
楼主最好贴一下代码,包括VC写的CLIENTStart函数体。这类错误往往是dll其本身有问题 --------------------编程问答-------------------- 这和dll应该关系不大,dll接口和传入结构体有关的只有memcpy这一个函数而已,应该不会有影响
http://topic.csdn.net/u/20081113/09/69F1C532-D1C6-4ED7-A2DD-C9851AC8BB1A.html
这个帖子中提到 由于非托管的代码试图访问CLR保护的内存引起的,在垃圾回收以后,垃圾回收器要压缩内存,这时有些内存块将要移动,这些内存是受到保护的
是不是这个问题呢???? --------------------编程问答-------------------- 平台调用时数据复制使用的内存是Introp自动调用AllocCoTaskMem分配的,并在完成后自动调用FreeCoTaskMem释放。不属托管内存,不受GC控制
只要封送的过程是正确的,就不必担心垃圾回收会造成影响。而又有谁会在使用DllImport时考虑内存是否会被垃圾回收呢?一样用的好好的额~ --------------------编程问答-------------------- 非托管的C++代码可访问的内存应仅限于调用时封送的数据缓冲区,包括调用时锁定的区域或AllocCoTaskMem(手动或自动)分配的区域
如产生了非法访问,要么是封送过程有错,要么是Dll本身的问题 --------------------编程问答--------------------
说的是应该由调用方分配的数据内存。被调用的C++内自行申请的内存不受此限 --------------------编程问答-------------------- 多谢foreachif的解答,但是如果封送错误,或是调用接口问题,不是应该每一次调用都会出错吗,为什么在每分钟20次调用30分钟左右才会弹出此错误呢??? --------------------编程问答-------------------- 是的。所以我说“往往是dll其本身有问题”
不过也不能完全排除是封送错误。也有可能是每次封送的数据不完全相同,而AccessViolation也不是每次无意的访问都能触发的,要视乎内存段的可访问性了。如果是封送错误的话,垃圾回收这时也同样可能成为影响因素。总之,异常的抛出也会因此可能变得随机了
这类问题可能需要仔细的检查和调试。如果楼主不贴代码的话,其它人是无法猜测到确切的错误原因的 --------------------编程问答-------------------- 1.看堆栈timer控件发现楼主用了timer控件,因此foreachif所说的
如产生了非法访问,要么是封送过程有错,要么是Dll本身的问题 就不见得是绝对的成立了
2. timer控件有一机制,如果当前未执行完而触发了下一次,那么当前的执行代码会被销毁,
我们可以假设楼主在OnTick中分配了非托管内存,并传递给vcdll,那么当代码被销毁时,vcdll中将会引用到错误的引用地址,报错也在情理之中。
3. 建议给放入线程中处理把,放弃timer控件 --------------------编程问答-------------------- 我在代码中使用timer时都是先tmr.Enabled = false;
在timer中代码执行完成之后再执行tmr.Enabled = true;
这样执行应该不会有问题吧?
代码我贴在下一楼
--------------------编程问答-------------------- vc中调用的代码:
long __stdcall ClientStart(CHANNEL* _pConInfor)
{
long lHandle = -1;
CHANNEL* pConInfor = (CHANNEL*)malloc(sizeof(CHANNEL));
if(pConInfor != NULL)
{
memset(pConInfor,0,sizeof(CHANNEL));
memcpy(pConInfor,_pConInfor,sizeof(CHANNEL));
lHandle = (int)pConInfor;
pConInfor->lRecv = lHandle;
CChannelSession* pSession = new CChannelSession();
if(pSession != NULL)
{
//与_pConInfor无关的代码
}
else
{
free(pConInfor);
pConInfor = NULL;
return -1;
}
}
return lHandle;
}
VC中的结构体
typedef struct
{
NET oNet;
int iChNo;
HWND hVideo;
int iBuffNum;
int iType;
int iStreamType;
long lRecv;
}CHANNEL,*pCHANNEL;
typedef struct
{
char pchFromId[12];
char pchToId[12];
char pchUser[12];
char pchPsw[12];
int iTranType;
char pchUrl[20];
int iPort;
}NET,*pNET;
--------------------编程问答-------------------- C#中相应的代码
[DllImport("Client.dll")]
public static extern int ClientStart(ref CHANNEL _pCHANNEL);
结构体
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct CHANNEL_CONINFOR
{
public NETLINK_INFO m_sNetLinkInfo;
public Int32 m_iChNo;
public IntPtr m_hVideo;
public Int32 m_iBuffNum;
public Int32 m_iType;
public Int32 m_iStreamType;
public UInt32 m_uRecv;
}
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct NETLINK_INFO
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
public byte[] m_pchFromID;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
public byte[] m_pchToID;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
public byte[] m_pchUser;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
public byte[] m_pchPsw;
public Int32 m_iTranType;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
public byte[] m_pchUrl;
public Int32 m_iPort;
}
调用的函数
public Int32 ConnectVideo(IntPtr _hWnd)
{
m_lHandle = -1;
if (!m_bInit)
{
m_ChCon = new CHANNEL();
m_ChCon .m_Net.m_pchFromID = Encoding.Default.GetBytes(ClientID);
m_ChCon .m_Net.m_pchToID = Encoding.Default.GetBytes(CamID);
byte[] temp = null;
byte[] bUser = new byte[12];
Array.Clear(bUser, 0, 12);
temp = Encoding.Default.GetBytes(User);
Array.Copy(temp, bUser, temp.Length);
m_ChCon .Net.m_pchUser = bUser;
byte[] bPass = new byte[12];
Array.Clear(bPass, 0, 12);
temp = Encoding.Default.GetBytes(Psw);
Array.Copy(temp, bPass, temp.Length);
m_ChCon.m_Net.m_pchPsw = bPass;
m_ChCon.m_Net.m_iTranType = 1;
byte[] bUrl = new byte[20];
Array.Clear(bUrl, 0, 20);
temp = Encoding.Default.GetBytes(MSIP);
Array.Copy(temp, bUrl, temp.Length);
m_ChCon.m_Net.m_pchUrl = bUrl;
m_ChCon.m_Net.m_iPort = MSPort;
m_ChCon.m_iType = 0;
m_ChCon.m_hVideo = _hWnd;
m_ChCon.m_iChNo = Convert.ToInt32(strChNo);
m_ChCon.m_iBuffNum = 50;
m_bInit = true;
}
m_lHandle = VSSP_ClientStart(ref m_ChCon);
return m_lHandle;
}
//计时器
void tmrCurse_Tick()
{
tmrCurse.Enable = false;
ConnectVideo(this.hWnd);
tmrCurse.Enable = true;
}
--------------------编程问答--------------------
Forms.Timer是依赖线程消息队列实现的,怎么可能会有这样的机制??
就是假设有吧,你说的“当前的执行代码会被销毁”是指内存被回收?那样回收的也只可能是托管内存,非托管内存是不会被GC回收的,在PInvoke结束前也不会有机会调用FreeCoTaskMem释放
我是建议如果CHANNELR结构内包含指针的话,在调用前应检查其有效性 --------------------编程问答-------------------- 直接写个while循环测试,看报错不?
while(true)
{
ConnectVideo(this.hWnd);
}
--------------------编程问答-------------------- 传输的结构体中并不包含指针,我把计时器改成了线程正在测试 --------------------编程问答-------------------- 1.虽然timer控件也实现窗口句柄,timer控件也不能保证tick代码能否被完整执行
2.Pinvoke并非原子性操作,存在被打断的可能性 --------------------编程问答-------------------- 我换成了线程 出现了vc dll 错误 --------------------编程问答-------------------- 这样能排除是我C#上层调用的问题吗? --------------------编程问答-------------------- 我写的demo只有一个计时器或线程在运行,并没有其他的工作在进行,也会有被打断的可能? --------------------编程问答-------------------- 需要测试的是当连续传入相同的或可控的数据内容时是否会引发错误
发现楼主的代码没有检查GetBytes返回的字节数是否超出了11个字节,也不确定hWnd是否合法,另外如果dll中仅以'\0'决定字符串的结束位置,则在C#中也要保证这一点。楼主没贴的那部分代码,也应该是相关的,不然传入的参数就没意义了,或者说是间接相关(pConInfor)。即使真的是完全与_pConInfor无关,也不是说就不可能是问题产生的原因
--------------------编程问答--------------------
但这不是说“当前的执行代码会被销毁”,仅仅是WM_TIMER
非要这么说也可以,但会被那个线程打断,自身?GC线程?还自动调用FreeCoTaskMem? --------------------编程问答-------------------- 我优化了dll部分代码,与此接口无关系,正在测试,暂时没有报错 --------------------编程问答-------------------- 我也遇见这类问题,好纠结啊 --------------------编程问答-------------------- 楼主,你提的问题解决了吗?我也遇到此问题,求解! --------------------编程问答-------------------- 楼主,此问题解决了吗?我也遇到此问题,求解!
补充:.NET技术 , C#