DllImport in FSI - f#

Need to port following C# code to F#:
[DllImport("libc", SetLastError = true)]
private static extern int chmod(string pathname, int mode);
What is wrong in following code? Tried it in FSI on Mac and getting error FS0193: internal error: Method 'FSI_0020+Libc.chmod' does not have a method body.
module Libc =
open System.Runtime.InteropServices
[<DllImport("libc", SetLastError = true)>]
extern int chmod(string pathname, int mode)
[<DllImport ("libc", EntryPoint = "chmod", SetLastError = true)>]
extern int sys_chmod (string _path, uint32 _mode)
let chmodCarry mode path = sys_chmod(path, mode)
And what to do with private and static in this case? I would make sys_chmod private and expose the carried chmod function only.
Or is there any other more portable way to make a file executable?

Related

global variables in a shared library?

I'm trying to add global variables to a shared library. Consider this example, in library libutils, globals.vala contains
namespace Libutils {
public static GLib.Object yorgi_obj;
public static int yorgi_int;
}
I see externs are properly generated in .c & .h files
libutils:globals.c
extern GObject* libutils_yorgi_obj;
GObject* libutils_yorgi_obj = NULL;
extern gint libutils_yorgi_int;
gint libutils_yorgi_int = 0;
libutils:globals.h
extern GObject* libutils_yorgi_obj;
extern gint libutils_yorgi_int;
My test program, tester.vala
int main(string[] args) {
Libutils.yorgi_int = 1;
All is well in compiling tester.vala. But linking to Libutils.so I get
...tester.vala:614: undefined reference to `libutils_yorgi_int'
Do I have to specify some special vala attributes or pass something to the linker?

Calling a dll created in VB6 from F#

I am trying to call a dll file created in vb6 from F#. I have written the following dll.
Public Function AddTwoNumbers(ByVal a As Integer, ByVal b As Integer)
AddTwoNumbers = a + b
End Function
Now I want to call it in my F# program, I wrote this code
open System.Runtime.InteropServices
module InteropWithNative =
[<DllImport(#"C:\add", CallingConvention = CallingConvention.Cdecl)>]
void AddTwoNumbers(int, int)
InteropWithNative.AddTwoNumbers(3,4)
let result = AddTwoNumbers_ 2.0 3.0
It gives me errors and doesn't recognize the function.
A working interop example with an EntryPoint
open System.Runtime.InteropServices // for DllImport
module KernelInterop =
[<DllImport("kernel32.dll", EntryPoint="Beep")>]
extern void Beep( int frequency, int duration )
KernelInterop.Beep // val Beep : int * int -> unit
KernelInterop.Beep(440, 1000)

Define attributes in method parameters in F#

I'm doing a DLL import in F# and then passing some parameters for a method.
[<DllImport("winspool.Drv", EntryPoint = "OpenPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)>]
extern bool OpenPrinter([<MarshalAs(UnmanagedType.LPStr)>] szPrinter)
A parameter with attributes must also receive a name is the compilers answer.
This method must have a MarshalAs attribute before it, like we have in C#
[DllImport("winspool.Drv", EntryPoint = "OpenPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
private static extern bool OpenPrinter([MarshalAs(UnmanagedType.LPStr)] string szPrinter);
The parameter of OpenPrinter is a string called szPrinter, but it have an attribute [MarshalAs(UnmanagedType.LPStr])
F# doesn't accept this like C#.
How to define attributes inside method parameters?
It's because your extern function is expecting a type for the parameter:
extern bool OpenPrinter([<MarshalAs(UnmanagedType.LPStr)>] string szPrinter)

MSBuild fails to see environment variable that is set from build workflow

This is related to TFS 2010 Build System. Within our build workflow we set a couple of environment variables using the SetEnvironmentVariable method of System.Environment class. I have confirmed that this environment variable gets set properly on the build server and that it gets set as a system wide environment variable.
The problem is that when MSBuild gets invoked within this WF and it compiles the solutions, our post build events which tries to read this environment variable fail as they are unable to see this environment variable.
Is there a way to force MSBuild to reload environment variables or force a running WF to reload environment variables? My suspicion is that even though WF creates this variable, it does not refresh its Environment state and hence can not see the variable. Further since the WF invokes MSBuild, it passes the same environment state to MSBuild which does ont contain this variable.
Update
Stick the following code in Visual Studio and run it. The delay to SendMessageTimeOut is 10 second so be patient.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.ComponentModel;
namespace EnvironmentVarTest
{
class Program
{
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool
SendMessageTimeout(
IntPtr hWnd,
int Msg,
int wParam,
string lParam,
int fuFlags,
int uTimeout,
out int lpdwResult
);
public const int HWND_BROADCAST = 0xffff;
public const int WM_SETTINGCHANGE = 0x001A;
public const int SMTO_NORMAL = 0x0000;
public const int SMTO_BLOCK = 0x0001;
public const int SMTO_ABORTIFHUNG = 0x0002;
public const int SMTO_NOTIMEOUTIFNOTHUNG = 0x0008;
static void Main(string[] args)
{
Program p = new Program();
string environmentVariableValue = DateTime.Now.ToLongTimeString().Replace(":", String.Empty);
Console.WriteLine("On the CMD window that opens up after about 10 seconds, if you type %samplevar% and hit Enter, you should see: " + environmentVariableValue);
p.SetEnvironmentVariable(environmentVariableValue);
RefreshProcessVars();
p.ReadEnvironmentVariable();
p.StartCMD();
Console.ReadLine();
}
void SetEnvironmentVariable(string value)
{
System.Environment.SetEnvironmentVariable("samplevar", value, EnvironmentVariableTarget.Machine);
}
static void RefreshProcessVars()
{
int result;
bool callresult = SendMessageTimeout(
(System.IntPtr)HWND_BROADCAST,
WM_SETTINGCHANGE,
0,
"Environment",
SMTO_BLOCK | SMTO_ABORTIFHUNG | SMTO_NOTIMEOUTIFNOTHUNG,
10000,
out result);
if (!callresult || result == 0)
{
int lasterror = Marshal.GetLastWin32Error();
Win32Exception winex = new Win32Exception(lasterror);
Console.WriteLine("Exception happened while calling SendMessageTimeOut. The exception message is " + winex.Message);
}
}
void ReadEnvironmentVariable()
{
var x = System.Environment.GetEnvironmentVariable("samplevar", EnvironmentVariableTarget.Machine);
}
void StartCMD()
{
Process.Start("cmd.exe");
}
}
}
Unfortunately, because the MSBuild process is passed a cached version of the environment variables when it's started up, the command-line functionality of that process will not be able to see the updated values. The best bet you've got, in my opinion, is to either change that variable in the post-build event or store the value in a medium that you can read from the post-build event.
Update
Okay, so the statement below (which can be found here) I think explains where the environment variables are coming from and why you're not getting an updated version in MSBuild.
By default, a child process inherits the environment variables of its parent process.
So, two can play at that game, let's just broadcast that a change has occurred and see if that will take care of it for us. Below is the code that should do that for you.
[DllImport("user32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return:MarshalAs(UnmanagedType.Bool)]
public static extern bool
SendMessageTimeout(
IntPtr hWnd,
int Msg,
int wParam,
string lParam,
int fuFlags,
int uTimeout,
out int lpdwResult
);
public const int HWND_BROADCAST = 0xffff;
public const int WM_SETTINGCHANGE = 0x001A;
public const int SMTO_NORMAL = 0x0000;
public const int SMTO_BLOCK = 0x0001;
public const int SMTO_ABORTIFHUNG = 0x0002;
public const int SMTO_NOTIMEOUTIFNOTHUNG = 0x0008;
int result;
SendMessageTimeout(
(System.IntPtr)HWND_BROADCAST,
WM_SETTINGCHANGE,
0,
"Environment",
SMTO_BLOCK | SMTO_ABORTIFHUNG | SMTO_NOTIMEOUTIFNOTHUNG,
SomeTimeoutValue,
out result);

Virtual printer port monitor installing

I have a port monitor dll, that I instaling by call AddMonitor function of the spooler. But when I want uninstal this monitor, the DeleteMonitor function return errorcode 3008 - "The specified print monitor is currently in use". How I can free my monitor dll?
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
private class MONITOR_INFO_2
{
[MarshalAs(UnmanagedType.LPStr)]
public string pName;
[MarshalAs(UnmanagedType.LPStr)]
public string pEnvironment;
[MarshalAs(UnmanagedType.LPStr)]
public string pDLLName;
}
[DllImport("winspool.Drv", EntryPoint = "AddMonitorA", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
private static extern bool AddMonitor(
[MarshalAs(UnmanagedType.LPStr)] string Name,
Int32 Level,
[In, MarshalAs(UnmanagedType.LPStruct)] MONITOR_INFO_2 mi2);
[DllImport("winspool.Drv", EntryPoint = "DeleteMonitorA", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
private static extern bool DeleteMonitor(
[MarshalAs(UnmanagedType.LPStr)] string pNullServerName,
[MarshalAs(UnmanagedType.LPStr)] string pNullEnvironment,
[MarshalAs(UnmanagedType.LPStr)] string MonitorName);
private unsafe void InstallMonitor(string monitorName, string dllName)
{
MONITOR_INFO_2 mi2 = new MONITOR_INFO_2();
mi2.pName = monitorName;
mi2.pEnvironment = null;
mi2.pDLLName = dllName;
try
{
bool bRet = AddMonitor(null, 2, mi2);
if (!bRet)
throw new Win32Exception(Marshal.GetLastWin32Error());
}
catch (Exception e)
{
if (!DeleteMonitor(null, null, monitorName))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
bRet = AddMonitor(null, 2, mi2);
if (!bRet)
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
You will not be able to Delete a Port Monitor via the DeleteMonitor call if there is one or more printer objects currently using a port of that type.
This leaves you with several options:
Swap the port of all affected printers to another port. (Best to use something like LPT1: since its always there).
Delete all printers using the port.
Stop the spooler service and remove the appropriate entries from registry (HKLM\SYSTEM\CurrentControlSet\Control\Print\Monitors) then restart the spooler. This will leave the affected printers there but they will be unusable.

Resources