April 8, 2009 at 23:39
filed under Dynamics AX
Tagged Dynamics AX, RPC, System.IO, WinAPI
A very common scenario when doing interfaces with other applications, is to have a directory where one application places files, and the other one picks those up and processes them.
Let’s say some application want to interface with Dynamics AX, one way, from that application to AX. That application will drop files of a specific format, say *.txt, in a directory. You’ll want to have a batch job in AX that checks that directory every 5 minutes for those files. When your batch job finds those files, it will open them and do something with that data, then move them to an other directory, so you don’t process them twice.
Now the first thing you’ll probably think of (well, I did…), is to use the class WinAPI. This class has some useful static methods for checking if folders or files exist, finding files with a filename of a certain format. Just the kind of thing we’re looking for. Your code might look like this:
That’s perfect. It checks a directory, in this cas C:\temp, for files with the extension txt… until you try to run this in batch. Your batch will have the status error, and there will be an entry in the event log saying:
RPC error: RPC exception 1702 occurred in session xxx
That error message is very misleading, but Florian Dittgen has a nice blog entry about this, that explains why this goes wrong: WinAPI methods a static and run on client. In batch, you can not use these client methods.
But hey! There is a class called WinAPIServer, surely this runs on server, right? Correct, but a lot of the methods available in WinAPI are missing in WinAPIServer. You can however extend the WinAPI server class by copying methods from the WinAPI class and modifying them a bit. Let’s try that.
This is what the WinAPI::FindFirstFile() method looks like:
As you can see, this method uses the function FindFirstFileW from the Kernel32 dll located in the folder C:\WINDOWS\system32. Let’s copy this method to WinAPIServer and modify it to look like this:
Three things have changed here:
When you test this in batch (running on server), this will work, and you can do this for all WinAPI methods in the examples above… until you try this on a x64 architecture. You’ll receive a message saying that an error occurred when calling the FindFirstFileW function in the Kernel32 DLL (the exact message slipped my mind).
The problem is described on the Axapta Programming Newsgroup. You can code your way around this problem, but I think the message is clear: don’t use WinAPI.
Instead, use the .NET Framework System.IO namespace (watch out for lowercase/uppercase here), in our case in specific, System.IO.File and System.IO.Directory.
I get this nice code example from Greg Pierce’s blog (modified it a bit):
This will run on client and on server without any problems. To have clean code, you could create your own class, just like WinAPI, but using the System.IO namespace. That way, you can reuse this code and save time.
Conclusion: abandon WinAPI, and use System.IO instead.
Jeroen Doens
on April 14, 2009 at 11:48
Nice research!
Amber
on April 14, 2009 at 18:01
How do you then move them into another directory to avoid processing the same files the next time the batch runs?
Thanks!
Klaas Deforche
on April 14, 2009 at 18:18
You can use this method:
{
#File
Set permissionSet;
;
permissionSet = new Set(Types::Class);
permissionSet.add(new FileIOPermission(fileName,#io_write));
permissionSet.add(new InteropPermission(InteropKind::ClrInterop));
CodeAccessPermission::assertMultiple(permissionSet);
System.IO.File::Move(fileName, newFileName);
CodeAccessPermission::revertAssert();
}
Let me know if it works or if you need more help.
Rob
on October 9, 2009 at 10:24
Hi there!
I’m creating an interface between Dax and another app just the way described by using textfiles in a folder. Everything works fine, only I have one problem: the exportscript from app1 (Oracle app) and the DAX importscript are executed asynchronic. Things go wrong when app 1 hasn’t finished writing the exportfile and Dax already starts importing the file.
I there a way to determine the filesize? So I can import only when the filesize > 0 kb. I tried WinAPI::filesize but it always returns 0.
Thanx!
Rob
Klaas Deforche
on October 9, 2009 at 14:39
Hi Rob,
I’m familiar with the problem, but it’s a tricky one.
If you are looking to determine the file size, this should return the file size in bytes:
I don’t know if that’s a good way to check if the process has finished writing the file, because the size could be greater than 0, even if the process hasn’t finished writing (for example when it is appending information to the file).
Here’s a few alternative ways to deal with this:
1. The Oracle app create the file in a temporary directory, and when it’s finished, it moves the file to the directory you are monitoring in AX.
2. Or the Oracle app creates the file with a filename or extension you are not monitoring, and renames it to the correct file name when it’s finished (my favourite).
3. Or the Oracle app create a second file when it has finished writing, with the same filename, but a different extension (eg: filename.csv and filename.done), and you only read files that have a corresponding .done file (check this in AX).
Hope this helps!
Michael
on March 24, 2010 at 16:02
When using the code in #3 above I get the following error:
ClrObject static method invocation error.
Anyone have ideas on what to check as to the cause of this error?
Klaas Deforche
on March 24, 2010 at 16:34
Hi Michael,
I’ve had this problem as well, especially when executing code in batch.
It probably has something to do with escaping the slashes in your string.
Try this:
Also, using FileName as datatype for you parameters in stead of str might do the trick.
Let us know what works for you.
Michael
on March 24, 2010 at 17:37
Thanks for the quick response. I ended up adding a try/catch to pull in the modt recent message and it told me that the file was still in use which pointed me to the fact that I had not closed the file (i.e. set it to NULL) before trying to move it. So once I did that it worked fine.
Thanks so much for this blog post – it saved me huge amounts of time.
Jan VO
on April 28, 2010 at 15:30
Thanks for the tips regarding escaping slashes, it works .
John
on May 17, 2010 at 17:33
I am using System.IO.File::Exists method to check a file and if it exists create a new file with a different name. Problem is when i call this code using a batch server getting error:
permissionSet = new Set(Types::Class);
permissionSet.add(new FileIOPermission(successDirectory + filename, #io_read));
CodeAccessPermission::assertMultiple(permissionSet);
Request for the permission of type ‘InteropPermission’ failed.
(S)\Classes\InteropPermission\demand
(S)\Classes\CLRInterop\staticInvoke
(S)\Classes\SIG_AIInboundController\run – line 190
(S)\Classes\BatchRun\runJobStatic – line 62
Can you think of any idea why this is happening
//w_OrigfilenameFilename = filename;
if (!System.IO.File::Exists(successDirectory + filename))
Klaas Deforche
on May 17, 2010 at 17:48
Hi John,
I believe the problem is that you didn’t add the InteropPermission to your set of permissions.
Please add the following line (before CodeAccessPermission::assertMultiple()) and try again:
You need the ClrInterop permission to use this function:
System.IO.File::Exists().
Hope this helps.