ATL/COM
http://www.exogenesis.org/notes/atlcom.html#registering
Local
- Adding Methods by Hand
- BSTR
- Categories
- CLSID's (Class IDs)
- Connection Points
- Creatablity, making an Interface not creatable from outside
- Debugging
- Deleting an Object
- Gotchas
- HTML's OBJECT PARA support for getting properties from
- #import
- Initialization (for class data, and for instance data)
- Licensing, adding support for
- Link Issues
- Radio Button
- Registering a COM object
- Registry Manipulation
- Return Values
- SAFEARRAY info
- Singleton, Making an Interface one
- Smart Pointers
- STDMETHOD and STDMETHODIMPL
- STL issue
- String Conversion
- Text Field
- Thunking, or how to get a classes
this
pointer through a static callback that has no place to save it as callback data. - Types allowed for Properties
- Unregistering a Control
- VARIANT and SAFEARRAY info
External
- New in ATL7 http://www.codeproject.com/atl/newinatl7.asp
- Tracing memory leaks with ATL http://www.codeguru.com/atl/AtlDebugHeap.shtml
- ATL Tutorial at MSDN
- ATL Under the Hood - Part 1 vtables, how they are stored for single and multiple inheritance.
- ATL Under the Hood - Part 2 virtual pointers, through a chain of constructors, pure virtual, and
ATL_NO_VTABLE
. - ATL Under the Hood - Part 3 template stuff, how
CComObject
can have differing base classes, and how smart pointers forCComPtr
andCComQIPtr
are made. - ATL Under the Hood - Part 4 _declspec(naked), used for
_QIThunk
used for debug reference counting when_ATL_DEBUG_INTERFACES
is defined. - ATL Under the Hood - Part 5 thunking , used to make WNDPROC callbacks contain a 'this' pointer instead of an HWND as the HWND parameter
- Connection Point tutorial http://www.codeproject.com/com/connection.asp
- Classic COM to .NET Interoperability http://my.execpc.com/~gopalan/dotnet/classic_com/com.net_interop.html
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
- add this to the base classes
public IPersistStreamInitImpl<scenegraphxcontrol>,
- In the section starting with
BEGIN_COM_MAP(SceneGraphXControl)
addCOM_INTERFACE_ENTRY(IPersistPropertyBag)
String Conversion
- Where to find MSDN info
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclib/html/_atl_String_Conversion_Macros.asp
.NET Development | Visual Studio .NET | Product Documentation | Visual C++ | Reference | Visual C++ Libraries | ATL Reference | ATL Macros | ATL Macros Alphabetical Reference | ATL and MFC String Conversion Macros
Macro | |
---|---|
CW2A asciiStr( wideStr) |
Text Field
- Set the Text in a text field
SetDlgItemText( IDC_EDIT_ServerURL, CComBSTR_URL.m_str);
Radio Button
- This checks a radio button, but causes a click event:
SendDlgItemMessage( IDC_RADIO_ServerNeededNo, BM_CLICK);
- This checks a radio button, and shouldn't cause an event, but wouldn't work for me:
SendDlgItemMessage( IDC_RADIO_ServerNeededYes, (WPARAM)BST_CHECKED, TRUE);
Connection Points
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
- Use the macro
OBJECT_ENTRY_NON_CREATEABLE_EX_AUTO( clsid, class)
- Delete the base class
CComCoClass<...>
from the class - Remove the classes
.rgs
file - Add
noncreatable
to the [ ] section just abovecoclass
in the.idl
file - Add a forward declaration of your interface in the
.idl
file, right after the imports. Make it look like this:interface INonCreatable;
- Since no
CComCoClass
, define a Category Map:BEGIN_CATEGORY_MAP(classname) END_CATEGORY_MAP()
- Add a Method like this from the place you want the interface to be created at:
STDMETHODIMP CCreatorObject::CreateIt( INonCreatable **ppNonCreatable) { *ppNonCreatable = NULL; return CoComCreator< CComObject< CNonCreatable>>:: CreateInstance( NULL, IID_INonCreatable, reinterpret_cast<void**>(ppNonCreatable)); }
- Note: It is recomended that
CComObject<CNonCreatable>::CreateInstance(...)
be used instead ofCoComCreator< CComObject< CNonCreatable>>::CreateInstance(...)
if the newly created class needs special initialization before it is returned. (?something about this version of CreateInstance not incrementing the Reference count.
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)
- Puts everything in a
namespace
named after the typelibrary name. - Wrapper to return [retval] directly and throws an exception if the HRESULT is not SUCEEDED
- Smart Pointers to interfaces
- Wraps VARIANT to _variant_t and BSTR to _bstr_t
- Allow [propget] and [propput] methods to be accessed like data members (as well as functions)
- Doesn't define things like
DVOCLASSESLib::CLSID_Quad
by default you need to specify the #import attributenamed_guids
Example:#import "DvoClasses.tlb" named_guids
It is preferable to use:__uuidof( DVOCLASSESLib::Quad)
// 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
- Open the
.idl
file and copy thecoclass
guid into the clipboard - Run regedit
- Right click on HK_CLASSES_ROOT/CLSID and choose New | Key
- Paste the
coclass
guid (Make sure the value is surrounded by braces { } (just like the other entries) - Select the newly created key, and double click on
Default
to edit it - Set the
Value
to the name of coclass - Add a new key under the just created key, and name it
InprocServer32
- Set the
Default
value to the fully specified path of the COM.dll
- Right click in the right hand pane to add a new
String
value toInprocServer32
- Name the String
ThreadingModel
and set its value toApartment
- You should be able to see the COM object in the
OLE/COM Object Viewer
it isOleView.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
Macro | Purpose |
---|---|
_ATL_DEBUG_INTERFACES | Shows Interface Leaks when _Module.Term is called. |
_ATL_DEBUG_QI | Shows when QueryInterface is called. |
ATLTRACE2 | See Micosofts docs, controls lots of settings. |
Debugging Reference Counts
- Make sure
_ATL_DEBUG_INTERFACES
is defined before the atl headers are included (best to just put it instdafx.h
) - 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
- Find the QIThunk # for the Object you want to look at references counts for (it is 10 in the example line above)
- Modify your code, so that somewhere before that object is first created you do this:
_AtlDebugInterfacesModule.m_nIndexBreakAt = {QIThinkNumberGoesHere};
- Now
DebugBreak();
will be called when there is Reference count activity on the specified Object Class. - So just run again in the debugger, and watch the debugging happen.
- NOTE: it is good to put a breakpoint in
atlbase.h
inbool CAtlDebugInterfacesModule::DumpLeakedThunks()
at the linem_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.aspAdding Methods by Hand
- You need write permission on the .idl .cpp .h files.
- Add you method to the interface, like this:
[id(15), helpstring("method DeleteIcon")] HRESULT DeleteIcon( [in] long IconHandle);
- To the .h file add a public prototype like this:
STDMETHOD(DeleteIcon)( long iconHandle );
- To the .cpp file add the method, like this:
STDMETHODIMP ClassName::DeleteIcon( long iconHandle) { return S_OK; }
- It should compile now.
Connection Point Example
I made an ActiveX control in Visual Studio .NET as follows
- new ATL project (ConnectionPoint6)
- add class 'ATL Control' with options 'composite control' 'connection points' (JbpCon6)
- added a button and an event handler for the button
- added a method to ConnectionPoint6Lib|_IJbpCon6Events, it was called OnJBP and had 1 param BSTR named string1
- added connection point to CJbpCon6
- added Fire_OnJBP to the button event handler, like this Fire_OnJBP( CComBSTR( "this is the message"));
To Test from C#
- new C# application
- goto the graphical designer view
- choose "Customize toolbox" from the Components context menu
- check the box next to JbpCon6 Class
- Now JbpCon6 is in the list of components, so just add it to the form
- 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
- A smart pointer for
IComThing
isIComThingPtr
and is found in the file:{somename}Tlb.h
- it is instanced like this
pLutFactory.CreateInstance( CLSID_DvoLutFactory);
- In ATL smart pointers are Templates wrapping around
_com_ptr_t
- Use
operator bool() const throw()
to tell if a smart pointer is pointing to NULL - See #import for info on getting CLSID_NAME values, it is preferable to use
__uuidof( DVOCLASSESLib::Quad)
#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
- VARIANT, Putting a SAFEARRAY into one
- Use
CComVariant
for VARIANTS, since it callsVariantInit
on creation,VariantClear
on destruction, and has many overloaded '=' operators
Article: http://www.whooper.co.uk/excelvariants.htm
Deleting an Object
- Delete the files:
{ClassName}.cpp {ClassName}.h {ClassName}.rgs
- Delete {ClassName}'s entries from the
.idl
file. - Delete {ClassName}'s reference in
resource.h
(hmm, does _APS_NEXT_SYMED_VALUE need to be considered? I am assuming not) - Delete {ClassName}'s from the
.rc
file - 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