I'm working with a third party COM server with its own custom interface that sets and gets structs as some of its properties. As it happens I'm using C++ for the client. I've posted some representative code from the IDL file below with names changed and GUIDs removed.
Is the packing of the structure defined or is it just good fortune that my client code happens to use the same packing settings that the COM server was built with? Would it be likely to go wrong in projects where the default C++ compiler packing settings had been changed? Is there a pragma pack setting that I could use to make sure the client compiler packing settings are correct?
I can't see any packing pragmas or statements in either the IDL or the header file generated from MIDL. What would happen if the client was in C# or VB instead? Is the packing behaviour more clearly specified if called via the IDispatch mechanism?
struct MyStruct
{
int a, b;
};
[
object,
uuid( /* removed */ ),
dual,
nonextensible,
pointer_default(unique)
]
interface IVideoOutputSettings : IDispatch{
[propget, id(1), HRESULT MyProperty([out, retval] struct MyStruct* pVal);
[propput, id(1), HRESULT MyProperty([in] struct MyStruct newVal);
/* other methods */
};
The default packing is along 8-byte boundaries, according to the MIDL command line switch reference here:
/Zp switch # MSDN (MIDL Language Reference)
Other parts of your code are more likely to break first if the pack value is changed, as the IDL file is usually pre-compiled ahead of time, and it is rare that someone will deliberately alter the command line switches given to MIDL (but not so rare that someone could fiddle with the C-scope #pragma pack and forget to restore the default state).
If you have a good reason to alter the setting, you can explicitly set the packing with a pragma pack statement.
pragma Attribute # MSDN (MIDL Language Reference)
It is pretty good fortune that no party has changed any setting that would interfere with the default packing. Can it go wrong? Yes, if someone goes out of their way to change the defaults.
When using an IDL file, the details are typically compiled into a typelib (.tlb), and it is assumed that the platform are the same for both servers and clients when using the same typelib. This is suggested in the footnotes for the /Zp switch, as certain values will fail against certain non-x86 or 16-bit targets. There can also be 32bit <-> 64bit conversion cases that could cause expectations to break. Unfortunately I don't know if there are even more cases out there, but the defaults do work with minimal fuss.
C# and VB do not have any intrinsic behavior to handle information in a .tlb; instead, a tool like tlbimp is typically used to convert COM definitions into definitions usable from .NET. I can't verify whether all expectations succeed between C#/VB.NET and COM clients and servers; However, I can verify that using a specific pragma setting other than 8 will work if you reference a .tlb that was created from an IDL compiled under that setting. While I wouldn't recommend going against the default pragma pack, here are the steps to perform if you'd like a working example to use as a reference. I created a C++ ATL project and a C# project to check.
Here are the C++ side instructions.
I created an ATL project called SampleATLProject with the default settings in Visual Studio 2010, no fields changed. This should create a dll project for you.
Compiled the project to assure that the proper C-side interface files are being created (SampleATLProject_i.c and SampleATLProject_i.h).
I added an ATL Simple Object called SomeFoo to the project. Again, no defaults were altered. This creates a class called CSomeFoo that is added to your project.
Compile SampleATLProject.
I right-clicked the SampleATLProject.idl file, then under the MIDL settings, set the Struct Member Alignment to 4 bytes (/Zp4).
Compile SampleATLProject.
I altered the IDL to add a struct definition called 'BarStruct'. This entailed adding a C-style struct definition with the MIDL uuid attribute, and an entry in library section referencing the struct definition. See snippet below.
Compile SampleATLProject.
From the Class View, I right-clicked on ISomeFoo and added a method called FooIt, that takes a struct BarStruct as an [in] parameter called theBar.
Compile SampleATLProject.
In SomeFoo.cpp, I added some code to print out the size of the struct and throw up a Message Box containing the details.
Here is my IDL for the ATL project.
import "oaidl.idl";
import "ocidl.idl";
[uuid(D2240D8B-EB97-4ACD-AC96-21F2EAFFE100)]
struct BarStruct
{
byte a;
int b;
byte c;
byte d;
};
[
object,
uuid(E6C3E82D-4376-41CD-A0DF-CB9371C0C467),
dual,
nonextensible,
pointer_default(unique)
]
interface ISomeFoo : IDispatch{
[id(1)] HRESULT FooIt([in] struct BarStruct theBar);
};
[
uuid(F15B6312-7C46-4DDC-8D04-9DEA358BD94B),
version(1.0),
]
library SampleATLProjectLib
{
struct BarStruct;
importlib("stdole2.tlb");
[
uuid(930BC9D6-28DF-4851-9703-AFCD1F23CCEF)
]
coclass SomeFoo
{
[default] interface ISomeFoo;
};
};
Inside the CSomeFoo class, here is the implementation for FooIt().
STDMETHODIMP CSomeFoo::FooIt(struct BarStruct theBar)
{
WCHAR buf[1024];
swprintf(buf, L"Size: %d, Values: %d %d %d %d", sizeof(struct BarStruct),
theBar.a, theBar.b, theBar.c, theBar.d);
::MessageBoxW(0, buf, L"FooIt", MB_OK);
return S_OK;
}
Next, on the C# side:
Go to the debug or desired output directory for SampleATLProject and run tlbimp.exe on the .tlb file generated as part of the C++ project output. The following worked for me:
tlbimp SampleATLProject.tlb /out:Foo.dll /namespace:SampleATL.FooStuff
Next, I created a C# console application, and added a reference to Foo.dll to the project.
In the References folder, go to the Properties for Foo and turn off Embed Interop Types by setting it to false.
I added a using statement to reference the namespace SampleATL.FooStuff as given to tlbimp, added the [STAThread] attribute to Main() (the COM apartment models have to match for in-proc consumption), and added some code to call the COM component.
Tlbimp.exe (Type Library Importer) # MSDN
Here is the source code for that console app.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SampleATL.FooStuff;
namespace SampleATLProjectConsumer
{
class Program
{
[STAThread]
static void Main(string[] args)
{
BarStruct s;
s.a = 1;
s.b = 127;
s.c = 255;
s.d = 128;
ISomeFoo handler = new SomeFooClass();
handler.FooIt(s);
}
}
}
Finally, it runs and I get a modal popup with the following string displayed:
Size: 12, Values: 1 127 255 128
To be sure that a pragma pack change can be made (as 4/8 byte packing are the most common alignments used), I followed these steps to change it to 1:
I returned to the C++ project, went to the properties for SampleATLProject.idl and changed the Struct Member Alignment to 1 (/Zp1).
Recompile SampleATLProject
Run tlbimp again with the updated .tlb file.
A warning icon will appear on the .NET File Reference to Foo, but may disappear if you click on the reference. If it doesn't, you can remove and re-add the reference to the C# console project to be sure it is using the new updated version.
I ran it from here and got this output:
Size: 12, Values: 1 1551957760 129 3
That's weird. But, if we forcefully edit the C-level pragma in SampleATLProject_i.h, we get the correct output.
#pragma pack(push, 1)
/* [uuid] */ struct DECLSPEC_UUID("D2240D8B-EB97-4ACD-AC96-21F2EAFFE100") BarStruct
{
byte a;
int b;
byte c;
byte d;
} ;
#pragma pack(pop)
SampleATLProject is recompiled here, no changes to the .tlb or .NET project, and we get the following:
Size: 7, Values: 1 127 255 128
Regarding IDispatch, it depends on whether your client is late-bound. Late-bound clients have to parse the type information side of IDispatch and discern the proper definitions for non-trivial types. The documentation for ITypeInfo and TYPEATTR suggests that it is possible, given that the cbAlignment field provides the information necessary. I suspect most will never alter or go against the defaults, as this would be tedious to debug if things went wrong or if the pack expectations had to change between versions. Also, structures are not typically supported by many scripting clients that can consume IDispatch. One can frequently expect that only the types governed by the IDL oleautomation keyword are supported.
IDispatch interface # MSDN
IDispatch::GetTypeInfo # MSDN
ITypeInfo interface # MSDN
TYPEATTR structure # MSDN
oleautomation keyword # MSDN
Yes, structs are a problem in COM. If you use IUnknown based interfaces then you'll have to roll the dice with proper compiler settings. Few reasons to change the default.
If you use COM Automation then you have to declare the struct with a typedef in the .IDL. So that the client code can use IRecordInfo to access the structure properly, guided by the type library info. All you have to do is ensure that your compiler's /Zp setting matches midl.exe's /Zp setting. Not hard to do.
You sail around the problem entirely by realizing that any structure can be described by an interface with properties. Now it doesn't matter.
Related
I'm trying to import a COM interface into VC++. The COM object is from a application called IDEA, but as that is not very easy to get a hold of for others to help me. So I figure that if someone could give me instructions as to how I would do this for Word, it would be equivalent.
IDEA does have a .tlb file, but it would appear that it is incomplete. I can access the COM API using python with an example being something like this:
if __name__ == "__main__":
dbName = "Sample-Employees.IMD"
idea = win32ComClient.Dispatch(dispatch="Idea.IdeaClient")
db = idea.OpenDatabase(dbName) # open db
table_def = db.TableDef() # get table definition
Using the .tbl file, I can get as far as this:
#import "D:\Program Files (x86)\CaseWare IDEA\IDEA\Idea.tlb"
#include "x64\Debug\idea.tlh"
#include "x64\Debug\idea.tli"
void fn()
{
Idea::IIdeaClientPtr client;
auto db = client->OpenDatabase("Sample-Employees.IMD");
db-> // interface not defined
}
Intellisense will complete after the db-> with the following: AddRef, GetIdOfNames, GetTypeInfo, GetTypeInfoCount, Invoke, QueryInterface and Release. Thus, what I mean by an incomplete interface definition.
Now, since the python example states Idea.IdeaClient, and I've seen this with word as well (i.e. word.application), I was thinking that it might be possible to use that. Looking around though, I can't seem to find reference to that using #import. I have seen it being used with CLSIDFromProgID, but that is very manual mechanism. COM SMARTPTRs would be far more preferable.
Is this even possible to do with VC++?
Maybe OpenDatabase returns IDispatch, but interface containing TableDef is still defined in TLB.
In this case you'll need to downcast IDispatch to I-something-containing-TableDef-method.
Use QueryInterface call to get derived interface from IDispatch, not C or C++ casts, such as static_cast.
Otherwise, you'll need to use IDispatch::Invoke. The best help you have is CComPtr<IDispatch> from ATL, this template specialization have Invoke helpers, so that you can do something like this:
CComPtr<IDispatch> p;
p = db;
CComVairant result;
p.Invoke("TableDef", &result);
Or use IDispatch::Invoke as is.
Python aways relies on IDispatch::Invoke and does not use static interfaces, that's why it does not encounter this problem.
I use Effect framework in my demo.
here are my defination in header file
ID3D11Buffer* m_pVertexBuffer;
ID3D11Buffer* m_pIndexBuffer;
ID3D11InputLayout* m_pInputLayout;
ID3DX11Effect* m_pFx;
ID3DX11EffectTechnique* m_pTechnique;
ID3DX11EffectMatrixVariable* m_pFxWorldViewProj;
I release them as follow
void HillsDemo::UnLoadContent()
{
if (m_pVertexBuffer) m_pVertexBuffer->Release();
if (m_pIndexBuffer) m_pIndexBuffer->Release();
if (m_pInputLayout) m_pInputLayout->Release();
if (m_pTechnique) m_pTechnique->Release();
if (m_pFx) m_pFx->Release();
}
then I run the demo,when I close the demo window, there is an error which means HillsDemo has stopped working.Why?Then I delete the lineif (m_pFx) m_pFx->Release();,there is no error. So is releasing the m_pFx make the error?
I view the documents of Effect11 https://github.com/Microsoft/FX11/wiki/Effects-11, there are on the document:
The following types are now derived from IUnknown: ID3DX11EffectType, ID3DX11EffectVariable, I3DX11EffectPass, ID3DX11EffectTechnique, ID3DX11EffectGroup. Note that these objects do not follow standard COM reference-counting rules and they are released when the parent ID3DX11Effect is released. This is mostly to simplify use of Effects 11 from managed languages.
Does it means that I should only release the m_pFx rather than release both m_pFx and m_pTechnique? Any ideas?
I assume you are using the latest version of Effects 11 from GitHub rather than the legacy DirectX SDK copy based on your reference to the project wiki.
You should really look at using ComPtr instead of raw pointers to COM objects. In the case of Effects 11, you'd just use them for ID3DX11Effect instances. You should stick with 'raw pointers' for ID3DX11EffectType, ID3DX11EffectVariable, I3DX11EffectPass, ID3DX11EffectTechnique, and ID3DX11EffectGroup. Don't bother calling Release on these raw pointers as the lifetime controlled by the ID3DX11Effect.
Something like:
Microsoft::WRL::ComPtr<ID3D11Buffer> m_pVertexBuffer;
Microsoft::WRL::ComPtr<ID3D11Buffer> m_pIndexBuffer;
Microsoft::WRL::ComPtr<ID3D11InputLayout> m_pInputLayout;
Microsoft::WRL::ComPtr<ID3DX11Effect> m_pFx;
ID3DX11EffectTechnique* m_pTechnique;
ID3DX11EffectMatrixVariable* m_pFxWorldViewProj;
void HillsDemo::UnLoadContent()
{
m_pVertexBuffer.Reset();
m_pIndexBuffer.Reset();
m_pInputLayout.Reset();
m_pFx.Reset();
m_pTechnique = nullptr;
pFxWorldViewProj = nullptr;
}
I started the project as an MFC Application (for the GUI..), and later added support for ATL.
I then coded a simple ATL-COM object implementing a non registered dual interface using IDispatchImpl, with the 0xfff for Major and Minor, to tell ATL to load the TLB from the EXE.
I skip some details, but at the end, after some debugging I found that the CComTypeInfoHolder::GetTI implementation in atlcom.h was NOT trying to load the TLB from the EXE, but was searching it in the registry. Reason : a m_plibid variable was NOT corresponding to the DECLARE_LIBID macro use in my ATL::CAtlMfcModule declaration.
After some googling I found Bug: CAtlMfcModule::InitLibId() not called and added a call to InitLibId in my module CTOR.
Works fine, now.
Question: Is that a known bug? with a known fix? I am not confortable with my workaround of such an old bug. Is there another way of dealing with that?
UPDATE: additional information, as an answer states there is no bug...
IDispatchImpl Class:
By default, the IDispatchImpl class looks up the type information for
T in the registry. To implement an unregistered interface, you can use
the IDispatchImpl class without accessing the registry by using a
predefined version number. If you create an IDispatchImpl object that
has 0xFFFF as the value for wMajor and 0xFFFF as the value for wMinor,
the IDispatchImpl class retrieves the type library from the .dll file
instead of the registry.
Excerpt from CComTypeInfoHolder::GetTI Implementation in atlcom.h:
if (InlineIsEqualGUID( CAtlModule::m_libid, *m_plibid) &&
m_wMajor == 0xFFFF &&
m_wMinor == 0xFFFF ) {
TCHAR szFilePath[MAX_PATH];
DWORD dwFLen = ::GetModuleFileName(_AtlBaseModule.GetModuleInstance(), szFilePath, MAX_PATH);
[...]
hRes = LoadTypeLib(pszFile, &pTypeLib);
} else {
[...]
hRes = LoadRegTypeLib(*m_plibid, m_wMajor, m_wMinor, lcid, &pTypeLib);
So, it seems clear to me that there is an advertised behavior: use 0xffff for minor and major and ATL will try to load the typelib from module, not from registry, provided that your CAtlModule::m_libid is up todate. How is CAtlModule::m_libid expected to be be up to date? By using the DECLARE_LIBID macros. How does work that macro? by defining a static InitLibId function, which set up CAtlModule::m_libid.
The bug: when your module derives from ATL::CAtlMfcModule, the defined InitLibId function is NOT called (as ATL::CAtlMfcModule is not a class template)
You are correct, if you are using -1 for major/minor versions, then is is assumed that type information would be taken from the binary. This however does not work with MFC projects: DECLARE_LIBID only works up to CAtlMfcModule class but not its descendants.
A quick fix might be like this, in atlbase.h:
//class CAtlMfcModule :
// public ATL::CAtlModuleT<CAtlMfcModule>
template <typename T>
class CAtlMfcModuleT :
public ATL::CAtlModuleT<T>
and then in your project:
//class CMFCApplication1Module :
// public ATL::CAtlMfcModule
class CMFCApplication1Module :
public ATL::CAtlMfcModuleT<CMFCApplication1Module>
If you post it on MS Connect as a bug, you can leave a link here for others to go upvote the bug.
Using the MFCApplication wizard in Visual C++ 2012, if "Generate attributed database class" is checked, a header with some special syntax (attributed C++ classes) are generated, which look like this:
// MFCApplication2Set.h: interface of the CMFCApplication2Set class
//
#pragma once
// code generated on March-05-13, 9:26 AM
[
db_source(L"Provider=SQLNCLI11.1;..."),
db_table(L"dbo.tblEmployee")
]
class CMFCApplication2Set
{
public:
... big list of stuff that corresponds to the fields in your db table omitted ...
}
The above header corresponds to a mostly empty implementation file:
// MFCApplication2Set.cpp : implementation of the CMFCApplication2Set class
//
#include "stdafx.h"
#include "MFCApplication2.h"
#include "MFCApplication2Set.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CMFCApplication2Set implementation
That was the WHOLE implementation class. Notice that:
A. No parent class name is specified anywhere.
B. There's some "Visual C++ magic" going on here, that is, as a novice to modern C++, I'm lost.
db_source is documented here but the documentation is pretty thin or at least opaque to me.
Questions:
I was mystified by this syntax when I first saw it, but I figured out it's probably a variant of this Attributes feature in Visual C++. It is that, right?
How am I meant to I use these generated "attributed database class" objects? I found this documentation but if you look carefully at that documentation, that code sample is showing people the Old Way and the New Way, and is not telling me what I want to know which is how to use this new object that the IDE can not give me any documentation or code completion features for. Also since the generated code for the CMFCApplication2Set class generated by the wizard does not reference any types or class names, I'm lost. If I could even use some IDE feature to know what methods and stuff have been Magically Injected into this Magical mystery Object, I'd be better off. The only think I can think to do is to learn the old way and learn all the things you can call from the old two-separate-ATL-types world, and then somehow learn to combine them.
In a nutshell, I'm looking for the minimum syntax I need to know to actually use one of these Attributed Database Class instances, variables, as they are generated in a new MFC app by the wizard. The instance shown below is a member of an MFC document class and CMFCApplication2Set m_MFCApplication2Set is declared as a field inside the MFC document class.
What I have tried is to use this "untyped object". This object appears to have lots of data fields (m_X) and has only one method that shows up in IDE code completion, called GetRowSetProperties. Thanks to whatever magic or injection is going on, this generated Attributed Database Class (which does not visibly inherit anything) is a complete mystery to me at edit time and compile time.
Here's me just trying to inspect this thing to see if it even initialized itself when its constructor ran:
BOOL CMFCApplication2Doc::OnNewDocument() { if (!CDocument::OnNewDocument()) return FALSE;
TRACE( m_MFCApplication2Set.m_AStringFieldName); // outputs NOISE.
return TRUE; }
At compile time and edit time, the IDE gives me NO help about the types involved in this "anonymous" class that inherits from nothing, but which gets lots of secret powers due to some kind of Injection via those attributes I'm guessing. At debug time, I can see that there is more than just a bunch of data fields in this C++ class, but this still doesn't help me know how to use it. A minimal code sample of using one of these to go get a recordset from the database, would be great.
Update: Calling OpenDataSource is fun, because it compiles but the IDE doesn't think it should be valid. Nevertheless, it runs, and returns 0 as the result, but that doesn't actually initialize this CThingyThatVisualStudioGaveYouThatYouDontKnowWhatItIs:
This is a deprecated feature of attributed C++ code. Pre-processor expands code and replaces attributes with actual base classes. If you enable generation of these intermediate files, things are going to be more clear to you:
You will have XXX.mrg.cpp and XXX.mrg.h files generated, which you can review and see the real C++ code forwarded to compiler.
The attributes will be replaced with substituted bases classes, maps like BEGIN_COLUMN_MAP etc. The attributed source code is compact, but the feature is deprecated and looking into expanded code it should be easy (if necessary) to strip the attributes and copy expanded code right into source. It's easy with DB attributes, and more difficult with COM attributes since the internal dependencies are most sophisticated.
We've created our own implementation of IXMLHttpRequest in a COM server (.exe) like so:
interface IMyXMLHttpRequest : IXMLHttpRequest {
...
};
coclass MyXMLHttpRequest {
[default] interface IMyXMLHttpRequest;
};
The problem is that when the build tries to register the COM server, we get the error "Error accessing the OLE registry". I debugged the registration code and it is failing in RegisterTypeLib. It looks like it is trying to pull in some of the type information relating to IXMLHttpRequest and (guessing here) can't change some registry keys related to that interface.
Is it just plain wrong to derive from IXMLHttpRequest? Should we be deriving from IDispatch instead and making our class use a dual interface? Or is it possible to derive from IXMLHttpRequest and we're just doing it wrong?
Update: I've uploaded a reproducible test case. I simply generated an ATL COM server using the Visual Studio wizard, and then I created a new interface derived from IXMLHttpRequest and a coclass that implements it. The registration fails as I described. If I change the interface to derive from IDispatch then it works fine. 100% reproducible on Windows 7 using Visual Studio 2010, running with elevated privileges.
error MSB3073: :VCEnd" exited with code -2147319780.
Just for the record, the error is 0x8002801C TYPE_E_REGISTRYACCESS "Error accessing the OLE registry."
As you already identified, the problem is around inheriting from IXMLHttpRequest interface which is defined outside of the type library. Extending an interface through inheritance is basically not a good idea in first place. Yes it is possible and it makes sense, however as soon as you approach putting this into a type library and having external references, you might be starting hitting weird issues.
As soon as you referenced IXMLHttpRequest, MIDL compiler is trying to put it into your type library as well. You can witness this by looking into intermediate build files:
It is not what you wanted, is it? You just wanted to reference it because it is already defined and hosted by another type library in msxml6.dll file in system32 (syswow64) directory.
The main question is why you want to inherit from IXMLHTTPRequest. Why you think a "normal" separate new IDispatch-derived interface is not good enough here? You can still implement IXMLHTTPRequest on this COM class as well. And you would not get into this trouble in first place then.
Anyway, the building problem is that on IDL the compiler sees definition of IXMLHTTPRequest coming from Windows SDK file directly.
You want to change your IDL file as follows:
import "oaidl.idl";
//import "ocidl.idl"; // <<--- Make direct IXMLHTTPRequest definition invisible
[
uuid(7397D60F-A428-42C5-B698-9FA850638074),
version(1.0),
]
library COMServerTestLib
{
importlib("stdole2.tlb");
importlib("msxml6.dll"); // <<--- Reference MSXML type library to import the right interface
In your C++ file you want to make the interface visible for C++ code:
#include "stdafx.h"
#include "resource.h"
#include <msxml6.h> // <<--- Re-add IXMLHTTPRequest definition for C++ code
#include "COMServerTest_i.h"
Your project is buildable again from here.