I'm trying to use PInvoke in order to call an unmanaged function from a C dll. Due to the fact that the source of the dll can't be released to developers, some of them have called the function in Delphi using the following declaration. We use SAT.dll with a CDECL calling convention.
function AssociarAssinatura( numeroSessao : Longint; codigoDeAtivacao: PChar;
CNPJvalue : PChar; assinaturaCNPJs : PChar ) : PChar ;
cdecl; External 'SAT.DLL';
Based on that structure, I made the following Console Application in C# in order to test the same function from the same DLL. I made some research and found out that the equivalent to Longint in delphi is int in C# and the equivalent of PChar is a pointer to a string (But I used C#'s string).
class Program
{
[DllImport("SAT.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern string AssociarAssinatura(int numeroSessao,
string codigoDeAtivacao, string CNPJvalue, string assinaturaCNPJs);
static void Main(string[] args)
{
Console.WriteLine("Comienza");
int numeroSessao = 111111;
string codigoDeAtivacao = "123123123";
string cnpJvalue = "2222222222222211111111111111";
string assinaturaCnpJs = "lrafwegmqcgvpzpbdmcmcgdvf";
string resposta = AssociarAssinatura(numeroSessao, codigoDeAtivacao,
cnpJvalue, assinaturaCnpJs);
Console.WriteLine(resposta);
}
}
When I call the function, an AccesViolationException is thrown. The code of AssociarAssinatura has some inner prints that show that the code from the function is indeed running well. Due to this I guess the problem is related when the function is returning it's value. My best guess is that somehow I'm having issues with the calling convention. Any thoughts?
Your problem here is most likely related to your PChar type in Delphi. In C#, strings are Unicode by default, and when calling your func, there will actually be a conversion from a PChar to PWideChar, which means a new block of memory will be allocated to hold this new PWideChar. This interop and difference between how strings are handled in .NET and in Delphi is more than likely causing your AccessViolationException.
You can use the MarshalAs attribute to explicitly tell .NET how to handle the specific type:
[DllImport("SAT.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern string AssociarAssinatura(int numeroSessao,
[MarshalAs(UnmanagedType.LPStr)] string codigoDeAtivacao, [MarshalAs(UnmanagedType.LPStr)] string CNPJvalue, [MarshalAs(UnmanagedType.LPStr)] string assinaturaCNPJs);
Which will explicitly specify how the string is handled. After that, your code should be fine.
Related
A (non-COM) Delphi dll has a function being exported:
function GetQuestions(Digit1, Digit2: string; CountryISO: string):string;
I have added this dll as an existing item in Visual Studio 2012 and have set its Build Action to None, Copy to Output Directory to Copy Always.
The class containing the DllImportAttribute:
public class RefundLibrary
{
[DllImport("RefundLibrary.dll", EntryPoint = "GetQuestions",
CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr GetQuestions(string digit1, string digit2,
string countryISO);
}
When I call this method in the Page_Load of a WebForm (not sure if relevant), it throws a PInvokeStackImbalance for a possible signature mismatch on the below indicated line:
protected void Page_Load(object sender, EventArgs e)
{
IntPtr str = RefundLibrary.GetQuestions("", "", "BE"); //<- here
string result = Marshal.PtrToStringUni(str);
testp.InnerText = result;
}
I also tried to change the DllImport method's return type to string, the error is identical.
I figure the Marshal.PtrToStringUni(str) is correct as far as the Embarcadero docs go?
In RAD Studio, string is an alias for UnicodeString
Is this really a signature mismatch? What am I missing (except, obviously, a decent understanding of P/Invoke)?
You cannot call that function. It uses the Delphi only register calling convention, and uses Delphi strings. Change it to:
procedure GetQuestions(
Digit1: WideString;
Digit2: WideString;
CountryISO: WideString;
out Questions: WideString
); stdcall;
On the C# side:
[DllImport("RefundLibrary.dll")]
public static extern void GetQuestions(
[MarshalAs(UnmanagedType.BStr)]
string digit1,
[MarshalAs(UnmanagedType.BStr)]
string digit2,
[MarshalAs(UnmanagedType.BStr)]
string countryISO,
[MarshalAs(UnmanagedType.BStr)]
out string questions
);
The use of WideString/BStr is great for the out parameter. Because the content is allocated on the shared COM heap which means that the caller can deallocate it.
You could use PWideChar/LPWStr for the input parameters. That would work fine. I used WideString because I wanted to be consistent. It's up to you.
In my C++ dll named "scandll.dll" I have the following function
extern "C" __declspec(dllexport) void scanfile(char * returnstring)
{
strcpy(returnstring, "return string");
}
in my C# code I'm doing this
[DllImport("scandll.dll", CharSet = CharSet.Ansi, SetLastError = true )]
public static extern int scanfile(ref IntPtr strReturn);
and this is the method that I'm using to get the value from the dll
public void Scan()
{
string filename = "";
IntPtr ptr = new IntPtr();
scanfile(ref ptr);//Here i get the error
filename = Marshal.PtrToStringAnsi(ptr);
}
I get an exception thrown as "Attempted to read or write protected memory. This is often an indication that other memory is corrupt."
I'm following this link
Calling C++ code from C# error using references in c++ ref in c#
Any help would be appreciated.
Thanks
Your C++ code is wrong - all it's doing is changing the value of the pointer which has been passed to it to point to a constant string. The caller knows nothing about that change.
It's the same as if you'd done this:
void MyCfunction(int number)
{
number = 3;
}
and then hoped that a caller of that function would somehow see the number '3' anywhere.
You could do something like this in C++:
void MyCFunction(char* pReturnString)
{
strcpy(pReturnString, "Hello, world");
}
but you also need to make sure on the C# side that you had provided a buffer with pReturnString pointing to it. One way to do this by using a StringBuilder, and then having your C# declaration of the C function take a parameter like this:
[MarshalAs(UnmanagedType.LPStr)] StringBuilder returnString
You need to reserve space in the StringBuilder before calling into the C++ function.
In real life, C/C++ functions like this pretty much always take an additional length parameter, which allows the caller to tell the callee the maximum length of string it's allowed to copy into the buffer. There is no consistency of convention about whether the length includes the terminating \0 or not, so people are often careful about running right up to the end of the buffer.
memcpy ( destination, * source, size_t num );
Try this to assign the value to returnstring = "rturn name";
I have written a DLL in C++. One of the functions writes to a character array.
C++ Function
EXPORT int xmain(int argc, char argv[], char argv2[])
{
char pTypeName[4096];
...
//Other pTypeName ends up populated with "Portable Network Graphics"
//This code verifies that pTypeName is populated with what I think it is:
char szBuff[64];
sprintf(szBuff, pTypeName, 0);
MessageBoxA(NULL, szBuff, szBuff, MB_OK);
//The caption and title are "Portable Network Graphics"
...
//Here, I attempt to copy the value in pTypeName to parameter 3.
sprintf(argv2, szBuff, 0);
return ret;
}
C# Import
//I believe I have to use CharSet.Ansi because by the C++ code uses char[],
[DllImport("FirstDll.dll", CharSet=CharSet.Ansi)]
public static extern int xmain(int argc, string argv, ref string zzz);
C# Function
private void button2_Click(object sender, EventArgs e)
{
string zzz = "";
int xxx = xmain(2, #"C:\hhh.bmp", ref zzz);
MessageBox.Show(zzz);
//The message box displays
//MessageBox.Show displays "IstuÈst¼ÓstÄstlÄstwÄstiÑstõÖstwÍst\
// aÖst[ÖstÃÏst¯ÄstÐstòÄstŽÐstÅstpÅstOleMainThreadWndClass"
}
I have attempted to pass a parameter from C# by reference and have the C++ DLL populate the parameter. Even though I have verified that the value is correct in the DLL, gibberish gets passed to the C# application.
What can I do to write the correct string value to the C# string?
Use a StringBuilder to pass a character array that native code can fill in (see Fixed-Length String Buffers).
Declare the function:
[DllImport("FirstDll.dll", CharSet=CharSet.Ansi)]
public static extern int xmain(int argc, string argv, StringBuilder argv2);
Use it:
// allocate a StringBuilder with enough space; if it is too small,
// the native code will corrupt memory
StringBuilder sb = new StringBuilder(4096);
xmain(2, #"C:\hhh.bmp", sb);
string argv2 = sb.ToString();
Give some other information to the DLLImport call. Look at the following example of my own:
[DllImport("tcpipNexIbnk.dll", EntryPoint = "SendData", CallingConvention = CallingConvention.Cdecl)]
public static extern int Send([MarshalAs(UnmanagedType.LPWStr)]string message);
Notice two things, the CallingConvention parameter:
CallingConvention = CallingConvention.Cdecl)
Use that as it is.
And then just behind the c# string type, you can play with the different Unmanaged types using the MarshalAS instruction, that will cast your C# string parameter to the native string type you have in your c++ program:
public static extern int Send([MarshalAs(UnmanagedType.LPWStr)]string message);
Hope it helps.
I have a C++ MFC regular DLL I am calling with the following:
public static class Access3rdPartyDLL
{
public static string FilePath;
[DllImport("3rdparty.dll")]
// I have also tried LPWStr
public static extern long Download([MarshalAs(UnmanagedType.LPStr)]string sDownloadFile,
int iDeviceNum
...);
public static long DownloadToDevice()
{
long result;
string FilePath = "C:\\myfile.txt"
result = Download(FilePath, 1, ...);
// check if success or error
if(result > 0)
...
}
}
I get an error back from the DLL saying "File: 'C:\myfile.txt' not found. But its there...
I have also tried using StringBuilder but this also fails.
Could this be a problem with the DLL or am I doing something wrong?
I found this current code here: SO: equivalent char* in C#
EDIT: I have done this in C++ before and this code works:
extern "C" __declspec(dllimport) HRESULT __stdcall Download(char* sDownloadFile, int ...
which I call with:
HRESULT result = Download(file_for_download, 1, .. // where file_for_download is a char*
The only thing wrong with the P/invoke is that you are using C# long which is 64 bits, but an HRESULT is only 32 bits.
You have matching calling conventions, default marshalling for managed string is char* on the unmanaged side.
Mismatching return value size would not explain why your C# code receives a string message File: 'C:\myfile.txt' not found so your main problem most likely lies in the code that you haven't shown us.
I don't see any reason why the following wouldn't work in this simple scenario:
[DllImport( "3rdparty.dll", CharSet = CharSet.Ansi )]
static extern long Download(string sDownloadFile, int iDeviceNum, ...)
long result = Download("C:\\myfile.txt", 1, ...);
I have created a win32 dll which sends and recieves ssl data packets from our server, I am calling a dll function using P/Invoke mechanism from my C# app which does all necessary tasks.
When I call Connect(char* lpPostData)
function and I use static char postData [] array as a posting request it works fine , if I use char* lpPostData as sent parameter from my C# app for posting request it doesn't works. Is it something with conversion of C# string to char * ?? if that is the case how do i do it ?? How to debug the in Win32 dll ???
Calling the exported function from C# app:
[DllImport("testdllwm6.dll", EntryPoint = "Connect")] public
static extern int pConnect(string postdata);
string postdata="<SyncML><SyncHdr><VerDTD>1.2</VerDTD><VerProto>SyncML/1.2</VerProto><SessionID>33622878</SessionID><MsgID>1</MsgID><Target><LocURI>http://sync.com</LocURI></Target><Source><LocURI>IMEI::358997011403172</LocURI><LocName>syncfdg</LocName></Source><Meta><MaxMsgSize
xmlns=\"syncml:metinf\">10000</MaxMsgSize></Meta></SyncHdr><SyncBody><Alert><CmdID>1</CmdID><Data>201</Data><Item><Target><LocURI>contacts</LocURI></Target><Source><LocURI>./contacts</LocURI></Source><Meta><Anchor
xmlns=\"syncml:metinf\"><Last>000000T000000Z</Last><Next>20091125T122400Z</Next></Anchor></Meta></Item></Alert><Final></Final></SyncBody></SyncML>";
int j = pConnect(postdata);
Declaration is:
__declspec(dllexport) int Connect(char* lpPostData);
The function is defined as:
__declspec(dllexport) int Connect(char* lpPostData) {
LPCTSTR lpszAgent = _T("CeHttp");
DWORD dwError; DWORD sizeInResult,
sizeOutResult, sizeToWrite,
sizeWritten,dwRead; HINTERNET
hInternet=NULL; HINTERNET
hConnect=NULL; HINTERNET
hRequest=NULL; LPDWORD
pSizeInResult = &sizeInResult;
LPDWORD pSizeOutResult = &sizeOutResult;
LPDWORD pSizeToWrite = &sizeToWrite;
LPDWORD pSizeWritten = &sizeWritten; int read = 0;
char postData[637]
="<SyncML><SyncHdr><VerDTD>1.2</VerDTD><VerProto>SyncML/1.2</VerProto><SessionID>66622878</SessionID><MsgID>1</MsgID><Target><LocURI>http://sync.com</LocURI></Target><Source><LocURI>IMEI::358997011403172</LocURI><LocName>new123</LocName></Source><Meta><MaxMsgSize
xmlns=\"syncml:metinf\">10000</MaxMsgSize></Meta></SyncHdr><SyncBody><Alert><CmdID>1</CmdID><Data>201</Data><Item><Target><LocURI>contacts</LocURI></Target><Source><LocURI>./contacts</LocURI></Source><Meta><Anchor
xmlns=\"syncml:metinf\"><Last>000000T000000Z</Last><Next>20091125T122400Z</Next></Anchor></Meta></Item></Alert><Final></Final></SyncBody></SyncML>";
LPCWSTR lpszHeaders =_T("Content-Type: application/vnd.sync+xml");
BOOL bResult;
if(!HttpSendRequest(hRequest,lpszHeaders,wcslen(lpszHeaders),
lpPostData,strlen(lpPostData)))
{
dwError = GetLastError();
printf(" not HttpSendRequest");
return read;
}
return read;
The failure point is very obvious. Windows CE is Unicode. The string in C# is a wide-character array, the char[] in C is a multibyte. You're mixing the two, and that is bad, bad, bad.
I mean you're mixing them in the same call, sending wide headers and multibyte postData to HttpSendRequest? That certainly can't be right.
Change the Connect function to look like this:
int Connect(TCHAR* lpPostData)
try it again, and come back with the results.
Of course this also means you need to change the strlen call as well.
As a side note, I don't understand why you would call into C++ for this call anyway. You could do it right from your C# app.
seems the dll is a MFC extension dll, maybe only can be callec by MFC application. i am not sure.
Is it something with conversion of C# string to char * ??
The default CharSet used by the .Net Interop Marshaler is Ansi.
If you want use Unicode(LPCWSTR) parameters,
you can try:
[DllImport("testdllwm6.dll", EntryPoint = "Connect", CharSet=CharSet.Unicode)]
public static extern int pConnect(string postdata);
BTW, you can refer to .Net 2.0 Interoperability Recipes: A Problem-Solution Approach
for more information.
You need to add the MarshalAs attribute to the string parameter so the .NET runtime knows how to marshal it; by default strings are marshaled as Unicode, but you want ANSI:
[DllImport("testdllwm6.dll", EntryPoint="Connect")]
public static extern int Connect([MarshalAs(UnmanagedType.LPStr)] string lpPostData);
use
System.Text.StringBuilder
pass that to your function