<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
	<title type="html"><![CDATA[Серый форум &mdash; VBA: Динамический вызов WinAPI в Excel]]></title>
	<link rel="self" href="http://forum.script-coding.com/extern.php?action=feed&amp;tid=4870&amp;type=atom" />
	<updated>2010-08-25T20:25:37Z</updated>
	<generator>PunBB</generator>
	<id>http://forum.script-coding.com/viewtopic.php?id=4870</id>
		<entry>
			<title type="html"><![CDATA[VBA: Динамический вызов WinAPI в Excel]]></title>
			<link rel="alternate" href="http://forum.script-coding.com/viewtopic.php?pid=38910#p38910" />
			<content type="html"><![CDATA[<p><em>Без гарантий. Используете на свой страх и риск.</em></p><p>Необязательно декларировать вызовы WinAPI в VBA (на примере Excel) с помощью Declare Function, можно вызывать WinAPI в VBA динамически, используя передачу управления коду с помощью функций CallWindowProc, либо EnumWindows: </p><p>1) Модуль <strong>DLLCALL_CallWindowProc.bas</strong>:<br /></p><div class="codebox"><pre><code>
&#039;
&#039; Вызов WINAPI в EXCEL через CallWindowProcA
&#039;
Option Explicit

Private Declare Function CallWindowProc Lib &quot;user32.dll&quot; Alias &quot;CallWindowProcA&quot; (ByVal lpPrevWndFunc _
As Long, ByVal hwnd As Long, ByVal msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long

Private Declare Function FreeLibrary Lib &quot;kernel32.dll&quot; (ByVal hLibModule As Long) As Long
Private Declare Function GetProcAddress Lib &quot;kernel32.dll&quot; (ByVal hModule As Long, ByVal lpProcName As String) As Long
Private Declare Function LoadLibrary Lib &quot;kernel32.dll&quot; Alias &quot;LoadLibraryA&quot; (ByVal lpLibFileName As String) As Long

Private Declare Sub MoveMem Lib &quot;kernel32&quot; Alias &quot;RtlMoveMemory&quot; (ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long)

Private Const MAX_PARAMS As Long = 10

&#039; Именованная коллекция адресов функций
Private ADDR As New Collection

Private Type POINT
x As Long
y As Long
End Type

Public sBuf As String

&#039;//Вызов WINAPI//
Public Function CallFunction(ByVal LibName As String, ByVal FuncName As String, ParamArray p()) As Long

    Dim i As Long, hMem As Long, ofs As Long
    Dim hLib As Long
    Dim hAPI_Address As Long
    Dim pMem() As Byte
    Dim arg As Long
    
  
    &#039; Найти адрес WINAPI
    &#039;-------------------------------------------------
    
    &#039; Извлечь адрес из коллекции, если таковой имется
    On Error GoTo GET_ADDR
    hAPI_Address = ADDR.Item(LibName &amp; &quot;_&quot; &amp; FuncName)
    &#039;MsgBox &quot;Извлечен адрес повторного вызова.&quot;, vbSystemModal + vbExclamation, &quot;Reply&quot;
    On Error GoTo 0
    GoTo CALL_DLL
    
GET_ADDR:

    hLib = LoadLibrary(LibName)
    If hLib = 0 Then Err.Raise 5: Exit Function
    
    hAPI_Address = GetProcAddress(hLib, FuncName)
    If hAPI_Address = 0 Then Err.Raise 5: Exit Function

    &#039; Добавить адрес в коллекцию
    ADDR.Add Item:=hAPI_Address, Key:=LibName &amp; &quot;_&quot; &amp; FuncName

CALL_DLL:
    
    &#039; Выделить и заполнить память
    &#039;-------------------------------------------------
    ReDim pMem(0 To 5 * MAX_PARAMS + 5 + 3)
    hMem = VarPtr(pMem(0))
    ofs = 0
    
    &#039; Обратный порядок записи в стек для stdcall
    For i = UBound(p) To LBound(p) Step -1
        
        &#039; Команда PUSH
        pMem(ofs) = &amp;H68 &#039;asmPUSH_imm32
        ofs = ofs + 1
        
        &#039; Аргумент
        If VarType(p(i)) = vbString Then
            arg = CLng(StrPtr(p(i)))
        Else
            arg = CLng(p(i))
        End If
        SetDWord pMem(), ofs, arg
        ofs = ofs + 4
        
    Next
    
    &#039; Вызов функции
    pMem(ofs) = &amp;HE8 &#039; asmCALL_rel32
    ofs = ofs + 1
    
    SetDWord pMem(), ofs, CLng(hAPI_Address - hMem - ofs - 4)
    ofs = ofs + 4
    
    &#039; Возврат с очисткой 0x0010 (16) байт стека
    &#039; ret 0x0010 - ret imm16 т.е. C2 1000, обратный порядок записи байт
    pMem(ofs) = &amp;HC2
    ofs = ofs + 1
    
    SetWord pMem(), ofs, &amp;H10
    ofs = ofs + 2
    
    CallFunction = CallWindowProc(hMem, 0, 0, 0, 0)
    
End Function

&#039;//Заполнить память двойным словом (Long)//
Private Sub SetDWord(ByRef bMemArr() As Byte, ByVal i As Long, ByVal iData As Long)
    
    Dim k As Integer
    For k = 0 To 3
        bMemArr(i + k) = iData And &amp;HFF
        iData = Int(iData / &amp;H100)  &#039; Сдвиг вправо на 8 бит равносилен делению на 2^8
    Next
    
End Sub

&#039;//Заполнить память словом (Integer)//
Private Sub SetWord(ByRef bMemArr() As Byte, ByVal i As Long, ByVal iData As Integer)
    
        bMemArr(i) = iData And &amp;HFF
        iData = Int(iData / &amp;H100)
        
        bMemArr(i + 1) = iData And &amp;HFF
        
End Sub

&#039;//Строковый буфер в области VBA//
Public Function GetStrBuf() As String
    GetStrBuf = sBuf
End Function
Public Function SetStrBuf(Lenght As Long) As Long
    sBuf = String(Lenght, Chr(0))
    SetStrBuf = StrPtr(sBuf)
End Function

&#039;//Протестировать вызов WINAPI//
Public Sub Load()

    Dim pid, res As Long
    Dim pt As POINT
    Dim sTempPath As String
    
    sTempPath = String(256, Chr(0))
    &#039;-------------------------
    pid = CallFunction(&quot;kernel32.dll&quot;, &quot;GetCurrentProcessId&quot;)
    MsgBox &quot;Excel PID:&quot; &amp; Chr(32) &amp; pid, vbInformation + vbOKOnly + vbSystemModal
    
    &#039;-------------------------
    res = CallFunction(&quot;user32.dll&quot;, &quot;MessageBoxA&quot;, 0, StrConv(&quot;Некоторое сообщение...&quot;, vbFromUnicode), StrConv(&quot;Заголовок&quot;, vbFromUnicode), vbInformation + vbSystemModal)
    
    &#039;-------------------------
    res = CallFunction(&quot;user32.dll&quot;, &quot;GetCursorPos&quot;, VarPtr(pt))
    MsgBox &quot;Положение курсора: &quot; &amp; pt.x &amp; &quot;__&quot; &amp; pt.y, vbInformation + vbSystemModal
    
    &#039;-------------------------
    res = CallFunction(&quot;kernel32.dll&quot;, &quot;GetTempPathW&quot;, Len(sTempPath), StrPtr(sTempPath))
    MsgBox &quot;Temp Path:&quot; &amp; Chr(32) &amp; sTempPath, vbInformation + vbOKOnly + vbSystemModal
    
End Sub
</code></pre></div><p>2) Модуль <strong>DLLCALL_EnumWindows.bas</strong>:<br />EnumWindows передает управление CALLBACK функции, в роли которой и выступает заданная WinAPI, которая вызывается однократно.<br /></p><div class="codebox"><pre><code>
&#039;
&#039; Вызов WINAPI в EXCEL через EnumWindows. WinAPI выступает в роли CALLBACK, которой и передается управление
&#039;
Option Explicit

Private Declare Function EnumWindows Lib &quot;user32&quot; (ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long

Private Declare Function CallWindowProc Lib &quot;user32.dll&quot; Alias &quot;CallWindowProcA&quot; (ByVal lpPrevWndFunc _
As Long, ByVal hwnd As Long, ByVal msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long

Private Declare Function FreeLibrary Lib &quot;kernel32.dll&quot; (ByVal hLibModule As Long) As Long
Private Declare Function GetProcAddress Lib &quot;kernel32.dll&quot; (ByVal hModule As Long, ByVal lpProcName As String) As Long
Private Declare Function LoadLibrary Lib &quot;kernel32.dll&quot; Alias &quot;LoadLibraryA&quot; (ByVal lpLibFileName As String) As Long
Private Declare Function LocalAlloc Lib &quot;kernel32&quot; (ByVal wFlags As Long, ByVal dwBytes As Long) As Long
Private Declare Function LocalFree Lib &quot;kernel32&quot; (ByVal hMem As Long) As Long
Private Declare Function LocalLock Lib &quot;kernel32&quot; (ByVal hMem As Long) As Long
Private Declare Function LocalUnlock Lib &quot;kernel32&quot; (ByVal hMem As Long) As Boolean

Private Declare Sub MoveMem Lib &quot;kernel32&quot; Alias &quot;RtlMoveMemory&quot; (ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long)

Private Const LMEM_FIXED = &amp;H0
Private Const LMEM_MOVEABLE = &amp;H2
Private Const LMEM_NOCOMPACT = &amp;H10
Private Const LMEM_NODISCARD = &amp;H20
Private Const LMEM_ZEROINIT = &amp;H40
Private Const LMEM_MODIFY = &amp;H80
Private Const LMEM_DISCARDABLE = &amp;HF00
Private Const LMEM_VALID_FLAGS = &amp;HF72
Private Const LMEM_INVALID_HANDLE = &amp;H8000

Private Const MAX_PARAMS As Long = 10

&#039; Именованная коллекция адресов функций
Private ADDR As New Collection

Private Type POINT
x As Long
y As Long
End Type

&#039;//Вызов WINAPI//
Public Function CallFunction(ByVal LibName As String, ByVal FuncName As String, ParamArray p()) As Long

    Dim i As Long, hMem As Long
    Dim ofs As Long
    Dim hLib As Long
    Dim hAPI_Address As Long
    Dim pMem() As Byte
    Dim arg As Long
    Dim bRes As Long
    
  
    &#039; Найти адрес WINAPI
    &#039;-------------------------------------------------
    
    &#039; Извлечь адрес из коллекции, если таковой имется
    On Error GoTo GET_ADDR
    
    hAPI_Address = ADDR.Item(LibName &amp; &quot;_&quot; &amp; FuncName)
    &#039;MsgBox &quot;Извлечен адрес повторного вызова.&quot;, vbSystemModal + vbExclamation, &quot;Reply&quot;
    On Error GoTo 0
    GoTo CALL_DLL
    
GET_ADDR:

    hLib = LoadLibrary(LibName)
    If hLib = 0 Then Err.Raise 5: Exit Function
    
    hAPI_Address = GetProcAddress(hLib, FuncName)
    If hAPI_Address = 0 Then Err.Raise 5: Exit Function
    
    &#039; Добавить адрес в коллекцию
    ADDR.Add Item:=hAPI_Address, Key:=LibName &amp; &quot;_&quot; &amp; FuncName

CALL_DLL:
    
    &#039; Выделить и заполнить память
    &#039;-------------------------------------------------
    hMem = LocalAlloc(LMEM_ZEROINIT + LMEM_FIXED, 5 * MAX_PARAMS + 5 + 5 + 5 + 3)
    If hMem = 0 Then Err.Raise 7: Exit Function
    
    hMem = LocalLock(hMem)
    ofs = hMem
    
    &#039; Обратный порядок записи в стек для stdcall
    For i = UBound(p) To LBound(p) Step -1
        
        MoveMem ByVal ofs, &amp;H68, 1 &#039;asmPUSH_imm32
        ofs = ofs + 1
      
        &#039; Аргумент
        If VarType(p(i)) = vbString Then
            arg = CLng(StrPtr(p(i)))
        Else
            arg = CLng(p(i))
        End If
        
        MoveMem ByVal ofs, arg, 4
        ofs = ofs + 4
    Next
    
    &#039; Вызов функции (относительный)
    MoveMem ByVal ofs, &amp;HE8, 1 &#039; asmCALL_rel32
    ofs = ofs + 1
    
    MoveMem ByVal ofs, CLng(hAPI_Address - ofs - 4), 4
    ofs = ofs + 4
        
    &#039; Записать результат API(eax) в возврат функции CallFunction
    &#039; mov [ptr],eax
    CallFunction = CLng(0)
    
    MoveMem ByVal ofs, &amp;HA3, 1
    ofs = ofs + 1
    
    MoveMem ByVal ofs, VarPtr(CallFunction), 4
    ofs = ofs + 4
    
    &#039; Результат работы EnumWindows = false
    &#039; mov eax,0
    MoveMem ByVal ofs, &amp;HB8, 1
    ofs = ofs + 1
    ofs = ofs + 4
    
    &#039;Возврат с удалением 8 байт из стека
    &#039;ret 0008
    MoveMem ByVal ofs, &amp;HC2, 1
    ofs = ofs + 1
    
    MoveMem ByVal ofs, &amp;H8, 2
    ofs = ofs + 2
    
    &#039; Передать управление через EnumWindows CALLBACK
    bRes = EnumWindows(hMem, 0)
    
    LocalUnlock hMem
    LocalFree hMem
   
End Function

&#039;//Строковый буфер в области VBA//
Public Function GetStrBuf() As String
    GetStrBuf = sBuf
End Function
Public Function SetStrBuf(Lenght As Long) As Long
    sBuf = String(Lenght, Chr(0))
    SetStrBuf = StrPtr(sBuf)
End Function

&#039;//Протестировать вызов WINAPI//
Public Sub Load()

    Dim pid, res As Long
    Dim pt As POINT
    Dim sTempPath As String
    
    sTempPath = String(256, Chr(0))
    &#039;-------------------------
    pid = CallFunction(&quot;kernel32.dll&quot;, &quot;GetCurrentProcessId&quot;)
    MsgBox &quot;Excel PID:&quot; &amp; Chr(32) &amp; pid, vbInformation + vbOKOnly + vbSystemModal
    
    &#039;-------------------------
    res = CallFunction(&quot;user32.dll&quot;, &quot;MessageBoxA&quot;, 0, StrConv(&quot;Некоторое сообщение...&quot;, vbFromUnicode), StrConv(&quot;Заголовок&quot;, vbFromUnicode), vbInformation + vbSystemModal)
    
    &#039;-------------------------
    res = CallFunction(&quot;user32.dll&quot;, &quot;GetCursorPos&quot;, VarPtr(pt))
    MsgBox &quot;Положение курсора: &quot; &amp; pt.x &amp; &quot;__&quot; &amp; pt.y, vbInformation + vbSystemModal
    
    &#039;-------------------------
    res = CallFunction(&quot;kernel32.dll&quot;, &quot;GetTempPathW&quot;, Len(sTempPath), StrPtr(sTempPath))
    MsgBox &quot;Temp Path:&quot; &amp; Chr(32) &amp; sTempPath, vbInformation + vbOKOnly + vbSystemModal
    
End Sub
</code></pre></div><br /><br /><p>Можно вызывать WinAPI из VBScript, используя Excel как сервер автоматизации, понадобится xlsm-документ(здесь - &quot;DLL_CALL.xlsm&quot;) расположенный рядом со скриптом и содержащий модули <strong>DLLCALL_CallWindowProc.bas</strong> и <strong>DLLCALL_EnumWindows.bas</strong>:</p><p><strong>VBScript</strong>:<br /></p><div class="codebox"><pre><code>
 
 &#039;
 &#039; Вызов WinAPI из VbScript, используя Excel как сервер автоматизации
 &#039; VBScript
 &#039;
 &#039;
 
 Option Explicit

 Dim objExcel, objWorkBook
 Dim sPath, sTempPath

 Const sWorkBookName = &quot;DLL_CALL.xlsm&quot;

 sPath = WScript.ScriptFullName
 sPath = Left(sPath, InStrRev(sPath, &quot;\&quot;)) &amp; sWorkBookName


 &#039; /Попытаться использовать уже существующий процесс Excel как сервер автоматизации/
 &#039;-------------------------------------------
 On Error Resume Next
 Set objExcel = GetObject(,&quot;Excel.Application&quot;)

 If IsObject(objExcel) Then 

	&#039; /Попытаться обнаружить рабочую книгу содержащую VBA-модуль вызова API/
	Set objWorkBook = objExcel.Workbooks.Item(sWorkBookName)
	If Not IsObject(objWorkBook) Then 
		Set objWorkBook = objExcel.Workbooks.Open(sPath)
	End If
 Else
	Set objExcel = CreateObject(&quot;Excel.Application&quot;)
	objExcel.Application.Visible = False
	Set objWorkBook = objExcel.Workbooks.Open(sPath) 
 End If
 On Error GoTo 0

 &#039; Протестировать вызов WINAPI
 &#039;-------------------------------------------
 WinAPITest &quot;DLLCALL_CallWindowProc&quot;
 WinAPITest &quot;DLLCALL_EnumWindows&quot;
 WScript.Quit()


 &#039; Вызов WINAPI через Excel (sModuleName - имя модуля содержащего вызов WinAPI в книге Excel)
 &#039;-------------------------------------------
 Sub WinAPITest(ByVal sModuleName)

	Dim sTitle, hRES, p1 		 
	sTitle = sModuleName
	With objWorkBook.Application

		hRES = .Run(sModuleName &amp; &quot;.CallFunction&quot;, &quot;user32.dll&quot;, &quot;MessageBoxW&quot;, 0, &quot;Процесс Excel не будет остановлен после выполнения этого сценария.&quot;, sTitle, vbExclamation + vbSystemModal)
		p1 = .Run(sModuleName &amp; &quot;.SetStrBuf&quot;, 255) 
		hRES = .Run(sModuleName &amp; &quot;.CallFunction&quot;, &quot;kernel32.dll&quot;, &quot;GetTempPathW&quot;, 255, p1)
		sTempPath = .Run(sModuleName &amp; &quot;.GetStrBuf&quot;) 

	End With
	MsgBox sTempPath, vbSystemModal + vbExclamation, sTitle
 End Sub
</code></pre></div>]]></content>
			<author>
				<name><![CDATA[Poltergeyst]]></name>
				<uri>http://forum.script-coding.com/profile.php?id=83</uri>
			</author>
			<updated>2010-08-25T20:25:37Z</updated>
			<id>http://forum.script-coding.com/viewtopic.php?pid=38910#p38910</id>
		</entry>
</feed>
