Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can Julia free unmanaged memory allocated in a native DLL? #10

Open
kyle-github opened this issue Oct 31, 2020 · 5 comments
Open

Can Julia free unmanaged memory allocated in a native DLL? #10

kyle-github opened this issue Oct 31, 2020 · 5 comments

Comments

@kyle-github
Copy link
Member

Sorry guys, dumb question: I am really close to figuring out how to dispose of C-style string data allocated in the native DLL for Java and I have been poking around a bit but my Google fu is weak today; can Julia do this too?

The reason I am asking is that I think I can make a C string API that is much, much easier to use than what I came up with before. But it requires that I return a newly allocated C string. Here is what the new string API would look like:

char *plc_tag_get_string(int32_t tag_id, int string_start_offset);
int plc_tag_set_string(int32_t tag_id, int string_start_offset, const char *string_val);
int plc_tag_get_string_capacity(int32_t tag_id, int string_start_offset);
int plc_tag_get_string_total_length(int32_t tag_id, int string_start_offset);

It is that first function that is the problem child. I have to allocate memory in order for this to really be easy to use. But then the caller needs to free it. I am about 99% certain that I have figured out how to do this in Java, I want to make sure that it can be done in Julia.

From what I can see of Julia's FFI, it looks like you just call free more or less directly once the string is converted to a Julia string.

@krrutkow
Copy link
Contributor

Yes, we could simply call free on the pointer in Julia. However, good C etiquette (and best portability/compatibility/stability) for using malloc/free would be to pair their usage either in the library or in the calling code. Having the calling code manage memory allocation can result in higher performance (probably not a concern here though) since a single string buffer could be reused for successive calls.

The approach that I would recommend is to implement malloc/free pairing within the plc_tag_get_string function itself and to only reallocate the buffer when necessary. Something like this:

char *plc_tag_get_string(int32_t tag_id, int string_start_offset) {
   static size_t len = 0;
   static char *buf = NULL;

   size_t nameLen = length_of_tag_name(tag_id, string_start_offset);
   if (!buf || nameLen > len) {
      if (buf)
         free(buf);
      len = nameLen;
      buf = malloc(len*sizeof(*buf));
   }
   // fill buf with the string contents
   return buf;
}

or if implementing in C++

namespace "C" char *plc_tag_get_string(int32_t tag_id, int string_start_offset) {
   static std::string result;
   result = get_tag_name(tag_id, string_start_offset);
   return result.c_str();
}

The buffer will be valid for the calling code to use until the next time plc_tag_get_string is called, and memory management is hidden. This function is not re-entrant though, so it will not work across multiple threads without synchronization. If that is a concern, then having the calling code provide a buffer or call a dedicated plc_tag_free_string function are better approaches and will allow for future implementation changes that the current approach does not.

@kyle-github
Copy link
Member Author

Hi Keith,

Hmm, that is an interesting idea. It also turns out that in C#/.Net this is difficult to do as well.

I think the above idea may not work because tags are explicitly usable between threads and I think this would cause problems. Perhaps there is a trick I can use with thread locals to make this more thread safe?

I will rethink the API. As you note, generally the cleanest way to do this is to have memory managed completely on one side or the other. Something like:

int plc_tag_get_string_length(int32_t tag_id, int string_start_offset);
int plc_tag_get_string(int32_t tag_id, int string_start_offset, char *buffer, int buffer_size);
int plc_tag_set_string(int32_t tag_id, int string_start_offset, const char *string_val);
int plc_tag_get_string_capacity(int32_t tag_id, int string_start_offset);
int plc_tag_get_string_total_length(int32_t tag_id, int string_start_offset);

It is only one more function, where you call it to get the string length. Then you pass a buffer in which to copy the string along with a size. Or I could have you just call the capacity function to get the maximum capacity of the string (i.e. 82 on most AB PLCs). You allocate that and then you get the length as the result of plc_tag_get_string()...

What is most ergonomic for you?

@kyle-github
Copy link
Member Author

Or...

Perhaps put all the memory handling in the wrapper?

int plc_tag_get_string(int32_t tag_id, int string_start_offset, char *buffer, int buffer_size);
int plc_tag_set_string(int32_t tag_id, int string_start_offset, const char *string_val);
int plc_tag_get_string_capacity(int32_t tag_id, int string_start_offset);
int plc_tag_get_string_total_length(int32_t tag_id, int string_start_offset);

First you call plc_tag_get_string_capacity() to get the maximum capacity. Then you create a byte buffer sufficiently large to handle that. Then you call plc_tag_get_string() with that buffer. You get back a value that is the length of the string, or a negative number that is an error (i.e. PLCTAG_ERR_TOO_SMALL if there is insufficient space). This is most consistent because you need to do a similar thing when you want to write a string.

Would that work for you?

@krrutkow
Copy link
Contributor

First you call plc_tag_get_string_capacity() to get the maximum capacity. Then you create a byte buffer sufficiently large to handle that. Then you call plc_tag_get_string() with that buffer. You get back a value that is the length of the string, or a negative number that is an error (i.e. PLCTAG_ERR_TOO_SMALL if there is insufficient space).

Yes, this is precisely the kind of API I was thinking when I said:

... a single string buffer could be reused for successive calls.

And it will work fine for the Julia wrapper!

@kyle-github
Copy link
Member Author

I will aim for something close the last API proposed above. Might take a while as I am still fighting Java and Android and have very, very little time due to work right now. Sorry for the delays!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants