I'm developing an app in C# and I need to use a method defined in a C++ dll. That method returns a CvRect* and I want to know how can I, in some way, do a wrapping for use it as a Rectangle[]. I'm using Emgu Cv in C#.
The method signature in the .h looks like these:
extern "C" DLLEXPORT
CvRect* MyClass::MyMethod(char* path, int* lenght);
where lenghtis the number of CvRectreturned.
The C# code:
[DllImport("MyDLL.dll", EntryPoint = "MyMethod")]
public static extern IntPtr MyMethod(string path, ref int lenght);
int lenght = 0;
IntPtr h_result = MyMethod(path, ref lenght);
CvRect[] result= new CvRect[lenght];
var sizeInBytes = Marshal.SizeOf(typeof(CvRect));
for (int i = 0; i < lenght; i++)
{
IntPtr p = new IntPtr((h_result.ToInt32() + i * sizeInBytes));
result[i] = (CvRect)Marshal.PtrToStructure(p, typeof(CvRect));
}
Related
I'm trying to connect a C# executable to a C++ dll. One of the methods of the dll receives a const char* and an int* (the first one specifying an input value, and the second one, an address to return a value):
extern "C" __declspec(dllexport)
int setVal(long handle, const char* ptrVal, int* ptrRet);
The first thing this function does is to check whether ptrVal is null, and returns -1 if so.
On the other hand, the C# code invokes the dll as follows:
[DllImport(dllName,
EntryPoint = "setVal",
ExactSpelling = true,
CallingConvention = CallingConvention.Cdecl,
CharSet = CharSet.Ansi)]
public static extern int setVal(long handle,
[MarshalAs(UnmanagedType.LPStr)] string str,
ref int ptrRes);
In the main function, I have
long handle = 0;
int result = 0;
int res = 0;
string str = "Hello World!";
result = setVal(handle, str, ref res);
When calling this function, I always receive a null pointer at the C side, which makes result equal to -1. I have tried different approaches when declaring the wrapper function, without success:
public static extern int setVal(long handle,
[MarshalAs(UnmanagedType.LPStr)] [In] string str,
[Out] int ptrRes);
public static unsafe extern int setVal(long handle,
[MarshalAs(UnmanagedType.LPStr)] string str,
ref int ptrRes);
public static extern int setVal(long handle,
StringBuilder sb,
ref int ptrRes); // also the unsafe version
public static extern int setVal(long handle,
byte[] value,
ref int ptrRes); // also the unsafe version
I'm using Visual Studio 2017, and .NET framework 4.6.1.
Why am I always receiving NULL as the second argument (const char*) of the dll function?
I did a bit of a google so this would be untested code but I can see it's something you haven't tried
declare external like so
[DllImport(dllName, EntryPoint = "setVal", ExactSpelling = true, CallingConvention, CallingConvention.Cdecl,CharSet = CharSet.Ansi)]
public static extern int setVal(int handle, StringBuilder sb, ref int ptrRes);
and use it like so
int handle = 0;
int result = 0;
int res = 0;
StringBuilder sb = new StringBuilder("Hello World");
result = setVal(handle, sb, ref res);
On Windows, the long handle in C/C++ is int handle in C# both at 32 and 64 bits. You can check it by doing a sizeof(long) in C/C++. Windows is LLP64.
I have the following export from a dll made with Delphi2006.
procedure ScSetMRStatus(StatusType: TStatusType; active_mr_ids: TLongIntArray; id_dst: Integer; id_src_list: TLongIntArray; IsComplete: Boolean); stdcall; export;
where TLongIntArray is defined as:
TLongIntArray = array of LongInt;
and TStatusType is just an enum:
TStatusType = ( stPhysical, stMaster );
Now I tried to call this method from a c# application.
[DllImport(DelphiDLLName, EntryPoint = "ScSetMRStatus", CallingConvention = CallingConvention.StdCall)]
private static extern void ScSetMRStatus(
Int32 statusType,
IntPtr activeMrIds,
Int32 IdDst,
IntPtr idSrcList,
[MarshalAs(UnmanagedType.U1)] bool isComplete);
Using it this way from c#:
ScSetMRStatus((Int32) statusType, ConvertManagedArrayToDelphiDynIntArray(activeMrIds), idDst, ConvertManagedArrayToDelphiDynIntArray(idSrcList), isComplete);
ConvertManagedArrayToDelpiDynIntArray looks like:
public static IntPtr ConvertManagedArrayToDelphiDynIntArray(int[] array)
{
if (array == null) return IntPtr.Zero;
int elementSize = sizeof(int);
int arrayLength = array.Length;
int allocatedMemSize = 8 + elementSize * arrayLength;
IntPtr delphiArrayPtr = Marshal.AllocHGlobal(allocatedMemSize);
Marshal.WriteInt32(delphiArrayPtr, 0, 1);
Marshal.WriteInt32(delphiArrayPtr, 4, arrayLength);
for (int k = 0; k < arrayLength; k++) {
Marshal.WriteInt32(delphiArrayPtr, 8 + k*elementSize, array[k]);
}
return delphiArrayPtr+8;
}
But this doesn't work!
How do I send c# arrays to delphi?
It's finally working!
We did a few changes around the call to free the allocated memory.
var activeMrIdsNative = ConvertManagedArrayToDelphiDynIntArray(activeMrIds);
var idSrcListNative = ConvertManagedArrayToDelphiDynIntArray(idSrcList);
ScSetMRStatus((Int32) statusType, activeMrIdsNative, idDst, idSrcListNative, isComplete);
Marshal.FreeHGlobal(activeMrIdsNative-8);
Marshal.FreeHGlobal(idSrcListNative-8);
We just thought it would not work, as we have not looked at what the Delphi side does with it. All data gets it's way into the delphi dll and it's working good.
Maybe there is a memory issue, but we will examine this.
I'd like to call an unmanaged method that allocates memory, creates an array of LPWSTRs, and returns it to managed code. I'd like to avoid in/out parameters and writing code to manage memory and variable scopes as much as possible so I decided I would rely on using CoTaskMemAlloc and let the marshaller automagically clean up after me.
Here's what I have (a modified version of a p/invoke tutorial method on MSDN):
extern "C" DLL1_API LPWSTR *TestArrayOfStrings(_In_ int count)
{
STRSAFE_LPWSTR temp = NULL;
wchar_t * ppStrArray[10] = { NULL };
const size_t alloc_size = sizeof(wchar_t *) * 10;
for (int i = 0; i < 10; i++)
{
temp = (STRSAFE_LPWSTR)CoTaskMemAlloc(alloc_size);
if (i % 2 == 0)
StringCchCopy(temp, alloc_size, L"0123456789");
else
StringCchCopy(temp, alloc_size, L"9876543210");
CoTaskMemFree(ppStrArray[i]);
ppStrArray[i] = temp;
}
count = 10;
return ppStrArray;
}
and on the managed side:
[DllImport("Dll1.Windows.dll", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0, ArraySubType = UnmanagedType.LPWStr)]
public static extern string[] TestArrayOfStrings(out int count);
As you can see I've tried to use additional attributes but the marshaller just doesn't seem to like it--I keep getting "Cannot marshal 'return value': Invalid managed/unmanaged type combination." I am trying to maintain typing as an array of LPWSTRs and would like to avoid SAFEARRAY, for which marshalling is marked obsolete.
Slightly modified code, but the signature was what was important. This method assigns a string value (the third parameter) to the first element of the uninitialized out array passed in.
[DllImport("Dll1.Windows.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
public static extern void TestArrayOfStrings(
[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr, SizeParamIndex = 1)] [Out] out string[] test,
out int size, string someString);
extern "C" DLL1_API void TestArrayOfStrings(wchar_t ***strings, int *size, wchar_t *someString){
const size_t alloc_size = 64;
STRSAFE_LPWSTR temp = (STRSAFE_LPWSTR)CoTaskMemAlloc(alloc_size);
StringCchCopy(temp, alloc_size, someString);
*strings = (wchar_t **)CoTaskMemAlloc(sizeof(wchar_t));
*strings[0] = temp;
*size = 1;
}
I am trying to create a Win32 DLL exposes some functions which are called in C# as follows
__declspec(dllexport) int GetData(unsigned char* *data, int* size)
{
try
{
int tlen = 3;
unsigned char* tchr = new unsigned char[5];
tchr[0] = 'a';
tchr[1] = 'b';
tchr[2] = 'c';
*size = tlen;
*data = tchr;
return 1;
}
catch (char *p)
{
return 0;
}
}
And on C# side
[DllImport("MyDll.dll")]
static extern int GetData(ref byte[] data, ref int size);
static void Main()
{
try
{
int hr = 0;
byte[] gData = null;
int gSize = 0;
hr = GetData(ref gData, ref gSize);
Console.WriteLine(gSize);
for (int i = 0; i < gSize; i++)
Console.WriteLine((char)gData[i]);
}
catch (Exception p)
{
Console.WriteLine(p.ToString());
}
}
When I run C# code, AccessViolationException happens on GetData function which is a sign of exception in C++ code however, following C++ code snippet works fine without any error.
int _tmain(int argc, _TCHAR* argv[])
{
unsigned char* data = NULL;
int size = NULL;
GetData(&data, &size);
printf("%d", size);
for (int i = 0; i < size; i++)
printf("%c,", data[i]);
return 0;
}
If you compare C# main function and C++ _tmain, they are almost analoguous so where I may make a mistake?
You are returning an array allocated by a call to C++ new and hoping that the marshaler will turn it into a C# byte[]. That won't happen.
You'll need to pass a pointer by reference and then marshal it by hand. Your p/invoke should look like this:
[DllImport("MyDll.dll")]
static extern int GetData(out IntPtr data, out int size);
When the function returns data will point to the array and you can read the contents using the Marshal class. I guess you would copy it to a new byte array.
var arr = new byte[size];
Marshal.Copy(data, arr, 0, size);
Some other points:
The calling conventions do not match. The native side is cdecl and the managed is stdcall.
You'll need to export a deallocator to delete the memory returned by the native function. Consider a re-design where the caller allocates the buffer.
C++ Function header in DLL this two function to get some information about the wifi stations around me using win mobile 6.5 device and i need to invoke them to use them in C# code
// (adapter names , pointer to destination buffer ,and the size , returned structs)
bool __declspec(dllexport) GetBBSIDs(LPWSTR pAdapter, struct BSSIDInfo *pDest, DWORD &dwBufSizeBytes, DWORD &dwReturnedItems);
bool __declspec(dllexport) RefreshBSSIDs(LPWSTR pAdapter);
bool __declspec(dllexport) GetAdapters(LPWSTR pDest, DWORD &dwBufSizeBytes);
C# sample
[DllImport(#"\Storage Card\Work\Beaad.dll", EntryPoint = "GetAdapters", SetLastError = true)]
public static extern bool getAdapters([MarshalAs(UnmanagedType.LPWStr)] String buf, ref UInt32 dwBufSizeBytes);
[DllImport(#"\Storage Card\Work\Beaad.dll", EntryPoint = "RefreshBSSIDs", SetLastError = true)]
public static extern bool refreshBSSIDs([MarshalAs(UnmanagedType.LPWStr)]String buf);
[DllImport(#"\Storage Card\Work\Beaad.dll", EntryPoint = "GetBBSIDs", SetLastError = true)]
public static extern bool getBBSIDs([MarshalAs(UnmanagedType.LPWStr)]String buf,BSSIDInfo [] nfo, ref UInt32 dwBufSizeBytes, ref UInt32 dwReturnedItems);
[StructLayout(LayoutKind.Sequential,CharSet=CharSet.Auto)]
public struct BSSIDInfo
{
public byte[] BSSID; //mac
public char[] SSID;
public BSSIDInfo(byte[]bs,char[] ss)
{
this.RSSI = 0;
this.Infastructure = 0;
this.Channel = 0;
this.Auth = 0;
bs = new byte[6];
ss = new char[32];
BSSID = bs;
SSID = ss;
}
public int RSSI;
public int Channel;
public int Infastructure;
public int Auth;
}
public static byte[] StrToByteArray(string str)
{
System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
return encoding.GetBytes(str);
}
public static char[] c = new char[1024];
string buf = new string(c);
public void button1_Click(object sender, EventArgs e)
{
BSSIDInfo[] nfo = new BSSIDInfo[128];
byte[] bytee=StrToByteArray(buf);
UInt32 dwsize= new UInt32();
UInt32 dwTmp = new UInt32();
UInt32 dwCount = new UInt32();
dwTmp = Convert.ToUInt32(Marshal.SizeOf(typeof(BSSIDInfo)) * nfo.Length);
dwCount =0;
dwsize=Convert.ToUInt32(bytee.Length);
if (false == getAdapters(buf,ref dwsize) || dwsize == 0)
{
label1.Text = "no adabters";
}
else
{
String [] strList=new String[15];
if (buf.Contains(',') == false)// one adapter
{
textBox1.Text = buf;
}
else
{
strList = buf.Split(',');
for (int i = 0; i < strList.Length; i++)
{
textBox1.Text+= strList[i]+Environment.NewLine;
}
}
if (refreshBSSIDs(buf) && getBBSIDs(buf, nfo, ref dwTmp, ref dwCount) && dwCount > 0)
{
//refreshBSSIDs(buf) &&
for (int i = 0; i < dwCount; i++)
{
textBox2.Text += nfo.GetValue(i).ToString() + Environment.NewLine;
}
}
else
{
//make another thing
}
}
}
and when i put this dll on the mobile and the C# app.exe the first function that named as Getadapters(..) return to me the name of the adapter in the first textbox1 then the app stopped and give me not supported exception when the mobile tries to execute the other two function that named as refreshBSSID() and getBSSIDs() so what is the problem ? or is there another solution to get this information (BSSID ,SS ..etc) ?
C++ by default unless changed uses a caller( Cdecl ) calling convention. Your C++ code does not change the calling convention. Your C# code by default ( unless you change it ) will use a callee convention ( StdCall ).
While this might not be exactly the problem your having it still is technically incorrect. Even if you were to fix your current problem you likely will end up having a problem because of the calling convention.
I am going to guess your C# BSSIDInfo structure does not match the C++ structure. Why do the method StrToByteArray when all it does is GetBytes on the given string...
when the mobile tries to execute the
other two function that named as
refreshBSSID() and getBSSIDs() so what
is the problem ? or is there another
solution to get this information
I thought I knew the reason took another look and I was wrong.