Jump to content
alank2

Using a DLL in MSVC

Recommended Posts

I am building a DLL in cppbuilder 10.2 using the classic compiler with the static RTL.  Sometimes I need a component I have available in cppbuilder to be called from microsoft visualc.

 

Mostly this is working - I have a test application where I am opening a form, changing it, closing it, and that works fine.  But another part of the application that uses this DLL crashes anytime the DLL is loaded.  Even if I don't call any of its functions, just loading it is enough to cause the crash.  

 

To make a msvc compatible lib file for it, I also made a sample msvc project with the same function names/declarations that are empty.  Then I can use that static lib file in the msvc project that loads the real cppb dll.  If I test that empty msvc produced dll, the other part of the application does not crash.

 

I'm trying to test to find out why it crashes.  If I do a dumpbin /exports on the msvc empty stub project that just makes the lib, its empty dll shows this.

 


    00000000 characteristics
    FFFFFFFF time date stamp
        0.00 version
           1 ordinal base
           5 number of functions
           5 number of names

    ordinal hint RVA      name

          1    0 00001010 displaydialog_dll_close = @_guard_check_icall_nop@4
          2    1 00001020 displaydialog_dll_iscanceled = ___acrt_initialize
          3    2 00001020 displaydialog_dll_isopened = ___acrt_initialize
          4    3 00001010 displaydialog_dll_open = @_guard_check_icall_nop@4
          5    4 00001010 displaydialog_dll_update = @_guard_check_icall_nop@4

  Summary

        1000 .data
        1000 .rdata
        1000 .reloc
        1000 .rsrc
        1000 .text

 

Here is the dumpbin /exports from the cppb real dll that I want to use:

 

What are these bold items that are also exported?  They are not in my .def file.

 


  Section contains the following exports for displaydialog_dll.dll

    00000000 characteristics
           0 time date stamp
        0.00 version
           1 ordinal base
          11 number of functions
          11 number of names

    ordinal hint RVA      name

          2    0 00002C4C @@Unit1@Finalize
          1    1 00002C34 @@Unit1@Initialize
          3    2 00066A10 TMethodImplementationIntercept
          6    3 00289B28 _Form1
          4    4 0028110C ___CPPdebugHook
          5    5 00289B14 dbkFCallWrapperAddr
          8    6 00002AD8 displaydialog_dll_close
         10    7 00002AE4 displaydialog_dll_iscanceled
         11    8 00002AE8 displaydialog_dll_isopened
          9    9 00002ADC displaydialog_dll_open
          7    A 00002AD0 displaydialog_dll_update

  Summary

       18000 .data
        1000 .didata
        1000 .edata
        4000 .idata
       37000 .reloc
        8000 .rsrc
      280000 .text
        1000 .tls

 

Code from my cppb dll:

 


//   Important note about DLL memory management when your DLL uses the
//   static version of the RunTime Library:
//
//   If your DLL exports any functions that pass String objects (or structs/
//   classes containing nested Strings) as parameter or function results,
//   you will need to add the library MEMMGR.LIB to both the DLL project and
//   any other projects that use the DLL.  You will also need to use MEMMGR.LIB
//   if any other projects which use the DLL will be performing new or delete
//   operations on any non-TObject-derived classes which are exported from the
//   DLL. Adding MEMMGR.LIB to your project will change the DLL and its calling
//   EXE's to use the BORLNDMM.DLL as their memory manager.  In these cases,
//   the file BORLNDMM.DLL should be deployed along with your DLL.
//
//   To avoid using BORLNDMM.DLL, pass string information using "char *" or
//   ShortString parameters.
//
//   If your DLL uses the dynamic version of the RTL, you do not need to
//   explicitly add MEMMGR.LIB as this will be done implicitly for you

#include <vcl.h>
#include <windows.h>
#pragma hdrstop

#include "Unit1.h"

