Link to home
Start Free TrialLog in
Avatar of portford_ts
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;
}

Open in new window

Avatar of Todd Gerbert
Todd Gerbert
Flag of United States of America image

Well, the C++ DLL is taking char*, not char**, as parameters so I think that would be a simple C-style (LPCSTR) string?

If I'm interpreting that right, I believe you can declare your DllImport as:
[DllImport("CEncryptDLL.dll", CallingConvention= CallingConvention.StdCall, EntryPoint = "DecryptData")]
public static extern int DecryptData(string strEncryptedData, long lDataSize, string KeyPathBA, StringBuilder strDecryptedData);

Open in new window


And call it with:
string encryptedData = "ABCDEF";
StringBuilder decrypted = new StringBuilder();
decrypted.EnsureCapacity(65535);
DecryptData(encryptedData, encryptedData.Length, @"C:\folder\file.ini", decrypted);
Console.WriteLine(decrypted.ToString());

Open in new window


Stay away from "unsafe" usage, 'tis almost never necessary - and, as the keyword itself suggests, is very unsafe. ;)
Avatar of portford_ts
portford_ts

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;

}

Open in new window

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:
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;
}

Open in new window


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());
}

Open in new window


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
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:

 User generated image
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;
            }

            
        }

Open in new window



This is puzzling to me, however I do not have much experience with C++. Thanks again for the help.
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 );

Open in new window

Can you give me the complete C++ code, including the exported function's signature?
ASKER CERTIFIED SOLUTION
Avatar of Todd Gerbert
Todd Gerbert
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
And just like that.... it works. Thanks!