locked
In a DLL why should a wstring pointer not work? RRS feed

  • Question

  • Hello,

    MyFunc() does not build and MyFunc1() builds and works. 

    I get the following error for MyFunc():

    error LNK2019: unresolved external symbol _invalid_parameter referenced in function "void * __cdecl std::_Allocate_manually_vector_aligned<struct std::_Default_allocate_traits>(unsigned __int64)" (??$_Allocate_manually_vector_aligned@U_Default_allocate_traits@std@@@std@@YAPEAX_K@Z)
    error LNK2019: unresolved external symbol _CrtDbgReport referenced in function "void * __cdecl std::_Allocate_manually_vector_aligned<struct std::_Default_allocate_traits>(unsigned __int64)" (??$_Allocate_manually_vector_aligned@U_Default_allocate_traits@std@@@std@@YAPEAX_K@Z)

    This failure happens only in the context of a DLL in a regular function call this works fine. Why is that? I guess in a DLL we have to do something more to tell the function about the space available. 

    One more related question. How can one connect the these Linker error back to the source code. The error does not publish the line number nor the names of variable that is causing the error. Is there a way to force VS2019 to give out more helpful error message. 

    Please advice

    thanks

    ananda

    _declspec(dllexport)
    BOOLEAN
    MyFunc(
    	wstring* PErrMsg
    )
    {
        *PErrMsg = L"abcd";
        return TRUE;
    }
    
    _declspec(dllexport)
    BOOLEAN
    MyFunc1(
    	char * PErrMsg1
    )
    {
        sprintf_s(PerrMsg1, 10, "xyz");
        return TRUE;
    }



    Saturday, August 8, 2020 5:39 AM

