Wednesday, February 16, 2011

Loading 32-bit Versus 64-bit DLLs [revisited]

In Monday's Loading 32-bit Versus 64-bit DLLs I wrote about how it took 5 years before I finally got around to creating a 64-bit version of a 32-bit C DLL. According to this advice, way back then, it was a requirement for running on the 64-bit version of SQL Anywhere:

Newsgroups: sybase.public.sqlanywhere.general
From: John Smirnios
Date: 11 Jul 2005 21:27:08 -0700
Subject: Re: External procedures with 64-bit engine

64-bit processes cannot load 32-bit DLLs and vice-versa. If you are using a 64-bit
server, you must build a 64-bit DLL to contain your external stored procedures.

Beware Ancient Advice!

As Volker Barth pointed out in his comments on Monday's post, matching bitness between server and DLL is no longer required...

Breck, when using external C/C++ environments, 64-bit servers can run 32-bit DLLs
and vice versa. - The external functions just have to be declared for the according
32/64-bit external envirnoment.


That being said, you could just restrict to use exactly *one* type of DLL and would
not have to check which one is available...

So the real answer might be:

You don't have to know.

If I'd read further, I might have noticed this more recent newsgroup posting:

Newsgroups: sybase.public.sqlanywhere.general
From: "Karim Khamis [Sybase iAnywhere]"
Date: 11 Jan 2010 06:20:01 -0800
Subject: Re: External procedures in x64

If you can do it, upgrade to SA 11.0.1. The external environments in SA 11.0.0
and up support the existing C external function api and you can mix and match
32 bit dlls with 64 bit servers AND vice versa. Changing your existing external
function declarations to external environment declarations is trivial since you
only need to add one of the C_ODBC32, C_ODBC64, C_ESQL32 or C_ESQL64 language
types. Once you do that, your dlls will load in an external environment and will
get invoked in exactly the same manner as they currently do within the server.
One note though, because the external environments are in a separate process,
external environment calls are much slower than in process external function
calls. As a result, if performance is the overriding factor, then your best
course of action is to see if you can recompile your dll for 64-bit.
What Karim and Volker are talking about is adding the LANGUAGE C_ESQL32 clause to the CREATE PROCEDURE statement. For example, this code works on both 32-bit and 64-bit SQL Anywhere 11 servers:


DECLARE @command_line VARCHAR ( 32766 );
DECLARE @return_code INTEGER;
DECLARE @diagnostic_code INTEGER;
DECLARE @diagnostic_string VARCHAR ( 255 );

DROP PROCEDURE rroad_get_command_line;

CREATE PROCEDURE rroad_get_command_line (
OUT command_line VARCHAR ( 32766 ),
OUT return_code INTEGER,
OUT diagnostic_code INTEGER,
OUT diagnostic_string VARCHAR ( 255 ) )
EXTERNAL NAME 'get_command_line@C:\\projects\\foxhound\\rroad1.dll' LANGUAGE C_ESQL32;

CALL rroad_get_command_line (
@diagnostic_string );

SELECT @command_line;


There is a wrinkle to the LANGUAGE C_ESQL32 clause, however: the code inside the DLL is running inside a different process and as such it has a different view of the outside world from code running inside the server itself.

For example, the get_command_line procedure in the code above calls the GetCommandLine Function in the Windows API to retrieve the command-line string for the current process. With Monday's version of the code, using separate 32-bit and 64-bit DLLs that run inside the server (no LANGUAGE C_ESQL32 clause), the command line that launched SQL Anywhere is returned:

SELECT @command_line;

'"C:\\Program Files\\SQL Anywhere 12\\bin32\\dbeng12.exe" -o dbeng12_log_ddd12.txt -oe dbeng12_log_fatal_ddd12.txt -os 10M -x tcpip -zl -zp -zt ddd12.db "-hn0,11148:216"'

'"C:\\Program Files\\SQL Anywhere 12\\bin64\\dbeng12.exe" -o dbeng12_log_ddd12.txt -oe dbeng12_log_fatal_ddd12.txt -os 10M -x tcpip -zl -zp -zt ddd12.db "-hn0,10816:196"'

When the LANGUAGE C_ESQL32 clause is used, something completely different (and unexpected) is returned:

SELECT @command_line;

'"C:\\Program Files\\SQL Anywhere 12\\bin32\\..\\bin32\\dbexternc12.exe" ESQL "ddd12" "ddd12" "EXTENV_MAIN" "527365143:131947461:3::2011-02-14 06:04:20.394" '

'"C:\\Program Files\\SQL Anywhere 12\\bin64\\..\\bin32\\dbexternc12.exe" ESQL "ddd12" "ddd12" "EXTENV_MAIN" "41488089:1092220081:4::2011-02-14 06:01:52.254" '

So, alas, Foxhound's going to stick with the kludgy code from Monday's post.

I'll say this, however... if I'd known the LANGUAGE C_ESQL32 clause made it possible to call a 32-bit DLL from a 64-bit server with no further changes, I might have looked for a way to make it work... or given up on calling GetCommandLine.


Anonymous said...

Breck, YABC (yet another blog comment):

In this particular case, I agree that the external process behaves totally different than an internal DLL (for obvious reasons).

But wouldn't the standard *property('CommandLine')* give you the wanted result from within the engine?

Something you could then
a) fetch in the server and set in the DLL via a particular API like SetSrvCommandLine() or
b) fetch inside the DLL (using the EXTFN_CONNECTION_HANDLE_ARG_NUM handle)?

Just a few remarks to get rid of kludgy code (though I have enough on my own, I should state...)


Anonymous said...

Oh, never leave a comment without testing one's advice...

That property (introduced in V9) only returns the command line arguments, i.e. it doesn't return the database engine's file path).

So that may not be exactly what you're looking for.