You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

咨询:如何修复winscard.dll包装器中SCardConnect函数导致的内存泄漏

我来帮你排查下这个winscard.dll相关的内存泄漏问题——核心原因是你没正确释放API创建的系统资源,再加上无限循环的调用逻辑,把这个问题放大了。下面是具体的问题分析和修复方案:

核心问题拆解

你的代码里存在几个关键的资源泄漏点:

  • SCardEstablishContext创建的上下文句柄hContext没有调用SCardReleaseContext释放,每次调用GetReadersList都会生成新的上下文,旧资源一直占用内存。
  • 当SCardConnect成功建立卡片连接后,没有调用SCardDisconnect释放卡片句柄hCard,无限循环调用Connect会堆积大量未释放的连接资源。
  • 主函数里的无限do-while循环会持续发起连接请求,即使成功连接后也不断开,进一步加剧内存占用。
具体修复步骤

1. 释放SCardEstablishContext创建的上下文

在GetReadersList函数里,每次建立上下文后必须释放,哪怕发生异常也要确保释放。这里用临时变量存储上下文,避免干扰全局句柄:

public List<string> GetReadersList()
{
    List<string> lstReaders = new List<string>();
    int hContextTemp = 0;
    try
    {
        // 建立临时上下文
        retCode = Card.SCardEstablishContext(Card.SCARD_SCOPE_USER, 0, 0, ref hContextTemp);
        if (retCode != Card.SCARD_S_SUCCESS)
        {
            throw new Exception("Failed to establish PC/SC context");
        }

        int pcchReaders = 0;
        // 第一次调用获取缓冲区大小
        retCode = Card.SCardListReaders(hContextTemp, null, null, ref pcchReaders);
        if (retCode != Card.SCARD_S_SUCCESS)
        {
            throw new Exception("Error getting reader list size");
        }

        byte[] ReadersList = new byte[pcchReaders];
        // 第二次调用填充读卡器列表
        retCode = Card.SCardListReaders(hContextTemp, null, ReadersList, ref pcchReaders);
        if (retCode != Card.SCARD_S_SUCCESS)
        {
            throw new Exception("Error reading reader list");
        }

        string rName = "";
        int indx = 0;
        while (indx < ReadersList.Length && ReadersList[indx] != 0)
        {
            while (indx < ReadersList.Length && ReadersList[indx] != 0)
            {
                rName += (char)ReadersList[indx];
                indx++;
            }
            lstReaders.Add(rName);
            rName = "";
            indx++;
        }
    }
    finally
    {
        // 无论是否成功,都释放临时上下文
        if (hContextTemp != 0)
        {
            Card.SCardReleaseContext(hContextTemp);
            hContextTemp = 0;
        }
    }
    return lstReaders;
}

2. 连接成功后必须释放卡片句柄

修改Connect函数,确保每次连接前先断开旧连接,成功连接后的句柄在使用完毕后也要释放:

public bool Connect()
{
    if (string.IsNullOrEmpty(readername))
    {
        try
        {
            readername = this.GetReadersList()[0];
        }
        catch (Exception e)
        {
            // 可以添加日志或用户提示
            return false;
        }
    }

    // 先断开已有连接
    if (hCard != 0)
    {
        Card.SCardDisconnect(hCard, Card.SCARD_UNPOWER_CARD);
        hCard = 0;
        Protocol = 0;
    }

    retCode = Card.SCardConnect(hContext, readername, Card.SCARD_SHARE_SHARED, 
                               Card.SCARD_PROTOCOL_T0 | Card.SCARD_PROTOCOL_T1, 
                               ref hCard, ref Protocol);
    if (retCode != Card.SCARD_S_SUCCESS)
    {
        hCard = 0;
        Protocol = 0;
        return false;
    }
    return true;
}

注意:SCARD_UNPOWER_CARD会给卡片断电,如果需要保持卡片供电,可以换成SCARD_LEAVE_CARD,但无论哪种都必须调用断开函数。

3. 优化主循环逻辑,避免无限重复连接

你的主函数里的无限循环会持续发起连接,即使成功也不释放,这是内存飙升的主要推手。改成合理的轮询逻辑,比如连接成功后处理业务,然后断开,再等待一段时间后重新检查:

public Form1()
{
    InitializeComponent();
    // 全局上下文只创建一次
    retCode = Card.SCardEstablishContext(Card.SCARD_SCOPE_USER, 0, 0, ref hContext);
    if (retCode != Card.SCARD_S_SUCCESS)
    {
        MessageBox.Show("Failed to initialize PC/SC context");
        return;
    }

    // 用后台线程处理轮询,避免阻塞UI
    Task.Run(() =>
    {
        while (true)
        {
            if (this.Connect())
            {
                // 这里添加卡片操作逻辑,比如读取卡片数据
                // ...

                // 操作完成后立即断开连接
                if (hCard != 0)
                {
                    Card.SCardDisconnect(hCard, Card.SCARD_UNPOWER_CARD);
                    hCard = 0;
                }
            }
            // 轮询间隔设为1秒,避免过于频繁调用API
            Thread.Sleep(1000);
        }
    });
}

// 窗体关闭时释放所有资源
protected override void OnFormClosing(FormClosingEventArgs e)
{
    base.OnFormClosing(e);
    if (hCard != 0)
    {
        Card.SCardDisconnect(hCard, Card.SCARD_UNPOWER_CARD);
        hCard = 0;
    }
    if (hContext != 0)
    {
        Card.SCardReleaseContext(hContext);
        hContext = 0;
    }
}

4. 关于SCardStatusChange的补充

你提到SCardStatusChange也有泄漏,本质原因还是资源没释放:如果用它监听卡片状态,要确保退出时调用SCardCancel终止监听,并且释放关联的上下文。不过上面的轮询逻辑已经能满足大部分场景需求,不需要额外引入复杂的状态监听逻辑。

修复原理说明

winscard.dll的PC/SC API是基于句柄的资源模型:

  • SCardEstablishContext创建的上下文是系统级资源,必须用SCardReleaseContext释放。
  • SCardConnect创建的卡片连接句柄hCard必须用SCardDisconnect释放,否则系统会一直保留该连接的内存和硬件资源。
  • 无限循环无限制地创建这些资源而不释放,必然导致内存持续飙升——尤其是卡片存在时,每次连接都会成功,泄漏的资源量会更大。

内容的提问来源于stack exchange,提问作者WorstNoob

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 22:03:16