Search code examples
c++c++builderdirectshowansistring

Including DShow.h breaks VCL AnsiString::sprintf() on BDS2006


I finally got some time to upgrade my video capture class. I wanted to compare VFW (what I have used until now) and DirectShow. As expected, DirectShow is faster, but when I added info texts, suddenly AnsiString::sprint() is no longer a member of AnsiString.

After some struggle, I found a workaround as AnsiString::printf() still works, but I am curious how to fix this. Maybe some define from dshow.h and dstring.h are conflicting?

I cut down all the unnecessary code to show this problem:

//$$---- Form CPP ----
//---------------------------------------------------------------------------
#include <vcl.h>
#include <dshow.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
    static int i=0;
    Caption=AnsiString().sprintf("%i",i);               // this does not work
    AnsiString s; s.printf("%i",i); Caption=s;  // this does work
    i++;
}
//---------------------------------------------------------------------------

It is just a simple VCL Form app with a single TTimer on it. The TTimer is incrementing the counter i and outputting it in the Form's Caption. The DirectX libs are not even linked, just headers included!

The Linker outputs error:

[C++ Error] Unit1.cpp(20): E2316 'sprintf_instead_use_StringCbPrintfA_or_StringCchPrintfA' is not a member of 'AnsiString'

If I swap the vcl.h and dshow.hincludes, the compiler stops indstring.h` on this line:

AnsiString& __cdecl         sprintf(const char* format, ...); // Returns *this

With this error message:

[C++ Error] dstring.h(59): E2040 Declaration terminated incorrectly

So, there is clearly some conflict (the AnsiString keyword is the problem). Putting dshow.h into a namespace does not help, either.

Does anyone have any clues?

Q1. How to fix this?

Q2. What/where exactly is causing this?

The only solution that I can think of, and should work (but I want to avoid it if I can), is to create an OBJ (or DLL) with the DirectShow stuff, and then link that into a standard VCL project without including dshow.h in it, and of course the exports must be without any funny stuff, too.


Solution

  • The problem is not with dshow.h itself, but is actually with strsafe.h instead, which dshow.h includes by default.

    strsafe.h contains the following code 1:

    #ifndef STRSAFE_NO_DEPRECATE
    // Deprecate all of the unsafe functions to generate compiletime errors. If you do not want
    // this then you can #define STRSAFE_NO_DEPRECATE before including this file
    #ifdef DEPRECATE_SUPPORTED
    
    ...
    #pragma deprecated(sprintf)
    ...
    
    #else // DEPRECATE_SUPPORTED
    
    ...
    #undef sprintf
    #define sprintf     sprintf_instead_use_StringCchPrintfA_or_StringCbPrintfA;
    ...
    
    #endif  // DEPRECATE_SUPPORTED
    #endif  // !STRSAFE_NO_DEPRECATE
    

    1 There are similar #pragma and #define statements for many other deprecated "unsafe" C functions.

    If both STRSAFE_NO_DEPRECATE and DEPRECATE_SUPPORTED are not defined (which is the case in this situation), the use of #define sprintf causes all subsequent references to any kind of sprintf symbol to be seen as sprintf_instead_use_StringCchPrintfA_or_StringCbPrintfA; during compiling.

    That is why you are getting the compiler error. When vcl.h is included before strsafe.h, dstring.h gets included first, so the compiler sees the correct declaration for the AnsiString::sprintf() method, and then strsafe.h gets included (presumably by Unit1.h) before the compiler sees your Timer1Timer() code, so your calls to AnsiString().sprint("%i",i) are actually trying to call AnsiString().sprintf_instead_use_StringCchPrintfA_or_StringCbPrintfA;("%i",i), which fail.

    When you swap the vcl.h and dshow.h includes, the #define sprintf statement in strsafe.h gets processed before dstring.h is included, so the compiler sees the following declaration for the AnsiString::sprintf() method in dstring.h and fails:

    AnsiString& __cdecl         sprintf_instead_use_StringCchPrintfA_or_StringCbPrintfA;(const char* format, ...); // Returns *this
    

    To prevent this behavior, you could use an #undef sprintf statement after #include <dshow.h>, like JeffRSon suggested. However the correct solution is to define STRSAFE_NO_DEPRECATE before #include <strsafe.h>. You can do that by either:

    1. adding #define STRSAFE_NO_DEPRECATE to your code before the #include <dshow.h> statement

    2. adding STRSAFE_NO_DEPRECATE to the Conditionals list in your Project Options.

    This solution is described on MSDN:

    About Strsafe.h

    • When you include Strsafe.h in your file, the older functions replaced by the Strsafe.h functions will be deprecated. Attempts to use these older functions will result in a compiler error telling you to use the newer functions. If you want to override this behavior, include the following statement before including Strsafe.h.

      #define STRSAFE_NO_DEPRECATE
      
    • To allow only character count functions, include the following statement before including Strsafe.h.

      #define STRSAFE_NO_CB_FUNCTIONS
      
    • To allow only byte count functions, include the following statement before including Strsafe.h.

      #define STRSAFE_NO_CCH_FUNCTIONS
      

    Another supported solution is to define NO_DSHOW_STRSAFE before #include <dshow.h> so that it will not include strsafe.h anymore, thanks to this code in dshow.h:

    #ifndef NO_DSHOW_STRSAFE
    #define NO_SHLWAPI_STRFCNS
    #include <strsafe.h>  
    #endif