I use the solution from Robert Giesecke http://sites.google.com/site/robertgiesecke/Home/uploads/unmanagedexports to export functions from managed code to unmanaged code. The solution works pretty good, but there is a problem using the solution with office (excel).
I tried to develop a DLL which
- connects to SQLServer
- by using SQLAuthentication
- passing the name of the database
- passing the SQL-Statement
- and returning the result
so the user of the DLL cannot see the password, I know it can be done by using special tools. This way to do it is sufficient for our requirement.
The code in C#:
using System;
using System.Collections.Generic;
using System.Text;
using RGiesecke.DllExport;
using ADODB;
using System.Xml;
using System.IO;
using System.Security.Cryptography;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace SqlConRVT
{
public static class SqlConRVT
{
[DllExport("SqlConRVT", CallingConvention = CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.IDispatch)]
public static Object OpenRecordset ([MarshalAs(UnmanagedType.AnsiBStr)] string databaseName,
[MarshalAs(UnmanagedType.AnsiBStr)] string commandText)
{
if (String.IsNullOrEmpty( databaseName)) throw new ArgumentNullException("databaseName");
if (String.IsNullOrEmpty( commandText)) throw new ArgumentNullException("commandText");
try
{
var connection = new ADODB.Connection();
var intConnectionMode = (int) ConnectModeEnum.adModeUnknown;
var username = Crypto.DecryptMessage("XEj0PC2lMIs=", "FinON");
var password = Crypto.DecryptMessage("7YIDPO7eBoFAhskAX6JGAg==", "FinON");
connection.Open("Provider='SQLOLEDB';Data Source='PETER-PC\\SQLEXPRESS'; Initial Catalog='" + databaseName + "';", username, password, intConnectionMode);
var rs = new Recordset();
rs.Open(commandText, connection, CursorTypeEnum.adOpenForwardOnly, LockTypeEnum.adLockOptimistic, -1);
return rs;
}
catch (Exception ex)
{
// an exception in a DLL will most likely kill the excel process
// we really dont want that to happen
MessageBox.Show(ex.Message, ex.GetType().Name, MessageBoxButtons.OK, MessageBoxIcon.Error);
return null;
}
}
}
public partial class Crypto
{
public static string DecryptMessage(string encryptedBase64, string password)
{
TripleDESCryptoServiceProvider des = new TripleDESCryptoServiceProvider();
des.IV = new byte[8];
PasswordDeriveBytes pdb = new PasswordDeriveBytes(password, new byte[0]);
des.Key = pdb.CryptDeriveKey("RC2", "MD5", 128, new byte[8]);
byte[] encryptedBytes = Convert.FromBase64String(encryptedBase64);
MemoryStream ms = new MemoryStream(encryptedBase64.Length);
CryptoStream decStream = new CryptoStream(ms, des.CreateDecryptor(), CryptoStreamMode.Write);
decStream.Write(encryptedBytes, 0, encryptedBytes.Length);
decStream.FlushFinalBlock();
byte[] plainBytes = new byte[ms.Length];
ms.Position = 0;
ms.Read(plainBytes, 0, (int)ms.Length);
decStream.Close();
return Encoding.UTF8.GetString(plainBytes);
}
}
}
My Code in VBA:
Declare Function SqlConRVT Lib _
"C:\Users\Administrator\Documents\Visual Studio 2008\Projects\SqlConRVT\SqlConRVT\bin\Debug\x86 \SqlConRVT.dll" (ByVal databaseName As String, ByVal commandText As String) As Object
Sub SQLCon()
Dim x As Object
x = SqlConRVT("Adressen", "Select * from tblAdressen")
End Sub
In the C# DLL and in all client applications I reference "Microsoft ActiveX Data Object 2.8 Library".
I tried to use the exported 64bit DLL with C#, works fine. I tried to use the exported 64bit DLL as a static class with C#, works fine. I tried to use the exported 32bit DLL with VB6, the application crashes. I tried to use the exported 32bit DLL with VBA (Excel), the application crashes.
I checked the existence of the exported function in the 32bit DLL with dependency walker.
Why can't I use the 32bit DLL with office (Excel)?
Of course I have 32-bit Office!
Your "simplified example" works fine, the class is given back correctly!
I reduced my example:
using System;
using System.Collections.Generic;
using System.Text;
using RGiesecke.DllExport;
using ADODB;
using System.Xml;
using System.IO;
using System.Security.Cryptography;
using System.Runtime.InteropServices;
using System.Windows.Forms;
[ComVisible(true), ClassInterface(ClassInterfaceType.AutoDual)]
static class SqlConRVT
{
[DllExport(CallingConvention = CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.IDispatch)]
//[return: MarshalAs(UnmanagedType.I4)]
//[return: MarshalAs(UnmanagedType.AnsiBStr)]
static Object GetNewObject([MarshalAs(UnmanagedType.AnsiBStr)] String databaseName,
[MarshalAs(UnmanagedType.AnsiBStr)] String commandText)
{
var test = new StreamReader("C:\\lxbu.log");
return test;
//var rs = new Recordset();
//return rs;
//int A = 1;
//return A;
//String A = commandText;
//return A;
}
}
My Code in VBA:
Declare Function GetNewObject Lib "C:\Users\Administrator\Documents\Visual Studio 2008\Projects\An\An\bin\Debug\x86\An.dll" (ByVal databaseName As String, ByVal commandText As String) As Object
Sub An1()
Dim x As Object
Set x = GetNewObject("Adressen", "Select * from tblAdressen")
End Sub
If I try to return an int-value -> works correct! If I try to return a string-value -> works correct! If I try to return an object (e.g a recordset-object or a streamreader-object) Excel crashes? There must be a stupid little error!
Thank you Robert - as everytime your code is perfect! I can see the content of the streamreader object if I use the following code in VBA
MsgBox instance.ReadtoEnd()
and the result is:
"abc Äö ~éêè @dkfjf -> Added fro VBA"
The problem is definitly the ADODB.connection!!!!!
[DllExport(CallingConvention = CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.IDispatch)]
static Object GetNewObject([MarshalAs(UnmanagedType.LPStr)] String databaseName, [MarshalAs(UnmanagedType.LPStr)] String commandText)
{
//if (String.IsNullOrEmpty(databaseName)) throw new ArgumentNullException("databaseName");
//if (String.IsNullOrEmpty(commandText)) throw new ArgumentNullException("commandText");
{
var connection = new ADODB.Connection();
//var rs = new Recordset();
StreamReader sr = new StreamReader("C:\\lxbu.log");
//var intConnectionMode = (int)ConnectModeEnum.adModeUnknown;
//var username = "...";
//var password = ".........";
//connection.Open("Provider='SQLOLEDB';Data Source='PETER-PC\\SQLEXPRESS'; Initial Catalog='" + databaseName + "';", username, password, intConnectionMode);
//rs.Open(commandText, connection, CursorTypeEnum.adOpenForwardOnly, LockTypeEnum.adLockOptimistic, -1);
return sr;
}
}
If I use "var connection = new ADODB.Connection();" Excel crashes. The problem is using ADODB in 32bit DLL (C# and using the 64bit-DLL no problem). There's no problem (!!!) with your solution!