Forum Discussion
about XLL memory management
The reason why calling test crashes Excel while calling testd doesn't is related to the difference in their function signatures and how Excel handles them.
When an XLL function is registered with a "Q" signature, Excel expects it to return a pointer to a static XLOPER12 object, which is valid until the next call to the same function. However, your test function allocates a new XLOPER12 object on the heap each time it's called and returns a pointer to that object. This violates Excel's assumption that the returned pointer will point to a static object, which can cause Excel to crash.
On the other hand, when a function is registered with a "Q$" signature, Excel knows that the function is thread-safe and can be called concurrently from multiple threads. Excel will call the function multiple times, and each time it will allocate and return a new XLOPER12 object on the heap. This is exactly what your testd function does, and therefore it doesn't crash Excel.
To fix the crash when calling test, you need to modify it to return a static XLOPER12 object instead of allocating a new object on the heap each time it's called. One way to do this is to declare a static XLOPER12 object inside the test function and return a pointer to that object. Here's an example of how you could modify test:
__declspec(dllexport)
LPXLOPER12 test() {
static XLOPER12 ret = { xltypeInt | xlbitDLLFree, { 10 } };
return &ret;
}
Note that testd is already thread-safe and doesn't need any changes.
- yushangMar 28, 2023Brass Contributor
Rodrigo_ Thanks for reply. I know the static approach and I believe there is a typo in your example, xlbitDLLFree is wrong, which will direct Excel to free (by call back into xlAutoFree12) a static allocated memory. The right one should be.
__declspec(dllexport) LPXLOPER12 test() { static XLOPER12 ret = { xltypeInt, { 10 } }; return &ret; }What I'm curious is how the xlFree (in xlAutoFree12) know whether an LPXLOPER12 is returned by a thread-safe UDF or by a non thread-safe UDF.
Just few minutes ago, I implemented my free logic as follow
void myxlFree(LPXLOPER12 px) { if (px->xltype == xltypeStr) { if (px->val.str) { delete px->val.str; px->val.str = nullptr; } } else if (px->xltype == xltypeRef ) { if(px->val.mref.lpmref) { delete px->val.mref.lpmref; px->val.mref.lpmref = nullptr; } } else if (px->xltype == xltypeMulti) { if (px->val.array.lparray) { auto count = px->val.array.rows*px->val.array.columns; for(unsigned i=0; i < count; i++) { myxlFree(&px->val.array.lparray[i]); } delete px->val.array.lparray; px->val.array.lparray = nullptr; } } }and replace following line
__declspec(dllexport) void WINAPI xlAutoFree12(LPXLOPER12 pxFree) { Excel12(xlFree,nullptr,1,pxFree); delete pxFree; }with
__declspec(dllexport) void WINAPI xlAutoFree12(LPXLOPER12 pxFree) { myxlFree(pxFree); delete pxFree; }test just works so far. But I'm not sure if there are potential issues.
- Rodrigo_Mar 29, 2023Iron Contributor
You are correct about the typo in my previous response. Thank you for pointing that out.
Regarding your question about how xlFree in xlAutoFree12 knows whether an LPXLOPER12 is returned by a thread-safe UDF or a non thread-safe UDF, the answer is that it doesn't.
When xlFree is called, it simply deallocates the memory pointed to by the LPXLOPER12. It is up to the UDF to make sure that it only calls xlFree on memory that was allocated using xl* functions like xlCoerce, xlret, xlAsyncReturn, and xlSet. If the UDF allocated memory using any other means, it must handle the deallocation itself.
In your implementation of myxlFree, you are correctly freeing the memory based on the xltype of the LPXLOPER12. This is a good practice to ensure that the memory is freed correctly for each type of data. Your implementation should work fine as long as you only call it on memory that was allocated using xl* functions.
- yushangMar 29, 2023Brass Contributor
Rodrigo_ Thanks for reply. I would like to summarize the usage of xlbitDLLFree as follow
1. xlbitDLLFree is used only in scenario when UDF return a dynamic allocated XLOPER12
2. For the returned XLOPER12, if xlbitDLLFree is set, Excel will pass it back to xlAutoFree12. if not set there will be a memory leak.
there leaves a question, what will happen if XLL did not export xlAutoFree12?
In parallel, can I summarize the usage of xlbitXLFree as follow?
1. xlbitXLFree is used only in scenario when UDF return a dynamic allocated XLOPER12
2. For the returned XLOPER12, if xlbitXLFree is set, Excel will free it including the XLOPER12 itself (I'm especially caring about this, from the official https://learn.microsoft.com/en-us/office/client-developer/excel/memory-management-in-excel#example, it seems there is little chance I'm right), if not set, there will be a memory leak.
(all the dynamic allocated XLOPER12 mentioned above does not contain static allocated memory, for example, val.str is not static allocated).