【发布时间】:2013-08-21 16:55:14
【问题描述】:
我要做的是访问一个 COM 接口,然后调用该接口的“Open”方法。 我在 Visual Basic 中有一个可以正常工作的示例代码,但我需要用 C++ 编写它,我似乎无法让它工作。
首先,这是有效的 VB 代码:
Dim CANapeApplication As CANAPELib.Application
CANapeApplication = CreateObject("CANape.Application")
Call CANapeApplication.Open("C:\Users\Public\Documents\Vector\CANape\12\Project", 0)
CANape.Application 是选择我需要的接口的 ProgID。
在阅读了 msdn.microsoft.com 和 this question 上的一些文档后,我编写了以下代码:
void ErrorDescription(HRESULT hr); //Function to output a readable hr error
int InitCOM();
int OpenCANape();
// Declarations of variables used.
HRESULT hresult;
void **canApeAppPtr;
IDispatch *pdisp;
CLSID ClassID;
DISPID FAR dispid;
UINT nArgErr;
OLECHAR FAR* canApeWorkingDirectory = L"C:\\Users\\Public\\Documents\\Vector\\CANape\\12\\Project";
int main(){
// Instantiate CANape COM interface
if (InitCOM() != 0) {
std::cout << "init error";
return 1;
}
// Open CANape
if (OpenCANape() != 0) {
std::cout << "Failed to open CANape Project" << std::endl;
return 1;
}
CoUninitialize();
return 0;
}
void ErrorDescription(HRESULT hr) {
if(FACILITY_WINDOWS == HRESULT_FACILITY(hr))
hr = HRESULT_CODE(hr);
TCHAR* szErrMsg;
if(FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
NULL, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&szErrMsg, 0, NULL) != 0)
{
_tprintf(TEXT("%s"), szErrMsg);
LocalFree(szErrMsg);
} else
_tprintf( TEXT("[Could not find a description for error # %#x.]\n"), hr);
}
int InitCOM() {
// Initialize OLE DLLs.
hresult = OleInitialize(NULL);
if (!SUCCEEDED(hresult)) {
ErrorDescription(hresult);
return 1;
}
// Get CLSID from ProgID
//hresult = CLSIDFromProgID(OLESTR("CANape.Application"), &ClassID);
hresult = CLSIDFromProgID(OLESTR("CanapeCom.CanapeCom"), &ClassID);
if (!SUCCEEDED(hresult)) {
ErrorDescription(hresult);
return 1;
}
// OLE function CoCreateInstance starts application using GUID/CLSID
hresult = CoCreateInstance(ClassID, NULL, CLSCTX_LOCAL_SERVER,
IID_IDispatch, (void **)&pdisp);
if (!SUCCEEDED(hresult)) {
ErrorDescription(hresult);
return 1;
}
// Call QueryInterface to see if object supports IDispatch
hresult = pdisp->QueryInterface(IID_IDispatch, (void **)&pdisp);
if (!SUCCEEDED(hresult)) {
ErrorDescription(hresult);
return 1;
}
std::cout << "success" << std::endl;
return 0;
}
int OpenCANape() {
//Method name
OLECHAR *szMember = L"Open";
// Retrieve the dispatch identifier for the Open method
// Use defaults where possible
DISPID idFileExists;
hresult = pdisp->GetIDsOfNames(
IID_NULL,
&szMember,
1,
LOCALE_SYSTEM_DEFAULT,
&idFileExists);
if (!SUCCEEDED(hresult)) {
std::cout << "GetIDsOfNames: ";
ErrorDescription(hresult);
return 1;
}
unsigned int puArgErr = 0;
VARIANT VarResult;
VariantInit(&VarResult);
DISPPARAMS pParams;
memset(&pParams, 0, sizeof(DISPPARAMS));
pParams.cArgs = 2;
VARIANT Arguments[2];
VariantInit(&Arguments[0]);
pParams.rgvarg = Arguments;
pParams.cNamedArgs = 0;
pParams.rgvarg[0].vt = VT_BSTR;
pParams.rgvarg[0].bstrVal = SysAllocString(canApeWorkingDirectory);
pParams.rgvarg[1].vt = VT_INT;
pParams.rgvarg[1].intVal = 0; // debug mode
// Invoke the method. Use defaults where possible.
hresult = pdisp->Invoke(
dispid,
IID_NULL,
LOCALE_SYSTEM_DEFAULT,
DISPATCH_METHOD,
&pParams,
&VarResult,
NULL,
&puArgErr
);
SysFreeString(pParams.rgvarg[0].bstrVal);
if (!SUCCEEDED(hresult)) {
ErrorDescription(hresult);
return 1;
}
return 0;
}
这有几个问题。
- 使用从CLSIDFromProgID接收的ClassID作为CoCreateInstance的第一个参数不起作用,它返回错误:class not registered
- 如果我使用 ProgID CanapeCom.CanapeCom(我在注册表中找到它),CoCreateInstance 有效。但是,当我使用 pdisp->GetIDsOfNames 时,我收到错误消息:Unkown name。我认为这意味着找不到该方法。这似乎合乎逻辑,因为我使用了不同的 ProgID,但我就是不知道如何访问我正在寻找的界面。
- 我还尝试使用来自
CLSIDFromProgID(OLESTR("CANape.Application"), &ClassID);的结果 CLSID 作为 CoCreateInstance 的第四个参数,但这会导致 “不支持此类接口” 错误。
我需要软件的dll文件吗?在 VB 示例中,dll 文件用于获取接口,然后使用 ProgID 创建一个新对象。我不确定我是否需要在 C++ 中做同样的事情,或者这应该如何工作。
我真的被困在这里,希望有人可以帮助我。
【问题讨论】:
-
通常你只是让一些工具在 C++ 中围绕 COM 库生成一个包装器并使用它。如果您使用的是 Visual Studio,请尝试
#import "CANape.tlb"。 -
检查你没有制作 64 位版本,因为大概 CanApe 只有 32 位。
-
根据 Jonathan 的评论,如果是这种情况,您可以使用 COM 代理使其工作,但不要指望它是微不足道的(老实说,我不't recc it)。
-
您不能随意替换 ProgId,您需要让 CLSID 恢复工作。在 VB 代码和您的代码上都使用 SysInternals 的 ProcMon。您会看到它在注册表中搜索 CLSID 键,差异会很快弹出。
-
是的,+1 用于使用进程监视器 - 这是停止猜测并开始调试的唯一方法。