Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people, just like you, are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
Solved

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

Posted on 2011-03-25
9
430 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
Free Tool: Path Explorer

An intuitive utility to help find the CSS path to UI elements on a webpage. These paths are used frequently in a variety of front-end development and QA automation tasks.

One of a set of tools we're offering as a way of saying thank you for being a part of the community.

 

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

Networking for the Cloud Era

Join Microsoft and Riverbed for a discussion and demonstration of enhancements to SteelConnect:
-One-click orchestration and cloud connectivity in Azure environments
-Tight integration of SD-WAN and WAN optimization capabilities
-Scalability and resiliency equal to a data center

Question has a verified solution.

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

Suggested Solutions

Title # Comments Views Activity
Hey Audio! 9 44
How to measure current at integrated circuit (IC) power pin using Current Probe ? 6 32
Sort GridView by ID Descending 1 17
How to read text with RegEx... 2 25
This tutorial is posted by Aaron Wojnowski, administrator at SDKExpert.net.  To view more iPhone tutorials, visit www.sdkexpert.net. This is a very simple tutorial on finding the user's current location easily. In this tutorial, you will learn ho…
This article introduced a TextBox that supports transparent background.   Introduction TextBox is the most widely used control component in GUI design. Most GUI controls do not support transparent background and more or less do not have the…
The goal of this video is to provide viewers with basic examples to understand recursion 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.

840 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