I am working on a DLL that is callable from MS Word Visual Basic. Everything works fine except if it encounters a runtime error, and I leave the DLL with ExitProcess. In that case, Word says bye without even asking if you want to save your precious work.
Any idea how that could be handled? Triggering On Error GoTo would be ideal, of course....
Quoteinclude \masm32\MasmBasic\MasmBasic.inc
; Demo showing how to use assembler from MS Word Visual Basic
; Do not use - it is buggy
.code
LibMain proc instance:DWORD, reason:DWORD, unused:DWORD
m2m eax, TRUE
ret
LibMain endp
GetString proc uses esi MyIndex:DWORD, pString:DWORD ; $export
mov eax, chr$("A string")
.if MyIndex<0
invoke ExitProcess ; NO GOOD
.endif
ret
GetString endp
end LibMain
The VB part:
Private Declare Sub GetString Lib "D:\masm32\MasmBasic\Mb2VB\MbDll.dll" (ByVal StrIndex As Long, ByVal lpString As String)
Sub MasmBasic()
Dim JJ$
JJ$ = "Hello Jochen"
SayHello JJ$ + ", how are you?"
End Sub
Sub ShowString()
Dim JJ$
JJ$ = Space$(1000) ' Loads \masm32\include\Windows.inc
GetString 2, JJ$
MsgBox JJ$, vbOKCancel, "MasmBasic returned a string:"
End Sub
I may not fully understand the nature of the problem however from my ancient vb archived knowledge
If JJ$ <> "" Then
might work or
If JJ$ = vbNullString Then
might work
In the VB section?.... You could use a 'vararg' type return (I forget the actual name) if NULL was to be possible....
This returns control of any exiting to the VB Code as the parent code
what about FreeLibraryAndExitThread (http://msdn.microsoft.com/en-us/library/ms683153(v=vs.85).aspx) (or simply FreeLibrary)?
qWord
It is actually a bit trickier:
- VB calls a dll written in Masm
- in the dll code, a fatal error happens
- the dll calls ExitProcess
- MS Word says silently bye
I have debugged the normal return. VB checks if edi==esp on return, if not it raises an error saying Bad DLL calling convention. That could be the best option: Instead of ExitProcess, try a regular return but change edi to raise a VB error.
Have you tried just sending a WM_CLOSE to word ? That way you give it a chance to clean up before it exits.
Quote from: donkey on March 09, 2011, 09:42:30 PM
Have you tried just sending a WM_CLOSE to word ? That way you give it a chance to clean up before it exits.
Sounds promising. But finding the handle to the right instance is not that simple... ::)
Quote from: jj2007 on March 09, 2011, 10:33:15 PM
Sounds promising. But finding the handle to the right instance is not that simple... ::)
Oh, I thought you were attached with the DLL, in that case you could have used GetCurrentProcessId to find the process ID then Enumerate the windows checking them against the process id using GetWindowThreadProcessId. Or alternatively you can get the main thread handle and use PostThreadMessage, never used that function myself but it might work.
I'm not an VB expert, but AFAIK:
ByVal StrIndex As Long
is equivalent to
StrIndex: ptr SDWORD
.
So removing the 'ByVal' should make it work (mabye ^^).
Thanks, Edgar. Sounds a bit complicated but probably it is the cleanest option.
In the meantime, I found a dirty hack that works, see attachment. Basically, in case of an error it jumps back to VB with edi different from the value VB expects. So VB complains about a 'bad DLL calling convention' but exits gracefully.
@qWord: It does work already. The only problem is an irregular exit, e.g. if a runtime error occurs.
JJ,
The ExitProcess() is probably the problem in that your DLL probably does not create a new thread. The ExitProcess() is probably being sent to the parent (Word in this instance) and this shuts it down.
Quote from: jj2007 on March 09, 2011, 08:31:32 PMAny idea how that could be handled? Triggering On Error GoTo would be ideal, of course....
.if MyIndex<0
invoke RaiseException ...
.endif
On Error GoTo ...
GetString ...
Quote from: hutch-- on March 10, 2011, 12:03:08 AM
The ExitProcess() is probably the problem in that your DLL probably does not create a new thread. The ExitProcess() is probably being sent to the parent (Word in this instance) and this shuts it down.
Hutch,
You are right, ExitProcess shuts down Word in a rather rude manner, without the mechanisms that WM_CLOSE would provide.
Quote from: drizz on March 10, 2011, 12:35:55 AM
.if MyIndex<0
invoke RaiseException ...
.endif
Drizz,
The problem is the RaiseException - at the end, the code
must return to where it left VB. My current runtime error routines don't care about the stack (you never know at which level of recursion a file not found happens), they just clean up open handles etc and then call ExitProcess. Works fine for an executable, but VB doesn't like it.
So what I do in the end is store ebp on entry, knowing that ebp=esp+4; and when an error is triggered, I recover the correct esp, zero edi to flag an error, and return to VB. It's an undocumented feature of VB but it works perfectly.
Thanks to all for good advice :U
Surely if your dll has an error it should just return an error value? ExitProcess isn't for dlls.
Quote from: MSDNCalling ExitProcess in a DLL can lead to unexpected application or system errors. Be sure to call ExitProcess from a DLL only if you know which applications or system components will load the DLL and that it is safe to call ExitProcess in this context.
Make GetString a function with a return value, then handle it in the VB code.
Quote from: sinsi on March 10, 2011, 09:02:07 AMSurely if your dll has an error it should just return an error value? ExitProcess isn't for dlls.
Sinsi,
The problem is the "return". If you try to Open "I", #1, "NoSuchFileHere.txt", MasmBasic shows you a MsgBox telling you that you are a bad coder, then closes open file handles and exits. That is by design - you can use Exist(MyFile$) if you want it even more gracefully.
But this design implies that you have no control over the current state of the stack. The
file not found might happen in your n-th subproc, and in theory you could pass the error message back to all levels but in practice you'll just say get outta here. That works fine with executables, ExitProcess, but VB wants that the call to the dll is being returned. So that is what I am doing, and it works fine, just triggers a "Bad DLL calling convention", and when you click Debug, the VB editor even highlights the line where it happened. Perfect :bg
Quote from: jj2007 on March 10, 2011, 11:29:47 AMThe problem is ...., MasmBasic
Now we are getting somewhere... :bg
Why don't you implement seh macros for MasmBasic and have "Open" routine raise an error(exception) instead of "MsgBox".
Simplest seh macros could be written like this:
SehHandler PROTO C :DWORD,:DWORD,:DWORD,:DWORD
UnhandledException proto :DWORD
sSEH STRUCT
OrgEsp DD ?
OrgEbp DD ?
SaveEip DD ?
sSEH ENDS
.data?
SEH sSEH <?>; Single threaded only!
.const
OnErrorBegin macro
LOCAL _AddrOnError
mov SEH.SaveEip, OFFSET _AddrOnError
mov SEH.OrgEbp, ebp
push OFFSET SehHandler
xor edx,edx
push dword ptr fs:[edx]
mov dword ptr fs:[edx], esp
mov SEH.OrgEsp, esp
OnErrorEnd macro
_AddrOnError:
xor edx,edx
pop dword ptr fs:[edx]
add esp,4
endm
endm
SehHandler PROC C pExcept:DWORD,pFrame:DWORD,pContext:DWORD,pDispatch:DWORD
mov eax, pContext
push SEH.OrgEsp
push SEH.OrgEbp
push SEH.SaveEip
pop [eax][CONTEXT.Eip_]
pop [eax][CONTEXT.Ebp_]
pop [eax][CONTEXT.Esp_]
xor eax,eax ;ExceptionContinueExecution
RET
SehHandler ENDP
OnErrorBegin
push 10
push 0
mov eax,11
bound eax,dword ptr [esp]
add esp,8
OnErrorEnd
Quote from: drizz on March 10, 2011, 12:08:22 PM
Quote from: jj2007 on March 10, 2011, 11:29:47 AMThe problem is ...., MasmBasic
Now we are getting somewhere... :bg
Why don't you implement seh macros for MasmBasic and have "Open" routine raise an error(exception) instead of "MsgBox".
drizz,
Thanks for the SEH lesson, but the problem is not MasmBasic - Visual Basic is the problem. I want a "graceful crash" if there is a coding error, such as opening a non-existent file, or asking for an array element with a negative index. The solution attached above works perfectly - remember the return address in a global variable, and return to VB with a "wrong" edi value to trigger the runtime error. Before the VB error message, MasmBasic will show the real error, and clean up open handles etc.
I don't know if this could help you, but when I had to trap the result of a DLL function(wriiten in C ) in VB (via On error ...) I always used the structure HRESULT.
VB doesn't interpret HRESULT as a normal handle to a result but to a special automation type that says if a fucntion is successful or not.
Here there is a description of the structure : http://www.maruf.ca/files/caadoc/CAASysQuickRefs/CAASysHRESULT.htm
After i declare an ODL file (automation description) to create a type library. At this point you can declare the fucntions in Vb and errors are trappable with On error goto statement.
Thanks, Marko. It seems that HRESULT is set by COM interfaces, pretty complex for my taste. A DLL in assember is typically a snippet meant to accelerate an innermost loop.
Anyway, the solution posted above works fine. What was really nasty was the sudden exit of MS Word. Now if there is a runtime error in the DLL, VB stops and displays a 'The DLL returned an error' message.
Sub ShowString()
Dim JJ$, ErrDesc$
WantError = 1
On Error GoTo MyErr
JJ$ = "Bad luck" + Space$(1000) ' Loads \masm32\include\Windows.inc
For n = 0 To 22271 ' with 22273 lines max, 7 secs/Mio
GetString n, JJ$
Next
If WantError Then
GetString2 123456, JJ$ ' Trigger an error
Else
GetString2 2, JJ$ ' Show line 2 (zero-based) of Windows.inc
End If
MsgBox JJ$, vbOK, "MasmBasic returned a string from Windows.inc:"
Exit Sub
MyErr:
ErrDesc$ = "Non-DLL error:"
If Err.Number = 49 Then ErrDesc$ = "The DLL returned an error"
MsgBox Err.Description, vbOK, ErrDesc$
End Sub