views:

305

answers:

8

I am currently writing a module which interfaces with a black box 3rd party DLL for a check scanner. I need to have the DLL functions loaded dynamically, and this is working for all but one function.
The SetScanParameters function has a record structure as a parameter, which I believe is somehow interfering with the methodology I am using to dynamically load it (see below). When loaded dynamically, the function is interrupted by an access violation.
SetScanParameters does load and function properly when statically loaded, however. Is there something else that I need to be doing to dynamically load a function with a record structure?

self-edited for clarity:

Record type:

  TBK_ScanParameter=packed record
    Left:short;
    Top:short;
    Width:short;
    Length:short;
    //
    xResolution:short;
    yResolution:short;
    BitsPerPixel:short;
    LightControl:short;
    MotorControl:short;
    //
    rGain:short;
    gGain:short;
    bGain:short;
    rOffset:short;
    gOffset:short;
    bOffset:short;
    rExposure:short;
    gExposure:short;
    bExposure:short;
    //
    FeedDirection:short;
    CropImage:short;
    ScanWithMICR:short;
    //
    Reserved:array [0..14] of short;
  end;

Static declaration:

function BK_SetScanParameter(var ScanParameter:TBK_ScanParameter):integer; cdecl;

Static implementation:

function BK_SetScanParameter(var ScanParameter:TBK_ScanParameter):integer; cdecl; external 'BKV2.dll' name '_BK_SetScanParameter@4';

