C Style char array returned by an event from a DLL COM
I am using a DLL (COM interop) in Visual Basic 2005 and can not get the correct values returned from an event. The last argument is a variable length array of char that is returned as a pointer to the first element in the array. The only thing I get is the first byte no matter how many characters are to be returned. For exampe, if the returned value is 257 in two bytes, my value I get is 1. This is the event handler:
Private Sub ads_devicenotification(ByRef pTime As TcAdsDll.TimeStamp, ByVal hNotification As Integer, ByVal cbLen As Integer, <MarshalAs(UnmanagedType.LPArray, SizeParamIndex:=128> ByRef pData As Byte) Handles TcComm1.DeviceNotification
Dim Data(cbLen - 1) As Byte
Try For i As Integer = 0 To cbLen - 1Data(i) = Marshal.ReadByte(pData, i)
Next Catch ex As Exception End Try '* This value is equal to "ActualValue AND 255" Dim x As Integer = BitConverter.ToInt16(Data, 0) End Sub
Ответы
Without actually trying it I'm not 100% sure but I think your marshalling flags are wrong. By using SizeParamIndex you are telling it that the 128th parameter contains the size of the array which is not what you wanted. I believe you should instead say SizeParamIndex=2 such that the size is contained in cbLen. I also believe that you should change the pData syntax to be an array instead of a single value. It should be an array of bytes.
Hope this helps,
Michael Taylor - 4/20/06
Все ответы
Without actually trying it I'm not 100% sure but I think your marshalling flags are wrong. By using SizeParamIndex you are telling it that the 128th parameter contains the size of the array which is not what you wanted. I believe you should instead say SizeParamIndex=2 such that the size is contained in cbLen. I also believe that you should change the pData syntax to be an array instead of a single value. It should be an array of bytes.
Hope this helps,
Michael Taylor - 4/20/06
If I change the pData to an array by adding parentheses after it, this is the error I get:
Error 1 Method 'Private Sub ads_devicenotification(ByRef pTime As TcAdsDll.TimeStamp, hNotification As Integer, cbLen As Integer, ByRef pData() As Byte)' cannot handle Event 'Public Event DeviceNotification(ByRef pTime As TcAdsDll.TimeStamp, hNotification As Integer, cbLen As Integer, ByRef pData As Byte)' because they do not have the same signature.
I tried SizeParamIndex=2 with no difference in results. I made it 128 because the array length can vary depending on the type of data being retreived. I used the bitconverter.int16 just for a test of retreiving data that is two bytes in length. If it returns data that is only a single byte, I get the correct value.
It almost seems that the marshalling sees it only defined as a single byte therefore only copies a single byte of the result to managed memory.
It does see it as only a single byte because that is how you declared it. Try specifying the array as an IntPtr instead of a byte (and remove the attributes) and then unmarshal the data using the Marshal class. I believe this is used in some of the more bizarre interop situations where you don't know the length ahead of time. Also if it is a character array then you can also just marshal it as a string. Refer to MSDN for the sample on that.
Michael Taylor - 4/20/06
I seem to be stuck with the declaration of a single byte. If I change it to anything else, I get the same error mentioned previously. That decalaration was generated automatically when I added a reference to the dll in Visual Studio and it generated the file interop.TcAdsDll.dll
I attempted to ildasm the file, edit the decalartion, and then ilasm the edited file. That only seemed to currupt the file and not allow anything to work.
Is there any other way to force a different declaration other than a byte?
What does the unmanaged API look like?
Not sure. I only have the dll, tlb, and header files. No source code. I searched the header file, but it does not define a prototype for the devicenotification event.
If I disassemble the dll, this is how the prototype is defined:
{
.custom instance void [mscorlib]System.Runtime.InteropServices.GuidAttribute::.ctor(string) = ( 01 00 24 30 37 33 45 35 45 46 45 2D 31 36 38 34 // ..$073E5EFE-1684
2D 34 36 38 37 2D 42 44 31 43 2D 36 42 30 43 34 // -4687-BD1C-6B0C4
35 43 37 42 37 44 43 00 00 ) // 5C7B7DC..
.custom instance void [mscorlib]System.Runtime.InteropServices.InterfaceTypeAttribute::.ctor(int16) = ( 01 00 01 00 00 00 )
.method public hidebysig newslot abstract virtual
instance void DeviceNotification([in] valuetype TcAdsDll.TimeStamp& pTime,
[in] int32 hNotification,
[in] int32 cbLen,
[in] uint8& pData) runtime managed internalcall
{
} // end of method _ITcAdsSyncEvents::DeviceNotificationWhat I meant was what was the original signature of DeviceNotification in unmanaged code. If this is a COM interface then the method signature can be found in the IDL file or the H file if it has been generated from the IDL. Otherwise you can use OleView to load the DLL or TLB and get the interface information manually.
Is the DLL you are calling through COM an unmanaged dll?
Michael Taylor - 4/20/06
I used OleView and found the interface information:
HRESULT _stdcall DeviceNotification(
[in] TimeStamp* pTime,
[in] long hNotification,
[in] long cbLen,
[in] unsigned char* pData);
The Dll is a COM object.
It looks to me like the COM wrapper that is generated is incorrect. I think the fact that it is trying to pass an unsigned char pointer is causing the marshaler to think it is a string or something. You might have to modify the generated wrapper using ILDASM and ILASM to get it to work properly. Alternatively it might just be easier to create a managed C++ class that works with the COM object through normal C++ and then use the managed C++ class in your VB code directly. This would resolve all the issues I believe.
Michael Taylor - 4/20/06
I'm running into a dead end. I was finally able to disassemble, edit and reassemble interop.TcAdsDll.dll. This is the two places I found to edit:
.method public virtual instance void Invoke([in] valuetype TcAdsDll.TimeStamp& pTime,
[in] int32 hNotification,
[in] int32 cbLen,
[in] uint8& pData) runtime managed
.method public hidebysig newslot abstract virtual
instance void DeviceNotification([in] valuetype TcAdsDll.TimeStamp& pTime,
[in] int32 hNotification,
[in] int32 cbLen,
[in] uint8& pData) runtime managed internalcallI've tried uiint[]&, uint[] marshal([]), and anything else I could get to work.
If I didn't do the same thing to both places, most of the time I would the event would not even fire. Other times it gave me an error about not being able to call the dll.
Any clues as to how I should edit the above code?
Unfortunately it's not just a matter of changing a type name and rebuilding. Forcing it from a scalar to an array will impact how the value is loaded onto the stack. I don't believe you can use the ldarg routine anymore. However as soon as you start mucky around with the IL then all the address labels change which could impact looping code. Even worse is that if the COM object ever changes you'll have to repeat the adjustments.
Like I said earlier it appears that the marshaller isn't generating the correct code for your signature. Your best bet is probably to create a thunk object that sits between the COM object and your .NET code. C++ is the way to go here. C++ can talk with your COM object using native COM calls. At the same time it can be a managed class that you can then directly instantiate in your .NET code without having to worry about COM interop at all. I would recommend this approach as you don't have to muck around with the IL and it is easier to make changes.
Perhaps someone else on the forums can provide you a better answer. Good luck.
Michael Taylor - 4/21/06
Unfotunately I haven't program in c++ for almost 10 years, so it would involve a learning curve for me.
Thanks for all the help and suggestions! Much appreciated.

