Я использую решение от Robert Giesecke http://sites.google.com/site/robertgiesecke/Home/uploads/unmanagedexports для экспорта функций из управляемого кода в неуправляемый код. Решение работает довольно хорошо, но есть проблема с использованием решения с офисом (Excel).
Я пытался разработать DLL, которая
- подключается к SQLServer
- с использованием SQLAuthentication
- передавая имя базы данных
- прохождение SQL-оператора
- и возвращая результат
чтобы пользователь DLL не мог видеть пароль, я знаю, что это можно сделать с помощью специальных инструментов. Этот способ достаточно для нашего требования.
код в 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);
}
}
}
Мой код в 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
В C # DLL и во всех клиентских приложениях я ссылаюсь на «Microsoft ActiveX Data Object 2.8 Library».
Я пытался использовать экспортированную 64-битную DLL с C #, работает нормально.
Я пытался использовать экспортированную 64-битную DLL в качестве статического класса с C #, отлично работает.
Я пытался использовать экспортированную 32-битную DLL с VB6, приложение вылетает.
Я пытался использовать экспортированную 32-битную DLL с VBA (Excel), приложение вылетает.
Я проверил существование экспортированной функции в 32-битной DLL с помощью обходчика зависимостей.
Почему я не могу использовать 32-битную DLL с офисом (Excel)?
Конечно, у меня 32-битный Office!
Ваш "упрощенный пример" работает отлично, класс возвращен правильно!
Я сократил свой пример:
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;
}
}
Мой код в 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
Если я попытаюсь вернуть int-значение -> работает правильно!
Если я пытаюсь вернуть строковое значение -> работает правильно!
Если я пытаюсь вернуть объект (например, объект набора записей или объект потокового чтения), происходит сбой Excel? Там должна быть маленькая глупая ошибка!
Спасибо, Роберт, - как всегда, твой код идеален! Я могу видеть содержимое объекта Streamreader, если я использую следующий код в VBA
MsgBox instance.ReadtoEnd()
и результат:
"abc Äö ~ éêè @dkfjf -> Добавлено для VBA"
Проблема определенно в 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;
}
}
Если я использую "var connection = new ADODB.Connection ();" Excel вылетает. Проблема заключается в использовании ADODB в 32-битной DLL (C # и использование 64-битной DLL не проблема). Нет проблем (!!!) с вашим решением!