Home   Notes Contact Me

ATL/COM

Local

External


Types allowed for Properties

COM value type COM reference type System type
bool bool * System.Int32
char, small char *, small * System.SByte
short short * System.Int16
long, int long *, int * System.Int32
Hyper hyper * System.Int64
unsigned char, byte unsigned char *, byte * System.Byte
wchar_t, unsigned short wchar_t *, unsigned short * System.UInt16
unsigned long, unsigned int unsigned long *, unsigned int * System.Int32
unsigned hyper unsigned hyper * System.UInt64
float float * System.Single
double double * System.Double
VARIANT_BOOL VARIANT_BOOL * System.Boolean
void * void ** System.IntPtr
HRESULT HRESULT * System.Int16 or System.IntPtr
SCODE SCODE * System.Int32
BSTR BSTR * System.String
LPSTR or [string, ...] char * LPSTR * System.String
LPWSTR or [string, ...] wchar_t * LPWSTR * System.String
VARIANT VARIANT * System.Object
DECIMAL DECIMAL * System.Decimal
DATE DATE * System.DateTime
GUID GUID * System.Guid
CURRENCY CURRENCY * System.Decimal
IUnknown * IUnknown ** System.Object
IDispatch * IDispatch ** System.Object
SAFEARRAY(type) SAFEARRAY(type) * type[]

Support for getting properties from HTML's OBJECT PARA

Do the following steps to enable PARAM support.

The HTML

The ActiveX controls .h file


String Conversion

Macro
CW2A asciiStr( wideStr)

Text Field


Radio Button


Connection Points

Connection Point Example

Note: here is a good article about adding a connection point to an existing COM object:
The title is very misleading "Dispinterface vs. Events and Runtime Sinks" http://www.codeguru.com/Cpp/COM-Tech/atl/article.php/c3573/


STDMETHOD and STDMETHODIMPL


// proto:
STDMETHOD(put_Sides)(SHORT newVal);

// impl:
STDMETHODIMP CPolyCtl::put_Sides(SHORT newVal)
{
    return S_OK;
}

Registering Via Explorer Right Click

register_in_menu.reg

Make a .reg file containing this:


REGEDIT4

[HKEY_CLASSES_ROOT\.dll]
@="dllfile"

[HKEY_CLASSES_ROOT\.ocx]
@="ocxfile"

[HKEY_CLASSES_ROOT\.olb]
@="olbfile"

[HKEY_CLASSES_ROOT\.exe]
@="exefile"


[HKEY_CLASSES_ROOT\dllfile\shell\Register\command]
@="regsvr32.exe %1"

[HKEY_CLASSES_ROOT\ocxfile\shell\Register\command]
@="regsvr32.exe %1"

[HKEY_CLASSES_ROOT\olbfile\shell\Register\command]
@="regsvr32.exe %1"

[HKEY_CLASSES_ROOT\exefile\shell\Register\command]
@="%1 /register"

[HKEY_CLASSES_ROOT\dllfile\shell\Register (Silent)\command]
@="regsvr32.exe /s %1"

[HKEY_CLASSES_ROOT\ocxfile\shell\Register (Silent)\command]
@="regsvr32.exe /s %1"

[HKEY_CLASSES_ROOT\olbfile\shell\Register (Silent)\command]
@="regsvr32.exe /s %1"



[HKEY_CLASSES_ROOT\dllfile\shell\UnRegister\command]
@="regsvr32.exe /u %1"

[HKEY_CLASSES_ROOT\ocxfile\shell\UnRegister\command]
@="regsvr32.exe /u %1"

[HKEY_CLASSES_ROOT\olbfile\shell\UnRegister\command]
@="regsvr32.exe /u %1"

[HKEY_CLASSES_ROOT\exefile\shell\UnRegister\command]
@="%1 /unregister"

[HKEY_CLASSES_ROOT\dllfile\shell\UnRegister (Silent)\command]
@="regsvr32.exe /u /s %1"

[HKEY_CLASSES_ROOT\ocxfile\shell\UnRegister (Silent)\command]
@="regsvr32.exe /u /s %1"

[HKEY_CLASSES_ROOT\olbfile\shell\UnRegister (Silent)\command]
@="regsvr32.exe /u /s %1"

Registering Via Command line


regsvr32.exe
c:\windows\system32\regsrv32.exe

Registering a COM object