Answers

  • The issue is so much deeper and I don't think your search on google does this justice.

    The issue comes down to the layout of the std::basic_string class, std::string and std::wstring are defined as:

    using string  = basic_string<char, char_traits<char>, allocator<char>>;
    using wstring = basic_string<wchar_t, char_traits<wchar_t>, allocator<wchar_t>>;

    so yes, it is about the layout of this class. The C++ standard definition of basic_string has changed over time, I think C++11 made copy on write invalid as an example.

    So as to illustrate this problem, suppose we have two source files:

    //source2.cpp
    #pragma pack(push)
    #pragma pack(1)
    struct test
    {
    	short a; //notice this
    	int b;
    	int c;
    };
    #pragma pack(pop)
    
    void set_a_value(test *p)
    {
    	p->a = 20;
    }
    
    void set_b_value(test *p)
    {
    	p->b = 30;
    }
    
    void set_c_value(test *p)
    {
    	p->c = 40;
    }
    #define _CRT_SECURE_NO_WARNINGS
    #include <cstdio>
    
    #pragma pack(push)
    #pragma pack(1)
    struct test
    {
    	int a; //notice this
    	int b;
    	int c;
    };
    #pragma pack(pop)
    
    void set_a_value(test *p);
    void set_b_value(test *p);
    void set_c_value(test *p);
    
    int main()
    {
    	test t{};
    
    	set_a_value(&t);
    	set_b_value(&t);
    	set_c_value(&t);
    
    	printf("a: %d\nb: %d\nc: %d\n", t.a, t.b, t.c);
    
    	return 0;
    }

    This will output:

    a: 1966100
    b: 2621440
    c: 0

    Not the nice 20, 30 and 40 that the source sets it to. So if two bits of code sees a structure or class with different layouts then problems can arise. This goes by the name One Definition Rule.

    So what does this have to do with basic_string? Suppose you have your DLL compiled using Visual C++ 2012 and your application compiled using Visual C++ 2019, do both of these see basic_string with the same layout? The answer is potentially no. Microsoft does not guarantee that the layout of C++ classes will not change between between versions of the compiler. If you read the list of changes between compiler versions, there are always entries stating how the C++ standard library classes have been updated because of bug fixes and conformance. This means that every one of these changes could modify the class layout.

    So what this means is that using a C++ class as a parameter to a DLL function isn't wrong, it is potentially dangerous. If you can guarantee that the DLL will always be built along with the executable with the same compiler version then it is not a problem. If you can't guarantee this then it is better to stick with the C types. I actually have plenty of projects where I use C++ types on the DLL boundary, but I always enforce the rule that the DLLs must be built at the same time as the executable, with the same compiler and using the DLL runtime.

    Also, passing strings as a pointer shouldn't be that much of a problem. If you are having problems then I would suggest that you are missing things.

    There is no harm in providing nicer ways of accessing the DLL functions:

    #include <cstring>
    #include <string>
    
    extern"C"
    {
    	//dll interface
    	void pass_string(const char *str, unsigned int size);
    	void pass_wstring(const wchar_t *str, unsigned int size);
    }
    
    template<unsigned int N>
    inline void pass_string(const char str[N])
    {
    	//remember, the array size includes the null terminator
    	pass_string(str, N - 1);
    }
    
    template<unsigned int N>
    inline void pass_string(const wchar_t str[N])
    {
    	//remember, the array size includes the null terminator
    	pass_wstring(str, N - 1);
    }
    
    inline void pass_string(const char *str)
    {
    	//remember, strlen does not include the null terminator
    	size_t s = strlen(str);
    	pass_string(str, s);
    }
    
    inline void pass_string(const wchar_t *str)
    {
    	//remember, wcslen does not include the null terminator
    	size_t s = wcslen(str);
    	pass_wstring(str, s);
    }
    
    inline void pass_string(const std::string &str)
    {
    	//remember std::basic_string does not include the null terminator
    	pass_string(str.c_str(), str.size());
    }
    
    inline void pass_string(const std::wstring &str)
    {
    	//remember std::basic_string does not include the null terminator
    	pass_wstring(str.c_str(), str.size());
    }

    Each of these functions automatically work out the size of the string and the DLL interface includes the string size.


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.

    Sunday, August 9, 2020 11:16 AM
  • Depending on the situation, the linker may not have any source information. If you are not working with object built using link time code generation, all the linker works with is .obj files and maybe the .pdb files.

    Anyway, the second linker error you mention is always possible if you are mixing runtime libraries. For example:

    C:\Program Files (x86)\Windows Kits\10\Lib\10.0.19041.0\ucrt\x64>dumpbin /exports ucrtd.lib | find "_CrtDbgReport"
                      _CrtDbgReport
                      _CrtDbgReportW

    C:\Program Files (x86)\Windows Kits\10\Lib\10.0.19041.0\ucrt\x64>dumpbin /exports ucrt.lib | find "_CrtDbgReport"

    C:\Program Files (x86)\Windows Kits\10\Lib\10.0.19041.0\ucrt\x64>

    Notice how these symbols are found in ucrtd.lib but not in ucrt.lib? The d signifies that ucrtd.lib is a debug version of the library and this means that it has a lot of extra debugging features. On the other hand, ucrt.lib is not a debug library and doesn't have any of this. If you are trying to build code that uses these debug features and then link it to the non debug version then this kind of linker error is expected. For versions older than VS2015, you can replace ucrt(d).lib with msvcrt(d).lib.


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.

    Sunday, August 9, 2020 11:26 AM
  • Well, the whole set of functions was written to only pass a string into the DLL, not receive one out of it. If you want to obtain a string from the DLL then you have two options.

    1) You return the string literal directly from the function.

    2) You require that the user passes a buffer in.

    If the function is assigning a string literal to a C++ string object, then you can just return it instead and allow a little helper function to create a C++ string object out of this.

    extern "C"
    {
        const char *get_string_literal();
        const wchar_t *get_wstring_literal();
    }
    
    inline std::string get_string()
    {
        return std::string(get_string_literal());
    }
    
    inline std::wstring get_wstring()
    {
        return std::wstring(get_wstring_literal());
    }

    This can be expanded on easily enough. But this also allows you to use it like:

    std::wstring str = get_wstring();
    std::wcout << str;

    So you should really think about the DLL interface and any helper functions.


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.

    Sunday, August 9, 2020 7:09 PM

