PInvoke and Unmanaged DLL calls

J

Joe Martin

Has anyone made use of PKWare's PKCDL.DLL for the Implode and Explode
functions?

I have been able to successfully define everything correctly and even
get the invoked function (Explode) to run properly. This means data is
being exploded properly, confirmed with CRC values and reviewing the
actual data.

However just before the unmanaged function exits, I get a null exception
error. I am using:

ReadHandler rh = new ReadHandler(this.ReadData);
WriteHandler wh = new WriteHandler(this.WriteData);

IntPtr buffer = Marshal.AllocHGlobal(bufferSize)
try {
object data = this;

// Invoke DLL here...
PkCdl.Explode(rh, wh, buffer, ref data);

} finally {
Marshal.FreeHGlobal(buffer);
}

to allocate the needed buffer for the DLL. In the corresponding
ReadData/WriteData callback methods, I use the Marshal.Copy() methods to
move data in/out of managed memory.

Now somewhere along the way an the exception happens inside the
try..finally (shown above).

If this rings a bell with anyone, I will gladly supply complete sample
code and would be more than interested in hearing your thoughts.


Thanks,


Joe Martin
 
R

Robert Jordan

Joe said:
ReadHandler rh = new ReadHandler(this.ReadData);
WriteHandler wh = new WriteHandler(this.WriteData);

IntPtr buffer = Marshal.AllocHGlobal(bufferSize)
try {
object data = this;

// Invoke DLL here...
PkCdl.Explode(rh, wh, buffer, ref data);

} finally {
Marshal.FreeHGlobal(buffer);
}

you have to pin "rh" and "wh", otherwise they are GCed.

....
} finally {
Marshal.FreeHGlobal(buffer);
}
GC.KeepAlive(rh);
GC.KeepAlive(wh);


bye
Rob
 
M

Mattias Sjögren

you have to pin "rh" and "wh", otherwise they are GCed.
...
} finally {
Marshal.FreeHGlobal(buffer);
}
GC.KeepAlive(rh);
GC.KeepAlive(wh);


The runtime will keep the parameters alive for the duration of the
call. Manually keeping the delegates alive is only necessary when the
callbacks are asynchronous. And if they are, GC.KeepAlive wont help
you.



Mattias
 
R

Robert Jordan

Mattias said:
The runtime will keep the parameters alive for the duration of the
call. Manually keeping the delegates alive is only necessary when the
callbacks are asynchronous. And if they are, GC.KeepAlive wont help
you.

Ok. I see. I had a case where the delegates had to be setup
before their call:

DelegateType someDelegate = new ...;
UnmanagedFuncRegisterCallback(someDelegate);
UnmanagedFuncDoWork();
GC.KeepAlive(someDelegate);

bye
Rob
 
J

Joe Martin

According to everything I've read, in my situation, I don't need to be
concerned about GC because the delegates are not cleaned up <used
loosely> until after the unmanaged call returns and the managed
invocation method goes out of scope.

As mentioned earlier, I can get the unmanaged call to run properly, it
just errors out at the end.

Any suggestions?


Joe Martin
 
W

Willy Denoyette [MVP]

Not sure why you pass the this pointer as fourth argument to explode.
Also note that CLR callbacks are __stdcall by default, are you sure Explode
doesn't expect __cdecl callbacks?

Willy.
 
J

Joe Martin

Okay... Here's more detailed code showing what I'm doing:



