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

Win32 Scard.h API使用问题:读卡器列表获取异常

智能卡读卡器列表获取问题及修正思路

问题现象

调用SCardEstablishContext成功后,使用SCardListReadersA获取读卡器列表时出现异常:API返回成功,且返回的缓冲区大小为89字节,但无法读取到有效字符串。尝试过修改数据类型、插拔读卡器、重启系统、逐行调试等操作,问题依旧,要么得到89个空字节,要么触发断言失败。

问题代码

#include <Windows.h>
#include <stdint.h>
#include <winscard.h>
#include <stdio.h>

typedef uint8_t  u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64; 
typedef uint32_t b32; 
typedef int8_t  i8;
typedef int16_t i16;
typedef int32_t i32;
typedef int64_t i64; 
#define l_persist static
#define gvar static 
#define internal static

#define KiloBytes(Value) ((Value) * 1024LL)
#define MegaBytes(Value) (KiloBytes(Value) * 1024LL)

#if INTERNAL
#define Assert(exp) if (!(exp)) {*((u8*)0) = 0;}
#define AssertWithMessage(exp, message) if (!(exp)) {OutputDebugString("\nAssertion failed: "); OutputDebugString(message); OutputDebugString("\n"); Assert(exp);} 

#else
#define Assert(exp) 
#define AssertWithMessage(exp, message)  
#endif 

#define U32_MAX 0xFFFFFFFF

b32 G_IsRunning = true;

LRESULT CALLBACK MainWindowCallback(HWND Window, UINT  Message, WPARAM  WParam, LPARAM  LParam)
{
    LRESULT Result = 0;

    switch(Message)
    {
        case WM_CLOSE:
        case WM_QUIT:
        case WM_DESTROY:
        {
            G_IsRunning = false;
        } break;

        default:
        {
            Result = DefWindowProc(Window, Message, WParam, LParam);
        }
    }

    return Result;
}

void MessagePump(HWND Window)
{
    MSG Message = {};

    u32 InputCount = 0;
    while(PeekMessage(&Message, Window, 0, 0, PM_REMOVE))
    {
        switch(Message.message)
        {
            case WM_QUIT:
            {
                G_IsRunning = false;
            } break;

            default:
            {
                TranslateMessage(&Message);
                DispatchMessage(&Message);
            } break;
        }
    }
}

INT CALLBACK
WinMain(HINSTANCE Instance, HINSTANCE PrevInstance, LPSTR CommandLine, INT ShowCommand)
{
    WNDCLASS WindowClass = {};

    WindowClass.style = CS_OWNDC |  CS_HREDRAW | CS_VREDRAW;
    WindowClass.lpfnWndProc = MainWindowCallback;
    WindowClass.hInstance = Instance;
    WindowClass.lpszClassName = "MainWindowClass";

    if (RegisterClass(&WindowClass))
    {
        HWND Window = CreateWindowEx(0,WindowClass.lpszClassName, "Main Window", WS_OVERLAPPEDWINDOW|WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,0,0,Instance, 0);

        if (Window)
        {
            SCARDCONTEXT CardContext = U32_MAX;
            LONG CardContextSuccess = SCardEstablishContext(SCARD_SCOPE_USER, 0, 0, &CardContext);

            Assert(CardContextSuccess == SCARD_S_SUCCESS);

            u8 *Memory = (u8*)VirtualAlloc(0, KiloBytes(1), MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
            u8 *MemoryHead = Memory;
            LPSTR ReadersList = (char*)MemoryHead;

            LPSTR ReadersListWip = 0;
            DWORD ReadersListSize = 0;
            LONG CardReadersListStatus = SCardListReadersA(CardContext, 0, (LPSTR)&ReadersList, &ReadersListSize);

            Assert(ReadersList);
            Assert(ReadersListSize);
            switch(CardReadersListStatus)
            {
                case SCARD_S_SUCCESS: 
                {
                    ReadersListWip = ReadersList;
                    while ( '\0' != *ReadersListWip )
                    {
                        printf("Reader: %S\n", (wchar_t*)ReadersListWip );
                        // Advance to the next value.
                        ReadersListWip = ReadersListWip + wcslen((wchar_t *)ReadersListWip) + 1;
                    }
                } break;

                case SCARD_E_NO_READERS_AVAILABLE:
                {
                    AssertWithMessage(CardReadersListStatus == SCARD_S_SUCCESS, "There were no card readers available");
                    G_IsRunning = false;
                } break;

                case SCARD_E_READER_UNAVAILABLE:
                {
                    AssertWithMessage(CardReadersListStatus == SCARD_S_SUCCESS, "The card reader was unavailable");
                    G_IsRunning = false;
                } break;
                default:
                {
                    AssertWithMessage(CardReadersListStatus == SCARD_S_SUCCESS, "Some other issue with card reader listing");
                    G_IsRunning = false;
                }
            }

            while(G_IsRunning)
            {
                MessagePump(Window);

                LPSCARDHANDLE CardHandle = 0;
                DWORD ActiveProtocol = 0;
                LONG CardConnectStatus =  SCardConnectA(CardContext, ReadersList, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0, (LPSCARDHANDLE)&CardHandle, &ActiveProtocol);

                Assert(CardConnectStatus == SCARD_S_SUCCESS);
            }
        }
    }


    return 0;
}

问题根源

经分析,核心错误在于对SCardListReaders系列API的参数逻辑理解错误:

  • 未遵循先获取缓冲区大小,再分配内存读取数据的标准流程:需要先传入NULL作为输出缓冲区,获取所需的缓冲区长度后,再分配对应大小的内存进行第二次调用。
  • 忽略了文档中SCARD_AUTOALLOCATE参数的用法:可以通过该标记让系统自动分配内存,避免手动管理缓冲区的问题。
  • 额外的类型不匹配错误:使用SCardListReadersA(ANSI版本)却用%S格式符和wcslen处理宽字符串,导致读取逻辑异常。

替代API选项

如果需要替代Windows原生的Winscard API,可以考虑:

  • PC/SC Lite:跨平台的PC/SC实现,接口与Winscard兼容,支持Windows、Linux、macOS等系统,适合跨平台开发。
  • 智能卡厂商专属SDK:部分读卡器厂商(如HID、Gemalto)会提供针对性的SDK,包含更精细化的读取控制接口。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 10:36:07