仅在运行单元测试时出现 OLE DB 异常“类未注册”
Posted
技术标签:
【中文标题】仅在运行单元测试时出现 OLE DB 异常“类未注册”【英文标题】:OLE DB exception "Class not registered" only when running Unit Test 【发布时间】:2018-08-21 15:23:45 【问题描述】:我有一个基于 Windows 的 C++ 应用程序/库,它使用 Excel 环境作为界面。该库从 Access 数据库中检索信息。
C++ 库类基于 COM 的活动模板库。
当我运行/调试库时,一切正常。负责通过 COM 从 Access 数据库中检索数据的类函数在建立连接时没有问题。
但是当我运行一个单元案例(Microsoft C++ 单元测试框架)时,它在到达 atldbcli.h 中的函数 OpenFromInitializationString 时失败。
HRESULT OpenFromInitializationString(
_In_z_ LPCOLESTR szInitializationString,
_In_ bool fPromptForInfo = false) throw()
CComPtr<IDataInitialize> spDataInit;
HRESULT hr = CoCreateInstance(__uuidof(MSDAINITIALIZE), NULL, CLSCTX_INPROC_SERVER, __uuidof(IDataInitialize), (void**)&spDataInit);
if (FAILED(hr))
return hr;
hr = spDataInit->GetDataSource(NULL, CLSCTX_INPROC_SERVER, szInitializationString, __uuidof(IDBInitialize), (IUnknown**)&m_spInit);
if (FAILED(hr))
return hr;
在运行单元测试时,后者对 GetDataSource 的调用失败,并返回“类未注册”。运行或调试整个应用程序/库时,相同的代码会成功。 更令人费解的是,相同的解决方案在我同事的计算机上运行,无论是单元测试还是实际的应用程序/库。
我正在运行 Windows 10 64 位和 Office 365 专业增强版。我安装了 Visual Studio 2017。
如果有人知道发生了什么,我将不胜感激。请记住这些。
单元测试不在单独的项目中,而只是在主项目中的单独文件中。 Oledb32.dll 安装在以下位置 C:\Program Files (x86)\Common Files\System\Ole DB\oledb32.dll(版本 10.0.16299.15)szInitializationString 在这两种情况下都是这样的:
L"Provider=Microsoft.ACE.OLEDB.12.0;User ID=Admin;Data Source = C:\\Repos\\Xerf\\XerfLib\\XerfData.accdb;Mode = Share Deny None;Extended Properties = \"\";Jet OLEDB:System database=\"\";Jet OLEDB:Registry Path=\"\";Jet OLEDB:Database Password=\"\";Jet OLEDB:Engine Type=6;Jet OLEDB:Database Locking Mode=1;Jet OLEDB:Global Partial Bulk Ops=2;Jet OLEDB:Global Bulk Transactions=1;Jet OLEDB:New Database Password=\"\";Jet OLEDB:Create System Database=False;Jet OLEDB:Encrypt Database=False;Jet OLEDB:Don't Copy Locale on Compact=False;Jet OLEDB:Compact Without Replica Repair=False;Jet OLEDB:SFP=False;Jet OLEDB:Support Complex Data=False;Jet OLEDB:Bypass UserInfo Validation=False;Jet OLEDB:Limited DB Caching=False;Jet OLEDB:Bypass ChoiceField Validation=False"
我尝试创建一个最小的项目,它返回相同的错误。这是最小的代码。
#include "stdafx.h"
#include <string.h>
int main()
WCHAR m_DBInitializationString[1024];
WCHAR m_DatabasePath[_MAX_PATH];
int iTmp;
CDataSource db;
HRESULT hr;
CoInitialize(NULL);
size_t wclen = (sizeof m_DBInitializationString) / (sizeof m_DBInitializationString[0]);
wcscpy_s(m_DatabasePath, _MAX_PATH, L"C:\\Repos\\Xerf\\XerfLib\\XerfData.accdb");
iTmp = swprintf_s(m_DBInitializationString, wclen, L"%s", L"Provider=Microsoft.ACE.OLEDB.16.0;");
iTmp += swprintf_s(&m_DBInitializationString[iTmp], wclen - iTmp, L"%s", L"User ID=Admin;");
iTmp += swprintf_s(&m_DBInitializationString[iTmp], wclen - iTmp, L"Data Source = %s;", m_DatabasePath);
iTmp += swprintf_s(&m_DBInitializationString[iTmp], wclen - iTmp, L"%s", L"Mode = Share Deny None;");
iTmp += swprintf_s(&m_DBInitializationString[iTmp], wclen - iTmp, L"%s", L"Extended Properties = \"\";");
iTmp += swprintf_s(&m_DBInitializationString[iTmp], wclen - iTmp, L"%s", L"Jet OLEDB:System database=\"\";");
iTmp += swprintf_s(&m_DBInitializationString[iTmp], wclen - iTmp, L"%s", L"Jet OLEDB:Registry Path=\"\";");
iTmp += swprintf_s(&m_DBInitializationString[iTmp], wclen - iTmp, L"%s", L"Jet OLEDB:Database Password=\"\";");
iTmp += swprintf_s(&m_DBInitializationString[iTmp], wclen - iTmp, L"%s", L"Jet OLEDB:Engine Type=6;");
iTmp += swprintf_s(&m_DBInitializationString[iTmp], wclen - iTmp, L"%s", L"Jet OLEDB:Database Locking Mode=1;");
iTmp += swprintf_s(&m_DBInitializationString[iTmp], wclen - iTmp, L"%s", L"Jet OLEDB:Global Partial Bulk Ops=2;");
iTmp += swprintf_s(&m_DBInitializationString[iTmp], wclen - iTmp, L"%s", L"Jet OLEDB:Global Bulk Transactions=1;");
iTmp += swprintf_s(&m_DBInitializationString[iTmp], wclen - iTmp, L"%s", L"Jet OLEDB:New Database Password=\"\";");
iTmp += swprintf_s(&m_DBInitializationString[iTmp], wclen - iTmp, L"%s", L"Jet OLEDB:Create System Database=False;");
iTmp += swprintf_s(&m_DBInitializationString[iTmp], wclen - iTmp, L"%s", L"Jet OLEDB:Encrypt Database=False;");
iTmp += swprintf_s(&m_DBInitializationString[iTmp], wclen - iTmp, L"%s", L"Jet OLEDB:Don't Copy Locale on Compact=False;");
iTmp += swprintf_s(&m_DBInitializationString[iTmp], wclen - iTmp, L"%s", L"Jet OLEDB:Compact Without Replica Repair=False;");
iTmp += swprintf_s(&m_DBInitializationString[iTmp], wclen - iTmp, L"%s", L"Jet OLEDB:SFP=False;");
iTmp += swprintf_s(&m_DBInitializationString[iTmp], wclen - iTmp, L"%s", L"Jet OLEDB:Support Complex Data=False;");
iTmp += swprintf_s(&m_DBInitializationString[iTmp], wclen - iTmp, L"%s", L"Jet OLEDB:Bypass UserInfo Validation=False;");
iTmp += swprintf_s(&m_DBInitializationString[iTmp], wclen - iTmp, L"%s", L"Jet OLEDB:Limited DB Caching=False;");
iTmp += swprintf_s(&m_DBInitializationString[iTmp], wclen - iTmp, L"%s", L"Jet OLEDB:Bypass ChoiceField Validation=False");
hr = db.OpenFromInitializationString(m_DBInitializationString);
return hr;
这段代码包含在预编译头文件stdafx.h中
#pragma once
#ifndef STRICT
#define STRICT
#endif
#include "targetver.h"
#define m_dwRef m_dwRefAtl
#define m_pOuterUnknown m_pOuterUnknownAtl
#define InternalQueryInterface InternalQueryInterfaceAtl
#define InternalAddRef InternalAddRefAtl
#define InternalRelease InternalReleaseAtl
//#define _ATL_DEBUG_INTERFACES //Writes, to the output window, any interface leaks that are detected when _Module.Term is called.
//#define _ATL_DEBUG_QI //Writes all calls to QueryInterface to the output window.
#define _ATL_APARTMENT_THREADED
#define _ATL_NO_AUTOMATIC_NAMESPACE
#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // some CString constructors will be explicit
//#define ATL_NO_ASSERT_ON_DESTROY_NONEXISTENT_WINDOW
#include <atlbase.h>
#include <atlcom.h>
#include <atlctl.h>
#include <AtlConv.h>
// DB access
#include <atldbcli.h>
#include <atldb.h>
using namespace ATL;
最好的问候, 约翰
【问题讨论】:
应用程序和单元测试是否编译为相同的位数?如果您正在运行 64 位单元测试,但尚未注册 64 位版本的 COM 服务器,则会收到该错误。 @IInspectable 这是一个解决方案和一个项目,单元测试只是一个单独的cpp文件,与调试项目一起编译。应用程序和单元测试都使用相同的类和 atl 函数。所以两者都是 32 位的。 两个 cpp 文件中是否包含相同的内容?在 cpp 文件中调用该方法时是否具有相同的参数? (szInitializationString) 您必须关注它所抱怨的特定类。容易认为它是你自己的 ATL 类。但事实并非如此,它不会走到这一步,也不会在这里崩溃,它找不到 OLEDB 数据提供者。我们看不到您使用的连接字符串。 @RobertAndrzejuk 两者的输入参数完全相同(szInitializationString)。头文件是一样的。运行/调试时,对类的调用来自 Excel/VBA 通过 COM。运行单元测试时,对类和函数的调用是“直接”的。 【参考方案1】:这不是解释,而是解决问题的方法。
安装 Microsoft Access 2013 Runtime 32 位解决了这个问题。
即使我之前在初始化字符串中指定了Provider=Microsoft.ACE.OLEDB.12.0
,它也没有任何影响,因为唯一安装的 ACE 提供程序是属于 Office 2016 套件的那个。
如果 Office 2016 32 位 ACE 提供程序不起作用,我将不胜感激。
这是最小的项目https://github.com/jahlqvis/TestOLEDBAccess
【讨论】:
在 HKEY_CLASSES_ROOT 下的注册表中查找类名(也称为 ProgID)。在我的机器上,有一个注册表项 HKEY_CLASSES_ROOT\Microsoft.ACE.OLEDB.12.0,我可以加载该提供程序(使用您的测试程序)。我没有 HKEY_CLASSES_ROOT\Microsoft.ACE.OLEDB.16.0 并使用该 ProgID(在您的程序的未修改版本中)我得到“类未注册”错误。 @PhilJollans 我有 Microsoft.ACE.OLEDB.12.0 和 Microsoft.ACE.OLEDB.15.0。因此,Microsoft.ACE.OLEDB.16.0 不起作用也就不足为奇了。以上是关于仅在运行单元测试时出现 OLE DB 异常“类未注册”的主要内容,如果未能解决你的问题,请参考以下文章
单元测试 UIDatePicker 时出现运行时异常([UIPickerColumnView isDragging:] 无法识别的选择器)