Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to disconnect an ADO Recordset in XE6?

I am trying to use a disconnected ADO Recordset in XE6. The idea is that you open the recordset normally, then you set the recordset's ActiveConnection to your language's equivalent of null/Nothing/nil:

rs.Set_ActiveConnection(null);

The following example from Delphi 5 works fine:

var rs: _Recordset;

rs := CoRecordset.Create;
rs.CursorLocation := adUseClient; //the default for a Recordset is adUseServer (Connection.Execute's default is adUseClient)
rs.CursorType := adOpenForwardOnly; //the default
rs.Open(CommandText, Conn,
      adOpenForwardOnly, //CursorType
      adLockReadOnly, //LockType
      adCmdText);

//Disconnect the recordset by setting the .ActiveConnection to null
rs.Set_ActiveConnection(nil);

It works in Delphi 5

The issue is that I cannot make it work in Delphi XE6. In Delphi 5 i would successfully call:

rs.Set_ActiveConnection(nil);

and everything worked splendidly. It worked because _Recordset interface was declared as:

procedure Set_ActiveConnection(const pvar: IDispatch); safecall;

So it was valid to pass nil; and it worked.

In XE6 the delcaration changed to:

procedure Set_ActiveConnection(pvar: OleVariant); safecall;

To which you cannot pass nil. The question then becomes, what is the OleVariant equivalent of nil?

Try #1

//Disconnect the recordset by setting the .ActiveConnection to null
rs.Set_ActiveConnection(nil); //E2010 Incompatible types: 'OleVariant' and 'Pointer'

Try #2

//Disconnect the recordset by setting the .ActiveConnection to null
rs.Set_ActiveConnection(Null);

causes exception:

Arguments are of the wrong type, are out of acceptable range, or are in conflict with one another

Try #3

//Disconnect the recordset by setting the .ActiveConnection to null
rs.Set_ActiveConnection(EmptyParam);

causes exception:

Arguments are of the wrong type, are out of acceptable range, or are in conflict with one another

Try #4

//Disconnect the recordset by setting the .ActiveConnection to null
rs.Set_ActiveConnection(Unassigned);

causes exception:

Arguments are of the wrong type, are out of acceptable range, or are in conflict with one another

Try #5

//Disconnect the recordset by setting the .ActiveConnection to null
rs.Set_ActiveConnection(OleVariant(nil)); //E2089 Invalid typecast

Try #6

//Disconnect the recordset by setting the .ActiveConnection to null
rs.Set_ActiveConnection(OleVariant(Null));

causes exception:

Arguments are of the wrong type, are out of acceptable range, or are in conflict with one another

Try #7

It's clear to me that Codebarcadero got the declaration wrong. It really is supposed to be an IDispatch. This means i need to trick the compiler into passing an OleVariant located at address 0x00000000 (i.e. nil). That way ADO will see the value 0x00000000 on the stack, and know i mean null:

rs.Set_ActiveConnection(POleVariant(nil)^); //access violation before call

I'm sure Bo..Imp...Co..Embarcadero has the intended way to call this; i just cannot figure it out.

Delphi 5 assembly

Dephi 5 does the correct thing; it pushes $00 (i.e. nil) onto the stack:

rs.Set_ActiveConnection(nil); push $0 ;push nil mov eax,[ebp-$08] ;get address of rs push eax ;push "this" mov eax,[eax] ;get VMT of IRecordset call dword ptr [eax+$28] ;call offset $28 of VMT

Whereas Delphi XE6 is going through heroic efforts to do something i don't know what:

rs.Set_ActiveConnection(nil); lea eax,[ebp-$000000d8] call Null lea edx,[ebp-$000000d8] lea eax,[ebp-$000000c8] call @OleVarFromVar push dword ptr [ebp-$000000bc] push dword ptr [ebp-$000000c0] push dword ptr [ebp-$000000c4] push dword ptr [ebp-$000000c8] mov eax,[ebp-$04] push eax mov eax,[eax] call dword ptr [eax+$2c]

Bonus Reading

  • MSDN: Disconnecting and Reconnecting the Recordset
  • KB184397: How To Create ADO Disconnected Recordsets in VBA/C++/Java
  • MSDN: Recordset Object (ADO)
  • MSDN: ActiveConnection Property (ADO)
like image 987
Ian Boyd Avatar asked Aug 14 '15 19:08

Ian Boyd


1 Answers

In D7 (don't have D5 to hand), AdoInt.Pas contains two flavours of Set_ActiveConnection, e.g.

  Recordset15 = interface(_ADO)
    ['{0000050E-0000-0010-8000-00AA006D2EA4}']
    procedure Set_ActiveConnection(const pvar: IDispatch); safecall;
    procedure _Set_ActiveConnection(pvar: OleVariant); safecall;

and in Delphi XE6:

Recordset15 = interface(_ADO)
   ['{0000050E-0000-0010-8000-00AA006D2EA4}']
   //...
   procedure _Set_ActiveConnection(const pvar: IDispatch); safecall;
   procedure Set_ActiveConnection(pvar: OleVariant); safecall;

So try the other version in XE6. Personally, I'd have tried

Set_ActiveConnection(IDispatch(Nil)) 

first, but you say in comments that _Set_ActiveConnection works for you.

The reason I'd have tried Set_ActiveConnection(IDispatch(Nil)) first, for an interface which requires an OleVariant to be passed, is this: Ever since interfaces were added into Delphi (in D3?), iirc in the version after variant-based OLE automation was added (D2), the compiler has known how to generate code to convert in both directions between an OleVariant and an IDispatch interface. So the "problem" is how to pass an IDispatch interface as an OleVariant argument. That bit, to my simple-minded way of looking at it, is easy, just write IDispatch() where the argument is supposed to be an OleVariant, and leave the compiler to sort out the code to generate. And if the value we want to pass as the IDisaptch interface is actually Nil, we just need to write

SomeInterfaceMemberExpectingAnOleVariant(IDispatch(Nil))
like image 141
MartynA Avatar answered Nov 05 '22 06:11

MartynA