Link to home
Start Free TrialLog in
Avatar of clifABB
clifABB

asked on

Returning a Recordset From a C++ DLL

I need to be able to return a recordset from a C++ DLL.
I have two possible choices (see below) however neither work.
What is wrong with either or both???

Option 1. Declare the routine as a recordset:

The VB Decalaration:
Public Declare Function dsGetRecordset Lib "DS.DLL" Alias "_dsGetRecordset@12" (ByVal App As String, ByVal Table As String, ByVal WhereClause As String) As Recordset

The VB Call:
Dim rsTable As Recordset
Set rsTable = dsGetRecordset("xx", "MyTable", "where x=1")
'After the call, VB Crashes

The following C++ Routine works within C++, but when run from VB, it kills VB.

extern "C" __declspec( dllexport ) CDaoRecordset *__stdcall dsGetRecordset(BSTR Application, BSTR TableName, BSTR WhereStr)
{
      CDaoRecordset      *TheRecordset;
      CString                  QString;
      CString                  Where = (LPSTR) WhereStr;
      CString                  Table = (LPSTR) TableName;
      CString                  App = (LPSTR) Application;

      TheRecordset = new CDaoRecordset(Tables->LocalDBObject);
      CString TempString = Where.Left(5);
      TempString.MakeLower();
      if(TempString != "where")
            Where = " where "+Where;
      Where.TrimRight();
      Where.TrimLeft();
      
      QString            = "select * from "+Table+" "+Where+" and App = '"+App+"'";
      TheRecordset->Open(AFX_DAO_USE_DEFAULT_TYPE, QString,0);
      
      return TheRecordset;
}


Option 2. Pass the recordset ByRef:
The VB Decalaration:
Public Declare Function dsGetRecordsetItself Lib "DS.DLL" Alias "_dsGetRecordsetItself@4" (Object1 As Recordset) As Long

The VB Call
Dim lRes As Long
Dim rsTable As Recordset
lRes = gsGetRecordset(rsTable)
'After the call, rsTable = Nothing

The following C++ routine is only a sample, and works, but the VB recordset = nothing.

extern "C" __declspec( dllexport ) long __stdcall dsGetRecordsetItself(CDaoRecordset *RS)
{
      RS = new CDaoRecordset(Tables->LocalDBObject);
      RS->Open(dbOpenSnapshot,"Select * from cu_gen_winding",0);

      return 0;
}
Avatar of clifABB
clifABB

ASKER

Edited text of question
Does the "As Recordset" at the end of

Public Declare Function dsGetRecordset Lib "DS.DLL" Alias "_dsGetRecordset@4" (ByVal App As String, ByVal Table As String, ByVal WhereClause As String) As Recordset

Mean the function returns a pointer to a RecordSet, as opossed to a to a value of a RecordSet?  The C++ DLL is returning a pointer.  I just want to make sure VB is expecting one.
In the second option, the C++ code (simplified)

 long dsGetRecordsetItself(CDaoRecordset *RS)
   {
   RS = new CDaoRecordset(Tables->LocalDBObject);
   RS->Open(dbOpenSnapshot,"Select * from cu_gen_winding",0);
   return 0;
   }

deffinitly won't work.  The problem is that the procedure is passed a pointer by value.  It alters its private copy of that pointer, but it does not alter the caller's copy of the pointer.  The pointer has to be bassed by reference.  In C++ that would be.

 long dsGetRecordsetItself(CDaoRecordset *&RS)

or

 long dsGetRecordsetItself(CDaoRecordset **RS)

I can't help you with the VB side of this.  Perhaps you can figure it out now?
Avatar of clifABB

ASKER

nietod:
Unfortunately your suggestion didn't work.

In reference to the first option.  VB is returning a value (not a pointer to a value).  The bad news is that, from what I understand, VB's recordset is not the same as C++'s CDaoRecordset, so it has to be a pointer.

Any other ideas?
Avatar of clifABB

ASKER

Edited text of question
Avatar of clifABB

ASKER

Edited text of question
Well in the first option.  The C++ code is returning a pointer to a record set.  If your VB code is not expecting a pointer, but is instead expecting a value.  That won't work.  you've got to make them match.

However, if as you say the C++ object has a different format, then getting the other part to match will not help.

Can you get C++ to work with something that is the same format as the VB recordset?  (Do you know the format of the VB record set?)

Why are you doing this in the first place?  Why both VB and C++?  Perhaps there is an easier design.
Avatar of clifABB

ASKER

Why are you doing this in the first place?  Why both VB and C++?

It's in the design specs.  Personally I would prefer a straight VB approach, but...

<Mongo just pawn in chess game of life.>
Well mongo, you need to get VB and C++ to agree on calling conventions and on the format of the objects you are passing back and forth or sheriff Bart is going to lock you up.
Why in the world are you doing it this way?  VB has DOA built into it.  The process of getting a record set it so simplified.  You shouldn't need a .dll call.

