8

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

Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
  • @MartynA `varNull` is a constant that is declared to be `1`. It is used in the variant record to indicate what the variant record contains. So it does not make sense to set the `ActiveConnection` to `1`. But i tried it for fun: it failed with the same exception. – Ian Boyd Aug 14 '15 at 19:34
  • What I failed to recollect correctly was "RecordSet.Set_ActiveConnection(IDispatch(Nil));" (at least this compiles in XE8, fwiw) – MartynA Aug 14 '15 at 19:50
  • @MartynA: That works, at least on XE8. (By *works*, I mean *doesn't raise an exception*.) – Ken White Aug 14 '15 at 19:52
  • @KenWhite: Indeed. There used to be a Briefcase-mode ADO demo in the D7era. Fwiw, the D7 AdoInt.Pas had "procedure Set_ActiveConnection(const pvar: IDispatch); safecall; AND procedure _Set_ActiveConnection(pvar: OleVariant); safecall;" in the RecordSet15 interface. – MartynA Aug 14 '15 at 19:58
  • @MartynA: The XE8 AdoInt unit has the same two declarations, and a fresh import of the ADO type library generates the same two declarations in MSADO_TLB.pas as well. Calling either with `IDispatch(nil)` seem to be equivalent - neither raises the exception. – Ken White Aug 14 '15 at 20:05
  • @MartynA You actually solved it. The `Recordset` interface really does have two overloads of `Set_ActiveConnection` - one at VMT offset $28 and the other at $2C. Delphi XE6 just changed around which one had the `_` prefix (i guess Delphi doesn't support overloads in interfaces). I just have to change my call from `rs.Set_ActiveConnection` to `rs._SetActiveConnection`. Phrase that in the form of an answer and you win. – Ian Boyd Aug 14 '15 at 20:11

1 Answers1

4

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))
MartynA
  • 30,454
  • 4
  • 32
  • 73