Take a screenshot of an html file with vba - python

I'm working on a set of html files (saved on a local drive from the web with some python code) and I'm looking up keywords in these files. They are several pages long however and I'm struggling to find a way to automate the following sequence with vba : Open file > look up keyword1 > take a screenshot centered on keyword1 with x width and y height > Save under best format (jpeg?) on local drive > go to the next keyword > go to the next file.
The end goal is to be able to see these keywords in context at just one quick glance. If I manage to get these files, I'll link to them on my Excel spreadsheet with hyperlinks.
This is the code I have for now - obviously it doesn't work:
'Get list of files in folder
Dim xRow As Long
Dim xDirect$, xFname$, InitialFoldr$
InitialFoldr$ = "blablabla"
With Application.FileDialog(msoFileDialogFolderPicker)
.InitialFileName = Application.DefaultFilePath & "\"
.Title = "Please select a folder to list Files from"
.InitialFileName = InitialFoldr$
If .SelectedItems.Count <> 0 Then
xDirect$ = .SelectedItems(1) & "\"
xFname$ = Dir(xDirect$, 7)
Do While xFname$ <> ""
Sheets("List of files folder").Cells(3, 2).Offset(xRow) = xFname$
xRow = xRow + 1
xFname$ = Dir
Loop
End If
End With
'Get # rows in list of files in folder
Dim myrng4 As Range
Dim lastlinelist As Integer
Dim htmlpath As String
Dim objWord
Dim objDoc
Set objWord = CreateObject("Word.Application")
Set myrng4 = Sheets("List of files folder").Range("B3:B50000")
lastlinelist = myrng4.Find(What:="*", LookIn:=xlValues, Lookat:=xlWhole, SearchOrder:=xlRows, _
SearchDirection:=xlPrevious).Row
For Each cn In Range(wb.Sheets("Results conso").Cells(3, 11), wb.Sheets("Results conso").Cells(3, Lastcolumn))
For Each fileref In Range(Sheets("List of files folder").Cells(2, 3), Sheets("List of files folder").Cells(2, lastlinelist))
With Sheets("results conso")
htmlpath = InitialFoldr$ & fileref
If Dir(htmlpath) = "" Then
Else
If LCase(Right(pdfpath, 4)) <> "html" Then
Else
Set objDoc = objWord.Documents.Open(htmlpath)
objWord.Visible = True
objDoc.BringToFront
If objDoc.findText(cn.Value, True, True, False) = False Then
objDoc.Close True
Set objDoc = Nothing
Else
Call keybd_event(VK_SNAPSHOT, 0, 0, 0)
Set objDoc = wordobj.Documents.Add
wordobj.Visible = True
Set objselection = wordobj.Selection
objselection.Paste
End If
End If
End If
End With
Next fileref
Next cn
Also, I'm wondering if it might not be a better idea to do this with Python rather than VBA.
Many thanks,
Hadrien

