问题是这样的,现在目前github上使用twain调用扫描仪一直都是基于mfc工程,我是参考了这篇文章,并且把资源下载到本地,改造成控制台程序https://blog.csdn.net/a1101000099/article/details/38561365
然后,在构造回调函数的时候,我们之间出现了分歧,MFC是通过 CDialogEx::PreTranslateMessage(MSG* pMsg) 这个回调函数来处理twain回调的(这里的msg变量是mfc带的)
if(SourceEnabled())
{
TW_EVENT twEvent;
twEvent.pEvent = (TW_MEMREF)&msg;
twEvent.TWMessage = MSG_NULL;
CallTwainProc(&m_AppId,&m_Source,DG_CONTROL,DAT_EVENT,MSG_PROCESSEVENT,(TW_MEMREF)&twEvent);
if(GetRC() != TWRC_NOTDSEVENT)
{
TranslateMessage(twEvent);
}
return FALSE;
}
return FALSE;
但是我控制台程序,无法构造这个msg对象,于是我用了如下的方法,间隔2s,去获取一下twain的最新状态(听说可以直接注册twain回调函数,但是试过好几次,都失败了)
TW_EVENT twEvent;
MSG windowsMsg;
windowsMsg.message = WM_USER; // 模拟的消息类型
windowsMsg.wParam = 0; // 模拟的消息参数
windowsMsg.lParam = 0; // 模拟的消息参数
windowsMsg.hwnd = GetConsoleWindow(); // 控制台窗口句柄,获取当前控制台窗口的句柄
twEvent.pEvent = &windowsMsg;
twEvent.TWMessage = MSG_NULL;
while (true)
{
//间隔2s
std::this_thread::sleep_for(std::chrono::seconds(2));
CallTwainProc(&m_AppId, &m_Source, DG_CONTROL, DAT_EVENT, MSG_PROCESSEVENT, (TW_MEMREF)&twEvent);
TranslateMessage(twEvent);
}
重点来了,上面的CallTwainProc 返回的状态码一直是5,我查了下,这个状态码是失败的意思,也就是说,当前的问题是无法正常处理twain回调,也就无法从扫描仪拿到图片
附上我的TwainCpp.cpp完整代码
#define _CRT_SECURE_NO_WARNINGS
#include "twaincpp.h"
#include <cstddef>
#include "Base64Util.h"
#include "ImageUtil.h"
#include <chrono>
#include <thread>
/*
Constructor:
Parameters : HWND
Window to subclass
*/
CTwain::CTwain(HWND hWnd)
{
m_hTwainDLL = NULL;
m_pDSMProc = NULL;
m_bSourceSelected = FALSE;
m_bDSOpen = m_bDSMOpen = FALSE;
m_bSourceEnabled = FALSE;
m_bModalUI = TRUE;
m_nImageCount = TWCPP_ANYCOUNT;
if (hWnd)
{
InitTwain(hWnd);
}
}
CTwain::~CTwain()
{
ReleaseTwain();
}
/*
Initializes TWAIN interface . Is already called from the constructor.
It should be called again if ReleaseTwain is called.
hWnd is the window which has to subclassed in order to recieve
Twain messaged. Normally - this would be your main application window.
*/
BOOL CTwain::InitTwain(HWND hWnd)
{
char libName[512];
if (IsValidDriver())
{
return TRUE;
}
memset(&m_AppId, 0, sizeof(m_AppId));
if (!IsWindow(hWnd))
{
return FALSE;
}
m_hMessageWnd = hWnd;
strcpy(libName, "twain_32.dll");
m_hTwainDLL = LoadLibraryA(libName);
if (m_hTwainDLL != NULL)
{
if (!(m_pDSMProc = (DSMENTRYPROC)GetProcAddress(m_hTwainDLL, MAKEINTRESOURCEA(1))))
{
FreeLibrary(m_hTwainDLL);
m_hTwainDLL = NULL;
}
}
if (IsValidDriver())
{
GetIdentity();
m_bDSMOpen = CallTwainProc(&m_AppId, NULL, DG_CONTROL, DAT_PARENT, MSG_OPENDSM, (TW_MEMREF)&m_hMessageWnd);
return TRUE;
}
else
{
return FALSE;
}
}
/*
Releases the twain interface . Need not be called unless you
want to specifically shut it down.
*/
void CTwain::ReleaseTwain()
{
if (IsValidDriver())
{
CloseDSM();
FreeLibrary(m_hTwainDLL);
m_hTwainDLL = NULL;
m_pDSMProc = NULL;
}
}
/*
Returns true if a valid driver has been loaded
*/
BOOL CTwain::IsValidDriver() const
{
return (m_hTwainDLL && m_pDSMProc);
}
/*
Entry point into Twain. For a complete description of this
routine please refer to the Twain specification 1.8
*/
BOOL CTwain::CallTwainProc(pTW_IDENTITY pOrigin, pTW_IDENTITY pDest,
TW_UINT32 DG, TW_UINT16 DAT, TW_UINT16 MSG,
TW_MEMREF pData)
{
if (IsValidDriver())
{
USHORT ret_val;
ret_val = (*m_pDSMProc)(pOrigin, pDest, DG, DAT, MSG, pData);
m_returnCode = ret_val;
if (ret_val != TWRC_SUCCESS)
{
(*m_pDSMProc)(pOrigin, pDest, DG_CONTROL, DAT_STATUS, MSG_GET, &m_Status);
}
return (ret_val == TWRC_SUCCESS);
}
else
{
m_returnCode = TWRC_FAILURE;
return FALSE;
}
}
/*
This function should ideally be overridden in the derived class . If only a
few fields need to be updated , call CTawin::GetIdentity first in your
derived class
*/
void CTwain::GetIdentity()
{
// Expects all the fields in m_AppId to be set except for the id field.
m_AppId.Id = 0; // Initialize to 0 (Source Manager
// will assign real value)
m_AppId.Version.MajorNum = 3; //Your app's version number
m_AppId.Version.MinorNum = 8;
m_AppId.Version.Language = TWLG_USA;
m_AppId.Version.Country = TWCY_USA;
strcpy(m_AppId.Version.Info, "3.8");
m_AppId.ProtocolMajor = TWON_PROTOCOLMAJOR;
m_AppId.ProtocolMinor = TWON_PROTOCOLMINOR;
m_AppId.SupportedGroups = DG_IMAGE | DG_CONTROL;
strcpy(m_AppId.Manufacturer, "MICSS");
strcpy(m_AppId.ProductFamily, "Generic");
strcpy(m_AppId.ProductName, "MyTwain");
}
/*
Called to display a dialog box to select the Twain source to use.
This can be overridden if a list of all sources is available
to the application. These sources can be enumerated by Twain.
it is not yet supportted by CTwain.
*/
BOOL CTwain::SelectSource()
{
memset(&m_Source, 0, sizeof(m_Source));
if (!SourceSelected())
{
SelectDefaultSource();
}
if (CallTwainProc(&m_AppId, NULL, DG_CONTROL, DAT_IDENTITY, MSG_USERSELECT, &m_Source))
{
m_bSourceSelected = TRUE;
}
return m_bSourceSelected;
}
/*
Called to select the default source
*/
BOOL CTwain::SelectDefaultSource()
{
m_bSourceSelected = CallTwainProc(&m_AppId, NULL, DG_CONTROL, DAT_IDENTITY, MSG_GETDEFAULT, &m_Source);
return m_bSourceSelected;
}
/*
Closes the Data Source
*/
void CTwain::CloseDS()
{
if (DSOpen())
{
DisableSource();
CallTwainProc(&m_AppId, NULL, DG_CONTROL, DAT_IDENTITY, MSG_CLOSEDS, (TW_MEMREF)&m_Source);
m_bDSOpen = FALSE;
}
}
/*
Closes the Data Source Manager
*/
void CTwain::CloseDSM()
{
if (DSMOpen())
{
CloseDS();
CallTwainProc(&m_AppId, NULL, DG_CONTROL, DAT_PARENT, MSG_CLOSEDSM, (TW_MEMREF)&m_hMessageWnd);
m_bDSMOpen = FALSE;
}
}
/*
Returns true if the Data Source Manager is Open
*/
BOOL CTwain::DSMOpen() const
{
return IsValidDriver() && m_bDSMOpen;
}
/*
Returns true if the Data Source is Open
*/
BOOL CTwain::DSOpen() const
{
return IsValidDriver() && DSMOpen() && m_bDSOpen;
}
/*
Opens a Data Source supplied as the input parameter
*/
BOOL CTwain::OpenSource(TW_IDENTITY* pSource)
{
if (pSource)
{
m_Source = *pSource;
}
if (DSMOpen())
{
m_bDSOpen = CallTwainProc(&m_AppId, NULL, DG_CONTROL, DAT_IDENTITY, MSG_OPENDS, (TW_MEMREF)&m_Source);
}
return DSOpen();
}
/*
Should be called from the main message loop of the application. Can always be called,
it will not process the message unless a scan is in progress.
*/
BOOL CTwain::ProcessMessage(MSG msg)
{
if (SourceEnabled())
{
TW_EVENT twEvent;
twEvent.pEvent = (TW_MEMREF)&msg;
twEvent.TWMessage = MSG_NULL;
CallTwainProc(&m_AppId, &m_Source, DG_CONTROL, DAT_EVENT, MSG_PROCESSEVENT, (TW_MEMREF)&twEvent);
if (GetRC() != TWRC_NOTDSEVENT)
{
TranslateMessage(twEvent);
}
return FALSE;
}
return FALSE;
}
/*
Queries the capability of the Twain Data Source
*/
BOOL CTwain::GetCapability(TW_CAPABILITY& twCap, TW_UINT16 cap, TW_UINT16 conType)
{
if (DSOpen())
{
twCap.Cap = cap;
twCap.ConType = conType;
twCap.hContainer = NULL;
if (CallTwainProc(&m_AppId, &m_Source, DG_CONTROL, DAT_CAPABILITY, MSG_GET, (TW_MEMREF)&twCap))
{
return TRUE;
}
}
return FALSE;
}
/*
Queries the capability of the Twain Data Source
*/
BOOL CTwain::GetCapability(TW_UINT16 cap, TW_UINT32& value)
{
TW_CAPABILITY twCap;
if (GetCapability(twCap, cap))
{
pTW_ONEVALUE pVal;
pVal = (pTW_ONEVALUE)GlobalLock(twCap.hContainer);
if (pVal)
{
value = pVal->Item;
GlobalUnlock(pVal);
GlobalFree(twCap.hContainer);
return TRUE;
}
}
return FALSE;
}
/*
Sets the capability of the Twain Data Source
*/
BOOL CTwain::SetCapability(TW_UINT16 cap, TW_UINT16 value, BOOL sign)
{
if (DSOpen())
{
TW_CAPABILITY twCap;
pTW_ONEVALUE pVal;
BOOL ret_value = FALSE;
twCap.Cap = cap;
twCap.ConType = TWON_ONEVALUE;
twCap.hContainer = GlobalAlloc(GHND, sizeof(TW_ONEVALUE));
if (twCap.hContainer)
{
pVal = (pTW_ONEVALUE)GlobalLock(twCap.hContainer);
pVal->ItemType = sign ? TWTY_INT16 : TWTY_UINT16;
pVal->Item = (TW_UINT32)value;
GlobalUnlock(twCap.hContainer);
ret_value = SetCapability(twCap);
GlobalFree(twCap.hContainer);
}
return ret_value;
}
return FALSE;
}
BOOL CTwain::SetResolution(TW_UINT16 cap, TW_UINT32 value)
{
if (DSOpen())
{
TW_CAPABILITY twCap;
pTW_ONEVALUE pVal;
BOOL ret_value = FALSE;
twCap.Cap = cap;
twCap.ConType = TWON_ONEVALUE;
twCap.hContainer = GlobalAlloc(GHND, sizeof(TW_ONEVALUE));
if (twCap.hContainer)
{
pVal = (pTW_ONEVALUE)GlobalLock(twCap.hContainer);
pVal->ItemType = TWTY_FIX32;
pVal->Item = (TW_UINT32)value;
GlobalUnlock(twCap.hContainer);
ret_value = SetCapability(twCap);
GlobalFree(twCap.hContainer);
}
return ret_value;
}
return FALSE;
}
/*
Sets the capability of the Twain Data Source
*/
BOOL CTwain::SetCapability(TW_CAPABILITY& cap)
{
if (DSOpen())
{
return CallTwainProc(&m_AppId, &m_Source, DG_CONTROL, DAT_CAPABILITY, MSG_SET, (TW_MEMREF)&cap);
}
return FALSE;
}
/*
Sets the number of images which can be accpeted by the application at one time
*/
BOOL CTwain::SetImageCount(TW_INT16 nCount)
{
if (SetCapability(CAP_XFERCOUNT, (TW_UINT16)nCount, TRUE))
{
m_nImageCount = nCount;
return TRUE;
}
else
{
if (GetRC() == TWRC_CHECKSTATUS)
{
TW_UINT32 count;
if (GetCapability(CAP_XFERCOUNT, count))
{
nCount = (TW_INT16)count;
if (SetCapability(CAP_XFERCOUNT, nCount))
{
m_nImageCount = nCount;
return TRUE;
}
}
}
}
return FALSE;
}
/*
Called to enable the Twain Acquire Dialog. This too can be
overridden but is a helluva job .
*/
BOOL CTwain::EnableSource(BOOL showUI)
{
if (DSOpen() && !SourceEnabled())
{
twUI.ShowUI = FALSE;
twUI.hParent = (TW_HANDLE)m_hMessageWnd;
if (CallTwainProc(&m_AppId, &m_Source, DG_CONTROL, DAT_USERINTERFACE, MSG_ENABLEDS, (TW_MEMREF)&twUI))
{
m_bSourceEnabled = TRUE;
m_bModalUI = twUI.ModalUI;
}
else
{
m_bSourceEnabled = FALSE;
m_bModalUI = TRUE;
}
return m_bSourceEnabled;
}
return FALSE;
}
/*
Called to acquire images from the source. parameter numImages i the
numberof images that you an handle concurrently
*/
BOOL CTwain::Acquire(int numImages, TW_UINT16 duplex, TW_UINT16 size, TW_UINT16 pixel, TW_UINT16 resolution)
{
if (DSOpen() || OpenSource())
{
BOOL ret_value = SetCapability(CAP_DUPLEXENABLED, duplex, TRUE);
ret_value = SetCapability(ICAP_SUPPORTEDSIZES, size, TRUE);
ret_value = SetCapability(ICAP_PIXELTYPE, pixel, TRUE);
ret_value = SetResolution(ICAP_XRESOLUTION, resolution);
ret_value = SetResolution(ICAP_YRESOLUTION, resolution);
//设置为jpeg格式返回,默认的位图文件很大
SetCapability(ICAP_IMAGEFILEFORMAT, TWCP_JPEG);
if (SetImageCount(numImages))
{
if (EnableSource())
{
return TRUE;
}
}
}
return FALSE;
}
/*
Called to disable the source.
*/
BOOL CTwain::DisableSource()
{
if (SourceEnabled())
{
if (CallTwainProc(&m_AppId, &m_Source, DG_CONTROL, DAT_USERINTERFACE, MSG_DISABLEDS, &twUI))
{
m_bSourceEnabled = FALSE;
return TRUE;
}
}
return FALSE;
}
/*
Called by ProcessMessage to Translate a TWAIN message
*/
void CTwain::TranslateMessage(TW_EVENT& twEvent)
{
switch (twEvent.TWMessage)
{
case MSG_XFERREADY:
TransferImage();
break;
case MSG_CLOSEDSREQ:
if (CanClose())
{
CloseDS();
}
break;
}
}
/*
Gets Imageinfo for an image which is about to be transferred.
*/
BOOL CTwain::GetImageInfo(TW_IMAGEINFO& info)
{
if (SourceEnabled())
{
return CallTwainProc(&m_AppId, &m_Source, DG_IMAGE, DAT_IMAGEINFO, MSG_GET, (TW_MEMREF)&info);
}
return FALSE;
}
/*
Trasnfers the image or cancels the transfer depending on the state of the
TWAIN system
*/
void CTwain::TransferImage()
{
TW_IMAGEINFO info;
BOOL bContinue = TRUE;
while (bContinue)
{
if (GetImageInfo(info))
{
int permission;
permission = ShouldTransfer(info);
switch (permission)
{
case TWCPP_CANCELTHIS:
bContinue = EndTransfer();
break;
case TWCPP_CANCELALL:
CancelTransfer();
bContinue = FALSE;
break;
case TWCPP_DOTRANSFER:
bContinue = GetImage(info);
break;
}
}
}
}
/*
Ends the current transfer.
Returns TRUE if the more images are pending
*/
BOOL CTwain::EndTransfer()
{
TW_PENDINGXFERS twPend;
if (CallTwainProc(&m_AppId, &m_Source, DG_CONTROL, DAT_PENDINGXFERS, MSG_ENDXFER, (TW_MEMREF)&twPend))
{
if (twPend.Count == 0)
{
DisableSource();
}
return twPend.Count != 0;
}
return FALSE;
}
/*
Aborts all transfers
*/
void CTwain::CancelTransfer()
{
TW_PENDINGXFERS twPend;
CallTwainProc(&m_AppId, &m_Source, DG_CONTROL, DAT_PENDINGXFERS, MSG_RESET, (TW_MEMREF)&twPend);
}
/*
Calls TWAIN to actually get the image
*/
BOOL CTwain::GetImage(TW_IMAGEINFO& info)
{
HANDLE hBitmap;
CallTwainProc(&m_AppId, &m_Source, DG_IMAGE, DAT_IMAGENATIVEXFER, MSG_GET, &hBitmap);
switch (m_returnCode)
{
case TWRC_XFERDONE:
CopyImage(hBitmap, info);
break;
case TWRC_CANCEL:
break;
case TWRC_FAILURE:
CancelTransfer();
return FALSE;
}
GlobalFree(hBitmap);
return EndTransfer();
}
/**
* 获取驱动列表
*/
vector<TW_IDENTITY> CTwain::getSources()
{
sourceList.clear();
if (CallTwainProc(&m_AppId, NULL, DG_CONTROL, DAT_IDENTITY, MSG_GETFIRST, &m_Source)) {
while (CallTwainProc(&m_AppId, NULL, DG_CONTROL, DAT_IDENTITY, MSG_GETNEXT, &m_Source)) {
TW_IDENTITY temp_Source = m_Source;
sourceList.push_back(temp_Source);
}
}
return sourceList;
}
void CTwain::CopyImage(HANDLE hBitmap, TW_IMAGEINFO& info) {
storeImage(hBitmap, info);
}
TW_IDENTITY CTwain::getSourceByName(const char* sourceName) {
for (size_t i = 0; i < sourceList.size(); i++) {
// 使用迭代器访问向量元素
string name = sourceList[i].ProductName;
if (sourceName == name) {
return sourceList[i];
}
}
return TW_IDENTITY{};
}
void CALLBACK ScanCallbackFunction(pTW_IDENTITY pOrigin, pTW_IDENTITY pDest, TW_UINT32 uiMsg, TW_MEMREF pData)
{
int a = 0;
}
void CTwain::startScan(const char* sourceName) {
TW_IDENTITY source = getSourceByName(sourceName);
if (source.Id == 0 && source.Version.MajorNum == 0 && source.Version.MinorNum == 0) {
return;
}
if (OpenSource(&source))
{
BOOL ret_value = SetCapability(CAP_DUPLEXENABLED, 0, TRUE);
ret_value = SetCapability(ICAP_SUPPORTEDSIZES, 0, TRUE);
ret_value = SetCapability(ICAP_PIXELTYPE, 0, TRUE);
ret_value = SetResolution(ICAP_XRESOLUTION, 0);
ret_value = SetResolution(ICAP_YRESOLUTION, 300);
//SetImageCount(-1)
EnableSource(true);
//注册回调事件
if (SourceEnabled())
{
TW_EVENT twEvent;
MSG windowsMsg;
windowsMsg.message = WM_USER; // 模拟的消息类型
windowsMsg.wParam = 0; // 模拟的消息参数
windowsMsg.lParam = 0; // 模拟的消息参数
windowsMsg.hwnd = GetConsoleWindow(); // 控制台窗口句柄,获取当前控制台窗口的句柄
twEvent.pEvent = &windowsMsg;
twEvent.TWMessage = MSG_NULL;
while (true)
{
//间隔2s
std::this_thread::sleep_for(std::chrono::seconds(2));
CallTwainProc(&m_AppId, &m_Source, DG_CONTROL, DAT_EVENT, MSG_PROCESSEVENT, (TW_MEMREF)&twEvent);
TranslateMessage(twEvent);
}
}
}
}
请确认你的消息构建:
MSG windowsMsg;
windowsMsg.message = WM_USER; // 模拟的消息类型
windowsMsg.wParam = 0; // 模拟的消息参数
windowsMsg.lParam = 0; // 模拟的消息参数
windowsMsg.hwnd = GetConsoleWindow(); // 控制台窗口句柄,获取当前控制台窗口的句柄
twEvent.pEvent = &windowsMsg;
twEvent.TWMessage = MSG_NULL;
和消息循环中的一致。,我感觉这里您构建的有点问题。
如果这个库依赖Win32的窗口和消息循环,那么应该在main函数最后运行消息循环,
控制台程序也是可以创建窗口和进行消息循环的,
创建一个窗口CreateWindow,但不要显示它ShowWindow,
main函数最后进行消息循环:
MSG msg;
while(GetMessage(&msg, NULL, 0, 0)){
//在这里取得的消息传递给twain这个库
TranslateMessage(&msg);
DispatchMessage(&msg);
}
我大概扫了一下你的代码
提供的代码,可能存在一下几个方面吧
MSG 对象的构造:在 MFC 应用程序中,MSG 对象是由 Windows 消息循环系统自动创建和传递的。而在控制台程序中,没有消息循环,也没有自动创建的 MSG 对象。因此,您需要手动创建 MSG 对象,并将其传递给 TWAIN 接口。在您的代码中,您创建了一个模拟的 MSG 对象,但是可能没有正确设置其各个成员变量。您可以参考 Windows API 中的 GetMessage() 函数的文档,了解如何正确创建和设置 MSG 对象。
TWAIN 回调函数的注册:在 MFC 应用程序中,TWAIN 回调函数通常是通过 TWAIN 接口提供的 TW_CALLBACK 类型来注册的。而在控制台程序中,您需要手动注册 TWAIN 回调函数,以便能够接收 TWAIN 接口的消息和事件。您可以参考 TWAIN 接口的文档,了解如何注册 TWAIN 回调函数。
TWAIN 应用程序 ID 和数据源 ID 的获取:在您的代码中,您使用了 m_AppId 和 m_Source 变量来表示 TWAIN 应用程序 ID 和数据源 ID。这些变量应该是在 TWAIN 接口初始化时获取的。您可以检查一下您的代码,确保在调用 CallTwainProc() 函数之前已经正确获取了 TWAIN 应用程序 ID 和数据源 ID。
TWAIN 接口的返回值和错误码:在您的代码中,您调用了 CallTwainProc() 函数来调用 TWAIN 接口。该函数返回一个 TW_UINT16 类型的值,表示 TWAIN 接口的返回值和错误码。您可以检查一下该返回值,并根据 TWAIN 接口的文档来判断是否出现了错误。您可以使用 TWAIN 接口提供的 TWCC 宏来检查错误码。
答案由GPT-4Plus大模型(语音)与博主@晓码自在合作编写:
根据您的描述,目前在控制台程序中调用TWAIN接口获取扫描仪图片时遇到的主要问题是:
要解决这个问题,有以下几点建议:
不必构造MFC的消息对象MSG,直接使用Windows消息即可。您当前代码中构造的windowsMsg对象是正确的。
需要在调用CallTwainProc后正确处理TW_EVENT中的消息,这对应TranslateMessage的处理。如果无法正确处理消息,状态码会返回失败。
不需要每2秒查询一次状态,可以直接注册TWAIN回调函数,在图片采集等状态变更时会自动触发回调。
TWAIN回调函数需要定义为全局函数,格式为:
TW_CALLBACK CallbackFunc(pTW_IDENTITY pOrigin, TW_UINT32 DG, TW_UINT16 DAT, TW_UINT16 MSG, TW_MEMREF pData);
TwainRegisterCallback(m_Source, CallbackFunc, pAppID);
综上,您应当:
构造windowsMsg作为TW_EVENT中的消息对象,而不必构造MSG对象
确保TranslateMessage正确处理TW_EVENT中的消息
注册TWAIN回调函数CallbackFunc,而不必每2秒查询状态
回调函数格式定义为TW_CALLBACK,需要在SourceEnable后调用TwainRegisterCallback进行注册
具体代码可修改为:
cpp
TW_CALLBACK CallbackFunc(pTW_IDENTITY pOrigin, TW_UINT32 DG, TW_UINT16 DAT, TW_UINT16 MSG, TW_MEMREF pData)
{
// 处理TWAIN回调消息,获取图像等
}
// 开启源并注册回调
SourceEnable();
TwainRegisterCallback(m_Source, CallbackFunc, pAppID);
// 主循环
while (true)
{
}
希望上述分析与建议能够帮助您解决问题,成功调用TWAIN接口获取扫描仪图像。
VC中基于TWAIN协议控制扫描仪
可以借鉴下
https://blog.csdn.net/sinat_29048381/article/details/80733727