public class PkCdl {
public const int FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;

[ DllImport( "Kernel32.dll" )]
public static extern int FormatMessage(int flags, IntPtr source, int
messageId, int languageId, StringBuilder buffer, int size, IntPtr
arguments);

[DllImport("PKCDL.DLL", EntryPoint="pkcrc32",
CallingConvention=CallingConvention.Cdecl)]
public static extern int Crc32(IntPtr buffer, UInt32 size, int oldCrc);

[DllImport("PKCDL.DLL", EntryPoint="pkexplode", CharSet=CharSet.Auto,
CallingConvention=CallingConvention.Cdecl, SetLastError=true)]
public static extern UInt32 Explode(ReadHandler reader, WriteHandler
writer, IntPtr buffer, ref object param);

[DllImport("PKCDL.DLL", EntryPoint="pkimplode", CharSet=CharSet.Auto,
CallingConvention=CallingConvention.Cdecl, SetLastError=true)]
public static extern UInt32 Implode(ReadHandler reader, WriteHandler
writer, IntPtr buffer, ref object param, DCLDataType cmdType,
DCLDictionarySize dsize);

[DllImport("PKCDL.DLL", EntryPoint="pkGetBufferSize",
CallingConvention=CallingConvention.Cdecl)]
public static extern int GetBufferSize(int type);

public static string LastWin32Message() {
int errCode = Marshal.GetLastWin32Error();
StringBuilder buffer = new StringBuilder(256);
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, IntPtr.Zero, errCode, 0,
buffer, buffer.Capacity, IntPtr.Zero);
return buffer.ToString();
}
}



public delegate int ReadHandler(IntPtr buffer, UInt32 size, ref object
param);
public delegate void WriteHandler(IntPtr buffer, UInt32 size, ref object
param);



public UInt32 ReadData(IntPtr buffer, UInt32 size, ref object param) {
try {
byte[] values = archiveReader.ReadBytes(Convert.ToInt32(size));
Marshal.Copy(values, 0, buffer, values.Length);

return Convert.ToUInt32(values.Length);
} catch (Exception ex) {
string msg = ex.Message;
return 0;
}
}

public void WriteData(IntPtr buffer, UInt32 size, ref object param) {
try {
byte[] values = new byte[size];
Marshal.Copy(buffer, values, 0, Convert.ToInt32(size));

outWriter.Write(values); // Write output to where ever

} catch (Exception ex) {
string msg = ex.Message;
}
}



// Actual code invocation


int bufferSize = GetBufferSize(DCLBufferType.PKEXT,
DCLAlgorithmType.DCLIMPLODE);

IntPtr buffer = Marshal.AllocHGlobal(bufferSize); // 12596
try {
object data = this;
ReadHandler rh = new ReadHandler(ReadData);
WriteHandler wh = new WriteHandler(WriteData);
UInt32 status = PkCdl.Explode(rh, wh, buffer, ref data);
} finally {
Marshal.FreeHGlobal(buffer);
}
 
W

Willy Denoyette [MVP]

This PInvoke declaration,

[DllImport("PKCDL.DLL", EntryPoint="pkexplode", CharSet=CharSet.Auto,
CallingConvention=CallingConvention.Cdecl, SetLastError=true)]
public static extern UInt32 Explode(ReadHandler reader, WriteHandler
writer, IntPtr buffer, ref object param);

- this function passes a reference to an object reference as argument (ref
object param ), I don't see why an unmanaged function would ever need a
reference to an GC object.
All Explode functions I have seen so far only took three arguments, could
you post the corresponding function prototype as declared in the PKWARE
header file.
- ReadHandler and WriteHandler function should be declared using __stdcall
calling convention. Are you sure the functions are declared as such in the
PKWARE headers? If this is not the case, the callbacks are called using a
mismatched calling convention (__stdcall for .NET and __cdecl for unmanaged
C), most possibly corrupting the stack at function call return.

Willy.



Joe Martin said:
Okay... Here's more detailed code showing what I'm doing:



