Solved

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

Posted on 2011-03-25
9
423 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
Comment Utility
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
Comment Utility
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
Comment Utility
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
 

Author Comment

by:portford_ts
Comment Utility
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
How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

 

Author Comment

by:portford_ts
Comment Utility
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
Comment Utility
Can you give me the complete C++ code, including the exported function's signature?
0
 

Author Comment

by:portford_ts
Comment Utility
0
 
LVL 33

Accepted Solution

by:
Todd Gerbert earned 250 total points
Comment Utility
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
Comment Utility
And just like that.... it works. Thanks!
0

Featured Post

How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

Join & Write a Comment

This article is for Object-Oriented Programming (OOP) beginners. An Interface contains declarations of events, indexers, methods and/or properties. Any class which implements the Interface should provide the concrete implementation for each Inter…
This is a short and sweet, but (hopefully) to the point article. There seems to be some fundamental misunderstanding about the function prototype for the "main" function in C and C++, more specifically what type this function should return. I see so…
The goal of this video is to provide viewers with basic examples to understand and use structures in the C programming language.
The goal of this video is to provide viewers with basic examples to understand and use switch statements in the C programming language.

743 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

Need Help in Real-Time?

Connect with top rated Experts

18 Experts available now in Live!

Get 1:1 Help Now