1

I have a vendor application that I believe was built using either C or C++, and it has a particular call it can make to a DLL to allow for expansion in functionality. I am trying to work up a POC for that dll, and am working with only this as the specification: extern int __stdcall LIQEXIT3_LoadCustomer(const char* name, char* id);

I should be able to pass in a char * name and then return that same char * back in the id field. Unfortunately, that isn't what is happening. I either get an access violation, or everything works without error, but the calling application doesn't get the new information within the ID field.

I have created the following DLL and header file code: TestDLL.h

#ifdef TestDLL_EXPORTS
#define TestDLL_API __declspec(dllexport)
#else
#define TestDLL_API __declspec(dllimport)
#endif

namespace TestDLL
{
    class TestDLL
    {
    public:
        static int __declspec(dllexport) __stdcall  TestDLL::TestMethod_LoadCustomer(const char* name,  char* id);
    };
}

TestDLL.cpp

// TestDLL.cpp : Defines the exported functions for the DLL application.
//

#include "stdafx.h"

#include "TestDLL.h"
#include <string>

namespace TestDLL{
    extern "C" int __declspec(dllexport) __stdcall  TestDLL::TestMethod_LoadCustomer(const char* name, char* id)
    {
        std::string test(name); 

        if (test.size() <= 8) {
            id = (char*)malloc(strlen(name)+1);
            strcpy(id, name);           // name contains customer id
        } else {
            id[0] = 0;                  // Customer not found
        }

      return 0;
    }
}

When I do a simple call to the TestMethod_LoadCustomer method and I have the malloc for the id field in the code, everything seems to progress fine, but my calling code does not get the updated id field. If I remove the malloc call, I get an access violation. I believe the violation is due to the fact that the id field will be placed into read only memory and cannot be directly overwritten.

How do I code this so that the calling application receives the updated values for the string in the id field?

Here is the test app code I put together, which I would expect to take the name "777777" and move it to the id field so that it can be printed to the console: RunTest.cpp

// RunTest.cpp : Defines the entry point for the console application.
//
#ifdef TestDLL_EXPORTS
#define TestDLL_API __declspec(dllexport)
#else
#define TestDLL_API __declspec(dllimport)
#endif

#include "stdafx.h"
#include <iostream>
#include "TestDLL.h"

using namespace std;
int _tmain()
{
    char* id= "";
    TestDLL::TestDLL::TestMethod_LoadCustomer("777777", id);
    cout << id << "\n";
    cout <<"This is a Test\n";
    cin >> id;
    return 0;
}

Thanks for taking the time to take a look at my issue!

James M
  • 18,506
  • 3
  • 48
  • 56
cdd
  • 45
  • 8
  • So `LIQEXIT3_LoadCustomer` is like `strcpy`? – Fiddling Bits Aug 14 '14 at 19:49
  • You're leaking memory. the target of your `strcpy` is a local allocation that is subsequently leaked as soon as the function exits. You also invoke *undefined behavior* if the `name` passed is longer than 8. The path taken invokes `id[0] = 0`, which writes to a string literal read-only memory area. `char* id= ""` – WhozCraig Aug 14 '14 at 19:52
  • @FiddlingBits This is a proof of concept, so yes, it is just like strcpy. – cdd Aug 14 '14 at 21:31

3 Answers3

1

You need to change the second argument type to char *&id; when you do id = ... inside TestMethod_LoadCustomer, you only change the local version of id, not the id variable outside.

Adding the & in the parameter makes id a reference to a char pointer, which means id from _tmain will be passed by reference, and your code will work.

Drew McGowen
  • 11,471
  • 1
  • 31
  • 57
  • I completely agree, but unfortunately, this is the spec from the vendor. I think the actual spec will be something along those lines, unfortunately, those I am dealing with either do not understand how the code is actually working or this is just some really odd programming. – cdd Aug 14 '14 at 21:32
0

You are modifying the value of the pointer in your DLL. Since the pointer id is not passed as a reference to a pointer char *&id, the changes made to the value of the pointer will not be reflected outside of the scope of the TestMethod_LoadCustomer function. I would also argue that allocating memory in a dll to be used across modules is a bad idea because then you have to make sure that both your program and the dll are compiled with the same C++ runtime. A safer approach would be to pass an allocated buffer to the function and let your calling program handle the the memory management.

Take a look here for some arguments of why you should keep ownership of resources in the calling code.

Community
  • 1
  • 1
Beed
  • 460
  • 3
  • 10
0

There's some issues in your code. One small issue is that you are mixing C and C++ code, it works, but can be confuse to understand. i.e.: You use std classes/objects, but you use c string functions (strcpy) and malloc function.

When you call your function with your id param, it's passing your pointer, that's pointing to a constant value.

i.e.: In the main function the value of id (that means, the address it's pointing) is 0x123. but then, inside the DLL function you allocate memory for id, so now the id inside that function is pointing to a different address (i.e: 0x456)

Then when your program return to the main function id is still pointing to the old address, because the changes inside the function don't reflect outside (as you changed to where it's pointing e not the value).

To make it work as you expect you need to make sure that both in the main and in the function id is pointing to the same address, and that's where you want to change the value.

To achieve that you can use i.e: a pointer to a pointer or a referente to a pointer as the id, and then you can allocate the value and copy.

An ugly (but functional) version of your code would be:

namespace TestDLL{
    extern "C" int __declspec(dllexport) __stdcall  TestDLL::TestMethod_LoadCustomer(const char* name, char* id)
    {(char* name, char** id) {
        std::string test(name);

        if (test.size() <= 8) {
            *id = (char*)malloc(strlen(name)+1);
            strcpy(*id, name);           // name contains customer id
        } else {
            id[0] = 0;                  // Customer not found
        }

      return 0;
}

and then you can call passing the address of the pointer:

int _tmain()
{
    char* id= "";
    TestDLL::TestDLL::TestMethod_LoadCustomer("777777", &id);
    cout << id << "\n";
    cout <<"This is a Test\n";
    cin >> id;
    return 0;
}

But I strongly recommend that you change your code to C or C++, not both, and don't forget that _tmain owning the id pointer, so you need to free the memory when you don't need it anymore.

dfranca
  • 5,156
  • 2
  • 32
  • 60