Component Object Model (COM) marshaling involves two primary concepts; the passing of data from one process or thread to another, and the synchronization of threads accessing a COM object. While there are many books that detail the concepts of COM marshaling, this article details more specifically how ADO handles COM marshaling. This is important specifically when passing recordsets across process boundaries or sharing ADO objects among threads. Understanding ADO marshaling can help you to understand your options and limitations when using ADO in the middle tier.
ADO General Marshaling
By default, ADO provides standard COM marshaling. This means that ADO uses standard OLE automation types for its arguments and interface pointers can be passed across processes without any special custom marshaling code. A COM server for example can return a connection or recordset object to a client and the ADO object's interface pointer is passed across the process (in other words, passing a reference to the object). Any calls to methods of the interface cause a call from the client back to the ADO object that was created and exists on the server.One key pitfall is that ADO does not allow you to pass a connection pointer across process boundaries and then attach that to an ADO recordset object that was created in a different process space. Recordsets and their associated connections must be created in the same process space. For example, suppose you have the following Visual Basic code:
Dim rs As ADODB.RecordsetDim conn As New ADODB.ConnectionDim server As New Server.Class1conn.Open "Provider=SQLOLEDB;Data Source=myserver;initial catalog=pubs", "sa"Set rs = server.GetRecordset(conn)
Public Function GetRecordset(conn As ADODB.Connection) As ADODB.Recordset Set GetRecordset = conn.Execute("Select * from authors") End Function
This code sample works. The connection's interface is marshaled from process #1 to process #2 using standard marshaling. When the Execute call is made, process #2 calls across its process boundary to process #1 where the ADO connection constructs a recordset object and returns it back to process #2.
However, if you have the following code in process #2:
Public Function GetRecordset(conn As ADODB.Connection) As ADODB.Recordset Dim rs As New Recordset Set rs.ActiveConnection = conn
The function fails on the assignment of the ADO connection object to the recordset object. ADO prevents the assignment of connection objects in one process from being assigned to recordset objects created in a different process.For the in-process case, ADO specializes the marshaling of its objects by using the COM free-threaded marshaler (using CoCreateFreeThreadedMarshaler). This means that while ADO is marked for apartment-model threading by default, ADO skirts the apartment rules with the free-threaded marshaler and has direct pass-through of any ADO object interfaces from one thread to another. No COM synchronization takes place as ADO is thread-safe and does all of the necessary synchronization. Overall, this means that ADO does no COM marshaling for the in-process scenarios.
Beyond Standard Marshaling
ADO's standard marshaling is sufficient for many scenarios but not all. For example, standard marshaling across processes can hurt performance because for each call to the ADO object, an application is making a call across process. It would be better for performance if all of the data of a recordset was returned to the client and then any subsequent calls such as fetching were made locally in the client rather than back to the COM server where the recordset was created.
ADO provides such a mechanism for the recordset object. To return a recordset object from a server and have the data of the recordset marshaled to the client, you specify adUseClient for the recordset's CursorLocation property. The adUseClient value tells the recordset object to use the Client Cursor Engine which caches all of the records of the recordset. Whenever a recordset is sent across process boundaries with adUseClient set, a recordset object (which also uses the client cursor engine) is constructed in the receiving process and it is populated with the sent records. At this point, the recordset is "disconnected" or is "disassociated" from a database connection. The database connection is not carried along with the recordset data when adUseClient is specified.
With adUseClient set, you always pass the data of the recordset when sending the recordset across process. This is typically called "passing by value". Without adUseClient, you always get standard marshaling and only a reference to the ADO Recordset object is passed across process boundaries and is typically called "passing by reference".
Limitations of Marshaling Recordsets "By Value"
When ADO recordsets are passed by-value, it is the client cursor engine that is really marshaling the recordset data across the process boundaries. The newly created ADO recordset object in the receiving process simply attaches to the recordset stored in the client cursor engine cache. There are some important ramifications from this. Because the ADO recordset is not being marshaled but rather than underlying the OLE DB rowset, the properties of the recordset object do not get marshaled. This means that properties such as Filter, Sort, and ActiveConnection are not carried from one process to the next. Thus, for example, a common pitfall is to set the Filter property of a recordset and then expect only the filtered records to be passed across the process boundaries. Instead, all of the original records of the query are passed. Furthermore, the cursor location is set to the beginning of the recordset as the cursor position is not preserved because a new ADO recordset is created in the receiving process and then it is attached to the marshaled rowset causing the cursor to be positioned at the first record. So as a general rule, do not expect recordset properties to be marshaled across the process. The advantage to this is that ADO recordset marshaling has very little overhead; only the metadata (table name, column name and data types, and some flags) and record data.Note
Marshaling ADO Field Objects is not supported.
Using the COM Global Interface Table with ADO Recordsets
COM provides a component called Global Interface Table (GIT). The GIT allows an application to store a reference to an object's interface in the table so that the interface pointer can be retrieved at any time. When storing the interface pointer into the GIT, the object is queried for IMarshal and if IMarshal is exposed by the object, then the marshaling data of the object is placed into a stream where it can be retrieved at some later time when the interface pointer is retrieved. IMarshal is exposed by the client cursor that actually does the passing of the recordset data. There is a problem if an open ADO recordset object which uses adUseClient (for example, disconnected recordset) is placed into the GIT and then is later revoked from the table. An access violation occurs. To avoid the problem, place the recordset's interface pointer into the GIT before calling Open on the recordset. This places the interface pointer into the GIT before the client cursor engine is invoked which essentially causes standard marshaling to occur rather than record data being streamed from the cursor engine through IMarshal. Only a pointer to the ADO recordset's interface is stored in this case and that is the real intent of the programmer.