Magic User Group

Magic Wiki-Wakka : CallingDLLsTest

HomePage :: CATEGORIES | Index :: Changes :: Comments :: Search :: Login/Register
Since these things with different calling conventions, the "@" notation for signaling Magic a "Standard DLL" and the conglomeration of available functions available to call external functions residing in a DLL are really a bit confusing, I did some test. Results are documented below.

Some of the results were surprising - or at least interesting.

I did not do much tests with FASTCALL. It works, unlike the other calling conventions it will however almost certainly crash your Magic application if you invoke it with the wrong DLL function. That's no surprise due to the low-level nature of FASTCALL and register access.

All tests were with eDeveloper 9.4 SP7. v10 most likely won't behave different - basically nothing changed in this respect between versions 9.4 and 10.1. For v8 this may be different. Most of these new things ("@ notation" in module specifiers, calling conventions) came with 8.3. They were not always working as expected.

For the tests I did, I wrote me a small DLL in C containing one function. I then generated me a debug build and a Magic (9.4) application as host for my DLL. Program execution I halted in the DLL. By looking in the diassembly there I was able to identify the calling convention Magic used for invocation of the function (procedure).

The name of the DLL I used is mgp.dll; the name of the function I tested with is "get_name". C declaration for this function get_name would be as follows:
long* get_name (char* A, long* B);


A sample CallDLL invocation from Magic then would be:
(Update) C = CallDLL('mgp.get_name', 'ALL', A, B)


The Magic client was a simple Magic program with three (virtual) variables
A - Alpha(128)
B - Numeric (N10)
C - Numeric (N10)


The mgp.get_name function changes both parameters and returns a value.

Before invocation of the external function I always initialized these variables with:
Update A = ''
Update B = LEN(A)
Update C = 0




1.) tests with "Magic DLLs"

For this test I gave the DLL a proper MAGIC_BIND. get_name() was declared _cdecl and NOT explicitely exported. This is a "Magic DLL" (extension/driver)

1.1) CALL UDP 'mgp.get_name', cnv: "C" (A, B)
Works. A and B changed
Magic implemented: _cdecl.

This is the good old CALL UDP on Magic DLLs. CALL UDP expects a "Magic DLL" unless you tell different by prefixing the module name with "@". The calling convention used is "C". A requirement for Magic DLLs. Some may remember that this was messed up in some 8.x -> 8.3 conversions. CALL UDP became "Standard" calling convention all of a sudden.
1.2) CALL UDP 'mgp.get_name', cnv: "Standard" (A, B)
Works. A and B changed
Magic implemented: _stdcall.

Note: I wrote "Works!". Magic however now used calling convention _stdcall (Standard) as advised by the "cnv:" setting. This is not supposed to ALWAYS work. mgp.get_name in this test is _cdecl. It has no stack cleanup code implemented. The caller needs to do. You NEED to use calling convention "C" for this. I am not sure if ever anything will happen because Magic's stack handling seemed quite hard-coded to me, I did not reverse-engineer it completely now however to find out. Again ... using calling convention "Standard" on a function actually having calling convention "C" is NOT a safe operation (I suppose).

1.3) CALL UDP '@mgp.get_name', cnv: "C" (A, B)
Failed: User function/procedure not found loaded

Thats obvious. the "@" signals Magic: "Standard DLL!!". Magic does not even look if there's a MAGIC_BIND then. It tries to load the function which will fail since its a Magic DLL which does not have this function explicitely exported.

1.4) Update C = UDF('mgp.get_name', A, B)
Works, A and B unchanged, 8 returned and assigned to C
Magic implemented _cdecl

Works. UDF on a Magic DLL will use calling convention _cdecl. UDF allows to return a value; the parameter changes in the DLL are not reflected. Magic passes a copy of the actual variables.

1.5) Update C = UDFS('mgp.get_name', A, B)
Works, A and B unchanged, 8 returned and assigned to C
Magic implemented _stdcall
Works. As noted in 1.2) ... result is questionable. Magic implemented _stdcall, the function is declared _cdecl. Don't use UDFS here

1.6) Update C = UDF('@mgp.get_name', A, B)
Failed: Failed to load driver: @MGP.DLL

