仅在运行单元测试时出现 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:] 无法识别的选择器)

尝试通过测试单元引用数据库连接时出现 C#.net 异常

运行 Gradle JUnit 测试时出现“地址已在使用:绑定”异常

测试 CursorPaginator 时出现异常

运行测试单元时出现 VUE 错误

运行单元测试时出现语法错误后 PhantomJS 退出