I have a C-DLL + header file and try to p/invoke a function from C#. I also have some example C++ code of how to use the function. Here is the function definition:
int GetData(unsigned char* buffer, long bufferSize);
The more interesting part is the example code and how the function can be called:
if(dataSize == 16)
{
unsigned short* usData = new unsigned short[m_numX * m_numY * 3 / 2];
GetData( (unsigned char*)usData, m_numX * m_numY * sizeof(unsigned short) );
}
else if (dataSize == 32)
{
unsigned long* ulData = new unsigned long[m_numX * m_numY];
GetData( (unsigned char*)ulData, m_numX * m_numY * sizeof(unsigned long) );
}
So, depending on the dataSize variable, the actual data array can be an ushort or an ulong. However, it is always passed as an unsigned char pointer.
For the sake of simpleness I just tried to get at least one of the variants to work. Here's the code I tried for dataSize = 16
[DllImport("External.dll", EntryPoint = "GetData", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
public static extern int GetData(ref ushort[] pBuffer, long lBufferSize);
long bufferSize1 = numX * numY * 3 / 2;
long bufferSize2 = numX * numY * sizeof(ushort);
ushort[] data = new ushort[bufferSize1];
GetData(ref data, bufferSize2)
If I run above code, the application quits with an 'Access Violation' Exception. That usually means, that the unsafe code tried to write over the buffer limits or that the p/invoke declaration has an error. I tried huge buffers (which would be able to hold any kind of data I'm expecting) but my guess would be, that my mistake is in the declaration.
I also tried to declare the buffer as byte[] (since the example casts it as unsigned char*) and ulong in the p/invoke declaration. same for the actual buffer I pass as reference. the error remains the same.
How can I make this work?
A couple of mistakes:
The array must not be passed as ref. That is because ref ushort[] matches unsigned short**.
C++ long does not match C# long on Windows. The former is 32 bits, the latter 64 bits.
You need to import like this:
[DllImport("External.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int GetData(
[Out] ushort[] pBuffer,
int lBufferSize
);
It would be perfectly reasonable, for convenience, to use an overload for the 32 bit data variant:
[DllImport("External.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int GetData(
[Out] uint[] pBuffer,
int lBufferSize
);
Likewise, and overload for an array of byte would also be valid should you need it.
Related
I am trying to create a simple dll in C++ and call it from C# using PInvoke. I want to pass in a byte array, do some manipulation on it, and send back another byte array. I figured I would pass in the frame and size then create an unmanaged unsigned char*. I would pass that back in an out IntPtr and return the size. Then later I would free that memory with another function. Everything is working ok except I cannot get the out IntPtr to work. I always just get 0 back. I created my C++ dll in qt. Here is the code I have so far.
#pragma once
#ifdef TOOLS_EXPORTS
#define TOOLS_API __declspec(dllexport)
#else
#define TOOLS_API __declspec(dllimport)
#endif
class Tools
{
public:
int TOOLS_API TestFunction(const unsigned char *inData, int inSize, unsigned char *outData);
};
#include "Tools.h"
int Tools::TestFunction(const unsigned char *inData, int inSize, unsigned char* outData)
{
outData = (unsigned char*)malloc(sizeof(unsigned char) * inSize);
memcpy(outData, inData, sizeof(unsigned char) * inSize);
return inSize;
}
[DllImport("Tools.dll", EntryPoint = "?TestFunction#Tools##QAEHPBEHPAE#Z", CallingConvention = CallingConvention.StdCall)]
public static extern int TestFunction(byte[] inData, int inSize, out IntPtr outData);
IntPtr outData;
int test = TestFunction(data, data.Length, out outData);
You have the final parameter as:
unsigned char* outData
This is a pointer, passed by value. Any modifications to the address are not seen by the caller, because the address is passed by value.
You need to return the address of the memory you allocate to the caller. So you need:
unsigned char** outData
Then in the implementation of the function in your C++ code you replace references to outData with *outData.
By definition sizeof(unsigned char) == 1 and it would be idiomatic to replace sizeof(unsigned char) * inSize with inSize.
I am calling a C++ dll from my C# program. The DLL consists of several functions and I am able to call most of them except this one.
The C++ function is like as below:
__declspec(dllexport) uint8_t* myHash(const char *filename)
{
uint8_t *hash = (unsigned char*)malloc(72*sizeof(uint8_t));
//some processing on hash
return hash;
}
As can be seen in the above code, the hash function stores a character array. I want to receive the values in my C# program but I am not able to do it.
My C# code is like as below:
[DllImport("myHash.dll", CharSet = CharSet.Ansi)]
public static extern IntPtr myHash(string filename);
IntPtr ptr = myHash(fileA);
char[] result = new char[72];
Marshal.Copy(ptr, result, 0, 72);
The problem is that char in C# is a 16 bit character element. Your C++ code returns an array of 8 bit uint8_t values. You should switch to using a byte array instead.
[DllImport("myHash.dll", CallingConvention=CallingConvention.Cdecl,
CharSet = CharSet.Ansi)]
public static extern IntPtr myHash(string filename);
....
IntPtr ptr = myHash(fileA);
byte[] result = new byte[72];
Marshal.Copy(ptr, result, 0, 72);
I specified a calling convention because, as written, your function is __cdecl. Perhaps you omitted something in the transcribing of the question, but the declaration above matches the unmanaged code in the question.
This function would be much better designed to allow the caller to allocate the buffer. That avoids you having to export a deallocator from the C++ code. I'd write the C++ like this:
__declspec(dllexport) int myHash(const char *filename, uint8_t* hash)
{
// calculate hash and copy to the provided buffer
return 0; // return value is an error code
}
And the corresponding C# code:
[DllImport("myHash.dll", CallingConvention=CallingConvention.Cdecl,
CharSet = CharSet.Ansi)]
public static extern int myHash(string filename, byte[] hash);
....
byte[] hash = new byte[72];
int retval = myHash(fileA, hash);
This function hard-codes in its interface that the buffer is of length 72. That might be reasonable, but it might make sense to pass the length of the buffer too so that the unmanaged code can defend against buffer overruns.
Note that although you refer to the output of this function as a character array, the use of uint8_t* makes it seem more likely to be a byte array. If it really is a character array, then you can use Encoding.GetString() to convert into a string.
I'm attempting to send a pointer to a pointer of a UInt16 array to a marshalled function like so in C#:
C++:
int foo(Unsigned_16_Type** Buffer_Pointer);
C#:
[DllImport("example.dll")]
public static extern int foo(IntPtr Buffer_Pointer);
UInt16[] bufferArray = new UInt16[32];
IntPtr p_Buffer = (IntPtr)Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(UInt16)) * bufferArray.Length);
Marshal.Copy(bufferArray, 0, p_Buffer, bufferArray.Length); //Issue is here
GCHandle handle = GCHandle.Alloc(p_Buffer, GCHandleType.Pinned);
IntPtr ppUnmanagedBuffer = (IntPtr)handle.AddrOfPinnedObject();
UInt16 word_count = 0;
this.lstbox_DATA_WORDS.Items.Clear();
if ( foo(ppUnmanagedBuffer );
My main problem is with the Marshal.Copy, for the first argument which is the source array, it does not take a UInt16[]. I was wondering if anyone knew how to use Marshal.Copy with a UInt16 array.
There is no Marshal.Copy overload that takes an unsigned short array. Fortunately, ushort and short are the same size, so you can use the Marshal.Copy(Int16[], IntPtr, int) overload. You just need to coerce your ushort[] into a short[] first.
Probably the fastest way to do this is to use Buffer.BlockCopy. It copies bytes, so you just have to tell it to copy 2 bytes per entry:
short[] temp = new short[bufferArray.Length];
System.Buffer.BlockCopy(bufferArray, 0, temp, 0, temp.Length * 2);
This will copy the unsigned 16-bit integer values into a signed 16-bit integer array, but the underlying byte values will remain the same, and the unmanaged code won't know the difference.
With C/C++ DLL SDK fun,like this:
INT CmdGetAllLog( BYTE *bStream, UINT16 *nCount, const UINT8 nblk )
but in project use c#,I do it with:
[DllImport("C:\\PrBioApi.dll", EntryPoint = "CmdGetAllLog")]
private static extern bool CmdGetAllLog(IntPtr bStream, ref UInt16 nCount, byte nblk);
and I use it with:
int nMallocSize = Marshal.SizeOf(new LOG_RECORD()) * stuSystem.wLogCnt + 4096;
byte[] pRecord = new byte[nMallocSize];
IntPtr p = Marshal.AllocHGlobal(Marshal.SizeOf(nMallocSize));
Marshal.Copy(pRecord, 0, p, pRecord.Length);
bGetSucc = CmdGetAllLog(p, ref nGet, nBlk++);
Marshal.FreeHGlobal(p);
but it did not work.
would anyone can help me ?thanks.
Your code which copies between the managed array, and the unmanaged pointer, is in the wrong place. It would need to be after the call to the unmanaged function.
But you may as well let the p/invoke marshaller do the work for you:
[DllImport(#"C:\PrBioApi.dll")]
private static extern bool CmdGetAllLog(
byte[] bStream,
ref ushort nCount,
byte nblk
);
int nMallocSize = ...;
byte[] pRecord = new byte[nMallocSize];
bool bGetSucc = CmdGetAllLog(pRecord, ref nGet, nBlk++);
Because a byte array is blittable then the marshaller will just pin your array during the call and hand it off to the native code.
I'm assuming that the other two parameters are passed correctly. Since you did not specify any more details of the interface, they could well be wrong. I'd guess that nGet is used to tell the function how big the buffer is, and to return how much was copied to it by the function. I cannot see where you specify nGet in the question. I'm trusting that you got that bit right.
Some other comments:
You may need to specify a calling convention in the DllImport attribute. Is the native code cdecl perhaps?
The return value is INT in the native code but you've mapped it to bool. That probably is fine if the protocol is that non-zero return means success. But if the return value indicates more than that then you'd clearly need to use int. Personally I'd be inclined to use int and stay true to the native.
With great help of the stackoverflow community, I've managed to call a native DLL function. However, I can't modify the values of ID or intersects array. No matter what I do with it on the DLL side, the old value remains. It seems read-only.
Here are some code fragments:
C++ struct:
typedef struct _Face {
int ID;
int intersects[625];
} Face;
C# mapping:
[StructLayout(LayoutKind.Sequential)]
public struct Face {
public int ID;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 625)]
public int[] intersects;
}
C++ method (type set to DLL in VS2010):
extern "C" int __declspec(dllexport) __stdcall
solve(Face *faces, int n){
for(int i =0; i<n; i++){
for(int r=0; r<625; r++){
faces[i].intersects[r] = 333;
faces[i].ID = 666;
}
}
C# method signature:
[DllImport("lib.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern int solve(Face[] faces, int len);
C# method invocation:
Face[] faces = new Face[10];
faces[0].intersects = new int[625];
faces[0].ID = -1; //.. and add 9 more ..
solve(faces, faces.Length);
// faces[0].ID still equals -1 and not 666
Kindest regards,
e.
You have to tell the pinvoke marshaller explicitly that the array needs to be marshaled back. You do this with the [In] and [Out] attributes. Like this:
[DllImport("...")]
public static extern int solve([In, Out] Face[] faces, int len);
This is an output field only? To get to the bottom of this, I'd try substituting your Face[] parameter with a large-enough a byte[] and see if the byte array gets filled with anything (you'll have to change your [DllExport] a bit).
Also, one other thing I used to experience when doing this with char*'s is that I had to pre-allocate the buffer in C#. For example:
StringBuilder theString=new StringBuilder();
MyUnmanagedFunction(theString);
would not work. But assuming that returned theString was a max 256 characters, I would do this:
StringBuilder theString=new StringBuilder(256);
MyUnmanagedFunction(theString);
And I'd be in business. I'd recommend trying the byte[] substitution, if that doesn't work, try the pre-allocated byte array. Once you are seeing the byte array actually get changed by your C++ code, then you can figure out how to marshal that thing into your C# struct.