mrider

Can't write DLL using __stdcall that works with AutoIt (__cdecl works)

5 posts in this topic

Here's my situation: I've dinked around with trying to create a C++ object in AutoIt by matching the object declaration as a struct, and failed miserably.  No doubt it's because while I understand in principal what "BEGIN_INTERFACE" and "END_INTERFACE" do, I don't really know what they do in actual fact, and consequently I can't replicate that when designing an AutoIt struct.

So - I figured I'd write a C DLL that fronts and/or wrappers my C++ objects that will allow me to basically steer them from AutoIt.  To validate that I know WTF I'm doing, I figured I'd write a dead simple C++ object and wrapper functions that allow me to communicate back and forth.  I can get it all to work if I declare my wrappers as __cdecl, but I can't get it to work if they're declared __stdcall.  I'm compiling from mingw.

Keep in mind that the code below is only intended to validate my thoughts.  So I took some fairly obvious and glaring shortcuts with safety and etcetera because I'm simply validating that I can instantiate a C++ object, and then have it call back to my AutoIt code.  We're not necessarily interested in style. 

What I'm interested in is why AutoIt can't see the functions if they're declared __stdcall.  I must be blind, or too close to the problem, because I just can't see why it doesn't work.  If I alter everything to use __cdecl it all works swimmingly.  With it as is, I get the error printout below.

 

>"C:\Program Files (x86)\AutoIt3\SciTE\AutoIt3Wrapper\AutoIt3Wrapper.exe" /run /prod /ErrorStdOut /in "C:Scriptstest.au3" /UserParams    
+>12:38:15 Starting AutoIt3Wrapper v.14.801.2025.0 SciTE v.3.4.4.0   Keyboard:00000409  OS:WIN_7/Service Pack 1  CPU:X64 OS:X64    Environment(Language:0409)
+>         SciTEDir => C:Program Files (x86)AutoIt3SciTE   UserDir => C:Program Files (x86)AutoIt3SciTEAutoIt3Wrapper
>Running AU3Check (3.3.12.0)  from:C:Program Files (x86)AutoIt3  input:C:Scriptstest.au3
+>12:38:15 AU3Check ended.rc:0
>Running:(3.3.12.0):C:Program Files (x86)AutoIt3autoit3.exe "C:Scriptstest.au3"    
--> Press Ctrl+Alt+Break to Restart or Ctrl+Break to Stop
'init' not found in the DLL file
'add_callback' not found in the DLL file
'add_callback' not found in the DLL file
'add_callback' not found in the DLL file
'add_callback' not found in the DLL file
'uninit' not found in the DLL file
+>12:38:15 AutoIt3.exe ended.rc:0
+>12:38:15 AutoIt3Wrapper Finished.
>Exit code: 0    Time: 0.533

 

All my code:

// File: test.h
#ifndef _TEST_H_
#define _TEST_H_

#ifdef BUILDING_TEST_DLL
#define TEST_DLL __declspec(dllexport)
#else
#define TEST_DLL __declspec(dllimport)
#endif

#ifdef __cplusplus
extern "C" {
#endif

void __stdcall TEST_DLL init(void);
void __stdcall TEST_DLL uninit(void);
void __stdcall TEST_DLL add_callback(const void * callback);
void __stdcall TEST_DLL do_callback(void);


#ifdef __cplusplus
}
#endif


#endif // _TEST_H_
// File: test.cpp
#include <stdlib.h>
#include <vector>
#include "test.h"

#define test_callback size_t
class TestObject {
    public:
        void __stdcall AddCallback(const void * pCallback);
        void __stdcall DoCallback(void);

        TestObject();
        ~TestObject();

    private:
        std::vector<const void *> m_pCallbacks;
        size_t m_uiLastIndex;

};

TestObject::TestObject() {
    m_pCallbacks.reserve(10);
    m_uiLastIndex = 0;
}

TestObject::~TestObject() {
    // No action needed since vector cleans up
}

