Porting UNIX C code to windows DLL (16/32)

OK, I developed UNIX utility in C. This is fairly complicated Parser. It accepts sql string (usualy created on the fly dynamic SQL, so it is normally one huge string) and then formats it nicely with all the intends, multiple levels of nested subselect, etc. so it is very easy to debug its syntax. Works great on UNIX, recognises all sql key words, smart enough to detect sql parameters, etc..

Now I want to port it to Windows DLL. I have 1.52 VC++ and 4.0 VC++, both installed on NT 3.51. I tried to create a DLL in both versions and failed. I have no problem creating primitive DLL on both 16/32 platforms that accepts the string and changes it and returns back (e.g. to VB application), but my utility does not want to work. I am able to build it, then I can call it from the main program (VB 3.0 for 16 bit DLL, VB 4.0 for 32) and my VB crashes.

There is no MFC functionality required, no C++ functionality, just plain UNIX C, that I want to make a DLL. This is really helpful application for those who creates dynamic SQL, I want to distribute it as a shareware. I will be very gratefull if you will be able to guide me through my porting process step by step. I know C pretty good, but not VC++. As additional bonus I will send you the registered version of this DLL when it will be finished, if you can help me. Thank you.
Who is Participating?

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Tommy HuiEngineerCommented:
Make sure you have the .c extension on your files and not .cpp. Then for each function you want to make available from your DLL add the _export keyword to it.

void _export foo(void)

That's all that is needed.
we use __dllexport in front of all the functions in a dll.

BTW, the parsing code for SQL is included in the MFC souce with VC++.

lapidesAuthor Commented:
Your 15 words answer did not make my life any easier.

Not a clue! Given as is it provides zero help and desrves zero points.

Your answer may be very useful for someone who already knows the answer, but for person like me the value of your answer is nill.

1. I already wrote that I build my DLL succesfully, I also was able to call it from outside, but then my VB calling program crashed.

This would imply that I shoulda declare it as an _export otherwise how coulda my calling program call it?

Nevertheless I renamed my cpp source to c and rebuild it and run it with debugger (using my calling VB program as a driver). It crashed inside my DLL on the first line. (I just run it under VC++ 1.52)

2. Now if you know how to do this port and WILLING to spend a bit more time and be more descriptive and show me what I am doing wrong please do.

Here what I did in detail:

3. Once again, I do not know what is going wrong. Should it be OK to build this DLL with VC++ 1.52? or with VC++4.0? Would I be able to call my 16 bit DLL when running 16 bit apps under NT? Would I be able to call this 16 bit DLL from 32 bit apps (e.g. VB 5.0 or PB 5.0)?

4. Then I have added to my program the code in the very beginning:


   #include <windows.h>
   #include <ole2.h>

   #ifdef _WIN32
     #define CCONV __stdcall
     #define NOMANGLE
     #define CCONV FAR PASCAL _export
     #define NOMANGLE EXTERN_C
     #include <stdlib.h>
     #include <compobj.h>    
     #include <dispatch.h>
     #include <variant.h>
     #include <olenls.h>  

When I compile under UNIX I comment the first line.

5. Then I changed the declaration of the function to be like this:
    external DLL functions declarations  
   #ifdef _WIN32
      NOMANGLE short CCONV dsqld(BSTR lsIn,short liSQLLen,
                              BSTR lsEr,short liErrorLen);
      NOMANGLE short CCONV dsqld(LPSTR lsIn,short liSQLLen,
                              LPSTR lsEr,short liErrorLen);
   int dsqld(char *lsSQL,char **lsOut,char *lsError, int liErrorLen);

BTW you see that "_export" is there in 16 bit version and it is abcent in 32 bit. But in 32 bit version (VC++ 4.0) I use def file where I declare this function as an export one, I also use this
def file with explicit external declaration.

Now as you see the interface is slightly different under UNIX and WINDOWS. Under UNIX I pass the pointer *lsSQL, dsqld() allocates memory for the output, parses the input string, inserts into it
intends, newline where they belong and then populates this allocated memory and passes back the pointer to this buffer. The calling program under UNIX can see this pointer.

Because this is impossible on WINDOWS I have changed the interface. My programm will accept lsIn, internally put all the additional characters (via allocating temporary memory in its own address space), and then just before exit it will copy the string from the internal buffer to lsIn overwriting the input. It is the responsibility of the calling programm under WINDOWS to allocate
the memory for input parameter, populate it and pass to my function along with the allocated length. My program will then make sure that if it writes (copies) the string in lsIn the length of this string is less then liSQLLen - the second parameter. The same story with the lsError.

6. Those were the only changes that I made.

7. I then started VC. It asked me what kind of new project I would like to build. I choose DLL with
no MFC. Then I added to this project two files - my source code that I renamed to dsqld.cpp
originally and then tried today with dsqld.c and the dsqld.def.

8. Then I created the simpliest executable (VB program) that calls my function.

