i have c++ type definition like this
typedef void* ScreenNode;
and use it in my c++ code like this
ret = InitNode("factory", &g_screen_node, 128, 128, EN_C1Dev);
**EN_C1Dev is a ENUM
my typedef variable is a void pointer and InitNode return a value by changing pointer value "g_screen_node".
now i use this function in a dll inside a C# program with this definition
c++ code
extern "C" __declspec(dllexport) int CB_initNode(ScreenNode* p_g_screen_node,int width,int heigth,EN_DevType EN_C1Dev)
{
int ret = 0;
//InitNode ----------------------------------------------------------------
ret = InitNode("factory", p_g_screen_node, 128, 128, EN_C1Dev);
return ret;
}
dll definition class
[DllImport(dllName)]
public static extern
int CB_initNode(out IntPtr g_screen_node, int width, int heigth, EN_DevType EN_C1Dev);
and finally my main program
IntPtr g_screen_node = new IntPtr();
ret = BBIA.CB_initNode(out g_screen_node , 128, 128, EN_DevType.EN_C3Dev);
the CB_initNode function return error code and g_screen_node not return Expected value and stay on 0x00000000!
Answering the parts of your question in order:
You should consult the documentation (or source code, if no documentation is available) of InitNode for the meanings of the input and output arguments of InitNode. Most likely the method actually allocates and initializes a ScreenNode and returns an opaque pointer to the caller -- one whose internal implementation is hidden. However, there is not enough information in your question to determine this for certain.
My answers to the remaining parts of your question assume that this is true. If not, please update your question with appropriate documentation from InitNode, then we can take another cut at answering.
c++ code. You need to decorate your function with extern "C" __declspec(dllexport):
extern "C" __declspec(dllexport) int CB_initNode(ScreenNode *p_g_screen_node,int width,int heigth, EN_DevType EN_C1Dev)
{
int ret = 0;
//InitNode ----------------------------------------------------------------
ret = InitNode("factory", p_g_screen_node, 128, 128, EN_C1Dev);
return ret;
}
You also need to pass in a pointer to a ScreenNode (i.e. a double pointer) so that the pointer value set in InitNode() can be communicated out.
And then in your c# extern declaration, you define the returned pointer as an out IntPtr:
[DllImport(dllName, CallingConvention = CallingConvention.Cdecl)]
public static extern int CB_initNode(out IntPtr g_screen_node, int width, int heigth, EN_DevType EN_C1Dev);
Then in the main program:
IntPtr g_screen_node;
var ret = BBIA.CB_initNode(out g_screen_node, 128, 128, EN_DevType.EN_C3Dev);
Then later, if you need to pass a ScreenNode back to the same library, you would use the IntPtr previously returned. And when done with it, don't forget to free it -- presumably the library you are using has a method for that also.
Finally, you don't state what EN_DevType is, but I am guessing that it is an enum. If so, you should check you have the correct values. If not an enum, then my answer to your question might need to be updated.
Update
If InitNode needs to know the current value of the incoming void ** pointer because it is preallocated or preinitialized, the signatures would be as follows:
[DllImport(dllName, CallingConvention = CallingConvention.Cdecl)]
public static extern int CB_initNode(ref IntPtr g_screen_node, int width, int heigth, EN_DevType EN_C1Dev);
IntPtr g_screen_node = // However you initialize are supposed to allocate it using your library, possibly just IntPtr.Zero.
var ret = BBIA.CB_initNode(ref g_screen_node, 128, 128, EN_DevType.EN_C3Dev);
In order for us to answer in confidence rather than guessing, please update your question with the documentation for InitNode, and let us know what functions you are calling before the call to InitNode, if any.
&g_screen_node is used to pass the address of g_screen_node to the InitNode function. This is usually means the parameter will be used as an output parameter.
As to why you're getting an error code, I'll take a stab and say your call to BBIA.CB_initNode needs &ab, not ab
Related
I have an exported function in unmanaged C++ code that expects a pointer to a BStr, where it will write some text data (258 bytes, max)
extern "C" __declspec(dllexport)
int CppFunc(BSTR *data)
{ ... }
I want that data as a string.
This works
[DllImport( ... CallingConvention = CallingConvention.Cdecl)]
public static extern int CppFunc([MarshalAs(UnmanagedType.BStr)] ref string data);
but it creates a memory leak.
I assume what I should be doing is creating and passing an IntPtr, then Marshal out the Bstr as a string, then free the IntPtr:
IntPtr p = Marshal.AllocHGlobal(512);
CppFunction(p);
string data = Marshal.PtrToStringBSTR(p);
Marshal.FreeHGlobal(p) ;
The problem is, with that code, I get a System.AccessViolationException on the call into Marshal.PtrToStringBSTR(p).
What am I doing wrong?!
The first line of the Remarks for Marshal.PtrToStringBSTR is
Call this method only on strings that were allocated with the unmanaged SysAllocString and SysAllocStringLen functions.
Which is probably where your crash came from.
Add to this your C++ function expects BSTR* (effectively a pointer to a pointer to the first character of data in the string), but you pass it a pointer to data.
Remember that a BSTR has a special structure: it starts with 4 bytes of length, then data, then a null. The pointer points to the first character of data. So Marshal.PtrToStringBSTR is looking backwards from the pointer to find the length of the string - but that isn't memory which was allocated by Marshal.AllocHGlobal.
It could be that your C++ function does something like *data = ....AllocSysString(); - that is, it never reads the string it's given, but instead assigns the pointer to a string which it allocates.
In that case, you probably want something like:
[DllImport( ... CallingConvention = CallingConvention.Cdecl)]
public static extern int CppFunc(out IntPtr data);
...
CppFunc(out IntPtr p);
string data = Marshal.PtrToStringBSTR(p);
Marshal.FreeBSTR(p) ;
Here, we pass it a pointer to a pointer. The C++ function re-assigns the pointer to point to the first character of data in a BSTR, and we use that to deserialize the BSTR, and then free it (using a method which knows how to free BSTRs).
If this isn't the case, it's unclear why your C++ function takes a BSTR* (as opposed to a BSTR), and what it does with it. I think we need to see that before much else can be said.
If your C++ function took a BSTR instead (remember that BSTR is itself a pointer), then what you should be doing is using a StringBuilder (with a particular initial capacity) - the marshalling layer turns that into a pointer the C++ code can write to, and then you can turn the StringBuilder into a string.
[DllImport( ... CallingConvention = CallingConvention.Cdecl)]
public static extern int CppFunc([MarshalAs(UnmanagedType.BStr)] StringBuilder data);
...
var data = new StringBuilder(512);
CppFunction(data);
string result = data.ToString();
I am trying to call a C++ method from C#. Unfortunately, the documentation for the C++ library is pretty skimpy, but I have access to a header file that defines the methods, so.
The C++ declaration is:
DWORD FAR PASCAL EXPORT Init(int numOfChannels,int startLead,int smpRate,double* pMV_in_1_NUM,int mode = 0);
The documentation unfortunately only gives a C++ example for using this method, which is:
Init (8, 1, 500, &pMV_in_1_NUM, 1);
But I'm trying to call it from C#, so my code is:
[DllImport("NVECGUSB.dll", EntryPoint = "Init")]
[return: MarshalAs(UnmanagedType.U4)]
private static extern UInt32 Init(
int numOfChannels,
int startLead,
int smpRate,
[param: MarshalAs(UnmanagedType.R8)]
ref double pMV_in_1_NUM,
int mode
);
and then...
double pmvIn1Num = 0;
resultCode = Init(8, 1, 500, ref pmvIn1Num, 1);
Console.WriteLine("Init returned {0}", resultCode.ToString("x"));
It doesn't crash or anything, but the resultCode is indicating that the parameters are incorrect, even though they values are correct according to the documentation, so I'm guessing there's something wrong with how I'm passing that double * parameter. (I'm willing to accept that I'm wrong there but right now I can't think of another reason for the error)
Note that I've also tried
[DllImport("NVECGUSB.dll", EntryPoint = "Init")]
[return: MarshalAs(UnmanagedType.U4)]
private static extern UInt32 Init(
int numOfChannels,
int startLead,
int smpRate,
double[] pMV_in_1_NUM,
int mode
);
and
double[] pmvIn1Num = new double[] { 0 };
resultCode = Init(6, 1, 250, pmvIn1Num, 1);
because Google searches throw up results about arrays of double, but I'm not sure this is applicable in my scenario.
So my question is, am I doing it correctly?
(Note that this relates to the Norav 1200M ECG device so if someone has worked wiht that and can tell me what I'm doing wrong, that would be great too)
In summary:
I`m trying to use a C++ dll with cdecl calling convention all ran fine unless i get to this method signature:
int SaveToBuffer( char **buf, int *buf_size );
from what i have read i should use it like this:
[DllImport("entry.dll",
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "SaveToBuffer")]
private static int SaveToBuffer( ref sbyte[] buf, ref int buf_size );
This does not work if this function is called from C# program crashes.
I suppose this is related to Cdecl calling model and should use Marshal.AllocHGlobal(value),
I can`t imagine how should it be done correct.
I also tryed this:
[DllImport("entry.dll",
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "SaveToBuffer")]
private static int SaveToBuffer( IntPtr buf, ref int buf_size );
And then alocate enough memory
IntPtr data=Marshal.AllocHGlobal(128000);
int bufSize=128000;
var sCode=SaveToBuffer(data,bufSize ); /* value of scode idicate succses*/
Calling this way i get return value from SaveToBuffer indicating function succseeded but: bufSize returns to 0 and how should i read my data from IntPtr.
I`m completly stuck on this.
This is not an issue with the calling convention. The problem is in the buffer handling.
There's really only one sensible way to interpret the C++ argument types and the apparent intent to return an array of bytes. That is that the buffer is allocated and populated by the callee, and its address returned in buf. The buffer length is returned in buf_size.
With these semantics the function arguments cannot be marshalled automatically and you'll have to do it manually:
[DllImport("entry.dll", CallingConvention = CallingConvention.Cdecl)]
private static int SaveToBuffer(out IntPtr buf, out int buf_size);
Call like this
IntPtr buf;
int buf_size;
int retval SaveToBuffer(out buf, out buf_size);
// check retval
Then copy to byte array like this:
byte[] buffer = new byte[buf_size];
Marshal.Copy(buf, buffer, 0, buf_size);
The DLL will also need to export a function to deallocate the unmanaged buffer.
I'm trying to marshall some data that my native dll allocated via CoTaskMemAlloc into my c# application and wondering if the way I'm doing it is just plain wrong or I'm missing some sublte decorating of the method c# side.
Currently I have c++ side.
extern "C" __declspec(dllexport) bool __stdcall CompressData( unsigned char* pInputData, unsigned int inSize, unsigned char*& pOutputBuffer, unsigned int& uOutputSize)
{ ...
pOutputBuffer = static_cast<unsigned char*>(CoTaskMemAlloc(60000));
uOutputSize = 60000;
And on the C# side.
private const string dllName = "TestDll.dll";
[System.Security.SuppressUnmanagedCodeSecurity]
[DllImport(dllName)]
public static extern bool CompressData(byte[] inputData, uint inputSize, out byte[] outputData, out uint outputSize );
...
byte[] outputData;
uint outputSize;
bool ret = CompressData(packEntry.uncompressedData, (uint)packEntry.uncompressedData.Length, out outputData, out outputSize);
here outputSize is 60000 as expected, but outputData has a size of 1, and when I memset the buffer c++ side, it seems to only copy across 1 byte, so is this just wrong and I need to marshall the data outside the call using an IntPtr + outputSize, or is there something sublte I'm missing to get working what I have already?
Thanks.
There are two things.
First, the P/Invoke layer does not handle reference parameters in C++, it can only work with pointers. The last two parameters (pOutputBuffer and uOutputSize) in particular are not guaranteed to marshal correctly.
I suggest you change your C++ method declaration to (or create a wrapper of the form):
extern "C" __declspec(dllexport) bool __stdcall CompressData(
unsigned char* pInputData, unsigned int inSize,
unsigned char** pOutputBuffer, unsigned int* uOutputSize)
That said, the second problem comes from the fact that the P/Invoke layer also doesn't know how to marshal back "raw" arrays (as opposed to say, a SAFEARRAY in COM that knows about it's size) that are allocated in unmanaged code.
This means that on the .NET side, you have to marshal the pointer that is created back, and then marshal the elements in the array manually (as well as dispose of it, if that's your responsibility, which it looks like it is).
Your .NET declaration would look like this:
[System.Security.SuppressUnmanagedCodeSecurity]
[DllImport(dllName)]
public static extern bool CompressData(byte[] inputData, uint inputSize,
ref IntPtr outputData, ref uint outputSize);
Once you have the outputData as an IntPtr (this will point to the unmanaged memory), you can convert into a byte array by calling the Copy method on the Marshal class like so:
var bytes = new byte[(int) outputSize];
// Copy.
Marshal.Copy(outputData, bytes, 0, (int) outputSize);
Note that if the responsibility is yours to free the memory, you can call the FreeCoTaskMem method, like so:
Marshal.FreeCoTaskMem(outputData);
Of course, you can wrap this up into something nicer, like so:
static byte[] CompressData(byte[] input, int size)
{
// The output buffer.
IntPtr output = IntPtr.Zero;
// Wrap in a try/finally, to make sure unmanaged array
// is cleaned up.
try
{
// Length.
uint length = 0;
// Make the call.
CompressData(input, size, ref output, ref length);
// Allocate the bytes.
var bytes = new byte[(int) length)];
// Copy.
Marshal.Copy(output, bytes, 0, bytes.Length);
// Return the byte array.
return bytes;
}
finally
{
// If the pointer is not zero, free.
if (output != IntPtr.Zero) Marshal.FreeCoTaskMem(output);
}
}
The pinvoke marshaller cannot guess how large the returned byte[] might be. Raw pointers to memory in C++ do not have a discoverable size of the pointed-to memory block. Which is why you added the uOutputSize argument. Good for the client program but not quite good enough for the pinvoke marshaller. You have to help and apply the [MarshalAs] attribute to pOutputBuffer, specifying the SizeParamIndex property.
Do note that the array is getting copied by the marshaller. That's not so desirable, you can avoid it by allowing the client code to pass an array. The marshaller will pin it and pass the pointer to the managed array. The only trouble is that the client code will have no decent way to guess how large to make the array. The typical solution is to allow the client to call it twice, first with uOutputSize = 0, the function returns the required array size. Which would make the C++ function look like this:
extern "C" __declspec(dllexport)
int __stdcall CompressData(
const unsigned char* pInputData, unsigned int inSize,
[Out]unsigned char* pOutputBuffer, unsigned int uOutputSize)
i have a c++ dll ,that has some extern function.
and its look like this
//C++ Code
void GetData(byte * pData)
{
byte a[] = {3,2,1};
pData = a;
}
and i have use this code in C# side to get data :
//C# Code
[DllImport(UnmanagedDLLAddress)]
public static extern void GetData(ref IntPtr pData);
//and use it like
IntPtr pointer = IntPtr.Zero;
GetData(ref pointer);
byte[] data = new byte[3] // <===== think we know size
Marshal.Copy(pointer,data ,0,3);
but always "pointer" is zero so Marshal.Copy throw null exception
where i did mistake ?
ty
First, your C++ code puts the array to the stack. You need to allocate it some other way, for documentation start from here: http://msdn.microsoft.com/en-us/library/aa366533%28VS.85%29.aspx
Second, pData is a "normal" value argument, effectively a local variable. You assign to it, then it gets forgotten when function returns. You need it to be reference to pointer or pointer to pointer if you want it to be "out parameter" returning a value back. If you want to actually copy the array contents to the buffer pointed to by pData, then you need to use memcpy function from #include <cstring>.
Actually, like hyde told, use pData as "out" parameter.
Because it's obviously a reference type and no value type, the called method shall take care about memory allocation. I used "ref" only for value types, like integer - e.g. getting the length of the array from the unmanaged method.
This way works fine for me, furthermore, i use the "cdecl" calling convention.
IntPtr aNewIntArray;
uint aNewIntArrayCount = 0;
NativeMethods.getEntityFieldIntArray(out aNewIntArray, ref aNewIntArrayCount);
int[] aNewIntArrayResult = new int[aNewIntArrayCount];
Marshal.Copy(aNewIntArray, aNewIntArrayResult, 0, (int)aNewIntArrayCount);
method declaration:
[DllImport(SettingsManager.PathToDLL, EntryPoint = "getEntityFieldIntArray", CallingConvention = CallingConvention.Cdecl)]
public static extern ErrorCode getEntityFieldIntArray(out IntPtr aNewIntArray, ref UInt32 aNewIntArrayCount);