All replies

  • Did you try these functions in a new “Dynamic-Link Library (Dll)” project, generated by Visual Studio wizards, with untouched default options?

    Saturday, August 8, 2020 8:00 AM
  • Hi @Viorel_. Yes with untouched default options. However the way I initiated the DLL is bit different than the usual way of starting a DLL. This is what I did.

    VS2019 -> Continue without code -> File -> New Project -> Empty Desktop Application for Drivers(Universal) -> Uncheck place solution and project in the same directory 

    This creates a Solution and a application project

    Right Click on Solution -> New Project  -> Empty DLL for Drivers (Universal) -> So this is where I land up. Would this be influencing it? So everything is empty what ever I get by default .... 




    Saturday, August 8, 2020 1:36 PM
  • Today I sat and did a through Google search and the gist is 

    "In the case of a string, I would pass a const char * (or const wchar_t * or const TCHAR *) and do the conversion to std::string or CString on the other side of the interface, within the DLL."

    Basically the operation is not wrong but the very fact of passing wstring as a pointer is. If VS2019 doe snot support it, why does it not give syntax error? The problem with passing a "char" instead of a string we have to deal with buffer size. The whole idea of moving away from char to string is lost .... 

    If there are anyway to overcome this or the info I got from Goolge is incomplete please enlighten me. 

    Sunday, August 9, 2020 4:48 AM
  • The issue is so much deeper and I don't think your search on google does this justice.

    The issue comes down to the layout of the std::basic_string class, std::string and std::wstring are defined as:

    using string  = basic_string<char, char_traits<char>, allocator<char>>;
    using wstring = basic_string<wchar_t, char_traits<wchar_t>, allocator<wchar_t>>;

    so yes, it is about the layout of this class. The C++ standard definition of basic_string has changed over time, I think C++11 made copy on write invalid as an example.

    So as to illustrate this problem, suppose we have two source files:

    //source2.cpp
    #pragma pack(push)
    #pragma pack(1)
    struct test
    {
    	short a; //notice this
    	int b;
    	int c;
    };
    #pragma pack(pop)
    
    void set_a_value(test *p)
    {
    	p->a = 20;
    }
    
    void set_b_value(test *p)
    {
    	p->b = 30;
    }
    
    void set_c_value(test *p)
    {
    	p->c = 40;
    }
    #define _CRT_SECURE_NO_WARNINGS
    #include <cstdio>
    
    #pragma pack(push)
    #pragma pack(1)
    struct test
    {
    	int a; //notice this
    	int b;
    	int c;
    };
    #pragma pack(pop)
    
    void set_a_value(test *p);
    void set_b_value(test *p);
    void set_c_value(test *p);
    
    int main()
    {
    	test t{};
    
    	set_a_value(&t);
    	set_b_value(&t);
    	set_c_value(&t);
    
    	printf("a: %d\nb: %d\nc: %d\n", t.a, t.b, t.c);
    
    	return 0;
    }

    This will output:

    a: 1966100
    b: 2621440
    c: 0

    Not the nice 20, 30 and 40 that the source sets it to. So if two bits of code sees a structure or class with different layouts then problems can arise. This goes by the name One Definition Rule.

    So what does this have to do with basic_string? Suppose you have your DLL compiled using Visual C++ 2012 and your application compiled using Visual C++ 2019, do both of these see basic_string with the same layout? The answer is potentially no. Microsoft does not guarantee that the layout of C++ classes will not change between between versions of the compiler. If you read the list of changes between compiler versions, there are always entries stating how the C++ standard library classes have been updated because of bug fixes and conformance. This means that every one of these changes could modify the class layout.

    So what this means is that using a C++ class as a parameter to a DLL function isn't wrong, it is potentially dangerous. If you can guarantee that the DLL will always be built along with the executable with the same compiler version then it is not a problem. If you can't guarantee this then it is better to stick with the C types. I actually have plenty of projects where I use C++ types on the DLL boundary, but I always enforce the rule that the DLLs must be built at the same time as the executable, with the same compiler and using the DLL runtime.

    Also, passing strings as a pointer shouldn't be that much of a problem. If you are having problems then I would suggest that you are missing things.

    There is no harm in providing nicer ways of accessing the DLL functions:

    #include <cstring>
    #include <string>
    
    extern"C"
    {
    	//dll interface
    	void pass_string(const char *str, unsigned int size);
    	void pass_wstring(const wchar_t *str, unsigned int size);
    }
    
    template<unsigned int N>
    inline void pass_string(const char str[N])
    {
    	//remember, the array size includes the null terminator
    	pass_string(str, N - 1);
    }
    
    template<unsigned int N>
    inline void pass_string(const wchar_t str[N])
    {
    	//remember, the array size includes the null terminator
    	pass_wstring(str, N - 1);
    }
    
    inline void pass_string(const char *str)
    {
    	//remember, strlen does not include the null terminator
    	size_t s = strlen(str);
    	pass_string(str, s);
    }
    
    inline void pass_string(const wchar_t *str)
    {
    	//remember, wcslen does not include the null terminator
    	size_t s = wcslen(str);
    	pass_wstring(str, s);
    }
    
    inline void pass_string(const std::string &str)
    {
    	//remember std::basic_string does not include the null terminator
    	pass_string(str.c_str(), str.size());
    }
    
    inline void pass_string(const std::wstring &str)
    {
    	//remember std::basic_string does not include the null terminator
    	pass_wstring(str.c_str(), str.size());
    }

    Each of these functions automatically work out the size of the string and the DLL interface includes the string size.


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.

    Sunday, August 9, 2020 11:16 AM
  • Depending on the situation, the linker may not have any source information. If you are not working with object built using link time code generation, all the linker works with is .obj files and maybe the .pdb files.

    Anyway, the second linker error you mention is always possible if you are mixing runtime libraries. For example:

    C:\Program Files (x86)\Windows Kits\10\Lib\10.0.19041.0\ucrt\x64>dumpbin /exports ucrtd.lib | find "_CrtDbgReport"
                      _CrtDbgReport
                      _CrtDbgReportW

    C:\Program Files (x86)\Windows Kits\10\Lib\10.0.19041.0\ucrt\x64>dumpbin /exports ucrt.lib | find "_CrtDbgReport"

    C:\Program Files (x86)\Windows Kits\10\Lib\10.0.19041.0\ucrt\x64>

    Notice how these symbols are found in ucrtd.lib but not in ucrt.lib? The d signifies that ucrtd.lib is a debug version of the library and this means that it has a lot of extra debugging features. On the other hand, ucrt.lib is not a debug library and doesn't have any of this. If you are trying to build code that uses these debug features and then link it to the non debug version then this kind of linker error is expected. For versions older than VS2015, you can replace ucrt(d).lib with msvcrt(d).lib.


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.

    Sunday, August 9, 2020 11:26 AM
  • @Darran Rowe a grateful and BIG thanks for taking the time to go into so much detail and explaining the subtle difference. I actually built your sample stepped through it and understood it fully. I stepped through the disassembly and looked at memory locations to get it fully. To my surprise the compiler does put out a detailed message in their but this message does not make it out to the user.  This message is very similar to what you explained. Thanks now let me go to your next answer and digest that too. Once again thanks. 

    Best Regards

    Ananda

     ................Stack around the variabl
     e '.....' was corrupted.........The vari
     able '..' is being used without being in
     itialized...............................
     ........The value of ESP was not properl
     y saved across a function call.  This is
      usually a result of calling a function 
     declared with one calling convention wit
     h a function pointer declared with a dif
     ferent calling convention...............
     ........................................
     A cast to a smaller data type has caused
      a loss of data.  If this was intentiona
     l, you should mask the source of the cas
     t with the appropriate bitmask.  For exa
     mple:  ...char c = (i & 0xFF);..Changing
      the code in this way will not affect th
     e quality of the resulting optimized cod
     e.......................................
     ........................Stack memory was
      corrupted..............A local variable
      was used before it was initialized.....
     ........Stack memory around _alloca was 
     corrupted...............Unknown Runtime 
     Check Error.............R.u.n.t.i.m.e. .
     C.h.e.c.k. .E.r.r.o.r....... .U.n.a.b.l.
     e. .t.o. .d.i.s.p.l.a.y. .R.T.C. .M.e.s.
     s.a.g.e.................................
     ........R.u.n.-.T.i.m.e. .C.h.e.c.k. .F.
     a.i.l.u.r.e. .#.%.d. .-. .%.s...........
     ........Unknown Filename........Unknown 
     Module Name.....Run-Time Check Failure #
     %d - %s.........Stack corrupted near unk
     nown variable...........%.2X ...Stack ar
     ea around _alloca memory reserved by thi
     s function is corrupted.................
     ....> ...Data: <.........Allocation numb
     er within this function: ...............
     .Size: ..........Address: 0x............
     Stack area around _alloca memory reserve
     d by this function is corrupted.........
     ........%s%s%p%s%zd%s%d%s%s%s%s%s.......
     A variable is being used without being i
     nitialized..............ø£*|ö....¤*|ö...
     P¤*|ö...p¤*|ö...¨¤*|ö...........Stack po
     inter corruption........Cast to smaller 
     type causing loss of data...............
     Stack memory corruption.........Local va
     riable used before initialization.......

    Sunday, August 9, 2020 2:44 PM
  • @Darran Rowe,

    Sorry for bugging you again but I could not use the helping inline functions you gave. I know I am wrong but my suspicion is either there is typo or one crucial declaration is missing in the your list, if I am wrong pardon my ignorance and help out. 

    Here is my sample code please edit it so that I can pass a wstring as a pointer with your inline functionality. Please note I will be providing only the lib, DLL and Driver and users have to write the app. So as you said earlier the user might use a different VS than I am using now which is VS2019. I plan to  provide documentation and sample code to the user along with my binaries. 

    Thanks again

    // In the C++ app 
    wstring errMSg;
    MyFunc(&errMsg);
    wcout << errMsg;
    
    // Should print abcd
    ********************************************
    // In the C++  DLL
    _declspec(dllexport)
    BOOLEAN
    MyFunc(wstring* PErrMsg)
    {
        *PErrMsg = L"abcd";
        return TRUE;
    }

    Sunday, August 9, 2020 4:25 PM
  • @Darran Rowe,

    Sorry for bugging you again but I could not use the helping inline functions you gave. I know I am wrong but my suspicion is either there is typo or one crucial declaration is missing in the your list, if I am wrong pardon my ignorance and help out. 


    What does "could not use" really mean?  Did you get an error when compiling?  When linking?  Did you execute?  What happened that was different from what you expected?  Have you tried to step through the code with the debugger?
    Sunday, August 9, 2020 5:51 PM
  • Well, the whole set of functions was written to only pass a string into the DLL, not receive one out of it. If you want to obtain a string from the DLL then you have two options.

    1) You return the string literal directly from the function.

    2) You require that the user passes a buffer in.

    If the function is assigning a string literal to a C++ string object, then you can just return it instead and allow a little helper function to create a C++ string object out of this.

    extern "C"
    {
        const char *get_string_literal();
        const wchar_t *get_wstring_literal();
    }
    
    inline std::string get_string()
    {
        return std::string(get_string_literal());
    }
    
    inline std::wstring get_wstring()
    {
        return std::wstring(get_wstring_literal());
    }

    This can be expanded on easily enough. But this also allows you to use it like:

    std::wstring str = get_wstring();
    std::wcout << str;

    So you should really think about the DLL interface and any helper functions.


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.

    Sunday, August 9, 2020 7:09 PM
  • @Barry-Schwarz all the while my mind set was to receive a ErrorMessage from the DLL into the app. So I was trying in that context and was not even getting it to compile. However now @Darran Rowe has given both sides of the story and I hope that will solve my issues. Thanks

    regards

    ananda

    Sunday, August 9, 2020 9:05 PM
  • Thanks you @Darran Rowe I am using your second option.

    2) You require that the user passes a buffer in.

    That is where I started from :). However I know the full reasoning behind what this is so and how one got to be careful when dealing with components built with different VS versions. How to get more Linker error. So all mystery solved. 

    Thank you very much

    regards

    ananda

    Sunday, August 9, 2020 11:15 PM