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.
Related
I've created a wpf project which has a helper static class that contains all my c++ backend code. One such function is defined as:
public static unsafe class Backend {
[DllImport("Mandel.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public extern static void write(void* ptr, char* path);
}
public partial class MainWindow : Window
{
public MainWindow()
{
string path = "mypath";
InitializeComponent();
unsafe
{
char *p; //convert
void* myObj = Backend.init_obj(1920, 1080);
Backend.gen(myObj);
Backend.write(myObj, p);
}
}
}
The void* ptr is actually my object that is casted in order to marshall it onto the C# side. The problem I face is that whenever I try to invoke this with a string literal in wpf, I get that Visual C# cannot convert this because string literals are encoded in UTF16. Naturally I tried many things other than manually copying the relevant bytes to a char array. Any tips?
One of the things the CLR can do pretty well for interop with C/C++ code is marshalling data structures between managed and unmanaged code. Since strings are pretty important, a lot of work went into making strings marshal as well as possible.
As a side note, you're using void* for the context object that's created by init and passed to write. Since you're just handing it back, you can replace it with IntPtr and avoid unsafe blocks altogether. IntPtr is always the size of a pointer in the current architecture.
First, let's change the declaration of the imported functions. CharSet.Ansi tells it to marshal strings as ANSI. The ptr parameter becomes IntPtr
[DllImport("Mandel.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public extern static IntPtr init(int width, int height);
[DllImport("Mandel.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public extern static void gen(IntPtr ptr);
[DllImport("Mandel.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public extern static void write(IntPtr ptr, string path);
And from there, you can figure out how to modify the function to deallocate ptr and any others you have to call.
Using those functions becomes a lot easier and a lot cleaner. You don't need the unsafe block and you can pass path directly to write.
public MainWindow()
{
string path = "mypath";
InitializeComponent();
IntPtr myObj = Backend.init_obj(1920, 1080);
Backend.gen(myObj);
Backend.write(myObj, path);
}
Original comment that got it working:
Instead of trying to create the char* parameter yourself, change the declaration so the second parameter is string and let the Runtime marshal it for you. Because it's an ANSI string, you're never going to get full unicode fidelity but that's a problem created by the C++ code.
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)
i am importing c++ function from a dll to my winform c# app:
[DllImport(#"eyeWhere.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern int Eye_GetPositionS(string filename, [MarshalAs(UnmanagedType.LPArray, SizeConst = 9)] double[] sensors);
when i call the function from the constructor its working fine.
the problem is when i am calling it from a new thread opened within the
"fleck websocket server" Fleck, "onMessage" Action,
then it throws the "system.access.violation" exception.
i managed to narrow down the problem to the double array that i am passing,
it seems like the pointer to it is corrupted.
i cant find the source of the problem one thing is sure the function from the dll is working as i tested it.
function call(two stages):
open new thread within "fleck":
socket.OnMessage = message =>
{Thread locationThread = new Thread( unused => processLocation(fileName,socket,sensorsList,sensors) );
locationThread.Start();}
the actual function:
private void processLocation(string fileName, IWebSocketConnection sock, List<Sensor> sensorsList, double[] sensors)
{
int map_position = Eye_GetPositionS(fileName,sensors);
string locationString = "floor:1,mx:" + (map_position / 10000) + ",my:" + (map_position % 10000);
// send location string to user
sock.Send(LOCATION_CODE + "-" + locationString);}
the interface is:
extern "C" __declspec(dllexport) int Eye_GetPositionS(const wchar_t *fname_mob, double sensors[9], int &map_x, int &map_y)
i am not passing the two last arguments (int&) as agreed with the man who wrote the dll.
I am not passing the two last arguments (int&) as agreed with the man who wrote the DLL.
Well, there's the problem. You cannot omit parameters. It should be:
[DllImport(#"eyeWhere.dll", CallingConvention = CallingConvention.Cdecl,
CharSet = CharSet.Unicode)]
public static extern int Eye_GetPositionS(
string filename,
[In, MarshalAs(UnmanagedType.LPArray, SizeConst = 9)]
double[] sensors,
ref int map_x,
ref int map_y
);
You simply cannot omit these parameters, no matter what the guy who wrote the DLL says. Perhaps he means that the parameters are really int* and you can pass nullptr.
I've used ref for these parameters, but perhaps they should be out. You presumably know which is appropriate.
Likewise I'm guessing at the intent of the sensors parameter. If the data flows out rather than in, then use Out. If the data flows in both directions, use [In, Out, ...].
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
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.