9. Then in VC I specified the debug option, executable program, set breakpoint in the first line and run it. It stopped on the first line, which was:

   if(NULL==(lsSQL=(char *)malloc(liSQLLen))){

and crashed.

10. What I am doing wrong?

11. In my C code I use (char *), may be under WINDOWS I have to use LPSTR everywhere instead?

12. why calling my file cpp instead of c is a crime?

I will appreciate your REAL advice very much, but it sure will take some time on your side. Keep in
mind please that I have about 10 years C experience on UNIX and zero on Windows. And please start
with the basics (1.52 vs 4.0, 16 vs 32, what project to start, etc.)

Protecting & Securing Your Critical Data

Considering 93 percent of companies file for bankruptcy within 12 months of a disaster that blocked access to their data for 10 days or more, planning for the worst is just smart business. Learn how Acronis Backup integrates security at every stage

Are you sure you want to support that 16 bit stuff? I ported
an application to WNT lately, from a Un*x environment.
I know next to nothing about WNT (and I'd like to keep
it that way, thank you ;-) but the porting process was a
breeze ... most of the time was spent setting the right
compiler and linker flags in those dialog window thingies ...

FYI, the application used the dlopen() stuff on the Unix
boxes which translated into dll stuff easily. Sockets
stuff were easy to port too (to my surprise ;-)

Have you exported/imported all the object names from
the 'base' executable and the dlls properly?

kind regards,

Jos aka jos@and.nl
OK, now we're getting somewhere.  

Here are the facts, I think:

1. you compiled the DLL with no errors/warnings
2. you are using BSTR for VB-compatibility purposes.
3. your dll is C
4. your testing app is VB

since BSTR more or less is a pain in the butt to work with, I would recommend that you use LPSTR everywhere instead.  I think that BSTR requires memory allocation and some kind of setup call first, too.  CString::sysAlloc or something like that.  Anyway, if you make the stuff LPSTR (compiles down to char *), your life will be easier.

BTW, do a TRACE (VC 4.0) or fprintf (debug file in VC 1.52) to tell you what lsSQLLen is.  I'd need that to help you.

So, really, if you get rid of that BSTR stuff (you don't need it.  VB can send char* if they are referenced/dereferenced correctly), you should be rid of your problems.

lapidesAuthor Commented:

Hi experts...

So I solved my problems. The facts are:

1. I have created both DLLs, on the same machine (NT 3.51) under VC++1.52 and VC++4.0.

The first one works with VB3.0 (16 bit) the second one works with VB 4.0 (32 bit).

2. The source code for both DLLs identical (and different from UNIX code!!!!).

3. BTW, both DLLs are declared as *.cpp not *.c. Everything works. Beats me can't understand why your suggestion about *.c extension is so important...

4. Also, there is no need to use tracing in the ourtput file under 1.52 (another your suggestion). Both versions (1.52 and 4.0) allow debugging DLL by specifiing in debug settings the external exe (which was my VB driver).

5. The ONLY reason for my grief, that caused DLL to behaiv crazy (non-detectable with simple debugging) was as follows.

Inside my original code there was a fragment where I wanted to save the string in the temporary buffer. So under UNIX I did:

char *lsSave;

void SaveTmp(char *lsIn,char **lsSave);

and then I call this SaveTmp, like this


Pointer to the pointer is absolutely natural and essential under UNIX. This is because when I call SaveTmp I do not know where to save, it is done within SaveTmp: first memory is allocated and then it is populated, then inside the calling program I can readily access this lsSave, it points to the right place.

By some unknown reason VC++ does not like this (thanx Billy) and goes crazy.

As soon as I allocated the memory lsSave just before calling SaveTmp (and changed it's interface to:
void SaveTmp(char *lsIn,char *lsSave);

it was immediately tamed. (lsSave does only populating of lsSave not allocating)

Thanx for trying to help. I understand that my question was not the easiest one.


re: .C nv .CPP
c++ has function overloading (ie. foo(int) and foo(float) can happily coexist) so it uses "mangling" to distinguish the two for the linker (calling them somehting like foo_int and foo_float).
This is because the compiler decides which version of foo to use, and has to have some way of telling the linker (in effect, the linker thinks the two foo's are completley different functions)

re: weird problem with char **:
somehting do do with thunking, ie. seg:offs vs. flat addressing perhaps?

I have written DLLs and called them from VB. If you don't get the calling conventions right, it can easily crash the system!

you can have your DLL in a .cpp file, just make sure you use extern "C" if you do. Declare the function like this:

extern "C" DWORD _declspec(dllexport) __stdcall theFunction(char *buf)

This will generate an external entry point called _theFunction@4
where the 4 is the total number of bytes needed for args

In VB, declare the call like this:
Declare Function foo Lib "SOMETHING.DLL" Alias "_theFunction@4" (ByVal s as String) As Long

This will pass the string as you want. Even though you are using ByVal, you can still modify the string. You need to make sure you are passing a big enough buffer, so you may want to preallocate the destination string with Spaces().

Hope this helps,


Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today

From novice to tech pro — start learning today.