Dynamic logic (or what would be dynamic logic if I didn't have to use the static call to make it work):

function TdmScannerV2.SetScanParameter(pScanParameter: TBK_ScanParameter): string;
type
  TBK_SetScanParameter = function (var ScanParameter:TBK_ScanParameter):integer; stdcall;
var
  hV2Dll:HMODULE;
  func:TBK_SetScanParameter;
begin
  hV2Dll:=0;
  result := '';
  try
    hV2Dll:=LoadLibrary('BKV2.dll');
    if hV2Dll>0 then
    begin
      @func:=GetProcAddress(hV2Dll, '_BK_SetScanParameter@4');
      if Assigned(@func) then
      begin
        try
          if BK_SetScanParameter(pScanParameter) > 0 then  {This one works, but is static}
          //if func(pScanParameter) > 0 then  {this one gets an AV}
          begin
            Result := 'Y:Scan Parameters Set';
          end
          else
            Result := 'ERROR:Failure code returned';
          {
          if func(pScanParameter) > 0 then
            Result := 'Y:Scan Parameters Set'
          else
            Result := 'ERROR:Failure code returned';
          }
        except
          on e:Exception do
          begin
            Result := 'ERROR:Exception:' + e.Message;
          end;
        end;
      end
      else
        Result := 'ERROR:Unable to load BK_SetScanParameter';
    end
    else
      Result := 'ERROR:Unable to load BKV2.dll';
  finally
    if hV2Dll>0 then FreeLibrary(hV2Dll);
  end;
end;

And I've tried using stdcall, cdecl, safecall, pascal, and register on the dynamic and they all resulted in the AV. I also tried making the array in the struct [1..15] instead of [0..14]. And in the But what I don't get is, if I pass the struct into the static version, it works.

Also, there were a few typos in the OP, and I apologize for that. I was re-writing the code in the OP and made a few typos, which may've muddied the thread a bit. I've replaced it with a copy/paste of the current test function.

edit: Below is the typedef as described by the documentation for the DLL:

typedef struct ScanParameter
{
    short Left;            // left start positsion
    short Top;             // top start positsion
    short Width;           // scan image width in 1/100 inch
    short Length;          // scan image length in 1/100 inch

    short xResolution;     // horizontal resolution
    short yResolution;     // vertical resolution
    short BitsPerPixel;    // 24bit color, 8bit gray
    short LightControl;    // 0 - All lamp Off, 1 - red, 2 - green, 3 - blue, 4 - All lamp On
    short MotorControl;    // Motor Control, 0 - off, 1 = on

    short rGain;         // AFE R-Gain
    short gGain;         // AFE G-Gain
    short bGain;         // AFE B-Gain
    short rOffset;       // AFE R-Offset
    short gOffset;       // AFE G-Offset
    short bOffset;       // AFE B-Offset
    short rExposure;         // AFE R-Exposure
    short gExposure;         // AFE G-Exposure
    short bExposure;         // AFE B-Exposure

    short FeedDirection;   // feedout paper direction, 0 –fordward, 1 - backward
    short CropImage;       // 0 - no trim edge , 1 - trim edge
    short ScanWithMICR;    // 0 –off, 1 –scan image until paper leave device

    short Reserved[15];
} ScanParameter;
+1  A: 

My suggestion is to check calling convention, In delphi default calling convention was Pascal but a microsoft compiled dll would be cdecl most probably. So try to define func as

TSetScanParameter = function (var ScanParameter:TParams):integer; cdecl;

as you did at static definition.

whoi
I tried cdecl, stdcall and safecall on the dynamic one. I just tried the other two (pascal and register), no luck.I also tried using a pointer to the TScanParameter instead of the var keyword. But they all give me the AV error.
RikG
My understanding is that, you get AV after getting function address and on calling function, isn't it?
whoi
Rik, don't just *try* a bunch of things. Seek to find out *non-experimentally* (i.e., without guessing) which one is the *correct* one, and then use only that. What are you using as a reference for this function? If it's C code, then edit your question to include the original declarations.
Rob Kennedy
That's right. It loads and calls the function but gets an AV somewhere in the DLL code.
RikG
If so there might be problem with the parameter byte long you passed to the function.Delphi way of storing enum etc is different than Microsoft env. What it is the exact parameter defined for SetScanParameter in native C or C++ language?
whoi
According to the sparse documentation that came with it, it's a bunch of short variables, and then a array[0..14] of short at the end
RikG
RikG, you need to be more clear how many bytes is long the parameter of the function? Please edit your question and include C/C++ function and parameter definition and also Your Delphi TParams.
whoi
Rik, your previous comment suggests that you didn't see my comments about being precise and showing the code. Don't try to summarize things for us. Summaries are incomplete, and that will only give you incomplete answers. Can you provide a *link* to the documentation, however sparse it might be?
Rob Kennedy
A: 

It's most likely an issue with how Delphi is storing the record structure and this being incompatible with the record structure in the DLL. I've had similar issues with DLLs I've written in C interfacing to Delphi.

To quote

Normally, complex data types, such as records have their elements aligned to 2, 4 or 8 byte boundaries, as appropriate to the data type. For example, a Word field would be aligned to a 4 byte boundary. Records are also padded to ensure that they end on a 4 byte boundary.

Consequently when you pass the record the field alignment will most likely differ from the definition being used internally and you see an access violation. Because the memory allocation differ between static and dynamic it's perfectly possible for the behaviour to see arise because the static allocation coincidentally does align properly.

The first thing to try is to use the Packed keyword on the record.

TPackedRecord = Packed Record

Again to quote the manual

The Packed keyword tells Delphi to minimise the storage taken up by the defined object. The Packed overrides this, compressing the data into the smallest storage, albeit with consequential reduced access performance.

Assuming your calling conventions are correct I would expect this to solve the issue.

Cruachan
Neither Packed Record, nor using cdecl solved it. It's looking like I'm going to have to go with the backup plan of writing a DLL wrapper for this DLL
RikG
That's OR, not XOR I hope? (like you did try both at once?)
Cruachan
Yep, OR. I tried all the combinations.
RikG
Hmm, another thought and one that's tripped me up before. Make sure your data types are consistent. For example the Delphi realvariable type is not the same as double or float (floats are usually 4 bytes, doubles 8, and real 6)
Cruachan
+2  A: 

You did define two different calling conventions:

  1. cdecl in the static declaration
  2. stdcall in the dynamic declaration.
DR
+3  A: 

As mentioned above, the calling convention looks like it should be cdecl, not stdcall. Second, try changing the load library to be,

hV2Dll := LoadLibrary('Scan.dll');

The original had 'ScanDLL.dll'.

chuckj
Hey chuck - that was a long time ago!
Jeroen Pluimers
A: 

You say the following is working:

function SetScanParameter(var Scans:TParams):integer; cdecl; external 'Scan.dll' name '_SetScanParameter@4';

Yet, the other is not working.... For a start, make sure you're doing the same things in each. Currently you are not, see below:

TSetScanParameter = function (var ScanParameter:TParams):integer; stdcall;
hV2Dll:=LoadLibrary('ScanDLL.dll');

Not to mention that you may be passing different passing different values in each call, you may be using different declarations of TParams. If you want to find out why one apple is different from another apple, you don't go and compare it to an orange.

Get these basics right; then, if you still have problems:

  • Provide us with the declaration of TParams.
  • Provide us with the relevant extract of this 3rd Party DLL documentation.
  • The language used to create the DLL would be useful if you can find out.
  • The actual declarations of the method and structure in the above language would be even more useful.

If you want help, at least put in the effort to help us help you.

Craig Young
A: 

If you have some demonstration .exe loading the DLL, see if you can disassemble it, and find the spot where it calls this function. If you are lucky, and the function is not too complicated, you might find out how big the record is supposed to be.

Afaik the alignment is typical for C compilers (everything is either short or a complex of short, and all aligned on natural bounderies), but trying to align the array on some bigger boundery (4,8,16 bytes) and/or padding the record with some extra dummy might help also.

Marco van de Voort
A: 

Can you post the C function declaration from the header file? Also check the load address of the DLL when the function is compiled as static vs address where the DLL is loaded dynamically. DLL itself could have some code that is location specific and when you link it statically Windows loader will load the DLL to correct location in memory but when you load it dynamically there may already be something at that location so Windows loader will relocate the DLL to new location but the code inside DLL may not have been written correctly and it may still be trying to access some static memory address. Another thing to try is loading and unloading the DLL outside the function call and declaring the dynamic function type outside the function, Delphi compiler could be optimizing something differently when you declare all of it inside the function. Finally make sure you are using the correct calling convention (should be available in C header file) as they are very different and DLL functions will cause A/Vs or stack issues if called with incorrect convention.

DeanM
A: 

Thanks for the help guys. Turns out it's no longer an issue. The DLL the scanner company had out was a bit buggy and they put out a new one which fixes the problem.

RikG