I have to set the properties of a COM object.
The COM object has a Set function that takes a key (string) and a value (variant) as parameters. For example I can do : com_object->Set("name", "John").
I have many properties to set, but I don't know how to do it in C++. In VB.Net it would look like that:
com_object.Set "name,age", Array("M2", 1)
I would like to do the same in C++ but I don't know the syntax.
EDIT
Here is the code I have written to access properties in batch:
template < class ComObjectType >
void read(ComObjectType com_object, std::string const& fields)
{
COleSafeArray data_array_;
data_array_ = com_object->GetGet((LPCSTR)fields.c_str());
long index = 0;
VARIANT value_temp_;
_variant_t value_reader_;
data_array_.GetElement(&index, &value_temp_);
value_reader_.Attach(value_temp_);
std::string str1 = (LPCSTR)((_bstr_t) value_reader_)
++index
data_array_.GetElement(&index, &value_temp_);
value_reader_.Attach(value_temp_);
long long1 = value_reader_.lVal;
}
Then I call this function (which actually does not do much but is just there for the demonstration. The IAPIOrderObj* can be anything but it is defined in the .tlh file of the API I am being provided with, and it inherits from IDispatch interface.
MYAPI::IAPIOrderObj* my_obj;
read< IAPIOrderObj* >(my_obj, "StatusString,StatusCode");
What are you using to access the COM component MFC or ATL or only c++ library like _bsrt_t I cannot derive this information from your code snippet.
With some basic assumptions here are some points to consider.
Normally VB uses the IDispatch interface to access the COM components. From C++ there may be a native interface which may be much more easier to access than the IDispatch interface. From the com_object you can query this c++ interface.
Always use the COM strings (BSTR, SysAllocString allocated) for passing string information across.
Don't typecast from LPCSTR to _bstr_t. Both string representation has different meanings. Always use proper conversion functions to do the conversions.
Hope this helps.
Related
I am creating a DLL in C++ to be used in a Delphi 7 project, this dll will use OpenMP and will replace some methods that already exist in the project, this in order to hopefully get a speed up in the application.
One of the functions is defined like this:
function ReplaceFunction(chain:String;functionTE:TEFunction):string;
The object functionTE is declared like this,
TEFunction = class(TObject)
private
FFunctionName: string;
procedure SetFunctionName(const Value: string);
function GetFunctionNameCapital: string;
public
Handle:THandle;
Funcion:Pointer;
FileName:string;
ParamNumber:integer;
Description:string;
property FunctionNameCapital:string read GetFunctionNameCapital;
property FunctionName:string read FFunctionName write SetFunctionName;
end;
How can I pass an object of this class to the dll and use it?
How can I pass an object of this class to the dll and use it?
You cannot. Only Delphi code can consume Delphi classes. And even then, only code that uses the same instance of the runtime. Which requires runtime packages.
You'll need to expose the functionality in an interop friendly manner. Either plain C style functions, or a COM style interface are the obvious options.
And not only can you not pass that object, you also must not attempt to use Delphi native strings across an interop boundary. Again you need to use interop friendly types. For strings this includes C string (null-terminated arrays of characters) and the COM BSTR.
I want to know what is exactly CLSID data type, as it is used in C++, and I want to use it in delphi.
what is CLSID?
A CLSID is a GUID that identifies a COM object. In order to instantiate a registered COM object, you need to know its CLSID.
Typically in Delphi you would be calling CoCreateInstance. You simply call the function and pass a CLSID. The declaration of CoCreateInstance declares the class ID parameter as having type TCLSID which is a simple alias of TGUID. So pass one of those.
Here are the declarations, as lifted from the Delphi source:
type
TCLSID = TGUID;
function CoCreateInstance(const clsid: TCLSID; unkOuter: IUnknown;
dwClsContext: Longint; const iid: TIID; out pv): HResult; stdcall;
An example of a call to CoCreateInstance, taken from my code base:
const
CLSID_WICImagingFactory: TGUID = '{CACAF262-9370-4615-A13B-9F5539DA4C0A}';
if not Succeeded(CoCreateInstance(CLSID_WICImagingFactory, ...)) then
...
You will likely be creating a different interface, and so will need to substitute the appropriate CLSID for that interface.
There is one other little trick that is worth knowing about. If you pass an interface type as a parameter of type TGUID, and that interface type has a GUID, then the compiler will substitute the GUID for you. So the above code could equally well be written:
type
IWICImagingFactory = interface
// this is the GUID of the interface, the CLSID
[{ec5ec8a9-c395-4314-9c77-54d7a935ff70}]
....
end;
....
if not Succeeded(CoCreateInstance(IWICImagingFactory, ...)) then
...
What is a CLSID? A Class ID (CLSID) is a 128 bit (large) number that represents a unique id for a software application or application
component. Typically they are displayed like this
"{AE7AB96B-FF5E-4dce-801E-14DF2C4CD681}".
You can think of a CLSID as a "social security number" for a piece of
software, or a software component.
What are they used for? CLSIDs are used by Windows to identify software components without having to know their "name". They can also
be used by software applications to identify a computer, file or other
item.
Where do they come from? Microsoft provides a utility (program) called GUIDGEN.EXE that generates these numbers. They are generated by
using the current time, network adapter address (if present) and other
items in your computer so that no two numbers will ever be the same. [1]
and
COM classes are named by CLSIDs, which are UUIDs as defined by OSF/DCE
RPC [2]
Two quotes are cited
I am in a situation where I have a COM object that I need to use in come windows only C++ code. The COM object has functions that accept SAFEARRAYs as arguments to pass arrays of bytes. After looking at the SAFEARRAY API ( http://msdn.microsoft.com/en-us/library/windows/desktop/ms221145(v=vs.85).aspx ) I decided it wasn't what I wanted and that I should find an object oriented wrapper. I tried looking for open source ones and I didn't find any. I found that microsoft has created two objects that seem to encapsulate SAFEARRAY. It looks like the CComSafeArray is exactly what I need, and like the the COleSafeArray might be useful but could exist only for legacy compatibility.
Is COleSafeArray just around for historical compatibility or is there something I am missing?
When should COleSafeArray be used instead of CComSafeArray?
Are there any open source implementations that might be worth looking into?
What are the Pros and Cons of each?
The difference is obvious from class names.
MFC COleSafeArray is designed to support OLE Automation and is actually wrapper for OLE VARIANT struct (which can contain SAFEARRAY). It works generally with array elements as they are of VARIANT type so you need to select & extract appropriate type manually.
ATL CComSafeArray is designed to support SAFEARRAY for generic COM and is actually wrapper for SAFEARRAY struct. It's template class parametrized with array elements type.
In general you shall use CComSafeArray, it's easier and simplier, accessing elements almost same way as for regular arrays/vectors.
COleSafeArray may be preferable sometimes if you work with OLE Automation interfaces that intensively use VARIANT parameters, e.g. automating MS Office, using Visual Basic components etc. For comparision in this case with CComSafeArray you will need to wrap/unwrap it manually to/from VARIANT object.
I've been writing a DLL in C++, now I must call this DLL from a VB6 application.
Here's a code sample from this DLL :
#include <vector>
#include <string>
using namespace std;
void __stdcall DLLFunction (vector<Object>*)
{
// performs a few operations on the Objects contained in the vector.
}
struct Object
{
long CoordX;
long CoordY;
long Width;
long Height;
LPSTR Id;
};
I also defined the "Object struct" in VB6
Private Type Object
CoordX As Integer
CoordY As Integer
Width As Integer
Height As Integer
Id As String
End Type
The issue is I don't know what vb6 type could stand for std::vector in order to call the DLL's function.
Notes :
- I use a vector for the DLL to be able to add objects.
- I use a pointer in order to use as less memory as possible.
- Sorry for my english, it ain't my home language at all.
- Thank you for reading and trying to help me.
Edit :
- I fixed the typing issues (Ids are definitely ended by NullChar, so LPSTR should do the trick).
- I read your answers, and I'd like to thank both of you, your answers are close to one another and a major issue remains. My DLL definitely needs to add elements to the container. Thus, I'm wondering how I could do the trick. Maybe I could add a return type to my function and then make that the function is able to return the items it created (instead of putting it directly into the container) so that the vb6 application gets these items and is able to process them, but I can't figure out how to do this
Edit bis :
#Rook : I feel like I could achieve this by using a new struct.
struct ObjectArrayPointer
{
Object* Pointer;
size_t Counter;
}
And then call my function this way :
void __stdcall DLLFunction (ObjectArrayPointer*);
I would then be able to add objects and edit the size parameter for my VB6 application to find these new objects. Was that what you meant?
You should not be trying to export template containers from a DLL anyway. They're likely to break when faced with newer compilers and libraries (eg. a library built under C++03 will not play well with code built using C++11).
The least painful thing to do is to accept a pointer to a buffer and a length parameter,
void __stdcall DLLFunction (Object* buffer, size_t nObjects);
if the size of the container will not change during execution. This interface is about as simple as it gets, and is easily accessible by any language that understand C calling conventions (eg. almost every single one.)
You've already thrown away most of the use of a std::vector because you've already specialised it to Object; you could consider going all the way and creating your own ObjectCollection class which uses a std::vector internally but presents a non-templated interface. Here's a simple example :
// In your public API header file:
typedef struct object_collection_t *object_collection;
object_collection CreateObjectCollection();
void DestroyObjectCollect(object_collection collection);
void AddObjectToCollection(object_collection collection, Object* object);
// etc
No template types are exposed in any form in the header. This is good.
// And the corresponding code file:
struct object_collection_t
{
std::vector<Object*> objects;
};
object_collection CreateObjectCollection() { return new object_collection_t; }
void DestroyObjectCollect(object_collection collection) { delete collection; }
void AddObjectToCollection(object_collection collection, Object* object)
{
collection->objects.push_back(object);
}
// etc
All of templating code is hidden away, leaving you with a fairly clean and simple interface which present an opaque pointer type that can be passed around by external code but only queried and modified by your own, etc.
EDIT: Incidentally, I've used Object* throughout the above code. It may well be safer and impler to use just plain old Object and avoid all of the issues associated with memory management and pointer manipulation by client code. If Object is sufficiently small and simple, passing by value may be a better approach.
(NB: not checked for compilability or functionality. E&OE. Caveat Implementor!)
You can't do that as it's a C++ class/template. Internally, it's an array but not in a way that can be created from VB6.
Your best bet is to change the function to accept a pointer to an array with a count parameter.
You'll also need to be very careful as to how the type is structured.
C++ ints are Longs in VB6.
Also, the Id string won't be compatible. VB6 will have a pointer to a unicode BString (unless you make it fixed length) where as a C++ will have std::string which is an array of ANSI chars. VB6 MAY marshal this if you pass an array of the objects (rather than a pointer)
The VB6 ABI is the COM Automation ABI.
Therefore, if you need an arry which is VB6 ABI compatible, you should probably use SAFEARRAY. I suggest you should also be using the Compiler COM Support classes:
http://msdn.microsoft.com/en-US/library/5yb2sfxk(v=vs.80).aspx
This question appears to do exactly what you want, using ATL's CComSafeArray class:
conversion between std::vector and _variant_t
You may also want to look at these:
https://stackoverflow.com/search?q=safearray+_variant_t
Alternatives to SAFEARRAY
The alternative to SAFEARRAY is to supply a COM Collection object. This is simply a COM object with a Dispinterface or Dual interface with the methods Count and Item. Item should have dispid=0 to be the default method. You may also want to supply _NewEnum with DISPID_NEWENUM to support the For Each syntax.
or a VB6 - compatible - collection object.
We provide hooks into our .net products through a set of API's.
We need to continue to support customers that call our API's from VB6, so we need to continue supporting VB6 collection objects (simple with VBA.Collection in .net).
The problem is supporting some sites that use VBScript to call our API's. VBScript has no concept of a collection object, so to create a collection object to pass to our API we built a VB6 ActiveX DLL that provides a "CreateCollection" method. This method simply creates and passes back a new collection object. Problem solved.
After many years of pruning, porting and re-building, this DLL is the only VB6 code we have. Because of it we still need to install Visual Studio 6 on our Dev & build Machines.
I'm not happy with our reliance on this DLL for several reasons (my personal dislike of VB6 is not one of them). Top of the list is that Microsoft no longer support Visual Studio 6.
My question is, how do I get ATL to create a collection object that implements the same interface as the VB6 collection object.
I've a good handle on C++, but only a loose grasp of ATL - I can create simple objects and implement simple methods, but this is beyond me.
Collections are more or less based on convention. They implement IDispatch and expose some standard methods and properties:
Add() - optional
Remove() - optional
Item()
Count - read-only
_NewEnum - hidden, read-only, returns pointer to enumerator object that implements IEnumVariant
The _NewEnum property is what allows Visual Basic For Each.
In the IDL you use a dual interface and:
DISPID_VALUE for Item()
[propget, id(DISPID_NEWENUM), restricted] HRESULT _NewEnum([out, retval] IUnknown** pVal)
Here are some MSDN entries: Design Considerations for ActiveX Objects
And here is some ATL specific convenience: ATL Collections and Enumerators
Lets target this VBScript snippet
Dim vElem
For Each vElem In MyObject
...
Next
particularly the implementation of MyObject. As a minimum you have to implement a method/propget with DISPID_NEWENUM on the default dispinterface (its dual/dispinterface to talk about DISPIDs). You can name it whatever you want, it doesn't matter. Most collections use NewEnum, and flag it in IDL as hidden. VB6 uses underscore prefix to mark hidden methods so you might see _NewEnum as recommendation but it's kind of a cargo cult ATL does.
You don't need any Count, Item, Add, Remove, Clear or any other method at all (on the default interface). You can supply these as a convenience (particulatly Item accessor and probably Count) but you don't have to, to make the sample code above work.
Next, the retval has to be a separate object (so called enumerator) which implements IEnumVARIANT interface by using a (private) pointer to MyObject. In IDL you can declare retval as IUnknown nothing wrong here. What is most interesting is that you have to implement only the Next method on IEnumVARIANT, you can return E_NOTIMPLEMENTED on the rest if you like or optionally implement them though these are never called by For Each. What makes the implementation even easier is that celt parameter of Next (the number of items requested) is always 1, so For Each requests items always one by one.
What you can use in ATL is CComEnumOnSTL and the like to create a "proxy" enumerator on an STL container, or the array based enumerator ATL provides (and exclude STL).
For a good example of how to implement COM collections that would be used naturally in script programming languages, check out my website
It offers a comprehensive example of how to do that...