void __stdcall TestObject::AddCallback(const void * pCallback) {
    if(pCallback) m_pCallbacks.push_back(pCallback);
}

void __stdcall TestObject::DoCallback(void) {
    size_t (*callback) (size_t);
    callback = (size_t(*)(size_t)) m_pCallbacks[m_uiLastIndex];
    callback(m_uiLastIndex);
    m_uiLastIndex++;
    if(m_uiLastIndex > (m_pCallbacks.size() -1)) {
        m_uiLastIndex = 0;
    }
}

// C Wrapper functions
TestObject * test_object;

void __stdcall init() {
    test_object = new TestObject();
}

void __stdcall TEST_DLL uninit(void) {
    delete test_object;
}

void __stdcall TEST_DLL add_callback(const void * callback) {
    test_object->AddCallback(callback);
}


void __stdcall TEST_DLL do_callback(void) {
    test_object->DoCallback();
}
OnAutoItExitRegister("TestDone")

Local $func_template = "CallBack%d"
Local $handles[4]
Local $pointers[4]
Local $dll = DllOpen("test.dll")
DllCall($dll, "NONE", "init")
If @error Then
    TranslateDllCallError(@error, "init")
EndIf

For $i = 0 To UBound($handles) - 1
    Local $name = StringFormat($func_template, ($i + 1))
    $handles[$i] = DllCallbackRegister($name, "ULONG_PTR", "ULONG_PTR")
    $pointers[$i] = DllCallbackGetPtr($handles[$i])
    DllCall($dll, "NONE", "add_callback", "PTR", $pointers[$i])
    If @error Then
        TranslateDllCallError(@error, "add_callback")
    Else
        ConsoleWrite("Added pointer " & $i & @LF)
    EndIf
Next

For $i = 1 To 10
    DllCall($dll, "NONE", "do_callback")
Next


Func TranslateDllCallError($err, $func)
    Switch $err
        Case 1
            ConsoleWriteError("Unable to use DLL file" & @LF)
        Case 2
            ConsoleWriteError("Unknown 'return type'" & @LF)
        Case 3
            ConsoleWriteError("'" & $func & "' not found in the DLL file" & @LF)
        Case 4
            ConsoleWriteError("Bad number of parameters" & @LF)
        Case 5
            ConsoleWriteError("Bad parameter" & @LF)
        Case Else
            ConsoleWriteError("Unknown error value" & @LF)
    EndSwitch
EndFunc

Func CallBack1($param)
    ConsoleWrite("CallBack1(" & $param & ")" & @LF)
    Return $param
EndFunc

Func CallBack2($param)
    ConsoleWrite("CallBack2(" & $param & ")" & @LF)
    Return $param
EndFunc

Func CallBack3($param)
    ConsoleWrite("CallBack3(" & $param & ")" & @LF)
    Return $param
EndFunc

Func CallBack4($param)
    ConsoleWrite("CallBack4(" & $param & ")" & @LF)
    Return $param
EndFunc


Func TestDone()
    For $i = 0 To UBound($handles) - 1
        DllCallbackFree($handles[$i])
    Next
    DllCall($dll, "NONE", "uninit")
    If @error Then
        TranslateDllCallError(@error, "uninit")
    EndIf
    DllClose($dll)
EndFunc
:: build.bat

@echo off
IF EXIST *.dll DEL *.dll
IF EXIST *.o DEL *.o
IF EXIST *.a DEL *.a

g++ -Wall -c -DBUILDING_TEST_DLL test.cpp
g++ -shared -o test.dll -static-libgcc -static-libstdc++ test.o -Wl,--out-implib,libtest_dll.a

How's my riding? Dial 1-800-Wait-There

Trying to use a computer with McAfee installed is like trying to read a book at a rock concert.

Share this post


Link to post
Share on other sites



I never successed to do this too, using VC++, however I'm sure I did it all OK.

