-1

I need to create a DLL file that can be used for MS Access and other applications that will return a string when fed parameters. I am fairly familiar with MS Access but an absolute novice at C.

Following is the code I am experimenting with. I want to be able to issue a call like getstring(32.1, 123.2, "here", 25) and have it return a string of up to 60 characters in length. The actual code works fine and buf contains the string I want when it's finished running but I am having trouble handing it back to the calling function.

UPDATE: Ok, I've worked out how to create a DLL and run a function from VBA but I am still struggling to understand how to return strings. I think if I can get this to work, I can work out my whole project. By running the following code I can get VBA to return the square of the input number e.g. feed it a parameter of 10 and I get an answer of 100

double _stdcall square(double *x)
{
    return *x * *x;
}

However when I run the following code in Excel and feed it a parameter of "test" all I get back is a square box character.

char _stdcall Boxx(char *x)
{
    return *x;
}

In this case all I want it to return is what I entered. If I can get it to return that I hope to be able to replace that with the actual result. Any suggestions?

char * Getstring(double lat, double lon, char *name, double zoom)
{
    char buf[60] = { '\0' };   // Set the max length of the final link string
    int ret = GenShortDroidMapUrl(lat, lon, zoom, name, buf, sizeof(buf) - 1);
    return buf;
}
Robbo
  • 1
  • 3
  • You cannot return the pointer to a local variable, there are many similar issues on SO. Basically `buf` will cease to exist as soon as you return from the `GetString` function. You could try `static char buf[60]`, that way, `buf` will survive after the return from the function. [This SO question](https://stackoverflow.com/questions/4570366/how-to-access-a-local-variable-from-a-different-function-using-pointers) may help. – Jabberwocky Sep 01 '17 at 10:10
  • That sounds like an idea but I from comments below that introduces restrictions on concurrency and reentrancy. No idea what that means. In this situation I need to be able to make a call from VBA and another system so I will be doing something like myVariable = getstring(32.1,123.2,"here",25 and it should return a string that will be used within VBA. That call may be made once or several times in a loop. Is using a Static going to work? – Robbo Sep 02 '17 at 02:25
  • 1
    I have no idea how you call C functions from VBA. But high level languages generally have their own ideas of strings. You might need to use some special API provided by VBA to create **bindings** for your function. Or VBA might at least have some policy about memory allocation for strings. Have you managed to create any kind of function in C that can be called from VBA? – Fabel Sep 03 '17 at 16:11

3 Answers3

2

In the posted code, buf[] is an automatic variable whose lifetime ends after the Getstring() function has returned. Since buf[] will no longer exist when control of the program has returned to the caller, a pointer to this variable will be invalid after Getstring() has returned.

One solution is to pass an additional argument into the Getstring() function to accept the string, along with a size argument. Since buf will decay to a pointer in the function call, the sizeof operator can't be used in Getstring() to find the size of the array, but buf_sz holds this value:

char * Getstring(char *buf, size_t buf_sz, double lat, double lon, char *name, double zoom)
{
    // buf[] has been zero-initialized in the caller
    int ret = GenShortDroidMapUrl(lat, lon, zoom, name, buf, buf_sz - 1);

    return buf;
}

Another option that does not require changing the function signature is to dynamically allocate storage for the returned string. Again, buf is a pointer to char in Getstring(), so the sizeof expression in GenShortDroidMapUrl() will need to be replaced; this time the constant BUF_SZ has been used here. Note that the malloced memory will need to be freed by the caller later.

#include <string.h>
#define BUF_SZ  60

/* ... */

char * Getstring(double lat, double lon, char *name, double zoom)
{
    char *buf = malloc(sizeof *buf * BUF_SZ);
    memset(buf, '\0', BUF_SZ);

    /* Or use calloc() and avoid the call to memset() */
    // char *buf = calloc(BUF_SZ, sizeof *buf);

    int ret = GenShortDroidMapUrl(lat, lon, zoom, name, buf, BUF_SZ - 1);

    return buf;
}

If Getstring() is part of a library, you need to ensure that the deallocator function matches the allocation functions. That is, there may be problems if the version of malloc() or calloc() that Getstring() is linked against differs from the version of free() that the calling code is linked against. One solution is to provide a deallocation function with the library. This could be as simple as wrapping free() in another function to be used by the caller to ensure that a matching deallocator is used. Here, the function DLL_Free() is part of the DLL, and malloc(), calloc(), and free() would all be linked against the same library when the DLL is created. The caller that uses Getstring() would use DLL_Free() to deallocate. From the caller, free() may not work as expected to deallocate the memory allocated by Getstring(), but DLL_Free() would since this deallocator uses the version of free() that matches the allocators used in the DLL.

/* Deallocation function included in DLL that matches allocation
 * functions used in library
 */
void DLL_Free(void *ptr)
{
    free(ptr);
}
ad absurdum
  • 19,498
  • 5
  • 37
  • 60
  • If I understand this, and subsequent comments correctly I can pass an additional argument into the Getstring() function to accept the string, along with a size argument. E.g. getstrung(32.1,123.4,"name",25,60) where 60 might be the size. The output of this function will always be 39 characters plus the length of name and I planned on imposing a restriction of 20 characters on name anyway so adding the size of the argument in the call seems unnecessary. – Robbo Sep 02 '17 at 02:32
  • Your second suggestion says I can avoid that but the calling function would need to free the space. Given this will be called externally using VBA, does that mean I need to call another function from VBA to free the memory? – Robbo Sep 02 '17 at 02:32
  • Regarding whether you should pass a size to `Getstring()` with the first method, I would think that you should with a library function. This is generally considered a good practice in any case. Concerning your second comment about deallocation, I am updating my answer in this regard right now.... – ad absurdum Sep 02 '17 at 02:35
  • @Robbo-- I have added some comments about deallocation to my answer; I'll have to admit that this issue with writing libraries is not something that I am all that familiar with. Let me know if you have more questions; [here is a link](https://stackoverflow.com/questions/13625388/is-it-bad-practice-to-allocate-memory-in-a-dll-and-give-a-pointer-to-it-to-a-cli) to another SO question that discusses the issue a bit. – ad absurdum Sep 02 '17 at 02:46
  • @Robbo-- in answer to your comments, the `Getstring()` function that allocates its own memory returns a pointer to this memory. When the calling program wants to deallocate, `DLL_Free()` is called. So you might have `char *str = Getstring(32.1, 123.2, "here", 25);` to obtain the string, and later `DLL_Free(str);` to deallocate. [Here is another article](https://blogs.msdn.microsoft.com/oldnewthing/20060915-04/?p=29723/) about the issue with allocators in libraries. – ad absurdum Sep 02 '17 at 04:57
  • I am trying to work with your first option but with the combination of a foggy head due to having a bad cold and having virtually no knowledge of C code, I am just not getting what call I need to make to generate the result. To make a call to the funciton char * Getstring(char *buf, size_t buf_sz, double lat, double lon, char *name, double zoom) Do I need to specify the value of size_t buf_sz in my call, e.g. Getstring(buf,0,32.123,115.222,"here",24.5)? – Robbo Sep 03 '17 at 02:07
  • @Robbo-- it depends on how your `Getstring()` function is written. If it needs the size of the array, then probably. You could instead use a global constant, like `BUF_SZ` from my second example. In your example call from your last comment, it looks like you have specified a buffer size of 0; I don't know why you would do that.... I don't know what `GenShortDroidMapUrl()` does, but it seems to need the size of the buffer (since the original code had `sizeof (buf) - 1` here). – ad absurdum Sep 03 '17 at 04:34
  • If you are asking what to pass for `buf_sz` in the function that I wrote, if you have `char main_buffer[60] = { '\0' };` in `main()`, then you should call `Getstring(main_buffer, 60, 32.123, 115.222, "here", 24.5);` – ad absurdum Sep 03 '17 at 04:37
1

There are many ways to return a string, but respect the lifetime of buffers:
Can a local variable's memory be accessed outside its scope?

One is to let the caller supply the buffer. Return how much space would have been needed, and a simple comparison will tell you whether it was enough.

Another is to use a static, optionally thread-local, buffer. Beware the restrictions on concurrency and reentrancy.

And finally, you can allocate it dynamically. Remember that it has to be freed with the same system, which on windows often means you have to manually export the way to free it from your DLL. Better not to reinvent the wheel, look at BSTRs for example.

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
  • I think I understand the concept that a variable's value may or may not remain for long outside the scope but I still don't understand how to transfer it. – Robbo Sep 02 '17 at 02:47
  • I want to make a DLL that will work like any other function in VBA. I declare a string variable: "dim myString as string". Then I assign a value to it with "myString = getString(32.1,123.4,"name",25). myString in VBA only lasts inside the scope of the VBA environment. Passing the size required from the calling function seems unnecessary because the output will never be more than 60 characters. And once the value of getString() has been passed back to VBA I am happy for that memory to be freed, but I don't understand how to free it from VBA. So, sadly, I am just not getting this – Robbo Sep 02 '17 at 02:57
0

You could either declare buf static and let the function return const char *. But that would not be reentrant. So another solution is to return strdup(buf), which will return a copy that the caller needs to free after use (otherwise you will have a memory leak).

Fabel
  • 1,711
  • 14
  • 36
  • 1
    static and const are not inter-dependent here. – Deduplicator Sep 01 '17 at 10:29
  • @Deduplicator That's true. I just think it's good practice to use const as a hint to the user of the function that the returned string should not be freed. – Fabel Sep 01 '17 at 11:10
  • What do you mean by reentrant? And how can I free the variable from outside the C environment after it's been called from say VBA? – Robbo Sep 02 '17 at 02:58
  • @Robbo I linked the Wikipedia article which describes reentrancy. In short it means it can handle multiple calls at the same time (for example two both calling the function). I don't know the practice for creating VBA-binding. But then used in C/C++ you call the function `free()` to free the memory allocated by `strdup()`. In Windows this can be a little more complicated thou (see the answer from David Bowling). – Fabel Sep 02 '17 at 10:14
  • @Fabel and @ Deduplicator, Re your suggestion “return strdup(buf)” Does that mean that I should change the function GenShortDroidMapUrl(double lat, double lon, double zoom, char const * name, char * buf, int bufSize) to – Robbo Sep 03 '17 at 02:40
  • @Robbo No, you do not need to change anything else inside the function (but you need to free the memory returned from the function). The second suggestion from David Bowling is actually even better than using `strdup()` (since it writes to the allocated memory directly without copying at the negligible expense of possibly using a few bytes more than necessary ). – Fabel Sep 03 '17 at 15:57