Solved

Problem passing a value (array) back from a C++ dll to a C# app?

Posted on 2011-03-25
9
428 Views
Last Modified: 2012-05-11
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
Comment
Question by:portford_ts
  • 5
  • 4
9 Comments
 
LVL 33

Expert Comment

by:Todd Gerbert
ID: 35218076
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
 

Author Comment

by:portford_ts
ID: 35219329
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
 
LVL 33

Expert Comment

by:Todd Gerbert
ID: 35220613
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
Courses: Start Training Online With Pros, Today

Brush up on the basics or master the advanced techniques required to earn essential industry certifications, with Courses. Enroll in a course and start learning today. Training topics range from Android App Dev to the Xen Virtualization Platform.

 

Author Comment

by:portford_ts
ID: 35236812
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
 

Author Comment

by:portford_ts
ID: 35236847
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
 
LVL 33

Expert Comment

by:Todd Gerbert
ID: 35240404
Can you give me the complete C++ code, including the exported function's signature?
0
 

Author Comment

by:portford_ts
ID: 35242359
0
 
LVL 33

Accepted Solution

by:
Todd Gerbert earned 250 total points
ID: 35242806
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
 

Author Comment

by:portford_ts
ID: 35242933
And just like that.... it works. Thanks!
0

Featured Post

Gigs: Get Your Project Delivered by an Expert

Select from freelancers specializing in everything from database administration to programming, who have proven themselves as experts in their field. Hire the best, collaborate easily, pay securely and get projects done right.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

Exception Handling is in the core of any application that is able to dignify its name. In this article, I'll guide you through the process of writing a DRY (Don't Repeat Yourself) Exception Handling mechanism, using Aspect Oriented Programming.
Performance in games development is paramount: every microsecond counts to be able to do everything in less than 33ms (aiming at 16ms). C# foreach statement is one of the worst performance killers, and here I explain why.
The goal of this video is to provide viewers with basic examples to understand and use pointers in the C programming language.
The goal of this video is to provide viewers with basic examples to understand opening and reading files in the C programming language.

805 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question