Registry Manipulation


	CRegKey key;
	retval = key.Open( HKEY_CLASSES_ROOT,
		"CLSID\\{0C234ACE-FF67-49F7-ABFD-C62A8D1F94EF}\\InprocServer32", KEY_READ);
	TCHAR buf[1000];
	key.QueryStringValue( "", buf, &nChars);

STL issue

Some stl container types need the operator& of a object to return its address. But CComBSTR and other classes override it. So you need to make them 'unoverride it'. To do this, wrapthen in CAdapt<>, example: std::list<CAdapt<CComBSTR>>


Creatablity, making an Interface not creatable from outside


Categories

Categories are used to group COM Objects. For Example you can say your object belongs to CATID_MyCoolControls - you can have the thing that uses it look for all the members of CATID_MyCoolControls and load only them


// defining a CATID:
struct __declspec(uuid("{EA4A7D89-7E4E-4450-B607-4E44E1F71CFD}")) CATID_MyCoolControls;

// Put a map like this COM classes header file somewhere:
BEGIN_CATEGORY_MAP(MyControl5)
	IMPLEMENTED_CATEGORY(__uuidof(CATID_MyCoolControls))
END_CATEGORY_MAP()

Licensing, adding support for

Add a DECLARE_CLASSFACTORY2({classname}) macro to your Interfaces .h file. Example:


