Solved

Returning a Recordset From a C++ DLL

Posted on 1998-06-01
35
863 Views
Last Modified: 2008-03-04
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;
}
0
Comment
Question by:clifABB
  • 14
  • 12
  • 4
  • +2
35 Comments
 
LVL 6

Author Comment

by:clifABB
Comment Utility
Edited text of question
0
 
LVL 22

Expert Comment

by:nietod
Comment Utility
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.
0
 
LVL 22

Expert Comment

by:nietod
Comment Utility
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?
0
 
LVL 6

Author Comment

by:clifABB
Comment Utility
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?
0
 
LVL 6

Author Comment

by:clifABB
Comment Utility
Edited text of question
0
 
LVL 6

Author Comment

by:clifABB
Comment Utility
Edited text of question
0
 
LVL 22

Expert Comment

by:nietod
Comment Utility
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.
0
 
LVL 6

Author Comment

by:clifABB
Comment Utility
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.>
0
 
LVL 22

Expert Comment

by:nietod
Comment Utility
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.
0
 
LVL 1

Expert Comment

by:ElmerFud
Comment Utility
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++.  :)
0
 
LVL 6

Author Comment

by:clifABB
Comment Utility
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."
0
 
LVL 22

Expert Comment

by:nietod
Comment Utility
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.)
0
 
LVL 1

Expert Comment

by:ElmerFud
Comment Utility
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
0
 
LVL 1

Expert Comment

by:ElmerFud
Comment Utility
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++.
0
 
LVL 15

Expert Comment

by:Tommy Hui
Comment Utility
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.
0
 
LVL 22

Expert Comment

by:nietod
Comment Utility
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++.
0
 
LVL 1

Expert Comment

by:ElmerFud
Comment Utility
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.
0
6 Surprising Benefits of Threat Intelligence

All sorts of threat intelligence is available on the web. Intelligence you can learn from, and use to anticipate and prepare for future attacks.

 
LVL 22

Expert Comment

by:nietod
Comment Utility
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.
0
 
LVL 6

Author Comment

by:clifABB
Comment Utility
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.
0
 
LVL 22

Expert Comment

by:nietod
Comment Utility
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.
0
 
LVL 6

Author Comment

by:clifABB
Comment Utility
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?
0
 
LVL 22

Expert Comment

by:nietod
Comment Utility
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++...)
0
 
LVL 3

Accepted Solution

by:
altena earned 250 total points
Comment Utility
There is a much cleaner way of doing this:

Use a collection. That way your VB code can be as simple as:

For each record in TheRecordSet
  'bla bla bla
Next record.
---------------------------------
This is as close to the design spec as you will get. The object to pass around will (probably) contain "TheRecordSet" and "TheWhereClause". If you would ask me, I would build it using ATL or MFC. (I would need some time to figure out how to build a VB-style "collection" class in C++.)

Since you are offering a planeload full of point for this: Here is why you will run into trouble with your current approach.
As some have pointed out to you: There is a difference between a C++ class and a VB class. In simple terms: a VB-class is a C++ object that implements the IDispatch interface. VB accesses all properties and methods through IDispatch.
"CDaoRecordset" does not support IDispatch.

And as far as your customer is concerned, as far as I can deduce from this thread your design spec (and I am going to put this technically) is utter crap. Really, only someone who NEVER writes any real code can come up with a convoluted construction like the one you are implementing.

I will hapilly talk/e-mail some sense into him/her. (for a very modest consulting fee).

Lets finish this with a few counter questions:
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?
Do you, as a professional developer want to deliver a solution that is bound to break?
I agree with your comments to ElmerFUd, but I do think that the
"the customer wants it like this, so so shall it be." excuse is too easy. Don't be suprised if the blame-storming session at the end of the project will identify your VC-VB-visual-recordset-bouncing module as the main cause for everything that went wrong.

0
 
LVL 22

Expert Comment

by:nietod
Comment Utility
>>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.
0
 
LVL 3

Expert Comment

by:altena
Comment Utility
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?
0
 
LVL 22

Expert Comment

by:nietod
Comment Utility
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...
0
 
LVL 6

Author Comment

by:clifABB
Comment Utility
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).
0
 
LVL 3

Expert Comment

by:altena
Comment Utility
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
0
 
LVL 6

Author Comment

by:clifABB
Comment Utility
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?
0
 
LVL 22

Expert Comment

by:nietod
Comment Utility
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.
0
 
LVL 3

Expert Comment

by:altena
Comment Utility
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...


0
 
LVL 22

Expert Comment

by:nietod
Comment Utility
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.)
0
 
LVL 6

Author Comment

by:clifABB
Comment Utility
I have requested of Linda (or whoever) to split the points between nietod and altena.

Thank you both for your help.
0
 
LVL 6

Author Comment

by:clifABB
Comment Utility
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.
0
 
LVL 22

Expert Comment

by:nietod
Comment Utility
If you haven't already asked it.  could you ask it in the C++ area.  I don't look here on a regular basis.
0

Featured Post

What Should I Do With This Threat Intelligence?

Are you wondering if you actually need threat intelligence? The answer is yes. We explain the basics for creating useful threat intelligence.

Join & Write a Comment

I was working on a PowerPoint add-in the other day and a client asked me "can you implement a feature which processes a chart when it's pasted into a slide from another deck?". It got me wondering how to hook into built-in ribbon events in Office.
If you need to start windows update installation remotely or as a scheduled task you will find this very helpful.
Get people started with the process of using Access VBA to control Outlook using automation, Microsoft Access can control other applications. An example is the ability to programmatically talk to Microsoft Outlook. Using automation, an Access applic…
Get people started with the utilization of class modules. Class modules can be a powerful tool in Microsoft Access. They allow you to create self-contained objects that encapsulate functionality. They can easily hide the complexity of a process from…

762 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

13 Experts available now in Live!

Get 1:1 Help Now