So after a lot of problems finding a proper way to make a way for my different processes to communicate, I cobbled together this:
public const uint HWND_BROADCAST = 0xffff;
[DllImport( "user32" )]
public static extern bool SendMessage(IntPtr hwnd, IntPtr msg, IntPtr wparam, IntPtr lparam);
[DllImport( "user32" )]
public static extern int RegisterWindowMessage(string message);
public static readonly uint WM_COPYDATA = (uint)RegisterWindowMessage( "WM_COPYDATA_DESKTOP_TODOLIST_9234892348932GIBBERISH" );
Which is used in the following way:
public static void SendMessage(string Message) {
SendMessage( (IntPtr)HWND_BROADCAST, (IntPtr)WM_COPYDATA, (IntPtr)Rawify( ref Message ), (IntPtr)Message.Length );
}
// Preparing the message for pointer-sending
public static IntPtr Rawify(ref string target) {
char[] CHARS = target.ToCharArray();
// Copy string to raw memory block of bytes
int count = target.Length;
IntPtr P = Marshal.AllocHGlobal( 2 * count );
for (int I = 0; I < count; I++) { Marshal.WriteInt16( (IntPtr)((int)P + I * 2), target[I] ); }
return P;
}
public static string Cook(IntPtr target, IntPtr length) {
int count = (int)length;
char[] CHARS = new char[count];
// Copy raw memory to char array->string
for (int I = 0; I < count; I++) { CHARS[I] = (char)Marshal.ReadInt16( (IntPtr)((int)target + I * 2) ); }
return new string( CHARS );
}
}
And on the receiving end
// Receiver for alternate applications
protected override void WndProc(ref Message m) {
if (m.Msg == Native.Messaging.WM_COPYDATA) { MessageBox.Show( "[" + Native.Messaging.Cook( m.WParam, m.LParam ) + "]" ); }
base.WndProc( ref m ); // Default handling
}
Now, all is peachy and I've tested thoroughly;
What happens is I've borrowed SendMessage from Runtime.InteropServices.
The message is sent successfully across.
However, it's limited to two pathetic IntPtr's.
So I decided the best thing to do is sent over a pointer to some unmanaged memory, and the size of that memory.
The sending is successful, however in the two different program instances, the pointer, apparently, doesn't point to the same place. It looks like the IntPtr that Marshal.AllocHGlobal is some arbitrary local pointer relevant only the current program, thus giving me exceptions for read/write access violations, or some random nulls, or some random other characters, and at some point I even sampled a random "l32.dll" string from somewhere. Spooky!
I only, and simply want to know how to get a Pointer, that would point to the SAME DATA, across two different applications. I'm so clueless where to even start searching for this answer, hence why I ask here.
Is there a way to get that pointer with Marshals, or I need something else.
I can figure out the details, I just need to be let known about some Pointer, that points to the same data if used in multiple programs.
Alternatively, if what I'm doing is grotesquely horrible and a bad idea, I'd like to be informed of alternatives, so long as they are not: Named pipes, RPC, Memorymapped, Files, Mutex; because, for reasons, I'm working in .NET 2.0, and those are not available there; And Mutex doesn't transfer data; And I like my hard disk nice, clean, un-busy and in pristine condition.
Again, Reminder: The goal is to communicate DATA of ANY TYPE and ANY SIZE (I can accept some limits), BETWEEN multiple INSTANCES of the same PROGRAM, or possibly other PROGRAMS, that is an .EXE in its own ApplicationDomain and etc.
Forgot to mention, this has to be in .NET 2.0, and all I need is a POINTER of some kind that points to the SAME DATA between multiple applications.
I'm guessing you'd either need to OpenHandle on the source process and ReadProcessMemory from there (since the memory address you're broadcasting is local to the application), or use memory-mapped files or shared memory space. Either way you are digging into Win32 P/Invoke land as neither is available fully managed on .NET 2.0.
Edit: It appears you edited your question to remove these as viable candidates. You're asking for methods of IPC while excluding all methods of IPC (without a reason).
Wow! Why you don't use WCF to comunicate between process via IPC?
http://tech.pro/tutorial/855/wcf-tutorial-basic-interprocess-communication
http://dotnet-experience.blogspot.com.es/2012/02/inter-process-duplex-communication-with.html
Related
I have built a C# wrapper for a commercial third-party DLL written in C++. What I've got works, but it does not perform as fast as expectations. The company that make the DLL also use it to power a piece of their own software: mine runs at about a sixth of the speed using the wrapper.
To improve performance I used a profiler to find the bottlenecks. This suggested that about 80% of the runtime was taken up in making calls direct to the third-party DLL. So, for example this function, which just returns an integer:
[DllImport("MyDllPath.dll", SetLastError = true, CharSet = CharSet.Ansi)]
public static extern int QABatchWV_FormattedLineCount(int handle, ref int count);
private int LineCount(int searchHandle)
{
int layoutLineCount = 0;
int layoutLineCountReturn = QABatchWV_FormattedLineCount(searchHandle, ref layoutLineCount);
return layoutLineCount;
}
Takes about 25% of the processing time. While this one:
[DllImport("MyDllPath.dll", SetLastError = true, CharSet = CharSet.Ansi)]
public static extern int QABatchWV_Clean(int handle, StringBuilder searchString, ref int searchHandle, StringBuilder returnPostcode, int postcodeLength, StringBuilder isoCode, StringBuilder returnCode, int returnLength);
private int AddressClean(StringBuilder returnPostcode, StringBuilder countryISO, StringBuilder returnCode, StringBuilder inputAddress, out int searchHandle)
{
searchHandle = 0;
int searchResult = 0;
searchResult = QABatchWV_Clean(ApiHandle, inputAddress, ref searchHandle, returnPostcode, 20, countryISO, returnCode, 256);
return searchResult;
}
Takes about 35%.
The handle in the code suggests the dll has an initialisation overhead. However, I've tried to engineer my code to minimize this. My calling code sets up a batch of "pre-initalised" DLL threads, each with their own handle. It then uses these for the run before closing them all down. The DLL seems to have been built to pass handles around freely: so, you get a handle when you start a session, pass that down into a clean function to use that session to clean which in turns gives you "clean" handle which you then pass back to the DLL to get the line count and so on.
I'm new to working with unmanaged code and I'm at a loss as to what - if anything - I can do here. Are these sorts of bottlenecks an intrinsic cost of passing variables between managed and unmanaged code? Or are there any tricks I can employ to speed up the process?
I am trying to create and pass an array of pointers to an unmanaged DLL function using the following C# code.
[DllImport("libantumbra.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern uint AnCtx_Init(IntPtr ctx);
//create context
this.ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(IntPtr)));
AnCtx_Init(ptr);//returns 0 (non-error)
this.ctx = (IntPtr)Marshal.PtrToStructure(ptr, typeof(IntPtr));
[DllImport("libantumbra.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int AnDevice_GetList(IntPtr ctx, out IntPtr outdevs, out int outndevs);
IntPtr devs, ndevs;
AnDevice_GetList(ctx, out devs, out ndevs); //exception occurs here
However upon my last call I receive an AccessViolationException. I think it has to do with the array pointer I am passing however I have not been able to find a solution.
The end goal I am trying to achieve here is to pass a pointer to AnDevice_GetList and with the parameter outdevs be left with an array that has been populated by the DLL.
Let me know if you need any further info or have any ideas for me to try.
Edit:
Here is the function I am trying to call.
Header file:
An_DLL AnError AnDevice_GetList(AnCtx *ctx, AnDeviceInfo ***outdevs,
size_t *outndevs);
typedef struct AnDevice AnDevice;
typedef int AnError;
typedef struct AnCtx AnCtx;
And implementation:
AnError AnDevice_GetList(AnCtx *ctx, AnDeviceInfo ***outdevs, size_t *outndevs)
{
An_LOG(ctx, AnLog_DEBUG, "enumerate devices...");
libusb_device **udevs;
ssize_t ndevs = libusb_get_device_list(ctx->uctx, &udevs);
if (ndevs < 0) {
An_LOG(ctx, AnLog_ERROR, "libusb_get_device_list: %s",
libusb_strerror(ndevs));
return AnError_LIBUSB;
}
AnDeviceInfo **devs = malloc((ndevs + 1) * sizeof *devs);
if (!devs) {
An_LOG(ctx, AnLog_ERROR, "malloc: %s", strerror(errno));
return AnError_MALLOCFAILED;
}
memset(devs, 0, (ndevs + 1) * sizeof *devs);
size_t j = 0;
for (ssize_t i = 0; i < ndevs; ++i) {
libusb_device *udev = udevs[i];
AnDeviceInfo info;
An_LOG(ctx, AnLog_DEBUG, "device: bus %03d addr %03d",
libusb_get_bus_number(udev), libusb_get_device_address(udev));
if (populate_info(ctx, &info, udev))
continue;
An_LOG(ctx, AnLog_DEBUG, "vid 0x%04x pid 0x%04x",
info.devdes.idVendor, info.devdes.idProduct);
if (!match_vid_pid(info.devdes.idVendor, info.devdes.idProduct)) {
An_LOG(ctx, AnLog_DEBUG, " does not match Antumbra VID/PID");
continue;
}
devs[j] = malloc(sizeof *devs[j]);
if (!devs[j]) {
An_LOG(ctx, AnLog_ERROR, "malloc: %s", strerror(errno));
continue;
}
libusb_ref_device(udev);
*devs[j] = info;
++j;
}
libusb_free_device_list(udevs, 1);
*outdevs = devs;
*outndevs = j;
return AnError_SUCCESS;
}
Your unmanaged function is declared like this:
AnError AnDevice_GetList(AnCtx *ctx, AnDeviceInfo ***outdevs, size_t *outndevs)
You should translate that as:
[DllImport("libantumbra.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int AnDevice_GetList(IntPtr ctx, out IntPtr outdevs,
out IntPtr outndevs);
And this is almost exactly as you have done. The only differences are that the return value is int and the outndevs parameter is of type IntPtr. That's because size_t is pointer sized on the platforms that I am aware of.
Call it like this:
IntPtr ctx = ...; // create a context somehow
IntPtr devs;
IntPtr ndevs;
int retval = AnDevice_GetList(ctx, out devs, out ndevs);
if (retval != AnError_SUCCESS)
// handle error
So, where could your code be going wrong? One likely explanation is that the context that you pass is invalid. Another possibility is that you execute 64 bit code and the incorrect size of outndevs in your translation caused the error.
This is a pretty hard API to call using p/invoke. What can you do now with devs. You can copy the values into an IntPtr[] array easily enough. And presumably the library has functions that operate on these opaque device pointers. But you have to keep hold of devs and pass it back to the library to deallocate it. Presumably the library exports a function to do that?
Based on your comments and various updates, it looks like you are not getting a proper context. We can only guess, but I expect that AnCtx_Init is declared as
AnError AnCtx_Init(AnCtx **octx)
That is a pointer to opaque context AnCtx*. Translate that as:
[DllImport("libantumbra.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int AnCtx_Init(out IntPtr octx);
Call it like this:
IntPtr ctx;
int retval = AnCtx_Init(out ctx);
if (retval != AnError_SUCCESS)
// handle error
The big thing that you have to do now is start checking for errors. Unmanaged code won't throw exceptions. You need to do error checking yourself. It is laborious, but it must be done. Take it one function at a time. Once you are sure a function call is working, move on to the next.
Some things don't make sense in your example. You create ptr2 and allocate space for it but never copy anything into that space, and you don't pass it to your AnDevice_GetList function so that seems completely unnecessary. You create ptArray but never use it anywhere either.
In this code, you're creating a managed array of IntPtr structures, and allocating memory for each of them to point to, and the size of what they are pointing to is the size of a single pointer:
IntPtr[] ptArray = new IntPtr[] {
Marshal.AllocHGlobal(IntPtr.Size),
Marshal.AllocHGlobal(IntPtr.Size)
};
To really help we need a clear understanding of exactly what AnDevice_GetList is going to do. If AnDevice_GetList is populating an array of pointers, what do they point to? Do they point to structures that were allocated by AnDevice_GetList? If so, then what you want to do is to create an array of IntPtr and pin it while you make the unmanaged call. Since you're creating an array for the call to fill, do NOT pass the array as an out parameter.
[DllImport("libsomething.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern uint AnDevice_GetList(IntPtr outdevs);
IntPtr[] ptArray = new IntPtr[numberOfPointersRequired];
GCHandle handle = GCHandle.Alloc(ptArray);
try
{
AnDevice_GetList(handle.AddrOfPinnedObject());
}
finally
{
handle.Free();
}
I left off the other parameters, because I have no idea what you're doing with them or how you're expecting them to be handled.
I have searched for days and have tried everything I could find, but still cannot get this to work.
Details:
I have a 3rd party stock trading app that is calling into my unmanaged dll. It is supplying data that the dll processes/filters and then saves into a global ring buffer. The ring buffer is an array of structures, 100 long. All of this runs in the stock trading apps process.
I also have a managed C# app calling into the same dll in a different process that needs to get the info in the global ring buffer as quickly and efficiently as possible. Everything works except that I can only get data for the first structure in the array. Also after the call to the dll from C# the C# code no longer knows that arrayMD is an array of structs, it shows up in the debugger as a simple structure. Could it be the memcpy in the dll causing the problem? I’ve tried all kinds of combinations with [In, Out], IntPtr, and Marchal.PtrToStructure combinations. I am greatly fubar. Any help would be greatly appreciated.
Thanks
Here is what I am attempting.
On the dll side:
struct stMD
{
float Price;
unsigned int PriceDir;
unsigned int PriceDirCnt;
};
// Global memory
#pragma data_seg (".IPC")
bool NewPoint = false; // Flag used to signal a new point.
static stMD aryMD [100] = {{0}};
#pragma data_seg()
void __stdcall RetrieveMD (stMD *LatestMD [])
{
memcpy(*LatestMD, aryMD, sizeof(aryMD));
}
On the C# side:
[StructLayout(LayoutKind.Sequential)]
public struct stMD
{
public float Price;
public uint PriceDir;
public uint PriceDirCnt;
};
public static stMD[] arrayMD = new stMD[100];
[DllImport(#"Market.dll")]
public static extern void RetrieveMD(ref stMD[] arrayMD);
RetrieveMD(ref arrayMD);
The problem is the definition of your DLL entry point:
void __stdcall RetrieveMD (stMDP *LatestMD [])
You don't specify the size of the array, so how is C# supposed to know how many elements were copied into it? This is a problem in other languages too. Your implementation simply assumes that the provided memory is large enough to contain aryMD. But what if it's not? You've just created a buffer overrun.
If you want the caller to allocate the array, then the caller must also pass in the number of elements that the array contains.
Edit
The C++ declaration should look something like this:
// On input, length should be the number of elements in the LatestMD array.
// On output, length should be the number of valid records copied into the array.
void __stdcall RetrieveMD( stMDP * LatestMD, int * length );
The C# declaration would then look something like this:
[DllImport(#"Market.dll")]
public static extern void RetrieveMD(
[In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] ref stMD[] arrayMD,
[In, Out] ref int length);
Your problem, I think, is that you are trying to pass an array by reference from C# into C, and you almost never need to do that. In your case, all you want to happen is for C# to allocate memory for 100 of your structures, then pass that memory to C to be filled in. In your case, you're passing a pointer to an array of structures, which is an extra level of indirection that you don't really need.
You rarely see C functions that take a fixed sized array; instead, your function would typically be defined in C to accept a pointer and a length, e.g. something like:
void __stdcall RetrieveMD (stMDP *LatestMD, int size)
{
int count = 0;
if (size < sizeof(aryMD))
count = size;
else
count = sizeof(aryMD);
memcpy(LatestMD, aryMD, count);
}
To call this method from C#, you need to do two things. First, you need to allocate an array of the appropriate size to pass in. Second, you need to tell the marshaling code (that does the C# -> C copying) how to figure out how much data to be marshaled, via the [MarshalAs] attribute:
[DllImport(#"Market.dll")]
public static extern void RetrieveMD (
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] stMDP[] arrayMD,
int length
);
var x = new stMDP[100];
RetrieveMD(x, 100);
I got it working. Was I ever tring to make this harder than it is.
I re-read chapter 2 of ".NET 2.0 Interoperability Recipes: A Problem-Solution Approach".
Here's what works.
On C++ side I removed the pointers
struct stMD
{
float Price;
unsigned int PriceDir;
unsigned int PriceDirCnt;
};
// Global memory
#pragma data_seg (".IPC")
bool NewPoint = false; // Flag used to signal a new point.
static stMD aryMD [100] = {{0}};
#pragma data_seg()
void __stdcall RetrieveMD (stMD LatestMD [100])
{
memcpy(LatestMD, aryMD, sizeof(aryMD));
}
On the C# side I removed both (ref)s and added [In, Out]
[StructLayout(LayoutKind.Sequential)]
public struct stMD
{
public float Price;
public uint PriceDir;
public uint PriceDirCnt;
};
public static stMD[] arrayMD = new stMD[100];
[DllImport(#"Market.dll")]
public static extern void RetrieveMD([In, Out] stMD[] arrayMD);
RetrieveMD(arrayMD);
Thanks to everyone who offered help.
I'm trying to use the RegisteredWindowMessage API function to send text from one application to another, and I have the following code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;
namespace Common
{
public static class RegisteredMsg
{
private const string MyMessage = "9C7EDA65363F4fdaAF32";
private static IntPtr m_targetWindow = new IntPtr(0xFFFF);
private static object m_object = new object();
private static HandleRef m_handleRef;
private static HandleRef m_handleRef;
public static uint RegisteredMessage
{
get { return m_regMsg; }
private set
{
m_regMsg = RegisterWindowMessage(SynchroMessage);
}
}
//-----------------------------------------------------------------
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Auto)]
static extern bool PostMessage(HandleRef hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
//-----------------------------------------------------------------
[DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Auto)]
static extern uint RegisterWindowMessage(string lpString);
//-----------------------------------------------------------------
static RegisteredMsg()
{
m_handleRef = new HandleRef(m_object, m_targetWindow);
}
//-----------------------------------------------------------------
public static void PostUpdateMsg(string text)
{
IntPtr lpData = Marshal.StringToHGlobalAuto(text);
IntPtr lpLength = new IntPtr(text.Length);
if (!PostMessage(m_handleRef, RegisteredMessage, lpData, lpLength))
{
throw new Exception("Could not post message.");
}
}
//-----------------------------------------------------------------
public static string GetMessageText(Message msg)
{
string text = "";
int length = msg.LParam.ToInt32();
text = Marshal.PtrToStringAuto(msg.WParam, length);
Marshal.FreeHGlobal(msg.WParam);
return text;
}
}
}
Posting the message works find, but when the receiving application calls GetMessageText, the string contains "\0\0\0\0" (which is NOT what the sending application sent).
I'm calling it like this:
RegisteredMsg.PostUpdateMsg("test");
and receiving it like this:
protected override void WndProc(ref Message msg)
{
base.WndProc(ref msg);
if (Convert.ToUInt32(msg.Msg) == RegisteredMsg.RegisteredMessage)
{
string text = RegisteredMsg.GetMessageText(msg);
}
}
EDIT #0
I also tried it this way, and all of the bytes in the received array are '\0':
//-------------------------------------------------------------------------
public static void PostUpdateMsg(string text)
{
byte[] array = StringToByteArray(text);
IntPtr lpData = Marshal.AllocHGlobal(array.Length);
Marshal.Copy(array, 0, lpData, array.Length);
IntPtr lpLength = new IntPtr(text.Length);
if (!PostMessage(m_handleRef, RegisteredMessage, lpData, lpLength))
{
throw new Exception("Could not post message.");
}
}
//-------------------------------------------------------------------------public static string GetMessageText(Message msg)
{
string text = "";
int length = msg.LParam.ToInt32();
byte[] array = new byte[length];
Marshal.Copy(msg.WParam, array, 0, length);
text = RegisteredMsg.ByteArrayToString(array);
return text;
}
EDIT #1
I also called this method from PostUpdateMessage, just to make sure what I was sending was what I thought I was sending:
private static void TestIntPtr(IntPtr ptr, int length)
{
string text = "";
byte[] array = new byte[length];
Marshal.Copy(ptr, array, 0, length);
text = ByteArrayToString(array); // <<------------
}
When the indicated line is executed, the text variablle is indeed = "test", so I'm doing it right on the sending side. It looks like the memory is getting cleared before it gets to the receiving application.
EDIT #2
I also tried making the IntPtr (pointing to the string I want to send) global to its parent class to make sure it would live long enough to be viable at the other end. No joy there, either.
EDIT #3
I also reverted back to using the StringToHGlobalAuto, and ran the "is it still okay in the sending app" test (see Edit #1 above), and that test proved that the way I was building the IntPtr was fine as well.
This cannot work by design. A pointer is only valid in the process that created it. Every process gets its own chunk of virtual memory. Retrieving the pointed-to memory content requires ReadProcessMemory(). Or you can allocate memory in the target process with VirtualAllocEx() and write to it with WriteProcessMemory(). Windows supports the WM_COPYDATA message to take care of this for you.
This is all rather low-level and painful. There are much better IPC mechanisms available. The ones that work well in .NET are sockets, pipes, WCF.
The PostMessage() manual states;
The system only does marshalling for system messages (those in the
range 0 to (WM_USER-1)). To send other messages (those >= WM_USER) to
another process, you must do custom marshalling.
As far as I understand, you're sending a pointer allocated on the local unmanaged heap and a length to a separate process, where the pointer points to something entirely different. The data is not passed along.
For simplicity (ie to avoid custom marshaling), you may want to use WM_COPYDATA instead to pass data between applications.
I think the problem lies in the message passing mechanism itself.
You're copying some data to a pointer (that's pointing to a memory block in the first process) and send this pointer to another application/process.
When you take the pointer in the other process, the memory location it points to is a block of memory in the other process and it will not contain whatever you've copied there in process 1.
If you want to send anything longer than a pair of ints with windows messages, you should look at WM_COPYDATA - this message is doing exactly what you want.
I am using DllImport to call method in c wrapper library from my own .net class. This method in c dll creates a string variable and returns the pointer of the string.
Something like this;
_declspec(dllexport) int ReturnString()
{
char* retval = (char *) malloc(125);
strcat(retval, "SOMETEXT");
strcat(retval, "SOMETEXT MORE");
return (int)retval;
}
Then i read the string using Marshall.PtrToStringAnsi(ptr). After i get a copy of the string, i simply call another c method HeapDestroy which is in c wrapper library that calls free(ptr).
Here is the question;
Recently while it is working like a charm, I started to get "Attempted to read or write protected memory area" exception. After a deeper analysis, i figured out, i beleive, although i call free method for this pointer, value of the pointer is not cleared, and this fills the heap unattended and makes my iis worker process to throw this exception. By the way, it is an web site project that calls this method in c library.
Would you kindly help me out on this issue?
Sure, here is C# code;
[DllImport("MyCWrapper.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
private extern static int ReturnString();
[DllImport("MyCWrapper.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
private extern static void HeapDestroy(int ptr);
public static string GetString()
{
try
{
int i = ReturnString();
string result = String.Empty;
if (i > 0)
{
IntPtr ptr = new IntPtr(i);
result = Marshal.PtrToStringAnsi(ptr);
HeapDestroy(i);
}
return result;
}
catch (Exception e)
{
return String.Empty;
}
}
What may be the problem is the underlying C code. You are not adding a NULL terminator to the string which strcat relies on (or checking for a NULL return from malloc). It's easy to get corrupted memory in that scenario. You can fix that by doing the following.
retval[0] = '\0';
strcat(retval, "SOMETEXT");
Also part of the problem is that you are playing tricks on the system. It's much better to write it correctly and let the system work on correctly functioning code. The first step is fixing up the native code to properly return the string. One thing you need to consider is that only certain types of memory can be natively freed by the CLR (HGlobal and CoTask allocations). So lets change the function signature to return a char* and use a different allocator.
_declspec(dllexport) char* ReturnString()
{
char* retval = (char *) CoTaskMemAlloc(125);
retval[0] = '\0';
strcat(retval, "SOMETEXT");
strcat(retval, "SOMETEXT MORE");
return retval;
}
Then you can use the following C# signature and free the IntPtr with Marshal.FreeCoTaskMem.
[DllImport("SomeDll.dll")]
public static extern IntPtr ReturnString();
Even better though. When marshalling, if the CLR ever thinks it needs to free memory it will use FreeCoTaskMem to do so. This is typically relevant for string returns. Since you allocated the memory with CoTaskMemAlloc you can save yourself the marshalling + freeing steps and do the following
[DllImport("SomeDll.dll", CharSet=Ansi)]
public static extern String ReturnString();
Freeing memory doesn't clear it, it just frees it up so it can be re-used. Some debug builds will write over the memory for you to make it easier to find problems with values such as 0xBAADFOOD
Callers should allocate memory, never pass back allocated memory:
_declspec(dllexport) int ReturnString(char*buffer, int bufferSize)
{
if (bufferSize < 125) {
return 125;
} else {
strcat(buffer, "SOMETEXT");
strcat(buffer, "SOMETEXT MORE");
return 0;
}
}
Although memory is allocated by the DLL in the same heap as your application, it MAY be using a different memory manager, depending on the library it was linked with. You need to either make sure you're using the same exact library, or add code to release the memory that the DLL allocates, in the DLL code itself.