This question already has answers here:
Why are Cdecl calls often mismatched in the "standard" P/Invoke Convention?
(2 answers)
Closed 7 years ago.
I'm currenty doing micro-benchmarks for a better understanding of clr to native code performance. In the following example I'm getting a StackOverflowException, when compiled as release and executed without debugger attached. I don't get the exception when compiling as debug-build or when running the program with debugger attached. Furthermore I also get this error only with SuppressUnmanagedCodeSecurityAttribute-Attribute.
I built a dll using c and VS2013 (platformtoolset=v120) with one function in it:
__declspec(dllexport) int __cdecl NativeTestFunction(int a, int b, int c, int d)
{
return a + c + b + d;
}
In my C#-program I use DllImport to call this function and do some timing-measurements:
[DllImport("Native.dll", EntryPoint = "NativeTestFunction")]
static extern int NativeTestFunction(int a, int b, int c, int d);
[DllImport("Native.dll", EntryPoint = "NativeTestFunction"), SuppressUnmanagedCodeSecurityAttribute]
static extern int NativeTestFunctionSuppressed(int a, int b, int c, int d);
static void Main(string[] args)
{
byte[] data = new byte[64];
int c = 0;
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 10000000; i++)
c += NativeTestFunction(2, -1, -2, 1);
Console.WriteLine("Unsuppressed: " + sw.Elapsed.ToString());
sw = Stopwatch.StartNew();
for (int i = 0; i < 10000000; i++)
c += NativeTestFunctionSuppressed(2, -1, -2, 1);
Console.WriteLine("Suppressed..: " + sw.Elapsed.ToString());
}
If I compile this code as release and start it without debugger attached the output is:
Unsuppressed: 00:00:00.2666255
Process is terminated due to StackOverflowException.
However, executed with debugger attached or compiled as debug and launched with or without debugger attached the program succeeds:
Unsuppressed: 00:00:00.2952272
Suppressed..: 00:00:00.1278980
Is this a known Bug in .NET/CLR? What is my mistake? I think the behavior should be the same between attached and not-attached debugger.
This error happens with .NET 2.0 and .NET 4.0. My software is compiled as x86 (and therefore tested only for x86) for compatibility to the Native.dll. If you don't want to setup this scenario yourself you can download my test-projects: Sourcecode.
__declspec(dllexport) int __cdecl NativeTestFunction(int a, char* b, int c, int d)
Note the type of b. It is char*. Then in the C# code you write:
[DllImport("Native.dll", EntryPoint = "NativeTestFunction"),
SuppressUnmanagedCodeSecurityAttribute]
static extern int NativeTestFunctionSuppressed(int a, int b, int c, int d);
Here you declare b to be int. That does not match. It gets worse when you call the function.
NativeTestFunctionSuppressed(2, -1, -2, 1);
Passing -1 will, in a 32 bit process, equate to passing the address 0xffffffff. Nothing good will come of attempting to de-reference that address.
The other problem is that the calling conventions do not match. The native code uses __cdecl, but the managed code uses the default of __stdcall. Change the managed code to:
[DllImport("Native.dll", EntryPoint = "NativeTestFunction",
CallingConvention = CallingConvention.Cdecl),
SuppressUnmanagedCodeSecurityAttribute]
And likewise for the other import.
Related
I'm pasting a small example code that calls Fortran from C# below. The larger problem on which this small reproducible example is based was previously published as 32-bit. More recently, I have need of getting the published code working in 64-bit, and that's when it was discovered that 64-bit is not working.
The code's development has been in Visual Studio, with Intel Visual Fortran installed. The .sln file has 2 projects - C# as the "startup project" and an Intel Visual Fortran project that is compiled as a DLL and called by the C# project. From the .sln file's properties page, setting the "Configuration" as 32-bit (x86) has worked well. However, if instead x64 is used for both the C# and Fortran, the following error message results:
System.DllNotFoundException: 'Unable to load DLL 'passStr_Fortran_C.dll': The specified module could not be found. (Exception from HRESULT: 0x8007007E)'
My hope is that there is a flag or setting that simply needs to be toggled? If it is more complicated than that, my hope is that someone could lay out the modifications that need to take place (perhaps with the example problem provided below)? Here are the two codes that form the basis of the C# and Fortran projects that work with x86 (win32) but not x64.
C#:
using System;
using System.Runtime.InteropServices;
public static class Program
{
public static int Nsegshold;
public static double[] Diversions = new double[1];
public static int Process_mode;
//Fortran DLL interface
[DllImport("passStr_Fortran_C.dll", CallingConvention = CallingConvention.Cdecl)] // CharSet = CharSet.Ansi,
public static extern void TEST_Var(ref int Process_mode, ref int Nsegshold, [In, Out] double[] Diversions);
public static void Main(string[] args)
{
Process_mode = 1;
Nsegshold = 1;
TEST_Var(ref Process_mode, ref Nsegshold, Diversions);
// Redimension arrays to size determined in fortran
Diversions = (double[])ResizeArray(Diversions, new int[] { Nsegshold });
Process_mode = 2;
TEST_Var(ref Process_mode, ref Nsegshold, Diversions);
Console.WriteLine("Two calls to fortran complete. ");
}
private static Array ResizeArray(Array arr, int[] newSizes)
{
if (newSizes.Length != arr.Rank)
throw new ArgumentException("arr must have the same number of dimensions " +
"as there are elements in newSizes", "newSizes");
var temp = Array.CreateInstance(arr.GetType().GetElementType(), newSizes);
int length = arr.Length <= temp.Length ? arr.Length : temp.Length;
Array.ConstrainedCopy(arr, 0, temp, 0, length);
return temp;
}
}
Fortran:
!
MODULE Fortran_C
!
CONTAINS
!
SUBROUTINE TEST_Var(Process_mode, Nsegshold, Diversions) BIND(C,NAME="TEST_Var")
!
!DEC$ ATTRIBUTES DLLEXPORT :: TEST_Var
!
INTEGER, INTENT(IN) :: Process_mode
INTEGER, INTENT(INOUT) :: Nsegshold
DOUBLE PRECISION, INTENT(INOUT) :: Diversions(Nsegshold)
!
INTEGER :: i
!
IF(Process_mode.eq.1) THEN
Nsegshold = 100
ELSE IF(Process_mode.eq.2) THEN
DO i = 1, Nsegshold
Diversions(i) = 3.14 * i
ENDDO
ENDIF
!
END SUBROUTINE TEST_Var
!
END MODULE Fortran_C
I am trying to import a DLL written in C. The definition of the function is:
__declspec(dllexport) double GetData(unsigned char *Buffer, int l)
{
int i;
for (i= 0; i<l; i++)
{
Buffer[i] = MyVal[i]; //MyVal is a global Variable, updated by differnt process
}
return 3.14;
}
The counter function used in C# to import this DLL is:
[DllImportAttribute(DLLLocation, CallingConvention = CallingConvention.Cdecl)]
public static extern double GetData(StringBuilder buffer, int l);
which is called using,
int l = 8;
StringBuilder buffer = new StringBuilder(8) ;
double t = GetData(buffer, l);
string S = buffer.ToString()
But when I run thiscode, I get an error,
An unhandled exception of type 'System.AccessViolationException' occurred in
TestCode.exe
Additional information: Attempted to read or write protected memory. This is
often an indication that other memory is corrupt.
PS: There are two processes that are using the same DLL. MyVal is getting updated by one process and the other process (WPF) is trying to get that.
Thanks in advance
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 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 catched a strange issue when i was trying to call unmanaged c++ function from c# code.
the c++ function looks like this:
extern "C"{
__declspec(dllexport) int Test(char * pixels, int length, int height){
int width = length / height;
// char * test = new char[length];
return width;
}
}
And from C# I'm trying to call it using next constructions:
[DllImport("Test.exe", EntryPoint = "Test")]
public static extern int Test(byte[] pixels, int length, int height);
...
var bytes = new byte[] { 1, 1, 1, 1, 1 };
var height = 1;
var result = Test(bytes, bytes.Length, height);
This code works well, but...
If I try to uncomment c++ line with 'new', i'm getting the System.AccessViolationException.
Can anybody explain what happening there and why?
UPDATED
Thanks to David Heffernan, and now my code looks like this:
[DllImport("Test.exe", EntryPoint = "Test",
CallingConvention = CallingConvention.Cdecl)]
public static extern int Test(byte[] pixels, int length, int height);
Unfortunately it changes nothing
RESOLVED
Don't use *.exe as module. Everything works well after compilation module as dynamic library.
It's caused by fundamental differences between DLLs and executable file linking. For more infomation see MSDN LoadLibrary page and MSDN About DLLs page.