class ATL_NO_VTABLE DvoResourceManager :
	...
{
public:
	DvoResourceManager();

DECLARE_REGISTRY_RESOURCEID(IDR_DVORESOURCEMANAGER)
DECLARE_CLASSFACTORY2(DvoResourceManager)

BEGIN_COM_MAP(DvoResourceManager)
	...

Singleton, Making an Interface one

Add a DECLARE_CLASSFACTORY_SINGLETON({classname}) macro to your Interfaces .h file. Example:


class ATL_NO_VTABLE DvoResourceManager :
	...
{
public:
	DvoResourceManager();

DECLARE_REGISTRY_RESOURCEID(IDR_DVORESOURCEMANAGER)
DECLARE_CLASSFACTORY_SINGLETON(DvoResourceManager)

BEGIN_COM_MAP(DvoResourceManager)
	...

#import

#import "{name}.tlb" provides the following: (Note: you can see this implementation of this stuff in the .tli file)


// pseudo .idl
interface Test1
{
	[id(1), helpstring("method Add")] HRESULT Add( [out] LONG* pOut, LONG a, LONG b);
	[propget, id(2), helpstring("")] HRESULT val([out, retval] short *pVal);
	[propput, id(2), helpstring("")] HRESULT val([in] short val);
}

coclass Test
{
	[default] interface Test1;
	interface Test2;
}

// pseudo .cpp file
#import "Test.tlb"

try {
	// smart pointer
	Test::ITest1Ptr p( __uuidof( Test::Test1));
	// returns a value and throws an exception if failure
	int sum = spITest->Add( 5, 5);
	// Access properties as data members
	int val = spITest->val;
	spITest->val = 37;
}
catch (const _com_error & Err) {
	// err number is:
	Err.Error();
}

Note: you don't always need enable exception handling in the compiler (Project Properties | C/C++ | Code Generation | Enable C++ Exceptions), because that has to do with unwinding locally declared objects.


Initialization (for class data, and for instance data)

For Class Data

For each interface entry in coclass (in the .idl file) the method ObjectMain( bool starting) will be called. It is called with true on startup and false on shutdown. These are called once when the COM server starts up and shuts down. Override it do do your own startup initialization and shutdown cleanup.

For Instance Data

Since Interface methods cannot be called from the constructor, due to no vtable until after the constructor returns; Initialization should occur in FinalConstruct (which should be defined as an empty inline method in the COM classes header file)

Note: FinalConstruct MUST return S_OK on success (any other success code is considered a failure)

Another good thing about using FinalConstuct, is that you can fail out of it to prevent your object from being returned (I assume it just destroys it)

Beware of the gotcha 'Premature deletion of an object in FinalConstruct'

There is also a FinalRelease, but I don't know why you would need to use it instead of the destructor.


Gotchas

Enum, cannot get to it when using #import "file.tlb"

It is reached by {Namespace}{enum val name} be careful not to use {Namespace}::{enum typedef name}::{enum val name}

ComPtr not being found even though type lib is imported

#import "name.tlb" always defines a namespace, use the namespace to get to the ComPtr you want.
The namespace is named after the library name (which can be found in the .idl file)

... unresolved external symbol _main

ATL (by default) doesn't include the full CRT (C Runtime library), and you hit some code that needs part of it that isn't available. Remove the definition of _ATL_MIN_CRT so that it will include the full CRT. It looks like the way to turn this off is now from Project Properties | General | Minimize CRT Use in ATL

Premature deletion of an object in FinalConstruct

If FinalConstruct causes an AddRef() and a Release() to be called it will delete itself (this may happen if you do a QueryInterface in FinalConstruct). To prevent this from happening, add this macro to your COM classes header file: DECLARE_PROTECT_FINAL_CONSTRUCT() (this seems to be added by default now)

SaveAllChanges Failed

The .tlb is probably loaded in some app, and cannot be written.


Registering a COM object Manually

  1. Open the .idl file and copy the coclass guid into the clipboard
  2. Run regedit
  3. Right click on HK_CLASSES_ROOT/CLSID and choose New | Key
  4. Paste the coclass guid (Make sure the value is surrounded by braces { } (just like the other entries)
  5. Select the newly created key, and double click on Default to edit it
  6. Set the Value to the name of coclass
  7. Add a new key under the just created key, and name it InprocServer32
  8. Set the Default value to the fully specified path of the COM .dll
  9. Right click in the right hand pane to add a new String value to InprocServer32
  10. Name the String ThreadingModel and set its value to Apartment
  11. You should be able to see the COM object in the OLE/COM Object Viewer it is OleView.exe or in the tools menu of Visual Studio. It will be at Object Classes | All Objects

CLSID's (Class IDs)


//CLSID if using import
#import "Something.tlb"
// Note: use the class name, not the Interface name ISomething
// you can find it in the .idl file as the coclass value
__uuidof(Something)

//CLSID from a String
CLSID clsId;
CLSIDFromString(L"{145AE31A-E4AE-4400-A485-A39900E03C84}", &clsId);

//CLSID from ProgID
CLSIDFromProgID(); // inverse is: ProgIDFromCLSID();

Return Values

S_OK
S_FAIL
if (SUCCEEDED(hr))
if (FAILED(hr))

Debugging

Debug Macros

MacroPurpose
_ATL_DEBUG_INTERFACESShows Interface Leaks when _Module.Term is called.
_ATL_DEBUG_QIShows when QueryInterface is called.
ATLTRACE2See Micosofts docs, controls lots of settings.

Debugging Reference Counts

  1. Make sure _ATL_DEBUG_INTERFACES is defined before the atl headers are included (best to just put it in stdafx.h)
  2. Run then exit your program in debug mode. When you finish, the output window in Visual Studio with show a line like:
    QIThunk - 10        	Release :	Object = 0x01d1aa6c	Refcount = 0	CGLX2DOGL - IDvoGLX2D
  3. Find the QIThunk # for the Object you want to look at references counts for (it is 10 in the example line above)
  4. Modify your code, so that somewhere before that object is first created you do this:
    _AtlDebugInterfacesModule.m_nIndexBreakAt = {QIThinkNumberGoesHere};
  5. Now DebugBreak(); will be called when there is Reference count activity on the specified Object Class.
  6. So just run again in the debugger, and watch the debugging happen.
  7. NOTE: it is good to put a breakpoint in atlbase.h in bool CAtlDebugInterfacesModule::DumpLeakedThunks() at the line m_aThunks.RemoveAll();, then it will break just after dumping the leaked list to the output window (so you don't have to scroll around for it).

_AtlDebugInterfacesModule is of type CAtlDebugInterfacesModule there are some other fields you can access in it ( m_nIndexQI, m_cs, and m_aThunks )

It seems like there is a way to remove a thunk from _AtlDebugInterfacesModule at runtime (from something I read about it).

Here is the article that tipped me off about this way to debug reference counts: http://codeguru.earthweb.com/atl/atldebug.shtml


BSTR

WARNING: Do NOT make a BSTR like this: BSTR str = L"Hello";. While it may work in many cases, it is not right and will not delete correctly.

Storage Format

BSTRs are pointers to the 5th byte of a memory block. When they are 'free()ed', they are 'free()ed' from 4 bytes before where they point to. They start with a count of the number of bytes of character data they contain, excluding 2 terminating 0 bytes at the end of the string.

Example Usage


BSTR str1 = SysAllocString( L"Hello");
BSTR str2 = SysAllocString( OLESTR("Hello"));

SysFreeString(str2);
SysFreeString(str1);

Functions

BSTR SysAllocString( WideString);Takes the Wide Char string, and copies it into a newly allocated BSTR
BSTR SysAllocStringLen(?)
BSTR SysAllocStringByteLen(?)
BSTR SysReAllocString(?)
BSTR SysReAllocStringLen(?)
???? SysStringLen(?)
???? SysStringByteLen(?)
???? VectorFromBstr(?)SAFEARRAY Vector from a BSTR
???? BstrFromVector(?)BSTR to a SAFEARRAY Vector
???? VarBstrCat(?)(undocumented) Concatinates
???? VarBstrCmp(?)(undocumented)Compares 2 BSTRs and returns one of:
VTCMP_LT, VTCMP_EQ, VTCMP_GT, VTCMP_NULL
SysFreeString( BSTR);

With ATL

Try using CComBSTR

With MFC

CString( BSTR);Construct directly from a BSTR
?? BSTR CString::AllocSysString();To Read it out
?? BSTR CString::SetSysString(?);To realloc

CString str1( BSTR bstr);

Other

Microsoft Visual C++® version 5.0 and later support the _bstr_t class to provide a more sophisticated wrapper for BSTR. In addition to the functionality of CComBSTR, _bstr_t also provides comparison operators. In addition, _bstr_t avoids allocating extra BSTR objects by using reference counting—more efficient, but not as thin a wrapper as the CComBSTR.

You can also use the C++ Standard Library's wstring class, but you'll have to convert to and from BSTR. You can convert from BSTR to wstring by constructing a new wstring object using the appropriate constructor. You convert to a BSTR by getting a pointer to the string wrapped by the wstring object and calling one of the COM APIs that allocate a BSTR, such as SysAllocString.

More Info

http://whidbey.msdn.microsoft.com/library/default.asp?url=/library/en-us/dnguion/html/drgui042099.asp

Adding Methods by Hand

  1. You need write permission on the .idl .cpp .h files.
  2. Add you method to the interface, like this:
    [id(15), helpstring("method DeleteIcon")]
    HRESULT DeleteIcon( [in] long IconHandle);
  3. To the .h file add a public prototype like this:
    STDMETHOD(DeleteIcon)( long iconHandle );
  4. To the .cpp file add the method, like this:
    STDMETHODIMP ClassName::DeleteIcon( long iconHandle)
    {
    	return S_OK;
    }
    
  5. It should compile now.

Connection Point Example

I made an ActiveX control in Visual Studio .NET as follows

  1. new ATL project (ConnectionPoint6)
  2. add class 'ATL Control' with options 'composite control' 'connection points' (JbpCon6)
  3. added a button and an event handler for the button
  4. added a method to ConnectionPoint6Lib|_IJbpCon6Events, it was called OnJBP and had 1 param BSTR named string1
  5. added connection point to CJbpCon6
  6. added Fire_OnJBP to the button event handler, like this Fire_OnJBP( CComBSTR( "this is the message"));

To Test from C#

  1. new C# application
  2. goto the graphical designer view
  3. choose "Customize toolbox" from the Components context menu
  4. check the box next to JbpCon6 Class
  5. Now JbpCon6 is in the list of components, so just add it to the form
  6. on the properties window click on the lightning bolt to be able to add a handler for OnJBP (double click 'OnJBP' to add a handler)

To Test from HTML

I made the html file to test the control look like this:


// NOTE: get the CLSID from coclass JbpCon6 uuid
<!doctype html public "-//w3c//dtd xhtml 1.0 transitional//en" 
	"http://www.w3.org/tr/xhtml1/dtd/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>ATL 7.0 test page for object JbpCon6</title>
</HEAD>
<body>


<object 
	id="JbpCon6" 
	classid="CLSID:807C00B5-0A73-485F-81AC-8630EF2FCD41" 
	viewastext>
</object>

<script language="javascript" for=JbpCon6 event=OnJBP>
<!--
JbpCon6_OnClick()
//-->
</script>

<script id=clientEventHandlersJS language="javascript">
function JbpCon6_OnClick()
{
	alert("hello");
}
</script>

</body>
</HTML>

And the alert box would pop up when I hit the button (after an ActiveX warning box)


Smart Pointers


#include "{???}Tlb.h"

// Connects on its own
{
  IComThingPtr spComThing;
	spComThing.CreateInstance( CLSID_ComThing);
	spComThing->foo();
	// or
	IComThing2Ptr spComThing2( CLSID_ComThing2); // ???
	spComThing2->foo();
}
// or Connects to an old style com object
{
	IComThing *pComThing = ...;
	IComThingPtr spComThing;
	// it adds a ref when it adds it
	spComThing = pComThing;
	spComThing->foo();
	// it removes a ref when it goes out of scope
}

// Switching Interfaces 'Casting'
IGooPtr spIGoo(__uuidof(MyObject));   // create object
spIGoo->Gunc();            // call method

IFooPtr spIFoo = spIGoo;   // QI different interface, same object
spIFoo->Func1();      // call method on new interface

Link Issues

if the linker cannot find something like: IID_IRasterBandCollection
make sure StdAfx.h contains something like: #include <idl/rdotlb.h>


SAFEARRAY info

See also VARIANT and SAFEARRAY info

When creating the SAFEARRAY:


SAFEARRAYBOUND *sab = new SAFEARRAYBOUND[2];
sab[0].lLbound = 0;
sab[0].cElements = pCalcBins; // least significant index
sab[1].lLbound = 0;
sab[1].cElements = pCalcBins; // most significant index

When reading the SAFEARRAY:


for ( j = 0; j < 3; j++) {
	for ( i = 0; i < pCalcBins; i++) {
		long indices[] = { i, j};  // { least significant, most significant }
		SafeArrayGetElement( psaOutLUT, indices, &val);
	}
}
SafeArrayCreate
SafeArrayCreateVector
SafeArrayCopy
SafeArrayCreateEx
SafeArrayCreateVectorEx
SafeArrayGetDim
SafeArrayGetLBound
SafeArrayGetUBound
SafeArrayGetElemsize
SafeArrayGetElement
SafeArrayPutElement
SafeArrayLock
SafeArrayAccessData
SafeArrayPtrOfIndex
SafeArrayUnlock
SafeArrayUnaccessData
SafeArrayRedim
SafeArrayDestroy
SafeArrayAllocDescriptor
SafeArrayAllocDescriptorEx
SafeArrayAllocData
SafeArrayCopyData
SafeArrayDestroyDescriptor
SafeArrayDestroyData
(undoc) SafeArraySetIID
(undoc) SafeArrayGetIID
(undoc) SafeArrayGetRecordInfo
(undoc) SafeArraySetRecordInfo
(undoc) SafeArrayGetVartype

VARIANT, Putting a SAFEARRAY into one


VARIANT v;
VariantInit( &v);                                 // Sets the Variant to VT_EMPTY
SAFEARRAYBOUND rgb [] = {100, 0};                 // numElements, lowerBounds
v.vt = VT_ARRAY | VT_I4;                          // Array of longs
v.parray = SafeArrayCreate(VT_I4, 1, rgb);        // Type, Dimensions, BoundsInfo
long *rgelems;                                    // Pointer for long elements
SafeArrayAccessData(v.parray, (void**)&rgelems);  // Get pointer to data and lock array
for (int c = 0; c < 100; c++)
    rgelems[c] = c;
SafeArrayUnaccessData(v.parray);                  // Release the lock on the array

// The VARIANT now owns the SafeArray, and will free it when VariantClear is called

VARIANT and SAFEARRAY info

Article: http://www.whooper.co.uk/excelvariants.htm


Deleting an Object

  1. Delete the files:
      {ClassName}.cpp
      {ClassName}.h
      {ClassName}.rgs
    
  2. Delete {ClassName}'s entries from the .idl file.
  3. Delete {ClassName}'s reference in resource.h (hmm, does _APS_NEXT_SYMED_VALUE need to be considered? I am assuming not)
  4. Delete {ClassName}'s from the .rc file
  5. Use regedit to remove {ClassName}'s stuff from

Thunking

Thunking, or how to get a classes this pointer through a static callback that has no place to save it as callback data.

You basically use the fact that a function pointer is an address that is called to jump into a function.

The change it so that it jumps into some assembly that you build, as opposed to the actual function start that it expects to jmp into.

For the WINDPROC case, you swap out the HWND parameter with the 'this' pointer you want. then you execute a jmp to the static WINDPROC of the class the 'this' belongs to.

At that point you cast the HWND parameter to a 'this' of the class the static function belongs to, and then you can call a member func with it.


Unregistering a Control


regsvr32 /u ./file.dll
// or
regsvr32 /u ./file.ocx