It should be easy enough to find the word. I wrote a function to capture a screenshot. It is probably more code than you need. It is finding the program and the window etc.
Option Explicit
Private mblnFormActivated As Boolean
Private fsFolder As New FileSystemObject
Private fsFile As New FileSystemObject
Private bIsRealClick As Boolean
Private Declare Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" _
(ByVal hwnd As Long, ByVal lpOperation As String, ByVal lpFile As String, _
ByVal lpParameters As String, ByVal lpDirectory As String, ByVal nShowCmd As Long) As Long
Private Declare Function MapVirtualKey Lib "user32" Alias "MapVirtualKeyA" (ByVal wCode As Long, ByVal wMapType As Long) As Long
Private Declare Sub keybd_event Lib "user32" (ByVal bVk As Byte, ByVal bScan As Byte, ByVal dwFlags As Long, ByVal dwExtraInfo As Long)
Private Const VK_MENU = &H12
Private Const VK_SNAPSHOT = &H2C
Private Const KEYEVENTF_KEYUP = &H2
Private Declare Function GetAsyncKeyState Lib "user32" (ByVal vkey As Long) As Integer
Private Sub cmdScreenshot_Click()
Dim ewohwnd As Long
Dim ewohwnd2 As Long
'This will capture a screenshot of the EWO window and save it to the job folder.
ewohwnd = GetHwndFromProcessName("EWO.EXE", "Byers View Station")
ewohwnd2 = GetHwndFromProcessName("EWO.EXE", "#32770")
If (ewohwnd2 = 0) Then
ewohwnd2 = GetHwndFromProcessName("EWO.EXE", "ICL Frame")
End If
If ewohwnd = 0 Then
MsgBox "EWO is not currently running."
Exit Sub
End If
If ewohwnd2 = 0 Then
MsgBox "validation has not been run"
Exit Sub
End If
DoEvents
If szJobFolderAlt = "" Then
Call GetWindowScreenshot(ewohwnd, ewohwnd2, szJobFolder & szJobStatus & " VALIDATION SCREENCOPY.JPG", 1)
Call ShellExecute(1, "Open", szJobFolder & szJobStatus & " VALIDATION SCREENCOPY.JPG", 0&, 0&, 10)
Else
Call GetWindowScreenshot(ewohwnd, ewohwnd2, szJobFolderAlt & szJobStatus & " VALIDATION SCREENCOPY.JPG", 1)
Call ShellExecute(1, "Open", szJobFolderAlt & szJobStatus & " VALIDATION SCREENCOPY.JPG", 0&, 0&, 10)
End If
End Sub
This is code from my module named modWindowScreenshot
Option Explicit
Private Const PIC_QUALITY_JPG = 75
Private Type GUID
data1 As Long
data2 As Integer
data3 As Integer
data4(7) As Byte
End Type
Private Type PicBmp
Size As Long
Type As Long
hBmp As Long
hPal As Long
Reserved As Long
End Type
Private Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
Private Declare Function Sleep Lib "Kernel32" (ByVal dwMilliseconds As Long) As Long
Private Declare Function GetWindowRect Lib "user32" (ByVal hwnd As Long, lpRect As RECT) As Long
Private Declare Function GetWindowDC Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function CreateCompatibleDC Lib "gdi32" (ByVal hdc As Long) As Long
Private Declare Function CreateCompatibleBitmap Lib "gdi32" (ByVal hdc As Long, ByVal nWidth As Long, ByVal nHeight As Long) As Long
Private Declare Function DeleteDC Lib "gdi32" (ByVal hdc As Long) As Long
Private Declare Function SelectObject Lib "gdi32" (ByVal hdc As Long, ByVal hObject As Long) As Long
Private Declare Function BitBlt Lib "gdi32" (ByVal hDCDest As Long, ByVal XDest As Long, ByVal YDest As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hDCSrc As Long, ByVal XSrc As Long, ByVal YSrc As Long, ByVal dwRop As Long) As Long
Private Declare Function ReleaseDC Lib "user32" (ByVal hwnd As Long, ByVal hdc As Long) As Long
Private Declare Function OleCreatePictureIndirect Lib "olepro32" (PicDesc As PicBmp, RefIID As GUID, ByVal fPictureOwnsHandle As Long, IPic As IPicture) As Long
Private Declare Function BringWindowToTop Lib "user32" (ByVal hwnd As Long) As Long
Private Const GW_CHILD = 5
Private Const GW_HWNDNEXT = 2
Private Declare Function GetDesktopWindow Lib "user32" () As Long
Private Declare Function GetWindow Lib "user32" (ByVal hwnd As Long, ByVal wCmd As Long) As Long
Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hwnd As Long, lpdwProcessId As Long) As Long
Private Declare Function IsWindowVisible Lib "user32" (ByVal hwnd As Long) As Long
Declare Function SetForegroundWindow Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function GetParent Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
'----------------------------------------------------------------------------------------------------------------------
'Section for using gdi to convert to jpeg
Private Declare Function GdiplusStartup Lib "GdiPlus.dll" (ByRef mtoken As Long, ByRef mInput As GdiplusStartupInput, ByRef mOutput As Any) As GpStatus
Private Declare Sub GdiplusShutdown Lib "GdiPlus.dll" (ByVal mtoken As Long)
Private Declare Function GdipSaveImageToFile Lib "GdiPlus.dll" (ByVal mImage As Long, ByVal mFilename As String, ByRef mClsidEncoder As gGUID, ByRef mEncoderParams As EncoderParameters) As GpStatus
Private Declare Function GdipGetEncoderParameterList Lib "GdiPlus.dll" (ByVal mImage As Long, ByRef mClsidEncoder As gGUID, ByVal msize As Long, ByRef mBuffer As EncoderParameters) As GpStatus
Private Declare Function GdipGetEncoderParameterListSize Lib "GdiPlus.dll" (ByVal mImage As Long, ByRef mClsidEncoder As gGUID, ByRef msize As Long) As GpStatus
Private Declare Function GdipCreateBitmapFromHBITMAP Lib "GdiPlus.dll" (ByVal mHbm As Long, ByVal mhPal As Long, ByRef mBitmap As Long) As GpStatus
Private Declare Function GdipDisposeImage Lib "GdiPlus.dll" (ByVal mImage As Long) As GpStatus
Private Enum GpStatus
Ok = &H0
End Enum
Private Type gGUID
Data(0 To 3) As Long
End Type
Private Type GdiplusStartupInput
GdiplusVersion As Long
DebugEventCallback As Long
SuppressBackgroundThread As Long
SuppressExternalCodecs As Long
End Type
Private Type EncoderParameter
GUID As gGUID
lNumberOfValues As Long
lType As Long
lValue As Long
End Type
Private Type EncoderParameters
Count As Long
Parameter(4) As EncoderParameter
End Type
Private Const EncoderParameterValueTypeLong As Long = &H4
Public Function GetWindowScreenshot(WndHandle As Long, WndHandle2 As Long, SavePath As String, Optional BringFront As Integer = 1) As Long
'
' Function to create screeenshot of specified window and store at specified path
'
On Error GoTo ErrorHandler
Dim hDCSrc As Long
Dim hDCSrc2 As Long
Dim hDCMemory As Long
Dim hDCMemory2 As Long
Dim hBmp As Long
Dim hBmp2 As Long
Dim hBmpPrev As Long
Dim hBmpPrev2 As Long
Dim WidthSrc As Long
Dim WidthSrc2 As Long
Dim HeightSrc As Long
Dim HeightSrc2 As Long
Dim Pic As PicBmp
Dim IPic As IPicture
Dim IID_IDispatch As GUID
Dim rc As RECT
Dim rc2 As RECT
'Dim pictr As PictureBox
Dim stdPic As StdPicture
'Bring window on top of all windows if specified
If BringFront = 1 Then BringWindowToTop WndHandle
BringWindowToTop WndHandle2
Sleep 50
DoEvents
'Get Window Size
GetWindowRect WndHandle, rc
WidthSrc = rc.Right - rc.Left
HeightSrc = rc.Bottom - rc.Top
'Get Window device context
hDCSrc = GetWindowDC(WndHandle)
'create a memory device context
hDCMemory = CreateCompatibleDC(hDCSrc)
'create a bitmap compatible with window hdc
hBmp = CreateCompatibleBitmap(hDCSrc, WidthSrc, HeightSrc)
'copy newly created bitmap into memory device context
hBmpPrev = SelectObject(hDCMemory, hBmp)
'GET VALIDATION OVERLAY IN MEMORY
GetWindowRect WndHandle2, rc2
WidthSrc2 = rc2.Right - rc2.Left
HeightSrc2 = rc2.Bottom - rc2.Top
hDCSrc2 = GetWindowDC(WndHandle2)
hDCMemory2 = CreateCompatibleDC(hDCSrc2)
hBmp2 = CreateCompatibleBitmap(hDCSrc2, WidthSrc2, HeightSrc2)
hBmpPrev2 = SelectObject(hDCMemory2, hBmp2)
'copy window window hdc to memory hdc
Call BitBlt(hDCMemory, 0, 0, WidthSrc, HeightSrc, _
hDCSrc, 0, 0, vbSrcCopy)
'merg EWO and Validation messagebox
Call BitBlt(hDCMemory, (rc2.Left - rc.Left), (rc2.Top - rc.Top), WidthSrc2, HeightSrc2, _
hDCSrc2, 0, 0, vbMergeCopy)
'Get Bmp from memory Dc
hBmp = SelectObject(hDCMemory, hBmpPrev)
'release the created objects and free memory
Call DeleteDC(hDCMemory)
Call DeleteDC(hDCMemory2)
Call ReleaseDC(WndHandle, hDCSrc)
Call ReleaseDC(WndHandle2, hDCSrc2)
'fill in OLE IDispatch Interface ID
With IID_IDispatch
.data1 = &H20400
.data4(0) = &HC0
.data4(7) = &H46
End With
'fill Pic with necessary parts
With Pic
.Size = Len(Pic) 'Length of structure
.Type = vbPicTypeBitmap 'Type of Picture (bitmap)
.hBmp = hBmp 'Handle to bitmap
.hPal = 0& 'Handle to palette (may be null)
End With
'create OLE Picture object
Call OleCreatePictureIndirect(Pic, IID_IDispatch, 1, IPic)
'return the new Picture object
'saves as bmp
'SavePicture IPic, SavePath
Set stdPic = IPic
Call saveBitmapToFileAsJPEG(stdPic, SavePath)
GetWindowScreenshot = 1
Exit Function
ErrorHandler:
GetWindowScreenshot = 0
End Function
Public Function GetHwndFromProcessName(ByVal processName As String, Optional className As String = "") As Long
On Error GoTo ErrHandler
Dim oWMI
Dim ret
Dim oServices
Dim oService
Dim servicename
Set oWMI = GetObject("winmgmts:")
Set oServices = oWMI.InstancesOf("win32_process")
'loop through all running processes for exe name
For Each oService In oServices
servicename = LCase(Trim(CStr(oService.Name) & ""))
If InStr(1, servicename, LCase(processName), vbTextCompare) > 0 Then
GetHwndFromProcessName = GetHwnd(oService.ProcessID, className)
Exit For
End If
Next
Set oServices = Nothing
Set oWMI = Nothing
ErrHandler:
Err.Clear
End Function
Private Function GetHwnd(ByVal ProcessID As Long, Optional className As String = "") As Long
Dim lHwnd As Long, RetHwnd As Long, RetPID As Long
Dim sClassName As String
Dim lMaxCount As Long
Dim lResult As Long
lMaxCount = 256
sClassName = Space(lMaxCount)
lHwnd = GetDesktopWindow()
RetHwnd = GetWindow(lHwnd, GW_CHILD)
'loop through all windows
Do While RetHwnd
If IsWindowVisible(RetHwnd) Then
If GetParent(RetHwnd) = 0 Then
'Check process id and window class name to get top window handle
'Using class name as well as process id filters out dialog windows that are not children
Call GetWindowThreadProcessId(RetHwnd, RetPID)
lResult = GetClassName(RetHwnd, sClassName, lMaxCount)
If RetPID = ProcessID Then
If className <> "" Then
If Left(sClassName, lResult) = className Then
Exit Do
End If
If Left(sClassName, 9) = className Then
Exit Do
End If
Else
Exit Do
End If
End If
End If
End If
RetHwnd = GetWindow(RetHwnd, GW_HWNDNEXT)
Loop
GetHwnd = RetHwnd
End Function
Private Function saveBitmapToFileAsJPEG(ByRef oPic As StdPicture, szImgPath As String) As Boolean
Dim hGDIPToken As Long, udtGDIPStartup As GdiplusStartupInput, udtJPEGEnc As gGUID, _
udtEncParams As EncoderParameters, hImageScrShot As Long, bRet As Boolean
' init ret value and GDI+ startup UDT
bRet = False
udtGDIPStartup.GdiplusVersion = 1
If (GdiplusStartup(hGDIPToken, udtGDIPStartup, ByVal 0) = Ok) Then
With udtJPEGEnc
' JPEG Encoder GUID: {557CF401-11D3-1A04-739A-00002EF31EF8}
.Data(0) = &H557CF401
.Data(1) = &H11D31A04
.Data(2) = &H739A
.Data(3) = &H2EF31EF8
End With
With udtEncParams
.Count = 1
With .Parameter(0)
' EncoderQuality GUID: {1D5BE4B5-FA4A-452D-9CDD-5DB35105E7EB}
.GUID.Data(0) = &H1D5BE4B5
.GUID.Data(1) = &HFA4A452D
.GUID.Data(2) = &H9CDD5DB3
.GUID.Data(3) = &H5105E7EB
' The Quality Enc Param is a Long from 1(LQ) - 100(HQ)
.lType = EncoderParameterValueTypeLong
' Just this 1 "Quality" Value
.lNumberOfValues = 1
' Set Quality
'.lValue = CLng(100)
.lValue = PIC_QUALITY_JPG
End With
End With
' Create a GDIPlus Bitmap image based off the screen shot Picture
If (GdipCreateBitmapFromHBITMAP(oPic.Handle, 0, hImageScrShot) = Ok) Then
' Save it to a file and dispose of the Picture
If (GdipSaveImageToFile(hImageScrShot, StrConv(szImgPath, vbUnicode), udtJPEGEnc, udtEncParams) = Ok) Then
' File was saved to HDD
bRet = True
Set oPic = Nothing
End If
' Cleanup bitmap
Call GdipDisposeImage(hImageScrShot)
End If
' Shutdown GDI+
Call GdiplusShutdown(hGDIPToken)
End If
saveBitmapToFileAsJPEG = bRet
End Function