#pragma argsused
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
  if (reason==DLL_PROCESS_ATTACH)
    {
      Application->Initialize();  //DEBUG i've tried it with and without this no difference still crashes
    }
  else
  if (reason==DLL_PROCESS_DETACH)
    {
    }
  return 1;
}

 

//i've tried remarking out all of the below code leaving only the empty function declarations, no difference still crashes

//none of these functions even have to be called for it to crash, if the DLL is linked in that is enough

extern "C" void __cdecl displaydialog_dll_update(const char *AMessage, const bool ACancelEnabled)
{
  //do nothing if closed
  if (Form1==NULL)
    goto end1;

  //set message
  Form1->MMessage->Text=AMessage;

  //set cancelenabled
  Form1->SBCancel->Enabled=ACancelEnabled;
  Form1->cancelselected=false;

  end1:
  //process messages
  Application->ProcessMessages();
}

extern "C" void __cdecl displaydialog_dll_close()
{
  //do nothing if closed
  if (Form1==NULL)
    goto end1;

  //show form
  Form1->Close();

  //delete form
  delete Form1;
  Form1=NULL;

  end1:
  //process messages
  Application->ProcessMessages();
}

extern "C" void __cdecl displaydialog_dll_open(const char *AMessage, const bool ACancelEnabled)
{
  //close if opened
  displaydialog_dll_close();

  //create form
  Form1=new TForm1(NULL);

  //update message and acancelenabled
  displaydialog_dll_update(AMessage, ACancelEnabled);

  //show form
  Form1->Show();

  //process messages
  Application->ProcessMessages();
}

extern "C" bool __cdecl displaydialog_dll_iscanceled()
{
  bool ret;

  //do nothing if closed
  if (Form1==NULL)
    {
      ret=false;
      goto end1;
    }

  //process messages
  Application->ProcessMessages();

  //is canceled
  ret=Form1->cancelselected;

  end1:
  return ret;
}

extern "C" bool __cdecl displaydialog_dll_isopened()
{
  bool ret;

  //do nothing if closed
  if (Form1==NULL)
    {
      ret=false;
      goto end1;
    }

  //process messages
  Application->ProcessMessages();

  //is opened
  ret=true;

  end1:
  return ret;
}

 

Yes, I know I could probably come up with a similar solution in msvc, but I am more familiar with cppbuilder.  Also, do this same approach with other components from cppbuilder that I also need like tnethtppclient.

 

Any ideas on what to try to troubelshoot this further?

Share this post


Link to post

Even if I make a brand new empty dll via file->new->other->dynamic library vcl with or without vc++ style dll set.  I then set release and set it to link with dynamic rtl=false and link with runtime packages=false, the application will crash if I try to loadlibrary it only (no functions being executed).

Share this post


Link to post
2 hours ago, alank2 said:

Mostly this is working - I have a test application where I am opening a form, changing it, closing it, and that works fine.  But another part of the application that uses this DLL crashes anytime the DLL is loaded.  Even if I don't call any of its functions, just loading it is enough to cause the crash.  

They are using the same DLL instance?

Quote

To make a msvc compatible lib file for it, I also made a sample msvc project with the same function names/declarations that are empty.  Then I can use that static lib file in the msvc project that loads the real cppb dll.

That is not how you should make the import lib for MSVC.  Use MSVC's command-line DUMPBIN and LIB tools to create a new import lib from the DLL itself.  Use DUMPBIN to create a .DEF file for the DLL's exports, and then use LIB to generate a import lib from the .DEF file.

Quote

What are these bold items that are also exported?  They are not in my .def file.

Which items are you referring to exactly? There is nothing "bold" in what you showed.

Quote

Code from my cppb dll:

You really should not be calling Application->ProcessMessages() at all.

Quote

//i've tried remarking out all of the below code leaving only the empty function declarations, no difference still crashes

What is the actual crash? What is the error message, verbatim?  Does the crash happen while the DLL is being loaded into memory, or inside of DllEntryPoint()?

