I am completely confused with how to go about calling functions in native dll with array arguments.
Example:
The function is defined in the C# project as:
[DllImport("Project2.dll", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
static extern void modifyArray([MarshalAs(UnmanagedType.LPArray)] int[] x, int len);
And the function call is:
modifyArray(arr, 3)
where arr = {4,5,6}
The native C++ function definition is as follows:
extern "C" _declspec(dllexport) void modifyArray(int* x,int len)
{
int arr[] = {1,2,3};
x = arr;
}
Why in the C# project, the array is not pointing to the new array after the function call? It still remains {4,5,6}.
I tried this to but failed
[DllImport("Project2.dll", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
static extern void modifyArray([In,Out] int[] x, int len);
This pinvoke fails whenever I try modifying arguments passed to these functions using pointers. Otherwise, I have had success passing ref array arguments for native dll sort functions where there is no pointer changes to newly created types.
Your C++ code is broken. The caller allocates the array, and the callee populates it. Like this:
extern "C" _declspec(dllexport) void modifyArray(int* x, int len)
{
for (int i=0; i<len; i++)
x[i] = i;
}
As far as your p/invoke call goes, SetLastError should not be true. The function is not calling SetLastError. It should be:
[DllImport("Project2.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void modifyArray(int[] x, int len);
This has nothing to do with PInvoke, this is just a plain old C issue. You would have the exact same problem if you called modifyArray from C
int* pArray = NULL;
modifyArray(pArray, len);
pArray == NULL; // true!
In modifyArray you are trying to change where x points to. This change won't be visible to the calling function because the pointer is passed by value. In order to change where it points to you need to pass a double pointer
void modifyArray(int** x, int len) {
*x = ...;
}
Note that you are currently trying to return stack allocated memory instead of heap allocated memory. That is incorrect and will lead to problems down the line
Related
In the below example, the dataArray defined in C++ works if defined as an array, but not as a pointer (Just turns out garbage data). Is there another way to marshal the C# array so that it reads the pointer in as an array?
C#
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct CSharpFoo{
int alpha;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5]
public int[] dataArray;
int beta;
}
C++
struct CPPFoo{
int alpha;
//int* dataArray; //Doesn't work, even though initialized to an array elsewhere
int dataArray[5];
int beta;
}
Being passed through a function like this
C#
[DllImport("MyDll.dll", CallingConvention = CallingConvention.Cdecl, BestFitMapping = false, ThrowOnUnmappableChar = true)]
public extern static bool InitializeDLL([MarshalAs(UnmanagedType.FunctionPtr)] ResultCallback callbackPointer);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void ResultCallback(CSharpFoo value);
C++
//Callback
typedef void(__stdcall * ResultCallback)(CPPFoo);
__declspec(dllexport) bool InitializeDLL(ResultCallback callback);
Thanks in advance!
Edit::
Because "initialized to an array elsewhere" wasn't clear:
CPPFoo(int dummy){ //Constructor
alpha = 32;
dataArray = new int[5];
for (int i = 0; i < 5; i++){
dataArray[i] = i;
}
beta = 13;
}
//dataArray C++ {0,1,2,3,4}
//alpha C# 32
//dataArray C# {Total random garbage} (dataArray[3] is 13!)
//beta C# 0
PS, the CPPFoo struct is a complex struct that comes from a DLL, so I cannot change it. For now, to get things working, I copy it to a more appropriate array like in NonCreature0714's answer, but this results in all of the data being copied--twice. It's this double copy that I'm trying to avoid.
Another edit:
While it seems that for a struct containing a single array the values get passed properly, for a complex struct, garbage gets thrown in.
I have updated the code to reflect a more complex struct!
I did a couple of tests on my local machine, as far as I understood your code. I had no problems with the following code
c# side
struct Foo
{
public int alpha;
public IntPtr Data;
public int beta;
public void GetData(ref int[] buffer,int length)
{
Marshal.Copy(Data,buffer,0,length);
}
}
class Program
{
[DllImport("MyPtr.dll",EntryPoint = "InitializeDLL", CallingConvention = CallingConvention.Cdecl)]
public static extern bool InitializeDLL([MarshalAs(UnmanagedType.FunctionPtr)]ResultCallback callbackPointer);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void ResultCallback(ref Foo value);
static void CallBackMe(ref Foo value)
{
var buffer = new int[5];
value.GetData(ref buffer,buffer.Length);
}
static void Main(string[] args)
{
InitializeDLL(CallBackMe);
}
}
C++ side
struct CPPFoo {
int* dataArrayPtr;
CPPFoo()
{
dataArrayPtr = new int[5];
for (int i = 0; i < 5; i++) {
dataArrayPtr[i] = i;
}
}
};
typedef void(__stdcall * ResultCallback)(CPPFoo);
extern "C" __declspec(dllexport) bool InitializeDLL(ResultCallback callback)
{
CPPFoo f;
callback(f);
return true;
}
Code in action
My experience in C# is very limited but it seems that it works with int dataArray[5]; because the code agrees to the type of CSharpFoo and CPPFoo. The size of both is five ints. When you change the type to a pointer then one is the size of five ints and the other is a single pointer which would be less then five ints.
Most likely passing a dynamically sized array will need a size and to get a pointer you may need to do something with unsafe code but I don't write in C# so that is just a guess.
struct CPPFoo {
int* dataArray = nullptr;
int size = 0;
}
Use a vector.
#include <vector>
struct CPPFoo {
std::vector<int> dataArray;
};
Vector is more readable and more safe. It only slighty increases the size of the code, and is only slightly slower than using a primitive array. You also don’t need to worry about keeping around a variable for size, and updating it every time it’s changes.
I'm trying to pass raw texture data from Texture2D (byte array) to unmanaged C++ code. In C# code array length is about 1,5kk, however in C++ 'sizeof' always returns 8.
C# declaration of native method :
[DllImport("LibName", CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr ProcessData(byte[] data);
C++:
extern "C" {
__declspec(dllexport) void ProcessData(uint8_t *data) {
//sizeof(data) is always 8
}
}
What am I doing wrong? Is there is a way to pass array without additional memory allocation in C++ code?
Few things you need to do with your current code:
1.You have to send the size of the array to the C++ plugin in another parameter as UnholySheep mentioned.
2.You also have to pin the array before sending it to the C++ side. This is done with the fixed keyword or GCHandle.Alloc function.
3.If the C++ function has a void return type, your C# function should also have the void return type.
Below is a corrected version of your code. No additional memory allocation is performed.
C#:
[DllImport("LibName", CallingConvention = CallingConvention.Cdecl)]
static extern void ProcessData(IntPtr data, int size);
public unsafe void ProcessData(byte[] data, int size)
{
//Pin Memory
fixed (byte* p = data)
{
ProcessData((IntPtr)p, size);
}
}
C++:
extern "C"
{
__declspec(dllexport) void ProcessData(unsigned char* data, int size)
{
//sizeof(data) is always 8
}
}
I defined a function in C DLL library.
__declspec(dllexport) void* GetText();
It will return a string which is dynamically allocated from heap memory (And GlobalAlloc is used here for allocating memory). Note that the returned string is not null-terminated.
Then at C# side I tried two methods to declare the function
[DllImport("D:\\ca\\TextAccessLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
static extern String GetText();
When calling above method, the application will crash without any exception thrown.
[DllImport("D:\\ca\\TextAccessLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr GetText();
ptr = GetText();
string text = Marshal.PtrToStringAuto(ptr, 1000);
And calling this method will return incorrect string. Checked the real bytes by using Marshal.Copy, I found the bytes value is not same as the value in DLL library. (I think it's caused by Virtual Memory, C# process cannot access memory space of the DLL directly)
(Don't mind the string length, I hard coded it to 1000 for ease)
This is the C++ code and the memory value of the string when debugging (It's a Console Application but not the original DLL, because Console Application is easy to debug. But the DLL code is same as this one except the logging part).
Following is the original DLL code
__declspec(dllexport) char* GetText(){
VTHDOC hDoc = NULL;
VTHTEXT hText = VTHDOC_INVALID;
DAERR da_err = NULL;
DAERR ta_err = NULL;
DAERR read_err = NULL;
char *buf = (char*)GlobalAlloc(GMEM_FIXED, 1000);
DWORD real_size;
DAInitEx(SCCOPT_INIT_NOTHREADS, OI_INIT_DEFAULT);
da_err = DAOpenDocument(&hDoc, 2, "D:\\1TB.doc", 0);
ta_err = TAOpenText(hDoc, &hText);
read_err = TAReadFirst(hText, (VTLPBYTE)buf, 1000, &real_size);
return buf;
}
But at C# side the bytes are not same as C++ side
You can see the first byte in C++ is 0, but it's 200 for C# (decimal)
Another thing to note: if I return a const string(e.g. "AASSDD") directly in DLL code, C# side will get the correct string
You can't do it that way. Marshaling of string works only for null-terminated strings (or for BSTR, if you specify some options). You can:
[DllImport("D:\\ca\\TextAccessLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr GetText();
But from there, it isn't clear how the C# program should know the length of the string.
The various Marshal methods of C# handle BSTR (that have internally their length) or NUL terminated strings.
As already stated, it works for null-terminated strings only, in the following way:
C# part, declaration:
[DllImport("myDll.dll", EntryPoint = "myString", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
extern private static string myString(out int size);
C# part, usage:
int size;
string s = myString(out size);
C++ part:
char* myString(int* size)
{
*size = 20;
char* strg = (char*)::GlobalAlloc(GMEM_FIXED, *size);
memset(strg, 0x3f, *size); //preset with a questionmark
for (int i=0; i < 9; i++)
strg[i] = 0x40 + i;
strg[*size -1] = 0; //limit the maximum string length
return strg;
}
And the obtained C# string:
"#ABCDEFGH??????????", value of size: 20
A treatment of the issue may be found here
I want to call two functions from a DLL with pure-C interface via PInvoke with the following signatures:
void *pj_init_plus(const char *srsName);
int pj_datum_transform(void *src, void *dst, long point_count, int point_offset,
double *x, double *y, double *z );
Pinvoke methods:
[DllImport("proj.dll", CallingConvention = CallingConvention.Cdecl,
EntryPoint = "pj_init_plus", CharSet = CharSet.Ansi)]
public static extern IntPtr PjInit(string srsName);
[DllImport("proj.dll", EntryPoint = "pj_transform", CallingConvention =
CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern int PjTransformation(IntPtr src, IntPtr dst,long pointCount,
int pointOffset, double[] x, double[] y,double[] z);
In my C#-code i call the methods:
IntPtr pjSrc = PjInit("+proj=longlat +datum=WGS84 +no_defs");
IntPtr pjDst = PjInit("+proj=utm +zone=32 +ellps=GRS80 +units=m +no_defs");
double[] x = { 4489580.7, 4489580.7 };
double[] y = { 5320767.7, 5320767.7 };
double[] z = { 0.0, 0.0};
PjTransformation(pjSrc, pjDst, x.Length, 1, x, y, z);
The PjInit-call works fine and returns a valid pointer.
But calling PjTransformation throws an AccessViolationException-Exception. I think there is a problem with the double arrays.
In one post was mentioned, that a clr-array is already compatible with a native array and
has not to be manually marshalled. I also tried it with the attribute [MarshalAs(UnmanagedType.LPArray)] for the double-arrays, but that didn't help. Or could the Exception come from the struct which is returned as a void pointers from the first function call. The problem is i don't know the type of the struct.
The dll functions are ok, i tried it with native c-code and it worked. Also the parameter pointOffset could not cause the exception.
Your P-invoke declaration looks good and valid. The only issue I can think of is that the AV exception is thrown from the native function itself rather than when the marshaling takes place.
I advise that you try to debug the code using a native debugger, you can than spot the exact line of code where the exception is thrown and diagnose if the problem occurs during marshalling or actual function execution.
I'm trying to use a DLL file in a C# program using DLLImport.
I have the following function in the DLL:
int method(char* method, char** params, int n_params, float** res_arr, int* n_res);
Function call should be something like this:
method = "method1"
char** = {"param1=1", "param2=2"}
n_params = 2
res_arr = the DLL function allocates an array and points this to it
n_res = the DLL function sets to the number of results
There is a seperate function for freeing the float**.
My current code in C# is this:
private static extern int method(string method, ref IntPtr params, Int32 n_params, ref IntPtr res_arr, IntPtr n_res);
I'm new to C# (and my C knowledge is a bit lacking) and can for the life of me not figure out how to call this function (been faceplanting my keyboard for two days). Could someone give me an example of how this should be done and how to call the function?
My main problem is what to do with the char** and float**, I don't know if it's the correct pointer types in the declaration and don't know how I'm supposed to create and send my char** to the function.
Worth noting is that I may not change anything in the DLL file.
EDIT
This is the description of the function which releases the result array:
free_results(float* res_arr)
EDIT2
I can now call the method and I get values back, my problem now is that I seem to have a problem accessing the float values. As suggested I'm using Marshal.Copy() like this:
[DllImport("libs\\myDll.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern int method(string method, string[] params, Int32 n_params, out IntPtr res_arr, ref int n_res);
IntPtr res_arr = IntPtr.Zero;
int n_res = 0;
string[] s = new string[] { "param1" };
method("analyze", s, s.Length, out res_arr, ref n_res);
float[] f_res = new float[n_res];
Marshal.Copy(res_arr, f_res, 0, n_res);
The problem is I only seem to get rubbish values in the float vector. For example in one case i should get 100.0 but get either 15.0 or 3840.0, which tells me that either I'm using the pointer wrong when copying or there is something else fishy. The code in the DLL is working as it should since there is another program written in C which gets the correct values. It feels like I making a float of the pointer and not what it points at.
This is the code that solved my initial problem (as suggested by Hans Passant):
[DllImport("libs\\myDll.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern int method(string method, string[] params, Int32 n_params, out IntPtr res_arr, ref int n_res);
IntPtr res_arr = IntPtr.Zero;
int n_res = 0;
string[] s = new string[] { "param1" };
method("analyze", s, s.Length, out res_arr, ref n_res);
float[] f_res = new float[n_res];
Marshal.Copy(res_arr, f_res, 0, n_res);
My second problem, where the float array gave rubbish values, were a result of me being a complete ass hat. There is another function in the DLL which has to be called before using method(...), in order for it to have values to process. After adding that call everything worked perfectly.