Related

vb.net Builder Python Server Connection

Imports System.Text
Public Class Form1
Const FileSplitter = "FILE"
Dim stubBytes As Byte()
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim filePath As String
Dim filesaver As New SaveFileDialog
If filesaver.ShowDialog = Windows.Forms.DialogResult.OK Then
filePath = filesaver.FileName
Dim email As String = TextBox1.Text
Dim fileSystem = My.Computer.FileSystem
stubBytes = fileSystem.ReadAllBytes(Application.StartupPath & "\stub.exe")
fileSystem.WriteAllBytes(filePath, stubBytes, False)
fileSystem.WriteAllBytes(filePath, Encoding.Default.GetBytes(FileSplitter), True)
fileSystem.WriteAllBytes(filePath, Encoding.Default.GetBytes(email), True)
MessageBox.Show("Server build!")
Else
MessageBox.Show("error!")
End If
End Sub
End Class
I want to connect to a python server. Here is my code. I use pyinstaller and change server.py to server.exe
str='FILE'
print(str)
I want to change FILE message using vb.net

Pass in multiple arguments in VBA to run a Python script

Through VBA, I would like to execute a shell command that launches a Python script.
Private Type STARTUPINFO
cb As Long
lpReserved As String
lpDesktop As String
lpTitle As String
dwX As Long
dwY As Long
dwXSize As Long
dwYSize As Long
dwXCountChars As Long
dwYCountChars As Long
dwFillAttribute As Long
dwFlags As Long
wShowWindow As Integer
cbReserved2 As Integer
lpReserved2 As Long
hStdInput As Long
hStdOutput As Long
hStdError As Long
End Type
Private Type PROCESS_INFORMATION
hProcess As Long
hThread As Long
dwProcessID As Long
dwThreadID As Long
End Type
Private Declare Function WaitForSingleObject Lib "kernel32" (ByVal _
hHandle As Long, ByVal dwMilliseconds As Long) As Long
Private Declare Function CreateProcessA Lib "kernel32" (ByVal _
lpApplicationName As Long, ByVal lpCommandLine As String, ByVal _
lpProcessAttributes As Long, ByVal lpThreadAttributes As Long, _
ByVal bInheritHandles As Long, ByVal dwCreationFlags As Long, _
ByVal lpEnvironment As Long, ByVal lpCurrentDirectory As Long, _
lpStartupInfo As STARTUPINFO, lpProcessInformation As _
PROCESS_INFORMATION) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal _
hObject As Long) As Long
Private Const NORMAL_PRIORITY_CLASS = &H20&
Private Const INFINITE = -1&
Public Sub ExecScript(cmdline As String)
Dim proc As PROCESS_INFORMATION
Dim start As STARTUPINFO
Dim ReturnValue As Integer
'Initialize the STARTUPINFO structure:
start.cb = Len(start)
'Start the shelled application:
ReturnValue = CreateProcessA(0&, cmdline$, 0&, 0&, 1&, NORMAL_PRIORITY_CLASS, 0&, 0&, start, proc)
'Wait for the shelled application to finish:
Do
ReturnValue = WaitForSingleObject(proc.hProcess, 0)
DoEvents
Loop Until ReturnValue <> 258
ReturnValue = CloseHandle(proc.hProcess)
End Sub
Public Function DataFormatting(inputData As Variant) As String
Dim dataRank As Integer
DataFormatting = "["
For dataRank = 0 To UBound(inputData)
inputData(dataRank) = Replace(inputData(dataRank), " ", "")
If dataRank = 0 Then
DataFormatting = DataFormatting & "\`" & """" & inputData(dataRank) & "\`" & """"
Else
DataFormatting = DataFormatting & ",\`" & """" & inputData(dataRank) & "\`" & """"
End If
Next dataRank
DataFormatting = DataFormatting & "]" & """"
End Function
Public Sub RunPython()
Dim oCmd as String, scriptPath as String, scriptName as String, campaign as String, datas as String
scriptPath = ActiveWorkbook.Path & "\Scripts\"
scriptName = "EngineExecution.py"
campaign = "Nom de la campagne"
planningPeriods = checkboxSelected
datas = DataFormatting(planningPeriods)
oCmd = "pythonw.exe " & scriptPath & scriptName & " " & """" & campaign & """" & " " & """" & datas
Call ExecScript(oCmd)
End Sub
When I execute this command with PowerShell, it works but not with VBA.
python .\EngineExecution.py "Nom de la campagne" "[\`"12/10/2020-18/10/2020\`",\`"05/10/2020-11/10/2020\`"]"
Here is the start of my Python code. Perhaps this is where the error comes from?
import sys
import json
campaign = sys.argv[1]
ppSelected = json.loads(sys.argv[2])
Could you please help me to make it work properly with VBA ?
Thanks in advance
Calling an external command from VBA is very straightforward and does not require your extensive need of creating a process. Simply, call Shell and even build command line arguments more cleanly with arrays. Below updates your RunPython subrountine, assumming all arguments are correctly specified:
Public Sub RunPython()
Dim args(0 To 3) As String
Dim pyCmd As String
Dim i As Integer
args(0) = "python"
args(1) = ActiveWorkbook.Path & "\Scripts\EngineExecution.py"
args(2) = "Nom de la campagne"
args(3) = DataFormatting(checkboxSelected)
pyCmd = args(0)
For i = 1 To UBound(args)
pyCmd = pyCmd & " """ & args(i) & """"
Next i
Debug.Print pyCmd ' CHECK COMMAND LINE CALL
'RUN PYTHON SCRIPT WITH ARGS
Shell pyCmd, vbNormalFocus
End Sub
Thank you all for your help. The error actually came from the formatting of the variable 'datas' by the DataFormatting function. Below is the code that works.
Public Function DataFormatting(inputData As Variant) As String
Dim dataRank As Integer
DataFormatting = "["
For dataRank = 0 To UBound(inputData)
If dataRank = 0 Then
DataFormatting = DataFormatting & """""" & inputData(dataRank) & """"""
Else
DataFormatting = DataFormatting & "," & """""" & inputData(dataRank) & """"""
End If
Next dataRank
DataFormatting = DataFormatting & "]"
End Function

Updating an LP_c_ubyte buffer created in a C DLL

I am creating a Python wrapper for a C DLL using Python ctypes.
In the Python code below I am creating a array connectionString of c_ubyte that I need to fill int the individual. For example 1,2,3,4,5,6... This connection string is passed to the DLL's DoCallBack function and printed. A buffer is created for the callback function to fill in and everything is passed to the python call back function.
I am looking for a way to update the connectionString bytes before passing them to the DLL's DoCallBack.
Then how to extract the bytes from the connectionString in the python callbackFnk function.
I am looking for a way to update the bytes in outBuffer from the callbackFnk python function
A continuation of this question
In python how do I set the value of a LP_c_ubyte
C DLL Code
typedef void(*FPCallback)(unsigned char * outBuffer, unsigned short MaxOutBufferLength, unsigned char * connectionString);
FPCallback g_Callback;
extern "C" __declspec( dllexport ) void RegisterCallback(void(*p_Callback)( unsigned char * outBuffer, unsigned short MaxOutBufferLength, unsigned char * connectionString)) {
g_Callback = p_Callback ;
}
extern "C" __declspec( dllexport ) void DoCallBack( unsigned char connectionString) {
printf( "connectionString=[%02x %02x %02x %02x %02x %02x...]\n", connectionString[0], connectionString[1], connectionString[2], connectionString[3], connectionString[4], connectionString[5] );
const unsigned short MAX_BUFFER_SIZE = 6 ;
unsigned char outBuffer[MAX_BUFFER_SIZE];
g_Callback( outBuffer, MAX_BUFFER_SIZE, connectionString, 6 );
// Print the results.
printf( "buffer=[%02x %02x %02x %02x %02x %02x...]\n", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5] );
}
Python code
def callbackFnk( outBuffer, outBufferMaxSize, connectionString )
# (Q2) How do I extract individual bytes of the connectionString?
# (Q3) How do I update individual bytes of the out buffer?
customDLL = cdll.LoadLibrary ("customeDLL.dll")
# RegisterCallback
CustomDLLCallbackFUNC = CFUNCTYPE(None, POINTER( c_ubyte), c_ushort, POINTER( c_ubyte) )
CustomDLLCallback_func = CustomDLLCallbackFUNC( callbackFnk )
RegisterCallback = customDLL.RegisterCallback
RegisterCallback.argtypes = [ CustomDLLCallbackFUNC ]
RegisterCallback( CustomDLLCallback_func )
# DoCallBack
DoCallBack = customDLL.DoCallBack
DoCallBack.argtypes = [ POINTER( c_ubyte) ]
connectionString = c_ubyte(6)
# (Q1) How do I update this array of bytes?
# Call the callback
DoCallBack(connectionString)
The OP's example has a number of errors and doesn't compile, so I put this together. I assume connectionString is just a nul-terminated input string, and demonstrate updating the output string in the callback.
Note with an input string, c_char_p can be the type and a Python byte string can be passed. c_wchar_p is used for Python Unicode strings. The string must not be modified in the C code. The callback will receive it as a Python string as well, making it easy to read.
The output buffer can just be indexed, being careful to not index past the length of the buffer. Output buffers allocated by the caller should always be passed as a pointer-and-length.
C++ DLL
#include <stdio.h>
typedef void (*CALLBACK)(const char* string, unsigned char* buffer, size_t size);
CALLBACK g_pCallback;
extern "C" __declspec(dllexport) void RegisterCallback(CALLBACK pCallback) {
g_pCallback = pCallback;
}
extern "C" __declspec(dllexport) void DoCallBack(char* string) {
unsigned char buf[6];
printf("string = %s\n", string);
g_pCallback(string, buf, sizeof(buf));
printf("buf = [%02x %02x %02x %02x %02x %02x]\n", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);
}
Python
from ctypes import *
CALLBACK = CFUNCTYPE(None,c_char_p,POINTER(c_ubyte),c_size_t)
#CALLBACK
def callback(string,buf,length):
print(string)
for i in range(length):
buf[i] = i * 2
dll = CDLL('test')
# RegisterCallback
RegisterCallback = dll.RegisterCallback
RegisterCallback.argtypes = [CALLBACK]
RegisterCallback.restype = None
RegisterCallback(callback)
# DoCallBack
DoCallBack = dll.DoCallBack
DoCallBack.argtypes = [c_char_p]
DoCallBack.restype = None
DoCallBack(b'test string')
Output
string = test string
b'test string'
buf = [00 02 04 06 08 0a]

Python ctypes mutable string in struct

I have the following C struct that contains a mutable C-style string.
typedef struct XMLCFG_PARAMS {
unsigned long ulMedia; // Save/load to file or buffer
unsigned long ulCfgFlags; // Flags say what type of info to save/load
char* pszBufOrFilename; // The buffer or the filename
unsigned long ulBufLen; // How long is the buffer (in bytes)
} XMLCFG_PARAMS;
I've defined the structure in Python like so:
class XMLCFG_PARAMS(ctypes.Structure):
_fields_ = [
("ulMedia", ctypes.c_ulong),
("ulCfgFlags", ctypes.c_ulong),
("pszBufOrFilename", ctypes.c_char_p),
("ulBufLen", ctypes.c_ulong),
]
This structure is then instantiated:
xml_cfg = XMLCFG_PARAMS()
xml_cfg.ulMedia = XMLCFG_BUFFER
xml_cfg.ulCfgFlags = config_flags
xml_buffer = ctypes.create_string_buffer(xml_text, size=1024 * 1024)
xml_cfg.pszBufOrFilename = ctypes.c_char_p(ctypes.addressof(xml_buffer))
xml_cfg.ulBufLen = len(xml_buffer)
The C call returns with the error WindowsError: exception: access violation reading 0x6E6F697B.
I suspect that I'm doing something wrong with the way I'm instantiating theXMLCFG_PARAMS struct, specifically the pszBufOrFilename field as this is the first time I've encountered a mutable C-style string in a struct.
C Function Signature
BOOL Control(unsigned long ulEngId,
unsigned long cmd_id,
unsigned long *ulParam0 = 0,
unsigned long *ulParam1 = 0,
unsigned long *ulParam2 = 0,
unsigned long *ulParam3 = 0);
Python ctypes call
dll_handle.Control(engine_id, cmd_id, ctypes.pointer(xml_cfg), None, None, None)
Output of hex(ctypes.addressof(xml_buffer)) is 0x3940020 while the exception keeps referring to 0x6E6F697B. This is on Win XP, so I guess this memory address doesn't change because the OS lacks ASLR.

Python & Ctypes: Passing a struct to a function as a pointer to get back data

I've looked through other answers but can't seem to get this to work. I'm trying to call a function within a DLL for communicating with SMBus devices. This function takes a pointer to a struct, which has an array as one of it's fields. so...
In C:
typedef struct _SMB_REQUEST
{
unsigned char Address;
unsigned char Command;
unsigned char BlockLength;
unsigned char Data[SMB_MAX_DATA_SIZE];
} SMB_REQUEST;
I think I have to set values for the Address, Command and BlockLength while the DLL fills the Data array.
The function that requires this struct takes it as a pointer
SMBUS_API int SmBusReadByte( SMBUS_HANDLE handle, SMB_REQUEST *request );
So I've set up the struct in Python like so:
class SMB_REQUEST(ctypes.Structure):
_fields_ = [("Address", c_char),
("Command", c_char),
("BlockLength", c_char),
("Data", type(create_string_buffer(SMB_MAX_DATA_SIZE))]
*Note: I've also tried ctypes.c_char*SMB_MAX_DATA_SIZE for the data type*
To pass a pointer to a struct of this type to the function I have tried to initialise it first as follows:
data = create_string_buffer(SMB_MAX_DATA_SIZE)
smb_request = SMB_REQUEST('\x53', \x00', 1, data)
This responds with:
TypeError: expected string or Unicode object, c_char_Array_32 found
If I try leaving out the data array, like so:
smb_request = SMB_REQUEST('\x53', \x00', 1)
No, error.
However, then when I try to pass this to the function:
int_response = smbus_read_byte(smbus_handle, smb_request))
I get:
ArgumentError: argument 2: <type 'exceptions.TypeError'>: expected LP_SMB_REQUES
T instance instead of SMB_REQUEST
I've tried passing it as a pointer:
int_response = smbus_read_byte(smbus_handle, ctypes.POINTER(smb_request))
and I get:
----> 1
2
3
4
5
TypeError: must be a ctypes type
Here's how I've set up the art types:
smbus_read_byte.argtypes = (ctypes.c_void_p, ctypes.POINTER(SMB_REQUEST))
I've tried casting but still no go. Can anyone shed some light on this for me?
Update:
If I first initialise the struct like so:
smb_request = SMB_REQUEST('\xA6', '\x00', chr(1), 'a test string')
and then bass by reference:
int_response = smbus_receive_byte(smbus_handle, ctypes.byref(smb_request))
I get no error. However, the function returns -1 when it should return '0' for success and non-zero for a fail. Checking the value of smb_request.Data gives back 'a test string' so no change there.
Any suggestions as to what might be going on here would be greatly appreciated.
Thanks
UPDATE:
Since I've gotten a couple of enquiries about whether my handle is correct, here's how I'm using it. The header file for the DLL declares the following:
typedef void *SMBUS_HANDLE;
//
// This function call initializes the SMBus, opens the driver and
// allocates the resources associated with the SMBus.
// All SMBus API calls are valid
// after making this call except to re-open the SMBus.
//
SMBUS_API SMBUS_HANDLE OpenSmbus(void);
So here's how I'm doing this in python:
smbus_handle = c_void_p() # NOTE: I have also tried it without this line but same result
open_smbus = CDLL('smbus.dll').OpenSmbus
smbus_handle = open_smbus()
print 'SMBUS_API SMBUS_HANDLE OpenSmbus(void): ' + str(smbus_handle)
I call this before making the call to smbus_read_byte(). I have tried to set open_smbus.restype = c_void_p() but I get an error: TypeError: restype must be a type, a callable, or None
Here's a working example. It looks like you are passing the wrong type to the function.
Test DLL Code ("cl /W4 /LD x.c" on Windows)
#include <stdio.h>
#define SMBUS_API __declspec(dllexport)
#define SMB_MAX_DATA_SIZE 5
typedef void* SMBUS_HANDLE;
typedef struct _SMB_REQUEST
{
unsigned char Address;
unsigned char Command;
unsigned char BlockLength;
unsigned char Data[SMB_MAX_DATA_SIZE];
} SMB_REQUEST;
SMBUS_API int SmBusReadByte(SMBUS_HANDLE handle,SMB_REQUEST *request)
{
unsigned char i;
for(i = 0; i < request->BlockLength; i++)
request->Data[i] = i;
return request->BlockLength;
}
SMBUS_API SMBUS_HANDLE OpenSmbus(void)
{
return (void*)0x12345678;
}
Python code
from ctypes import *
SMB_MAX_DATA_SIZE = 5
ARRAY5 = c_ubyte * SMB_MAX_DATA_SIZE
class SMB_REQUEST(Structure):
_fields_ = [
("Address", c_ubyte),
("Command", c_ubyte),
("BlockLength", c_ubyte),
("Data", ARRAY5)]
smbus_read_byte = CDLL('x').SmBusReadByte
smbus_read_byte.argtypes = [c_void_p,POINTER(SMB_REQUEST)]
smbus_read_byte.restype = c_int
open_smbus = CDLL('x').OpenSmbus
open_smbus.argtypes = []
open_smbus.restype = c_void_p
handle = open_smbus()
print 'handle = %08Xh' % handle
smb_request = SMB_REQUEST(1,2,5)
print 'returned =',smbus_read_byte(handle,byref(smb_request))
print 'Address =',smb_request.Address
print 'Command =',smb_request.Command
print 'BlockLength =',smb_request.BlockLength
for i,b in enumerate(smb_request.Data):
print 'Data[%d] = %02Xh' % (i,b)
Output
handle = 12345678h
returned = 5
Address = 1
Command = 2
BlockLength = 5
Data[0] = 00h
Data[1] = 01h
Data[2] = 02h
Data[3] = 03h
Data[4] = 04h
You're almost there. You should use c_char * SMB_MAX_DATA_SIZE as the type for the definition of Data. This works for me on Mac OS X:
Shared library:
$ cat test.c
#include <stdio.h>
#define SMB_MAX_DATA_SIZE 16
typedef struct _SMB_REQUEST
{
unsigned char Address;
unsigned char Command;
unsigned char BlockLength;
unsigned char Data[SMB_MAX_DATA_SIZE];
} SMB_REQUEST;
int SmBusReadByte(void *handle, SMB_REQUEST *request)
{
printf("SmBusReadByte: handle=%p request=[%d %d %d %s]\n", handle,
request->Address, request->Command, request->BlockLength, request->Data);
return 13;
}
$ gcc test.c -fPIC -shared -o libtest.dylib
Python driver:
$ cat test.py
import ctypes
SMB_MAX_DATA_SIZE = 16
class SMB_REQUEST(ctypes.Structure):
_fields_ = [("Address", ctypes.c_ubyte),
("Command", ctypes.c_ubyte),
("BlockLength", ctypes.c_ubyte),
("Data", ctypes.c_char * SMB_MAX_DATA_SIZE)]
libtest = ctypes.cdll.LoadLibrary('libtest.dylib')
req = SMB_REQUEST(1, 2, 3, 'test')
result = libtest.SmBusReadByte(ctypes.c_voidp(0x12345678), ctypes.byref(req))
print 'result: %d' % result
$ python test.py
SmBusReadByte: handle=0x12345678 request=[1 2 3 test]
result: 13
UPDATE
You're having problems because you need to set the result type of open_smbus to void*. By default, ctypes assumes that functions return ints. You need to say this:
open_smbus.restype = ctypes.c_void_p
You were getting an error because you were using c_void_p() (note the extra parentheses). There's an important distinction between c_void_p and c_void_p(). The former is a type, and the latter is an instance of a type. c_void_p represents the C type void*, whereas c_void_p() represents an actual pointer instance (with a default value of 0).
Try changing
("Data", type(create_string_buffer(SMB_MAX_DATA_SIZE))
to
("Data", (c_char * SMB_MAX_DATA_SIZE)]

Categories