portford_ts
asked on
Problem passing a value (array) back from a C++ dll to a C# app?
I am having a problem passing an array (or at least its pointer) from a C++ dll to a C# app. It is a decryption DLL implementing a standard blowfish algorithm. My understanding is that the C# app provides the pointer and the C++ DLL fills the memory. I have created a C++ test app that successfully does this, but I have been unable to figure out the same process for C#.
C# code snippet:
[DllImport("CEncryptDLL.dll", CallingConvention= CallingConvention.StdCall, EntryPoint = "DecryptData")]
public static extern int DecryptData(IntPtr strEncryptedData, long lDataSize, IntPtr KeyPathBA, IntPtr strDecryptedData);
unsafe
{
IntPtr pDecoded = Marshal.UnsafeAddrOfPinnedArrayElement(encrypted, 0);
IntPtr pDecryptedData = Marshal.UnsafeAddrOfPinnedArrayElement(DecryptedData, 0);
IntPtr pkeyPathBA = Marshal.UnsafeAddrOfPinnedArrayElement(keyPathBA, 0);
try
{
int myReturnVal = DecryptData(pDecoded, (long)decoded.Length, pkeyPathBA, pDecryptedData);
string testStr = Marshal.PtrToStringAuto(pDecryptedData);
}
catch (Exception f)
{
string mess = f.Message;
}
}
C++ DLL snippet:
int __stdcall DecryptData( const char * strEncryptedData,
const long lEncryptedDataLength,
char * str_iniFile,
char * strDecryptedData )
{
....
unsigned char * cptr;
unsigned char * cptr2;
....
cptr = (unsigned char *)ucPlainText;
cptr2 = (unsigned char *)strDecryptedData;
CBlowFish oBlowFish( ucKey, iKeyLen );
oBlowFish.Decrypt( cptr, cptr2, lDataSize );
cout << "strDecryptedData = " << strDecryptedData;
cout << endl;
return 1;
}
ASKER
Thank you for the response, a value is now being returned (i also had to add the [Out] keyword in the function declaration). However, not the value expected or desired. When I call the DLL from a C++ app I get a different output than when called from a C# app. The output from the C++ is correct while the C# output still is not. I have included the C++ test app code below to compare. Thanks again
C# call:
[DllImport("CEncryptDLL.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "DecryptData")]
public static extern int DecryptData(string strEncryptedData, long lDataSize, string KeyPathBA, [Out] StringBuilder strDecryptedData);
protected void Page_Load(object sender, EventArgs e)
{
try
{
string encryptedData = enc_case_num; //ex: %E9%F4%76%AC%B3
StringBuilder decrypted = new StringBuilder();
decrypted.EnsureCapacity(65535);
DecryptData(encryptedData, encryptedData.Length, @"C:\Development\eJournalDEV\DPSS-eJournal.Web\cencrypt.ini", decrypted);
string testStr = decrypted.ToString();
}
catch (Exception f)
{
string mess = f.Message;
}
}
C++ test app successful call:
int __stdcall DecryptData( const char * strEncryptedData,
const long lEncryptedDataLength,
char * str_iniFile,
char * strDecryptedData )
{
unsigned char ucKey[64];
unsigned char ucPlainText[257];
unsigned char ucCipherText[257];
char strKey[129];
int ist, iKeyLen;
char strdata[257];
unsigned long lDataSize = 256;
int i;
unsigned char * cptr;
unsigned char * cptr2;
const unsigned char * siPtr;
ini_t iniTable[4];
cout << endl;
cout << "Entered DecryptData...";
cout << endl;
memset( strKey, 0, sizeof(strKey) );
iniTable[0].Key = "Key";
iniTable[0].Format = "%s";
iniTable[0].Data = strKey;
iniTable[0].ElemSize = 32;
iniTable[0].Array = NULL;
iniTable[0].HashKey = 0;
iniTable[1].Key = NULL;
iniTable[1].Format = NULL;
iniTable[1].Data = NULL;
iniTable[1].ElemSize = 0;
iniTable[1].Array = NULL;
iniTable[1].HashKey = 0;
iniTable[2].Key = NULL;
iniTable[2].Format = NULL;
iniTable[2].Data = NULL;
iniTable[2].ElemSize = 0;
iniTable[2].Array = NULL;
iniTable[2].HashKey = 0;
iniTable[3].Key = NULL;
iniTable[3].Format = NULL;
iniTable[3].Data = NULL;
iniTable[3].ElemSize = 0;
iniTable[3].Array = NULL;
iniTable[3].HashKey = 0;
INI_ReadFile( iniTable, str_iniFile );
// cout << "strKey = " << strKey;
// cout << endl;
memset( strdata, 0, sizeof(strdata) );
memset( ucCipherText, 0, sizeof(ucCipherText) );
memset( ucPlainText, 0, sizeof(ucPlainText) );
//
// A max of 56 characters for the key
//
memset( ucKey, 0, sizeof(ucKey) );
if( strlen( strKey ) > 56 )
{
memcpy( ucKey, strKey, 56 );
iKeyLen = 56;
}
else
{
strcpy( (char *)ucKey, strKey );
iKeyLen = strlen(strKey);
}
// cout << "strKey = " << strKey;
// cout << endl;
// cout << "ist1 = " << ist;
// cout << endl;
if( strEncryptedData != NULL )
{
// cout << "strEncryptedData = " << strEncryptedData;
// cout << endl;
int i = URLDecode( strEncryptedData, (char *)ucPlainText );
lDataSize = strlen( (char *)ucPlainText );
if( lDataSize <= 8 )
lDataSize = 8;
else
lDataSize += (8 - lDataSize%8);
cptr = (unsigned char *)ucPlainText;
cptr2 = (unsigned char *)strDecryptedData;
CBlowFish oBlowFish( ucKey, iKeyLen );
oBlowFish.Decrypt( cptr, cptr2, lDataSize );
cout << "strDecryptedData = " << strDecryptedData;
cout << endl;
return 1;
}
return 0;
}
C++ isn't really my strong suit, so I just came up with this silly little test DLL, which just reverses the incoming string and copies it to the output string:
And this C# code:
This works correctly for me...the .Net rutime will know to marshal the string and StringBuilder as char* for you, and append a terminating null (you also shouldn't need the [In] or [Out] parameters). I used outString.EnsureCapacity to ensure enough memory was allocated
extern "C" __declspec(dllexport) void WINAPI TestFunction(char* inString, DWORD len, char* outString)
{
int i, j = 0;
for(i = len - 2; i > -1; i--)
{
outString[j] = inString[i];
j++;
}
outString[len] = 0;
return;
}
And this C# code:
[DllImport("TestInteropDll.dll", CharSet=CharSet.Ansi)]
private static extern void TestFunction(string inString, int len, StringBuilder outString);
private void button1_Click(object sender, EventArgs e)
{
string inString = textBox1.Text;
int len = inString.Length + 1; // Include room for null char
StringBuilder outString = new StringBuilder();
outString.EnsureCapacity(len);
TestFunction(inString, len, outString);
MessageBox.Show(outString.ToString());
}
This works correctly for me...the .Net rutime will know to marshal the string and StringBuilder as char* for you, and append a terminating null (you also shouldn't need the [In] or [Out] parameters). I used outString.EnsureCapacity to ensure enough memory was allocated
ASKER
I have created a similar bare-bones C# app to call the DLL. It builds and runs but when i step through the DLL, the values received are not correct. When called by C# app the 3rd parameter for DecryptData [char * str_iniFile] has the value of 0x000000 <Bad Ptr> and the 4th parameter [strDecryptedData] is actually being assigned the value that is passed as the 3rd. Once the DLL finishes, I then get the following error upon return to the C# app:
C# call:
This is puzzling to me, however I do not have much experience with C++. Thanks again for the help.
C# call:
[DllImport("CEncryptDLL.dll", CallingConvention = CallingConvention.StdCall, CharSet=CharSet.Ansi, EntryPoint = "DecryptData")]
public static extern int DecryptData(string strEncryptedData, long lDataSize, string KeyPath, StringBuilder strDecryptedData);
static void Main(string[] args)
{
string enc_case_num = "%E9%F4%76%AC%B3";
string encryptedData = Console.ReadLine();
try
{
StringBuilder decrypted = new StringBuilder();
decrypted.EnsureCapacity(65535);
int returnVal = DecryptData(enc_case_num, enc_case_num.Length+1, "strKeyPath-placeholder", decrypted);
string testStr = decrypted.ToString();
Console.Write(returnVal.ToString());
Console.ReadLine();
}
catch (Exception f)
{
string mess = f.Message;
}
}
This is puzzling to me, however I do not have much experience with C++. Thanks again for the help.
ASKER
Here is the C++ call that works as expected:
char strEncryptedData[256];
char strDecryptedData[256];
char strUnEncryptedData[256];
long lDataSize;
char strKeyPath[256];
/* get handle to dll */
HINSTANCE hGetProcIDDLL = LoadLibrary( "CEncryptDLL.dll" );
/* get pointer to the function in the dll*/
FARPROC lpfnGetProcessID = GetProcAddress(HMODULE (hGetProcIDDLL),"EncryptData");
FARPROC lpfnGetProcessID2 = GetProcAddress(HMODULE (hGetProcIDDLL),"DecryptData");
/*
Define the Function in the DLL for reuse. This is just prototyping the dll's function.
A mock of it. Use "stdcall" for maximum compatibility.
*/
typedef int (__stdcall * pICFUNC)( const char *, const long, char *, char * );
pICFUNC EncryptData;
EncryptData = pICFUNC( lpfnGetProcessID );
pICFUNC DecryptData;
DecryptData = pICFUNC( lpfnGetProcessID2 );
memset( strUnEncryptedData, 0, sizeof(strUnEncryptedData) );
memset( strEncryptedData, 0, sizeof(strEncryptedData) );
strcpy( (char *)strUnEncryptedData, "%94%2E%E4%1DC%9C%E5%9F" );
lDataSize = strlen( (char *)strUnEncryptedData );
strcpy( strKeyPath, "cencrypt.ini" );
printf( "UnEncrypted Data: \n%s\nData Length: %d\n\n", strUnEncryptedData, lDataSize );
/* The actual call to the function contained in the dll */
//int intMyReturnVal = EncryptData( strUnEncryptedData, lDataSize, strKeyPath, strEncryptedData );
//printf( "Encrypted Data:\n%s\nReturn Value: %d\n\n", strEncryptedData, intMyReturnVal );
/*
strEncryptedData[0] = 43;
strEncryptedData[1] = 149;
strEncryptedData[2] = 16;
strEncryptedData[3] = 81;
strEncryptedData[4] = 31;
strEncryptedData[5] = 209;
strEncryptedData[6] = 230;
strEncryptedData[7] = 49;
strEncryptedData[8] = 0;
*/
strcpy( (char *)strEncryptedData, "%94%2E%E4%1DC%9C%E5%9F" );
lDataSize = strlen( (char *)strEncryptedData );
int intMyReturnVal = DecryptData( strEncryptedData, lDataSize, strKeyPath, strDecryptedData );
printf( "Decrypted Data:\n%s\nReturn Value: %d\n\n", strDecryptedData, intMyReturnVal );
Can you give me the complete C++ code, including the exported function's signature?
ASKER
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
And just like that.... it works. Thanks!
If I'm interpreting that right, I believe you can declare your DllImport as:
Open in new window
And call it with:
Open in new window
Stay away from "unsafe" usage, 'tis almost never necessary - and, as the keyword itself suggests, is very unsafe. ;)