Quote

Any ideas on what to try to troubelshoot this further?

Have you tried debugging the DLL in the IDE?  Configure your DLL project with your MSVC app exe as its Host.  Then put breakpoints in the DLL code as needed.  When you run the DLL project, it will execute the MSVC app, which will then load your DLL, and the debugger should attach to it so it can step through the DLL code normally.

Edited by Remy Lebeau
  • Like 1

Share this post


Link to post

I'll need to work through your email questions Remy, but in the meantime, I found that a new DLL (VCL, multithreading) will also fail on all versions I tried until I got to 12.2 and there is does not fail.  I'll try to compile it there and see if it works with everything enabled again.

 

cppb6 - fails

cppb10.3 - what I was testing on - fails

cppb11.3 - fails

cppb12.2 - does not fail interestingly - perhaps something was fixed.

 

Share this post


Link to post

>They are using the same DLL instance?

 

I probably didn't explain it clearly enough.

 

>That is not how you should make the import lib for MSVC.  Use MSVC's command-line DUMPBIN and LIB tools to create a new import lib from the DLL itself. 

>Use DUMPBIN to create a .DEF file for the DLL's exports, and then use LIB to generate a import lib from the .DEF file.

 

I haven't tried this method yet, but will look into it.

 

>Which items are you referring to exactly? There is nothing "bold" in what you showed.

 

I had bolded the exports I didn't ask for, but it didn't paste in.

 

>You really should not be calling Application->ProcessMessages() at all.

 

I have to or it won't update the form properly.  If I just call Show for example and skip the ProcessMessages, it won't paint it properly.  If I don't call ProcessMessages, it won't process a click on the cancel button that I can read.

 

>What is the actual crash? What is the error message, verbatim?  Does the crash happen while the DLL is being loaded into memory, or inside of DllEntryPoint()?

>Have you tried debugging the DLL in the IDE?  Configure your DLL project with your MSVC app exe as its Host.  Then put breakpoints in the DLL code as needed.  

>When you run the DLL project, it will execute the MSVC app, which will then load your DLL, and the debugger should attach to it so it can step through the DLL code normally.

 

The application is an annoying one to install and is running in a VM where the compiler is not.  I don't want to install it on my dev machine.  The error is a code that c000008e that a search says is a floating point divide by zero error.  I'm assuming it is just some type of crash.

 

I just built the real DLL (not an empty stub) and with 12.2 it fully works with no problems.  With 13.3 it crashes just like it did with 10.3.  I have to think they must have fixed something to make it work in 12.2.

 

Thank you for the help - it is greatly appreciated!

Share this post


Link to post

Also, thanks for the tip about dumpbin and lib to make an import library - that is much easier than what I was doing.

 

I found an example at this link:

https://stackoverflow.com/questions/9946322/how-to-generate-an-import-library-lib-file-from-a-dll

 

... and expanded it to allow specifying an alternate output lib filename, some error checking, and returning an errorlevel:

buildlib_msvc.bat

 

One may have to change the dumpbin/lib path (or eliminate it if running from a MSVC developer prompt).

 


@echo off

rem check for 2 options only
if empty%1==empty goto badopt
if empty%2==empty goto badopt
if not empty%3==empty goto badopt

rem make sure dll exists
if not exist %1 goto dlldoesnotexist

rem delete old lib file if it exists
if exist %2 del %2
if exist %2 goto cantdeleteoldlib

rem add msvc path for dumpbin/lib
if empty%MSVC_PATH_ADDED%==empty SET PATH=C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\bin\HostX86\x86\;%PATH%
if empty%MSVC_PATH_ADDED%==empty SET MSVC_PATH_ADDED=yes