Unlike documented in the "Whats's new" of 8.3, UDF does NOT support this "@" notation in module specifiers. It does not interpret the first character and instead tries to load a @MGP.DLL

1.7) Update C = UDFS('@mgp.get_name', A, B)
Failed: Failed to load driver: @MGP.DLL

same as above

1.8) Update C = CallDLL('mgp.get_name', A, B)
Failed: User function/procedure not found loaded

CallDLL does not know how to handle "Magic DLLs". Its for Standard DLLs only and looks for exported functions . MGP.DLL does not have any (besides MAGIC_BIND)

1.9) Update C = CallDLLS('@mgp.get_name', A, B)
Failed: Failed to load driver: @MGP.DLL

Well, I tried it. Since CallDLLS can ONLY handle Standard DLLs, as noted above, it does NOT cope with the "@" character.


2.) tests with "Standard DLL"

For this test I generated a "Standard DLL". It does not contain a MAGIC_BIND and exports exactly one function, get_name. Calling convention used for the function was _stdcall ("Standard"). This is a typical Windows DLL - all the Windows APIs are like that.

2.1) CALL UDP 'mgp.get_name', cnv: Standard (A, B)
Fails with: "Failed to load driver: MGP.DLL"

If module name is not prefixed with "@" CALL UDP assumes "Magic DLL" here. mgp.dll (for this test) is none. A lookup for MAGIC_BIND will fail. Note that Magic internally refers to "Magic DLLs" as "driver". If you see this you know that the "@" is missing

2.2) CALL UDP '@mgp.get_name', cnv: "Standard" (A, B)
Fails with: Wrong number of arguments in user function/procedure

Standard DLL in CALL UDP! The first parameter needs to be the parameter string and we passed ... garbage.

2.3) CALL UDP '@mgp.get_name', cnv: "Standard" ('ALL', A, B, C)
Works. A and B are changed, C receives result.
Implemented: _stdcall

This is the correct way of calling this function with CALL UDP.
2.4) Update C = UDF('@mgp.get_name', 'ALL', A, B)
Fails with: "Failed to load driver: @MGP.DLL"

We know from test 1.6, UDF does not deal with "@" and Standard DLLs. Use CallDLLS instead

2.5) Update C = UDF('mgp.get_name', 'ALL', A, B)
Fails with: "Failed to load driver: MGP.DLL"

"Failed to load driver" ... again. Can only use Magic DLLs (driver) with UDF
2.5) Update C = UDFS('@mgp.get_name', 'ALL', A, B)
Fails with: "Failed to load driver: @MGP.DLL"

2.6) Update C = UDFS('mgp.get_name', 'ALL', A, B)
Fails with: "Failed to load driver: MGP.DLL"

2.7) Update C = CallDll('mgp.get_name', 'ALL', A, B)
Works. function is invoked, A and B are unchanged, C receives return value.
Magic implements _cdecl for this !

Magic REALLY implements _cdecl for this. I triple-checked. This is not a bug because its the only way Magic knows to call a Standard DLL with _cdecl (besides CALL UDP). Its seems however pretty dangerous since the documentation does not say anything about the calling convention of CallDLL(). Furthermore, since you can use CallDLL for "Standard DLLs" only it is more likely that your DLL will have _stdcall. Be careful with CallDLL. Standard DLL ? => CallDLLS


2.8) Update C = CallDll('@mgp.get_name', 'ALL', A, B)
Fails with: "Failed to load driver: @MGP.DLL"

See previous notes. CallDLL knows Standard DLLs only. No "@" support

2.9) Update C = CallDllS('mgp.get_name', 'ALL', A, B)
Works. function is invoked, A and B are unchanged, C receives return value.
Magic implements _stdcall

This is the proper "CallDLL way" to invoke this function when you are interested in the return value only. Use CALL UDP as given in sample 2.3) if you are interested in the parameter changes as well



See also:
- calling Windows API functions
- "Magic DLLs"
- "Standard DLLs"
- eDeveloper online help on topics CallDLL, CallDLLS, CallDLLF, UDF, UDFS, UDFF and CALL (UDP) operation

 Comments [Hide comments/form]
Valid XHTML 1.0 Transitional :: Valid CSS :: Powered by Wikka Wakka Wiki trunk
Page was generated in 0.2632 seconds