[QA] ActiveX/COM
来源:互联网 发布:网络语言不规范原因 编辑:程序博客网 时间:2024/05/21 09:32
From: http://www.microsoft.com/msj/0696/activex0696.aspx
A Your problem stems from the fact that dual interfaces suffer from the same limitation as normal dispatch interfaces: all parameters must be VARIANT-compatible. Both dispatch and dual interfaces (which ultimately are just an extension to dispatch interfaces) are meant to expose functionality to clients written in scripting languages, many of which are interpreted. To allow scripting clients to pass method parameters whose type may not be known until run time, you must use some alternative mechanism for passing parameters. This is the purpose of the VARIANT from IDispatch.
IDispatch uses the VARIANT data structure to pass generic, self-describing parameters between the caller and the object. As shown in Figure 1, the VARIANT is a discriminated union that contains a union of all supported data types accompanied by a type tag that describes which union member is actually valid. It is the responsibility of the caller to initialize the union member and set the type tag appropriately. The following code initializes an array of VARIANTs to contain two doubles and a short:
VARIANT args[3]; ZeroMemory(args, sizeof(args)); args[2].vt = VT_R8; args[2].dblVal = 2.0; args[1].vt = VT_R8; args[1].dblVal = 4.5; args[0].vt = VT_I2; args[0].iVal = 9;
The type tag's value must be chosen from a limited set of values (see Figure 2). This list is fixed and is not user-extensible. If your data type is not found in the list, then you must somehow coerce your parameter into a data type supported by the VARIANT. While the VARIANT does contain a union member of type void * (selected by specifying VT_PTR), this member cannot be used when the object is located on a different thread or in a different process; the standard marshaler for IDispatch has no idea how to marshal the data that is referred to by the pointer.
[ uuid(00110022-0000-1111-2222-BABAABABBAAB), odl, dual ] interface IPoint : IDispatch { [ id(10) ] HRESULT SetCoords([in] long x, [in] long y); }
VARIANT args[2]; ZeroMemory(args, sizeof(args)); args[0].vt = VT_I4; args[0].lVal = 200; args[1].vt = VT_I4; args[1].lVal = 100; DISPPARAMS params = { args, 0, 2, 0 }; pDispatch->Invoke(10, IID_NULL, 0, DISPATCH_METHOD, ¶ms, 0, 0, 0);
pPoint->SetCoords(100, 200);
The limitation imposed by the VARIANT is obvious for dispatch interfaces, as all logical parameters must be passed as an array of variants. If there isn't an appropriate union member to hold your parameter, there is probably no way to pass your parameter without some sort of manual type coercion. It is less obvious why this limitation applies to dual interfaces, as clients can access the method implementations directly. They can bypass the IDispatch::Invoke mechanism and pass the parameters directly on the stack instead of in a VARIANT array. Given the fact that dual interface clients don't use VARIANTs, it seems counterintuitive that the methods of a dual are also limited to VARIANT-compatible types. This limitation exists for two reasons. First, it is assumed that all methods of the dual interface are accessible from Invoke. Since Invoke can only receive parameters that are packed in a VARIANT array, this immediately presents a problem for VARIANT-incompatible types.
The second and most important limitation stems from the universal marshaler that is used to remote the methods of the dual interface. The universal marshaler uses the type information for the interface to dynamically translate between the call stack and the marshaling packet for use in standard marshaling. The advantage of this approach is that no user-supplied proxy/stub pair is needed to remote the interface. The limitation of this approach is that the universal marshaler is hard-wired to support VARIANT-compatible types only. The type library compiler is aware of this limitation, and will not allow you to create a dual interface that accepts VARIANT-incompatible types.
In spite of the previous explanation, there must be some technique that allows user-defined structures to be passed as method parameters. In fact, there are several. If you must remain in the world of dispatch and dual interfaces to maintain scripting compatibility, there are four viable options for passing structures as parameters:
- Pass each structure member as an individual method parameter.
- Serialize the structure to an opaque array of bytes.
- Serialize the structure to an array of VARIANTs, one array element per structure member.
- Implement a dispatch/dual wrapper object that contains the structure and pass an interface to the wrapper object as a parameter.
Before critiquing each approach, let's see an example of each applied to a simple structure—passing a RECT structure from a client to an object as a method parameter. In case you don't have a copy of Petzold handy, here's the definition of RECT from WINUSER.H:
typedef struct tagRECT { int left; int top; int right; int bottom; } RECT;
Private Type RECT left as Long top as Long right as Long bottom as Long End Type
The first technique requires the least explanation. To allow callers to pass a rectangle, you can simply break apart the structure into individual parameters, as is demonstrated by this interface:
[ uuid(00110023-0000-1111-2222-BABAABABBAAB), odl, dual ] interface INeedARect : IDispatch { HRESULT GiveMeARect([in] long left, [in] long top, [in] long right, [in] long bottom); }
Sub PassARect(nar as INeedARect, r as RECT) nar.GiveMeARect r.left, r.top, r.right, r.bottom End Sub
void PassARect(INeedARect *pnar, const RECT& r) { pnar->GiveMeARect(r.left, r.top, r.right, r.bottom); }
HRESULT CoNeedARect::GiveMeARect(long left, long top, long right, long bottom) { RECT r = { left, top, right, bottom }; m_rect = r; return S_OK; }
The second technique takes advantage of the fact that VARIANTs can be used to pass primitive types, pointers to single instances of primitive types, and self-describing arrays of primitive types. One of the supported primitive types is unsigned char, which for our purposes translates to byte. Instead of breaking apart the struct as in the previous example, this technique simply copies the structure into an array of bytes and assumes that the method implementation treats the byte array appropriately. Unlike vanilla C and C++, when dual and dispatch interfaces use arrays, they must be accompanied by a description of the array's dimensions. This array descriptor is called a SAFEARRAY and describes the bounds for each array dimension as well as the individual element size.
typedef struct tagSAFEARRAYBOUND { ULONG cElements; // # of elems in dimension LONG lLbound; // lower bound of dimension } SAFEARRAYBOUND; typedef struct tagSAFEARRAY { unsigned short cDims; // # of dimensions unsigned short fFeatures; // misc. flags unsigned long cbElements; // element size unsigned long cLocks; // lock count void * pvData; // ptr to array SAFEARRAYBOUND rgsabound[cDims]; // dimensions } SAFEARRAY;
SAFEARRAYs can be created and manipulated using API functions or through direct manipulation. The SafeArrayCreate API function creates a new array descriptor and allocates the space for the array contents. To access the array data, call the SafeArrayAccessData function, which returns a pointer to the base of the array after incrementing the lock count. Release the lock by calling SafeArrayUnaccessData.
SAFEARRAY *CreateArray() { // one dimensional array with 100 elements and // zero-based indexing SAFEARRAYBOUND rgb [] = { 100, 0 }; // allocate the memory for the descriptor // and the array data SAFEARRAY *psa = SafeArrayCreate(VT_I4, 1, rgb); if (psa) { // get ptr to array base long *rgelems; SafeArrayAccessData(psa, (void**)&rgelems); // initialize each element for (int c = 0; c < 100; c++) rgelems[c] = c; // release lock on array state SafeArrayUnaccessData(psa); } return psa; }
#define FADF_AUTO ( 0x1 ) #define FADF_STATIC ( 0x2 ) #define FADF_EMBEDDED ( 0x4 ) #define FADF_FIXEDSIZE ( 0x10 )
Armed with the knowledge of SAFEARRAYs, we can now get back to the problem of passing structs through a dual interface. The type library compiler allows you to specify parameter types that are arrays by using the SAFEARRAY keyword.
[ uuid(00110024-0000-1111-2222-BABAABABBAAB), odl, dual ] interface INeedARectToo : IDispatch { HRESULT GiveMeARect( [in] SAFEARRAY (unsigned char) parray); }
[ uuid(00110024-0000-1111-2222-BABAABABBAAB), odl, dual ] interface INeedARectAlso : IDispatch { HRESULT GiveMeARect([in] VARIANT var); }
Rem VB can't convert from RECT to byte array, Rem so this intermediate type is needed to Rem use LSet (the typecast operator of VB) Type RectByteArray ba(16) as Byte End Type Sub PassARect(nar as INeedARectAlso, r as RECT) Dim rba as RectByteArray LSet rba = r nar.GiveMeARect rba.ba End Sub
HRESULT CoNeedARectAlso::GiveMeARect (VARIANT var) { if (var.vt != (VT_UI1 | VT_ARRAY)) return E_INVALIDARG; const RECT *pr; SafeArrayAccessData(var.parray, (void**)&pr); m_rect = *pr; SafeArrayUnaccessData(var.parray); return S_OK; }
The third technique for passing structures is to serialize each struct member into an array of VARIANTs. This technique is similar to the previous approach, but it requires the client and object to individually pack each structure member into a variant, which allows the use of somewhat richer member types (interface pointers are legal). To use this technique, the interface description is the same as in the previous example.
[ uuid(00110025-0000-1111-2222-BABAABABBAAB), odl, dual ] interface INeedARectAsWell : IDispatch { HRESULT GiveMeARect([in] VARIANT var); }
Sub PassARect(nar as INeedARectAsWell, r as RECT) Dim va(4) as Variant va(0) = r.left va(1) = r.top va(2) = r.right va(3) = r.bottom nar.GiveMeARect va End Sub
HRESULT CoNeedARectAsWell::GiveMeARect(VARIANT var) { if (var.vt != (VT_VARIANT | VT_ARRAY)) return E_INVALIDARG; VARIANT *rgv; SafeArrayAccessData(var.parray, (void**)&rgv); RECT r = { rgv[0].lVal, rgv[1].lVal, rgv[2].lVal, rgv[3].lVal }; SafeArrayUnaccessData(var.parray); m_rect = r; return S_OK; }
Figure 8 is an example mapping of the RECT struct mapped onto a dual interface. Figure 9 shows CoRect, an implementation of this interface that simply contains a RECT and implements the accessor and mutator functions as one would expect. Given the implementation of this RECT wrapper, our RECT-hungry interface would now look like this:
[ uuid(00110026-0000-1111-2222-BABAABABBAAB), odl, dual ] interface INeedARectSoBadItHurts : IDispatch { HRESULT GiveMeARect([in] IRect *pRect); }
Sub PassARect(nar as INeedARectSoBadItHurts, r as RECT) Dim rct as new CoRect rct.Left = r.left rct.Top = r.top rct.Right = r.right rct.Bottom = r.bottom nar.GiveMeARect rct End Sub
On the implementation side, you must reconstruct the RECT by extracting each member from the wrapper using the propget functions.
HRESULT CoNeedARectSoBadItHurts::GiveMeARect(IRect *pRect) { RECT r; pRect->get_Left(&r.left); pRect->get_Top(&r.top); pRect->get_Right(&r.right); pRect->get_Bottom(&r.bottom); m_rect = r; return S_OK; }
I intentionally skipped two other alternative techniques. One of these techniques is a variation on the struct-wrapper approach, but instead of exposing the embedded struct via a dispatch/dual interface, you would expose it via an IDataObject interface. This approach suffers all of the limitations of the byte-array technique (it is just passing raw bytes around via GetData) but does not offer the same performance benefits (the GetData method requires an additional round-trip, which severely impacts performance in the out-of-process case). It also cannot be used from Visual Basic, which limits its usefulness. Given that the byte-array technique is actually easier to implement and is available from Visual Basic, there is no reason to favor the IDataObject approach.
Another technique that was popular in the early days of IDispatch was to treat a BSTR as an opaque array of bytes. This approach ceases to work now that BSTRs are Unicode. The marshaler will perform byte-swapping when communicating with some remote hosts, and performs Unicode-to-ASCII conversion when communicating with 16-bit processes.
As you might have concluded by now, structs and IDispatch/dual interfaces are not a good match. All of the techniques described above are workable, but less than ideal. For small structures, breaking out the structure members as individual parameters is definitely the way to go. For large structures, the choice is not so obvious. If Visual Basic compatibility is important, then the struct-wrapper technique is elegant but inefficient. However, it is arguably less work to simply break out 20 structure members as parameters than to perform 20 PROPERTYPUT operations. Unless programmers will use your struct-wrappers as native data types in their Visual Basic-based applications, be prepared to see the performance of your library slow down considerably.
If optimal performance and elegance from C++ is important, perhaps the best approach is to leverage the key feature of COM, QueryInterface. Instead of forcing C++ clients to call through the dual interface, you could add support for a second custom interface that is not subject to the VARIANT-compatible restriction. By supporting both a dual and a custom interface from the same object, Visual Basic clients could still QueryInterface for the more restricted dual interface, while C++ clients should use the more flexible struct-friendly interface. This technique requires more work by the object implementor, but yields the best overall performance and is considerably more convenient for the object's client.
Have a question about programming with ActiveX or COM? Send your questions via email to Don Box atdbox@develop.com orhttp://www.develop.com/dbox/default.asp
- [QA] ActiveX/COM
- COM ActiveX C++ Builder
- ActiveX and Com
- ActiveX and Com
- MFC -> COM/ActiveX/more...
- ActiveX and Com
- ActiveX and Com
- ActiveX and Com(转)
- com与activex
- ActiveX、OLE和COM
- ActiveX、OLE和COM
- 理一理COM、OLE、ActiveX~~
- ActiveX、OLE和COM
- COM、OLE、ActiveX
- ActiveX COM ATL OLE
- COM,OLE,ActiveX
- ActiveX,NPAPI,COM总结
- QA
- 【GTK】GTK之任意拖动窗口中的按钮
- Linux 驱动程序实验
- Java:集合类性能分析
- Rails宝典之第十一式: 重构用户名-p2
- android控件27 SQLite
- [QA] ActiveX/COM
- django 处理静态页面
- PL/SQL developer远程连接oracle服务器
- mysqldump备份还原和mysqldump导入导出
- mysql python 汉字乱码的解决方案
- Oracle 10gR2 32bit on SuSE9安装技术文档(原版英文)
- 指令集学习
- Rails宝典之第十二式: 重构用户名-p3
- 無題