2016-03-15 1 views
1

Я использую Code :: Blocks для записи DLL в C, который я намерен использовать в сценарии Winbatch, но на данный момент я ' m тестируя его с помощью Excel VBA. В момент, когда скрипт VBA запускает функцию getVersion() библиотеки DLL, Excel аварийно завершает работу. Поиск в Интернете в течение нескольких дней: я не нашел правильного решения.Сценарий Excel VBA сбой при вызове функции DDL, написанной на C, которая возвращает строку BSTR

: C код, как этот

#define MQTTPUB_VERSION "V3.1.1Test" 
DLL_EXPORT BSTR __stdcall WINAPI getVersion(void) 
{ 
    return MQTTPUB_VERSION ; 
} 

код VBA, как это

Public Declare Function mqttPubMsg Lib "mqttPubMsg.dll" _ 
(ByVal MQTT_ADDRESS As String, ByVal MQTT_CLIENTID As String, ByVal MQTT_TOPIC As String, ByVal MQTT_PAYLOAD As String) As Long 
Public Declare Function getVersion Lib "mqttPubMsg.dll"() As String 
Sub Test_DDL_mqttPubMsg() 
' 
' to test mqttPubMsg.DLL used in Visual Basic (VBA) 
' 
Dim DLLVersion As String * 35 
Dim WorkDir As String 
DLLVersion = Space(35) 
WorkDir = ThisWorkbook.Path 
ChDir WorkDir 

If Dir(WorkDir & "\mqttPubMsg.dll", vbDirectory) = vbNullString Then 
    MsgBox "DLL not found" 
Else 
    On Error GoTo DLLError 
    DLLVersion = getVersion() 'Excel crashes on executing this statement 
End If 
MsgBox ("Version DDL: " & DLLVersion) 
Exit Sub 
DLLError: 
    MsgBox ("DDL error") 
End Sub 

программа на языке С, вызывающей DLL и функция GetVersion() работает нормально.

Что может быть причиной этой ошибки времени выполнения и как ее решить.

Заранее спасибо.

ответ

0

Я недавно не программировал Windows, и у меня нет готовой среды для экспериментов, но вот некоторые мысли.

Наиболее вероятная проблема заключается в том, что getVersion() возвращает BSTR, что также (возможно) ожидается кодом вызывающего VBA, когда тип возврата объявлен как String. Теперь BSTR должен предшествовать в памяти с помощью 4-байтового префикса длины, см. https://msdn.microsoft.com/en-us/library/windows/desktop/ms221069%28v=vs.85%29.aspx. getVersion() просто возвращает указатель на «V», 1-й символ строки, а когда VBA пытается интерпретировать предыдущие 4 байта, перед «V», как длина, это катастрофа ...

Вот некоторые возможные решения, которые я вижу:

  • Построить BSTR с помощью функции SysAllocString() в getVersion(), как рекомендовано в упомянутой выше статье.
  • Проведите DLLVersion строку до getVersion() по ссылке и см. , если вы можете как-то заполнить ее внутри getVersion(). Помните, что вы имеете дело с Unicode здесь.

Несколько других вещей, чтобы играть с из любопытства:

  • Убедитесь, что функция DLL на самом деле может быть вызвана из VBA; let getVersion() return void и посмотреть, просто ли вызов этой функции сбоев Excel. Если да, то проблема глубже.
  • Сделайте getVersion() верните целое число и посмотрите, можете ли вы его получить в свой код VBA.
  • Сделать getVersion() взять целое число в качестве параметра, передать ему целое число из VBA и посмотреть, может ли код C получить правильное значение.
  • У вас есть достаточно большой глобальный массив символов в вашем коде на C, поместите строку версии (Unicode!), Начиная со смещения 4 в массиве, и заполнить 1-й 4 байта с помощью (мало-конечного, я думаю) целого числа , указывающий длину строки Юникода, исключая терминатор NULL .Make getVersion() верните указатель на buf + 4, где buf - это глобальный массив символов.

Извините, что моя память не свежа на этом, и я не могу предоставить более конкретные советы, но, надеюсь, это помогает.

+0

Большое спасибо за ваш совет, я попробую его, а также жду некоторые другие советы. – JanWillemT

0

Следующее решение для 64-разрядной системы с установленным Office 2013.

код C:

Чтобы получить код C работает для меня, я должен был немного изменить объявление, а также использовать .def файл, чтобы убедиться, DLL экспортировал имя нужной функции.

#define MQTTPUB_VERSION L"V3.1.1Test" // notice the L - it casts better to BSTR 
extern "C" BSTR __stdcall WINAPI getVersion(void) 
{ 
    return MQTTPUB_VERSION; 
} 

VBA код:

На стороне VBA, мой подход был

  1. Получить адрес целевой строки, объявляя тип возвращаемого GetVersion в Лунлун вместо строки ,
  2. Выделите пустую строку с таким же количеством символов
  3. Скопируйте память с исходного адреса на адрес целевой строки.

    Private Declare PtrSafe Sub CopyMemUC Lib "kernel32" Alias "RtlMoveMemory" _ 
         (ByVal Destination As LongLong, ByVal Source As LongLong, ByVal Length As Long) 
        Private Declare PtrSafe Function wcslen Lib "msvcrt" _ 
         (ByVal Source As LongLong) As Long 
        Private Declare PtrSafe Function _ 
         getVersionAddress Lib "D:\codeblocks\xltest\bin\Debug\mqttPubMsg.dll" _ 
         Alias "getVersion" _ 
         () As LongLong 
    
        Function getVersion() 
         Dim sAddress As LongLong, sTemp As String, sLen 
         sAddress = getVersionAddress 
         sLen = wcslen(sAddress) ' Length (2 bytes per character) 
         sTemp = String(sLen, " ") ' Allocates memory 
         CopyMemUC ByVal StrPtr(sTemp), getVersionAddress, sLen * 2 
         getVersion = sTemp 
        End Function 
    

Тестирование, если все работает:

Оценка ?getVersion в ближайшем окне возвращает строку без сбоев:

enter image description here

На 32-битных систем, или VBA версия < 7:

Не забывайте удалить PtrSafe ключевые слова и изменить все LongLong заметок в Long. Альтернативно, используйте проверку компилятора - пример можно найти здесь: how to make VBA code compatible for office 2010 - 64 bit version and older office versions

Смежные вопросы