rem make lib (got this from someones example at stackoverflow)
setlocal enabledelayedexpansion
for /f "tokens=1-4" %%1 in ('dumpbin /exports %1') do (
    set /a ordinal=%%1 2>nul
    set /a hint=0x%%2 2>nul
    set /a rva=0x%%3 2>nul
    if !ordinal! equ %%1 if !hint! equ 0x%%2 if !rva! equ 0x%%3 set exports=!exports! /export:%%4
)
for /f %%i in ("%1") do set dllpath=%%~dpni
lib "/out:%dllpath%.lib" /machine:x86 /def: %exports%
if errorlevel 1 exit /b 1

rem make sure new lib exists
if not exist %1 goto didnotproducenewlib

rem rename lib file to specified lib filename
ren "%dllpath%.lib" %2

rem remove exp file
if exist "%dllpath%.exp" del "%dllpath%.exp"

rem success
exit /b 0

:badopt
echo ERROR: Specify buildlib_msvc name.dll name.lib
exit /b 1

:dlldoesnotexist
echo ERROR: %1 dll does not exist
exit /b 1

:didnotproducenewlib
echo ERROR: Did not produce new %2 lib
exit /b 1

:cantdeleteoldlib
echo ERROR: Can't delete old %2 lib
exit /b 1

Edited by alank2

Share this post


Link to post
18 hours ago, alank2 said:

cppb6 - fails

cppb10.3 - what I was testing on - fails

cppb11.3 - fails

cppb12.2 - does not fail interestingly - perhaps something was fixed.

I've written a fair share of DLLs in BCB6 that work just fine in other compilers, including MSVC.  Never had a problem with that.  Makes me think that either you are doing something weird/incompatible that you are not showing, or maybe you are linking in another library into your DLL and that library is incompatible in some way.  Hard to say without a reproducible test case.

18 hours ago, alank2 said:

If I just call Show for example and skip the ProcessMessages, it won't paint it properly.

You can call the Form's Update() method to force an immediate repaint.

18 hours ago, alank2 said:

If I don't call ProcessMessages, it won't process a click on the cancel button that I can read. 

That implies your EXE is calling the DLL functions in a loop that is not processing your UI's message queues in between calls into the DLL.  That is not a good design choice, especially for a DLL with a UI.  I would suggest either:

  • moving your DLL's UI into a worker thread with its own message loop.
  • having the DLL export a function that the EXE can call periodically to pump your UI's message queue.
  • If the EXE has its own UI, then simply break up the EXE's logic to work asynchronously so its own message loop can also service your DLL's UI.
18 hours ago, alank2 said:

The application is an annoying one to install and is running in a VM where the compiler is not.

Have you considered using the Remote Debugger?

18 hours ago, alank2 said:

The error is a code that c000008e that a search says is a floating point divide by zero error.  I'm assuming it is just some type of crash.

Now we're getting somewhere useful.  RAD Studio 12.0 did make changes to how it handles floating-point exceptions:

 

https://docwiki.embarcadero.com/RADStudio/Athens/en/What's_New#Disabling_Floating-Point_Exceptions_on_All_Platforms

 

On older versions, sometimes you do need to adjust the exception masks manually.  Particularly for MSVC compatibility.

Edited by Remy Lebeau
  • Like 1

Share this post


Link to post

Is there a way to hit the Quote button and split into multiple quotes you can respond to?


>You can call the Form's Update() method to force an immediate repaint.

 

Will try this.

 

>That implies your EXE is calling the DLL functions in a loop that is not processing your UI's message queues in between calls into the DLL.  That is not a good design choice, especially for a DLL with a UI.  I would suggest either:

>    moving your DLL's UI into a worker thread with its own message loop.

>    having the DLL export a function that the EXE can call periodically to pump your UI's message queue.

>    If the EXE has its own UI, then simply break up the EXE's logic to work asynchronously so its own message loop can also service your DLL's UI.

 

I did have a processmessages function exported that would call processmessages, is this what you mean by the bold suggestion here?

 

>Have you considered using the Remote Debugger?

 

I've never used but, but will have to look into it!

 

>Now we're getting somewhere useful.  RAD Studio 12.0 did make changes to how it handles floating-point exceptions:

