• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 464
  • Last Modified:

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

0
portford_ts
Asked:
portford_ts
  • 5
  • 4
1 Solution
 
Todd GerbertIT ConsultantCommented:
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. ;)
0
 
portford_tsAuthor Commented:
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

0
 
Todd GerbertIT ConsultantCommented:
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
0
Concerto Cloud for Software Providers & ISVs

Can Concerto Cloud Services help you focus on evolving your application offerings, while delivering the best cloud experience to your customers? From DevOps to revenue models and customer support, the answer is yes!

Learn how Concerto can help you.

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

 error
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.
0
 
portford_tsAuthor Commented:
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

0
 
Todd GerbertIT ConsultantCommented:
Can you give me the complete C++ code, including the exported function's signature?
0
 
portford_tsAuthor Commented:
0
 
Todd GerbertIT ConsultantCommented:
Sorry, I'm such a doofus, I should have noticed this much earlier - I didn't catch that your C# declaration is:

[DllImport("CEncryptDLL.dll", CallingConvention = CallingConvention.StdCall, CharSet=CharSet.Ansi, EntryPoint = "DecryptData")]
public static extern int DecryptData(string strEncryptedData, long lDataSize, string KeyPath, StringBuilder strDecryptedData);


But should be:
[DllImport("CEncryptDLL.dll")]
public static extern int DecryptData(string strEncryptedData, int lDataSize, string KeyPath, StringBuilder strDecryptedData);

A long in C++ is 32-bits, but in C# a long is 64-bits - an int in C# is 32-bits, and would correspond to your C++ long. Use DWORD to avoid confusion. ;)

Also note that the by default, in C#, the calling convention is stdcall and the CharSet is Ansi, so you don't need to specify any additional parameters in the DllImport attribute.
0
 
portford_tsAuthor Commented:
And just like that.... it works. Thanks!
0

Featured Post

Free Tool: Port Scanner

Check which ports are open to the outside world. Helps make sure that your firewall rules are working as intended.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

  • 5
  • 4
Tackle projects and never again get stuck behind a technical roadblock.
Join Now