Dim db As Database
Dim rstRS As Recordset
    Set db = OpenDatabase("c:\mydatabase.mdb)
    Set rstRS = db.OpenRecordset("select * from MyTable where x=1")

You could easily turn this into a function that returns a recordset.  I don't under stand the reason for such a weird call.  Also the Database and RecordSet object as intimately linked in VB.  I bet that the RecordSet object is different in VB than it in in C++.  I bet they added some things to it for people who are too lazy to use a real programming language.  I bet it's an incompatibility problem with the data structure which in anycase will never work.

VB blows a goat -- it crashed when the wind blows.  Stick with a real man's programming language like C++.  :)
Avatar of clifABB

ASKER

ElmerFud:
I didn't put this question out in the ether to be insulted.

The reason I'm doing it this way it because that's what is in the design spec.  I have made my point to the customer, but in the end the customer is always right.

I wonder, if you are a professional programmer as you say, how good you are if you can crash a crash-proof language like VB.  Also makes me wonder how you program in C++ which, if you son't watch yourself, can bring down the whole system.

A wise man once said:
"C makes it easy to shoot yourself in the foot.  With C++, you can blow your whole leg off."
Well, the customer is rarely right, but they alway get their way.

What is it exactly that they want?  What part has to be in C++ and what part in VB?  (I'm thinking you can use handles to get it to work.)
VB is not crash proof.  It make programs that are not very robust.  If you get a record that is NULL and you try to do something with it, it say "Invalid use of NULL" and stop exicuting code.  In VB is yo uwere to do say "kill c:\hi.txt" and hi.txt was not there, it would stop exicuting the program.  If I go "DeleteFile("c:\\hi.txt);" and it's not there it throws back an error and I can do what ever I want with it.

Anyway, I never ment to insult you by insulting VB.  I apollogize.
-Elmer
PS  I like having the ability to blow off my leg if need be.  If I wanted to blow off my leg in VB I'd have to have the user copy the file "blowoffleg.dll" which contains the API for blowing off your leg which was most likely written in C++.
The fundamental problem with your question is that VB and VC use different types of objects and even though they are called the same in the respective languages, they are not the same thing. Since they are not the same types of objects, you cannot get the two languages to mix and match them correctly.
But solutions are possible where one language (C++) creates and manipulates object for the other language (VB).  What would happen is that C++ returns pointers to the objects.it creates, and VB can specify these pointers as parameters to C++ functions.  VB could not use these pointers directly, but would instead use them as handles that make sense only to C++.
neitod, but manipulating the RecordSet data structure would be a lot of work.  You'd have to dig into each data structure and figure out how to reconsile them, which most likely only a handfull of M$ programmers know.  In my opinion is's way too much work.
No.  That's exactly what I'm saying you shouldn't do.  

One language (probably C++) considers the objects to be objects and uses them as objects.  The other language just has handles (pointers) to the objects but does not know what they really are and does not directly manipulate them in any way.  If it needs to manipulate the object, it calls a procedure from the other languange and passes the handle (pointer) so that the language that understands the object can use it.
Avatar of clifABB

ASKER

This conversation is interesting and all, but I'm still stumped.

VB will either retrieve a pointer or the actual object depending on the declare:
Declare Sub Foo Lib "MyDll.dll"(ByRef Param1 As Object)
Passes a pointer.

Declare Sub Foo Lib "MyDll.dll"(ByVal Param1 As Object)
Passes the actual object.

Whether the parameter is declared As Recordset or As Object, ByRef or ByVal, my code example in the original question still doesn't work.
Forget about your original example for the moment--even though that is the part that is important to you.

It seems the only hope you've got is going to be a handle type approach.  But the feasability of this type of approach depends mostly on what sort of things you need to be doing.  You need to answer the following questions.

What is the C++ code going to be responsible for?.
What is the VB code going to be responsible for?
Who creates and destroys the objects?
What sort of manipulations need to be performed on the objects? By C++  By VC?

If I can give you an analogy.  This sort of solution is similar to the way you work with the Windows OS objects.  Take a DC for example.  a DC handle is a pointer to some structure that you know nothing about.  So you can't create it or manipulate it dirrectly.  You have to make function calls to the OS to have the OS manipulate it.  For example, when you want a new DC you call CreateDC() and gett back a handle.  You then specify that handle to other calls that work on the DC.  Like SelectObject(), for example.  When you are done with the DC you call ReleaseDC() to get the OS to delete the DC for you.  

This is the type of solution you are probably going to have to take.  C++ will create objects for VC and return points to them.  When VB needs somethign done it calls a C++ function and specifies a pointer to one of the objects.
Avatar of clifABB

ASKER

Ok, here's the scenario:
1. VB creates the recordset object.
2. VB passes the recordset to C++ as well as a tablename and a 'where' clause.
3. C++ is responsible for loading the recordset from that tablename based on the where clause.
4. C++ passes the recordset back to VB.
5. VB deals with the recordset.
6. When done, VB destroys the recordset.

7. I just reviewed the steps above and realized it can't be done, because the recordset VB creates is not a recordset until it is initialized with VB's Set command.  Have you ever tried to pass an uninitilized variable to a procedure?
Have I ever tried to pass an uninitialized varaible?  Well not on purpose..

Your options are either

change 1 so that C++ creates the recordset for VB
chagne 2 so that VB passes the record set pointer back to C++
change 5 so that it calls C++ each time it needs to "deal" withthe record set.  This is the
    desciding factor.  It could be simple or could be a nightmeer.
change 6 so that  VB calls C++ to destroy the record set.

OR

change 3 so that C++ calls back to VB to change the recordset

The second option is probably a lot simpler, but it almost certainly defeats the point of using C++.  (Actually, there might be no point to using C++...)
ASKER CERTIFIED SOLUTION
Avatar of altena
altena

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
>>Just suppose you do manage to somehow pull this off. You call
>>into a c++-dll which returns you a recordset. You bounce the
>>recordset around the court for a while.
>>Do you think the application will still work whenever VB-6.0
>>or VC-6 or DAO 3.5.6.7.8.8.5 comes around?

Yes.  Not that I'm defending what we all agree is a stupid design, but it will work fine.  That's the beauty of encapsulation.  VC can change the record set and as long as its interface to VB does not change, VB needs no changes.  Same thing in the other direction.
I too am enchanted by the beauty of encapsulation, really.
But that will mean that you intend to build an Automation
interface AROUND the CDaorecordset class...

The code on this thread so far does no encapsulation whatsoever. VB will call straight into a dll. and will somehow manage to use the raw CDaoRecordset class. (in other words the interface will span just about every aspect of VB, VC and DAO that you can imagine)
My gut feeling is that this thing will break soon. (Assuming you get it to work in the first place.) I think it can be done too. But Should it be done?
The code that I posted around june 5 at 9:51 deals with encapsulating the class within the C++ DLL and providing a standard function interface for VB.

Should it be done?  Well, if someone is goign to pay for it.  And if you warned them...
Avatar of clifABB

ASKER

altena:
I fail to see how your answer solves my problem.  But being an honorable sort, I will give you a chance to explain the solution you gave before I decide whether to grade or reopen.
However, if your answer only amounts to "It's a stupid idea" or, to use your words, "utter crap", everyone (including me) has said the same thing.  I must say that, if I were to grade that answer (which I just might), I would have to give the points to the first one who said it (which I believe was nietod).
Here is a way to do it more or less according
to the specs (I also mention this in the answer):
Use a collection. This is easy to use/create in VB. Also
possible in C++ (and as I said in the answer, I would have to
dive into the MSDN library to find out how to do it)

This might just be what the original designer had in mind.
(at least let us hope so)

Another way (this gets a bit ugly, but according to the spec):
Whenever you are required to do the recordset-bounce-around,
do it like this:
Use VB to create a temporary mdb-file, pass the filename to
VC, fill up the database and then retreive the data again
in VB. (I would rather put my job on the line than to do this, but if the spec is sacred to you...)

However, if you have gotten the point from all of our comments, that the spec just might not be optimal, then by all means award the points to nietod (who was the first one to question the spec, I will hapily take credit for being the first one to use some stronger words)

Both you and your customer have a strong interest in revising the spec: Your customer just might end up with working software and you might have a more pleasant programming experience.

Good Luck
Avatar of clifABB

ASKER

Altena:
If I understand your suggestion, you are saying don't return a recordset, rather return a collection the looks like a recordset.

Is so, this isn't what the spec asked for.  The spec clearly requires that C++ return a true recordset.

However, I'm giving up.  I have told my manager that it can't be done for the reasons I have given above.  That a recordset is declared, but unitialized.  And you can't pass an uninitialized variable.

Now, what to do with the points...
All answerers made the point from the beginning that this was a stupid idea.
Nietod and Altena both made some valid gestures which, I'm sure, would have worked had this been possible.

I have 60 points in the bank.  If I award a "D" and then enter another question for nietod for 60 points and award an "A" this would balance out (almost).  But it doesn't seem fair to award a "D" for all the hard thinking I put ya'll through.

What are your suggestions as to points?
First of all it is possible.  The system I proposed of using handles will work.  It will be extra work for no benefit, but it will work.
I once read you can ask Linda to split the points.
However I have no clue as to how that works.

The A-D construction is kind of cruel to the guy receiving a D
The D shows up in your (and mine) page for a long long time...


You can post a question (o points) in the customer service section that makes the request.  However, I don't think they want it to be done too often.  (Extra work for Linda.)
Avatar of clifABB

ASKER

I have requested of Linda (or whoever) to split the points between nietod and altena.

Thank you both for your help.
Avatar of clifABB

ASKER

Amazingly, Linda has no method of splitting points.  However she did give me information (and a few points) on doing this myself.

Altena:
Here is your grade.  Thank you very much for your help.

nietod:
I will create a new question for you for equivilant points (and equivilant grade).  Thank you as well for your help.
If you haven't already asked it.  could you ask it in the C++ area.  I don't look here on a regular basis.