Keeping forward to oit.

Share this post


Link to post
Share on other sites

#3 ·  Posted (edited)

Hello,
I do it always follows!

/*
 * test.h
 *
 */

#ifndef TEST_H_
#define TEST_H_


#ifdef BUILD_DLL
    #ifdef __cplusplus
        #define DLL_EXPORT extern "C" __declspec(dllexport)
    #else
        #define DLL_EXPORT __declspec(dllexport)
    #endif
#else
    #define DLL_EXPORT
#endif

DLL_EXPORT void __stdcall init(void);
DLL_EXPORT void __stdcall uninit(void);
DLL_EXPORT void __stdcall add_callback(const void * callback);
DLL_EXPORT void __stdcall do_callback(void);



#endif /* TEST_H_ */

 

/*
 * test.cpp
 *
 */

#include "test.h"

#include <_mingw.h>
#include <minwindef.h>
#include <crtdefs.h>
#include <vector>

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch (fdwReason)
    {
        case DLL_PROCESS_ATTACH:
            // attach to process
            // return FALSE to fail DLL load
            break;

        case DLL_PROCESS_DETACH:
            // detach from process
            break;

        case DLL_THREAD_ATTACH:
            // attach to thread
            break;

        case DLL_THREAD_DETACH:
            // detach from thread
            break;
    }
    return TRUE; // succesful
}


#define test_callback size_t
class TestObject {
    public:
        void __stdcall AddCallback(const void * pCallback);
        void __stdcall DoCallback(void);

        TestObject();
        ~TestObject();

    private:
        std::vector<const void *> m_pCallbacks;
        size_t m_uiLastIndex;

};

TestObject::TestObject() {
    m_pCallbacks.reserve(10);
    m_uiLastIndex = 0;
}

TestObject::~TestObject() {
    // No action needed since vector cleans up
}

void __stdcall TestObject::AddCallback(const void * pCallback) {
    if(pCallback) m_pCallbacks.push_back(pCallback);
}

void __stdcall TestObject::DoCallback(void) {
    size_t (*callback) (size_t);
    callback = (size_t(*)(size_t)) m_pCallbacks[m_uiLastIndex];
    callback(m_uiLastIndex);
    m_uiLastIndex++;
    if(m_uiLastIndex > (m_pCallbacks.size() -1)) {
        m_uiLastIndex = 0;
    }
}

// C Wrapper functions
TestObject * test_object;

DLL_EXPORT void __stdcall init() {
    test_object = new TestObject();
}

DLL_EXPORT void __stdcall uninit(void) {
    delete test_object;
}

DLL_EXPORT void __stdcall add_callback(const void * callback) {
    test_object->AddCallback(callback);
}


DLL_EXPORT void __stdcall do_callback(void) {
    test_object->DoCallback();
}

with the following compiler options

for Debug Version:

g++ -DBUILD_DLL -O0 -g3 -Wall -c -fmessage-length=0 -o test.o "..\\test.cpp" 
g++ -s -mwindows -Wl,--add-stdcall-alias -Wl,--kill-at -static-libgcc -static-libstdc++ -shared -Wl,--out-implib=TestDLL.lib -Wl,--output-def=TestDLL.def -o TestDLL.dll test.o

and for Release Version:

g++ -DBUILD_DLL -O3 -Wall -c -fmessage-length=0 -o test.o "..\\test.cpp" 
g++ -s -mwindows -Wl,--add-stdcall-alias -Wl,--kill-at -static-libgcc -static-libstdc++ -shared -Wl,--out-implib=TestDLL.lib -Wl,--output-def=TestDLL.def -o TestDLL.dll test.o

 

Edited by bernd670

greetings
bernd


I hacked 127.0.0.1 -> pcfred6.gif

Share this post


Link to post
Share on other sites

#4 ·  Posted (edited)

First off, sorry it took a few days to respond - I didn't notice the new post until now. :)