public class PkCdl {
public const int FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;

[ DllImport( "Kernel32.dll" )]
public static extern int FormatMessage(int flags, IntPtr source, int
messageId, int languageId, StringBuilder buffer, int size, IntPtr
arguments);

[DllImport("PKCDL.DLL", EntryPoint="pkcrc32",
CallingConvention=CallingConvention.Cdecl)]
public static extern int Crc32(IntPtr buffer, UInt32 size, int oldCrc);

[DllImport("PKCDL.DLL", EntryPoint="pkexplode", CharSet=CharSet.Auto,
CallingConvention=CallingConvention.Cdecl, SetLastError=true)]
public static extern UInt32 Explode(ReadHandler reader, WriteHandler
writer, IntPtr buffer, ref object param);

[DllImport("PKCDL.DLL", EntryPoint="pkimplode", CharSet=CharSet.Auto,
CallingConvention=CallingConvention.Cdecl, SetLastError=true)]
public static extern UInt32 Implode(ReadHandler reader, WriteHandler
writer, IntPtr buffer, ref object param, DCLDataType cmdType,
DCLDictionarySize dsize);

[DllImport("PKCDL.DLL", EntryPoint="pkGetBufferSize",
CallingConvention=CallingConvention.Cdecl)]
public static extern int GetBufferSize(int type);

public static string LastWin32Message() {
int errCode = Marshal.GetLastWin32Error();
StringBuilder buffer = new StringBuilder(256);
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, IntPtr.Zero, errCode, 0,
buffer, buffer.Capacity, IntPtr.Zero);
return buffer.ToString();
}
}



public delegate int ReadHandler(IntPtr buffer, UInt32 size, ref object
param);
public delegate void WriteHandler(IntPtr buffer, UInt32 size, ref object
param);



public UInt32 ReadData(IntPtr buffer, UInt32 size, ref object param) {
try {
byte[] values = archiveReader.ReadBytes(Convert.ToInt32(size));
Marshal.Copy(values, 0, buffer, values.Length);

return Convert.ToUInt32(values.Length);
} catch (Exception ex) {
string msg = ex.Message;
return 0;
}
}

public void WriteData(IntPtr buffer, UInt32 size, ref object param) {
try {
byte[] values = new byte[size];
Marshal.Copy(buffer, values, 0, Convert.ToInt32(size));

outWriter.Write(values); // Write output to where ever

} catch (Exception ex) {
string msg = ex.Message;
}
}



// Actual code invocation


int bufferSize = GetBufferSize(DCLBufferType.PKEXT,
DCLAlgorithmType.DCLIMPLODE);

IntPtr buffer = Marshal.AllocHGlobal(bufferSize); // 12596
try {
object data = this;
ReadHandler rh = new ReadHandler(ReadData);
WriteHandler wh = new WriteHandler(WriteData);
UInt32 status = PkCdl.Explode(rh, wh, buffer, ref data);
} finally {
Marshal.FreeHGlobal(buffer);
}
 
J

Joe Martin

Sure, here you go:


typedef unsigned int(PKCALLBACK * PKReadBuf)(void* buf, unsigned int
size, void* param)

Read buffer call back function definition.


Parameters:
buf Pointer the buffer to fill with data
size The amount of data to read
param User specified call back pointer.

Returns:
The actual amount of data read, must be less than or equal to size.



typedef void(PKCALLBACK * PKWriteBuf)(const void* buf, unsigned int
size, void* param)

Write buffer call back function definition.


Parameters:
buf Pointer to the buffer containing the data to write
size The size of the buffer
param User specified call back pointer.






//----------------------------------

PKCDL_DECL unsigned int PKCALL pkexplode (
PKReadBuf read_buf,
PKWriteBuf write_buf,
void * work_buf,
void * param
)

Function to decompress data using the DCL implode algorithm.


Parameters:
read_buf Pointer to read buffer function.
write_buf Pointer to the write buffer function.
work_buf Pointer to the working buffer.

Returns:
Returns the error code for the function.
See also:
ERROR_CODES for possible return values

//----------------------------


Hope this helps...
 
J

Joe Martin

How would you suggest I go about sync-ing up both the managed delegate
declaration and the unmanaged cdecl of the pkexplode functions?
 
W

Willy Denoyette [MVP]

We are getting closer....
How are PKCALLBACK and PKCALL defined (typedef PKCALL ....), these are
defining the calling convention used.
And the "void* param" is still a mistery to me, you pass it a ref to managed
instance of a class, but this is something that the function is not
expecting for sure.
Worse, it's a reference pointer so the caller can change the reference at
will brrrr....
I suggest you to look at a C sample using that function, and see what get's
passed as param here


Willy.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top