Hey guys,
I'm attempting to write a .NET stub for a Scheme program. I want to compile the gambit scheme code down to C and then compile it into a Windows DLL. Then I can load this DLL into .NET and write an interface for it.
I've accomplished all of this (with a simple hello world program), but the .NET app always crashes. The first time I tried it, I used cygwin and compiled this scheme code:
* (c-define (helloworld) () void "helloworld" "" * (display "Hello"))
with gsc like this:
* gsc -link func.scm
and then compiled the corresponding c files with gcc like so:
* gcc -Iinclude -shared -D___DYNAMIC func*.c -o func.dll -lgambc -lwsock32 -lws2_32
which compiled fine. That should produce a Windows DLL, right? I'm a little skeptical at that part because I've never used gcc to produce a windows dll before. But read on before you think that's the problem.
As the final step I created the .NET interface with the following few lines:
* [DllImport("func.dll")] * public static extern void helloworld();
And this compiled, but .NET threw an access violation at me when the function ran. Actually, I fixed some of the gcc flags and got it instead to throw a NullReference exception when the function returned. Since I was skeptical about gcc, I tried compiling a hand-rolled ansi c file that contained the equivalent helloworld function with basically the same gcc command as above, ran the .NET program and the function ran fine (printed "Hello World" to the console). It shouldn't be anything with the calling convention or anything either since it looks like the c file produced by scheme declares the helloworld function as __cdecl. I know that the Gambit-C code includes a bunch of other stuff though (note how I had to link the winsock libraries), so it must be somewhere in there that I'm not compiling it right or something.
I found the Visual Studio solution files in the gambit directory and tried compiling gambit to a windows shared lib to see if that would help. I recompiled the Gambit-C code with visual studio and linked gambit in to a complete dll, ran the .NET app again, and now it's giving me an exception saying it can't find an entry point in the dll for the helloworld function.
There's got to be something I'm missing, has anyone else gotten this to work? Or does anyone see anything wrong with how I'm compiling the dll (the loading part is relatively simple and I doubt the problem is in there)?
Thanks, James
Afficher les réponses par date
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1
On 13-Feb-07, at 1:18 AM, James Long wrote:
There's got to be something I'm missing, has anyone else gotten this to work? Or does anyone see anything wrong with how I'm compiling the dll (the loading part is relatively simple and I doubt the problem is in there)?
Yes. What is missing is the *initialization* of the Gambit-C runtime system (all the tables used by Gambit-C, the memory manager, the I/O subsystem, etc). This is done with a call to ___setup(...). The initialization must happen before the first call to any function your Scheme DLL is exporting. The best approach is probably to call ___setup(...) when the code is loaded (i.e. the call ___setup(...) should be in the DllMain function). It is good style to call ___cleanup() when the DLL is unloaded.
I've copied below the code that works for me. I'm using MinGW. Small variations may be needed to the makefile for Cygwin and Microsoft's compiler.
Let me know if this works with .NET .
Marc
********************************************************************** # File: "makefile"
GSC="C:/Gambit-C/4.0b20/bin/gsc"
all: main.exe mydll.dll main
main.exe: main.c mydll.dll mydll.h gcc -Wall -o main.exe main.c mydll.dll
mydll.dll: mydll.c mydll_.c gcc -shared -IC:/Gambit-C/4.0b20/include -LC:/Gambit-C/ 4.0b20/lib -o mydll.dll mydll.c mydll_.c -lgambc -lwsock32 -lws2_32
mydll.c: mydll.scm $(GSC) -c mydll.scm
mydll_.c: mydll.c $(GSC) -link mydll.c ********************************************************************** /* File: "main.c" */
#include <stdio.h>
#include "mydll.h"
int main () { printf ("func1 (10, "*") = %d\n", func1 (10, "*")); printf ("func2 ("hello") = %s\n", func2 ("hello")); return 0; } ********************************************************************** /* File: "mydll.h" */
int func1 (int n, char* s); char* func2 (char* s); ********************************************************************** ;; File: "mydll.scm"
;;;----------------------------------------------------
;; Initialize Gambit-C runtime system when "mydll.dll" ;; is loaded.
(c-declare #<<c-declare-end
#define LINKER ____20_mydll__
___BEGIN_C_LINKAGE extern ___mod_or_lnk LINKER (___global_state_struct*); ___END_C_LINKAGE
___setup_params_struct setup_params;
BOOL WINAPI DllMain (HINSTANCE hinst, DWORD reason, LPVOID ptr) { switch (reason) { case DLL_PROCESS_ATTACH: ___setup_params_reset (&setup_params); setup_params.version = ___VERSION; setup_params.linker = LINKER; #if 1 setup_params.debug_settings = (___DEBUG_SETTINGS_REPL_STDIO << ___DEBUG_SETTINGS_REPL_SHIFT); #endif return ___setup (&setup_params) == ___FIX(___NO_ERR);
case DLL_PROCESS_DETACH: ___cleanup (); break; }
return FALSE; }
c-declare-end )
;;;----------------------------------------------------
;; Procedures exported by "mydll.dll":
(c-define (func1 n s) (int char-string) int "func1" "" (let loop ((i 0)) (if (< i n) (begin (display s) (loop (+ i 1))))) (expt 2 n))
(c-define (func2 s) (char-string) char-string "func2" "" (list->string (reverse (string->list s))))
;;;----------------------------------------------------
(display "hello from mydll.scm\n") **********************************************************************
On 2/13/07, Marc Feeley feeley@iro.umontreal.ca wrote:
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1
On 13-Feb-07, at 1:18 AM, James Long wrote:
There's got to be something I'm missing, has anyone else gotten this to work? Or does anyone see anything wrong with how I'm compiling the dll (the loading part is relatively simple and I doubt the problem is in there)?
Yes. What is missing is the *initialization* of the Gambit-C runtime system (all the tables used by Gambit-C, the memory manager, the I/O subsystem, etc). This is done with a call to ___setup(...). The initialization must happen before the first call to any function your Scheme DLL is exporting. The best approach is probably to call ___setup(...) when the code is loaded (i.e. the call ___setup(...) should be in the DllMain function). It is good style to call ___cleanup() when the DLL is unloaded.
I've copied below the code that works for me. I'm using MinGW. Small variations may be needed to the makefile for Cygwin and Microsoft's compiler.
Let me know if this works with .NET .
Marc
Thanks, that worked great! Ah the benefits of compiling to C. brilliant.
Could you explain the LINKER macro, though? Or is there any documentation about this setup anywhere? I found a couple other emails about this but none of them really explained it. What is LINKER (defined as ____20_dll__ in mine, which supposedly works) supposed to represent?
Also, what exactly does the D___DYNAMIC flag to gcc do, out of curiousity? I'm assuming it does things like tell gambit not to generate a main function, and possibly specify calling conventions and exporting attributes. Seems like it's mainly used to generate a dynamic library to be loaded by Gambit, though I think I need it in this situation as well. Gambit could use the same flag to generate that DllMain function, but I guess the focus is on a library for use with Gambit.