It looks like you are using 64 bit MinGW, where I'm using 32 bit.  I had to change the includes a bit in test.cpp in order to get it to compile, since "minwindef.h" and "crtdefs.h" is not included with my version.  I'm not sure if that's why this doesn't work for me or what, but now AutoIt crashes when the function returns.  The good news is that AutoIt can find the function though - so I'm a bit closer.  So thanks for that, I'll investigate further when I have some free time...

Not that this is that big of a deal, it's just annoying that I can only use cdecl. :)

Edited by mrider

How's my riding? Dial 1-800-Wait-There

Trying to use a computer with McAfee installed is like trying to read a book at a rock concert.

Share this post


Link to post
Share on other sites

#5 ·  Posted (edited)

Hello, for me it also works with the 32 bit version of mingw. I only

#include <minwindef.h>
#include <crtdefs.h>

replaced by

#include <windef.h>

and then with the same compiler options compiled as mentioned above.
Important as well as the option is

#AutoIt3Wrapper_UseX64=n

in AutoIt.

With this script

#AutoIt3Wrapper_UseX64=n

OnAutoItExitRegister("TestDone")

Local $func_template = "CallBack%d"
Local $handles[4]
Local $pointers[4]
Local $dll = DllOpen("TestDLL.dll")
DllCall($dll, "NONE", "init")
If @error Then
    TranslateDllCallError(@error, "init")
EndIf

For $i = 0 To UBound($handles) - 1
    Local $name = StringFormat($func_template, ($i + 1))
    $handles[$i] = DllCallbackRegister($name, "ULONG_PTR", "ULONG_PTR")
    $pointers[$i] = DllCallbackGetPtr($handles[$i])
    DllCall($dll, "NONE", "add_callback", "PTR", $pointers[$i])
    If @error Then
        TranslateDllCallError(@error, "add_callback")
    Else
        ConsoleWrite("Added pointer " & $i & @LF)
    EndIf
Next

For $i = 1 To 10
    DllCall($dll, "NONE", "do_callback")
Next


Func TranslateDllCallError($err, $func)
    Switch $err
        Case 1
            ConsoleWriteError("Unable to use DLL file" & @LF)
        Case 2
            ConsoleWriteError("Unknown 'return type'" & @LF)
        Case 3
            ConsoleWriteError("'" & $func & "' not found in the DLL file" & @LF)
        Case 4
            ConsoleWriteError("Bad number of parameters" & @LF)
        Case 5
            ConsoleWriteError("Bad parameter" & @LF)
        Case Else
            ConsoleWriteError("Unknown error value" & @LF)
    EndSwitch
EndFunc

Func CallBack1($param)
    ConsoleWrite("CallBack1(" & $param & ")" & @LF)
    Return $param
EndFunc

Func CallBack2($param)
    ConsoleWrite("CallBack2(" & $param & ")" & @LF)
    Return $param
EndFunc

Func CallBack3($param)
    ConsoleWrite("CallBack3(" & $param & ")" & @LF)
    Return $param
EndFunc

Func CallBack4($param)
    ConsoleWrite("CallBack4(" & $param & ")" & @LF)
    Return $param
EndFunc


Func TestDone()
    For $i = 0 To UBound($handles) - 1
        DllCallbackFree($handles[$i])
    Next
    DllCall($dll, "NONE", "uninit")
    If @error Then
        TranslateDllCallError(@error, "uninit")
    EndIf
    DllClose($dll)
EndFunc

I get the result

Added pointer 0
Added pointer 1
Added pointer 2
Added pointer 3
CallBack1(0)
CallBack2(1)
CallBack3(2)
CallBack4(3)
CallBack1(0)
CallBack2(1)
CallBack3(2)
CallBack4(3)
CallBack1(0)
CallBack2(1)

I hope my English is somewhat understandable :'(

Edited by bernd670

greetings
bernd


I hacked 127.0.0.1 -> pcfred6.gif

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now