>https://docwiki.embarcadero.com/RADStudio/Athens/en/What's_New#Disabling_Floating-Point_Exceptions_on_All_Platforms

>On older versions, sometimes you do need to adjust the exception masks manually.  Particularly for MSVC compatibility.

 

How would I do that?  Adjust these:

Default8087CW, DefaultMXCSR, DefaultFPSCR, and DefaultFPSCR

Somehow in the older compiler version?

 

Share this post


Link to post
1 hour ago, alank2 said:

Is there a way to hit the Quote button and split into multiple quotes you can respond to?

If you want to quote multiple messages at one time, you can click on the '+' button on the bottom of each message, and then click the resulting "Quote # posts" popup when ready.

 

If you want to quote multiple sections of a single message, that takes a little more work.

  • In an original message, you can highlight the text you want to quote, then roll the mouse over the selection and click on the resulting "Quote selection" popup.  Repeat as needed.
  • Alternatively (on a desktop browser only, don't try this on a mobile browser, it doesn't work well)...
    • after quoting a message, if you put the edit caret inside of the quote text where desired and then insert a few line breaks, SOMETIMES that will automatically split the quote into 2 separate quotes, and then you can add your responses in between them.
    • But, that split does not always work, in which case I end up having to copy/paste/edit the quotes to get what I want.  Roll the mouse over a quote and you will see a little box appear in the top-left corner of the quote.  Left-click on that box to select the quote as an object, then you can copy/paste the quote, and then edit the quotes as needed.  You can also drag&drop quotes using that little corner box, too.

 

Quoting can be a headache, err, art form on this forum! :classic_wacko:

1 hour ago, alank2 said:

I did have a processmessages function exported that would call processmessages, is this what you mean by the bold suggestion here?

Yes.

1 hour ago, alank2 said:
3 hours ago, Remy Lebeau said:

On older versions, sometimes you do need to adjust the exception masks manually.  Particularly for MSVC compatibility.

How would I do that?  Adjust these:

Default8087CW, DefaultMXCSR, DefaultFPSCR, and DefaultFPSCR

Somehow in the older compiler version?

Have you read the documentation yet?

 

https://docwiki.embarcadero.com/Libraries/en/System.Default8087CW

https://docwiki.embarcadero.com/Libraries/en/System.DefaultMXCSR

https://docwiki.embarcadero.com/RADStudio/en/Floating_Point_Operation_Exception_Masks

 

Edited by Remy Lebeau
  • Like 1

Share this post


Link to post

You have figured it out Remy!!!  Good work!  Superb!!!  Thank you!

 

I added System::Set8087CW(0x133f) to my DLL and rebuilt it in 10.2 - no crash.  Remark it out, back to the crash.

 

This brings up a question #1 - is the original value (whatever it was set to before loading the DLL changes it) stored somewhere (could it be Saved8087CW)?  I'm not sure I'd want a DLL changing the floating point behavior of a program just because it is loaded.

 

Also, should I do an Application->Initialize() or not in the DllEntryPoint/atttach?  Question #2 - What does Application->Initialize() do?

 


int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
  if (reason==DLL_PROCESS_ATTACH)
    {
      //Application->Initialize();
      //Application->MessageBox(L"test",L"test",MB_OK);

      System::Set8087CW(0x133f);

    }
  else
  if (reason==DLL_PROCESS_DETACH)
    {
    }
  return 1;
}

 

Edited by alank2

Share this post


Link to post
11 hours ago, alank2 said:

is the original value (whatever it was set to before loading the DLL changes it) stored somewhere (could it be Saved8087CW)?  I'm not sure I'd want a DLL changing the floating point behavior of a program just because it is loaded.

You will have to call Get8087CW() first and save the value before calling Set8087CW(), then do your work, and then call Set8087CW() again with the value you saved. Do this in each of your exported functions that suffer from the problem.

 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×