Reputation: 3703
I'm using DllImport to call the functions of C++ from C#.
#if defined(__cplusplus)
extern "C" {
#endif
__declspec(dllexport) int __stdcall ABC(int i);
__declspec(dllexport) char* __stdcall C(int i);
#if defined(__cplusplus)
}
#endif
int __stdcall ABC(int i)
{
return i;
}
char* __stdcall C(int i)
{
char* n = new char[i];
memset(n, 9, i);
return n;
}
Code in C# is:
using System.Runtime.InteropServices;
using System;
namespace DepartmentStore
{
class Exercise
{
[DllImport("library.dll")]
public static extern int ABC(int i);
[DllImport("library.dll")]
public static extern char* C(int i);
static int Main()
{
int k = ABC(10);
byte[] b = C(1024);
return 0;
}
}
}
The function ABC(int i)
is OK, but the function C(int i)
yielded the following error when building:
"Pointers and fixed size buffers may only be used in an unsafe context"
I guess that I didn't understand how to export a pointer return of functions.
May Someone please tell me the correct way C# invokes functions with the return type as a pointer?
Upvotes: 2
Views: 1937
Reputation: 74530
The P/Invoke later doesn't know how to marshal C-style arrays from the unmanaged to the managed side; unlike SAFEARRAY
, there is no information embedded in C-style arrays that the P/Invoke later can use to determine how many bytes should be copied.
Because of this, you'll want to declare your C
function to return an IntPtr
and then call the Marshal.PtrToStrAnsi
method to convert the pointer to a string on the managed side like so:
[DllImport("library.dll")]
public static extern IntPtr C(int i);
static int Main()
{
int k = ABC(10);
IntPtr b = C(1024);
string s = Marshal.PtrToStrAnsi(b);
// Problem: How do you release what is pointed to
// by IntPtr?
return 0;
}
Additionally, you'll have to pass the IntPtr
back to the unmanaged side to deallocate the memory that you allocated with new
; if you don't you'll have a memory leak.
An easier option is to create a managed wrapper for your unmanaged library in C++ that exposes managed functions which make the calls and perform the conversion to a String^
(using marshal_as
) like so:
// compile with: /clr
#include <stdlib.h>
#include <string.h>
#include <memory>
#include <msclr\marshal.h>
using namespace System;
using namespace msclr::interop;
String^ WrappedC(int i) {
// Make the call to the native function.
// Let's store in an auto_ptr to handle
// cleanup when the wrapper is exited.
auto_ptr<char> c(C(i));
// Convert to a managed string and
// return.
return marshal_as<String^>(c.get());
}
If you didn't want to marshal back a string
, and instead marshal back a byte
array, then I'd still recommend the wrapper approach (although in this case, it requires you to have some specific knowledge in order to create the managed array to return):
// compile with: /clr
#include <stdlib.h>
#include <memory>
using namespace System;
using namespace msclr::interop;
String^ WrappedC(int i) {
// Make the call to the native function.
// Let's store in an auto_ptr to handle
// cleanup when the wrapper is exited.
auto_ptr<char> c(C(i));
// Copy the pointer.
char* p = c.get();
// The byte array to return.
// i is the size of the array, as per the call
// to C.
array<byte>^ a = gcnew array<byte>(i);
// Populate.
for (int index = 0; index < i; ++index)
a[index] = (byte) *p++;
// Return the array.
return a;
}
These are better options than handling it in managed code completely, as you don't have to worry about continuously marshalling between managed and unmanaged to handle pointers to memory allocated in the unmanaged space.
Upvotes: 3